diff --git a/docs/docs.go b/docs/docs.go index 12f27b3d..e8b29608 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -62,31 +62,31 @@ const docTemplate = `{ "200": { "description": "Successfully retrieved Bynik data", "schema": { - "$ref": "#/definitions/peserta.PesertaResponse" + "$ref": "#/definitions/api-service_internal_models_vclaim_peserta.PesertaResponse" } }, "400": { "description": "Bad request - invalid parameters", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" } }, "401": { "description": "Unauthorized - invalid API credentials", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" } }, "404": { "description": "Not found - Bynik not found", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" } } } @@ -130,431 +130,31 @@ const docTemplate = `{ "200": { "description": "Successfully retrieved Bynokartu data", "schema": { - "$ref": "#/definitions/peserta.PesertaResponse" + "$ref": "#/definitions/api-service_internal_models_vclaim_peserta.PesertaResponse" } }, "400": { "description": "Bad request - invalid parameters", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" } }, "401": { "description": "Unauthorized - invalid API credentials", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" } }, "404": { "description": "Not found - Bynokartu not found", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - } - }, - "/Rujukan/:norujukan": { - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Update existing Rujukan in BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Update existing Rujukan", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "Rujukan update data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/rujukan.RujukanRequest" - } - } - ], - "responses": { - "200": { - "description": "Successfully updated Rujukan", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Rujukan not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict - update conflict occurred", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - }, - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Create new Rujukan in BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Create new Rujukan", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "Rujukan data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/rujukan.RujukanRequest" - } - } - ], - "responses": { - "201": { - "description": "Successfully created Rujukan", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - }, - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete existing Rujukan from BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Delete existing Rujukan", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - } - ], - "responses": { - "200": { - "description": "Successfully deleted Rujukan", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Rujukan not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - } - }, - "/Rujukanbalik/:norujukan": { - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Update existing Rujukanbalik in BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Update existing Rujukanbalik", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "Rujukanbalik update data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/rujukan.RujukanRequest" - } - } - ], - "responses": { - "200": { - "description": "Successfully updated Rujukanbalik", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Rujukanbalik not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict - update conflict occurred", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - }, - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Create new Rujukanbalik in BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Create new Rujukanbalik", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "Rujukanbalik data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/rujukan.RujukanRequest" - } - } - ], - "responses": { - "201": { - "description": "Successfully created Rujukanbalik", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - }, - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete existing Rujukanbalik from BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Delete existing Rujukanbalik", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - } - ], - "responses": { - "200": { - "description": "Successfully deleted Rujukanbalik", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Rujukanbalik not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" } } } @@ -580,7 +180,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.LoginRequest" + "$ref": "#/definitions/api-service_internal_models_auth.LoginRequest" } } ], @@ -588,7 +188,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.TokenResponse" + "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" } }, "400": { @@ -631,7 +231,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.User" + "$ref": "#/definitions/api-service_internal_models_auth.User" } }, "401": { @@ -677,7 +277,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.TokenResponse" + "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" } }, "400": { @@ -750,6 +350,43 @@ const docTemplate = `{ } } }, + "/api/v1/listkiosks/stats": { + "get": { + "description": "Returns comprehensive statistics about listkiosk data", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Listkiosk" + ], + "summary": "Get listkiosk statistics", + "parameters": [ + { + "type": "string", + "description": "Filter statistics by status", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Statistics data", + "schema": { + "$ref": "#/definitions/api-service_internal_models.AggregateData" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + } + } + } + } + }, "/api/v1/retribusi/{id}": { "get": { "description": "Returns a single retribusi by ID", @@ -776,25 +413,25 @@ const docTemplate = `{ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/retribusi.RetribusiGetByIDResponse" + "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetByIDResponse" } }, "400": { "description": "Invalid ID format", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } @@ -825,7 +462,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/retribusi.RetribusiUpdateRequest" + "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiUpdateRequest" } } ], @@ -833,25 +470,25 @@ const docTemplate = `{ "200": { "description": "Retribusi updated successfully", "schema": { - "$ref": "#/definitions/retribusi.RetribusiUpdateResponse" + "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiUpdateResponse" } }, "400": { "description": "Bad request or validation error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } @@ -881,25 +518,25 @@ const docTemplate = `{ "200": { "description": "Retribusi deleted successfully", "schema": { - "$ref": "#/definitions/retribusi.RetribusiDeleteResponse" + "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiDeleteResponse" } }, "400": { "description": "Invalid ID format", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } @@ -969,19 +606,19 @@ const docTemplate = `{ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/retribusi.RetribusiGetResponse" + "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse" } }, "400": { "description": "Bad request", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } @@ -1005,7 +642,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/retribusi.RetribusiCreateRequest" + "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiCreateRequest" } } ], @@ -1013,19 +650,19 @@ const docTemplate = `{ "201": { "description": "Retribusi created successfully", "schema": { - "$ref": "#/definitions/retribusi.RetribusiCreateResponse" + "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiCreateResponse" } }, "400": { "description": "Bad request or validation error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } @@ -1082,19 +719,19 @@ const docTemplate = `{ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/retribusi.RetribusiGetResponse" + "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse" } }, "400": { "description": "Bad request", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } @@ -1125,13 +762,13 @@ const docTemplate = `{ "200": { "description": "Statistics data", "schema": { - "$ref": "#/definitions/models.AggregateData" + "$ref": "#/definitions/api-service_internal_models.AggregateData" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } @@ -1157,7 +794,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.LoginRequest" + "$ref": "#/definitions/api-service_internal_models_auth.LoginRequest" } } ], @@ -1165,7 +802,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.TokenResponse" + "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" } }, "400": { @@ -1220,7 +857,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.TokenResponse" + "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" } }, "400": { @@ -1235,14 +872,9 @@ const docTemplate = `{ } } }, - "/bynokartu/:nokartu": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get rujukan by card number", + "/kiosk": { + "post": { + "description": "Creates a new listkiosk record", "consumes": [ "application/json" ], @@ -1250,67 +882,45 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Rujukan" + "Listkiosk" ], - "summary": "Get Bynokartu data", + "summary": "Create listkiosk", "parameters": [ { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "type": "string", - "example": "\"example_value\"", - "description": "nokartu", - "name": "nokartu", - "in": "path", - "required": true + "description": "Listkiosk creation request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api-service_internal_models_kiosk.ListkioskCreateRequest" + } } ], "responses": { - "200": { - "description": "Successfully retrieved Bynokartu data", + "201": { + "description": "Listkiosk created successfully", "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" + "$ref": "#/definitions/api-service_internal_models_kiosk.ListkioskCreateResponse" } }, "400": { - "description": "Bad request - invalid parameters", + "description": "Bad request or validation error", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Bynokartu not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } } }, - "/bynorujukan/:norujukan": { + "/kiosk/listkiosk": { "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get rujukan by nomor rujukan", + "description": "Returns a paginated list of listkiosks with optional summary statistics", "consumes": [ "application/json" ], @@ -1318,54 +928,168 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Rujukan" + "Listkiosk" ], - "summary": "Get Bynorujukan data", + "summary": "Get listkiosk with pagination and optional aggregation", "parameters": [ { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" + "type": "integer", + "default": 10, + "description": "Limit (max 100)", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "default": 0, + "description": "Offset", + "name": "offset", + "in": "query" + }, + { + "type": "boolean", + "default": false, + "description": "Include aggregation summary", + "name": "include_summary", + "in": "query" }, { "type": "string", - "example": "\"example_value\"", - "description": "norujukan", - "name": "norujukan", + "description": "Filter by status", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "Search in multiple fields", + "name": "search", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success response", + "schema": { + "$ref": "#/definitions/api-service_internal_models_kiosk.ListkioskGetResponse" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + } + } + } + } + }, + "/kiosk/{id}": { + "put": { + "description": "Updates an existing listkiosk record", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Listkiosk" + ], + "summary": "Update listkiosk", + "parameters": [ + { + "type": "integer", + "description": "Kiosk ID (integer)", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Listkiosk update request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api-service_internal_models_kiosk.ListkioskUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "Listkiosk updated successfully", + "schema": { + "$ref": "#/definitions/api-service_internal_models_kiosk.ListkioskUpdateResponse" + } + }, + "400": { + "description": "Bad request or validation error", + "schema": { + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + } + }, + "404": { + "description": "Listkiosk not found", + "schema": { + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + } + } + } + }, + "delete": { + "description": "Soft deletes a listkiosk by setting status to 'deleted'", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Listkiosk" + ], + "summary": "Delete listkiosk", + "parameters": [ + { + "type": "integer", + "description": "Kiosk ID (integer)", + "name": "id", "in": "path", "required": true } ], "responses": { "200": { - "description": "Successfully retrieved Bynorujukan data", + "description": "Listkiosk deleted successfully", "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" + "$ref": "#/definitions/api-service_internal_models_kiosk.ListkioskDeleteResponse" } }, "400": { - "description": "Bad request - invalid parameters", + "description": "Invalid ID format", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "404": { - "description": "Not found - Bynorujukan not found", + "description": "Listkiosk not found", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } @@ -1373,7 +1097,7 @@ const docTemplate = `{ } }, "definitions": { - "models.AggregateData": { + "api-service_internal_models.AggregateData": { "type": "object", "properties": { "by_dinas": { @@ -1414,7 +1138,7 @@ const docTemplate = `{ } } }, - "models.ErrorResponse": { + "api-service_internal_models.ErrorResponse": { "type": "object", "properties": { "code": { @@ -1431,7 +1155,7 @@ const docTemplate = `{ } } }, - "models.ErrorResponseBpjs": { + "api-service_internal_models.ErrorResponseBpjs": { "type": "object", "properties": { "code": { @@ -1452,22 +1176,7 @@ const docTemplate = `{ } } }, - "models.LoginRequest": { - "type": "object", - "required": [ - "password", - "username" - ], - "properties": { - "password": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "models.MetaResponse": { + "api-service_internal_models.MetaResponse": { "type": "object", "properties": { "current_page": { @@ -1493,7 +1202,7 @@ const docTemplate = `{ } } }, - "models.NullableInt32": { + "api-service_internal_models.NullableInt32": { "type": "object", "properties": { "int32": { @@ -1504,29 +1213,22 @@ const docTemplate = `{ } } }, - "models.NullableString": { + "api-service_internal_models_auth.LoginRequest": { "type": "object", + "required": [ + "password", + "username" + ], "properties": { - "string": { + "password": { "type": "string" }, - "valid": { - "type": "boolean" + "username": { + "type": "string" } } }, - "models.NullableTime": { - "type": "object", - "properties": { - "time": { - "type": "string" - }, - "valid": { - "type": "boolean" - } - } - }, - "models.TokenResponse": { + "api-service_internal_models_auth.TokenResponse": { "type": "object", "properties": { "access_token": { @@ -1540,7 +1242,7 @@ const docTemplate = `{ } } }, - "models.User": { + "api-service_internal_models_auth.User": { "type": "object", "properties": { "email": { @@ -1557,7 +1259,480 @@ const docTemplate = `{ } } }, - "peserta.PesertaData": { + "api-service_internal_models_kiosk.Listkiosk": { + "type": "object", + "properties": { + "active": { + "$ref": "#/definitions/sql.NullBool" + }, + "ds_sd_location": { + "$ref": "#/definitions/sql.NullString" + }, + "fk_ref_healthcare_type_id": { + "$ref": "#/definitions/api-service_internal_models.NullableInt32" + }, + "fk_ref_service_type_id": { + "$ref": "#/definitions/api-service_internal_models.NullableInt32" + }, + "fk_sd_location_id": { + "$ref": "#/definitions/sql.NullString" + }, + "icon": { + "$ref": "#/definitions/sql.NullString" + }, + "id": { + "type": "integer" + }, + "name": { + "$ref": "#/definitions/sql.NullString" + }, + "url": { + "$ref": "#/definitions/sql.NullString" + } + } + }, + "api-service_internal_models_kiosk.ListkioskCreateRequest": { + "type": "object", + "properties": { + "active": { + "type": "boolean", + "enum": [ + true, + false + ] + }, + "ds_sd_location": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "fk_ref_healthcare_type_id": { + "type": "integer", + "minimum": 1 + }, + "fk_ref_service_type_id": { + "type": "integer", + "minimum": 1 + }, + "fk_sd_location_id": { + "type": "string", + "minLength": 1 + }, + "icon": { + "type": "string", + "maxLength": 20, + "minLength": 1 + }, + "name": { + "type": "string", + "maxLength": 20, + "minLength": 1 + }, + "url": { + "type": "string", + "maxLength": 255, + "minLength": 1 + } + } + }, + "api-service_internal_models_kiosk.ListkioskCreateResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/api-service_internal_models_kiosk.Listkiosk" + }, + "message": { + "type": "string" + } + } + }, + "api-service_internal_models_kiosk.ListkioskDeleteResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "api-service_internal_models_kiosk.ListkioskGetResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api-service_internal_models_kiosk.Listkiosk" + } + }, + "message": { + "type": "string" + }, + "meta": { + "$ref": "#/definitions/api-service_internal_models.MetaResponse" + }, + "summary": { + "$ref": "#/definitions/api-service_internal_models.AggregateData" + } + } + }, + "api-service_internal_models_kiosk.ListkioskUpdateRequest": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "active": { + "type": "boolean", + "enum": [ + true, + false + ] + }, + "ds_sd_location": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "fk_ref_healthcare_type_id": { + "type": "integer", + "minimum": 1 + }, + "fk_ref_service_type_id": { + "type": "integer", + "minimum": 1 + }, + "fk_sd_location_id": { + "type": "string", + "minLength": 1 + }, + "icon": { + "type": "string", + "maxLength": 20, + "minLength": 1 + }, + "id": { + "type": "integer", + "minimum": 1 + }, + "name": { + "type": "string", + "maxLength": 20, + "minLength": 1 + }, + "url": { + "type": "string", + "maxLength": 255, + "minLength": 1 + } + } + }, + "api-service_internal_models_kiosk.ListkioskUpdateResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/api-service_internal_models_kiosk.Listkiosk" + }, + "message": { + "type": "string" + } + } + }, + "api-service_internal_models_retribusi.Retribusi": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "date_created": { + "$ref": "#/definitions/sql.NullTime" + }, + "date_updated": { + "$ref": "#/definitions/sql.NullTime" + }, + "deletedAt": { + "$ref": "#/definitions/gorm.DeletedAt" + }, + "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/sql.NullInt32" + }, + "status": { + "type": "string" + }, + "tarif": { + "$ref": "#/definitions/sql.NullString" + }, + "tarif_overtime": { + "$ref": "#/definitions/sql.NullString" + }, + "updatedAt": { + "type": "string" + }, + "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" + } + } + }, + "api-service_internal_models_vclaim_peserta.PesertaData": { "type": "object", "properties": { "cob": { @@ -1678,11 +1853,11 @@ const docTemplate = `{ } } }, - "peserta.PesertaResponse": { + "api-service_internal_models_vclaim_peserta.PesertaResponse": { "type": "object", "properties": { "data": { - "$ref": "#/definitions/peserta.PesertaData" + "$ref": "#/definitions/api-service_internal_models_vclaim_peserta.PesertaData" }, "message": { "type": "string" @@ -1699,520 +1874,64 @@ const docTemplate = `{ } } }, - "retribusi.Retribusi": { + "gorm.DeletedAt": { "type": "object", "properties": { - "date_created": { - "$ref": "#/definitions/models.NullableTime" - }, - "date_updated": { - "$ref": "#/definitions/models.NullableTime" - }, - "dinas": { - "$ref": "#/definitions/models.NullableString" - }, - "id": { + "time": { "type": "string" }, - "jenis": { - "$ref": "#/definitions/models.NullableString" - }, - "kelompok_obyek": { - "$ref": "#/definitions/models.NullableString" - }, - "kode_tarif": { - "$ref": "#/definitions/models.NullableString" - }, - "pelayanan": { - "$ref": "#/definitions/models.NullableString" - }, - "rekening_denda": { - "$ref": "#/definitions/models.NullableString" - }, - "rekening_pokok": { - "$ref": "#/definitions/models.NullableString" - }, - "satuan": { - "$ref": "#/definitions/models.NullableString" - }, - "satuan_overtime": { - "$ref": "#/definitions/models.NullableString" - }, - "sort": { - "$ref": "#/definitions/models.NullableInt32" - }, - "status": { - "type": "string" - }, - "tarif": { - "$ref": "#/definitions/models.NullableString" - }, - "tarif_overtime": { - "$ref": "#/definitions/models.NullableString" - }, - "uraian_1": { - "$ref": "#/definitions/models.NullableString" - }, - "uraian_2": { - "$ref": "#/definitions/models.NullableString" - }, - "uraian_3": { - "$ref": "#/definitions/models.NullableString" - }, - "user_created": { - "$ref": "#/definitions/models.NullableString" - }, - "user_updated": { - "$ref": "#/definitions/models.NullableString" + "valid": { + "description": "Valid is true if Time is not NULL", + "type": "boolean" } } }, - "retribusi.RetribusiCreateRequest": { + "sql.NullBool": { "type": "object", - "required": [ - "status" - ], "properties": { - "dinas": { - "type": "string", - "maxLength": 255, - "minLength": 1 + "bool": { + "type": "boolean" }, - "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" + "valid": { + "description": "Valid is true if Bool is not NULL", + "type": "boolean" } } }, - "retribusi.RetribusiCreateResponse": { + "sql.NullInt32": { "type": "object", "properties": { - "data": { - "$ref": "#/definitions/retribusi.Retribusi" + "int32": { + "type": "integer", + "format": "int32" }, - "message": { - "type": "string" + "valid": { + "description": "Valid is true if Int32 is not NULL", + "type": "boolean" } } }, - "retribusi.RetribusiDeleteResponse": { + "sql.NullString": { "type": "object", "properties": { - "id": { + "string": { "type": "string" }, - "message": { - "type": "string" + "valid": { + "description": "Valid is true if String is not NULL", + "type": "boolean" } } }, - "retribusi.RetribusiGetByIDResponse": { + "sql.NullTime": { "type": "object", "properties": { - "data": { - "$ref": "#/definitions/retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "retribusi.RetribusiGetResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/retribusi.Retribusi" - } - }, - "message": { + "time": { "type": "string" }, - "meta": { - "$ref": "#/definitions/models.MetaResponse" - }, - "summary": { - "$ref": "#/definitions/models.AggregateData" - } - } - }, - "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" - } - } - }, - "retribusi.RetribusiUpdateResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "rujukan.DataPeserta": { - "type": "object", - "properties": { - "cob": { - "type": "object", - "properties": { - "nmAsuransi": {}, - "noAsuransi": {}, - "tglTAT": {}, - "tglTMT": {} - } - }, - "hakKelas": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "informasi": { - "type": "object", - "properties": { - "dinsos": {}, - "noSKTM": {}, - "prolanisPRB": {} - } - }, - "jenisPeserta": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "mr": { - "type": "object", - "properties": { - "noMR": { - "type": "string" - }, - "noTelepon": {} - } - }, - "nama": { - "type": "string" - }, - "nik": { - "type": "string" - }, - "noKartu": { - "type": "string" - }, - "pisa": { - "type": "string" - }, - "provUmum": { - "type": "object", - "properties": { - "kdProvider": { - "type": "string" - }, - "nmProvider": { - "type": "string" - } - } - }, - "sex": { - "type": "string" - }, - "statusPeserta": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "tglCetakKartu": { - "type": "string" - }, - "tglLahir": { - "type": "string" - }, - "tglTAT": { - "type": "string" - }, - "tglTMT": { - "type": "string" - }, - "umur": { - "type": "object", - "properties": { - "umurSaatPelayanan": { - "type": "string" - }, - "umurSekarang": { - "type": "string" - } - } - } - } - }, - "rujukan.DiagnosaData": { - "type": "object", - "properties": { - "kode": { - "type": "string" - }, - "nama": { - "type": "string" - } - } - }, - "rujukan.PelayananData": { - "type": "object", - "properties": { - "kode": { - "type": "string" - }, - "nama": { - "type": "string" - } - } - }, - "rujukan.PoliRujukanData": { - "type": "object", - "properties": { - "kode": { - "type": "string" - }, - "nama": { - "type": "string" - } - } - }, - "rujukan.ProvPerujukData": { - "type": "object", - "properties": { - "kode": { - "type": "string" - }, - "nama": { - "type": "string" - } - } - }, - "rujukan.RujukanData": { - "type": "object", - "properties": { - "diagnosa": { - "$ref": "#/definitions/rujukan.DiagnosaData" - }, - "keluhan": { - "type": "string" - }, - "noKunjungan": { - "type": "string" - }, - "pelayanan": { - "$ref": "#/definitions/rujukan.PelayananData" - }, - "peserta": { - "$ref": "#/definitions/rujukan.DataPeserta" - }, - "poliRujukan": { - "$ref": "#/definitions/rujukan.PoliRujukanData" - }, - "provPerujuk": { - "$ref": "#/definitions/rujukan.ProvPerujukData" - }, - "tglKunjungan": { - "type": "string" - } - } - }, - "rujukan.RujukanRequest": { - "type": "object", - "required": [ - "noRujukan" - ], - "properties": { - "noKartu": { - "type": "string" - }, - "noRujukan": { - "type": "string" - }, - "request_id": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - } - }, - "rujukan.RujukanResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/rujukan.RujukanData" - }, - "list": { - "type": "array", - "items": { - "$ref": "#/definitions/rujukan.RujukanData" - } - }, - "message": { - "type": "string" - }, - "metaData": {}, - "request_id": { - "type": "string" - }, - "status": { - "type": "string" - }, - "timestamp": { - "type": "string" + "valid": { + "description": "Valid is true if Time is not NULL", + "type": "boolean" } } } diff --git a/docs/swagger.json b/docs/swagger.json index 47b3b1cc..6d740f61 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -60,31 +60,31 @@ "200": { "description": "Successfully retrieved Bynik data", "schema": { - "$ref": "#/definitions/peserta.PesertaResponse" + "$ref": "#/definitions/api-service_internal_models_vclaim_peserta.PesertaResponse" } }, "400": { "description": "Bad request - invalid parameters", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" } }, "401": { "description": "Unauthorized - invalid API credentials", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" } }, "404": { "description": "Not found - Bynik not found", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" } } } @@ -128,431 +128,31 @@ "200": { "description": "Successfully retrieved Bynokartu data", "schema": { - "$ref": "#/definitions/peserta.PesertaResponse" + "$ref": "#/definitions/api-service_internal_models_vclaim_peserta.PesertaResponse" } }, "400": { "description": "Bad request - invalid parameters", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" } }, "401": { "description": "Unauthorized - invalid API credentials", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" } }, "404": { "description": "Not found - Bynokartu not found", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - } - }, - "/Rujukan/:norujukan": { - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Update existing Rujukan in BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Update existing Rujukan", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "Rujukan update data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/rujukan.RujukanRequest" - } - } - ], - "responses": { - "200": { - "description": "Successfully updated Rujukan", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Rujukan not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict - update conflict occurred", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - }, - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Create new Rujukan in BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Create new Rujukan", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "Rujukan data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/rujukan.RujukanRequest" - } - } - ], - "responses": { - "201": { - "description": "Successfully created Rujukan", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - }, - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete existing Rujukan from BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Delete existing Rujukan", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - } - ], - "responses": { - "200": { - "description": "Successfully deleted Rujukan", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Rujukan not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - } - }, - "/Rujukanbalik/:norujukan": { - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Update existing Rujukanbalik in BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Update existing Rujukanbalik", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "Rujukanbalik update data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/rujukan.RujukanRequest" - } - } - ], - "responses": { - "200": { - "description": "Successfully updated Rujukanbalik", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Rujukanbalik not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict - update conflict occurred", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - }, - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Create new Rujukanbalik in BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Create new Rujukanbalik", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "Rujukanbalik data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/rujukan.RujukanRequest" - } - } - ], - "responses": { - "201": { - "description": "Successfully created Rujukanbalik", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - }, - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete existing Rujukanbalik from BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Delete existing Rujukanbalik", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - } - ], - "responses": { - "200": { - "description": "Successfully deleted Rujukanbalik", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Rujukanbalik not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" } } } @@ -578,7 +178,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.LoginRequest" + "$ref": "#/definitions/api-service_internal_models_auth.LoginRequest" } } ], @@ -586,7 +186,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.TokenResponse" + "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" } }, "400": { @@ -629,7 +229,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.User" + "$ref": "#/definitions/api-service_internal_models_auth.User" } }, "401": { @@ -675,7 +275,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.TokenResponse" + "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" } }, "400": { @@ -748,6 +348,43 @@ } } }, + "/api/v1/listkiosks/stats": { + "get": { + "description": "Returns comprehensive statistics about listkiosk data", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Listkiosk" + ], + "summary": "Get listkiosk statistics", + "parameters": [ + { + "type": "string", + "description": "Filter statistics by status", + "name": "status", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Statistics data", + "schema": { + "$ref": "#/definitions/api-service_internal_models.AggregateData" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + } + } + } + } + }, "/api/v1/retribusi/{id}": { "get": { "description": "Returns a single retribusi by ID", @@ -774,25 +411,25 @@ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/retribusi.RetribusiGetByIDResponse" + "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetByIDResponse" } }, "400": { "description": "Invalid ID format", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } @@ -823,7 +460,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/retribusi.RetribusiUpdateRequest" + "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiUpdateRequest" } } ], @@ -831,25 +468,25 @@ "200": { "description": "Retribusi updated successfully", "schema": { - "$ref": "#/definitions/retribusi.RetribusiUpdateResponse" + "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiUpdateResponse" } }, "400": { "description": "Bad request or validation error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } @@ -879,25 +516,25 @@ "200": { "description": "Retribusi deleted successfully", "schema": { - "$ref": "#/definitions/retribusi.RetribusiDeleteResponse" + "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiDeleteResponse" } }, "400": { "description": "Invalid ID format", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } @@ -967,19 +604,19 @@ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/retribusi.RetribusiGetResponse" + "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse" } }, "400": { "description": "Bad request", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } @@ -1003,7 +640,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/retribusi.RetribusiCreateRequest" + "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiCreateRequest" } } ], @@ -1011,19 +648,19 @@ "201": { "description": "Retribusi created successfully", "schema": { - "$ref": "#/definitions/retribusi.RetribusiCreateResponse" + "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiCreateResponse" } }, "400": { "description": "Bad request or validation error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } @@ -1080,19 +717,19 @@ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/retribusi.RetribusiGetResponse" + "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse" } }, "400": { "description": "Bad request", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } @@ -1123,13 +760,13 @@ "200": { "description": "Statistics data", "schema": { - "$ref": "#/definitions/models.AggregateData" + "$ref": "#/definitions/api-service_internal_models.AggregateData" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponse" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } @@ -1155,7 +792,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.LoginRequest" + "$ref": "#/definitions/api-service_internal_models_auth.LoginRequest" } } ], @@ -1163,7 +800,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.TokenResponse" + "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" } }, "400": { @@ -1218,7 +855,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.TokenResponse" + "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" } }, "400": { @@ -1233,14 +870,9 @@ } } }, - "/bynokartu/:nokartu": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get rujukan by card number", + "/kiosk": { + "post": { + "description": "Creates a new listkiosk record", "consumes": [ "application/json" ], @@ -1248,67 +880,45 @@ "application/json" ], "tags": [ - "Rujukan" + "Listkiosk" ], - "summary": "Get Bynokartu data", + "summary": "Create listkiosk", "parameters": [ { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "type": "string", - "example": "\"example_value\"", - "description": "nokartu", - "name": "nokartu", - "in": "path", - "required": true + "description": "Listkiosk creation request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api-service_internal_models_kiosk.ListkioskCreateRequest" + } } ], "responses": { - "200": { - "description": "Successfully retrieved Bynokartu data", + "201": { + "description": "Listkiosk created successfully", "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" + "$ref": "#/definitions/api-service_internal_models_kiosk.ListkioskCreateResponse" } }, "400": { - "description": "Bad request - invalid parameters", + "description": "Bad request or validation error", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Bynokartu not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } } }, - "/bynorujukan/:norujukan": { + "/kiosk/listkiosk": { "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get rujukan by nomor rujukan", + "description": "Returns a paginated list of listkiosks with optional summary statistics", "consumes": [ "application/json" ], @@ -1316,54 +926,168 @@ "application/json" ], "tags": [ - "Rujukan" + "Listkiosk" ], - "summary": "Get Bynorujukan data", + "summary": "Get listkiosk with pagination and optional aggregation", "parameters": [ { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" + "type": "integer", + "default": 10, + "description": "Limit (max 100)", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "default": 0, + "description": "Offset", + "name": "offset", + "in": "query" + }, + { + "type": "boolean", + "default": false, + "description": "Include aggregation summary", + "name": "include_summary", + "in": "query" }, { "type": "string", - "example": "\"example_value\"", - "description": "norujukan", - "name": "norujukan", + "description": "Filter by status", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "Search in multiple fields", + "name": "search", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Success response", + "schema": { + "$ref": "#/definitions/api-service_internal_models_kiosk.ListkioskGetResponse" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + } + } + } + } + }, + "/kiosk/{id}": { + "put": { + "description": "Updates an existing listkiosk record", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Listkiosk" + ], + "summary": "Update listkiosk", + "parameters": [ + { + "type": "integer", + "description": "Kiosk ID (integer)", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Listkiosk update request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/api-service_internal_models_kiosk.ListkioskUpdateRequest" + } + } + ], + "responses": { + "200": { + "description": "Listkiosk updated successfully", + "schema": { + "$ref": "#/definitions/api-service_internal_models_kiosk.ListkioskUpdateResponse" + } + }, + "400": { + "description": "Bad request or validation error", + "schema": { + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + } + }, + "404": { + "description": "Listkiosk not found", + "schema": { + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + } + } + } + }, + "delete": { + "description": "Soft deletes a listkiosk by setting status to 'deleted'", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Listkiosk" + ], + "summary": "Delete listkiosk", + "parameters": [ + { + "type": "integer", + "description": "Kiosk ID (integer)", + "name": "id", "in": "path", "required": true } ], "responses": { "200": { - "description": "Successfully retrieved Bynorujukan data", + "description": "Listkiosk deleted successfully", "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" + "$ref": "#/definitions/api-service_internal_models_kiosk.ListkioskDeleteResponse" } }, "400": { - "description": "Bad request - invalid parameters", + "description": "Invalid ID format", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "404": { - "description": "Not found - Bynorujukan not found", + "description": "Listkiosk not found", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "$ref": "#/definitions/api-service_internal_models.ErrorResponse" } } } @@ -1371,7 +1095,7 @@ } }, "definitions": { - "models.AggregateData": { + "api-service_internal_models.AggregateData": { "type": "object", "properties": { "by_dinas": { @@ -1412,7 +1136,7 @@ } } }, - "models.ErrorResponse": { + "api-service_internal_models.ErrorResponse": { "type": "object", "properties": { "code": { @@ -1429,7 +1153,7 @@ } } }, - "models.ErrorResponseBpjs": { + "api-service_internal_models.ErrorResponseBpjs": { "type": "object", "properties": { "code": { @@ -1450,22 +1174,7 @@ } } }, - "models.LoginRequest": { - "type": "object", - "required": [ - "password", - "username" - ], - "properties": { - "password": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "models.MetaResponse": { + "api-service_internal_models.MetaResponse": { "type": "object", "properties": { "current_page": { @@ -1491,7 +1200,7 @@ } } }, - "models.NullableInt32": { + "api-service_internal_models.NullableInt32": { "type": "object", "properties": { "int32": { @@ -1502,29 +1211,22 @@ } } }, - "models.NullableString": { + "api-service_internal_models_auth.LoginRequest": { "type": "object", + "required": [ + "password", + "username" + ], "properties": { - "string": { + "password": { "type": "string" }, - "valid": { - "type": "boolean" + "username": { + "type": "string" } } }, - "models.NullableTime": { - "type": "object", - "properties": { - "time": { - "type": "string" - }, - "valid": { - "type": "boolean" - } - } - }, - "models.TokenResponse": { + "api-service_internal_models_auth.TokenResponse": { "type": "object", "properties": { "access_token": { @@ -1538,7 +1240,7 @@ } } }, - "models.User": { + "api-service_internal_models_auth.User": { "type": "object", "properties": { "email": { @@ -1555,7 +1257,480 @@ } } }, - "peserta.PesertaData": { + "api-service_internal_models_kiosk.Listkiosk": { + "type": "object", + "properties": { + "active": { + "$ref": "#/definitions/sql.NullBool" + }, + "ds_sd_location": { + "$ref": "#/definitions/sql.NullString" + }, + "fk_ref_healthcare_type_id": { + "$ref": "#/definitions/api-service_internal_models.NullableInt32" + }, + "fk_ref_service_type_id": { + "$ref": "#/definitions/api-service_internal_models.NullableInt32" + }, + "fk_sd_location_id": { + "$ref": "#/definitions/sql.NullString" + }, + "icon": { + "$ref": "#/definitions/sql.NullString" + }, + "id": { + "type": "integer" + }, + "name": { + "$ref": "#/definitions/sql.NullString" + }, + "url": { + "$ref": "#/definitions/sql.NullString" + } + } + }, + "api-service_internal_models_kiosk.ListkioskCreateRequest": { + "type": "object", + "properties": { + "active": { + "type": "boolean", + "enum": [ + true, + false + ] + }, + "ds_sd_location": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "fk_ref_healthcare_type_id": { + "type": "integer", + "minimum": 1 + }, + "fk_ref_service_type_id": { + "type": "integer", + "minimum": 1 + }, + "fk_sd_location_id": { + "type": "string", + "minLength": 1 + }, + "icon": { + "type": "string", + "maxLength": 20, + "minLength": 1 + }, + "name": { + "type": "string", + "maxLength": 20, + "minLength": 1 + }, + "url": { + "type": "string", + "maxLength": 255, + "minLength": 1 + } + } + }, + "api-service_internal_models_kiosk.ListkioskCreateResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/api-service_internal_models_kiosk.Listkiosk" + }, + "message": { + "type": "string" + } + } + }, + "api-service_internal_models_kiosk.ListkioskDeleteResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "api-service_internal_models_kiosk.ListkioskGetResponse": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/api-service_internal_models_kiosk.Listkiosk" + } + }, + "message": { + "type": "string" + }, + "meta": { + "$ref": "#/definitions/api-service_internal_models.MetaResponse" + }, + "summary": { + "$ref": "#/definitions/api-service_internal_models.AggregateData" + } + } + }, + "api-service_internal_models_kiosk.ListkioskUpdateRequest": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "active": { + "type": "boolean", + "enum": [ + true, + false + ] + }, + "ds_sd_location": { + "type": "string", + "maxLength": 255, + "minLength": 1 + }, + "fk_ref_healthcare_type_id": { + "type": "integer", + "minimum": 1 + }, + "fk_ref_service_type_id": { + "type": "integer", + "minimum": 1 + }, + "fk_sd_location_id": { + "type": "string", + "minLength": 1 + }, + "icon": { + "type": "string", + "maxLength": 20, + "minLength": 1 + }, + "id": { + "type": "integer", + "minimum": 1 + }, + "name": { + "type": "string", + "maxLength": 20, + "minLength": 1 + }, + "url": { + "type": "string", + "maxLength": 255, + "minLength": 1 + } + } + }, + "api-service_internal_models_kiosk.ListkioskUpdateResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/api-service_internal_models_kiosk.Listkiosk" + }, + "message": { + "type": "string" + } + } + }, + "api-service_internal_models_retribusi.Retribusi": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "date_created": { + "$ref": "#/definitions/sql.NullTime" + }, + "date_updated": { + "$ref": "#/definitions/sql.NullTime" + }, + "deletedAt": { + "$ref": "#/definitions/gorm.DeletedAt" + }, + "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/sql.NullInt32" + }, + "status": { + "type": "string" + }, + "tarif": { + "$ref": "#/definitions/sql.NullString" + }, + "tarif_overtime": { + "$ref": "#/definitions/sql.NullString" + }, + "updatedAt": { + "type": "string" + }, + "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" + } + } + }, + "api-service_internal_models_vclaim_peserta.PesertaData": { "type": "object", "properties": { "cob": { @@ -1676,11 +1851,11 @@ } } }, - "peserta.PesertaResponse": { + "api-service_internal_models_vclaim_peserta.PesertaResponse": { "type": "object", "properties": { "data": { - "$ref": "#/definitions/peserta.PesertaData" + "$ref": "#/definitions/api-service_internal_models_vclaim_peserta.PesertaData" }, "message": { "type": "string" @@ -1697,520 +1872,64 @@ } } }, - "retribusi.Retribusi": { + "gorm.DeletedAt": { "type": "object", "properties": { - "date_created": { - "$ref": "#/definitions/models.NullableTime" - }, - "date_updated": { - "$ref": "#/definitions/models.NullableTime" - }, - "dinas": { - "$ref": "#/definitions/models.NullableString" - }, - "id": { + "time": { "type": "string" }, - "jenis": { - "$ref": "#/definitions/models.NullableString" - }, - "kelompok_obyek": { - "$ref": "#/definitions/models.NullableString" - }, - "kode_tarif": { - "$ref": "#/definitions/models.NullableString" - }, - "pelayanan": { - "$ref": "#/definitions/models.NullableString" - }, - "rekening_denda": { - "$ref": "#/definitions/models.NullableString" - }, - "rekening_pokok": { - "$ref": "#/definitions/models.NullableString" - }, - "satuan": { - "$ref": "#/definitions/models.NullableString" - }, - "satuan_overtime": { - "$ref": "#/definitions/models.NullableString" - }, - "sort": { - "$ref": "#/definitions/models.NullableInt32" - }, - "status": { - "type": "string" - }, - "tarif": { - "$ref": "#/definitions/models.NullableString" - }, - "tarif_overtime": { - "$ref": "#/definitions/models.NullableString" - }, - "uraian_1": { - "$ref": "#/definitions/models.NullableString" - }, - "uraian_2": { - "$ref": "#/definitions/models.NullableString" - }, - "uraian_3": { - "$ref": "#/definitions/models.NullableString" - }, - "user_created": { - "$ref": "#/definitions/models.NullableString" - }, - "user_updated": { - "$ref": "#/definitions/models.NullableString" + "valid": { + "description": "Valid is true if Time is not NULL", + "type": "boolean" } } }, - "retribusi.RetribusiCreateRequest": { + "sql.NullBool": { "type": "object", - "required": [ - "status" - ], "properties": { - "dinas": { - "type": "string", - "maxLength": 255, - "minLength": 1 + "bool": { + "type": "boolean" }, - "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" + "valid": { + "description": "Valid is true if Bool is not NULL", + "type": "boolean" } } }, - "retribusi.RetribusiCreateResponse": { + "sql.NullInt32": { "type": "object", "properties": { - "data": { - "$ref": "#/definitions/retribusi.Retribusi" + "int32": { + "type": "integer", + "format": "int32" }, - "message": { - "type": "string" + "valid": { + "description": "Valid is true if Int32 is not NULL", + "type": "boolean" } } }, - "retribusi.RetribusiDeleteResponse": { + "sql.NullString": { "type": "object", "properties": { - "id": { + "string": { "type": "string" }, - "message": { - "type": "string" + "valid": { + "description": "Valid is true if String is not NULL", + "type": "boolean" } } }, - "retribusi.RetribusiGetByIDResponse": { + "sql.NullTime": { "type": "object", "properties": { - "data": { - "$ref": "#/definitions/retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "retribusi.RetribusiGetResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/retribusi.Retribusi" - } - }, - "message": { + "time": { "type": "string" }, - "meta": { - "$ref": "#/definitions/models.MetaResponse" - }, - "summary": { - "$ref": "#/definitions/models.AggregateData" - } - } - }, - "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" - } - } - }, - "retribusi.RetribusiUpdateResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "rujukan.DataPeserta": { - "type": "object", - "properties": { - "cob": { - "type": "object", - "properties": { - "nmAsuransi": {}, - "noAsuransi": {}, - "tglTAT": {}, - "tglTMT": {} - } - }, - "hakKelas": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "informasi": { - "type": "object", - "properties": { - "dinsos": {}, - "noSKTM": {}, - "prolanisPRB": {} - } - }, - "jenisPeserta": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "mr": { - "type": "object", - "properties": { - "noMR": { - "type": "string" - }, - "noTelepon": {} - } - }, - "nama": { - "type": "string" - }, - "nik": { - "type": "string" - }, - "noKartu": { - "type": "string" - }, - "pisa": { - "type": "string" - }, - "provUmum": { - "type": "object", - "properties": { - "kdProvider": { - "type": "string" - }, - "nmProvider": { - "type": "string" - } - } - }, - "sex": { - "type": "string" - }, - "statusPeserta": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "tglCetakKartu": { - "type": "string" - }, - "tglLahir": { - "type": "string" - }, - "tglTAT": { - "type": "string" - }, - "tglTMT": { - "type": "string" - }, - "umur": { - "type": "object", - "properties": { - "umurSaatPelayanan": { - "type": "string" - }, - "umurSekarang": { - "type": "string" - } - } - } - } - }, - "rujukan.DiagnosaData": { - "type": "object", - "properties": { - "kode": { - "type": "string" - }, - "nama": { - "type": "string" - } - } - }, - "rujukan.PelayananData": { - "type": "object", - "properties": { - "kode": { - "type": "string" - }, - "nama": { - "type": "string" - } - } - }, - "rujukan.PoliRujukanData": { - "type": "object", - "properties": { - "kode": { - "type": "string" - }, - "nama": { - "type": "string" - } - } - }, - "rujukan.ProvPerujukData": { - "type": "object", - "properties": { - "kode": { - "type": "string" - }, - "nama": { - "type": "string" - } - } - }, - "rujukan.RujukanData": { - "type": "object", - "properties": { - "diagnosa": { - "$ref": "#/definitions/rujukan.DiagnosaData" - }, - "keluhan": { - "type": "string" - }, - "noKunjungan": { - "type": "string" - }, - "pelayanan": { - "$ref": "#/definitions/rujukan.PelayananData" - }, - "peserta": { - "$ref": "#/definitions/rujukan.DataPeserta" - }, - "poliRujukan": { - "$ref": "#/definitions/rujukan.PoliRujukanData" - }, - "provPerujuk": { - "$ref": "#/definitions/rujukan.ProvPerujukData" - }, - "tglKunjungan": { - "type": "string" - } - } - }, - "rujukan.RujukanRequest": { - "type": "object", - "required": [ - "noRujukan" - ], - "properties": { - "noKartu": { - "type": "string" - }, - "noRujukan": { - "type": "string" - }, - "request_id": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - } - }, - "rujukan.RujukanResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/rujukan.RujukanData" - }, - "list": { - "type": "array", - "items": { - "$ref": "#/definitions/rujukan.RujukanData" - } - }, - "message": { - "type": "string" - }, - "metaData": {}, - "request_id": { - "type": "string" - }, - "status": { - "type": "string" - }, - "timestamp": { - "type": "string" + "valid": { + "description": "Valid is true if Time is not NULL", + "type": "boolean" } } } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 84640d0c..c78b66df 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,6 +1,6 @@ basePath: /api/v1 definitions: - models.AggregateData: + api-service_internal_models.AggregateData: properties: by_dinas: additionalProperties: @@ -27,7 +27,7 @@ definitions: updated_today: type: integer type: object - models.ErrorResponse: + api-service_internal_models.ErrorResponse: properties: code: type: integer @@ -38,7 +38,7 @@ definitions: timestamp: type: string type: object - models.ErrorResponseBpjs: + api-service_internal_models.ErrorResponseBpjs: properties: code: type: string @@ -52,17 +52,7 @@ definitions: status: type: string type: object - models.LoginRequest: - properties: - password: - type: string - username: - type: string - required: - - password - - username - type: object - models.MetaResponse: + api-service_internal_models.MetaResponse: properties: current_page: type: integer @@ -79,28 +69,24 @@ definitions: total_pages: type: integer type: object - models.NullableInt32: + api-service_internal_models.NullableInt32: properties: int32: type: integer valid: type: boolean type: object - models.NullableString: + api-service_internal_models_auth.LoginRequest: properties: - string: + password: type: string - valid: - type: boolean - type: object - models.NullableTime: - properties: - time: + username: type: string - valid: - type: boolean + required: + - password + - username type: object - models.TokenResponse: + api-service_internal_models_auth.TokenResponse: properties: access_token: type: string @@ -109,7 +95,7 @@ definitions: token_type: type: string type: object - models.User: + api-service_internal_models_auth.User: properties: email: type: string @@ -120,7 +106,339 @@ definitions: username: type: string type: object - peserta.PesertaData: + api-service_internal_models_kiosk.Listkiosk: + properties: + active: + $ref: '#/definitions/sql.NullBool' + ds_sd_location: + $ref: '#/definitions/sql.NullString' + fk_ref_healthcare_type_id: + $ref: '#/definitions/api-service_internal_models.NullableInt32' + fk_ref_service_type_id: + $ref: '#/definitions/api-service_internal_models.NullableInt32' + fk_sd_location_id: + $ref: '#/definitions/sql.NullString' + icon: + $ref: '#/definitions/sql.NullString' + id: + type: integer + name: + $ref: '#/definitions/sql.NullString' + url: + $ref: '#/definitions/sql.NullString' + type: object + api-service_internal_models_kiosk.ListkioskCreateRequest: + properties: + active: + enum: + - true + - false + type: boolean + ds_sd_location: + maxLength: 255 + minLength: 1 + type: string + fk_ref_healthcare_type_id: + minimum: 1 + type: integer + fk_ref_service_type_id: + minimum: 1 + type: integer + fk_sd_location_id: + minLength: 1 + type: string + icon: + maxLength: 20 + minLength: 1 + type: string + name: + maxLength: 20 + minLength: 1 + type: string + url: + maxLength: 255 + minLength: 1 + type: string + type: object + api-service_internal_models_kiosk.ListkioskCreateResponse: + properties: + data: + $ref: '#/definitions/api-service_internal_models_kiosk.Listkiosk' + message: + type: string + type: object + api-service_internal_models_kiosk.ListkioskDeleteResponse: + properties: + id: + type: string + message: + type: string + type: object + api-service_internal_models_kiosk.ListkioskGetResponse: + properties: + data: + items: + $ref: '#/definitions/api-service_internal_models_kiosk.Listkiosk' + 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_kiosk.ListkioskUpdateRequest: + properties: + active: + enum: + - true + - false + type: boolean + ds_sd_location: + maxLength: 255 + minLength: 1 + type: string + fk_ref_healthcare_type_id: + minimum: 1 + type: integer + fk_ref_service_type_id: + minimum: 1 + type: integer + fk_sd_location_id: + minLength: 1 + type: string + icon: + maxLength: 20 + minLength: 1 + type: string + id: + minimum: 1 + type: integer + name: + maxLength: 20 + minLength: 1 + type: string + url: + maxLength: 255 + minLength: 1 + type: string + required: + - id + type: object + api-service_internal_models_kiosk.ListkioskUpdateResponse: + properties: + data: + $ref: '#/definitions/api-service_internal_models_kiosk.Listkiosk' + message: + type: string + type: object + api-service_internal_models_retribusi.Retribusi: + properties: + createdAt: + type: string + date_created: + $ref: '#/definitions/sql.NullTime' + date_updated: + $ref: '#/definitions/sql.NullTime' + deletedAt: + $ref: '#/definitions/gorm.DeletedAt' + 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/sql.NullInt32' + status: + type: string + tarif: + $ref: '#/definitions/sql.NullString' + tarif_overtime: + $ref: '#/definitions/sql.NullString' + updatedAt: + type: string + 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 + api-service_internal_models_vclaim_peserta.PesertaData: properties: cob: properties: @@ -200,10 +518,10 @@ definitions: type: string type: object type: object - peserta.PesertaResponse: + api-service_internal_models_vclaim_peserta.PesertaResponse: properties: data: - $ref: '#/definitions/peserta.PesertaData' + $ref: '#/definitions/api-service_internal_models_vclaim_peserta.PesertaData' message: type: string metaData: {} @@ -214,358 +532,46 @@ definitions: timestamp: type: string type: object - retribusi.Retribusi: + gorm.DeletedAt: properties: - date_created: - $ref: '#/definitions/models.NullableTime' - date_updated: - $ref: '#/definitions/models.NullableTime' - dinas: - $ref: '#/definitions/models.NullableString' - id: + time: type: string - jenis: - $ref: '#/definitions/models.NullableString' - kelompok_obyek: - $ref: '#/definitions/models.NullableString' - kode_tarif: - $ref: '#/definitions/models.NullableString' - pelayanan: - $ref: '#/definitions/models.NullableString' - rekening_denda: - $ref: '#/definitions/models.NullableString' - rekening_pokok: - $ref: '#/definitions/models.NullableString' - satuan: - $ref: '#/definitions/models.NullableString' - satuan_overtime: - $ref: '#/definitions/models.NullableString' - sort: - $ref: '#/definitions/models.NullableInt32' - status: - type: string - tarif: - $ref: '#/definitions/models.NullableString' - tarif_overtime: - $ref: '#/definitions/models.NullableString' - uraian_1: - $ref: '#/definitions/models.NullableString' - uraian_2: - $ref: '#/definitions/models.NullableString' - uraian_3: - $ref: '#/definitions/models.NullableString' - user_created: - $ref: '#/definitions/models.NullableString' - user_updated: - $ref: '#/definitions/models.NullableString' + valid: + description: Valid is true if Time is not NULL + type: boolean type: object - retribusi.RetribusiCreateRequest: + sql.NullBool: 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 + bool: + type: boolean + valid: + description: Valid is true if Bool is not NULL + type: boolean type: object - retribusi.RetribusiCreateResponse: + sql.NullInt32: properties: - data: - $ref: '#/definitions/retribusi.Retribusi' - message: - type: string + int32: + format: int32 + type: integer + valid: + description: Valid is true if Int32 is not NULL + type: boolean type: object - retribusi.RetribusiDeleteResponse: + sql.NullString: properties: - id: - type: string - message: + string: type: string + valid: + description: Valid is true if String is not NULL + type: boolean type: object - retribusi.RetribusiGetByIDResponse: + sql.NullTime: properties: - data: - $ref: '#/definitions/retribusi.Retribusi' - message: - type: string - type: object - retribusi.RetribusiGetResponse: - properties: - data: - items: - $ref: '#/definitions/retribusi.Retribusi' - type: array - message: - type: string - meta: - $ref: '#/definitions/models.MetaResponse' - summary: - $ref: '#/definitions/models.AggregateData' - type: object - 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 - retribusi.RetribusiUpdateResponse: - properties: - data: - $ref: '#/definitions/retribusi.Retribusi' - message: - type: string - type: object - rujukan.DataPeserta: - properties: - cob: - properties: - nmAsuransi: {} - noAsuransi: {} - tglTAT: {} - tglTMT: {} - type: object - hakKelas: - properties: - keterangan: - type: string - kode: - type: string - type: object - informasi: - properties: - dinsos: {} - noSKTM: {} - prolanisPRB: {} - type: object - jenisPeserta: - properties: - keterangan: - type: string - kode: - type: string - type: object - mr: - properties: - noMR: - type: string - noTelepon: {} - type: object - nama: - type: string - nik: - type: string - noKartu: - type: string - pisa: - type: string - provUmum: - properties: - kdProvider: - type: string - nmProvider: - type: string - type: object - sex: - type: string - statusPeserta: - properties: - keterangan: - type: string - kode: - type: string - type: object - tglCetakKartu: - type: string - tglLahir: - type: string - tglTAT: - type: string - tglTMT: - type: string - umur: - properties: - umurSaatPelayanan: - type: string - umurSekarang: - type: string - type: object - type: object - rujukan.DiagnosaData: - properties: - kode: - type: string - nama: - type: string - type: object - rujukan.PelayananData: - properties: - kode: - type: string - nama: - type: string - type: object - rujukan.PoliRujukanData: - properties: - kode: - type: string - nama: - type: string - type: object - rujukan.ProvPerujukData: - properties: - kode: - type: string - nama: - type: string - type: object - rujukan.RujukanData: - properties: - diagnosa: - $ref: '#/definitions/rujukan.DiagnosaData' - keluhan: - type: string - noKunjungan: - type: string - pelayanan: - $ref: '#/definitions/rujukan.PelayananData' - peserta: - $ref: '#/definitions/rujukan.DataPeserta' - poliRujukan: - $ref: '#/definitions/rujukan.PoliRujukanData' - provPerujuk: - $ref: '#/definitions/rujukan.ProvPerujukData' - tglKunjungan: - type: string - type: object - rujukan.RujukanRequest: - properties: - noKartu: - type: string - noRujukan: - type: string - request_id: - type: string - timestamp: - type: string - required: - - noRujukan - type: object - rujukan.RujukanResponse: - properties: - data: - $ref: '#/definitions/rujukan.RujukanData' - list: - items: - $ref: '#/definitions/rujukan.RujukanData' - type: array - message: - type: string - metaData: {} - request_id: - type: string - status: - type: string - timestamp: + time: type: string + valid: + description: Valid is true if Time is not NULL + type: boolean type: object host: localhost:8080 info: @@ -603,23 +609,23 @@ paths: "200": description: Successfully retrieved Bynik data schema: - $ref: '#/definitions/peserta.PesertaResponse' + $ref: '#/definitions/api-service_internal_models_vclaim_peserta.PesertaResponse' "400": description: Bad request - invalid parameters schema: - $ref: '#/definitions/models.ErrorResponseBpjs' + $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' "401": description: Unauthorized - invalid API credentials schema: - $ref: '#/definitions/models.ErrorResponseBpjs' + $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' "404": description: Not found - Bynik not found schema: - $ref: '#/definitions/models.ErrorResponseBpjs' + $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' "500": description: Internal server error schema: - $ref: '#/definitions/models.ErrorResponseBpjs' + $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' security: - ApiKeyAuth: [] summary: Get Bynik data @@ -647,284 +653,28 @@ paths: "200": description: Successfully retrieved Bynokartu data schema: - $ref: '#/definitions/peserta.PesertaResponse' + $ref: '#/definitions/api-service_internal_models_vclaim_peserta.PesertaResponse' "400": description: Bad request - invalid parameters schema: - $ref: '#/definitions/models.ErrorResponseBpjs' + $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' "401": description: Unauthorized - invalid API credentials schema: - $ref: '#/definitions/models.ErrorResponseBpjs' + $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' "404": description: Not found - Bynokartu not found schema: - $ref: '#/definitions/models.ErrorResponseBpjs' + $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' "500": description: Internal server error schema: - $ref: '#/definitions/models.ErrorResponseBpjs' + $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' security: - ApiKeyAuth: [] summary: Get Bynokartu data tags: - Peserta - /Rujukan/:norujukan: - delete: - consumes: - - application/json - description: Delete existing Rujukan from BPJS system - parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - produces: - - application/json - responses: - "200": - description: Successfully deleted Rujukan - schema: - $ref: '#/definitions/rujukan.RujukanResponse' - "400": - description: Bad request - invalid parameters - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "404": - description: Not found - Rujukan not found - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Delete existing Rujukan - tags: - - Rujukan - post: - consumes: - - application/json - description: Create new Rujukan in BPJS system - parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - - description: Rujukan data - in: body - name: request - required: true - schema: - $ref: '#/definitions/rujukan.RujukanRequest' - produces: - - application/json - responses: - "201": - description: Successfully created Rujukan - schema: - $ref: '#/definitions/rujukan.RujukanResponse' - "400": - description: Bad request - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "409": - description: Conflict - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Create new Rujukan - tags: - - Rujukan - put: - consumes: - - application/json - description: Update existing Rujukan in BPJS system - parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - - description: Rujukan update data - in: body - name: request - required: true - schema: - $ref: '#/definitions/rujukan.RujukanRequest' - produces: - - application/json - responses: - "200": - description: Successfully updated Rujukan - schema: - $ref: '#/definitions/rujukan.RujukanResponse' - "400": - description: Bad request - invalid parameters - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "404": - description: Not found - Rujukan not found - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "409": - description: Conflict - update conflict occurred - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Update existing Rujukan - tags: - - Rujukan - /Rujukanbalik/:norujukan: - delete: - consumes: - - application/json - description: Delete existing Rujukanbalik from BPJS system - parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - produces: - - application/json - responses: - "200": - description: Successfully deleted Rujukanbalik - schema: - $ref: '#/definitions/rujukan.RujukanResponse' - "400": - description: Bad request - invalid parameters - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "404": - description: Not found - Rujukanbalik not found - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Delete existing Rujukanbalik - tags: - - Rujukan - post: - consumes: - - application/json - description: Create new Rujukanbalik in BPJS system - parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - - description: Rujukanbalik data - in: body - name: request - required: true - schema: - $ref: '#/definitions/rujukan.RujukanRequest' - produces: - - application/json - responses: - "201": - description: Successfully created Rujukanbalik - schema: - $ref: '#/definitions/rujukan.RujukanResponse' - "400": - description: Bad request - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "409": - description: Conflict - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Create new Rujukanbalik - tags: - - Rujukan - put: - consumes: - - application/json - description: Update existing Rujukanbalik in BPJS system - parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - - description: Rujukanbalik update data - in: body - name: request - required: true - schema: - $ref: '#/definitions/rujukan.RujukanRequest' - produces: - - application/json - responses: - "200": - description: Successfully updated Rujukanbalik - schema: - $ref: '#/definitions/rujukan.RujukanResponse' - "400": - description: Bad request - invalid parameters - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "404": - description: Not found - Rujukanbalik not found - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "409": - description: Conflict - update conflict occurred - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Update existing Rujukanbalik - tags: - - Rujukan /api/v1/auth/login: post: consumes: @@ -936,14 +686,14 @@ paths: name: login required: true schema: - $ref: '#/definitions/models.LoginRequest' + $ref: '#/definitions/api-service_internal_models_auth.LoginRequest' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/models.TokenResponse' + $ref: '#/definitions/api-service_internal_models_auth.TokenResponse' "400": description: Bad request schema: @@ -968,7 +718,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/models.User' + $ref: '#/definitions/api-service_internal_models_auth.User' "401": description: Unauthorized schema: @@ -1000,7 +750,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/models.TokenResponse' + $ref: '#/definitions/api-service_internal_models_auth.TokenResponse' "400": description: Bad request schema: @@ -1048,6 +798,30 @@ paths: summary: Register new user tags: - Authentication + /api/v1/listkiosks/stats: + get: + consumes: + - application/json + description: Returns comprehensive statistics about listkiosk data + parameters: + - description: Filter statistics by status + in: query + name: status + type: string + produces: + - application/json + responses: + "200": + description: Statistics data + schema: + $ref: '#/definitions/api-service_internal_models.AggregateData' + "500": + description: Internal server error + schema: + $ref: '#/definitions/api-service_internal_models.ErrorResponse' + summary: Get listkiosk statistics + tags: + - Listkiosk /api/v1/retribusi/{id}: delete: consumes: @@ -1065,19 +839,19 @@ paths: "200": description: Retribusi deleted successfully schema: - $ref: '#/definitions/retribusi.RetribusiDeleteResponse' + $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiDeleteResponse' "400": description: Invalid ID format schema: - $ref: '#/definitions/models.ErrorResponse' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' "404": description: Retribusi not found schema: - $ref: '#/definitions/models.ErrorResponse' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/models.ErrorResponse' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' summary: Delete retribusi tags: - Retribusi @@ -1097,19 +871,19 @@ paths: "200": description: Success response schema: - $ref: '#/definitions/retribusi.RetribusiGetByIDResponse' + $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiGetByIDResponse' "400": description: Invalid ID format schema: - $ref: '#/definitions/models.ErrorResponse' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' "404": description: Retribusi not found schema: - $ref: '#/definitions/models.ErrorResponse' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/models.ErrorResponse' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' summary: Get Retribusi by ID tags: - Retribusi @@ -1128,26 +902,26 @@ paths: name: request required: true schema: - $ref: '#/definitions/retribusi.RetribusiUpdateRequest' + $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiUpdateRequest' produces: - application/json responses: "200": description: Retribusi updated successfully schema: - $ref: '#/definitions/retribusi.RetribusiUpdateResponse' + $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiUpdateResponse' "400": description: Bad request or validation error schema: - $ref: '#/definitions/models.ErrorResponse' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' "404": description: Retribusi not found schema: - $ref: '#/definitions/models.ErrorResponse' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/models.ErrorResponse' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' summary: Update retribusi tags: - Retribusi @@ -1194,15 +968,15 @@ paths: "200": description: Success response schema: - $ref: '#/definitions/retribusi.RetribusiGetResponse' + $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse' "400": description: Bad request schema: - $ref: '#/definitions/models.ErrorResponse' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/models.ErrorResponse' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' summary: Get retribusi with pagination and optional aggregation tags: - Retribusi @@ -1216,22 +990,22 @@ paths: name: request required: true schema: - $ref: '#/definitions/retribusi.RetribusiCreateRequest' + $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiCreateRequest' produces: - application/json responses: "201": description: Retribusi created successfully schema: - $ref: '#/definitions/retribusi.RetribusiCreateResponse' + $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiCreateResponse' "400": description: Bad request or validation error schema: - $ref: '#/definitions/models.ErrorResponse' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/models.ErrorResponse' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' summary: Create retribusi tags: - Retribusi @@ -1269,15 +1043,15 @@ paths: "200": description: Success response schema: - $ref: '#/definitions/retribusi.RetribusiGetResponse' + $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse' "400": description: Bad request schema: - $ref: '#/definitions/models.ErrorResponse' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/models.ErrorResponse' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' summary: Get retribusi with dynamic filtering tags: - Retribusi @@ -1297,11 +1071,11 @@ paths: "200": description: Statistics data schema: - $ref: '#/definitions/models.AggregateData' + $ref: '#/definitions/api-service_internal_models.AggregateData' "500": description: Internal server error schema: - $ref: '#/definitions/models.ErrorResponse' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' summary: Get retribusi statistics tags: - Retribusi @@ -1316,14 +1090,14 @@ paths: name: token required: true schema: - $ref: '#/definitions/models.LoginRequest' + $ref: '#/definitions/api-service_internal_models_auth.LoginRequest' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/models.TokenResponse' + $ref: '#/definitions/api-service_internal_models_auth.TokenResponse' "400": description: Bad request schema: @@ -1360,7 +1134,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/models.TokenResponse' + $ref: '#/definitions/api-service_internal_models_auth.TokenResponse' "400": description: Bad request schema: @@ -1370,94 +1144,154 @@ paths: summary: Generate token directly tags: - Token - /bynokartu/:nokartu: + /kiosk: + post: + consumes: + - application/json + description: Creates a new listkiosk record + parameters: + - description: Listkiosk creation request + in: body + name: request + required: true + schema: + $ref: '#/definitions/api-service_internal_models_kiosk.ListkioskCreateRequest' + produces: + - application/json + responses: + "201": + description: Listkiosk created successfully + schema: + $ref: '#/definitions/api-service_internal_models_kiosk.ListkioskCreateResponse' + "400": + description: Bad request or validation error + schema: + $ref: '#/definitions/api-service_internal_models.ErrorResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/api-service_internal_models.ErrorResponse' + summary: Create listkiosk + tags: + - Listkiosk + /kiosk/{id}: + delete: + consumes: + - application/json + description: Soft deletes a listkiosk by setting status to 'deleted' + parameters: + - description: Kiosk ID (integer) + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: Listkiosk deleted successfully + schema: + $ref: '#/definitions/api-service_internal_models_kiosk.ListkioskDeleteResponse' + "400": + description: Invalid ID format + schema: + $ref: '#/definitions/api-service_internal_models.ErrorResponse' + "404": + description: Listkiosk not found + schema: + $ref: '#/definitions/api-service_internal_models.ErrorResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/api-service_internal_models.ErrorResponse' + summary: Delete listkiosk + tags: + - Listkiosk + put: + consumes: + - application/json + description: Updates an existing listkiosk record + parameters: + - description: Kiosk ID (integer) + in: path + name: id + required: true + type: integer + - description: Listkiosk update request + in: body + name: request + required: true + schema: + $ref: '#/definitions/api-service_internal_models_kiosk.ListkioskUpdateRequest' + produces: + - application/json + responses: + "200": + description: Listkiosk updated successfully + schema: + $ref: '#/definitions/api-service_internal_models_kiosk.ListkioskUpdateResponse' + "400": + description: Bad request or validation error + schema: + $ref: '#/definitions/api-service_internal_models.ErrorResponse' + "404": + description: Listkiosk not found + schema: + $ref: '#/definitions/api-service_internal_models.ErrorResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/api-service_internal_models.ErrorResponse' + summary: Update listkiosk + tags: + - Listkiosk + /kiosk/listkiosk: get: consumes: - application/json - description: Get rujukan by card number + description: Returns a paginated list of listkiosks with optional summary statistics parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID + - default: 10 + description: Limit (max 100) + in: query + name: limit + type: integer + - default: 0 + description: Offset + in: query + name: offset + type: integer + - default: false + description: Include aggregation summary + in: query + name: include_summary + type: boolean + - description: Filter by status + in: query + name: status type: string - - description: nokartu - example: '"example_value"' - in: path - name: nokartu - required: true + - description: Search in multiple fields + in: query + name: search type: string produces: - application/json responses: "200": - description: Successfully retrieved Bynokartu data + description: Success response schema: - $ref: '#/definitions/rujukan.RujukanResponse' + $ref: '#/definitions/api-service_internal_models_kiosk.ListkioskGetResponse' "400": - description: Bad request - invalid parameters + description: Bad request schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "404": - description: Not found - Bynokartu not found - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' + $ref: '#/definitions/api-service_internal_models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Get Bynokartu data + $ref: '#/definitions/api-service_internal_models.ErrorResponse' + summary: Get listkiosk with pagination and optional aggregation tags: - - Rujukan - /bynorujukan/:norujukan: - get: - consumes: - - application/json - description: Get rujukan by nomor rujukan - parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - - description: norujukan - example: '"example_value"' - in: path - name: norujukan - required: true - type: string - produces: - - application/json - responses: - "200": - description: Successfully retrieved Bynorujukan data - schema: - $ref: '#/definitions/rujukan.RujukanResponse' - "400": - description: Bad request - invalid parameters - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "404": - description: Not found - Bynorujukan not found - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Get Bynorujukan data - tags: - - Rujukan + - Listkiosk schemes: - http - https diff --git a/go.mod b/go.mod index 36a04e72..2339983b 100644 --- a/go.mod +++ b/go.mod @@ -34,9 +34,15 @@ require ( gorm.io/gorm v1.30.0 ) +require ( + github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect + github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect +) + require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/KyleBanks/depth v1.2.1 // indirect + github.com/Masterminds/squirrel v1.5.4 github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/bytedance/sonic v1.14.0 // indirect diff --git a/go.sum b/go.sum index 8b8fb53b..47782f74 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mx github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= +github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= @@ -134,6 +136,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= @@ -184,6 +190,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/internal/config/config.go b/internal/config/config.go index f34deb46..51ff777a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -689,18 +689,18 @@ func (c *Config) Validate() error { } } - if c.Bpjs.BaseURL == "" { - log.Fatal("BPJS Base URL is required") - } - if c.Bpjs.ConsID == "" { - log.Fatal("BPJS Consumer ID is required") - } - if c.Bpjs.UserKey == "" { - log.Fatal("BPJS User Key is required") - } - if c.Bpjs.SecretKey == "" { - log.Fatal("BPJS Secret Key is required") - } + // if c.Bpjs.BaseURL == "" { + // log.Fatal("BPJS Base URL is required") + // } + // if c.Bpjs.ConsID == "" { + // log.Fatal("BPJS Consumer ID is required") + // } + // if c.Bpjs.UserKey == "" { + // log.Fatal("BPJS User Key is required") + // } + // if c.Bpjs.SecretKey == "" { + // log.Fatal("BPJS Secret Key is required") + // } // Validate Keycloak configuration if enabled if c.Keycloak.Enabled { @@ -716,24 +716,24 @@ func (c *Config) Validate() error { } // Validate SatuSehat configuration - if c.SatuSehat.OrgID == "" { - log.Fatal("SatuSehat Organization ID is required") - } - if c.SatuSehat.FasyakesID == "" { - log.Fatal("SatuSehat Fasyankes ID is required") - } - if c.SatuSehat.ClientID == "" { - log.Fatal("SatuSehat Client ID is required") - } - if c.SatuSehat.ClientSecret == "" { - log.Fatal("SatuSehat Client Secret is required") - } - if c.SatuSehat.AuthURL == "" { - log.Fatal("SatuSehat Auth URL is required") - } - if c.SatuSehat.BaseURL == "" { - log.Fatal("SatuSehat Base URL is required") - } + // if c.SatuSehat.OrgID == "" { + // log.Fatal("SatuSehat Organization ID is required") + // } + // if c.SatuSehat.FasyakesID == "" { + // log.Fatal("SatuSehat Fasyankes ID is required") + // } + // if c.SatuSehat.ClientID == "" { + // log.Fatal("SatuSehat Client ID is required") + // } + // if c.SatuSehat.ClientSecret == "" { + // log.Fatal("SatuSehat Client Secret is required") + // } + // if c.SatuSehat.AuthURL == "" { + // log.Fatal("SatuSehat Auth URL is required") + // } + // if c.SatuSehat.BaseURL == "" { + // log.Fatal("SatuSehat Base URL is required") + // } return nil } diff --git a/internal/handlers/kiosk/listkiosk.go b/internal/handlers/kiosk/listkiosk.go new file mode 100644 index 00000000..d59b7547 --- /dev/null +++ b/internal/handlers/kiosk/listkiosk.go @@ -0,0 +1,871 @@ +package handlers + +import ( + "api-service/internal/config" + "api-service/internal/database" + models "api-service/internal/models" + "api-service/internal/models/kiosk" + "api-service/internal/utils/validation" + "context" + "database/sql" + "fmt" + "log" + "net/http" + "strconv" + "strings" + "sync" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" +) + +var ( + listkioskdb database.Service + listkioskonce sync.Once + listkioskvalidate *validator.Validate +) + +// Initialize the database connection and validator +func init() { + listkioskonce.Do(func() { + listkioskdb = database.New(config.LoadConfig()) + listkioskvalidate = validator.New() + listkioskvalidate.RegisterValidation("listkiosk_status", validateListkioskStatus) + if listkioskdb == nil { + log.Fatal("Failed to initialize database connection") + } + }) +} + +// Custom validation for listkiosk status +func validateListkioskStatus(fl validator.FieldLevel) bool { + return models.IsValidStatus(fl.Field().String()) +} + +// ListkioskHandler handles listkiosk services +type ListkioskHandler struct { + db database.Service +} + +// NewListkioskHandler creates a new ListkioskHandler +func NewListkioskHandler() *ListkioskHandler { + return &ListkioskHandler{ + db: listkioskdb, + } +} + +// GetListkiosk godoc +// @Summary Get listkiosk with pagination and optional aggregation +// @Description Returns a paginated list of listkiosks with optional summary statistics +// @Tags Listkiosk +// @Accept json +// @Produce json +// @Param limit query int false "Limit (max 100)" default(10) +// @Param offset query int false "Offset" default(0) +// @Param include_summary query bool false "Include aggregation summary" default(false) +// @Param status query string false "Filter by status" +// @Param search query string false "Search in multiple fields" +// @Success 200 {object} kiosk.ListkioskGetResponse "Success response" +// @Failure 400 {object} models.ErrorResponse "Bad request" +// @Failure 500 {object} models.ErrorResponse "Internal server error" +// @Router /kiosk/listkiosk [get] +func (h *ListkioskHandler) GetListkiosk(c *gin.Context) { + // Parse pagination parameters + limit, offset, err := h.parsePaginationParams(c) + if err != nil { + h.respondError(c, "Invalid pagination parameters", err, http.StatusBadRequest) + return + } + + // Parse filter parameters + filter := h.parseFilterParams(c) + includeAggregation := c.Query("include_summary") == "true" + + // Get database connection + dbConn, err := h.db.GetDB("db_antrean") + if err != nil { + h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError) + return + } + + // Create context with timeout + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + // Execute concurrent operations + var ( + items []kiosk.Listkiosk + total int + aggregateData *models.AggregateData + wg sync.WaitGroup + errChan = make(chan error, 3) + mu sync.Mutex + ) + + // Fetch total count + wg.Add(1) + go func() { + defer wg.Done() + if err := h.getTotalCount(ctx, dbConn, filter, &total); err != nil { + mu.Lock() + errChan <- fmt.Errorf("failed to get total count: %w", err) + mu.Unlock() + } + }() + + // Fetch main data + wg.Add(1) + go func() { + defer wg.Done() + result, err := h.fetchListkiosks(ctx, dbConn, filter, limit, offset) + mu.Lock() + if err != nil { + errChan <- fmt.Errorf("failed to fetch data: %w", err) + } else { + items = result + } + mu.Unlock() + }() + + // Fetch aggregation data if requested + if includeAggregation { + wg.Add(1) + go func() { + defer wg.Done() + result, err := h.getAggregateData(ctx, dbConn, filter) + mu.Lock() + if err != nil { + errChan <- fmt.Errorf("failed to get aggregate data: %w", err) + } else { + aggregateData = result + } + mu.Unlock() + }() + } + + // Wait for all goroutines + wg.Wait() + close(errChan) + + // Check for errors + for err := range errChan { + if err != nil { + h.logAndRespondError(c, "Data processing failed", err, http.StatusInternalServerError) + return + } + } + + // Build response + meta := h.calculateMeta(limit, offset, total) + response := kiosk.ListkioskGetResponse{ + Message: "Data kiosk berhasil diambil", + Data: items, + Meta: meta, + } + + if includeAggregation && aggregateData != nil { + response.Summary = aggregateData + } + + c.JSON(http.StatusOK, response) +} + +// GetListkioskByID godoc +// @Summary Get Listkiosk by ID +// @Description Returns a single listkiosk by ID +// @Tags Listkiosk +// @Accept json +// @Produce json +// @Param id path string true "Listkiosk ID (UUID)" +// @Success 200 {object} kiosk.ListkioskGetByIDResponse "Success response" +// @Failure 400 {object} models.ErrorResponse "Invalid ID format" +// @Failure 404 {object} models.ErrorResponse "Listkiosk not found" +// @Failure 500 {object} models.ErrorResponse "Internal server error" +// @Router /api/v1/listkiosk/{id} [get] +// func (h *ListkioskHandler) GetListkioskByID(c *gin.Context) { +// id := c.Param("id") + +// // Validate UUID format +// if _, err := uuid.Parse(id); err != nil { +// h.respondError(c, "Invalid ID format", err, http.StatusBadRequest) +// return +// } + +// dbConn, err := h.db.GetDB("postgres_satudata") +// if err != nil { +// h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError) +// return +// } + +// ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second) +// defer cancel() + +// item, err := h.getListkioskByID(ctx, dbConn, id) +// if err != nil { +// if err == sql.ErrNoRows { +// h.respondError(c, "Listkiosk not found", err, http.StatusNotFound) +// } else { +// h.logAndRespondError(c, "Failed to get listkiosk", err, http.StatusInternalServerError) +// } +// return +// } + +// response := kiosk.ListkioskGetByIDResponse{ +// Message: "kiosk details retrieved successfully", +// Data: item, +// } + +// c.JSON(http.StatusOK, response) +// } + +// CreateListkiosk godoc +// @Summary Create listkiosk +// @Description Creates a new listkiosk record +// @Tags Listkiosk +// @Accept json +// @Produce json +// @Param request body kiosk.ListkioskCreateRequest true "Listkiosk creation request" +// @Success 201 {object} kiosk.ListkioskCreateResponse "Listkiosk created successfully" +// @Failure 400 {object} models.ErrorResponse "Bad request or validation error" +// @Failure 500 {object} models.ErrorResponse "Internal server error" +// @Router /kiosk [post] +func (h *ListkioskHandler) CreateKiosk(c *gin.Context) { + var req kiosk.ListkioskCreateRequest + if err := c.ShouldBindJSON(&req); err != nil { + h.respondError(c, "Invalid request body", err, http.StatusBadRequest) + return + } + + // Validate request + if err := listkioskvalidate.Struct(&req); err != nil { + h.respondError(c, "Validation failed", err, http.StatusBadRequest) + return + } + + dbConn, err := h.db.GetDB("db_antrean") + if err != nil { + h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError) + return + } + + ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second) + defer cancel() + + // Validate duplicate and daily submission + if err := h.validateListkioskSubmission(ctx, dbConn, &req); err != nil { + h.respondError(c, "Validation failed", err, http.StatusBadRequest) + return + } + + item, err := h.createKiosk(ctx, dbConn, &req) + if err != nil { + h.logAndRespondError(c, "Failed to create listkiosk", err, http.StatusInternalServerError) + return + } + + response := kiosk.ListkioskCreateResponse{ + Message: "Listkiosk berhasil dibuat", + Data: item, + } + + c.JSON(http.StatusCreated, response) +} + +// UpdateListkiosk godoc +// @Summary Update listkiosk +// @Description Updates an existing listkiosk record +// @Tags Listkiosk +// @Accept json +// @Produce json +// @Param id path int true "Kiosk ID (integer)" +// @Param request body kiosk.ListkioskUpdateRequest true "Listkiosk update request" +// @Success 200 {object} kiosk.ListkioskUpdateResponse "Listkiosk updated successfully" +// @Failure 400 {object} models.ErrorResponse "Bad request or validation error" +// @Failure 404 {object} models.ErrorResponse "Listkiosk not found" +// @Failure 500 {object} models.ErrorResponse "Internal server error" +// @Router /kiosk/{id} [put] +func (h *ListkioskHandler) UpdateKiosk(c *gin.Context) { + id := c.Param("id") + + // Validate UUID format + // if _, err := uuid.Parse(id); err != nil { + // h.respondError(c, "Invalid ID format", err, http.StatusBadRequest) + // return + // } + + // Validate ID is integer + if _, err := strconv.Atoi(id); err != nil { + h.respondError(c, "Invalid ID format", err, http.StatusBadRequest) + return + } + + var req kiosk.ListkioskUpdateRequest + if err := c.ShouldBindJSON(&req); err != nil { + h.respondError(c, "Invalid request body", err, http.StatusBadRequest) + return + } + + // Set ID from path parameter + // req.ID = id + + // Set ID from path parameter + idInt, _ := strconv.Atoi(id) + req.ID = idInt + + // Validate request + if err := listkioskvalidate.Struct(&req); err != nil { + h.respondError(c, "Validation failed", err, http.StatusBadRequest) + return + } + + dbConn, err := h.db.GetDB("db_antrean") + if err != nil { + h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError) + return + } + + ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second) + defer cancel() + + item, err := h.updateKiosk(ctx, dbConn, &req) + if err != nil { + if err == sql.ErrNoRows { + h.respondError(c, "Listkiosk not found", err, http.StatusNotFound) + } else { + h.logAndRespondError(c, "Failed to update listkiosk", err, http.StatusInternalServerError) + } + return + } + + response := kiosk.ListkioskUpdateResponse{ + Message: "Listkiosk berhasil diperbarui", + Data: item, + } + + c.JSON(http.StatusOK, response) +} + +// DeleteListkiosk godoc +// @Summary Delete listkiosk +// @Description Soft deletes a listkiosk by setting status to 'deleted' +// @Tags Listkiosk +// @Accept json +// @Produce json +// @Param id path int true "Kiosk ID (integer)" +// @Success 200 {object} kiosk.ListkioskDeleteResponse "Listkiosk deleted successfully" +// @Failure 400 {object} models.ErrorResponse "Invalid ID format" +// @Failure 404 {object} models.ErrorResponse "Listkiosk not found" +// @Failure 500 {object} models.ErrorResponse "Internal server error" +// @Router /kiosk/{id} [delete] +func (h *ListkioskHandler) DeleteKiosk(c *gin.Context) { + id := c.Param("id") + + // Validate ID is integer + if _, err := strconv.Atoi(id); err != nil { + h.respondError(c, "Invalid ID format", err, http.StatusBadRequest) + return + } + + dbConn, err := h.db.GetDB("db_antrean") + if err != nil { + h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError) + return + } + + ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second) + defer cancel() + + err = h.deleteKiosk(ctx, dbConn, id) + if err != nil { + if err == sql.ErrNoRows { + h.respondError(c, "Listkiosk not found", err, http.StatusNotFound) + } else { + h.logAndRespondError(c, "Failed to delete listkiosk", err, http.StatusInternalServerError) + } + return + } + + response := kiosk.ListkioskDeleteResponse{ + Message: "Listkiosk berhasil dihapus", + ID: id, + } + + c.JSON(http.StatusOK, response) +} + +// GetListkioskStats godoc +// @Summary Get listkiosk statistics +// @Description Returns comprehensive statistics about listkiosk data +// @Tags Listkiosk +// @Accept json +// @Produce json +// @Param status query string false "Filter statistics by status" +// @Success 200 {object} models.AggregateData "Statistics data" +// @Failure 500 {object} models.ErrorResponse "Internal server error" +// @Router /api/v1/listkiosks/stats [get] +func (h *ListkioskHandler) GetKioskStats(c *gin.Context) { + dbConn, err := h.db.GetDB("db_antrean") + if err != nil { + h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError) + return + } + + ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second) + defer cancel() + + filter := h.parseFilterParams(c) + aggregateData, err := h.getAggregateData(ctx, dbConn, filter) + if err != nil { + h.logAndRespondError(c, "Failed to get statistics", err, http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Statistik listkiosk berhasil diambil", + "data": aggregateData, + }) +} + +// Database operations +// func (h *ListkioskHandler) getListkioskByID(ctx context.Context, dbConn *sql.DB, id string) (*kiosk.Listkiosk, error) { +// query := "SELECT id, status, sort, user_created, date_created, user_updated, date_updated, name FROM data_kiosk_listkiosk WHERE id = $1 AND status != 'deleted'" +// row := dbConn.QueryRowContext(ctx, query, id) + +// var item kiosk.Listkiosk +// err := row.Scan(&item.ID, &item.Status, &item.Sort, &item.UserCreated, &item.DateCreated, &item.UserUpdated, &item.DateUpdated, &item.Name) +// if err != nil { +// return nil, err +// } + +// return &item, nil +// } + +func (h *ListkioskHandler) createKiosk(ctx context.Context, dbConn *sql.DB, req *kiosk.ListkioskCreateRequest) (*kiosk.Listkiosk, error) { + // id := uuid.New().String() + // now := time.Now() + + query := `INSERT INTO master.ms_kiosk + (name, icon, url, active, fk_ref_healthcare_type_id, fk_ref_service_type_id, fk_sd_location_id, ds_sd_location) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + RETURNING id, name, icon, url, active, fk_ref_healthcare_type_id, fk_ref_service_type_id, fk_sd_location_id, ds_sd_location` + + row := dbConn.QueryRowContext(ctx, query, req.Name, req.Icon, req.Url, req.Active, req.FKRefHealthcareTypeID, req.FKRefServiceTypeID, req.FKSdLocationID, req.DsSdLocation) + + var item kiosk.Listkiosk + err := row.Scan(&item.ID, &item.Name, &item.Icon, &item.Url, &item.Active, &item.FKRefHealthcareTypeID, &item.FKRefServiceTypeID, &item.FKSdLocationID, &item.DsSdLocation) + if err != nil { + return nil, fmt.Errorf("failed to create kiosk: %w", err) + } + + return &item, nil +} + +func (h *ListkioskHandler) updateKiosk(ctx context.Context, dbConn *sql.DB, req *kiosk.ListkioskUpdateRequest) (*kiosk.Listkiosk, error) { + // now := time.Now() + + query := `UPDATE master.ms_kiosk + SET name = $2, icon = $3, url = $4, active = $5, fk_ref_healthcare_type_id = $6, fk_ref_service_type_id = $7, fk_sd_location_id = $8, ds_sd_location = $9 + WHERE id = $1 + RETURNING id, name, icon, url, active, fk_ref_healthcare_type_id, fk_ref_service_type_id, fk_sd_location_id, ds_sd_location` + + row := dbConn.QueryRowContext(ctx, query, req.ID, req.Name, req.Icon, req.Url, req.Active, req.FKRefHealthcareTypeID, req.FKRefServiceTypeID, req.FKSdLocationID, req.DsSdLocation) + + var item kiosk.Listkiosk + err := row.Scan(&item.ID, &item.Name, &item.Icon, &item.Url, &item.Active, &item.FKRefHealthcareTypeID, &item.FKRefServiceTypeID, &item.FKSdLocationID, &item.DsSdLocation) + if err != nil { + return nil, fmt.Errorf("failed to update kiosk: %w", err) + } + + return &item, nil +} + +func (h *ListkioskHandler) deleteKiosk(ctx context.Context, dbConn *sql.DB, id string) error { + // now := time.Now() + query := `UPDATE master.ms_kiosk SET active = false + WHERE id = $1` + + result, err := dbConn.ExecContext(ctx, query, id) + if err != nil { + return fmt.Errorf("failed to delete kiosk: %w", err) + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("failed to get affected rows: %w", err) + } + + if rowsAffected == 0 { + return sql.ErrNoRows + } + + return nil +} + +func (h *ListkioskHandler) fetchListkiosks(ctx context.Context, dbConn *sql.DB, filter kiosk.ListkioskFilter, limit, offset int) ([]kiosk.Listkiosk, error) { + whereClause, args := h.buildWhereClause(filter) + query := fmt.Sprintf(`SELECT id, name, icon, url, active, fk_ref_healthcare_type_id, fk_ref_service_type_id, fk_sd_location_id, ds_sd_location + FROM master.ms_kiosk + WHERE %s LIMIT $%d OFFSET $%d`, whereClause, len(args)+1, len(args)+2) + args = append(args, limit, offset) + + rows, err := dbConn.QueryContext(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("fetch listkiosks query failed: %w", err) + } + defer rows.Close() + + items := make([]kiosk.Listkiosk, 0, limit) + for rows.Next() { + item, err := h.scanListkiosk(rows) + if err != nil { + return nil, fmt.Errorf("scan Listkiosk failed: %w", err) + } + items = append(items, item) + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("rows iteration error: %w", err) + } + + log.Printf("Successfully fetched %d listkiosks with filters applied", len(items)) + return items, nil +} + +// Optimized scanning function +func (h *ListkioskHandler) scanListkiosk(rows *sql.Rows) (kiosk.Listkiosk, error) { + var item kiosk.Listkiosk + + // Scan into individual fields to handle nullable types properly + err := rows.Scan( + &item.ID, + &item.Name, + &item.Icon, + &item.Url, + &item.Active, + &item.FKRefHealthcareTypeID, + &item.FKRefServiceTypeID, + &item.FKSdLocationID, + &item.DsSdLocation, + ) + + return item, err +} + +func (h *ListkioskHandler) getTotalCount(ctx context.Context, dbConn *sql.DB, filter kiosk.ListkioskFilter, total *int) error { + whereClause, args := h.buildWhereClause(filter) + countQuery := fmt.Sprintf("SELECT COUNT(*) FROM master.ms_kiosk WHERE %s", whereClause) + if err := dbConn.QueryRowContext(ctx, countQuery, args...).Scan(total); err != nil { + return fmt.Errorf("total count query failed: %w", err) + } + return nil +} + +// Get comprehensive aggregate data dengan filter support +func (h *ListkioskHandler) getAggregateData(ctx context.Context, dbConn *sql.DB, filter kiosk.ListkioskFilter) (*models.AggregateData, error) { + aggregate := &models.AggregateData{ + ByStatus: make(map[string]int), + } + + // Build where clause untuk filter + whereClause, args := h.buildWhereClause(filter) + + // Use concurrent execution untuk performance + var wg sync.WaitGroup + var mu sync.Mutex + errChan := make(chan error, 4) + + // 1. Count by status + wg.Add(1) + go func() { + defer wg.Done() + statusQuery := fmt.Sprintf("SELECT status, COUNT(*) FROM master.ms_kiosk WHERE %s GROUP BY active ORDER BY active", whereClause) + + rows, err := dbConn.QueryContext(ctx, statusQuery, args...) + if err != nil { + errChan <- fmt.Errorf("status query failed: %w", err) + return + } + defer rows.Close() + + mu.Lock() + for rows.Next() { + var status string + var count int + if err := rows.Scan(&status, &count); err != nil { + mu.Unlock() + errChan <- fmt.Errorf("status scan failed: %w", err) + return + } + aggregate.ByStatus[status] = count + switch status { + case "active": + aggregate.TotalActive = count + case "draft": + aggregate.TotalDraft = count + case "inactive": + aggregate.TotalInactive = count + } + } + mu.Unlock() + + if err := rows.Err(); err != nil { + errChan <- fmt.Errorf("status iteration error: %w", err) + } + }() + + // 2. Get last updated time dan today statistics + wg.Add(1) + go func() { + defer wg.Done() + + // Last updated + lastUpdatedQuery := fmt.Sprintf("SELECT MAX(date_updated) FROM master.ms_kiosk WHERE %s AND date_updated IS NOT NULL", whereClause) + var lastUpdated sql.NullTime + if err := dbConn.QueryRowContext(ctx, lastUpdatedQuery, args...).Scan(&lastUpdated); err != nil { + errChan <- fmt.Errorf("last updated query failed: %w", err) + return + } + + // Today statistics + today := time.Now().Format("2006-01-02") + todayStatsQuery := fmt.Sprintf(` + SELECT + SUM(CASE WHEN DATE(date_created) = $%d THEN 1 ELSE 0 END) as created_today, + SUM(CASE WHEN DATE(date_updated) = $%d AND DATE(date_created) != $%d THEN 1 ELSE 0 END) as updated_today + FROM master.ms_kiosk + WHERE %s`, len(args)+1, len(args)+1, len(args)+1, whereClause) + + todayArgs := append(args, today) + var createdToday, updatedToday int + if err := dbConn.QueryRowContext(ctx, todayStatsQuery, todayArgs...).Scan(&createdToday, &updatedToday); err != nil { + errChan <- fmt.Errorf("today stats query failed: %w", err) + return + } + + mu.Lock() + if lastUpdated.Valid { + aggregate.LastUpdated = &lastUpdated.Time + } + aggregate.CreatedToday = createdToday + aggregate.UpdatedToday = updatedToday + mu.Unlock() + }() + + // Wait for all goroutines + wg.Wait() + close(errChan) + + // Check for errors + for err := range errChan { + if err != nil { + return nil, err + } + } + + return aggregate, nil +} + +// Enhanced error handling +func (h *ListkioskHandler) logAndRespondError(c *gin.Context, message string, err error, statusCode int) { + log.Printf("[ERROR] %s: %v", message, err) + h.respondError(c, message, err, statusCode) +} + +func (h *ListkioskHandler) respondError(c *gin.Context, message string, err error, statusCode int) { + errorMessage := message + if gin.Mode() == gin.ReleaseMode { + errorMessage = "Internal server error" + } + + c.JSON(statusCode, models.ErrorResponse{ + Error: errorMessage, + Code: statusCode, + Message: err.Error(), + Timestamp: time.Now(), + }) +} + +// Parse pagination parameters dengan validation yang lebih ketat +func (h *ListkioskHandler) parsePaginationParams(c *gin.Context) (int, int, error) { + limit := 10 // Default limit + offset := 0 // Default offset + + if limitStr := c.Query("limit"); limitStr != "" { + parsedLimit, err := strconv.Atoi(limitStr) + if err != nil { + return 0, 0, fmt.Errorf("invalid limit parameter: %s", limitStr) + } + if parsedLimit <= 0 { + return 0, 0, fmt.Errorf("limit must be greater than 0") + } + if parsedLimit > 100 { + return 0, 0, fmt.Errorf("limit cannot exceed 100") + } + limit = parsedLimit + } + + if offsetStr := c.Query("offset"); offsetStr != "" { + parsedOffset, err := strconv.Atoi(offsetStr) + if err != nil { + return 0, 0, fmt.Errorf("invalid offset parameter: %s", offsetStr) + } + if parsedOffset < 0 { + return 0, 0, fmt.Errorf("offset cannot be negative") + } + offset = parsedOffset + } + + log.Printf("Pagination - Limit: %d, Offset: %d", limit, offset) + return limit, offset, nil +} + +func (h *ListkioskHandler) parseFilterParams(c *gin.Context) kiosk.ListkioskFilter { + filter := kiosk.ListkioskFilter{} + + if status := c.Query("active"); status != "" { + if models.IsValidStatus(status) { + filter.Status = &status + } + } + + if search := c.Query("search"); search != "" { + filter.Search = &search + } + + // Parse date filters + if dateFromStr := c.Query("date_from"); dateFromStr != "" { + if dateFrom, err := time.Parse("2006-01-02", dateFromStr); err == nil { + filter.DateFrom = &dateFrom + } + } + + if dateToStr := c.Query("date_to"); dateToStr != "" { + if dateTo, err := time.Parse("2006-01-02", dateToStr); err == nil { + filter.DateTo = &dateTo + } + } + + return filter +} + +// Build WHERE clause dengan filter parameters +func (h *ListkioskHandler) buildWhereClause(filter kiosk.ListkioskFilter) (string, []interface{}) { + conditions := []string{"1=1"} + args := []interface{}{} + paramCount := 1 + + if filter.Status != nil { + conditions = append(conditions, fmt.Sprintf("active = $%d", paramCount)) + args = append(args, *filter.Status) + paramCount++ + } + + if filter.Search != nil { + searchCondition := fmt.Sprintf("name ILIKE $%d", paramCount) + conditions = append(conditions, searchCondition) + searchTerm := "%" + *filter.Search + "%" + args = append(args, searchTerm) + paramCount++ + } + + // if filter.DateFrom != nil { + // conditions = append(conditions, fmt.Sprintf("date_created >= $%d", paramCount)) + // args = append(args, *filter.DateFrom) + // paramCount++ + // } + + // if filter.DateTo != nil { + // conditions = append(conditions, fmt.Sprintf("date_created <= $%d", paramCount)) + // args = append(args, filter.DateTo.Add(24*time.Hour-time.Nanosecond)) + // paramCount++ + // } + + return strings.Join(conditions, " AND "), args +} + +func (h *ListkioskHandler) calculateMeta(limit, offset, total int) models.MetaResponse { + totalPages := 0 + currentPage := 1 + if limit > 0 { + totalPages = (total + limit - 1) / limit // Ceiling division + currentPage = (offset / limit) + 1 + } + + return models.MetaResponse{ + Limit: limit, + Offset: offset, + Total: total, + TotalPages: totalPages, + CurrentPage: currentPage, + HasNext: offset+limit < total, + HasPrev: offset > 0, + } +} + +// validateListkioskSubmission performs validation for duplicate entries and daily submission limits +func (h *ListkioskHandler) validateListkioskSubmission(ctx context.Context, dbConn *sql.DB, req *kiosk.ListkioskCreateRequest) error { + // Import the validation utility + validator := validation.NewDuplicateValidator(dbConn) + + // Use default configuration + config := validation.ValidationConfig{ + TableName: "master.ms_kiosk", + IDColumn: "id", + // StatusColumn: "active", + // DateColumn: "date_created", + ActiveStatuses: []string{"active"}, + AdditionalFields: map[string]interface{}{ + "name": req.Name, + }, + } + + // Prepare fields for validation + fields := map[string]interface{}{ + "name": req.Name, + "active": true, + } + + err := validator.ValidateDuplicateWithCustomFields(ctx, config, fields) + if err != nil { + return fmt.Errorf("validation failed: %w", err) + } + + return nil +} + +// Example usage of the validation utility with custom configuration +// func (h *ListkioskHandler) validateWithCustomConfig(ctx context.Context, dbConn *sql.DB, req *kiosk.ListkioskCreateRequest) error { +// // Create validator instance +// validator := validation.NewDuplicateValidator(dbConn) + +// // Use custom configuration +// config := validation.ValidationConfig{ +// TableName: "master.ms_kiosk", +// IDColumn: "id", +// StatusColumn: "active", +// DateColumn: "date_created", +// ActiveStatuses: []string{"true", "false"}, +// AdditionalFields: map[string]interface{}{ +// "name": req.Name, +// }, +// } + +// // Validate with custom fields +// fields := map[string]interface{}{ +// "name": *req.Name, +// } + +// err := validator.ValidateDuplicateWithCustomFields(ctx, config, fields) +// if err != nil { +// return fmt.Errorf("custom validation failed: %w", err) +// } + +// return nil +// } + +// GetLastSubmissionTime example +func (h *ListkioskHandler) getLastSubmissionTimeExample(ctx context.Context, dbConn *sql.DB, identifier string) (*time.Time, error) { + validator := validation.NewDuplicateValidator(dbConn) + return validator.GetLastSubmissionTime(ctx, "master.ms_kiosk", "id", "date_created", identifier) +} diff --git a/internal/handlers/rujukan/rujukan.go b/internal/handlers/rujukan/rujukan.go deleted file mode 100644 index f6076598..00000000 --- a/internal/handlers/rujukan/rujukan.go +++ /dev/null @@ -1,885 +0,0 @@ -// Package rujukan handles Rujukan BPJS services -// Generated on: 2025-09-07 11:01:18 -package handlers - -import ( - "context" - "encoding/json" - "net/http" - "strings" - "time" - - "api-service/internal/config" - "api-service/internal/models" - "api-service/internal/models/vclaim/rujukan" - "api-service/internal/services/bpjs" - "api-service/pkg/logger" - - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" - "github.com/google/uuid" -) -// RujukanHandler handles Rujukan BPJS services -type RujukanHandler struct { - service services.VClaimService - validator *validator.Validate - logger logger.Logger - config config.BpjsConfig -} -// RujukanHandlerConfig contains configuration for RujukanHandler -type RujukanHandlerConfig struct { - BpjsConfig config.BpjsConfig - Logger logger.Logger - Validator *validator.Validate -} -// NewRujukanHandler creates a new RujukanHandler -func NewRujukanHandler(cfg RujukanHandlerConfig) *RujukanHandler { - return &RujukanHandler{ - service: services.NewService(cfg.BpjsConfig), - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.BpjsConfig, - } -} - - - - -// CreateRujukan godoc -// @Summary Create new Rujukan -// @Description Create new Rujukan in BPJS system -// @Tags Rujukan -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param request body rujukan.RujukanRequest true "Rujukan data" -// @Success 201 {object} rujukan.RujukanResponse "Successfully created Rujukan" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router /Rujukan/:norujukan [post] -func (h *RujukanHandler) CreateRujukan(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - - h.logger.Info("Processing CreateRujukan request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "/Rujukan", - }) - - - // Bind and validate request body - var req rujukan.RujukanRequest - if err := c.ShouldBindJSON(&req); err != nil { - - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - - - // Call service method - var response rujukan.RujukanResponse - - resp, err := h.service.PostRawResponse(ctx, "/Rujukan", req) - - if err != nil { - - h.logger.Error("Failed to create Rujukan", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "Rujukan already exists or conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &rujukan.RujukanData{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["rujukan"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - - h.logger.Info("Successfully created Rujukan", map[string]interface{}{ - "request_id": requestID, - }) - - - c.JSON(http.StatusCreated, response) -} - - - - -// UpdateRujukan godoc -// @Summary Update existing Rujukan -// @Description Update existing Rujukan in BPJS system -// @Tags Rujukan -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param request body rujukan.RujukanRequest true "Rujukan update data" -// @Success 200 {object} rujukan.RujukanResponse "Successfully updated Rujukan" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - Rujukan not found" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict - update conflict occurred" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router /Rujukan/:norujukan [put] -func (h *RujukanHandler) UpdateRujukan(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - - h.logger.Info("Processing UpdateRujukan request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "/Rujukan", - - }) - - - // Extract path parameters - - - // Bind and validate request body - var req rujukan.RujukanRequest - if err := c.ShouldBindJSON(&req); err != nil { - - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Call service method - var response rujukan.RujukanResponse - - resp, err := h.service.PutRawResponse(ctx, "/Rujukan", req) - - if err != nil { - - h.logger.Error("Failed to update Rujukan", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "Rujukan not found", - RequestID: requestID, - }) - return - } - - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "Update conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &rujukan.RujukanData{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["rujukan"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - - h.logger.Info("Successfully updated Rujukan", map[string]interface{}{ - "request_id": requestID, - }) - - - c.JSON(http.StatusOK, response) -} - - - -// DeleteRujukan godoc -// @Summary Delete existing Rujukan -// @Description Delete existing Rujukan from BPJS system -// @Tags Rujukan -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Param X-Request-ID header string false "Request ID for tracking" -// @Success 200 {object} rujukan.RujukanResponse "Successfully deleted Rujukan" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - Rujukan not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router /Rujukan/:norujukan [delete] -func (h *RujukanHandler) DeleteRujukan(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - - h.logger.Info("Processing DeleteRujukan request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "/Rujukan", - - }) - - - // Extract path parameters - - - // Call service method - var response rujukan.RujukanResponse - - resp, err := h.service.DeleteRawResponse(ctx, "/Rujukan") - - if err != nil { - - h.logger.Error("Failed to delete Rujukan", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "Rujukan not found", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &rujukan.RujukanData{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["rujukan"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } else { - // For delete operations, sometimes there's no data in response - response.Data = nil - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - - h.logger.Info("Successfully deleted Rujukan", map[string]interface{}{ - "request_id": requestID, - }) - - - c.JSON(http.StatusOK, response) -} - - - - - -// CreateRujukanbalik godoc -// @Summary Create new Rujukanbalik -// @Description Create new Rujukanbalik in BPJS system -// @Tags Rujukan -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param request body rujukan.RujukanRequest true "Rujukanbalik data" -// @Success 201 {object} rujukan.RujukanResponse "Successfully created Rujukanbalik" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router /Rujukanbalik/:norujukan [post] -func (h *RujukanHandler) CreateRujukanbalik(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - - h.logger.Info("Processing CreateRujukanbalik request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "/Rujukanbalik", - }) - - - // Bind and validate request body - var req rujukan.RujukanRequest - if err := c.ShouldBindJSON(&req); err != nil { - - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - - - // Call service method - var response rujukan.RujukanResponse - - resp, err := h.service.PostRawResponse(ctx, "/Rujukanbalik", req) - - if err != nil { - - h.logger.Error("Failed to create Rujukanbalik", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "Rujukanbalik already exists or conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &rujukan.RujukanData{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["rujukan"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - - h.logger.Info("Successfully created Rujukanbalik", map[string]interface{}{ - "request_id": requestID, - }) - - - c.JSON(http.StatusCreated, response) -} - - - - -// UpdateRujukanbalik godoc -// @Summary Update existing Rujukanbalik -// @Description Update existing Rujukanbalik in BPJS system -// @Tags Rujukan -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param request body rujukan.RujukanRequest true "Rujukanbalik update data" -// @Success 200 {object} rujukan.RujukanResponse "Successfully updated Rujukanbalik" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - Rujukanbalik not found" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict - update conflict occurred" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router /Rujukanbalik/:norujukan [put] -func (h *RujukanHandler) UpdateRujukanbalik(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - - h.logger.Info("Processing UpdateRujukanbalik request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "/Rujukanbalik", - - }) - - - // Extract path parameters - - - // Bind and validate request body - var req rujukan.RujukanRequest - if err := c.ShouldBindJSON(&req); err != nil { - - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Call service method - var response rujukan.RujukanResponse - - resp, err := h.service.PutRawResponse(ctx, "/Rujukanbalik", req) - - if err != nil { - - h.logger.Error("Failed to update Rujukanbalik", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "Rujukanbalik not found", - RequestID: requestID, - }) - return - } - - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "Update conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &rujukan.RujukanData{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["rujukan"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - - h.logger.Info("Successfully updated Rujukanbalik", map[string]interface{}{ - "request_id": requestID, - }) - - - c.JSON(http.StatusOK, response) -} - - - -// DeleteRujukanbalik godoc -// @Summary Delete existing Rujukanbalik -// @Description Delete existing Rujukanbalik from BPJS system -// @Tags Rujukan -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Param X-Request-ID header string false "Request ID for tracking" -// @Success 200 {object} rujukan.RujukanResponse "Successfully deleted Rujukanbalik" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - Rujukanbalik not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router /Rujukanbalik/:norujukan [delete] -func (h *RujukanHandler) DeleteRujukanbalik(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - - h.logger.Info("Processing DeleteRujukanbalik request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "/Rujukanbalik", - - }) - - - // Extract path parameters - - - // Call service method - var response rujukan.RujukanResponse - - resp, err := h.service.DeleteRawResponse(ctx, "/Rujukanbalik") - - if err != nil { - - h.logger.Error("Failed to delete Rujukanbalik", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "Rujukanbalik not found", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &rujukan.RujukanData{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["rujukan"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } else { - // For delete operations, sometimes there's no data in response - response.Data = nil - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - - h.logger.Info("Successfully deleted Rujukanbalik", map[string]interface{}{ - "request_id": requestID, - }) - - - c.JSON(http.StatusOK, response) -} - diff --git a/internal/handlers/rujukan/search.go b/internal/handlers/rujukan/search.go deleted file mode 100644 index 3f41afa5..00000000 --- a/internal/handlers/rujukan/search.go +++ /dev/null @@ -1,291 +0,0 @@ -// Package rujukan handles Search BPJS services -// Generated on: 2025-09-07 11:01:18 -package handlers - -import ( - "context" - "encoding/json" - "net/http" - "strings" - "time" - - "api-service/internal/config" - "api-service/internal/models" - "api-service/internal/models/vclaim/rujukan" - "api-service/internal/services/bpjs" - "api-service/pkg/logger" - - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" - "github.com/google/uuid" -) -// SearchHandler handles Search BPJS services -type SearchHandler struct { - service services.VClaimService - validator *validator.Validate - logger logger.Logger - config config.BpjsConfig -} -// SearchHandlerConfig contains configuration for SearchHandler -type SearchHandlerConfig struct { - BpjsConfig config.BpjsConfig - Logger logger.Logger - Validator *validator.Validate -} -// NewSearchHandler creates a new SearchHandler -func NewSearchHandler(cfg SearchHandlerConfig) *SearchHandler { - return &SearchHandler{ - service: services.NewService(cfg.BpjsConfig), - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.BpjsConfig, - } -} - - -// GetBynorujukan godoc -// @Summary Get Bynorujukan data -// @Description Get rujukan by nomor rujukan -// @Tags Rujukan -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param norujukan path string true "norujukan" example("example_value") -// @Success 200 {object} rujukan.RujukanResponse "Successfully retrieved Bynorujukan data" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - Bynorujukan not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router /bynorujukan/:norujukan [get] -func (h *SearchHandler) GetBynorujukan(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - - h.logger.Info("Processing GetBynorujukan request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "/Rujukan/:norujukan", - - "norujukan": c.Param("norujukan"), - - }) - - - // Extract path parameters - - norujukan := c.Param("norujukan") - if norujukan == "" { - - h.logger.Error("Missing required parameter norujukan", map[string]interface{}{ - "request_id": requestID, - }) - - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter norujukan", - RequestID: requestID, - }) - return - } - - - // Call service method - var response rujukan.RujukanResponse - - endpoint := "/Rujukan/:norujukan" - - endpoint = strings.Replace(endpoint, ":norujukan", norujukan, 1) - - resp, err := h.service.GetRawResponse(ctx, endpoint) - - if err != nil { - - h.logger.Error("Failed to get Bynorujukan", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &rujukan.RujukanData{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["rujukan"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} - - - - - - - - - - -// GetBynokartu godoc -// @Summary Get Bynokartu data -// @Description Get rujukan by card number -// @Tags Rujukan -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param nokartu path string true "nokartu" example("example_value") -// @Success 200 {object} rujukan.RujukanResponse "Successfully retrieved Bynokartu data" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - Bynokartu not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router /bynokartu/:nokartu [get] -func (h *SearchHandler) GetBynokartu(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - - h.logger.Info("Processing GetBynokartu request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "/Rujukan/:nokartu", - - "nokartu": c.Param("nokartu"), - - }) - - - // Extract path parameters - - nokartu := c.Param("nokartu") - if nokartu == "" { - - h.logger.Error("Missing required parameter nokartu", map[string]interface{}{ - "request_id": requestID, - }) - - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter nokartu", - RequestID: requestID, - }) - return - } - - - // Call service method - var response rujukan.RujukanResponse - - endpoint := "/Rujukan/:nokartu" - - endpoint = strings.Replace(endpoint, ":nokartu", nokartu, 1) - - resp, err := h.service.GetRawResponse(ctx, endpoint) - - if err != nil { - - h.logger.Error("Failed to get Bynokartu", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &rujukan.RujukanData{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["rujukan"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} - - - - - - - - diff --git a/internal/models/kiosk/listkiosk.go b/internal/models/kiosk/listkiosk.go new file mode 100644 index 00000000..79fcc232 --- /dev/null +++ b/internal/models/kiosk/listkiosk.go @@ -0,0 +1,143 @@ +package kiosk + +import ( + "api-service/internal/models" + "database/sql" + "encoding/json" + "time" +) + +// Listkiosk represents the data structure for the listkiosk table +// with proper null handling and optimized JSON marshaling +type Listkiosk struct { + ID int64 `json:"id" db:"id"` + Name sql.NullString `json:"name,omitempty" db:"name"` + Icon sql.NullString `json:"icon,omitempty" db:"icon"` + Url sql.NullString `json:"url,omitempty" db:"url"` + Active sql.NullBool `json:"active,omitempty" db:"active"` + FKRefHealthcareTypeID models.NullableInt32 `json:"fk_ref_healthcare_type_id,omitempty" db:"fk_ref_healthcare_type_id"` + FKRefServiceTypeID models.NullableInt32 `json:"fk_ref_service_type_id,omitempty" db:"fk_ref_service_type_id"` + FKSdLocationID sql.NullString `json:"fk_sd_location_id,omitempty" db:"fk_sd_location_id"` + DsSdLocation sql.NullString `json:"ds_sd_location,omitempty" db:"ds_sd_location"` +} + +// Custom JSON marshaling untuk Listkiosk agar NULL values tidak muncul di response +func (r Listkiosk) MarshalJSON() ([]byte, error) { + type Alias Listkiosk + aux := &struct { + Name *string `json:"name,omitempty"` + Icon *string `json:"icon,omitempty"` + Url *string `json:"url,omitempty"` + Active *bool `json:"active,omitempty"` + FKRefHealthcareTypeID *int32 `json:"fk_ref_healthcare_type_id,omitempty"` + FKRefServiceTypeID *int32 `json:"fk_ref_service_type_id,omitempty"` + FKSdLocationID *string `json:"fk_sd_location_id,omitempty"` + DsSdLocation *string `json:"ds_sd_location,omitempty"` + *Alias + }{ + Alias: (*Alias)(&r), + } + if r.Name.Valid { + aux.Name = &r.Name.String + } + if r.Icon.Valid { + aux.Icon = &r.Icon.String + } + if r.Url.Valid { + aux.Url = &r.Url.String + } + if r.Active.Valid { + aux.Active = &r.Active.Bool + } + + if r.FKRefHealthcareTypeID.Valid { + fkrht := int32(r.FKRefHealthcareTypeID.Int32) + aux.FKRefHealthcareTypeID = &fkrht + } + + if r.FKRefServiceTypeID.Valid { + fkrst := int32(r.FKRefServiceTypeID.Int32) + aux.FKRefServiceTypeID = &fkrst + } + + if r.FKSdLocationID.Valid { + aux.FKSdLocationID = &r.FKSdLocationID.String + } + if r.DsSdLocation.Valid { + aux.DsSdLocation = &r.DsSdLocation.String + } + return json.Marshal(aux) +} + +// Helper methods untuk mendapatkan nilai yang aman +func (r *Listkiosk) GetName() string { + if r.Name.Valid { + return r.Name.String + } + return "" +} + +// Response struct untuk GET by ID +type ListkioskGetByIDResponse struct { + Message string `json:"message"` + Data *Listkiosk `json:"data"` +} + +// Enhanced GET response dengan pagination dan aggregation +type ListkioskGetResponse struct { + Message string `json:"message"` + Data []Listkiosk `json:"data"` + Meta models.MetaResponse `json:"meta"` + Summary *models.AggregateData `json:"summary,omitempty"` +} + +// Request struct untuk create +type ListkioskCreateRequest struct { + Name string `json:"name" validate:"min=1,max=20"` + Icon string `json:"icon" validate:"min=1,max=20"` + Url string `json:"url" validate:"min=1,max=255"` + Active bool `json:"active"` + FKRefHealthcareTypeID int32 `json:"fk_ref_healthcare_type_id" validate:"min=1"` + FKRefServiceTypeID int32 `json:"fk_ref_service_type_id" validate:"min=1"` + FKSdLocationID string `json:"fk_sd_location_id"` + DsSdLocation string `json:"ds_sd_location" validate:"min=1,max=255"` +} + +// Response struct untuk create +type ListkioskCreateResponse struct { + Message string `json:"message"` + Data *Listkiosk `json:"data"` +} + +// Update request +type ListkioskUpdateRequest struct { + ID int `json:"id" validate:"required,min=1"` + Name string `json:"name" validate:"min=1,max=20"` + Icon string `json:"icon" validate:"min=1,max=20"` + Url string `json:"url" validate:"min=1,max=255"` + Active bool `json:"active"` + FKRefHealthcareTypeID int32 `json:"fk_ref_healthcare_type_id" validate:"min=1"` + FKRefServiceTypeID int32 `json:"fk_ref_service_type_id" validate:"min=1"` + FKSdLocationID string `json:"fk_sd_location_id" validate:"min=1"` + DsSdLocation string `json:"ds_sd_location" validate:"min=1,max=255"` +} + +// Response struct untuk update +type ListkioskUpdateResponse struct { + Message string `json:"message"` + Data *Listkiosk `json:"data"` +} + +// Response struct untuk delete +type ListkioskDeleteResponse struct { + Message string `json:"message"` + ID string `json:"id"` +} + +// Filter struct untuk query parameters +type ListkioskFilter struct { + Status *string `json:"status,omitempty" form:"status"` + Search *string `json:"search,omitempty" form:"search"` + DateFrom *time.Time `json:"date_from,omitempty" form:"date_from"` + DateTo *time.Time `json:"date_to,omitempty" form:"date_to"` +} diff --git a/internal/routes/v1/routes.go b/internal/routes/v1/routes.go index 98f49255..019ff9ca 100644 --- a/internal/routes/v1/routes.go +++ b/internal/routes/v1/routes.go @@ -5,6 +5,7 @@ import ( "api-service/internal/database" authHandlers "api-service/internal/handlers/auth" healthcheckHandlers "api-service/internal/handlers/healthcheck" + kioskListkioskHandlers "api-service/internal/handlers/kiosk" pesertaHandlers "api-service/internal/handlers/peserta" retribusiHandlers "api-service/internal/handlers/retribusi" "api-service/internal/handlers/websocket" @@ -782,5 +783,17 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine { }) } + // Listkiosk endpoints + kioskListkioskHandler := kioskListkioskHandlers.NewListkioskHandler() + kioskListkioskGroup := v1.Group("/kiosk") + { + kioskListkioskGroup.GET("/listkiosk", kioskListkioskHandler.GetListkiosk) + //kioskListkioskGroup.GET("/:id", kioskListkioskHandler.GetListkioskByID) + kioskListkioskGroup.POST("", kioskListkioskHandler.CreateKiosk) + kioskListkioskGroup.PUT("/:id", kioskListkioskHandler.UpdateKiosk) + kioskListkioskGroup.DELETE("/:id", kioskListkioskHandler.DeleteKiosk) + kioskListkioskGroup.GET("/stats", kioskListkioskHandler.GetKioskStats) + } + return router } diff --git a/internal/utils/query/builder.go b/internal/utils/query/builder.go index a119dd1a..5ddb85bb 100644 --- a/internal/utils/query/builder.go +++ b/internal/utils/query/builder.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "github.com/Masterminds/squirrel" + squirrel "github.com/Masterminds/squirrel" // Still useful for array types, especially with PostgreSQL ) diff --git a/internal/utils/query/exemple.go b/internal/utils/query/example.go similarity index 100% rename from internal/utils/query/exemple.go rename to internal/utils/query/example.go diff --git a/internal/utils/validation/duplicate_validator.go b/internal/utils/validation/duplicate_validator.go index 863c0580..412c234f 100644 --- a/internal/utils/validation/duplicate_validator.go +++ b/internal/utils/validation/duplicate_validator.go @@ -52,20 +52,40 @@ func (dv *DuplicateValidator) ValidateDuplicate(ctx context.Context, config Vali // ValidateDuplicateWithCustomFields checks for duplicates with additional custom fields func (dv *DuplicateValidator) ValidateDuplicateWithCustomFields(ctx context.Context, config ValidationConfig, fields map[string]interface{}) error { - whereClause := fmt.Sprintf("%s = ANY($1) AND DATE(%s) = CURRENT_DATE", config.StatusColumn, config.DateColumn) - args := []interface{}{config.ActiveStatuses} - argIndex := 2 + whereClause := "" + args := []interface{}{} + argIndex := 1 // Add additional field conditions + // for fieldName, fieldValue := range config.AdditionalFields { + // whereClause += fmt.Sprintf(" AND %s = $%d", fieldName, argIndex) + // args = append(args, fieldValue) + // argIndex++ + // } + + // Add additional field conditions from config for fieldName, fieldValue := range config.AdditionalFields { - whereClause += fmt.Sprintf(" AND %s = $%d", fieldName, argIndex) + if whereClause != "" { + whereClause += " AND " + } + whereClause += fmt.Sprintf("%s = $%d", fieldName, argIndex) args = append(args, fieldValue) argIndex++ } // Add dynamic fields + // for fieldName, fieldValue := range fields { + // whereClause += fmt.Sprintf(" AND %s = $%d", fieldName, argIndex) + // args = append(args, fieldValue) + // argIndex++ + // } + + // Add dynamic fields from input for fieldName, fieldValue := range fields { - whereClause += fmt.Sprintf(" AND %s = $%d", fieldName, argIndex) + if whereClause != "" { + whereClause += " AND " + } + whereClause += fmt.Sprintf("%s = $%d", fieldName, argIndex) args = append(args, fieldValue) argIndex++ } diff --git a/tools/bpjs/generate-handler.go b/tools/bpjs/generate-handler.go deleted file mode 100644 index 77cfe328..00000000 --- a/tools/bpjs/generate-handler.go +++ /dev/null @@ -1,3176 +0,0 @@ -package main - -import ( - "fmt" - "go/ast" - "go/parser" - "go/token" - "io/ioutil" - "os" - "path/filepath" - "regexp" - "strings" - "text/template" - "time" - - "gopkg.in/yaml.v2" -) - -// Enhanced structures untuk validasi -type HandlerValidation struct { - ExistingFunctions map[string]bool - NewFunctions []string - UpdatedFiles []string - CreatedFiles []string -} - -type DirectoryInfo struct { - Path string - IsFile bool - Functions []FunctionInfo - Children map[string]*DirectoryInfo -} - -type FunctionInfo struct { - Name string - Methods []string - Endpoint string - Config EndpointConfig -} - -type EndpointConfig struct { - Methods []string `yaml:"methods"` - GetRoutes string `yaml:"get_routes"` - PostRoutes string `yaml:"post_routes"` - PutRoutes string `yaml:"put_routes"` - DeleteRoutes string `yaml:"delete_routes"` - GetPath string `yaml:"get_path"` - PostPath string `yaml:"post_path"` - PutPath string `yaml:"put_path"` - DeletePath string `yaml:"delete_path"` - Model string `yaml:"model"` - ResponseModel string `yaml:"response_model"` - RequestModel string `yaml:"request_model"` - Description string `yaml:"description"` - Summary string `yaml:"summary"` - Tags []string `yaml:"tags"` - RequireAuth bool `yaml:"require_auth"` - CacheEnabled bool `yaml:"cache_enabled"` - CacheTTL int `yaml:"cache_ttl"` -} - -type GlobalConfig struct { - ModuleName string `yaml:"module_name"` - OutputDir string `yaml:"output_dir"` - EnableSwagger bool `yaml:"enable_swagger"` - EnableLogging bool `yaml:"enable_logging"` -} - -type ServiceConfig struct { - Global GlobalConfig `yaml:"global,omitempty"` - Services map[string]Service `yaml:"services"` -} - -type Service struct { - Name string `yaml:"name"` - Category string `yaml:"category"` - Package string `yaml:"package"` - Description string `yaml:"description"` - BaseURL string `yaml:"base_url"` - Timeout int `yaml:"timeout"` - RetryCount int `yaml:"retry_count"` - Endpoints map[string]EndpointGroup `yaml:"endpoints"` - Dependencies string `yaml:"dependencies,omitempty"` - Middleware string `yaml:"middleware,omitempty"` -} - -type EndpointGroup struct { - Description string `yaml:"description"` - HandlerFolder string `yaml:"handler_folder"` - HandlerFile string `yaml:"handler_file"` - HandlerName string `yaml:"handler_name"` - Functions map[string]FunctionConfig `yaml:"functions"` -} - -type FunctionConfig struct { - Methods []string `yaml:"methods"` - Path string `yaml:"path"` - Model string `yaml:"model"` - RoutesLink string `yaml:"routes_link"` - // Routes untuk endpoint generation - GetRoutes string `yaml:"get_routes"` - PostRoutes string `yaml:"post_routes"` - PutRoutes string `yaml:"put_routes"` - DeleteRoutes string `yaml:"delete_routes"` - // āœ… Path untuk swagger documentation - GetPath string `yaml:"get_path"` - PostPath string `yaml:"post_path"` - PutPath string `yaml:"put_path"` - DeletePath string `yaml:"delete_path"` - ResponseModel string `yaml:"response_model"` - RequestModel string `yaml:"request_model"` - Description string `yaml:"description"` - Summary string `yaml:"summary"` - Tags []string `yaml:"tags"` - RequireAuth bool `yaml:"require_auth"` - CacheEnabled bool `yaml:"cache_enabled"` - CacheTTL int `yaml:"cache_ttl"` -} - -type TemplateData struct { - ServiceName string - ServiceLower string - ServiceUpper string - Category string - Package string - Description string - BaseURL string - Timeout int - RetryCount int - Endpoints []EndpointData - Timestamp string - ModuleName string - HasValidator bool - HasLogger bool - HasSwagger bool - GlobalConfig GlobalConfig - ShouldGenerateStruct bool - ShouldGenerateConstructor bool - FunctionalArea string // e.g. "rujukan", "search" - HandlerName string -} - -type EndpointData struct { - Name string - NameLower string - NameUpper string - NameCamel string - Methods []string - GetRoutes string - PostRoutes string - PutRoutes string - DeleteRoutes string - GetPath string - PostPath string - PutPath string - DeletePath string - Model string - ResponseModel string - RequestModel string - DataModel string - Description string - Summary string - Tags []string - HasGet bool - HasPost bool - HasPut bool - HasDelete bool - RequireAuth bool - CacheEnabled bool - CacheTTL int - PathParams []string - ModelPackage string -} - -// Fungsi utama yang diupdate untuk menggunakan separation of concerns -func generateHandlerWithValidation(serviceName string, svc Service, gc GlobalConfig) error { - // Step 1: Generate base handler file (struct + constructor) - SEKALI SAJA - // err := generateBaseHandlerFile(serviceName, svc, gc) - // if err != nil { - // return fmt.Errorf("generate base handler: %w", err) - // } - - // Step 2: Generate methods files per endpoint group - baseDir := gc.OutputDir - for groupName, grp := range svc.Endpoints { - folder := filepath.Join(baseDir, grp.HandlerFolder) - if err := os.MkdirAll(folder, 0755); err != nil { - return fmt.Errorf("mkdir %s: %w", folder, err) - } - - // Generate methods file dengan naming yang jelas - methodsFileName := fmt.Sprintf("%s.go", strings.ToLower(groupName)) - methodsFilePath := filepath.Join(folder, methodsFileName) - - // Check if methods file exists - fileExists := false - if _, err := os.Stat(methodsFilePath); err == nil { - fileExists = true - } - - if !fileExists { - // Create new methods file - err := createMethodsFileFromConfig(methodsFilePath, svc, grp, gc, groupName) - if err != nil { - return fmt.Errorf("create methods file %s: %w", methodsFilePath, err) - } - fmt.Printf("āœ… Created methods file: %s\n", methodsFilePath) - } else { - // Update existing methods file with new functions only - err := updateExistingMethodsFile(methodsFilePath, svc, grp, gc, groupName) - if err != nil { - return fmt.Errorf("update methods file %s: %w", methodsFilePath, err) - } - fmt.Printf("āœ… Updated methods file: %s\n", methodsFilePath) - } - } - // āœ… Step 2: Generate routes - err := generateRoutes(serviceName, svc, gc) - if err != nil { - return fmt.Errorf("generate routes: %w", err) - } - - return nil -} - -// Create new methods file -func createMethodsFileFromConfig(filePath string, svc Service, grp EndpointGroup, gc GlobalConfig, functionalArea string) error { - // Collect all functions for this group - var allEndpoints []EndpointData - for fname, fcfg := range grp.Functions { - td := processFunctionData(svc, grp, fname, fcfg, gc) - allEndpoints = append(allEndpoints, td.Endpoints...) - } - - templateData := TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - Package: grp.HandlerFolder, - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - Endpoints: allEndpoints, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - ShouldGenerateStruct: false, // NEVER generate struct in methods file - ShouldGenerateConstructor: false, // NEVER generate constructor in methods file - FunctionalArea: functionalArea, - HandlerName: grp.HandlerName, // PERBAIKAN: Pastikan HandlerName diset - } - - return createMethodsFile(filePath, templateData) -} - -// Create methods file using methods-only template -func createMethodsFile(filePath string, templateData TemplateData) error { - if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { - return err - } - - tmpl := template.New("methods").Funcs(template.FuncMap{ - "title": strings.Title, - "index": func(slice []string, i int) string { - if i >= 0 && i < len(slice) { - return slice[i] - } - return "" - }, - }) - - tmpl, err := tmpl.Parse(handlerTemplate) - if err != nil { - return err - } - - file, err := os.Create(filePath) - if err != nil { - return err - } - defer file.Close() - - return tmpl.Execute(file, templateData) -} - -// Update existing methods file (sama seperti sebelumnya tapi tanpa struct) -func updateExistingMethodsFile(filePath string, svc Service, grp EndpointGroup, gc GlobalConfig, functionalArea string) error { - existingContent, err := ioutil.ReadFile(filePath) - if err != nil { - return err - } - - content := string(existingContent) - // Check for existing functions and collect new ones - var newEndpoints []EndpointData - - for fname, fcfg := range grp.Functions { - functionExists := false - for _, method := range fcfg.Methods { - funcName := generateFunctionName(fname, method) - // PERBAIKAN: Gunakan grp.HandlerName bukan svc.Name - sig := fmt.Sprintf("func (h *%sHandler) %s", grp.HandlerName, funcName) - if strings.Contains(content, sig) { - fmt.Printf("āš ļø Skip existing: %s (%s)\n", fname, funcName) - functionExists = true - break - } - } - - if functionExists { - continue - } - - td := processFunctionData(svc, grp, fname, fcfg, gc) - newEndpoints = append(newEndpoints, td.Endpoints...) - fmt.Printf("āœ… Will add: %s\n", fname) - } - - if len(newEndpoints) == 0 { - fmt.Printf("ā­ļø No new functions to add\n") - return nil - } - - // Generate new functions using methods-only template - templateData := TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - Package: grp.HandlerFolder, - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - Endpoints: newEndpoints, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - ShouldGenerateStruct: false, // NEVER - ShouldGenerateConstructor: false, // NEVER - FunctionalArea: functionalArea, - HandlerName: grp.HandlerName, // PERBAIKAN: Pastikan HandlerName diset - } - - newFunctions, err := generateNewMethodsOnly(templateData) - if err != nil { - return err - } - - // Merge content - mergedContent := mergeGoFileContent(content, newFunctions) - return ioutil.WriteFile(filePath, []byte(mergedContent), 0644) -} - -// Generate new methods using methods-only template -func generateNewMethodsOnly(templateData TemplateData) (string, error) { - tmpl := template.New("newMethods") - tmpl, err := tmpl.Parse(handlerTemplate) - if err != nil { - return "", err - } - - var result strings.Builder - err = tmpl.Execute(&result, templateData) - if err != nil { - return "", err - } - - return result.String(), nil -} - -// Helper toCamelCase -func toCamelCase(s string) string { - parts := strings.FieldsFunc(s, func(r rune) bool { - return r == '_' || r == '-' || r == ' ' - }) - for i, p := range parts { - parts[i] = strings.Title(strings.ToLower(p)) - } - return strings.Join(parts, "") -} - -// Parse struktur direktori dari endpoints YAML -func parseDirectoryStructure(endpoints map[string]interface{}, serviceName string) *DirectoryInfo { - root := &DirectoryInfo{ - Path: "", - Children: make(map[string]*DirectoryInfo), - } - - for key, value := range endpoints { - parseNestedEndpoints(root, key, value) - } - - return root -} - -func parseNestedEndpoints(parent *DirectoryInfo, name string, value interface{}) { - switch v := value.(type) { - case map[string]interface{}: - // Check if this contains direct endpoint config - if isDirectEndpoint(v) { - // This is a direct endpoint - create as file - parent.Children[name] = &DirectoryInfo{ - Path: name, - IsFile: true, - Functions: []FunctionInfo{parseEndpointToFunction(name, v)}, - Children: make(map[string]*DirectoryInfo), - } - } else { - // This is nested structure - create as directory or file - child := &DirectoryInfo{ - Path: name, - IsFile: false, - Functions: make([]FunctionInfo, 0), - Children: make(map[string]*DirectoryInfo), - } - - // Check if any direct children are endpoints - hasDirectEndpoints := false - for childName, childValue := range v { - if childMap, ok := childValue.(map[string]interface{}); ok && isDirectEndpoint(childMap) { - hasDirectEndpoints = true - child.Functions = append(child.Functions, parseEndpointToFunction(childName, childMap)) - } - } - - if hasDirectEndpoints { - child.IsFile = true - } - - parent.Children[name] = child - - // Recursively parse nested children - for childName, childValue := range v { - if childMap, ok := childValue.(map[string]interface{}); ok && !isDirectEndpoint(childMap) { - parseNestedEndpoints(child, childName, childValue) - } - } - } - } -} - -func isDirectEndpoint(m map[string]interface{}) bool { - _, hasMethods := m["methods"] - _, hasGetPath := m["get_path"] - _, hasPostPath := m["post_path"] - return hasMethods || hasGetPath || hasPostPath -} - -func parseEndpointToFunction(name string, config map[string]interface{}) FunctionInfo { - function := FunctionInfo{ - Name: name, - Endpoint: name, - Methods: make([]string, 0), - } - - if methods, ok := config["methods"].([]interface{}); ok { - for _, method := range methods { - if methodStr, ok := method.(string); ok { - function.Methods = append(function.Methods, strings.ToUpper(strings.TrimSpace(methodStr))) - } - } - } else if methodStr, ok := config["methods"].(string); ok { - // Handle case where methods is a string like "GET,POST" - methods := strings.Split(methodStr, ",") - for _, method := range methods { - function.Methods = append(function.Methods, strings.ToUpper(strings.TrimSpace(method))) - } - } - - return function -} - -// Process directory structure dan generate files -func processDirectoryStructure(baseDir string, dirInfo *DirectoryInfo, service Service, globalConfig GlobalConfig, validation *HandlerValidation) error { - for name, child := range dirInfo.Children { - currentPath := filepath.Join(baseDir, child.Path) - - if child.IsFile { - // Process as file - err := processHandlerFile(currentPath, name, child, service, globalConfig, validation) - if err != nil { - return fmt.Errorf("failed to process file %s: %w", name, err) - } - } else { - // Create directory dan process children - if err := os.MkdirAll(currentPath, 0755); err != nil { - return fmt.Errorf("failed to create directory %s: %w", currentPath, err) - } - - err := processDirectoryStructure(currentPath, child, service, globalConfig, validation) - if err != nil { - return err - } - } - } - - return nil -} -func processFunctionData(svc Service, grp EndpointGroup, fname string, fcfg FunctionConfig, gc GlobalConfig) TemplateData { - ed := EndpointData{ - Name: toCamelCase(fname), - NameLower: strings.ToLower(fname), - NameUpper: strings.ToUpper(fname), - NameCamel: toCamelCase(fname), - Methods: fcfg.Methods, - GetRoutes: fcfg.GetRoutes, - PostRoutes: fcfg.PostRoutes, - PutRoutes: fcfg.PutRoutes, - DeleteRoutes: fcfg.DeleteRoutes, - GetPath: fcfg.Path, - PostPath: fcfg.Path, - PutPath: fcfg.Path, - DeletePath: fcfg.Path, - Model: fcfg.Model, - ResponseModel: fcfg.ResponseModel, - RequestModel: fcfg.RequestModel, - DataModel: strings.Replace(fcfg.ResponseModel, "Response", "Data", 1), - Description: fcfg.Description, - Summary: fcfg.Summary, - Tags: fcfg.Tags, - RequireAuth: fcfg.RequireAuth, - CacheEnabled: fcfg.CacheEnabled, - CacheTTL: fcfg.CacheTTL, - PathParams: extractPathParams(fcfg.Path), - ModelPackage: grp.HandlerFolder, - } - // set flags - for _, m := range fcfg.Methods { - switch strings.ToUpper(m) { - case "GET": - ed.HasGet = true - case "POST": - ed.HasPost = true - case "PUT": - ed.HasPut = true - case "DELETE": - ed.HasDelete = true - } - } - return TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - Package: grp.HandlerFolder, - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - Endpoints: []EndpointData{ed}, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - HandlerName: grp.HandlerName, - } -} - -// extractPathParams (jika belum ada) -func extractPathParams(path string) []string { - var ps []string - for _, part := range strings.Split(path, "/") { - if strings.HasPrefix(part, ":") { - ps = append(ps, strings.TrimPrefix(part, ":")) - } - } - return ps -} - -// Process individual handler file - PERBAIKAN: Hapus referensi grp.HandlerName -func processHandlerFile(basePath, fileName string, dirInfo *DirectoryInfo, service Service, globalConfig GlobalConfig, validation *HandlerValidation) error { - filePath := filepath.Join(filepath.Dir(basePath), fmt.Sprintf("%s.go", fileName)) - fmt.Printf("šŸ“„ Processing file: %s\n", filePath) - - // Check if file exists - fileExists := false - if _, err := os.Stat(filePath); err == nil { - fileExists = true - fmt.Printf(" šŸ“‹ File exists, checking functions...\n") - } - - var existingFunctions map[string]bool - if fileExists { - // Parse existing functions - PERBAIKAN: Hanya gunakan 1 parameter - functions, err := extractExistingFunctions(filePath) - if err != nil { - fmt.Printf(" āš ļø Warning: Could not parse existing file: %v\n", err) - existingFunctions = make(map[string]bool) - } else { - existingFunctions = functions - fmt.Printf(" āœ“ Found %d existing functions\n", len(functions)) - } - } else { - existingFunctions = make(map[string]bool) - } - - // Determine which functions to generate - functionsToGenerate := make([]FunctionInfo, 0) - for _, fnInfo := range dirInfo.Functions { - for _, method := range fnInfo.Methods { - functionName := generateFunctionName(fnInfo.Name, method) - if !existingFunctions[functionName] { - functionsToGenerate = append(functionsToGenerate, FunctionInfo{ - Name: fnInfo.Name, - Methods: []string{method}, - Endpoint: fnInfo.Endpoint, - }) - validation.NewFunctions = append(validation.NewFunctions, functionName) - fmt.Printf(" āœ… Will generate: %s\n", functionName) - } else { - fmt.Printf(" ā­ļø Already exists: %s\n", functionName) - } - } - } - - // Generate file if needed - if len(functionsToGenerate) > 0 { - templateData := prepareTemplateData(fileName, service, globalConfig, functionsToGenerate) - - if fileExists { - // Merge with existing file - err := mergeWithExistingFile(filePath, templateData) - if err != nil { - return fmt.Errorf("failed to merge file %s: %w", filePath, err) - } - validation.UpdatedFiles = append(validation.UpdatedFiles, filePath) - fmt.Printf(" šŸ“ Updated existing file\n") - } else { - // Create new file - err := createNewHandlerFile(filePath, templateData) - if err != nil { - return fmt.Errorf("failed to create file %s: %w", filePath, err) - } - validation.CreatedFiles = append(validation.CreatedFiles, filePath) - fmt.Printf(" šŸ“ Created new file\n") - } - } else if !fileExists { - fmt.Printf(" ā­ļø No functions to generate and file doesn't exist\n") - } else { - fmt.Printf(" ā­ļø All functions already exist\n") - } - - return nil -} - -// Extract existing functions from Go file -func extractExistingFunctions(filePath string) (map[string]bool, error) { - fileSet := token.NewFileSet() - node, err := parser.ParseFile(fileSet, filePath, nil, parser.ParseComments) - if err != nil { - return nil, err - } - - functions := make(map[string]bool) - ast.Inspect(node, func(n ast.Node) bool { - if fn, ok := n.(*ast.FuncDecl); ok { - if fn.Name != nil { - functions[fn.Name.Name] = true - } - } - return true - }) - return functions, nil -} - -// Generate function name berdasarkan endpoint dan method -func generateFunctionName(endpointName, method string) string { - switch strings.ToUpper(method) { - case "GET": - return fmt.Sprintf("Get%s", strings.Title(endpointName)) - case "POST": - return fmt.Sprintf("Create%s", strings.Title(endpointName)) - case "PUT": - return fmt.Sprintf("Update%s", strings.Title(endpointName)) - case "DELETE": - return fmt.Sprintf("Delete%s", strings.Title(endpointName)) - case "PATCH": - return fmt.Sprintf("Patch%s", strings.Title(endpointName)) - default: - return fmt.Sprintf("%s%s", strings.Title(method), strings.Title(endpointName)) - } -} - -// Prepare template data -func prepareTemplateData(packageName string, service Service, globalConfig GlobalConfig, functions []FunctionInfo) TemplateData { - endpoints := make([]EndpointData, 0) - - for _, fnInfo := range functions { - endpoint := EndpointData{ - Name: strings.Title(fnInfo.Name), - NameLower: strings.ToLower(fnInfo.Name), - NameUpper: strings.ToUpper(fnInfo.Name), - NameCamel: toCamelCase(fnInfo.Name), - ModelPackage: packageName, - Model: fmt.Sprintf("%sRequest", strings.Title(fnInfo.Name)), - ResponseModel: fmt.Sprintf("%sResponse", strings.Title(fnInfo.Name)), - RequestModel: fmt.Sprintf("%sRequest", strings.Title(fnInfo.Name)), - DataModel: fmt.Sprintf("%sData", strings.Title(fnInfo.Name)), - Description: fmt.Sprintf("Handle %s operations", fnInfo.Name), - Tags: []string{strings.Title(packageName)}, - } - - // Set paths dan methods - for _, method := range fnInfo.Methods { - switch strings.ToUpper(method) { - case "GET": - endpoint.HasGet = true - endpoint.GetPath = fmt.Sprintf("/%s/%s", packageName, fnInfo.Name) - case "POST": - endpoint.HasPost = true - endpoint.PostPath = fmt.Sprintf("/%s", packageName) - case "PUT": - endpoint.HasPut = true - endpoint.PutPath = fmt.Sprintf("/%s/%s", packageName, fnInfo.Name) - case "DELETE": - endpoint.HasDelete = true - endpoint.DeletePath = fmt.Sprintf("/%s/%s", packageName, fnInfo.Name) - } - } - - endpoints = append(endpoints, endpoint) - } - - return TemplateData{ - ServiceName: service.Name, - ServiceLower: strings.ToLower(service.Name), - ServiceUpper: strings.ToUpper(service.Name), - Category: service.Category, - Package: packageName, - Description: service.Description, - BaseURL: service.BaseURL, - Timeout: getOrDefault(service.Timeout, 30), - RetryCount: getOrDefault(service.RetryCount, 3), - Endpoints: endpoints, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: globalConfig.ModuleName, - HasValidator: true, - HasLogger: globalConfig.EnableLogging, - HasSwagger: globalConfig.EnableSwagger, - GlobalConfig: globalConfig, - HandlerName: strings.Title(packageName), - } -} - -// Check if handler struct already exists in directory -func shouldGenerateHandlerStruct(baseDir, handlerName string) bool { - structSignature := fmt.Sprintf("type %sHandler struct", handlerName) - - files, err := ioutil.ReadDir(baseDir) - if err != nil { - return true // If directory doesn't exist, generate struct - } - - for _, file := range files { - if file.IsDir() || !strings.HasSuffix(file.Name(), ".go") { - continue - } - - filePath := filepath.Join(baseDir, file.Name()) - content, err := ioutil.ReadFile(filePath) - if err != nil { - continue - } - - if strings.Contains(string(content), structSignature) { - return false // Struct already exists - } - } - - return true // Struct not found, should generate -} - -// Check if constructor already exists -func shouldGenerateConstructor(baseDir, handlerName string) bool { - constructorSignature := fmt.Sprintf("func New%sHandler", handlerName) - - files, err := ioutil.ReadDir(baseDir) - if err != nil { - return true - } - - for _, file := range files { - if file.IsDir() || !strings.HasSuffix(file.Name(), ".go") { - continue - } - - filePath := filepath.Join(baseDir, file.Name()) - content, err := ioutil.ReadFile(filePath) - if err != nil { - continue - } - - if strings.Contains(string(content), constructorSignature) { - return false - } - } - - return true -} - -// Generate base handler file (struct + constructor only) -func generateBaseHandlerFile(serviceName string, svc Service, gc GlobalConfig) error { - baseDir := gc.OutputDir - if err := os.MkdirAll(baseDir, 0755); err != nil { - return fmt.Errorf("mkdir %s: %w", baseDir, err) - } - - baseFileName := fmt.Sprintf("%s_base.go", strings.ToLower(serviceName)) - baseFilePath := filepath.Join(baseDir, baseFileName) - - // Skip if base file already exists - if _, err := os.Stat(baseFilePath); err == nil { - fmt.Printf("ā­ļø Base handler already exists: %s\n", baseFilePath) - return nil - } - - templateData := TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - Package: "handlers", - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - Endpoints: []EndpointData{}, // Empty - base only - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - } - - return createBaseHandlerFile(baseFilePath, templateData) -} - -// Template untuk base handler (struct + constructor only) -const baseHandlerTemplate = `// Package handlers handles {{.HandlerName}} BPJS services - Base Handler -// Generated on: {{.Timestamp}} -package handlers - -import ( - "{{.ModuleName}}/internal/config" - "{{.ModuleName}}/internal/services/bpjs" - "{{.ModuleName}}/pkg/logger" - - "github.com/go-playground/validator/v10" -) - -// {{.HandlerName}}Handler handles {{.HandlerName}} BPJS services -type {{.HandlerName}}Handler struct { - service services.VClaimService - validator *validator.Validate - logger logger.Logger - config config.BpjsConfig -} - -// {{.HandlerName}}HandlerConfig contains configuration for {{.HandlerName}}Handler -type {{.HandlerName}}HandlerConfig struct { - BpjsConfig config.BpjsConfig - Logger logger.Logger - Validator *validator.Validate -} - -// New{{.HandlerName}}Handler creates a new {{.HandlerName}}Handler -func New{{.HandlerName}}Handler(cfg {{.HandlerName}}HandlerConfig) *{{.HandlerName}}Handler { - return &{{.HandlerName}}Handler{ - service: services.NewService(cfg.BpjsConfig), - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.BpjsConfig, - } -} -` - -// Function to create base handler file -func createBaseHandlerFile(filePath string, templateData TemplateData) error { - if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { - return err - } - - tmpl := template.New("baseHandler").Funcs(template.FuncMap{ - "title": strings.Title, - }) - - tmpl, err := tmpl.Parse(baseHandlerTemplate) - if err != nil { - return err - } - - file, err := os.Create(filePath) - if err != nil { - return err - } - defer file.Close() - - return tmpl.Execute(file, templateData) -} - -// Template untuk handler file lengkap (untuk file baru) -const handlerTemplate = `// Package {{.Package}} handles {{.HandlerName}} BPJS services -// Generated on: {{.Timestamp}} -package handlers - -import ( - "context" - "encoding/json" - "net/http" - "strings" - "time" - - "{{.ModuleName}}/internal/config" - "{{.ModuleName}}/internal/models" - "{{.ModuleName}}/internal/models/vclaim/{{.Package}}" - "{{.ModuleName}}/internal/services/bpjs" - "{{.ModuleName}}/pkg/logger" - - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" - "github.com/google/uuid" -) -// {{.HandlerName}}Handler handles {{.HandlerName}} BPJS services -type {{.HandlerName}}Handler struct { - service services.VClaimService - validator *validator.Validate - logger logger.Logger - config config.BpjsConfig -} -// {{.HandlerName}}HandlerConfig contains configuration for {{.HandlerName}}Handler -type {{.HandlerName}}HandlerConfig struct { - BpjsConfig config.BpjsConfig - Logger logger.Logger - Validator *validator.Validate -} -// New{{.HandlerName}}Handler creates a new {{.HandlerName}}Handler -func New{{.HandlerName}}Handler(cfg {{.HandlerName}}HandlerConfig) *{{.HandlerName}}Handler { - return &{{.HandlerName}}Handler{ - service: services.NewService(cfg.BpjsConfig), - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.BpjsConfig, - } -} -{{range .Endpoints}} -{{if .HasGet}} -// Get{{.Name}} godoc -// @Summary Get {{.Name}} data -// @Description {{.Description}} -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully retrieved {{.Name}} data" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.GetRoutes}} [get] -func (h *{{$.HandlerName}}Handler) Get{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.GetPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.GetPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.GetRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.GetRawResponse(ctx, "{{.GetPath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to get {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasPost}} -// Create{{.Name}} godoc -// @Summary Create new {{.Name}} -// @Description Create new {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} data" -// @Success 201 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully created {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PostRoutes}} [post] -func (h *{{$.HandlerName}}Handler) Create{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PostPath}}", - }) - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - {{if .PathParams}} - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - endpoint := "{{.PostPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - resp, err := h.service.PostRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PostRawResponse(ctx, "{{.PostPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} already exists or conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusCreated, response) -} -{{end}} - - -{{if .HasPut}} -// Update{{.Name}} godoc -// @Summary Update existing {{.Name}} -// @Description Update existing {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} update data" -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully updated {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict - update conflict occurred" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PutRoutes}} [put] -func (h *{{$.HandlerName}}Handler) Update{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PutPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.PutPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.PutRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PutRawResponse(ctx, "{{.PutPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to update {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "Update conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasDelete}} -// Delete{{.Name}} godoc -// @Summary Delete existing {{.Name}} -// @Description Delete existing {{.Name}} from BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully deleted {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.DeleteRoutes}} [delete] -func (h *{{$.HandlerName}}Handler) Delete{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.DeletePath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.DeletePath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.DeleteRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.DeleteRawResponse(ctx, "{{.DeletePath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to delete {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } else { - // For delete operations, sometimes there's no data in response - response.Data = nil - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{end}}` - -// Template untuk menambah function saja (untuk file yang sudah ada) -const functionsOnlyTemplate = `{{range .Endpoints}} -{{if .HasGet}} -// Get{{.Name}} godoc -// @Summary Get {{.Name}} data -// @Description {{.Description}} -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully retrieved {{.Name}} data" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.GetRoutes}} [get] -func (h *{{$.HandlerName}}Handler) Get{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.GetPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.GetPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.GetRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.GetRawResponse(ctx, "{{.GetPath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to get {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasPost}} -// Create{{.Name}} godoc -// @Summary Create new {{.Name}} -// @Description Create new {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} data" -// @Success 201 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully created {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PostRoutes}} [post] -func (h *{{$.HandlerName}}Handler) Create{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PostPath}}", - }) - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - {{if .PathParams}} - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - endpoint := "{{.PostPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - resp, err := h.service.PostRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PostRawResponse(ctx, "{{.PostPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} already exists or conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusCreated, response) -} -{{end}} - - -{{if .HasPut}} -// Update{{.Name}} godoc -// @Summary Update existing {{.Name}} -// @Description Update existing {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} update data" -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully updated {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict - update conflict occurred" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PutRoutes}} [put] -func (h *{{$.HandlerName}}Handler) Update{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PutPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.PutPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.PutRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PutRawResponse(ctx, "{{.PutPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to update {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "Update conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasDelete}} -// Delete{{.Name}} godoc -// @Summary Delete existing {{.Name}} -// @Description Delete existing {{.Name}} from BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully deleted {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.DeleteRoutes}} [delete] -func (h *{{$.HandlerName}}Handler) Delete{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.DeletePath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.DeletePath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.DeleteRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.DeleteRawResponse(ctx, "{{.DeletePath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to delete {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } else { - // For delete operations, sometimes there's no data in response - response.Data = nil - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{end}}` - -// Create new handler file -func createNewHandlerFile(filePath string, templateData TemplateData) error { - // Create directory - if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { - return err - } - - // Parse template - tmpl := template.New("handler").Funcs(template.FuncMap{ - "title": strings.Title, - "index": func(slice []string, i int) string { - if i >= 0 && i < len(slice) { - return slice[i] - } - return "" - }, - }) - - tmpl, err := tmpl.Parse(handlerTemplate) - if err != nil { - return err - } - - // Create file - file, err := os.Create(filePath) - if err != nil { - return err - } - defer file.Close() - - // Execute template - return tmpl.Execute(file, templateData) -} - -// Merge dengan existing file -func mergeWithExistingFile(filePath string, templateData TemplateData) error { - // Read existing content - existingContent, err := ioutil.ReadFile(filePath) - if err != nil { - return err - } - - // Generate new functions - newFunctions, err := generateNewFunctionsOnly(templateData) - if err != nil { - return err - } - - // Merge content - mergedContent := mergeGoFileContent(string(existingContent), newFunctions) - - // Write back - return ioutil.WriteFile(filePath, []byte(mergedContent), 0644) -} - -func generateNewFunctionsOnly(templateData TemplateData) (string, error) { - funcTemplate := ` -{{range .Endpoints}} -{{if .HasGet}} -// Get{{.Name}} godoc -// @Summary Get {{.Name}} data -// @Description {{.Description}} -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully retrieved {{.Name}} data" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.GetRoutes}} [get] -func (h *{{$.HandlerName}}Handler) Get{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.GetPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.GetPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.GetRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.GetRawResponse(ctx, "{{.GetPath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to get {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasPost}} -// Create{{.Name}} godoc -// @Summary Create new {{.Name}} -// @Description Create new {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} data" -// @Success 201 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully created {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PostRoutes}} [post] -func (h *{{$.HandlerName}}Handler) Create{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PostPath}}", - }) - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - {{if .PathParams}} - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - endpoint := "{{.PostPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - resp, err := h.service.PostRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PostRawResponse(ctx, "{{.PostPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} already exists or conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusCreated, response) -} -{{end}} - - -{{if .HasPut}} -// Update{{.Name}} godoc -// @Summary Update existing {{.Name}} -// @Description Update existing {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} update data" -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully updated {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict - update conflict occurred" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PutRoutes}} [put] -func (h *{{$.HandlerName}}Handler) Update{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PutPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.PutPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.PutRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PutRawResponse(ctx, "{{.PutPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to update {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "Update conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasDelete}} -// Delete{{.Name}} godoc -// @Summary Delete existing {{.Name}} -// @Description Delete existing {{.Name}} from BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully deleted {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.DeleteRoutes}} [delete] -func (h *{{$.HandlerName}}Handler) Delete{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.DeletePath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.DeletePath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.DeleteRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.DeleteRawResponse(ctx, "{{.DeletePath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to delete {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } else { - // For delete operations, sometimes there's no data in response - response.Data = nil - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{end}}` - - tmpl := template.New("functions") - tmpl, err := tmpl.Parse(funcTemplate) - if err != nil { - return "", err - } - - var result strings.Builder - err = tmpl.Execute(&result, templateData) - if err != nil { - return "", err - } - - // Remove duplicate function definitions by simple regex grouping - // This is a simple approach to avoid duplicate functions in merged content - funcRegex := regexp.MustCompile(`(?s)(func \(h \*\w+Handler\) Get\w+\(c \*gin.Context\) \{.*?\})`) - matches := funcRegex.FindAllString(result.String(), -1) - uniqueFuncs := make(map[string]bool) - var uniqueResult strings.Builder - for _, m := range matches { - if !uniqueFuncs[m] { - uniqueFuncs[m] = true - uniqueResult.WriteString(m) - uniqueResult.WriteString("\n\n") - } - } - - // If no matches found, return original result - if uniqueResult.Len() == 0 { - return result.String(), nil - } - - return uniqueResult.String(), nil -} - -func mergeGoFileContent(existingContent, newFunctions string) string { - // Find last closing brace - re := regexp.MustCompile(`}\s*$`) - lastBraceIndex := re.FindStringIndex(existingContent) - - if lastBraceIndex == nil { - return existingContent + "\n" + newFunctions - } - - before := existingContent[:lastBraceIndex[0]] - after := existingContent[lastBraceIndex[0]:] - - return before + "\n" + newFunctions + "\n" + after -} - -// Main function -func main() { - if len(os.Args) < 2 { - printUsage() - os.Exit(1) - } - - configFile := os.Args[1] - var targetService string - if len(os.Args) > 2 { - targetService = os.Args[2] - } - - config, err := loadConfig(configFile) - if err != nil { - fmt.Printf("Error loading config: %v\n", err) - os.Exit(1) - } - - fmt.Println("šŸš€ Starting BPJS Dynamic Handler Generation with Validation...") - fmt.Printf("šŸ“ Config file: %s\n", configFile) - if targetService != "" { - fmt.Printf("šŸŽÆ Target service: %s\n", targetService) - } - - generated := 0 - errors := 0 - - for serviceName, service := range config.Services { - if targetService != "" && serviceName != targetService { - continue - } - - err := generateHandlerWithValidation(serviceName, service, config.Global) - if err != nil { - fmt.Printf("āŒ Error generating handler for %s: %v\n", serviceName, err) - errors++ - continue - } - - generated++ - } - - // Summary - fmt.Println("\nšŸ“Š Generation Summary:") - fmt.Printf("āœ… Successfully processed: %d services\n", generated) - if errors > 0 { - fmt.Printf("āŒ Failed: %d services\n", errors) - } - - if generated > 0 { - fmt.Println("šŸŽ‰ Generation completed successfully!") - } -} - -// Helper functions -func loadConfig(filename string) (*ServiceConfig, error) { - data, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - - var config ServiceConfig - err = yaml.Unmarshal(data, &config) - if err != nil { - return nil, err - } - - // Set defaults - if config.Global.ModuleName == "" { - config.Global.ModuleName = "api-service" - } - if config.Global.OutputDir == "" { - config.Global.OutputDir = "internal/handlers" - } - - return &config, nil -} - -func getOrDefault(value, defaultValue int) int { - if value == 0 { - return defaultValue - } - return value -} - -func printUsage() { - fmt.Println("BPJS Dynamic Handler Generator with Function Validation") - fmt.Println() - fmt.Println("Usage:") - fmt.Println(" go run generate-handler.go [service-name]") - fmt.Println() - fmt.Println("Examples:") - fmt.Println(" go run generate-handler.go services-config-bpjs.yaml") - fmt.Println(" go run generate-handler.go services-config-bpjs.yaml vclaim") -} - -// Generate routes file untuk service -func generateRoutes(serviceName string, svc Service, gc GlobalConfig) error { - routesFilePath := "internal/routes/v1/routes.go" - routesContent, err := ioutil.ReadFile(routesFilePath) - if err != nil { - return fmt.Errorf("failed to read routes file: %w", err) - } - - routesContentStr := string(routesContent) - - // Check if routes are already registered - if strings.Contains(routesContentStr, fmt.Sprintf("Register%sRoutes", svc.Name)) { - fmt.Printf("āš ļø Routes for %s already registered in main routes file\n", svc.Name) - return nil - } - - var imports []string - var allRoutes []string - - // āœ… PERBAIKAN: Gunakan groupName dengan benar - // āœ… VALIDASI: Track handler folder yang sudah diimport - processedFolders := make(map[string]bool) - for groupName, grp := range svc.Endpoints { - // Import berdasarkan handler folder - // imports = append(imports, fmt.Sprintf("\t%sHandlers \"%s/internal/handlers/%s\"", - // grp.HandlerFolder, gc.ModuleName, grp.HandlerFolder)) - if !processedFolders[grp.HandlerFolder] { - importLine := fmt.Sprintf("\t%sHandlers \"%s/internal/handlers/%s\"", - grp.HandlerFolder, gc.ModuleName, grp.HandlerFolder) - imports = append(imports, importLine) - processedFolders[grp.HandlerFolder] = true - fmt.Printf("āœ… Added import: %sHandlers\n", grp.HandlerFolder) - } else { - fmt.Printf("āš ļø Skipped duplicate import for folder: %s\n", grp.HandlerFolder) - } - - var routesCode strings.Builder - - // Gunakan groupName untuk comment dan identifier - routesCode.WriteString(fmt.Sprintf("\n\t// %s (%s) routes\n", grp.Description, groupName)) - - // Handler instantiation menggunakan HandlerName dari config - routesCode.WriteString(fmt.Sprintf("\t%sHandler := %sHandlers.New%sHandler(%sHandlers.%sHandlerConfig{\n", - strings.ToLower(grp.HandlerName), - grp.HandlerFolder, - grp.HandlerName, - grp.HandlerFolder, - grp.HandlerName)) - - routesCode.WriteString("\t\tBpjsConfig: cfg.Bpjs,\n") - routesCode.WriteString("\t\tLogger: *logger.Default(),\n") - routesCode.WriteString("\t\tValidator: validator.New(),\n") - routesCode.WriteString("\t})\n") - - // āœ… GUNAKAN groupName untuk route group path - routesCode.WriteString(fmt.Sprintf("\t%sGroup := v1.Group(\"/%s\")\n", - strings.ToLower(grp.HandlerName), groupName)) // ← Gunakan groupName di sini - - // Process functions - for fname, fcfg := range grp.Functions { - td := processFunctionData(svc, grp, fname, fcfg, gc) - - for _, endpoint := range td.Endpoints { - handlerVar := strings.ToLower(grp.HandlerName) + "Handler" - groupVar := strings.ToLower(grp.HandlerName) + "Group" - - // āœ… MODIFIKASI: Loop through methods dan gunakan specific routes - for _, method := range fcfg.Methods { - var cleanPath string - - // āœ… Pilih path berdasarkan method - switch strings.ToUpper(method) { - case "GET": - cleanPath = fcfg.GetRoutes - if cleanPath == "" { - cleanPath = fcfg.GetPath // fallback ke get_path - } - case "POST": - cleanPath = fcfg.PostRoutes - if cleanPath == "" { - cleanPath = fcfg.PostPath // fallback ke post_path - } - case "PUT": - cleanPath = fcfg.PutRoutes - if cleanPath == "" { - cleanPath = fcfg.PutPath // fallback ke put_path - } - case "DELETE": - cleanPath = fcfg.DeleteRoutes - if cleanPath == "" { - cleanPath = fcfg.DeletePath // fallback ke delete_path - } - default: - fmt.Printf("āš ļø Unsupported HTTP method: %s for function %s\n", method, fname) - continue - } - - // āœ… Final fallback ke path jika specific route kosong - if cleanPath == "" { - cleanPath = fcfg.Path - } - - // āœ… Bersihkan path - hapus prefix groupName jika ada - if strings.HasPrefix(cleanPath, "/"+groupName) { - cleanPath = strings.TrimPrefix(cleanPath, "/"+groupName) - } - if cleanPath == "" { - cleanPath = "/" - } - - // āœ… Generate route berdasarkan method - switch strings.ToUpper(method) { - case "GET": - routesCode.WriteString(fmt.Sprintf("\t%s.GET(\"%s\", %s.Get%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - case "POST": - routesCode.WriteString(fmt.Sprintf("\t%s.POST(\"%s\", %s.Create%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - case "PUT": - routesCode.WriteString(fmt.Sprintf("\t%s.PUT(\"%s\", %s.Update%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - case "DELETE": - routesCode.WriteString(fmt.Sprintf("\t%s.DELETE(\"%s\", %s.Delete%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - } - } - } - } - - allRoutes = append(allRoutes, routesCode.String()) - } - - // āœ… PERBAIKAN: Insert imports setelah "api-service/internal/database" - if len(imports) > 0 { - // āœ… PERBAIKAN: Hilangkan newline di awal, langsung import lines saja - importSection := strings.Join(imports, "\n") + "\n" - - // āœ… PERBAIKAN: Cari posisi setelah "api-service/internal/database" - databaseImportMarker := fmt.Sprintf("\"%s/internal/database\"", gc.ModuleName) - if strings.Contains(routesContentStr, databaseImportMarker) { - // Temukan posisi marker - markerPos := strings.Index(routesContentStr, databaseImportMarker) - // Temukan akhir baris dari marker - endOfLinePos := strings.Index(routesContentStr[markerPos:], "\n") + markerPos - // Insert import section setelah baris marker - routesContentStr = routesContentStr[:endOfLinePos+1] + importSection + routesContentStr[endOfLinePos+1:] - fmt.Printf("āœ… Inserted imports after database import\n") - } else { - // Fallback: Insert setelah "import (" jika marker tidak ditemukan - importMarker := "import (" - if strings.Contains(routesContentStr, importMarker) { - importIndex := strings.Index(routesContentStr, importMarker) + len(importMarker) - routesContentStr = routesContentStr[:importIndex] + "\n" + importSection + routesContentStr[importIndex:] - fmt.Printf("āš ļø Database import not found, inserted after import (\n") - } else { - fmt.Printf("āš ļø Could not find import section to insert imports\n") - } - } - } - - // Find and insert routes - publishedRoutesMarker := "// ============= PUBLISHED ROUTES ===============================================" - if !strings.Contains(routesContentStr, publishedRoutesMarker) { - return fmt.Errorf("PUBLISHED ROUTES marker not found in routes.go") - } - - insertionPoint := strings.Index(routesContentStr, publishedRoutesMarker) + len(publishedRoutesMarker) - newRoutesContent := routesContentStr[:insertionPoint] + "\n" + strings.Join(allRoutes, "\n") + "\n" + routesContentStr[insertionPoint:] - - err = ioutil.WriteFile(routesFilePath, []byte(newRoutesContent), 0644) - if err != nil { - return fmt.Errorf("failed to write updated routes file: %w", err) - } - - fmt.Printf("āœ… Updated main routes file with %s routes\n", svc.Name) - return nil -} diff --git a/tools/bpjs/generete b/tools/bpjs/generete deleted file mode 100644 index 570f2d07..00000000 --- a/tools/bpjs/generete +++ /dev/null @@ -1,2941 +0,0 @@ -package main - -import ( - "fmt" - "go/ast" - "go/parser" - "go/token" - "io/ioutil" - "os" - "path/filepath" - "regexp" - "strings" - "text/template" - "time" - - "gopkg.in/yaml.v2" -) - -// Enhanced structures untuk validasi -type HandlerValidation struct { - ExistingFunctions map[string]bool - NewFunctions []string - UpdatedFiles []string - CreatedFiles []string -} - -type DirectoryInfo struct { - Path string - IsFile bool - Functions []FunctionInfo - Children map[string]*DirectoryInfo -} - -type FunctionInfo struct { - Name string - Methods []string - Endpoint string - Config EndpointConfig -} - -type EndpointConfig struct { - Methods []string `yaml:"methods"` - GetPath string `yaml:"get_path"` - PostPath string `yaml:"post_path"` - PutPath string `yaml:"put_path"` - DeletePath string `yaml:"delete_path"` - Model string `yaml:"model"` - ResponseModel string `yaml:"response_model"` - RequestModel string `yaml:"request_model"` - Description string `yaml:"description"` - Summary string `yaml:"summary"` - Tags []string `yaml:"tags"` - RequireAuth bool `yaml:"require_auth"` - CacheEnabled bool `yaml:"cache_enabled"` - CacheTTL int `yaml:"cache_ttl"` -} - -type GlobalConfig struct { - ModuleName string `yaml:"module_name"` - OutputDir string `yaml:"output_dir"` - EnableSwagger bool `yaml:"enable_swagger"` - EnableLogging bool `yaml:"enable_logging"` -} - -type ServiceConfig struct { - Global GlobalConfig `yaml:"global,omitempty"` - Services map[string]Service `yaml:"services"` -} - -type Service struct { - Name string `yaml:"name"` - Category string `yaml:"category"` - Package string `yaml:"package"` - Description string `yaml:"description"` - BaseURL string `yaml:"base_url"` - Timeout int `yaml:"timeout"` - RetryCount int `yaml:"retry_count"` - Endpoints map[string]EndpointGroup `yaml:"endpoints"` - Dependencies string `yaml:"dependencies,omitempty"` - Middleware string `yaml:"middleware,omitempty"` -} - -type EndpointGroup struct { - Description string `yaml:"description"` - HandlerFolder string `yaml:"handler_folder"` - HandlerFile string `yaml:"handler_file"` - Functions map[string]FunctionConfig `yaml:"functions"` -} - -type FunctionConfig struct { - Methods []string `yaml:"methods"` - Path string `yaml:"path"` - Model string `yaml:"model"` - ResponseModel string `yaml:"response_model"` - RequestModel string `yaml:"request_model"` - Description string `yaml:"description"` - Summary string `yaml:"summary"` - Tags []string `yaml:"tags"` - RequireAuth bool `yaml:"require_auth"` - CacheEnabled bool `yaml:"cache_enabled"` - CacheTTL int `yaml:"cache_ttl"` -} - -type TemplateData struct { - ServiceName string - ServiceLower string - ServiceUpper string - Category string - Package string - Description string - BaseURL string - Timeout int - RetryCount int - Endpoints []EndpointData - Timestamp string - ModuleName string - HasValidator bool - HasLogger bool - HasSwagger bool - GlobalConfig GlobalConfig -} - -type EndpointData struct { - Name string - NameLower string - NameUpper string - NameCamel string - Methods []string - GetPath string - PostPath string - PutPath string - DeletePath string - Model string - ResponseModel string - RequestModel string - DataModel string - Description string - Summary string - Tags []string - HasGet bool - HasPost bool - HasPut bool - HasDelete bool - RequireAuth bool - CacheEnabled bool - CacheTTL int - PathParams []string - ModelPackage string -} - -// Fungsi utama untuk generate handler dengan validasi -func generateHandlerWithValidation(serviceName string, svc Service, gc GlobalConfig) error { - baseDir := gc.OutputDir - for _, grp := range svc.Endpoints { - folder := filepath.Join(baseDir, grp.HandlerFolder) - if err := os.MkdirAll(folder, 0755); err != nil { - return fmt.Errorf("mkdir %s: %w", folder, err) - } - filePath := filepath.Join(folder, grp.HandlerFile) - - // Check if file exists - fileExists := false - if _, err := os.Stat(filePath); err == nil { - fileExists = true - } - - if !fileExists { - // Create new file with full template - err := createNewHandlerFileFromConfig(filePath, svc, grp, gc) - if err != nil { - return fmt.Errorf("create file %s: %w", filePath, err) - } - fmt.Printf("āœ… Created new file: %s\n", filePath) - } else { - // Update existing file with functions only - err := updateExistingHandlerFile(filePath, svc, grp, gc) - if err != nil { - return fmt.Errorf("update file %s: %w", filePath, err) - } - fmt.Printf("āœ… Updated existing file: %s\n", filePath) - } - } - return nil -} - -// Create new handler file with full template -func createNewHandlerFileFromConfig(filePath string, svc Service, grp EndpointGroup, gc GlobalConfig) error { - // Collect all functions for this group - var allEndpoints []EndpointData - for fname, fcfg := range grp.Functions { - td := processFunctionData(svc, grp, fname, fcfg, gc) - allEndpoints = append(allEndpoints, td.Endpoints...) - } - - templateData := TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - Package: grp.HandlerFolder, - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - Endpoints: allEndpoints, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - } - - return createNewHandlerFile(filePath, templateData) -} - -// Update existing handler file with functions only -func updateExistingHandlerFile(filePath string, svc Service, grp EndpointGroup, gc GlobalConfig) error { - // Read existing content - existingContent, err := ioutil.ReadFile(filePath) - if err != nil { - return err - } - - content := string(existingContent) - - // Check for existing functions and collect new ones - var newEndpoints []EndpointData - for fname, fcfg := range grp.Functions { - // Check for all possible function names that could be generated - functionExists := false - for _, method := range fcfg.Methods { - funcName := generateFunctionName(fname, method) - sig := fmt.Sprintf("func (h *%sHandler) %s", svc.Name, funcName) - if strings.Contains(content, sig) { - fmt.Printf("āš ļø Skip existing: %s (%s)\n", fname, funcName) - functionExists = true - break - } - } - if functionExists { - continue - } - // siapkan data - td := processFunctionData(svc, grp, fname, fcfg, gc) - newEndpoints = append(newEndpoints, td.Endpoints...) - fmt.Printf("āœ… Will add: %s\n", fname) - } - - if len(newEndpoints) == 0 { - fmt.Printf("ā­ļø No new functions to add\n") - return nil - } - - // Generate new functions using functions-only template - templateData := TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - Package: grp.HandlerFolder, - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - Endpoints: newEndpoints, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - } - - newFunctions, err := generateNewFunctionsOnly(templateData) - if err != nil { - return err - } - - // Merge content - mergedContent := mergeGoFileContent(content, newFunctions) - - // Write back - return ioutil.WriteFile(filePath, []byte(mergedContent), 0644) -} - -// Helper toCamelCase -func toCamelCase(s string) string { - parts := strings.FieldsFunc(s, func(r rune) bool { - return r == '_' || r == '-' || r == ' ' - }) - for i, p := range parts { - parts[i] = strings.Title(strings.ToLower(p)) - } - return strings.Join(parts, "") -} - -// Parse struktur direktori dari endpoints YAML -func parseDirectoryStructure(endpoints map[string]interface{}, serviceName string) *DirectoryInfo { - root := &DirectoryInfo{ - Path: "", - Children: make(map[string]*DirectoryInfo), - } - - for key, value := range endpoints { - parseNestedEndpoints(root, key, value) - } - - return root -} - -func parseNestedEndpoints(parent *DirectoryInfo, name string, value interface{}) { - switch v := value.(type) { - case map[string]interface{}: - // Check if this contains direct endpoint config - if isDirectEndpoint(v) { - // This is a direct endpoint - create as file - parent.Children[name] = &DirectoryInfo{ - Path: name, - IsFile: true, - Functions: []FunctionInfo{parseEndpointToFunction(name, v)}, - Children: make(map[string]*DirectoryInfo), - } - } else { - // This is nested structure - create as directory or file - child := &DirectoryInfo{ - Path: name, - IsFile: false, - Functions: make([]FunctionInfo, 0), - Children: make(map[string]*DirectoryInfo), - } - - // Check if any direct children are endpoints - hasDirectEndpoints := false - for childName, childValue := range v { - if childMap, ok := childValue.(map[string]interface{}); ok && isDirectEndpoint(childMap) { - hasDirectEndpoints = true - child.Functions = append(child.Functions, parseEndpointToFunction(childName, childMap)) - } - } - - if hasDirectEndpoints { - child.IsFile = true - } - - parent.Children[name] = child - - // Recursively parse nested children - for childName, childValue := range v { - if childMap, ok := childValue.(map[string]interface{}); ok && !isDirectEndpoint(childMap) { - parseNestedEndpoints(child, childName, childValue) - } - } - } - } -} - -func isDirectEndpoint(m map[string]interface{}) bool { - _, hasMethods := m["methods"] - _, hasGetPath := m["get_path"] - _, hasPostPath := m["post_path"] - return hasMethods || hasGetPath || hasPostPath -} - -func parseEndpointToFunction(name string, config map[string]interface{}) FunctionInfo { - function := FunctionInfo{ - Name: name, - Endpoint: name, - Methods: make([]string, 0), - } - - if methods, ok := config["methods"].([]interface{}); ok { - for _, method := range methods { - if methodStr, ok := method.(string); ok { - function.Methods = append(function.Methods, strings.ToUpper(strings.TrimSpace(methodStr))) - } - } - } else if methodStr, ok := config["methods"].(string); ok { - // Handle case where methods is a string like "GET,POST" - methods := strings.Split(methodStr, ",") - for _, method := range methods { - function.Methods = append(function.Methods, strings.ToUpper(strings.TrimSpace(method))) - } - } - - return function -} - -// Process directory structure dan generate files -func processDirectoryStructure(baseDir string, dirInfo *DirectoryInfo, service Service, globalConfig GlobalConfig, validation *HandlerValidation) error { - for name, child := range dirInfo.Children { - currentPath := filepath.Join(baseDir, child.Path) - - if child.IsFile { - // Process as file - err := processHandlerFile(currentPath, name, child, service, globalConfig, validation) - if err != nil { - return fmt.Errorf("failed to process file %s: %w", name, err) - } - } else { - // Create directory dan process children - if err := os.MkdirAll(currentPath, 0755); err != nil { - return fmt.Errorf("failed to create directory %s: %w", currentPath, err) - } - - err := processDirectoryStructure(currentPath, child, service, globalConfig, validation) - if err != nil { - return err - } - } - } - - return nil -} -func processFunctionData(svc Service, grp EndpointGroup, fname string, fcfg FunctionConfig, gc GlobalConfig) TemplateData { - ed := EndpointData{ - Name: toCamelCase(fname), - NameLower: strings.ToLower(fname), - NameUpper: strings.ToUpper(fname), - NameCamel: toCamelCase(fname), - Methods: fcfg.Methods, - GetPath: fcfg.Path, - PostPath: fcfg.Path, - PutPath: fcfg.Path, - DeletePath: fcfg.Path, - Model: fcfg.Model, - ResponseModel: fcfg.ResponseModel, - RequestModel: fcfg.RequestModel, - DataModel: strings.Replace(fcfg.ResponseModel, "Response", "Data", 1), - Description: fcfg.Description, - Summary: fcfg.Summary, - Tags: fcfg.Tags, - RequireAuth: fcfg.RequireAuth, - CacheEnabled: fcfg.CacheEnabled, - CacheTTL: fcfg.CacheTTL, - PathParams: extractPathParams(fcfg.Path), - ModelPackage: grp.HandlerFolder, - } - // set flags - for _, m := range fcfg.Methods { - switch strings.ToUpper(m) { - case "GET": - ed.HasGet = true - case "POST": - ed.HasPost = true - case "PUT": - ed.HasPut = true - case "DELETE": - ed.HasDelete = true - } - } - return TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - Package: grp.HandlerFolder, - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - Endpoints: []EndpointData{ed}, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - } -} - -// extractPathParams (jika belum ada) -func extractPathParams(path string) []string { - var ps []string - for _, part := range strings.Split(path, "/") { - if strings.HasPrefix(part, ":") { - ps = append(ps, strings.TrimPrefix(part, ":")) - } - } - return ps -} - -// Process individual handler file -func processHandlerFile(basePath, fileName string, dirInfo *DirectoryInfo, service Service, globalConfig GlobalConfig, validation *HandlerValidation) error { - filePath := filepath.Join(filepath.Dir(basePath), fmt.Sprintf("%s.go", fileName)) - - fmt.Printf("šŸ“„ Processing file: %s\n", filePath) - - // Check if file exists - fileExists := false - if _, err := os.Stat(filePath); err == nil { - fileExists = true - fmt.Printf(" šŸ“‹ File exists, checking functions...\n") - } - - var existingFunctions map[string]bool - if fileExists { - // Parse existing functions - functions, err := extractExistingFunctions(filePath) - if err != nil { - fmt.Printf(" āš ļø Warning: Could not parse existing file: %v\n", err) - existingFunctions = make(map[string]bool) - } else { - existingFunctions = functions - fmt.Printf(" āœ“ Found %d existing functions\n", len(functions)) - } - } else { - existingFunctions = make(map[string]bool) - } - - // Determine which functions to generate - functionsToGenerate := make([]FunctionInfo, 0) - for _, fnInfo := range dirInfo.Functions { - for _, method := range fnInfo.Methods { - functionName := generateFunctionName(fnInfo.Name, method) - if !existingFunctions[functionName] { - functionsToGenerate = append(functionsToGenerate, FunctionInfo{ - Name: fnInfo.Name, - Methods: []string{method}, - Endpoint: fnInfo.Endpoint, - }) - validation.NewFunctions = append(validation.NewFunctions, functionName) - fmt.Printf(" āœ… Will generate: %s\n", functionName) - } else { - fmt.Printf(" ā­ļø Already exists: %s\n", functionName) - } - } - } - - // Generate file if needed - if len(functionsToGenerate) > 0 { - templateData := prepareTemplateData(fileName, service, globalConfig, functionsToGenerate) - - if fileExists { - // Merge with existing file - err := mergeWithExistingFile(filePath, templateData) - if err != nil { - return fmt.Errorf("failed to merge file %s: %w", filePath, err) - } - validation.UpdatedFiles = append(validation.UpdatedFiles, filePath) - fmt.Printf(" šŸ“ Updated existing file\n") - } else { - // Create new file - err := createNewHandlerFile(filePath, templateData) - if err != nil { - return fmt.Errorf("failed to create file %s: %w", filePath, err) - } - validation.CreatedFiles = append(validation.CreatedFiles, filePath) - fmt.Printf(" šŸ“ Created new file\n") - } - } else if !fileExists { - fmt.Printf(" ā­ļø No functions to generate and file doesn't exist\n") - } else { - fmt.Printf(" ā­ļø All functions already exist\n") - } - - return nil -} - -// Extract existing functions from Go file -func extractExistingFunctions(filePath string) (map[string]bool, error) { - fileSet := token.NewFileSet() - node, err := parser.ParseFile(fileSet, filePath, nil, parser.ParseComments) - if err != nil { - return nil, err - } - - functions := make(map[string]bool) - ast.Inspect(node, func(n ast.Node) bool { - if fn, ok := n.(*ast.FuncDecl); ok { - if fn.Name != nil { - functions[fn.Name.Name] = true - } - } - return true - }) - - return functions, nil -} - -// Generate function name berdasarkan endpoint dan method -func generateFunctionName(endpointName, method string) string { - switch strings.ToUpper(method) { - case "GET": - return fmt.Sprintf("Get%s", strings.Title(endpointName)) - case "POST": - return fmt.Sprintf("Create%s", strings.Title(endpointName)) - case "PUT": - return fmt.Sprintf("Update%s", strings.Title(endpointName)) - case "DELETE": - return fmt.Sprintf("Delete%s", strings.Title(endpointName)) - case "PATCH": - return fmt.Sprintf("Patch%s", strings.Title(endpointName)) - default: - return fmt.Sprintf("%s%s", strings.Title(method), strings.Title(endpointName)) - } -} - -// Prepare template data -func prepareTemplateData(packageName string, service Service, globalConfig GlobalConfig, functions []FunctionInfo) TemplateData { - endpoints := make([]EndpointData, 0) - - for _, fnInfo := range functions { - endpoint := EndpointData{ - Name: strings.Title(fnInfo.Name), - NameLower: strings.ToLower(fnInfo.Name), - NameUpper: strings.ToUpper(fnInfo.Name), - NameCamel: toCamelCase(fnInfo.Name), - ModelPackage: packageName, - Model: fmt.Sprintf("%sRequest", strings.Title(fnInfo.Name)), - ResponseModel: fmt.Sprintf("%sResponse", strings.Title(fnInfo.Name)), - RequestModel: fmt.Sprintf("%sRequest", strings.Title(fnInfo.Name)), - DataModel: fmt.Sprintf("%sData", strings.Title(fnInfo.Name)), - Description: fmt.Sprintf("Handle %s operations", fnInfo.Name), - Tags: []string{strings.Title(packageName)}, - } - - // Set paths dan methods - for _, method := range fnInfo.Methods { - switch strings.ToUpper(method) { - case "GET": - endpoint.HasGet = true - endpoint.GetPath = fmt.Sprintf("/%s/%s", packageName, fnInfo.Name) - case "POST": - endpoint.HasPost = true - endpoint.PostPath = fmt.Sprintf("/%s", packageName) - case "PUT": - endpoint.HasPut = true - endpoint.PutPath = fmt.Sprintf("/%s/%s", packageName, fnInfo.Name) - case "DELETE": - endpoint.HasDelete = true - endpoint.DeletePath = fmt.Sprintf("/%s/%s", packageName, fnInfo.Name) - } - } - - endpoints = append(endpoints, endpoint) - } - - return TemplateData{ - ServiceName: service.Name, - ServiceLower: strings.ToLower(service.Name), - ServiceUpper: strings.ToUpper(service.Name), - Category: service.Category, - Package: packageName, - Description: service.Description, - BaseURL: service.BaseURL, - Timeout: getOrDefault(service.Timeout, 30), - RetryCount: getOrDefault(service.RetryCount, 3), - Endpoints: endpoints, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: globalConfig.ModuleName, - HasValidator: true, - HasLogger: globalConfig.EnableLogging, - HasSwagger: globalConfig.EnableSwagger, - GlobalConfig: globalConfig, - } -} - -// Template untuk handler file lengkap (untuk file baru) -const handlerTemplate = `// Package {{.Package}} handles {{.ServiceName}} BPJS services -// Generated on: {{.Timestamp}} -package handlers - -import ( - "context" - "encoding/json" - "net/http" - "strings" - "time" - - "{{.ModuleName}}/internal/config" - "{{.ModuleName}}/internal/models" - "{{.ModuleName}}/internal/models/vclaim/{{.Package}}" - "{{.ModuleName}}/internal/services/bpjs" - "{{.ModuleName}}/pkg/logger" - - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" - "github.com/google/uuid" -) - -// {{.ServiceName}}Handler handles {{.ServiceName}} BPJS services -type {{.ServiceName}}Handler struct { - service services.VClaimService - validator *validator.Validate - logger logger.Logger - config config.BpjsConfig -} - -// {{.ServiceName}}HandlerConfig contains configuration for {{.ServiceName}}Handler -type {{.ServiceName}}HandlerConfig struct { - BpjsConfig config.BpjsConfig - Logger logger.Logger - Validator *validator.Validate -} - -// New{{.ServiceName}}Handler creates a new {{.ServiceName}}Handler -func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig) *{{.ServiceName}}Handler { - return &{{.ServiceName}}Handler{ - service: services.NewService(cfg.BpjsConfig), - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.BpjsConfig, - } -} - -{{range .Endpoints}} -{{if .HasGet}} -// Get{{.Name}} godoc -// @Summary Get {{.Name}} data -// @Description {{.Description}} -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully retrieved {{.Name}} data" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.GetPath}} [get] -func (h *{{$.ServiceName}}Handler) Get{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.GetPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.GetPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.GetRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.GetRawResponse(ctx, "{{.GetPath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to get {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasPost}} -// Create{{.Name}} godoc -// @Summary Create new {{.Name}} -// @Description Create new {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} data" -// @Success 201 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully created {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PostPath}} [post] -func (h *{{$.ServiceName}}Handler) Create{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PostPath}}", - }) - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - {{if .PathParams}} - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - endpoint := "{{.PostPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - resp, err := h.service.PostRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PostRawResponse(ctx, "{{.PostPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} already exists or conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusCreated, response) -} -{{end}} - - -{{if .HasPut}} -// Update{{.Name}} godoc -// @Summary Update existing {{.Name}} -// @Description Update existing {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} update data" -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully updated {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict - update conflict occurred" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PutPath}} [put] -func (h *{{$.ServiceName}}Handler) Update{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PutPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.PutPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.PutRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PutRawResponse(ctx, "{{.PutPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to update {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "Update conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasDelete}} -// Delete{{.Name}} godoc -// @Summary Delete existing {{.Name}} -// @Description Delete existing {{.Name}} from BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully deleted {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.DeletePath}} [delete] -func (h *{{$.ServiceName}}Handler) Delete{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.DeletePath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.DeletePath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.DeleteRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.DeleteRawResponse(ctx, "{{.DeletePath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to delete {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } else { - // For delete operations, sometimes there's no data in response - response.Data = nil - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{end}}` - -// Template untuk menambah function saja (untuk file yang sudah ada) -const functionsOnlyTemplate = `{{range .Endpoints}} -{{if .HasGet}} -// Get{{.Name}} godoc -// @Summary Get {{.Name}} data -// @Description {{.Description}} -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully retrieved {{.Name}} data" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.GetPath}} [get] -func (h *{{$.ServiceName}}Handler) Get{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.GetPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.GetPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.GetRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.GetRawResponse(ctx, "{{.GetPath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to get {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasPost}} -// Create{{.Name}} godoc -// @Summary Create new {{.Name}} -// @Description Create new {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} data" -// @Success 201 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully created {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PostPath}} [post] -func (h *{{$.ServiceName}}Handler) Create{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PostPath}}", - }) - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - {{if .PathParams}} - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - endpoint := "{{.PostPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - resp, err := h.service.PostRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PostRawResponse(ctx, "{{.PostPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} already exists or conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusCreated, response) -} -{{end}} - - -{{if .HasPut}} -// Update{{.Name}} godoc -// @Summary Update existing {{.Name}} -// @Description Update existing {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} update data" -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully updated {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict - update conflict occurred" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PutPath}} [put] -func (h *{{$.ServiceName}}Handler) Update{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PutPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.PutPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.PutRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PutRawResponse(ctx, "{{.PutPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to update {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "Update conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasDelete}} -// Delete{{.Name}} godoc -// @Summary Delete existing {{.Name}} -// @Description Delete existing {{.Name}} from BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully deleted {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.DeletePath}} [delete] -func (h *{{$.ServiceName}}Handler) Delete{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.DeletePath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.DeletePath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.DeleteRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.DeleteRawResponse(ctx, "{{.DeletePath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to delete {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } else { - // For delete operations, sometimes there's no data in response - response.Data = nil - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{end}}` - -// Create new handler file -func createNewHandlerFile(filePath string, templateData TemplateData) error { - // Create directory - if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { - return err - } - - // Parse template - tmpl := template.New("handler").Funcs(template.FuncMap{ - "title": strings.Title, - "index": func(slice []string, i int) string { - if i >= 0 && i < len(slice) { - return slice[i] - } - return "" - }, - }) - - tmpl, err := tmpl.Parse(handlerTemplate) - if err != nil { - return err - } - - // Create file - file, err := os.Create(filePath) - if err != nil { - return err - } - defer file.Close() - - // Execute template - return tmpl.Execute(file, templateData) -} - -// Merge dengan existing file -func mergeWithExistingFile(filePath string, templateData TemplateData) error { - // Read existing content - existingContent, err := ioutil.ReadFile(filePath) - if err != nil { - return err - } - - // Generate new functions - newFunctions, err := generateNewFunctionsOnly(templateData) - if err != nil { - return err - } - - // Merge content - mergedContent := mergeGoFileContent(string(existingContent), newFunctions) - - // Write back - return ioutil.WriteFile(filePath, []byte(mergedContent), 0644) -} - -func generateNewFunctionsOnly(templateData TemplateData) (string, error) { - funcTemplate := ` -{{range .Endpoints}} -{{if .HasGet}} -// Get{{.Name}} godoc -// @Summary Get {{.Name}} data -// @Description {{.Description}} -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully retrieved {{.Name}} data" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.GetPath}} [get] -func (h *{{$.ServiceName}}Handler) Get{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.GetPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.GetPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.GetRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.GetRawResponse(ctx, "{{.GetPath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to get {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasPost}} -// Create{{.Name}} godoc -// @Summary Create new {{.Name}} -// @Description Create new {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} data" -// @Success 201 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully created {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PostPath}} [post] -func (h *{{$.ServiceName}}Handler) Create{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PostPath}}", - }) - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - {{if .PathParams}} - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - endpoint := "{{.PostPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - resp, err := h.service.PostRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PostRawResponse(ctx, "{{.PostPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} already exists or conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusCreated, response) -} -{{end}} - - -{{if .HasPut}} -// Update{{.Name}} godoc -// @Summary Update existing {{.Name}} -// @Description Update existing {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} update data" -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully updated {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict - update conflict occurred" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PutPath}} [put] -func (h *{{$.ServiceName}}Handler) Update{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PutPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.PutPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.PutRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PutRawResponse(ctx, "{{.PutPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to update {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "Update conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasDelete}} -// Delete{{.Name}} godoc -// @Summary Delete existing {{.Name}} -// @Description Delete existing {{.Name}} from BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully deleted {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.DeletePath}} [delete] -func (h *{{$.ServiceName}}Handler) Delete{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.DeletePath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.DeletePath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.DeleteRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.DeleteRawResponse(ctx, "{{.DeletePath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to delete {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } else { - // For delete operations, sometimes there's no data in response - response.Data = nil - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{end}}` - - tmpl := template.New("functions") - tmpl, err := tmpl.Parse(funcTemplate) - if err != nil { - return "", err - } - - var result strings.Builder - err = tmpl.Execute(&result, templateData) - if err != nil { - return "", err - } - - // Remove duplicate function definitions by simple regex grouping - // This is a simple approach to avoid duplicate functions in merged content - funcRegex := regexp.MustCompile(`(?s)(func \(h \*\w+Handler\) Get\w+\(c \*gin.Context\) \{.*?\})`) - matches := funcRegex.FindAllString(result.String(), -1) - uniqueFuncs := make(map[string]bool) - var uniqueResult strings.Builder - for _, m := range matches { - if !uniqueFuncs[m] { - uniqueFuncs[m] = true - uniqueResult.WriteString(m) - uniqueResult.WriteString("\n\n") - } - } - - // If no matches found, return original result - if uniqueResult.Len() == 0 { - return result.String(), nil - } - - return uniqueResult.String(), nil -} - -func mergeGoFileContent(existingContent, newFunctions string) string { - // Find last closing brace - re := regexp.MustCompile(`}\s*$`) - lastBraceIndex := re.FindStringIndex(existingContent) - - if lastBraceIndex == nil { - return existingContent + "\n" + newFunctions - } - - before := existingContent[:lastBraceIndex[0]] - after := existingContent[lastBraceIndex[0]:] - - return before + "\n" + newFunctions + "\n" + after -} - -// Main function -func main() { - if len(os.Args) < 2 { - printUsage() - os.Exit(1) - } - - configFile := os.Args[1] - var targetService string - if len(os.Args) > 2 { - targetService = os.Args[2] - } - - config, err := loadConfig(configFile) - if err != nil { - fmt.Printf("Error loading config: %v\n", err) - os.Exit(1) - } - - fmt.Println("šŸš€ Starting BPJS Dynamic Handler Generation with Validation...") - fmt.Printf("šŸ“ Config file: %s\n", configFile) - if targetService != "" { - fmt.Printf("šŸŽÆ Target service: %s\n", targetService) - } - - generated := 0 - errors := 0 - - for serviceName, service := range config.Services { - if targetService != "" && serviceName != targetService { - continue - } - - err := generateHandlerWithValidation(serviceName, service, config.Global) - if err != nil { - fmt.Printf("āŒ Error generating handler for %s: %v\n", serviceName, err) - errors++ - continue - } - - generated++ - } - - // Summary - fmt.Println("\nšŸ“Š Generation Summary:") - fmt.Printf("āœ… Successfully processed: %d services\n", generated) - if errors > 0 { - fmt.Printf("āŒ Failed: %d services\n", errors) - } - - if generated > 0 { - fmt.Println("šŸŽ‰ Generation completed successfully!") - } -} - -// Helper functions -func loadConfig(filename string) (*ServiceConfig, error) { - data, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - - var config ServiceConfig - err = yaml.Unmarshal(data, &config) - if err != nil { - return nil, err - } - - // Set defaults - if config.Global.ModuleName == "" { - config.Global.ModuleName = "api-service" - } - if config.Global.OutputDir == "" { - config.Global.OutputDir = "internal/handlers" - } - - return &config, nil -} - -func getOrDefault(value, defaultValue int) int { - if value == 0 { - return defaultValue - } - return value -} - -func printUsage() { - fmt.Println("BPJS Dynamic Handler Generator with Function Validation") - fmt.Println() - fmt.Println("Usage:") - fmt.Println(" go run generate-handler.go [service-name]") - fmt.Println() - fmt.Println("Examples:") - fmt.Println(" go run generate-handler.go services-config-bpjs.yaml") - fmt.Println(" go run generate-handler.go services-config-bpjs.yaml vclaim") -} - -func generateRoutes(serviceName string, service Service, globalConfig GlobalConfig) error { - // Read the main routes.go file - routesFilePath := "internal/routes/v1/routes.go" - routesContent, err := ioutil.ReadFile(routesFilePath) - if err != nil { - return fmt.Errorf("failed to read routes file: %w", err) - } - - routesContentStr := string(routesContent) - - // Check if routes are already registered - if strings.Contains(routesContentStr, fmt.Sprintf("Register%sRoutes", service.Name)) { - fmt.Printf("āš ļø Routes for %s already registered in main routes file\n", service.Name) - return nil - } - - // Prepare template data for all groups - var allRoutes []string - - // Loop over each module group in service.Endpoints - for groupName, subEndpoints := range service.Endpoints { - // Prepare template data for this group - templateData := TemplateData{ - ServiceName: service.Name, - ServiceLower: strings.ToLower(service.Name), - ServiceUpper: strings.ToUpper(service.Name), - Category: service.Category, - Package: groupName, // package name is group name - Description: service.Description, - BaseURL: service.BaseURL, - Timeout: getOrDefault(service.Timeout, 30), - RetryCount: getOrDefault(service.RetryCount, 3), - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: globalConfig.ModuleName, - HasValidator: true, - HasLogger: globalConfig.EnableLogging, - HasMetrics: globalConfig.EnableMetrics, - HasSwagger: globalConfig.EnableSwagger, - Dependencies: service.Dependencies, - Middleware: service.Middleware, - GlobalConfig: globalConfig, - } - - // Process endpoints for this group - for subEndpointName, endpoint := range subEndpoints { - fullEndpointName := groupName - if subEndpointName != "" { - fullEndpointName = fullEndpointName + strings.Title(subEndpointName) - } - endpointData := processEndpoint(fullEndpointName, endpoint, groupName) - templateData.Endpoints = append(templateData.Endpoints, endpointData) - } - - // Generate routes code for this group - var routesCode strings.Builder - - routesCode.WriteString(fmt.Sprintf("\n\t// %s routes\n", strings.Title(groupName))) - routesCode.WriteString(fmt.Sprintf("\t%sHandler := %s.New%sHandler(%s.%sHandlerConfig{\n", groupName, groupName, service.Name, groupName, service.Name)) - routesCode.WriteString("\t\tBpjsConfig: cfg.Bpjs,\n") - routesCode.WriteString("\t\tLogger: *logger.Default(),\n") - routesCode.WriteString("\t\tValidator: nil,\n") - routesCode.WriteString("\t})\n") - routesCode.WriteString(fmt.Sprintf("\t%sGroup := v1.Group(\"/%s\")\n", groupName, groupName)) - - for _, endpoint := range templateData.Endpoints { - if endpoint.HasGet { - routesCode.WriteString(fmt.Sprintf("\t%sGroup.GET(\"%s\", %sHandler.Get%s)\n", groupName, endpoint.GetPath, groupName, endpoint.Name)) - } - if endpoint.HasPost { - routesCode.WriteString(fmt.Sprintf("\t%sGroup.POST(\"%s\", %sHandler.Create%s)\n", groupName, endpoint.PostPath, groupName, endpoint.Name)) - } - if endpoint.HasPut { - routesCode.WriteString(fmt.Sprintf("\t%sGroup.PUT(\"%s\", %sHandler.Update%s)\n", groupName, endpoint.PutPath, groupName, endpoint.Name)) - } - if endpoint.HasDelete { - routesCode.WriteString(fmt.Sprintf("\t%sGroup.DELETE(\"%s\", %sHandler.Delete%s)\n", groupName, endpoint.DeletePath, groupName, endpoint.Name)) - } - if endpoint.HasPatch { - routesCode.WriteString(fmt.Sprintf("\t%sGroup.PATCH(\"%s\", %sHandler.Patch%s)\n", groupName, endpoint.PatchPath, groupName, endpoint.Name)) - } - } - - allRoutes = append(allRoutes, routesCode.String()) - } - - // Find the PUBLISHED ROUTES section and insert the routes - publishedRoutesMarker := "// ============= PUBLISHED ROUTES ===============================================" - if !strings.Contains(routesContentStr, publishedRoutesMarker) { - return fmt.Errorf("PUBLISHED ROUTES marker not found in routes.go") - } - - // Insert the routes after the marker - insertionPoint := strings.Index(routesContentStr, publishedRoutesMarker) + len(publishedRoutesMarker) - newRoutesContent := routesContentStr[:insertionPoint] + "\n" + strings.Join(allRoutes, "\n") + "\n" + routesContentStr[insertionPoint:] - - // Write back the modified routes file - err = ioutil.WriteFile(routesFilePath, []byte(newRoutesContent), 0644) - if err != nil { - return fmt.Errorf("failed to write updated routes file: %w", err) - } - - fmt.Printf("āœ… Updated main routes file with %s routes\n", service.Name) - return nil -} - -func processEndpoint(name string, endpoint Endpoint, endpointGroup string) EndpointData { - data := EndpointData{ - Name: strings.Title(name), - NameLower: strings.ToLower(name), - NameUpper: strings.ToUpper(name), - NameCamel: toCamelCase(name), - Methods: endpoint.Methods, - GetPath: endpoint.GetPath, - PostPath: endpoint.PostPath, - PutPath: endpoint.PutPath, - DeletePath: endpoint.DeletePath, - PatchPath: endpoint.PatchPath, - Model: endpoint.Model, - ResponseModel: endpoint.ResponseModel, - Description: endpoint.Description, - Summary: endpoint.Summary, - Tags: endpoint.Tags, - RequireAuth: endpoint.RequireAuth, - RateLimit: endpoint.RateLimit, - CacheEnabled: endpoint.CacheEnabled, - CacheTTL: getOrDefault(endpoint.CacheTTL, 300), - CustomHeaders: endpoint.CustomHeaders, - ModelPackage: endpointGroup, // Set the model package based on endpoint group - } - - // Set method flags and extract path parameters - for _, method := range endpoint.Methods { - switch strings.ToUpper(method) { - case "GET": - data.HasGet = true - data.PathParams = extractPathParams(endpoint.GetPath) - case "POST": - data.HasPost = true - case "PUT": - data.HasPut = true - data.PathParams = extractPathParams(endpoint.PutPath) - case "DELETE": - data.HasDelete = true - data.PathParams = extractPathParams(endpoint.DeletePath) - case "PATCH": - data.HasPatch = true - data.PathParams = extractPathParams(endpoint.PatchPath) - } - } - - return data -} - -func extractPathParams(path string) []string { - if path == "" { - return nil - } - - var params []string - parts := strings.Split(path, "/") - for _, part := range parts { - if strings.HasPrefix(part, ":") { - params = append(params, strings.TrimPrefix(part, ":")) - } - } - - return params -} - -func toCamelCase(str string) string { - words := strings.FieldsFunc(str, func(c rune) bool { - return c == '_' || c == '-' || c == ' ' - }) - - if len(words) == 0 { - return str - } - - result := strings.ToLower(words[0]) - for _, word := range words[1:] { - result += strings.Title(strings.ToLower(word)) - } - - return result -} - -func getOrDefault(value, defaultValue int) int { - if value == 0 { - return defaultValue - } - return value -} diff --git a/tools/bpjs/newgenerete b/tools/bpjs/newgenerete deleted file mode 100644 index 2cf0d0bb..00000000 --- a/tools/bpjs/newgenerete +++ /dev/null @@ -1,3230 +0,0 @@ -package main - -import ( - "fmt" - "go/ast" - "go/parser" - "go/token" - "io/ioutil" - "os" - "path/filepath" - "regexp" - "strings" - "text/template" - "time" - - "gopkg.in/yaml.v2" -) - -// Enhanced structures untuk validasi -type HandlerValidation struct { - ExistingFunctions map[string]bool - NewFunctions []string - UpdatedFiles []string - CreatedFiles []string -} - -type DirectoryInfo struct { - Path string - IsFile bool - Functions []FunctionInfo - Children map[string]*DirectoryInfo -} - -type FunctionInfo struct { - Name string - Methods []string - Endpoint string - Config EndpointConfig -} - -type EndpointConfig struct { - Methods []string `yaml:"methods"` - GetRoutes string `yaml:"get_routes"` - PostRoutes string `yaml:"post_routes"` - PutRoutes string `yaml:"put_routes"` - DeleteRoutes string `yaml:"delete_routes"` - GetPath string `yaml:"get_path"` - PostPath string `yaml:"post_path"` - PutPath string `yaml:"put_path"` - DeletePath string `yaml:"delete_path"` - Model string `yaml:"model"` - ResponseModel string `yaml:"response_model"` - RequestModel string `yaml:"request_model"` - Description string `yaml:"description"` - Summary string `yaml:"summary"` - Tags []string `yaml:"tags"` - RequireAuth bool `yaml:"require_auth"` - CacheEnabled bool `yaml:"cache_enabled"` - CacheTTL int `yaml:"cache_ttl"` -} - -type GlobalConfig struct { - ModuleName string `yaml:"module_name"` - OutputDir string `yaml:"output_dir"` - EnableSwagger bool `yaml:"enable_swagger"` - EnableLogging bool `yaml:"enable_logging"` -} - -type ServiceConfig struct { - Global GlobalConfig `yaml:"global,omitempty"` - Services map[string]Service `yaml:"services"` -} - -type Service struct { - Name string `yaml:"name"` - Category string `yaml:"category"` - Package string `yaml:"package"` - Description string `yaml:"description"` - BaseURL string `yaml:"base_url"` - Timeout int `yaml:"timeout"` - RetryCount int `yaml:"retry_count"` - Endpoints map[string]EndpointGroup `yaml:"endpoints"` - Dependencies string `yaml:"dependencies,omitempty"` - Middleware string `yaml:"middleware,omitempty"` -} - -type EndpointGroup struct { - Description string `yaml:"description"` - HandlerFolder string `yaml:"handler_folder"` - HandlerFile string `yaml:"handler_file"` - HandlerName string `yaml:"handler_name"` - Functions map[string]FunctionConfig `yaml:"functions"` -} - -type FunctionConfig struct { - Methods []string `yaml:"methods"` - Path string `yaml:"path"` - Model string `yaml:"model"` - RoutesLink string `yaml:"routes_link"` - // Routes untuk endpoint generation - GetRoutes string `yaml:"get_routes"` - PostRoutes string `yaml:"post_routes"` - PutRoutes string `yaml:"put_routes"` - DeleteRoutes string `yaml:"delete_routes"` - // āœ… Path untuk swagger documentation - GetPath string `yaml:"get_path"` - PostPath string `yaml:"post_path"` - PutPath string `yaml:"put_path"` - DeletePath string `yaml:"delete_path"` - ResponseModel string `yaml:"response_model"` - RequestModel string `yaml:"request_model"` - Description string `yaml:"description"` - Summary string `yaml:"summary"` - Tags []string `yaml:"tags"` - RequireAuth bool `yaml:"require_auth"` - CacheEnabled bool `yaml:"cache_enabled"` - CacheTTL int `yaml:"cache_ttl"` -} - -type TemplateData struct { - ServiceName string - ServiceLower string - ServiceUpper string - Category string - Package string - Description string - BaseURL string - Timeout int - RetryCount int - Endpoints []EndpointData - Timestamp string - ModuleName string - HasValidator bool - HasLogger bool - HasSwagger bool - GlobalConfig GlobalConfig - ShouldGenerateStruct bool - ShouldGenerateConstructor bool - FunctionalArea string // e.g. "rujukan", "search" - HandlerName string -} - -type EndpointData struct { - Name string - NameLower string - NameUpper string - NameCamel string - Methods []string - GetRoutes string - PostRoutes string - PutRoutes string - DeleteRoutes string - GetPath string - PostPath string - PutPath string - DeletePath string - Model string - ResponseModel string - RequestModel string - DataModel string - Description string - Summary string - Tags []string - HasGet bool - HasPost bool - HasPut bool - HasDelete bool - RequireAuth bool - CacheEnabled bool - CacheTTL int - PathParams []string - ModelPackage string -} - -// Fungsi utama yang diupdate untuk menggunakan separation of concerns -func generateHandlerWithValidation(serviceName string, svc Service, gc GlobalConfig) error { - // Step 1: Generate base handler file (struct + constructor) - SEKALI SAJA - // err := generateBaseHandlerFile(serviceName, svc, gc) - // if err != nil { - // return fmt.Errorf("generate base handler: %w", err) - // } - - // Step 2: Generate methods files per endpoint group - baseDir := gc.OutputDir - for groupName, grp := range svc.Endpoints { - folder := filepath.Join(baseDir, grp.HandlerFolder) - if err := os.MkdirAll(folder, 0755); err != nil { - return fmt.Errorf("mkdir %s: %w", folder, err) - } - - // Generate methods file dengan naming yang jelas - methodsFileName := fmt.Sprintf("%s.go", strings.ToLower(groupName)) - methodsFilePath := filepath.Join(folder, methodsFileName) - - // Check if methods file exists - fileExists := false - if _, err := os.Stat(methodsFilePath); err == nil { - fileExists = true - } - - if !fileExists { - // Create new methods file - err := createMethodsFileFromConfig(methodsFilePath, svc, grp, gc, groupName) - if err != nil { - return fmt.Errorf("create methods file %s: %w", methodsFilePath, err) - } - fmt.Printf("āœ… Created methods file: %s\n", methodsFilePath) - } else { - // Update existing methods file with new functions only - err := updateExistingMethodsFile(methodsFilePath, svc, grp, gc, groupName) - if err != nil { - return fmt.Errorf("update methods file %s: %w", methodsFilePath, err) - } - fmt.Printf("āœ… Updated methods file: %s\n", methodsFilePath) - } - } - // āœ… Step 2: Generate routes - err := generateRoutes(serviceName, svc, gc) - if err != nil { - return fmt.Errorf("generate routes: %w", err) - } - - return nil -} - -// Create new methods file -func createMethodsFileFromConfig(filePath string, svc Service, grp EndpointGroup, gc GlobalConfig, functionalArea string) error { - // Collect all functions for this group - var allEndpoints []EndpointData - for fname, fcfg := range grp.Functions { - td := processFunctionData(svc, grp, fname, fcfg, gc) - allEndpoints = append(allEndpoints, td.Endpoints...) - } - - templateData := TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - Package: grp.HandlerFolder, - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - Endpoints: allEndpoints, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - ShouldGenerateStruct: false, // NEVER generate struct in methods file - ShouldGenerateConstructor: false, // NEVER generate constructor in methods file - FunctionalArea: functionalArea, - HandlerName: grp.HandlerName, // PERBAIKAN: Pastikan HandlerName diset - } - - return createMethodsFile(filePath, templateData) -} - -// Create methods file using methods-only template -func createMethodsFile(filePath string, templateData TemplateData) error { - if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { - return err - } - - tmpl := template.New("methods").Funcs(template.FuncMap{ - "title": strings.Title, - "index": func(slice []string, i int) string { - if i >= 0 && i < len(slice) { - return slice[i] - } - return "" - }, - }) - - tmpl, err := tmpl.Parse(handlerTemplate) - if err != nil { - return err - } - - file, err := os.Create(filePath) - if err != nil { - return err - } - defer file.Close() - - return tmpl.Execute(file, templateData) -} - -// Update existing methods file (sama seperti sebelumnya tapi tanpa struct) -func updateExistingMethodsFile(filePath string, svc Service, grp EndpointGroup, gc GlobalConfig, functionalArea string) error { - existingContent, err := ioutil.ReadFile(filePath) - if err != nil { - return err - } - - content := string(existingContent) - // Check for existing functions and collect new ones - var newEndpoints []EndpointData - - for fname, fcfg := range grp.Functions { - functionExists := false - for _, method := range fcfg.Methods { - funcName := generateFunctionName(fname, method) - // PERBAIKAN: Gunakan grp.HandlerName bukan svc.Name - sig := fmt.Sprintf("func (h *%sHandler) %s", grp.HandlerName, funcName) - if strings.Contains(content, sig) { - fmt.Printf("āš ļø Skip existing: %s (%s)\n", fname, funcName) - functionExists = true - break - } - } - - if functionExists { - continue - } - - td := processFunctionData(svc, grp, fname, fcfg, gc) - newEndpoints = append(newEndpoints, td.Endpoints...) - fmt.Printf("āœ… Will add: %s\n", fname) - } - - if len(newEndpoints) == 0 { - fmt.Printf("ā­ļø No new functions to add\n") - return nil - } - - // Generate new functions using methods-only template - templateData := TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - Package: grp.HandlerFolder, - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - Endpoints: newEndpoints, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - ShouldGenerateStruct: false, // NEVER - ShouldGenerateConstructor: false, // NEVER - FunctionalArea: functionalArea, - HandlerName: grp.HandlerName, // PERBAIKAN: Pastikan HandlerName diset - } - - newFunctions, err := generateNewMethodsOnly(templateData) - if err != nil { - return err - } - - // Merge content - mergedContent := mergeGoFileContent(content, newFunctions) - return ioutil.WriteFile(filePath, []byte(mergedContent), 0644) -} - -// Generate new methods using methods-only template -func generateNewMethodsOnly(templateData TemplateData) (string, error) { - tmpl := template.New("newMethods") - tmpl, err := tmpl.Parse(handlerTemplate) - if err != nil { - return "", err - } - - var result strings.Builder - err = tmpl.Execute(&result, templateData) - if err != nil { - return "", err - } - - return result.String(), nil -} - -// Helper toCamelCase -func toCamelCase(s string) string { - parts := strings.FieldsFunc(s, func(r rune) bool { - return r == '_' || r == '-' || r == ' ' - }) - for i, p := range parts { - parts[i] = strings.Title(strings.ToLower(p)) - } - return strings.Join(parts, "") -} - -// Parse struktur direktori dari endpoints YAML -func parseDirectoryStructure(endpoints map[string]interface{}, serviceName string) *DirectoryInfo { - root := &DirectoryInfo{ - Path: "", - Children: make(map[string]*DirectoryInfo), - } - - for key, value := range endpoints { - parseNestedEndpoints(root, key, value) - } - - return root -} - -func parseNestedEndpoints(parent *DirectoryInfo, name string, value interface{}) { - switch v := value.(type) { - case map[string]interface{}: - // Check if this contains direct endpoint config - if isDirectEndpoint(v) { - // This is a direct endpoint - create as file - parent.Children[name] = &DirectoryInfo{ - Path: name, - IsFile: true, - Functions: []FunctionInfo{parseEndpointToFunction(name, v)}, - Children: make(map[string]*DirectoryInfo), - } - } else { - // This is nested structure - create as directory or file - child := &DirectoryInfo{ - Path: name, - IsFile: false, - Functions: make([]FunctionInfo, 0), - Children: make(map[string]*DirectoryInfo), - } - - // Check if any direct children are endpoints - hasDirectEndpoints := false - for childName, childValue := range v { - if childMap, ok := childValue.(map[string]interface{}); ok && isDirectEndpoint(childMap) { - hasDirectEndpoints = true - child.Functions = append(child.Functions, parseEndpointToFunction(childName, childMap)) - } - } - - if hasDirectEndpoints { - child.IsFile = true - } - - parent.Children[name] = child - - // Recursively parse nested children - for childName, childValue := range v { - if childMap, ok := childValue.(map[string]interface{}); ok && !isDirectEndpoint(childMap) { - parseNestedEndpoints(child, childName, childValue) - } - } - } - } -} - -func isDirectEndpoint(m map[string]interface{}) bool { - _, hasMethods := m["methods"] - _, hasGetPath := m["get_path"] - _, hasPostPath := m["post_path"] - return hasMethods || hasGetPath || hasPostPath -} - -func parseEndpointToFunction(name string, config map[string]interface{}) FunctionInfo { - function := FunctionInfo{ - Name: name, - Endpoint: name, - Methods: make([]string, 0), - } - - if methods, ok := config["methods"].([]interface{}); ok { - for _, method := range methods { - if methodStr, ok := method.(string); ok { - function.Methods = append(function.Methods, strings.ToUpper(strings.TrimSpace(methodStr))) - } - } - } else if methodStr, ok := config["methods"].(string); ok { - // Handle case where methods is a string like "GET,POST" - methods := strings.Split(methodStr, ",") - for _, method := range methods { - function.Methods = append(function.Methods, strings.ToUpper(strings.TrimSpace(method))) - } - } - - return function -} - -// Process directory structure dan generate files -func processDirectoryStructure(baseDir string, dirInfo *DirectoryInfo, service Service, globalConfig GlobalConfig, validation *HandlerValidation) error { - for name, child := range dirInfo.Children { - currentPath := filepath.Join(baseDir, child.Path) - - if child.IsFile { - // Process as file - err := processHandlerFile(currentPath, name, child, service, globalConfig, validation) - if err != nil { - return fmt.Errorf("failed to process file %s: %w", name, err) - } - } else { - // Create directory dan process children - if err := os.MkdirAll(currentPath, 0755); err != nil { - return fmt.Errorf("failed to create directory %s: %w", currentPath, err) - } - - err := processDirectoryStructure(currentPath, child, service, globalConfig, validation) - if err != nil { - return err - } - } - } - - return nil -} -func processFunctionData(svc Service, grp EndpointGroup, fname string, fcfg FunctionConfig, gc GlobalConfig) TemplateData { - ed := EndpointData{ - Name: toCamelCase(fname), - NameLower: strings.ToLower(fname), - NameUpper: strings.ToUpper(fname), - NameCamel: toCamelCase(fname), - Methods: fcfg.Methods, - GetRoutes: fcfg.GetRoutes, - PostRoutes: fcfg.PostRoutes, - PutRoutes: fcfg.PutRoutes, - DeleteRoutes: fcfg.DeleteRoutes, - GetPath: fcfg.Path, - PostPath: fcfg.Path, - PutPath: fcfg.Path, - DeletePath: fcfg.Path, - Model: fcfg.Model, - ResponseModel: fcfg.ResponseModel, - RequestModel: fcfg.RequestModel, - DataModel: strings.Replace(fcfg.ResponseModel, "Response", "Data", 1), - Description: fcfg.Description, - Summary: fcfg.Summary, - Tags: fcfg.Tags, - RequireAuth: fcfg.RequireAuth, - CacheEnabled: fcfg.CacheEnabled, - CacheTTL: fcfg.CacheTTL, - PathParams: extractPathParams(fcfg.Path), - ModelPackage: grp.HandlerFolder, - } - // set flags - for _, m := range fcfg.Methods { - switch strings.ToUpper(m) { - case "GET": - ed.HasGet = true - case "POST": - ed.HasPost = true - case "PUT": - ed.HasPut = true - case "DELETE": - ed.HasDelete = true - } - } - return TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - Package: grp.HandlerFolder, - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - Endpoints: []EndpointData{ed}, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - HandlerName: grp.HandlerName, - } -} - -// extractPathParams (jika belum ada) -func extractPathParams(path string) []string { - var ps []string - for _, part := range strings.Split(path, "/") { - if strings.HasPrefix(part, ":") { - ps = append(ps, strings.TrimPrefix(part, ":")) - } - } - return ps -} - -// Process individual handler file - PERBAIKAN: Hapus referensi grp.HandlerName -func processHandlerFile(basePath, fileName string, dirInfo *DirectoryInfo, service Service, globalConfig GlobalConfig, validation *HandlerValidation) error { - filePath := filepath.Join(filepath.Dir(basePath), fmt.Sprintf("%s.go", fileName)) - fmt.Printf("šŸ“„ Processing file: %s\n", filePath) - - // Check if file exists - fileExists := false - if _, err := os.Stat(filePath); err == nil { - fileExists = true - fmt.Printf(" šŸ“‹ File exists, checking functions...\n") - } - - var existingFunctions map[string]bool - if fileExists { - // Parse existing functions - PERBAIKAN: Hanya gunakan 1 parameter - functions, err := extractExistingFunctions(filePath) - if err != nil { - fmt.Printf(" āš ļø Warning: Could not parse existing file: %v\n", err) - existingFunctions = make(map[string]bool) - } else { - existingFunctions = functions - fmt.Printf(" āœ“ Found %d existing functions\n", len(functions)) - } - } else { - existingFunctions = make(map[string]bool) - } - - // Determine which functions to generate - functionsToGenerate := make([]FunctionInfo, 0) - for _, fnInfo := range dirInfo.Functions { - for _, method := range fnInfo.Methods { - functionName := generateFunctionName(fnInfo.Name, method) - if !existingFunctions[functionName] { - functionsToGenerate = append(functionsToGenerate, FunctionInfo{ - Name: fnInfo.Name, - Methods: []string{method}, - Endpoint: fnInfo.Endpoint, - }) - validation.NewFunctions = append(validation.NewFunctions, functionName) - fmt.Printf(" āœ… Will generate: %s\n", functionName) - } else { - fmt.Printf(" ā­ļø Already exists: %s\n", functionName) - } - } - } - - // Generate file if needed - if len(functionsToGenerate) > 0 { - templateData := prepareTemplateData(fileName, service, globalConfig, functionsToGenerate) - - if fileExists { - // Merge with existing file - err := mergeWithExistingFile(filePath, templateData) - if err != nil { - return fmt.Errorf("failed to merge file %s: %w", filePath, err) - } - validation.UpdatedFiles = append(validation.UpdatedFiles, filePath) - fmt.Printf(" šŸ“ Updated existing file\n") - } else { - // Create new file - err := createNewHandlerFile(filePath, templateData) - if err != nil { - return fmt.Errorf("failed to create file %s: %w", filePath, err) - } - validation.CreatedFiles = append(validation.CreatedFiles, filePath) - fmt.Printf(" šŸ“ Created new file\n") - } - } else if !fileExists { - fmt.Printf(" ā­ļø No functions to generate and file doesn't exist\n") - } else { - fmt.Printf(" ā­ļø All functions already exist\n") - } - - return nil -} - -// Extract existing functions from Go file -func extractExistingFunctions(filePath string) (map[string]bool, error) { - fileSet := token.NewFileSet() - node, err := parser.ParseFile(fileSet, filePath, nil, parser.ParseComments) - if err != nil { - return nil, err - } - - functions := make(map[string]bool) - ast.Inspect(node, func(n ast.Node) bool { - if fn, ok := n.(*ast.FuncDecl); ok { - if fn.Name != nil { - functions[fn.Name.Name] = true - } - } - return true - }) - return functions, nil -} - -// Generate function name berdasarkan endpoint dan method -func generateFunctionName(endpointName, method string) string { - switch strings.ToUpper(method) { - case "GET": - return fmt.Sprintf("Get%s", strings.Title(endpointName)) - case "POST": - return fmt.Sprintf("Create%s", strings.Title(endpointName)) - case "PUT": - return fmt.Sprintf("Update%s", strings.Title(endpointName)) - case "DELETE": - return fmt.Sprintf("Delete%s", strings.Title(endpointName)) - case "PATCH": - return fmt.Sprintf("Patch%s", strings.Title(endpointName)) - default: - return fmt.Sprintf("%s%s", strings.Title(method), strings.Title(endpointName)) - } -} - -// Prepare template data -func prepareTemplateData(packageName string, service Service, globalConfig GlobalConfig, functions []FunctionInfo) TemplateData { - endpoints := make([]EndpointData, 0) - - for _, fnInfo := range functions { - endpoint := EndpointData{ - Name: strings.Title(fnInfo.Name), - NameLower: strings.ToLower(fnInfo.Name), - NameUpper: strings.ToUpper(fnInfo.Name), - NameCamel: toCamelCase(fnInfo.Name), - ModelPackage: packageName, - Model: fmt.Sprintf("%sRequest", strings.Title(fnInfo.Name)), - ResponseModel: fmt.Sprintf("%sResponse", strings.Title(fnInfo.Name)), - RequestModel: fmt.Sprintf("%sRequest", strings.Title(fnInfo.Name)), - DataModel: fmt.Sprintf("%sData", strings.Title(fnInfo.Name)), - Description: fmt.Sprintf("Handle %s operations", fnInfo.Name), - Tags: []string{strings.Title(packageName)}, - } - - // Set paths dan methods - for _, method := range fnInfo.Methods { - switch strings.ToUpper(method) { - case "GET": - endpoint.HasGet = true - endpoint.GetPath = fmt.Sprintf("/%s/%s", packageName, fnInfo.Name) - case "POST": - endpoint.HasPost = true - endpoint.PostPath = fmt.Sprintf("/%s", packageName) - case "PUT": - endpoint.HasPut = true - endpoint.PutPath = fmt.Sprintf("/%s/%s", packageName, fnInfo.Name) - case "DELETE": - endpoint.HasDelete = true - endpoint.DeletePath = fmt.Sprintf("/%s/%s", packageName, fnInfo.Name) - } - } - - endpoints = append(endpoints, endpoint) - } - - return TemplateData{ - ServiceName: service.Name, - ServiceLower: strings.ToLower(service.Name), - ServiceUpper: strings.ToUpper(service.Name), - Category: service.Category, - Package: packageName, - Description: service.Description, - BaseURL: service.BaseURL, - Timeout: getOrDefault(service.Timeout, 30), - RetryCount: getOrDefault(service.RetryCount, 3), - Endpoints: endpoints, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: globalConfig.ModuleName, - HasValidator: true, - HasLogger: globalConfig.EnableLogging, - HasSwagger: globalConfig.EnableSwagger, - GlobalConfig: globalConfig, - HandlerName: strings.Title(packageName), - } -} - -// Check if handler struct already exists in directory -func shouldGenerateHandlerStruct(baseDir, handlerName string) bool { - structSignature := fmt.Sprintf("type %sHandler struct", handlerName) - - files, err := ioutil.ReadDir(baseDir) - if err != nil { - return true // If directory doesn't exist, generate struct - } - - for _, file := range files { - if file.IsDir() || !strings.HasSuffix(file.Name(), ".go") { - continue - } - - filePath := filepath.Join(baseDir, file.Name()) - content, err := ioutil.ReadFile(filePath) - if err != nil { - continue - } - - if strings.Contains(string(content), structSignature) { - return false // Struct already exists - } - } - - return true // Struct not found, should generate -} - -// Check if constructor already exists -func shouldGenerateConstructor(baseDir, handlerName string) bool { - constructorSignature := fmt.Sprintf("func New%sHandler", handlerName) - - files, err := ioutil.ReadDir(baseDir) - if err != nil { - return true - } - - for _, file := range files { - if file.IsDir() || !strings.HasSuffix(file.Name(), ".go") { - continue - } - - filePath := filepath.Join(baseDir, file.Name()) - content, err := ioutil.ReadFile(filePath) - if err != nil { - continue - } - - if strings.Contains(string(content), constructorSignature) { - return false - } - } - - return true -} - -// Generate base handler file (struct + constructor only) -func generateBaseHandlerFile(serviceName string, svc Service, gc GlobalConfig) error { - baseDir := gc.OutputDir - if err := os.MkdirAll(baseDir, 0755); err != nil { - return fmt.Errorf("mkdir %s: %w", baseDir, err) - } - - baseFileName := fmt.Sprintf("%s_base.go", strings.ToLower(serviceName)) - baseFilePath := filepath.Join(baseDir, baseFileName) - - // Skip if base file already exists - if _, err := os.Stat(baseFilePath); err == nil { - fmt.Printf("ā­ļø Base handler already exists: %s\n", baseFilePath) - return nil - } - - templateData := TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - Package: "handlers", - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - Endpoints: []EndpointData{}, // Empty - base only - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - } - - return createBaseHandlerFile(baseFilePath, templateData) -} - -// Template untuk base handler (struct + constructor only) -const baseHandlerTemplate = `// Package handlers handles {{.HandlerName}} BPJS services - Base Handler -// Generated on: {{.Timestamp}} -package handlers - -import ( - "{{.ModuleName}}/internal/config" - "{{.ModuleName}}/internal/services/bpjs" - "{{.ModuleName}}/pkg/logger" - - "github.com/go-playground/validator/v10" -) - -// {{.HandlerName}}Handler handles {{.HandlerName}} BPJS services -type {{.HandlerName}}Handler struct { - service services.VClaimService - validator *validator.Validate - logger logger.Logger - config config.BpjsConfig -} - -// {{.HandlerName}}HandlerConfig contains configuration for {{.HandlerName}}Handler -type {{.HandlerName}}HandlerConfig struct { - BpjsConfig config.BpjsConfig - Logger logger.Logger - Validator *validator.Validate -} - -// New{{.HandlerName}}Handler creates a new {{.HandlerName}}Handler -func New{{.HandlerName}}Handler(cfg {{.HandlerName}}HandlerConfig) *{{.HandlerName}}Handler { - return &{{.HandlerName}}Handler{ - service: services.NewService(cfg.BpjsConfig), - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.BpjsConfig, - } -} -` - -// Function to create base handler file -func createBaseHandlerFile(filePath string, templateData TemplateData) error { - if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { - return err - } - - tmpl := template.New("baseHandler").Funcs(template.FuncMap{ - "title": strings.Title, - }) - - tmpl, err := tmpl.Parse(baseHandlerTemplate) - if err != nil { - return err - } - - file, err := os.Create(filePath) - if err != nil { - return err - } - defer file.Close() - - return tmpl.Execute(file, templateData) -} - -// Template untuk handler file lengkap (untuk file baru) -const handlerTemplate = `// Package {{.Package}} handles {{.HandlerName}} BPJS services -// Generated on: {{.Timestamp}} -package handlers - -import ( - "context" - "encoding/json" - "net/http" - "strings" - "time" - - "{{.ModuleName}}/internal/config" - "{{.ModuleName}}/internal/models" - "{{.ModuleName}}/internal/models/vclaim/{{.Package}}" - "{{.ModuleName}}/internal/services/bpjs" - "{{.ModuleName}}/pkg/logger" - - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" - "github.com/google/uuid" -) -// {{.HandlerName}}Handler handles {{.HandlerName}} BPJS services -type {{.HandlerName}}Handler struct { - service services.VClaimService - validator *validator.Validate - logger logger.Logger - config config.BpjsConfig -} -// {{.HandlerName}}HandlerConfig contains configuration for {{.HandlerName}}Handler -type {{.HandlerName}}HandlerConfig struct { - BpjsConfig config.BpjsConfig - Logger logger.Logger - Validator *validator.Validate -} -// New{{.HandlerName}}Handler creates a new {{.HandlerName}}Handler -func New{{.HandlerName}}Handler(cfg {{.HandlerName}}HandlerConfig) *{{.HandlerName}}Handler { - return &{{.HandlerName}}Handler{ - service: services.NewService(cfg.BpjsConfig), - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.BpjsConfig, - } -} -{{range .Endpoints}} -{{if .HasGet}} -// Get{{.Name}} godoc -// @Summary Get {{.Name}} data -// @Description {{.Description}} -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully retrieved {{.Name}} data" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.GetRoutes}} [get] -func (h *{{$.HandlerName}}Handler) Get{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.GetPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.GetPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.GetRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.GetRawResponse(ctx, "{{.GetPath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to get {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasPost}} -// Create{{.Name}} godoc -// @Summary Create new {{.Name}} -// @Description Create new {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} data" -// @Success 201 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully created {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PostRoutes}} [post] -func (h *{{$.HandlerName}}Handler) Create{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PostPath}}", - }) - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - {{if .PathParams}} - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - endpoint := "{{.PostPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - resp, err := h.service.PostRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PostRawResponse(ctx, "{{.PostPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} already exists or conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusCreated, response) -} -{{end}} - - -{{if .HasPut}} -// Update{{.Name}} godoc -// @Summary Update existing {{.Name}} -// @Description Update existing {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} update data" -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully updated {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict - update conflict occurred" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PutRoutes}} [put] -func (h *{{$.HandlerName}}Handler) Update{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PutPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.PutPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.PutRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PutRawResponse(ctx, "{{.PutPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to update {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "Update conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasDelete}} -// Delete{{.Name}} godoc -// @Summary Delete existing {{.Name}} -// @Description Delete existing {{.Name}} from BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully deleted {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.DeleteRoutes}} [delete] -func (h *{{$.HandlerName}}Handler) Delete{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.DeletePath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.DeletePath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.DeleteRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.DeleteRawResponse(ctx, "{{.DeletePath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to delete {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } else { - // For delete operations, sometimes there's no data in response - response.Data = nil - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{end}}` - -// Template untuk menambah function saja (untuk file yang sudah ada) -const functionsOnlyTemplate = `{{range .Endpoints}} -{{if .HasGet}} -// Get{{.Name}} godoc -// @Summary Get {{.Name}} data -// @Description {{.Description}} -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully retrieved {{.Name}} data" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.GetRoutes}} [get] -func (h *{{$.HandlerName}}Handler) Get{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.GetPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.GetPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.GetRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.GetRawResponse(ctx, "{{.GetPath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to get {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasPost}} -// Create{{.Name}} godoc -// @Summary Create new {{.Name}} -// @Description Create new {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} data" -// @Success 201 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully created {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PostRoutes}} [post] -func (h *{{$.HandlerName}}Handler) Create{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PostPath}}", - }) - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - {{if .PathParams}} - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - endpoint := "{{.PostPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - resp, err := h.service.PostRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PostRawResponse(ctx, "{{.PostPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} already exists or conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusCreated, response) -} -{{end}} - - -{{if .HasPut}} -// Update{{.Name}} godoc -// @Summary Update existing {{.Name}} -// @Description Update existing {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} update data" -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully updated {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict - update conflict occurred" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PutRoutes}} [put] -func (h *{{$.HandlerName}}Handler) Update{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PutPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.PutPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.PutRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PutRawResponse(ctx, "{{.PutPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to update {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "Update conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasDelete}} -// Delete{{.Name}} godoc -// @Summary Delete existing {{.Name}} -// @Description Delete existing {{.Name}} from BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully deleted {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.DeleteRoutes}} [delete] -func (h *{{$.HandlerName}}Handler) Delete{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.DeletePath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.DeletePath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.DeleteRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.DeleteRawResponse(ctx, "{{.DeletePath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to delete {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } else { - // For delete operations, sometimes there's no data in response - response.Data = nil - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{end}}` - -// Create new handler file -func createNewHandlerFile(filePath string, templateData TemplateData) error { - // Create directory - if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { - return err - } - - // Parse template - tmpl := template.New("handler").Funcs(template.FuncMap{ - "title": strings.Title, - "index": func(slice []string, i int) string { - if i >= 0 && i < len(slice) { - return slice[i] - } - return "" - }, - }) - - tmpl, err := tmpl.Parse(handlerTemplate) - if err != nil { - return err - } - - // Create file - file, err := os.Create(filePath) - if err != nil { - return err - } - defer file.Close() - - // Execute template - return tmpl.Execute(file, templateData) -} - -// Merge dengan existing file -func mergeWithExistingFile(filePath string, templateData TemplateData) error { - // Read existing content - existingContent, err := ioutil.ReadFile(filePath) - if err != nil { - return err - } - - // Generate new functions - newFunctions, err := generateNewFunctionsOnly(templateData) - if err != nil { - return err - } - - // Merge content - mergedContent := mergeGoFileContent(string(existingContent), newFunctions) - - // Write back - return ioutil.WriteFile(filePath, []byte(mergedContent), 0644) -} - -func generateNewFunctionsOnly(templateData TemplateData) (string, error) { - funcTemplate := ` -{{range .Endpoints}} -{{if .HasGet}} -// Get{{.Name}} godoc -// @Summary Get {{.Name}} data -// @Description {{.Description}} -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully retrieved {{.Name}} data" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.GetRoutes}} [get] -func (h *{{$.HandlerName}}Handler) Get{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.GetPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.GetPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.GetRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.GetRawResponse(ctx, "{{.GetPath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to get {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasPost}} -// Create{{.Name}} godoc -// @Summary Create new {{.Name}} -// @Description Create new {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} data" -// @Success 201 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully created {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PostRoutes}} [post] -func (h *{{$.HandlerName}}Handler) Create{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PostPath}}", - }) - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - {{if .PathParams}} - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - endpoint := "{{.PostPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - resp, err := h.service.PostRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PostRawResponse(ctx, "{{.PostPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} already exists or conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusCreated, response) -} -{{end}} - - -{{if .HasPut}} -// Update{{.Name}} godoc -// @Summary Update existing {{.Name}} -// @Description Update existing {{.Name}} in BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Param request body {{.ModelPackage}}.{{.RequestModel}} true "{{.Name}} update data" -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully updated {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict - update conflict occurred" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PutRoutes}} [put] -func (h *{{$.HandlerName}}Handler) Update{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PutPath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Bind and validate request body - var req {{.ModelPackage}}.{{.RequestModel}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.PutPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.PutRawResponse(ctx, endpoint, req) - {{else}} - resp, err := h.service.PutRawResponse(ctx, "{{.PutPath}}", req) - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to update {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - if strings.Contains(err.Error(), "409") || strings.Contains(err.Error(), "conflict") { - c.JSON(http.StatusConflict, models.ErrorResponseBpjs{ - Status: "error", - Message: "Update conflict occurred", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasDelete}} -// Delete{{.Name}} godoc -// @Summary Delete existing {{.Name}} -// @Description Delete existing {{.Name}} from BPJS system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json {{if .RequireAuth}} -// @Security ApiKeyAuth {{end}} -// @Param X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} -// @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully deleted {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - {{.Name}} not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.DeleteRoutes}} [delete] -func (h *{{$.HandlerName}}Handler) Delete{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.DeletePath}}", - {{range .PathParams}} - "{{.}}": c.Param("{{.}}"), - {{end}} - }) - {{end}} - - // Extract path parameters - {{range .PathParams}} - {{.}} := c.Param("{{.}}") - if {{.}} == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.DeletePath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - resp, err := h.service.DeleteRawResponse(ctx, endpoint) - {{else}} - resp, err := h.service.DeleteRawResponse(ctx, "{{.DeletePath}}") - {{end}} - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to delete {{.Name}}", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - - // Handle specific BPJS errors - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusNotFound, models.ErrorResponseBpjs{ - Status: "error", - Message: "{{.Name}} not found", - RequestID: requestID, - }) - return - } - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Map the raw response - response.MetaData = resp.MetaData - if resp.Response != nil { - response.Data = &{{.ModelPackage}}.{{.DataModel}}{} - if respStr, ok := resp.Response.(string); ok { - // Decrypt the response string - consID, secretKey, _, tstamp, _ := h.config.SetHeader() - decryptedResp, err := services.ResponseVclaim(respStr, consID+secretKey+tstamp) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to decrypt response", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - } else { - json.Unmarshal([]byte(decryptedResp), response.Data) - } - } else if respMap, ok := resp.Response.(map[string]interface{}); ok { - // Response is already unmarshaled JSON - if dataMap, exists := respMap["{{.ModelPackage}}"]; exists { - dataBytes, _ := json.Marshal(dataMap) - json.Unmarshal(dataBytes, response.Data) - } else { - // Try to unmarshal the whole response - respBytes, _ := json.Marshal(resp.Response) - json.Unmarshal(respBytes, response.Data) - } - } - } else { - // For delete operations, sometimes there's no data in response - response.Data = nil - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - {{if $.HasLogger}} - h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{end}}` - - tmpl := template.New("functions") - tmpl, err := tmpl.Parse(funcTemplate) - if err != nil { - return "", err - } - - var result strings.Builder - err = tmpl.Execute(&result, templateData) - if err != nil { - return "", err - } - - // Remove duplicate function definitions by simple regex grouping - // This is a simple approach to avoid duplicate functions in merged content - funcRegex := regexp.MustCompile(`(?s)(func \(h \*\w+Handler\) Get\w+\(c \*gin.Context\) \{.*?\})`) - matches := funcRegex.FindAllString(result.String(), -1) - uniqueFuncs := make(map[string]bool) - var uniqueResult strings.Builder - for _, m := range matches { - if !uniqueFuncs[m] { - uniqueFuncs[m] = true - uniqueResult.WriteString(m) - uniqueResult.WriteString("\n\n") - } - } - - // If no matches found, return original result - if uniqueResult.Len() == 0 { - return result.String(), nil - } - - return uniqueResult.String(), nil -} - -func mergeGoFileContent(existingContent, newFunctions string) string { - // Find last closing brace - re := regexp.MustCompile(`}\s*$`) - lastBraceIndex := re.FindStringIndex(existingContent) - - if lastBraceIndex == nil { - return existingContent + "\n" + newFunctions - } - - before := existingContent[:lastBraceIndex[0]] - after := existingContent[lastBraceIndex[0]:] - - return before + "\n" + newFunctions + "\n" + after -} - -// Main function -func main() { - if len(os.Args) < 2 { - printUsage() - os.Exit(1) - } - - configFile := os.Args[1] - var targetService string - if len(os.Args) > 2 { - targetService = os.Args[2] - } - - config, err := loadConfig(configFile) - if err != nil { - fmt.Printf("Error loading config: %v\n", err) - os.Exit(1) - } - - fmt.Println("šŸš€ Starting BPJS Dynamic Handler Generation with Validation...") - fmt.Printf("šŸ“ Config file: %s\n", configFile) - if targetService != "" { - fmt.Printf("šŸŽÆ Target service: %s\n", targetService) - } - - generated := 0 - errors := 0 - - for serviceName, service := range config.Services { - if targetService != "" && serviceName != targetService { - continue - } - - err := generateHandlerWithValidation(serviceName, service, config.Global) - if err != nil { - fmt.Printf("āŒ Error generating handler for %s: %v\n", serviceName, err) - errors++ - continue - } - - generated++ - } - - // Summary - fmt.Println("\nšŸ“Š Generation Summary:") - fmt.Printf("āœ… Successfully processed: %d services\n", generated) - if errors > 0 { - fmt.Printf("āŒ Failed: %d services\n", errors) - } - - if generated > 0 { - fmt.Println("šŸŽ‰ Generation completed successfully!") - } -} - -// Helper functions -func loadConfig(filename string) (*ServiceConfig, error) { - data, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - - var config ServiceConfig - err = yaml.Unmarshal(data, &config) - if err != nil { - return nil, err - } - - // Set defaults - if config.Global.ModuleName == "" { - config.Global.ModuleName = "api-service" - } - if config.Global.OutputDir == "" { - config.Global.OutputDir = "internal/handlers" - } - - return &config, nil -} - -func getOrDefault(value, defaultValue int) int { - if value == 0 { - return defaultValue - } - return value -} - -func printUsage() { - fmt.Println("BPJS Dynamic Handler Generator with Function Validation") - fmt.Println() - fmt.Println("Usage:") - fmt.Println(" go run generate-handler.go [service-name]") - fmt.Println() - fmt.Println("Examples:") - fmt.Println(" go run generate-handler.go services-config-bpjs.yaml") - fmt.Println(" go run generate-handler.go services-config-bpjs.yaml vclaim") -} - -func generateRoutes(serviceName string, svc Service, gc GlobalConfig) error { - routesFilePath := "internal/routes/v1/routes.go" - routesContent, err := ioutil.ReadFile(routesFilePath) - if err != nil { - return fmt.Errorf("failed to read routes file: %w", err) - } - - routesContentStr := string(routesContent) - - // Check if routes are already registered - if strings.Contains(routesContentStr, fmt.Sprintf("Register%sRoutes", svc.Name)) { - fmt.Printf("āš ļø Routes for %s already registered in main routes file\n", svc.Name) - return nil - } - - var imports []string - var allRoutes []string - - // āœ… TAMBAHAN: Extract existing routes untuk validasi - existingRoutes := extractExistingRoutes(routesContentStr) - - processedFolders := make(map[string]bool) - for groupName, grp := range svc.Endpoints { - if !processedFolders[grp.HandlerFolder] { - importLine := fmt.Sprintf("\t%sHandlers \"%s/internal/handlers/%s\"", - grp.HandlerFolder, gc.ModuleName, grp.HandlerFolder) - imports = append(imports, importLine) - processedFolders[grp.HandlerFolder] = true - fmt.Printf("āœ… Added import: %sHandlers\n", grp.HandlerFolder) - } else { - fmt.Printf("āš ļø Skipped duplicate import for folder: %s\n", grp.HandlerFolder) - } - - var routesCode strings.Builder - hasNewRoutes := false // āœ… Track if this group has new routes - - // Gunakan groupName untuk comment dan identifier - routesCode.WriteString(fmt.Sprintf("\n\t// %s (%s) routes\n", grp.Description, groupName)) - - // Handler instantiation menggunakan HandlerName dari config - routesCode.WriteString(fmt.Sprintf("\t%sHandler := %sHandlers.New%sHandler(%sHandlers.%sHandlerConfig{\n", - strings.ToLower(grp.HandlerName), - grp.HandlerFolder, - grp.HandlerName, - grp.HandlerFolder, - grp.HandlerName)) - - routesCode.WriteString("\t\tBpjsConfig: cfg.Bpjs,\n") - routesCode.WriteString("\t\tLogger: *logger.Default(),\n") - routesCode.WriteString("\t\tValidator: validator.New(),\n") - routesCode.WriteString("\t})\n") - - // āœ… GUNAKAN groupName untuk route group path - routesCode.WriteString(fmt.Sprintf("\t%sGroup := v1.Group(\"/%s\")\n", - strings.ToLower(grp.HandlerName), groupName)) - - // Process functions - for fname, fcfg := range grp.Functions { - td := processFunctionData(svc, grp, fname, fcfg, gc) - - for _, endpoint := range td.Endpoints { - handlerVar := strings.ToLower(grp.HandlerName) + "Handler" - groupVar := strings.ToLower(grp.HandlerName) + "Group" - - // āœ… MODIFIKASI: Loop through methods dan gunakan specific routes - for _, method := range fcfg.Methods { - var cleanPath string - - // āœ… Pilih path berdasarkan method - switch strings.ToUpper(method) { - case "GET": - cleanPath = fcfg.GetRoutes - if cleanPath == "" { - cleanPath = fcfg.GetPath - } - case "POST": - cleanPath = fcfg.PostRoutes - if cleanPath == "" { - cleanPath = fcfg.PostPath - } - case "PUT": - cleanPath = fcfg.PutRoutes - if cleanPath == "" { - cleanPath = fcfg.PutPath - } - case "DELETE": - cleanPath = fcfg.DeleteRoutes - if cleanPath == "" { - cleanPath = fcfg.DeletePath - } - default: - fmt.Printf("āš ļø Unsupported HTTP method: %s for function %s\n", method, fname) - continue - } - - // āœ… Final fallback ke path jika specific route kosong - if cleanPath == "" { - cleanPath = fcfg.Path - } - - // āœ… Bersihkan path - hapus prefix groupName jika ada - if strings.HasPrefix(cleanPath, "/"+groupName) { - cleanPath = strings.TrimPrefix(cleanPath, "/"+groupName) - } - if cleanPath == "" { - cleanPath = "/" - } - - // āœ… TAMBAHAN: Check if route already exists - routeSignature := fmt.Sprintf("%s.%s(\"%s\", %s.%s%s)", - groupVar, strings.ToUpper(method), cleanPath, handlerVar, - getMethodPrefix(method), endpoint.Name) - - if existingRoutes[routeSignature] { - fmt.Printf("ā­ļø Route already exists: %s %s\n", method, cleanPath) - continue - } - - // āœ… Generate route berdasarkan method hanya jika belum ada - switch strings.ToUpper(method) { - case "GET": - routesCode.WriteString(fmt.Sprintf("\t%s.GET(\"%s\", %s.Get%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - hasNewRoutes = true - fmt.Printf("āœ… Adding new route: GET %s\n", cleanPath) - case "POST": - routesCode.WriteString(fmt.Sprintf("\t%s.POST(\"%s\", %s.Create%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - hasNewRoutes = true - fmt.Printf("āœ… Adding new route: POST %s\n", cleanPath) - case "PUT": - routesCode.WriteString(fmt.Sprintf("\t%s.PUT(\"%s\", %s.Update%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - hasNewRoutes = true - fmt.Printf("āœ… Adding new route: PUT %s\n", cleanPath) - case "DELETE": - routesCode.WriteString(fmt.Sprintf("\t%s.DELETE(\"%s\", %s.Delete%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - hasNewRoutes = true - fmt.Printf("āœ… Adding new route: DELETE %s\n", cleanPath) - } - } - } - } - - // āœ… Hanya tambahkan ke allRoutes jika ada route baru - if hasNewRoutes { - allRoutes = append(allRoutes, routesCode.String()) - } else { - fmt.Printf("ā­ļø No new routes for group: %s\n", groupName) - } - } - - // āœ… Skip jika tidak ada routes baru sama sekali - if len(allRoutes) == 0 { - fmt.Printf("ā­ļø No new routes to add for service: %s\n", svc.Name) - return nil - } - - // āœ… PERBAIKAN: Insert imports setelah "api-service/internal/database" - if len(imports) > 0 { - importSection := strings.Join(imports, "\n") + "\n" - - databaseImportMarker := fmt.Sprintf("\"%s/internal/database\"", gc.ModuleName) - if strings.Contains(routesContentStr, databaseImportMarker) { - markerPos := strings.Index(routesContentStr, databaseImportMarker) - endOfLinePos := strings.Index(routesContentStr[markerPos:], "\n") + markerPos - routesContentStr = routesContentStr[:endOfLinePos+1] + importSection + routesContentStr[endOfLinePos+1:] - fmt.Printf("āœ… Inserted imports after database import\n") - } else { - importMarker := "import (" - if strings.Contains(routesContentStr, importMarker) { - importIndex := strings.Index(routesContentStr, importMarker) + len(importMarker) - routesContentStr = routesContentStr[:importIndex] + "\n" + importSection + routesContentStr[importIndex:] - fmt.Printf("āš ļø Database import not found, inserted after import (\n") - } else { - fmt.Printf("āš ļø Could not find import section to insert imports\n") - } - } - } - - // Find and insert routes - publishedRoutesMarker := "// ============= PUBLISHED ROUTES ===============================================" - if !strings.Contains(routesContentStr, publishedRoutesMarker) { - return fmt.Errorf("PUBLISHED ROUTES marker not found in routes.go") - } - - insertionPoint := strings.Index(routesContentStr, publishedRoutesMarker) + len(publishedRoutesMarker) - newRoutesContent := routesContentStr[:insertionPoint] + "\n" + strings.Join(allRoutes, "\n") + "\n" + routesContentStr[insertionPoint:] - - err = ioutil.WriteFile(routesFilePath, []byte(newRoutesContent), 0644) - if err != nil { - return fmt.Errorf("failed to write updated routes file: %w", err) - } - - fmt.Printf("āœ… Updated main routes file with %d new route groups for %s\n", len(allRoutes), svc.Name) - return nil -} - -// āœ… TAMBAHAN: Extract existing routes dari file -func extractExistingRoutes(content string) map[string]bool { - existingRoutes := make(map[string]bool) - - // Regex pattern untuk menangkap route definitions - routePattern := regexp.MustCompile(`\s+(\w+Group\.(GET|POST|PUT|DELETE)\("[^"]*",\s*\w+Handler\.\w+\))`) - matches := routePattern.FindAllStringSubmatch(content, -1) - - for _, match := range matches { - if len(match) > 1 { - existingRoutes[match[1]] = true - } - } - - return existingRoutes -} - -// āœ… TAMBAHAN: Helper function untuk method prefix -func getMethodPrefix(method string) string { - switch strings.ToUpper(method) { - case "GET": - return "Get" - case "POST": - return "Create" - case "PUT": - return "Update" - case "DELETE": - return "Delete" - default: - return strings.Title(strings.ToLower(method)) - } -} diff --git a/tools/bpjs/services-config-bpjs.yaml b/tools/bpjs/services-config-bpjs.yaml deleted file mode 100644 index 4dd95e97..00000000 --- a/tools/bpjs/services-config-bpjs.yaml +++ /dev/null @@ -1,160 +0,0 @@ -global: - module_name: "api-service" - output_dir: "internal/handlers" - enable_swagger: true - enable_logging: true - -services: - vclaim: - name: "VClaim" - category: "vclaim" - package: "vclaim" - description: "BPJS VClaim service for eligibility and SEP management" - base_url: "https://apijkn.bpjs-kesehatan.go.id/vclaim-rest" - timeout: 30 - retry_count: 3 - - endpoints: - peserta: - description: "Participant eligibility information" - handler_folder: "peserta" - handler_file: "peserta.go" - handler_name: "Peserta" - functions: - bynokartu: - methods: ["GET"] - path: "/peserta/:nokartu" - get_routes: "/nokartu/:nokartu" - # post_routes: "/Peserta/nokartu/:nokartu" - # put_routes: "/Peserta/nokartu/:nokartu" - # delete_routes: "/Peserta/nokartu/:nokartu" - get_path: "/Peserta/nokartu/:nokartu/tglSEP/:tglSEP" - # post_path: "/peserta" - # put_path: "/peserta/:nokartu" - # delete_path: "/peserta/:nokartu" - model: "PesertaRequest" - response_model: "PesertaResponse" - request_model: "RujukanRequest" - description: "Get participant eligibility information by card number" - summary: "Get Participant Info by No Kartu" - tags: ["Peserta"] - require_auth: true - cache_enabled: true - cache_ttl: 300 - - bynik: - methods: ["GET"] - path: "/peserta/nik/:nik" - get_routes: "/nik/:nik" - # post_routes: "/Peserta/nik/:nik" - # put_routes: "/Peserta/nik/:nik" - # delete_routes: "/Peserta/nik/:nik" - get_path: "/Peserta/nik/:nik/tglSEP/:tglSEP" - # post_path: "/peserta" - # put_path: "/peserta/nik/:nik" - # delete_path: "/peserta/nik/:nik" - model: "PesertaRequest" - response_model: "PesertaResponse" - request_model: "PesertaRequest" - description: "Get participant eligibility information by NIK" - summary: "Get Participant Info by NIK" - tags: ["Peserta"] - require_auth: true - cache_enabled: true - cache_ttl: 300 - - rujukan: - description: "Rujukan management endpoints" - handler_folder: "rujukan" - handler_file: "rujukan.go" - handler_name: "Rujukan" - functions: - rujukan: - methods: ["POST", "PUT", "DELETE"] - path: "/Rujukan" - # get_routes: "/Rujukan/:norujukan" - post_routes: "/Rujukan/:norujukan" - put_routes: "/Rujukan/:norujukan" - delete_routes: "/Rujukan/:norujukan" - # get_path: "/Rujukan/:norujukan" - post_path: "/Rujukan" - put_path: "/Rujukan/:norujukan" - delete_path: "/Rujukan/:norujukan" - model: "RujukanRequest" - response_model: "RujukanResponse" - request_model: "RujukanRequest" - description: "Manage rujukan" - summary: "Rujukan Management" - tags: ["Rujukan"] - require_auth: true - cache_enabled: true - cache_ttl: 180 - rujukanbalik: - methods: ["POST", "PUT", "DELETE"] - path: "/Rujukanbalik" - # get_routes: "/Rujukanbalik/:norujukan" - post_routes: "/Rujukanbalik/:norujukan" - put_routes: "/Rujukanbalik/:norujukan" - delete_routes: "/Rujukanbalik/:norujukan" - # get_path: "/Rujukanbalik/:norujukan" - post_path: "/Rujukanbalik" - put_path: "/Rujukanbalik/:norujukan" - delete_path: "/Rujukanbalik/:norujukan" - model: "RujukanRequest" - response_model: "RujukanResponse" - request_model: "RujukanRequest" - description: "Manage rujukan" - summary: "Rujukan Management" - tags: ["Rujukan"] - require_auth: true - cache_enabled: true - cache_ttl: 180 - - search: - description: "Search for rujukan endpoints" - handler_folder: "rujukan" - handler_file: "search.go" - handler_name: "Search" - functions: - bynorujukan: - methods: ["GET"] - path: "/Rujukan/:norujukan" - get_routes: "/bynorujukan/:norujukan" - # post_routes: "/bynorujukan/:norujukan" - # put_routes: "/bynorujukan/:norujukan" - # delete_routes: "/bynorujukan/:norujukan" - get_path: "/Rujukan/:norujukan" - # post_path: "/Rujukan" - # put_path: "/Rujukan/:norujukan" - # delete_path: "/Rujukan/:norujukan" - model: "RujukanRequest" - response_model: "RujukanResponse" - request_model: "RujukanRequest" - description: "Get rujukan by nomor rujukan" - summary: "Rujukan Management" - tags: ["Rujukan"] - require_auth: true - cache_enabled: true - cache_ttl: 300 - - bynokartu: - methods: ["GET"] - path: "/Rujukan/:nokartu" - get_routes: "/bynokartu/:nokartu" - # post_routes: "/bynokartu/:nokartu" - # put_routes: "/bynokartu/:nokartu" - # delete_routes: "/bynokartu/:nokartu" - get_path: "/Rujukan/:nokartu" - # post_path: "/Rujukan" - # put_path: "/Rujukan/:nokartu" - # delete_path: "/Rujukan/:nokartu" - model: "RujukanRequest" - response_model: "RujukanResponse" - request_model: "RujukanRequest" - description: "Get rujukan by card number" - summary: "Rujukan Management" - tags: ["Rujukan"] - require_auth: true - cache_enabled: true - cache_ttl: 300 - diff --git a/tools/fhir/generate-handler.go b/tools/fhir/generate-handler.go deleted file mode 100644 index ccc2a552..00000000 --- a/tools/fhir/generate-handler.go +++ /dev/null @@ -1,2729 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "time" -) - -// Hl7FhirHandlerData contains template data for HL7 FHIR handler generation -type Hl7FhirHandlerData struct { - Name string - NameLower string - NameUpper string - Category string - CategoryPath string - CategoryParts []string - ModuleName string - HasGet bool - HasPost bool - HasPut bool - HasPatch bool - HasDelete bool - HasSearch bool - HasHistory bool - GetEndpoint string - PostEndpoint string - PutEndpoint string - PatchEndpoint string - DeleteEndpoint string - SearchEndpoint string - HistoryEndpoint string - Timestamp string - DirectoryDepth int - FhirResource string - FhirVersion string // R4, R5 - Profile string // US Core, AU Base, UK Core, etc. -} - -func main() { - if len(os.Args) < 2 { - fmt.Println("Usage: go run generate-hl7fhir-handler.go [level1[/level2[/level3[/level4]]]]/entity [methods] [--version=R4|R5] [--profile=profile]") - fmt.Println("Examples:") - fmt.Println(" go run generate-hl7fhir-handler.go fhir/patient get post put patch --version=R4") - fmt.Println(" go run generate-hl7fhir-handler.go us-core/patient get post search --profile=USCore") - fmt.Println(" go run generate-hl7fhir-handler.go au-base/practitioner get post --profile=AUBase --version=R4") - fmt.Println(" go run generate-hl7fhir-handler.go api/v1/fhir/r4/observation get post put patch delete search history") - fmt.Println(" go run generate-hl7fhir-handler.go medication get") - os.Exit(1) - } - - // Parse entity path (up to 4 levels + entity) - entityPath := os.Args[1] - methods := []string{} - fhirVersion := "R4" // Default to R4 - profile := "" // Default to no specific profile - - // Parse arguments - for i := 2; i < len(os.Args); i++ { - arg := os.Args[i] - if strings.HasPrefix(arg, "--version=") { - fhirVersion = strings.TrimPrefix(arg, "--version=") - } else if strings.HasPrefix(arg, "--profile=") { - profile = strings.TrimPrefix(arg, "--profile=") - } else { - methods = append(methods, arg) - } - } - - if len(methods) == 0 { - // Default methods for FHIR resources - methods = []string{"get", "post", "put", "patch", "search"} - } - - // Parse multi-level category and entity - var categoryParts []string - var entityName string - var category string - - parts := strings.Split(entityPath, "/") - - if len(parts) > 1 && len(parts) <= 5 { // Up to 4 levels + entity - categoryParts = parts[:len(parts)-1] - entityName = parts[len(parts)-1] - category = strings.Join(categoryParts, "/") - } else if len(parts) == 1 { - category = "" - entityName = parts[0] - categoryParts = []string{} - } else { - fmt.Println("āŒ Error: Invalid path format. Use up to 4 levels like 'level1/level2/level3/level4/entity' or just 'entity'") - fmt.Printf("āŒ You provided %d levels, maximum is 4 levels + entity\n", len(parts)-1) - os.Exit(1) - } - - // Format names - entityName = strings.Title(entityName) // PascalCase entity name - entityLower := strings.ToLower(entityName) - entityUpper := strings.ToUpper(entityName) - - // FHIR Resource name (capitalize first letter only) - fhirResource := strings.Title(strings.ToLower(entityName)) - - data := Hl7FhirHandlerData{ - Name: entityName, - NameLower: entityLower, - NameUpper: entityUpper, - Category: category, - CategoryPath: category, - CategoryParts: categoryParts, - ModuleName: "api-service", - DirectoryDepth: len(categoryParts), - FhirResource: fhirResource, - FhirVersion: fhirVersion, - Profile: profile, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - } - - // Set methods and endpoints for FHIR resources - for _, m := range methods { - switch strings.ToLower(m) { - case "get": - data.HasGet = true - data.GetEndpoint = fmt.Sprintf("%s/{id}", fhirResource) - case "post": - data.HasPost = true - data.PostEndpoint = fhirResource - case "put": - data.HasPut = true - data.PutEndpoint = fmt.Sprintf("%s/{id}", fhirResource) - case "patch": - data.HasPatch = true - data.PatchEndpoint = fmt.Sprintf("%s/{id}", fhirResource) - case "delete": - data.HasDelete = true - data.DeleteEndpoint = fmt.Sprintf("%s/{id}", fhirResource) - case "search": - data.HasSearch = true - data.SearchEndpoint = fhirResource - case "history": - data.HasHistory = true - data.HistoryEndpoint = fmt.Sprintf("%s/{id}/_history", fhirResource) - } - } - - // Create directories with multi-level support - var handlerDir, modelDir string - if category != "" { - // Multi-level directory support - handlerDirParts := append([]string{"internal", "handlers", "hl7fhir"}, categoryParts...) - modelDirParts := append([]string{"internal", "models", "hl7fhir"}, categoryParts...) - - handlerDir = filepath.Join(handlerDirParts...) - modelDir = filepath.Join(modelDirParts...) - } else { - // No category: direct internal/handlers/hl7fhir/ - handlerDir = filepath.Join("internal", "handlers", "hl7fhir") - modelDir = filepath.Join("internal", "models", "hl7fhir") - } - - // Create directories - for _, d := range []string{handlerDir, modelDir} { - if err := os.MkdirAll(d, 0755); err != nil { - panic(err) - } - } - - // Generate files - generateOptimizedHl7FhirHandlerFile(data, handlerDir) - generateOptimizedHl7FhirModelFile(data, modelDir) - // updateOptimizedHl7FhirRoutesFile(data) - - fmt.Printf("āœ… Successfully generated optimized HL7 FHIR handler: %s\n", entityName) - if category != "" { - fmt.Printf("šŸ“ Category Path: %s (%d levels deep)\n", category, data.DirectoryDepth) - } - fmt.Printf("šŸ“ FHIR Resource: %s\n", fhirResource) - fmt.Printf("šŸ“ FHIR Version: %s\n", fhirVersion) - if profile != "" { - fmt.Printf("šŸ“ FHIR Profile: %s\n", profile) - } - fmt.Printf("šŸ“ Handler: %s\n", filepath.Join(handlerDir, entityLower+".go")) - fmt.Printf("šŸ“ Model: %s\n", filepath.Join(modelDir, entityLower+".go")) -} - -// ================= OPTIMIZED HANDLER GENERATION ===================== - -func generateOptimizedHl7FhirHandlerFile(data Hl7FhirHandlerData, handlerDir string) { - var modelsImportPath string - if data.Category != "" { - modelsImportPath = data.ModuleName + "/internal/models/hl7fhir/" + data.Category - } else { - modelsImportPath = data.ModuleName + "/internal/models/hl7fhir" - } - - profileInfo := "" - if data.Profile != "" { - profileInfo = fmt.Sprintf("// FHIR Profile: %s\n", data.Profile) - } - - handlerContent := `package handlers - -import ( - "context" - "fmt" - "net/http" - "strconv" - "time" - - "` + data.ModuleName + `/internal/config" - "` + modelsImportPath + `" - services "` + data.ModuleName + `/internal/services/hl7fhir" - "` + data.ModuleName + `/pkg/logger" - "` + data.ModuleName + `/pkg/validator" - - "api-service/pkg/utils" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" - "github.com/go-playground/validator/v10" -) - -// ` + data.Name + `Handler handles ` + data.NameLower + ` HL7 FHIR services with multi-level organization -// Generated for FHIR Resource: ` + data.FhirResource + ` -// FHIR Version: ` + data.FhirVersion + ` -` + profileInfo + `// Path: ` + data.Category + ` -// Directory depth: ` + fmt.Sprintf("%d", data.DirectoryDepth) + ` levels -type ` + data.Name + `Handler struct { - service services.Hl7FhirService - validator *validator.Validate - logger logger.Logger - config *config.Hl7FhirConfig -} - -// HandlerConfig contains configuration for ` + data.Name + `Handler -type ` + data.Name + `HandlerConfig struct { - Hl7FhirConfig *config.Hl7FhirConfig - Logger logger.Logger - Validator *validator.Validate -} - -// New` + data.Name + `Handler creates a new optimized ` + data.Name + `Handler for HL7 FHIR -func New` + data.Name + `Handler(cfg *` + data.Name + `HandlerConfig) *` + data.Name + `Handler { - return &` + data.Name + `Handler{ - service: services.NewHl7FhirService(cfg.Hl7FhirConfig), - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.Hl7FhirConfig, - } -}` - - // Add optimized methods based on flags - if data.HasPost { - handlerContent += generateOptimizedHl7FhirCreateMethod(data) - } - - if data.HasPut { - handlerContent += generateOptimizedHl7FhirUpdateMethod(data) - } - - if data.HasPatch { - handlerContent += generateOptimizedHl7FhirPatchMethod(data) - } - - if data.HasDelete { - handlerContent += generateOptimizedHl7FhirDeleteMethod(data) - } - - if data.HasGet { - handlerContent += generateOptimizedHl7FhirGetMethod(data) - } - - if data.HasSearch { - handlerContent += generateOptimizedHl7FhirSearchMethod(data) - } - - if data.HasHistory { - handlerContent += generateOptimizedHl7FhirHistoryMethod(data) - } - - // Add helper methods - handlerContent += generateHl7FhirHelperMethods(data) - - writeFile(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent) -} - -func generateOptimizedHl7FhirCreateMethod(data Hl7FhirHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = "hl7fhir/" + data.Category + "/" + data.NameLower - tagParts := append([]string{"HL7FHIR"}, data.CategoryParts...) - tagParts = append(tagParts, strings.Title(data.NameLower)) - tagName = strings.Join(tagParts, "-") - } else { - routePath = "hl7fhir/" + data.NameLower - tagName = "HL7FHIR-" + strings.Title(data.NameLower) - } - - profileTag := "" - if data.Profile != "" { - profileTag = fmt.Sprintf(" (%s Profile)", data.Profile) - } - - return ` - -// Create` + data.Name + ` creates a new HL7 FHIR ` + data.FhirResource + ` resource -// @Summary Create a new HL7 FHIR ` + data.FhirResource + ` resource -// @Description Create a new ` + data.FhirResource + ` resource compliant with HL7 FHIR ` + data.FhirVersion + `` + profileTag + ` -// @Description FHIR Resource: ` + data.FhirResource + ` | Version: ` + data.FhirVersion + ` | Path: ` + data.Category + ` -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param Accept header string false "Preferred response format (application/fhir+json, application/json)" -// @Param Content-Type header string true "Request content type (application/fhir+json, application/json)" -// @Param Prefer header string false "Return preference (return=minimal, return=representation, return=OperationOutcome)" -// @Param request body models.` + data.Name + `CreateRequest true "` + data.FhirResource + ` FHIR resource creation request" -// @Success 201 {object} models.` + data.Name + `Response "` + data.FhirResource + ` resource created successfully" -// @Failure 400 {object} models.Hl7FhirOperationOutcome "Bad request - validation error" -// @Failure 422 {object} models.Hl7FhirOperationOutcome "Unprocessable entity - FHIR validation error" -// @Failure 500 {object} models.Hl7FhirOperationOutcome "Internal server error" -// @Router /api/v1/` + routePath + ` [post] -func (h *` + data.Name + `Handler) Create` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - - h.logger.Info("Creating HL7 FHIR ` + data.FhirResource + ` resource", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "fhir_profile": "` + data.Profile + `", - "category_path": "` + data.Category + `", - "directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - "content_type": c.GetHeader("Content-Type"), - "accept": c.GetHeader("Accept"), - "prefer": c.GetHeader("Prefer"), - }) - - var req models.` + data.Name + `CreateRequest - req.RequestID = requestID - req.Timestamp = startTime - - // Enhanced JSON binding with HL7 FHIR validation - if err := c.ShouldBindJSON(&req); err != nil { - h.logger.Error("Failed to bind HL7 FHIR JSON", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "path": "` + routePath + `", - }) - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "structure", - "Invalid FHIR resource structure", err.Error(), requestID) - return - } - - // HL7 FHIR resource validation - if err := req.ValidateHl7Fhir(); err != nil { - h.logger.Error("HL7 FHIR validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "path": "` + routePath + `", - }) - h.sendHl7FhirErrorResponse(c, http.StatusUnprocessableEntity, "business-rule", - "HL7 FHIR resource validation failed", err.Error(), requestID) - return - } - - // Profile-specific validation - if "` + data.Profile + `" != "" { - if err := req.ValidateProfile("` + data.Profile + `"); err != nil { - h.logger.Error("Profile validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "fhir_profile": "` + data.Profile + `", - "path": "` + routePath + `", - }) - h.sendHl7FhirErrorResponse(c, http.StatusUnprocessableEntity, "business-rule", - "Profile validation failed", err.Error(), requestID) - return - } - } - - // Struct validation - if err := h.validator.Struct(&req); err != nil { - h.logger.Error("Struct validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "path": "` + routePath + `", - }) - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "structure", - "Resource structure validation failed", h.formatValidationError(err), requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - var fhirResponse models.Hl7FhirResponse - if err := h.service.CreateResource(ctx, "` + data.PostEndpoint + `", req, &fhirResponse); err != nil { - h.logger.Error("Failed to create HL7 FHIR resource", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "endpoint": "` + data.PostEndpoint + `", - "path": "` + routePath + `", - }) - - statusCode, errorCode := h.categorizeHl7FhirError(err) - h.sendHl7FhirErrorResponse(c, statusCode, errorCode, - "Failed to create ` + data.FhirResource + ` resource", err.Error(), requestID) - return - } - - // Check for HL7 FHIR OperationOutcome - if fhirResponse.ResourceType == "OperationOutcome" { - h.logger.Warn("HL7 FHIR server returned OperationOutcome", map[string]interface{}{ - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "path": "` + routePath + `", - "outcome": fhirResponse, - }) - - h.sendHl7FhirOperationOutcome(c, http.StatusUnprocessableEntity, fhirResponse, requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resource created successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "resource_id": fhirResponse.ID, - "path": "` + routePath + `", - }) - - // Handle Prefer header for response - prefer := c.GetHeader("Prefer") - if strings.Contains(prefer, "return=minimal") { - c.Header("Location", fmt.Sprintf("%s/%s", "` + data.FhirResource + `", fhirResponse.ID)) - c.Header("ETag", fmt.Sprintf("W/\"%s\"", fhirResponse.Meta.VersionId)) - c.Header("Last-Modified", fhirResponse.Meta.LastUpdated) - c.Status(http.StatusCreated) - return - } - - h.sendHl7FhirSuccessResponse(c, http.StatusCreated, "` + data.FhirResource + ` resource created successfully", - fhirResponse, requestID) -}` -} - -func generateOptimizedHl7FhirSearchMethod(data Hl7FhirHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = "hl7fhir/" + data.Category + "/" + data.NameLower - tagParts := append([]string{"HL7FHIR"}, data.CategoryParts...) - tagParts = append(tagParts, strings.Title(data.NameLower)) - tagName = strings.Join(tagParts, "-") - } else { - routePath = "hl7fhir/" + data.NameLower - tagName = "HL7FHIR-" + strings.Title(data.NameLower) - } - - profileTag := "" - if data.Profile != "" { - profileTag = fmt.Sprintf(" (%s Profile)", data.Profile) - } - - return ` - -// Search` + data.Name + ` searches for HL7 FHIR ` + data.FhirResource + ` resources with parameters -// @Summary Search for HL7 FHIR ` + data.FhirResource + ` resources -// @Description Search for ` + data.FhirResource + ` resources compliant with HL7 FHIR ` + data.FhirVersion + `` + profileTag + ` using search parameters -// @Description FHIR Resource: ` + data.FhirResource + ` | Version: ` + data.FhirVersion + ` | Path: ` + data.Category + ` -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param Accept header string false "Preferred response format (application/fhir+json, application/json)" -// @Param _count query integer false "Number of results to return (default: 10, max: 50)" -// @Param _page query integer false "Page number for pagination (default: 1)" -// @Param _include query string false "Include referenced resources" -// @Param _revinclude query string false "Reverse include referenced resources" -// @Param _sort query string false "Sort parameters (field1,-field2)" -// @Param _elements query string false "Elements to return (comma-separated)" -// @Param _summary query string false "Summary mode (true, text, data, count, false)" -// @Param _format query string false "Response format override (json, xml)" -// @Success 200 {object} models.Hl7FhirBundleResponse "` + data.FhirResource + ` resources search results" -// @Failure 400 {object} models.Hl7FhirOperationOutcome "Bad request - invalid search parameters" -// @Failure 500 {object} models.Hl7FhirOperationOutcome "Internal server error" -// @Router /api/v1/` + routePath + ` [get] -func (h *` + data.Name + `Handler) Search` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - - h.logger.Info("Searching HL7 FHIR ` + data.FhirResource + ` resources", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "fhir_profile": "` + data.Profile + `", - "query_params": c.Request.URL.Query(), - "category_path": "` + data.Category + `", - "directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - "accept": c.GetHeader("Accept"), - }) - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - // Parse and validate search parameters - searchParams := make(map[string]string) - for key, values := range c.Request.URL.Query() { - if len(values) > 0 { - searchParams[key] = values[0] - } - } - - // Validate pagination parameters - count := 10 // default - if countStr := searchParams["_count"]; countStr != "" { - if parsedCount, err := strconv.Atoi(countStr); err == nil { - if parsedCount > 0 && parsedCount <= 50 { - count = parsedCount - } - } - } - - page := 1 // default - if pageStr := searchParams["_page"]; pageStr != "" { - if parsedPage, err := strconv.Atoi(pageStr); err == nil && parsedPage > 0 { - page = parsedPage - } - } - - // Add pagination info to search params - searchParams["_count"] = fmt.Sprintf("%d", count) - searchParams["_page"] = fmt.Sprintf("%d", page) - - var fhirBundle models.Hl7FhirBundleResponse - if err := h.service.SearchResources(ctx, "` + data.FhirResource + `", searchParams, &fhirBundle); err != nil { - h.logger.Error("Failed to search HL7 FHIR resources", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "search_params": searchParams, - "path": "` + routePath + `", - }) - - statusCode, errorCode := h.categorizeHl7FhirError(err) - h.sendHl7FhirErrorResponse(c, statusCode, errorCode, - "Failed to search ` + data.FhirResource + ` resources", err.Error(), requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resources search completed", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "total_results": fhirBundle.Total, - "search_params": searchParams, - "path": "` + routePath + `", - }) - - h.sendHl7FhirBundleResponse(c, http.StatusOK, "` + data.FhirResource + ` resources search completed", - fhirBundle, requestID) -}` -} - -func generateHl7FhirHelperMethods(data Hl7FhirHandlerData) string { - return ` - -// Helper methods for ` + data.Name + `Handler with HL7 FHIR support -func (h *` + data.Name + `Handler) sendHl7FhirSuccessResponse(c *gin.Context, statusCode int, message string, data interface{}, requestID string) { - response := models.` + data.Name + `Response{ - Hl7FhirResource: models.Hl7FhirResource{ - ResourceType: "` + data.FhirResource + `", - Meta: models.Hl7FhirMeta{ - LastUpdated: time.Now().Format(time.RFC3339), - VersionId: "1", - FhirVersion: "` + data.FhirVersion + `", - Profile: h.getProfileUrl("` + data.Profile + `"), - }, - }, - BaseResponse: models.BaseResponse{ - Status: "success", - Message: message, - Data: data, - Metadata: &models.ResponseMetadata{ - Timestamp: time.Now(), - Version: "HL7 FHIR ` + data.FhirVersion + `", - RequestID: requestID, - Path: "` + data.Category + `", - Depth: ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - FhirResource: "` + data.FhirResource + `", - FhirVersion: "` + data.FhirVersion + `", - FhirProfile: "` + data.Profile + `", - }, - }, - } - - // Set appropriate FHIR headers - c.Header("Content-Type", "application/fhir+json; fhirVersion=` + data.FhirVersion + `") - c.Header("ETag", fmt.Sprintf("W/\"%s\"", response.Hl7FhirResource.Meta.VersionId)) - c.Header("Last-Modified", response.Hl7FhirResource.Meta.LastUpdated) - - c.JSON(statusCode, response) -} - -func (h *` + data.Name + `Handler) sendHl7FhirErrorResponse(c *gin.Context, statusCode int, errorCode, message, details, requestID string) { - operationOutcome := models.Hl7FhirOperationOutcome{ - ResourceType: "OperationOutcome", - ID: requestID, - Meta: models.Hl7FhirMeta{ - LastUpdated: time.Now().Format(time.RFC3339), - VersionId: "1", - FhirVersion: "` + data.FhirVersion + `", - }, - Issue: []models.Hl7FhirOperationOutcomeIssue{{ - Severity: h.mapHttpStatusToSeverity(statusCode), - Code: errorCode, - Details: models.Hl7FhirCodeableConcept{ - Text: message, - Coding: []models.Hl7FhirCoding{{ - System: "http://terminology.hl7.org/CodeSystem/operation-outcome", - Code: errorCode, - Display: message, - }}, - }, - Diagnostics: details, - Location: []string{fmt.Sprintf("` + data.FhirResource + `")}, - Expression: []string{fmt.Sprintf("` + data.FhirResource + `")}, - }}, - } - - c.Header("Content-Type", "application/fhir+json; fhirVersion=` + data.FhirVersion + `") - c.JSON(statusCode, operationOutcome) -} - -func (h *` + data.Name + `Handler) sendHl7FhirOperationOutcome(c *gin.Context, statusCode int, outcome models.Hl7FhirResponse, requestID string) { - c.Header("Content-Type", "application/fhir+json; fhirVersion=` + data.FhirVersion + `") - c.JSON(statusCode, outcome) -} - -func (h *` + data.Name + `Handler) sendHl7FhirBundleResponse(c *gin.Context, statusCode int, message string, bundle models.Hl7FhirBundleResponse, requestID string) { - bundle.Meta = models.Hl7FhirMeta{ - LastUpdated: time.Now().Format(time.RFC3339), - VersionId: "1", - FhirVersion: "` + data.FhirVersion + `", - } - - // Add self link - bundle.Link = append(bundle.Link, models.Hl7FhirBundleLink{ - Relation: "self", - URL: fmt.Sprintf("%s?%s", "` + data.FhirResource + `", h.buildQueryString(bundle.SearchParams)), - }) - - c.Header("Content-Type", "application/fhir+json; fhirVersion=` + data.FhirVersion + `") - c.JSON(statusCode, bundle) -} - -func (h *` + data.Name + `Handler) formatValidationError(err error) string { - if validationErrors, ok := err.(validator.ValidationErrors); ok { - var messages []string - for _, e := range validationErrors { - switch e.Tag() { - case "required": - messages = append(messages, fmt.Sprintf("Field '%s' is required", e.Field())) - case "min": - messages = append(messages, fmt.Sprintf("Field '%s' must be at least %s characters", e.Field(), e.Param())) - case "max": - messages = append(messages, fmt.Sprintf("Field '%s' must be at most %s characters", e.Field(), e.Param())) - case "oneof": - messages = append(messages, fmt.Sprintf("Field '%s' must be one of: %s", e.Field(), e.Param())) - case "url": - messages = append(messages, fmt.Sprintf("Field '%s' must be a valid URL", e.Field())) - case "uuid": - messages = append(messages, fmt.Sprintf("Field '%s' must be a valid UUID", e.Field())) - case "datetime": - messages = append(messages, fmt.Sprintf("Field '%s' must be a valid datetime", e.Field())) - default: - messages = append(messages, fmt.Sprintf("Field '%s' is invalid", e.Field())) - } - } - return fmt.Sprintf("HL7 FHIR validation failed: %v", messages) - } - return err.Error() -} - -func (h *` + data.Name + `Handler) categorizeHl7FhirError(err error) (int, string) { - if err == nil { - return http.StatusOK, "informational" - } - - errStr := err.Error() - - // HL7 FHIR-specific error categorization - if strings.Contains(errStr, "unauthorized") || strings.Contains(errStr, "invalid token") { - return http.StatusUnauthorized, "security" - } - - if strings.Contains(errStr, "not found") || strings.Contains(errStr, "404") { - return http.StatusNotFound, "not-found" - } - - if strings.Contains(errStr, "validation") || strings.Contains(errStr, "invalid") { - return http.StatusUnprocessableEntity, "business-rule" - } - - if strings.Contains(errStr, "duplicate") || strings.Contains(errStr, "conflict") { - return http.StatusConflict, "duplicate" - } - - if h.isTimeoutError(err) { - return http.StatusRequestTimeout, "timeout" - } - - if h.isNetworkError(err) { - return http.StatusBadGateway, "transient" - } - - return http.StatusInternalServerError, "exception" -} - -func (h *` + data.Name + `Handler) mapHttpStatusToSeverity(statusCode int) string { - switch { - case statusCode >= 500: - return "fatal" - case statusCode >= 400: - return "error" - case statusCode >= 300: - return "warning" - default: - return "information" - } -} - -func (h *` + data.Name + `Handler) getProfileUrl(profile string) []string { - profileUrls := map[string]string{ - "USCore": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-` + strings.ToLower(data.FhirResource) + `", - "AUBase": "http://hl7.org.au/fhir/base/StructureDefinition/au-` + strings.ToLower(data.FhirResource) + `", - "UKCore": "https://fhir.hl7.org.uk/StructureDefinition/UKCore-` + data.FhirResource + `", - "CABaseline": "http://hl7.org/fhir/ca/baseline/StructureDefinition/profile-` + strings.ToLower(data.FhirResource) + `", - "InternationalPatientSummary": "http://hl7.org/fhir/uv/ips/StructureDefinition/` + data.FhirResource + `-uv-ips", - } - - if profile != "" { - if url, exists := profileUrls[profile]; exists { - return []string{url} - } - } - return []string{} -} - -func (h *` + data.Name + `Handler) buildQueryString(params map[string]string) string { - var parts []string - for key, value := range params { - parts = append(parts, fmt.Sprintf("%s=%s", key, value)) - } - return strings.Join(parts, "&") -} - -func (h *` + data.Name + `Handler) isTimeoutError(err error) bool { - return err != nil && (strings.Contains(err.Error(), "timeout") || - strings.Contains(err.Error(), "deadline exceeded")) -} - -func (h *` + data.Name + `Handler) isNetworkError(err error) bool { - return err != nil && (strings.Contains(err.Error(), "connection refused") || - strings.Contains(err.Error(), "no such host") || - strings.Contains(err.Error(), "network unreachable")) -}` -} - -func generateOptimizedHl7FhirModelFile(data Hl7FhirHandlerData, modelDir string) { - profileInfo := "" - if data.Profile != "" { - profileInfo = fmt.Sprintf("// FHIR Profile: %s\n", data.Profile) - } - - modelContent := `package models - -import ( - "encoding/json" - "fmt" - "strings" - "time" - "regexp" -) - -// ` + data.Name + ` HL7 FHIR ` + data.FhirVersion + ` Models with Enhanced Multi-Level Support -// Generated at: ` + data.Timestamp + ` -// FHIR Resource: ` + data.FhirResource + ` -// FHIR Version: ` + data.FhirVersion + ` -` + profileInfo + `// Category Path: ` + data.Category + ` -// Directory Depth: ` + fmt.Sprintf("%d", data.DirectoryDepth) + ` levels - -// Base HL7 FHIR structures -type Hl7FhirResource struct { - ResourceType string ` + "`json:\"resourceType\"`" + ` - ID string ` + "`json:\"id,omitempty\"`" + ` - Meta Hl7FhirMeta ` + "`json:\"meta,omitempty\"`" + ` - ImplicitRules string ` + "`json:\"implicitRules,omitempty\"`" + ` - Language string ` + "`json:\"language,omitempty\"`" + ` -} - -type Hl7FhirMeta struct { - VersionId string ` + "`json:\"versionId,omitempty\"`" + ` - LastUpdated string ` + "`json:\"lastUpdated,omitempty\"`" + ` - Source string ` + "`json:\"source,omitempty\"`" + ` - Profile []string ` + "`json:\"profile,omitempty\"`" + ` - Security []Hl7FhirCoding ` + "`json:\"security,omitempty\"`" + ` - Tag []Hl7FhirCoding ` + "`json:\"tag,omitempty\"`" + ` - FhirVersion string ` + "`json:\"fhirVersion,omitempty\"`" + ` -} - -type Hl7FhirCoding struct { - System string ` + "`json:\"system,omitempty\"`" + ` - Version string ` + "`json:\"version,omitempty\"`" + ` - Code string ` + "`json:\"code,omitempty\"`" + ` - Display string ` + "`json:\"display,omitempty\"`" + ` - UserSelected *bool ` + "`json:\"userSelected,omitempty\"`" + ` -} - -type Hl7FhirCodeableConcept struct { - Coding []Hl7FhirCoding ` + "`json:\"coding,omitempty\"`" + ` - Text string ` + "`json:\"text,omitempty\"`" + ` -} - -type Hl7FhirReference struct { - Reference string ` + "`json:\"reference,omitempty\"`" + ` - Type string ` + "`json:\"type,omitempty\"`" + ` - Identifier Hl7FhirIdentifier ` + "`json:\"identifier,omitempty\"`" + ` - Display string ` + "`json:\"display,omitempty\"`" + ` -} - -type Hl7FhirIdentifier struct { - Use string ` + "`json:\"use,omitempty\" validate:\"omitempty,oneof=usual official temp secondary old\"`" + ` - Type Hl7FhirCodeableConcept ` + "`json:\"type,omitempty\"`" + ` - System string ` + "`json:\"system,omitempty\" validate:\"omitempty,url\"`" + ` - Value string ` + "`json:\"value,omitempty\"`" + ` - Period Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + ` - Assigner Hl7FhirReference ` + "`json:\"assigner,omitempty\"`" + ` -} - -type Hl7FhirPeriod struct { - Start string ` + "`json:\"start,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + ` - End string ` + "`json:\"end,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + ` -} - -// HL7 FHIR OperationOutcome for error handling -type Hl7FhirOperationOutcome struct { - ResourceType string ` + "`json:\"resourceType\"`" + ` - ID string ` + "`json:\"id,omitempty\"`" + ` - Meta Hl7FhirMeta ` + "`json:\"meta,omitempty\"`" + ` - Issue []Hl7FhirOperationOutcomeIssue ` + "`json:\"issue\"`" + ` -} - -type Hl7FhirOperationOutcomeIssue struct { - Severity string ` + "`json:\"severity\" validate:\"required,oneof=fatal error warning information\"`" + ` - Code string ` + "`json:\"code\" validate:\"required\"`" + ` - Details Hl7FhirCodeableConcept ` + "`json:\"details,omitempty\"`" + ` - Diagnostics string ` + "`json:\"diagnostics,omitempty\"`" + ` - Location []string ` + "`json:\"location,omitempty\"`" + ` - Expression []string ` + "`json:\"expression,omitempty\"`" + ` -} - -// HL7 FHIR Bundle for search results -type Hl7FhirBundleResponse struct { - ResourceType string ` + "`json:\"resourceType\"`" + ` - ID string ` + "`json:\"id,omitempty\"`" + ` - Meta Hl7FhirMeta ` + "`json:\"meta,omitempty\"`" + ` - Type string ` + "`json:\"type\" validate:\"required,oneof=document message transaction transaction-response batch batch-response history searchset collection\"`" + ` - Timestamp string ` + "`json:\"timestamp,omitempty\"`" + ` - Total int ` + "`json:\"total,omitempty\"`" + ` - Link []Hl7FhirBundleLink ` + "`json:\"link,omitempty\"`" + ` - Entry []Hl7FhirBundleEntry ` + "`json:\"entry,omitempty\"`" + ` - Signature Hl7FhirSignature ` + "`json:\"signature,omitempty\"`" + ` - SearchParams map[string]string ` + "`json:\"-\"`" + ` // For internal use -} - -type Hl7FhirBundleLink struct { - Relation string ` + "`json:\"relation\" validate:\"required\"`" + ` - URL string ` + "`json:\"url\" validate:\"required,url\"`" + ` -} - -type Hl7FhirBundleEntry struct { - Link []Hl7FhirBundleLink ` + "`json:\"link,omitempty\"`" + ` - FullURL string ` + "`json:\"fullUrl,omitempty\" validate:\"omitempty,url\"`" + ` - Resource interface{} ` + "`json:\"resource,omitempty\"`" + ` - Search Hl7FhirBundleEntrySearch ` + "`json:\"search,omitempty\"`" + ` - Request Hl7FhirBundleEntryRequest ` + "`json:\"request,omitempty\"`" + ` - Response Hl7FhirBundleEntryResponse` + "`json:\"response,omitempty\"`" + ` -} - -type Hl7FhirBundleEntrySearch struct { - Mode string ` + "`json:\"mode,omitempty\" validate:\"omitempty,oneof=match include outcome\"`" + ` - Score float64 ` + "`json:\"score,omitempty\"`" + ` -} - -type Hl7FhirBundleEntryRequest struct { - Method string ` + "`json:\"method\" validate:\"required,oneof=GET HEAD POST PUT DELETE PATCH\"`" + ` - URL string ` + "`json:\"url\" validate:\"required\"`" + ` - IfNoneMatch string ` + "`json:\"ifNoneMatch,omitempty\"`" + ` - IfModifiedSince string ` + "`json:\"ifModifiedSince,omitempty\"`" + ` - IfMatch string ` + "`json:\"ifMatch,omitempty\"`" + ` - IfNoneExist string ` + "`json:\"ifNoneExist,omitempty\"`" + ` -} - -type Hl7FhirBundleEntryResponse struct { - Status string ` + "`json:\"status\" validate:\"required\"`" + ` - Location string ` + "`json:\"location,omitempty\" validate:\"omitempty,url\"`" + ` - ETag string ` + "`json:\"etag,omitempty\"`" + ` - LastModified string ` + "`json:\"lastModified,omitempty\"`" + ` - Outcome interface{} ` + "`json:\"outcome,omitempty\"`" + ` -} - -type Hl7FhirSignature struct { - Type []Hl7FhirCoding ` + "`json:\"type\" validate:\"required,min=1\"`" + ` - When string ` + "`json:\"when\" validate:\"required,datetime=2006-01-02T15:04:05Z07:00\"`" + ` - Who Hl7FhirReference` + "`json:\"who\" validate:\"required\"`" + ` - OnBehalfOf Hl7FhirReference` + "`json:\"onBehalfOf,omitempty\"`" + ` - TargetFormat string ` + "`json:\"targetFormat,omitempty\"`" + ` - SigFormat string ` + "`json:\"sigFormat,omitempty\"`" + ` - Data string ` + "`json:\"data,omitempty\"`" + ` -} - -// Base request/response structures with HL7 FHIR integration -type BaseRequest struct { - RequestID string ` + "`json:\"request_id,omitempty\"`" + ` - Timestamp time.Time ` + "`json:\"timestamp,omitempty\"`" + ` -} - -type BaseResponse struct { - Status string ` + "`json:\"status\"`" + ` - Message string ` + "`json:\"message\"`" + ` - Data interface{} ` + "`json:\"data,omitempty\"`" + ` - Error *ErrorResponse ` + "`json:\"error,omitempty\"`" + ` - Metadata *ResponseMetadata ` + "`json:\"metadata,omitempty\"`" + ` -} - -type ErrorResponse struct { - Code string ` + "`json:\"code\"`" + ` - Message string ` + "`json:\"message\"`" + ` - Details string ` + "`json:\"details,omitempty\"`" + ` -} - -type ResponseMetadata struct { - Timestamp time.Time ` + "`json:\"timestamp\"`" + ` - Version string ` + "`json:\"version\"`" + ` - RequestID string ` + "`json:\"request_id,omitempty\"`" + ` - Path string ` + "`json:\"path,omitempty\"`" + ` - Depth int ` + "`json:\"depth,omitempty\"`" + ` - FhirResource string ` + "`json:\"fhir_resource,omitempty\"`" + ` - FhirVersion string ` + "`json:\"fhir_version,omitempty\"`" + ` - FhirProfile string ` + "`json:\"fhir_profile,omitempty\"`" + ` -} - -// ` + data.Name + ` Response Structure with HL7 FHIR integration -type ` + data.Name + `Response struct { - Hl7FhirResource - BaseResponse -} - -// Generic HL7 FHIR Response -type Hl7FhirResponse struct { - ResourceType string ` + "`json:\"resourceType\"`" + ` - ID string ` + "`json:\"id,omitempty\"`" + ` - Meta Hl7FhirMeta ` + "`json:\"meta,omitempty\"`" + ` - Content map[string]interface{} ` + "`json:\"-\"`" + ` // For dynamic content -}` - - // Add CRUD request structures based on methods - if data.HasPost { - modelContent += generateHl7FhirCreateRequestModel(data) - } - - if data.HasPut { - modelContent += generateHl7FhirUpdateRequestModel(data) - } - - if data.HasPatch { - modelContent += generateHl7FhirPatchRequestModel(data) - } - - // Add common HL7 FHIR data types and validation helpers - modelContent += generateHl7FhirCommonDataTypes(data) - modelContent += generateHl7FhirValidationHelpers(data) - - writeFile(filepath.Join(modelDir, data.NameLower+".go"), modelContent) -} - -func generateHl7FhirCreateRequestModel(data Hl7FhirHandlerData) string { - profileValidation := "" - if data.Profile != "" { - profileValidation = ` - // Validate profile-specific constraints - if err := r.ValidateProfile("` + data.Profile + `"); err != nil { - return err - }` - } - - return ` - -// ` + data.Name + ` CREATE Request Structure with HL7 FHIR ` + data.FhirVersion + ` Validation -type ` + data.Name + `CreateRequest struct { - BaseRequest - ResourceType string ` + "`json:\"resourceType\" binding:\"required\" validate:\"required,eq=` + data.FhirResource + `\"`" + ` - - // Core HL7 FHIR ` + data.FhirResource + ` fields - customize based on specific resource - // FHIR Version: ` + data.FhirVersion + ` - // Path: ` + data.Category + ` - Meta Hl7FhirMeta ` + "`json:\"meta,omitempty\"`" + ` - ImplicitRules string ` + "`json:\"implicitRules,omitempty\" validate:\"omitempty,url\"`" + ` - Language string ` + "`json:\"language,omitempty\" validate:\"omitempty,len=2\"`" + ` - Text Hl7FhirNarrative ` + "`json:\"text,omitempty\"`" + ` - Contained []interface{} ` + "`json:\"contained,omitempty\"`" + ` - Extension []Hl7FhirExtension ` + "`json:\"extension,omitempty\"`" + ` - ModifierExtension []Hl7FhirExtension ` + "`json:\"modifierExtension,omitempty\"`" + ` - - // Resource-specific fields (customize per resource type) - Identifier []Hl7FhirIdentifier ` + "`json:\"identifier,omitempty\" validate:\"dive\"`" + ` - Active *bool ` + "`json:\"active,omitempty\"`" + ` - Name []Hl7FhirHumanName ` + "`json:\"name,omitempty\" validate:\"dive\"`" + ` - Telecom []Hl7FhirContactPoint ` + "`json:\"telecom,omitempty\" validate:\"dive\"`" + ` - Gender string ` + "`json:\"gender,omitempty\" validate:\"omitempty,oneof=male female other unknown\"`" + ` - BirthDate string ` + "`json:\"birthDate,omitempty\" validate:\"omitempty,datetime=2006-01-02\"`" + ` - Address []Hl7FhirAddress ` + "`json:\"address,omitempty\" validate:\"dive\"`" + ` - MaritalStatus Hl7FhirCodeableConcept ` + "`json:\"maritalStatus,omitempty\"`" + ` - Contact []Hl7FhirPatientContact ` + "`json:\"contact,omitempty\"`" + ` - Communication []Hl7FhirPatientCommunication ` + "`json:\"communication,omitempty\"`" + ` - GeneralPractitioner []Hl7FhirReference ` + "`json:\"generalPractitioner,omitempty\"`" + ` - ManagingOrganization Hl7FhirReference ` + "`json:\"managingOrganization,omitempty\"`" + ` -} - -// Additional HL7 FHIR data types for ` + data.FhirResource + ` -type Hl7FhirNarrative struct { - Status string ` + "`json:\"status\" validate:\"required,oneof=generated extensions additional empty\"`" + ` - Div string ` + "`json:\"div\" validate:\"required\"`" + ` -} - -type Hl7FhirExtension struct { - URL string ` + "`json:\"url\" validate:\"required,url\"`" + ` - Value interface{} ` + "`json:\"value,omitempty\"`" + ` // Can be various types -} - -type Hl7FhirHumanName struct { - Use string ` + "`json:\"use,omitempty\" validate:\"omitempty,oneof=usual official temp nickname anonymous old maiden\"`" + ` - Text string ` + "`json:\"text,omitempty\"`" + ` - Family string ` + "`json:\"family,omitempty\"`" + ` - Given []string ` + "`json:\"given,omitempty\"`" + ` - Prefix []string ` + "`json:\"prefix,omitempty\"`" + ` - Suffix []string ` + "`json:\"suffix,omitempty\"`" + ` - Period Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + ` -} - -type Hl7FhirContactPoint struct { - System string ` + "`json:\"system,omitempty\" validate:\"omitempty,oneof=phone fax email pager url sms other\"`" + ` - Value string ` + "`json:\"value,omitempty\"`" + ` - Use string ` + "`json:\"use,omitempty\" validate:\"omitempty,oneof=home work temp old mobile\"`" + ` - Rank int ` + "`json:\"rank,omitempty\" validate:\"omitempty,min=1\"`" + ` - Period Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + ` -} - -type Hl7FhirAddress struct { - Use string ` + "`json:\"use,omitempty\" validate:\"omitempty,oneof=home work temp old billing\"`" + ` - Type string ` + "`json:\"type,omitempty\" validate:\"omitempty,oneof=postal physical both\"`" + ` - Text string ` + "`json:\"text,omitempty\"`" + ` - Line []string ` + "`json:\"line,omitempty\"`" + ` - City string ` + "`json:\"city,omitempty\"`" + ` - District string ` + "`json:\"district,omitempty\"`" + ` - State string ` + "`json:\"state,omitempty\"`" + ` - PostalCode string ` + "`json:\"postalCode,omitempty\"`" + ` - Country string ` + "`json:\"country,omitempty\" validate:\"omitempty,len=2\"`" + ` - Period Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + ` -} - -type Hl7FhirPatientContact struct { - Relationship []Hl7FhirCodeableConcept ` + "`json:\"relationship,omitempty\"`" + ` - Name Hl7FhirHumanName ` + "`json:\"name,omitempty\"`" + ` - Telecom []Hl7FhirContactPoint ` + "`json:\"telecom,omitempty\"`" + ` - Address Hl7FhirAddress ` + "`json:\"address,omitempty\"`" + ` - Gender string ` + "`json:\"gender,omitempty\" validate:\"omitempty,oneof=male female other unknown\"`" + ` - Organization Hl7FhirReference ` + "`json:\"organization,omitempty\"`" + ` - Period Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + ` -} - -type Hl7FhirPatientCommunication struct { - Language Hl7FhirCodeableConcept ` + "`json:\"language\" validate:\"required\"`" + ` - Preferred *bool ` + "`json:\"preferred,omitempty\"`" + ` -} - -// ValidateHl7Fhir validates the ` + data.Name + `CreateRequest with HL7 FHIR ` + data.FhirVersion + ` business rules -func (r *` + data.Name + `CreateRequest) ValidateHl7Fhir() error { - if r.ResourceType != "` + data.FhirResource + `" { - return fmt.Errorf("invalid resourceType: expected ` + data.FhirResource + `, got %s", r.ResourceType) - } - - // Validate narrative if present - if r.Text.Status != "" { - if r.Text.Div == "" { - return fmt.Errorf("narrative div is required when status is provided") - } - if !isValidXhtml(r.Text.Div) { - return fmt.Errorf("narrative div must be valid XHTML") - } - } - - // Validate identifiers - for i, identifier := range r.Identifier { - if identifier.System != "" && identifier.Value == "" { - return fmt.Errorf("identifier[%d]: value is required when system is provided", i) - } - } - - // Validate extensions - for i, ext := range r.Extension { - if ext.URL == "" { - return fmt.Errorf("extension[%d]: url is required", i) - } - }` + profileValidation + ` - - // Path: ` + data.Category + ` - return nil -} - -// ValidateProfile validates profile-specific constraints -func (r *` + data.Name + `CreateRequest) ValidateProfile(profile string) error { - switch profile { - case "USCore": - return r.validateUSCoreProfile() - case "AUBase": - return r.validateAUBaseProfile() - case "UKCore": - return r.validateUKCoreProfile() - case "CABaseline": - return r.validateCABaselineProfile() - case "InternationalPatientSummary": - return r.validateIPSProfile() - default: - return nil // No specific profile validation - } -} - -func (r *` + data.Name + `CreateRequest) validateUSCoreProfile() error { - // US Core Patient profile requirements - if len(r.Identifier) == 0 { - return fmt.Errorf("US Core Patient requires at least one identifier") - } - - if len(r.Name) == 0 { - return fmt.Errorf("US Core Patient requires at least one name") - } - - if r.Gender == "" { - return fmt.Errorf("US Core Patient requires gender") - } - - return nil -} - -func (r *` + data.Name + `CreateRequest) validateAUBaseProfile() error { - // AU Base Patient profile requirements - // Add Australian-specific validation rules - return nil -} - -func (r *` + data.Name + `CreateRequest) validateUKCoreProfile() error { - // UK Core Patient profile requirements - // Add UK-specific validation rules - return nil -} - -func (r *` + data.Name + `CreateRequest) validateCABaselineProfile() error { - // CA Baseline Patient profile requirements - // Add Canadian-specific validation rules - return nil -} - -func (r *` + data.Name + `CreateRequest) validateIPSProfile() error { - // International Patient Summary profile requirements - // Add IPS-specific validation rules - return nil -} - -// ToJSON converts struct to JSON string -func (r *` + data.Name + `CreateRequest) ToJSON() (string, error) { - data, err := json.Marshal(r) - return string(data), err -}` -} - -func generateHl7FhirValidationHelpers(data Hl7FhirHandlerData) string { - return ` - -// HL7 FHIR validation helper functions -func isValidXhtml(xhtml string) bool { - // Basic XHTML validation - in production, use proper XML parser - return strings.HasPrefix(xhtml, "") -} - -func isValidHl7FhirDate(date string) bool { - // FHIR date format: YYYY, YYYY-MM, or YYYY-MM-DD - patterns := []string{ - "^[0-9]{4}$", // Year only - "^[0-9]{4}-[0-9]{2}$", // Year-Month - "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", // Full date - } - - for _, pattern := range patterns { - matched, _ := regexp.MatchString(pattern, date) - if matched { - return true - } - } - return false -} - -func isValidHl7FhirDateTime(datetime string) bool { - // FHIR datetime format with timezone - pattern := "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$" - matched, _ := regexp.MatchString(pattern, datetime) - return matched -} - -func isValidHl7FhirID(id string) bool { - // FHIR ID: 1-64 characters, alphanumeric, dash, dot - if len(id) < 1 || len(id) > 64 { - return false - } - - pattern := "^[A-Za-z0-9\\-\\.]{1,64}$" - matched, _ := regexp.MatchString(pattern, id) - return matched -} - -func isValidHl7FhirUri(uri string) bool { - // Basic URI validation - in production, use proper URI parser - pattern := "^[a-zA-Z][a-zA-Z0-9+.-]*:" - matched, _ := regexp.MatchString(pattern, uri) - return matched -} - -func isValidHl7FhirCode(code string) bool { - // FHIR code: no whitespace, control chars - pattern := "^[^\\s\\x00-\\x1F\\x7F]+$" - matched, _ := regexp.MatchString(pattern, code) - return matched && len(code) > 0 -} - -func isValidLanguageCode(lang string) bool { - // BCP 47 language code (simplified validation) - if len(lang) < 2 || len(lang) > 8 { - return false - } - - pattern := "^[a-z]{2,3}(-[A-Z]{2})?(-[a-z]{2,8})*$" - matched, _ := regexp.MatchString(pattern, lang) - return matched -} - -// GetHl7FhirPathInfo returns information about the multi-level HL7 FHIR path -func GetHl7FhirPathInfo() map[string]interface{} { - return map[string]interface{}{ - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "fhir_profile": "` + data.Profile + `", - "path": "` + data.Category + `", - "depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - "parts": []string{` + `"` + strings.Join(data.CategoryParts, `", "`) + `"` + `}, - "base_url": "http://hl7.org/fhir/` + strings.ToLower(data.FhirVersion) + `", - "profile_url": getStandardProfileUrl("` + data.Profile + `", "` + data.FhirResource + `"), - "specification": "http://hl7.org/fhir/` + strings.ToLower(data.FhirVersion) + `/` + strings.ToLower(data.FhirResource) + `.html", - } -} - -func getStandardProfileUrl(profile, resource string) string { - profileUrls := map[string]string{ - "USCore": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-" + strings.ToLower(resource), - "AUBase": "http://hl7.org.au/fhir/base/StructureDefinition/au-" + strings.ToLower(resource), - "UKCore": "https://fhir.hl7.org.uk/StructureDefinition/UKCore-" + resource, - "CABaseline": "http://hl7.org/fhir/ca/baseline/StructureDefinition/profile-" + strings.ToLower(resource), - "InternationalPatientSummary": "http://hl7.org/fhir/uv/ips/StructureDefinition/" + resource + "-uv-ips", - } - - if url, exists := profileUrls[profile]; exists { - return url - } - return "" -} - -// Common terminology system URLs -const ( - SNOMED_CT_URL = "http://snomed.info/sct" - LOINC_URL = "http://loinc.org" - ICD10_URL = "http://hl7.org/fhir/sid/icd-10" - ICD11_URL = "http://id.who.int/icd/release/11/mms" - RXNORM_URL = "http://www.nlm.nih.gov/research/umls/rxnorm" - CPT_URL = "http://www.ama-assn.org/go/cpt" - UCUM_URL = "http://unitsofmeasure.org" - HL7_V3_URL = "http://terminology.hl7.org/CodeSystem/v3-" - HL7_V2_URL = "http://terminology.hl7.org/CodeSystem/v2-" -)` -} -func generateOptimizedHl7FhirUpdateMethod(data Hl7FhirHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = "hl7fhir/" + data.Category + "/" + data.NameLower - tagParts := append([]string{"HL7FHIR"}, data.CategoryParts...) - tagParts = append(tagParts, strings.Title(data.NameLower)) - tagName = strings.Join(tagParts, "-") - } else { - routePath = "hl7fhir/" + data.NameLower - tagName = "HL7FHIR-" + strings.Title(data.NameLower) - } - - profileTag := "" - if data.Profile != "" { - profileTag = fmt.Sprintf(" (%s Profile)", data.Profile) - } - - return ` - -// Update` + data.Name + ` updates (replaces) an existing HL7 FHIR ` + data.FhirResource + ` resource -// @Summary Update (replace) an existing HL7 FHIR ` + data.FhirResource + ` resource -// @Description Update an existing ` + data.FhirResource + ` resource compliant with HL7 FHIR ` + data.FhirVersion + `` + profileTag + ` -// @Description FHIR Resource: ` + data.FhirResource + ` | Version: ` + data.FhirVersion + ` | Path: ` + data.Category + ` -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param Accept header string false "Preferred response format (application/fhir+json, application/json)" -// @Param Content-Type header string true "Request content type (application/fhir+json, application/json)" -// @Param If-Match header string false "Version-aware update (ETag)" -// @Param Prefer header string false "Return preference (return=minimal, return=representation, return=OperationOutcome)" -// @Param id path string true "` + data.FhirResource + ` resource ID" -// @Param request body models.` + data.Name + `UpdateRequest true "` + data.FhirResource + ` FHIR resource update request" -// @Success 200 {object} models.` + data.Name + `Response "` + data.FhirResource + ` resource updated successfully" -// @Failure 400 {object} models.Hl7FhirOperationOutcome "Bad request - validation error" -// @Failure 404 {object} models.Hl7FhirOperationOutcome "Resource not found" -// @Failure 409 {object} models.Hl7FhirOperationOutcome "Version conflict" -// @Failure 412 {object} models.Hl7FhirOperationOutcome "Precondition failed" -// @Failure 422 {object} models.Hl7FhirOperationOutcome "Unprocessable entity - FHIR validation error" -// @Failure 500 {object} models.Hl7FhirOperationOutcome "Internal server error" -// @Router /api/v1/` + routePath + `/{id} [put] -func (h *` + data.Name + `Handler) Update` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - id := c.Param("id") - - h.logger.Info("Updating HL7 FHIR ` + data.FhirResource + ` resource", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "fhir_profile": "` + data.Profile + `", - "resource_id": id, - "category_path": "` + data.Category + `", - "directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - "content_type": c.GetHeader("Content-Type"), - "if_match": c.GetHeader("If-Match"), - "prefer": c.GetHeader("Prefer"), - }) - - if id == "" { - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "required", - "Resource ID is required", "", requestID) - return - } - - if !isValidHl7FhirID(id) { - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "invalid", - "Invalid resource ID format", "", requestID) - return - } - - var req models.` + data.Name + `UpdateRequest - req.RequestID = requestID - req.Timestamp = startTime - req.ID = id - - if err := c.ShouldBindJSON(&req); err != nil { - h.logger.Error("Failed to bind HL7 FHIR JSON for update", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "structure", - "Invalid FHIR resource structure", err.Error(), requestID) - return - } - - // Validate resource ID matches URL parameter - if req.ID != "" && req.ID != id { - h.logger.Error("Resource ID mismatch", map[string]interface{}{ - "url_id": id, - "resource_id": req.ID, - "request_id": requestID, - }) - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "invalid", - "Resource ID in body must match URL parameter", "", requestID) - return - } - req.ID = id // Ensure ID is set - - if err := req.ValidateHl7Fhir(); err != nil { - h.logger.Error("HL7 FHIR update validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - h.sendHl7FhirErrorResponse(c, http.StatusUnprocessableEntity, "business-rule", - "HL7 FHIR resource validation failed", err.Error(), requestID) - return - } - - // Profile-specific validation - if "` + data.Profile + `" != "" { - if err := req.ValidateProfile("` + data.Profile + `"); err != nil { - h.logger.Error("Profile validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "fhir_profile": "` + data.Profile + `", - "resource_id": id, - "path": "` + routePath + `", - }) - h.sendHl7FhirErrorResponse(c, http.StatusUnprocessableEntity, "business-rule", - "Profile validation failed", err.Error(), requestID) - return - } - } - - if err := h.validator.Struct(&req); err != nil { - h.logger.Error("Struct validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "structure", - "Resource structure validation failed", h.formatValidationError(err), requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - endpoint := fmt.Sprintf("` + strings.Replace(data.PutEndpoint, "{id}", "%s", 1) + `", id) - var fhirResponse models.Hl7FhirResponse - - // Handle If-Match header for version-aware updates - ifMatch := c.GetHeader("If-Match") - if ifMatch != "" { - req.Meta.VersionId = parseETag(ifMatch) - } - - if err := h.service.UpdateResource(ctx, endpoint, req, &fhirResponse); err != nil { - h.logger.Error("Failed to update HL7 FHIR resource", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "resource_id": id, - "endpoint": endpoint, - "path": "` + routePath + `", - }) - - statusCode, errorCode := h.categorizeHl7FhirError(err) - h.sendHl7FhirErrorResponse(c, statusCode, errorCode, - "Failed to update ` + data.FhirResource + ` resource", err.Error(), requestID) - return - } - - if fhirResponse.ResourceType == "OperationOutcome" { - h.logger.Warn("HL7 FHIR server returned OperationOutcome for update", map[string]interface{}{ - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - "outcome": fhirResponse, - }) - - h.sendHl7FhirOperationOutcome(c, http.StatusUnprocessableEntity, fhirResponse, requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resource updated successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "resource_id": id, - "path": "` + routePath + `", - }) - - // Handle Prefer header for response - prefer := c.GetHeader("Prefer") - if strings.Contains(prefer, "return=minimal") { - c.Header("Location", fmt.Sprintf("%s/%s", "` + data.FhirResource + `", fhirResponse.ID)) - c.Header("ETag", fmt.Sprintf("W/\"%s\"", fhirResponse.Meta.VersionId)) - c.Header("Last-Modified", fhirResponse.Meta.LastUpdated) - c.Status(http.StatusOK) - return - } - - h.sendHl7FhirSuccessResponse(c, http.StatusOK, "` + data.FhirResource + ` resource updated successfully", - fhirResponse, requestID) -}` -} - -func generateOptimizedHl7FhirPatchMethod(data Hl7FhirHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = "hl7fhir/" + data.Category + "/" + data.NameLower - tagParts := append([]string{"HL7FHIR"}, data.CategoryParts...) - tagParts = append(tagParts, strings.Title(data.NameLower)) - tagName = strings.Join(tagParts, "-") - } else { - routePath = "hl7fhir/" + data.NameLower - tagName = "HL7FHIR-" + strings.Title(data.NameLower) - } - - profileTag := "" - if data.Profile != "" { - profileTag = fmt.Sprintf(" (%s Profile)", data.Profile) - } - - return ` - -// Patch` + data.Name + ` partially updates an existing HL7 FHIR ` + data.FhirResource + ` resource -// @Summary Patch (partial update) an existing HL7 FHIR ` + data.FhirResource + ` resource -// @Description Partially update an existing ` + data.FhirResource + ` resource compliant with HL7 FHIR ` + data.FhirVersion + `` + profileTag + ` using JSON Patch -// @Description FHIR Resource: ` + data.FhirResource + ` | Version: ` + data.FhirVersion + ` | Path: ` + data.Category + ` -// @Tags ` + tagName + ` -// @Accept json-patch+json -// @Produce json -// @Param Accept header string false "Preferred response format (application/fhir+json, application/json)" -// @Param Content-Type header string true "Request content type (application/json-patch+json)" -// @Param If-Match header string false "Version-aware patch (ETag)" -// @Param Prefer header string false "Return preference (return=minimal, return=representation, return=OperationOutcome)" -// @Param id path string true "` + data.FhirResource + ` resource ID" -// @Param request body models.` + data.Name + `PatchRequest true "` + data.FhirResource + ` FHIR resource patch request" -// @Success 200 {object} models.` + data.Name + `Response "` + data.FhirResource + ` resource patched successfully" -// @Failure 400 {object} models.Hl7FhirOperationOutcome "Bad request - validation error" -// @Failure 404 {object} models.Hl7FhirOperationOutcome "Resource not found" -// @Failure 409 {object} models.Hl7FhirOperationOutcome "Version conflict" -// @Failure 412 {object} models.Hl7FhirOperationOutcome "Precondition failed" -// @Failure 422 {object} models.Hl7FhirOperationOutcome "Unprocessable entity - FHIR validation error" -// @Failure 500 {object} models.Hl7FhirOperationOutcome "Internal server error" -// @Router /api/v1/` + routePath + `/{id} [patch] -func (h *` + data.Name + `Handler) Patch` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - id := c.Param("id") - - h.logger.Info("Patching HL7 FHIR ` + data.FhirResource + ` resource", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "fhir_profile": "` + data.Profile + `", - "resource_id": id, - "category_path": "` + data.Category + `", - "directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - "content_type": c.GetHeader("Content-Type"), - "if_match": c.GetHeader("If-Match"), - "prefer": c.GetHeader("Prefer"), - }) - - if id == "" { - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "required", - "Resource ID is required", "", requestID) - return - } - - if !isValidHl7FhirID(id) { - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "invalid", - "Invalid resource ID format", "", requestID) - return - } - - // Validate Content-Type for patch operations - contentType := c.GetHeader("Content-Type") - if !strings.Contains(contentType, "application/json-patch+json") && - !strings.Contains(contentType, "application/json") { - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "not-supported", - "Unsupported content type for patch operation", "", requestID) - return - } - - var req models.` + data.Name + `PatchRequest - req.RequestID = requestID - req.Timestamp = startTime - req.ID = id - - if err := c.ShouldBindJSON(&req); err != nil { - h.logger.Error("Failed to bind HL7 FHIR patch JSON", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "structure", - "Invalid FHIR patch structure", err.Error(), requestID) - return - } - - if err := req.ValidateHl7FhirPatch(); err != nil { - h.logger.Error("HL7 FHIR patch validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - h.sendHl7FhirErrorResponse(c, http.StatusUnprocessableEntity, "business-rule", - "HL7 FHIR patch validation failed", err.Error(), requestID) - return - } - - if err := h.validator.Struct(&req); err != nil { - h.logger.Error("Struct validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "structure", - "Patch structure validation failed", h.formatValidationError(err), requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - endpoint := fmt.Sprintf("` + strings.Replace(data.PatchEndpoint, "{id}", "%s", 1) + `", id) - var fhirResponse models.Hl7FhirResponse - - // Handle If-Match header for version-aware patches - ifMatch := c.GetHeader("If-Match") - if ifMatch != "" { - req.VersionId = parseETag(ifMatch) - } - - if err := h.service.PatchResource(ctx, endpoint, req, &fhirResponse); err != nil { - h.logger.Error("Failed to patch HL7 FHIR resource", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "resource_id": id, - "endpoint": endpoint, - "path": "` + routePath + `", - }) - - statusCode, errorCode := h.categorizeHl7FhirError(err) - h.sendHl7FhirErrorResponse(c, statusCode, errorCode, - "Failed to patch ` + data.FhirResource + ` resource", err.Error(), requestID) - return - } - - if fhirResponse.ResourceType == "OperationOutcome" { - h.logger.Warn("HL7 FHIR server returned OperationOutcome for patch", map[string]interface{}{ - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - "outcome": fhirResponse, - }) - - h.sendHl7FhirOperationOutcome(c, http.StatusUnprocessableEntity, fhirResponse, requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resource patched successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "resource_id": id, - "patch_ops": len(req.Patches), - "path": "` + routePath + `", - }) - - // Handle Prefer header for response - prefer := c.GetHeader("Prefer") - if strings.Contains(prefer, "return=minimal") { - c.Header("Location", fmt.Sprintf("%s/%s", "` + data.FhirResource + `", fhirResponse.ID)) - c.Header("ETag", fmt.Sprintf("W/\"%s\"", fhirResponse.Meta.VersionId)) - c.Header("Last-Modified", fhirResponse.Meta.LastUpdated) - c.Status(http.StatusOK) - return - } - - h.sendHl7FhirSuccessResponse(c, http.StatusOK, "` + data.FhirResource + ` resource patched successfully", - fhirResponse, requestID) -}` -} - -func generateOptimizedHl7FhirDeleteMethod(data Hl7FhirHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = "hl7fhir/" + data.Category + "/" + data.NameLower - tagParts := append([]string{"HL7FHIR"}, data.CategoryParts...) - tagParts = append(tagParts, strings.Title(data.NameLower)) - tagName = strings.Join(tagParts, "-") - } else { - routePath = "hl7fhir/" + data.NameLower - tagName = "HL7FHIR-" + strings.Title(data.NameLower) - } - - profileTag := "" - if data.Profile != "" { - profileTag = fmt.Sprintf(" (%s Profile)", data.Profile) - } - - return ` - -// Delete` + data.Name + ` deletes an existing HL7 FHIR ` + data.FhirResource + ` resource -// @Summary Delete an existing HL7 FHIR ` + data.FhirResource + ` resource -// @Description Delete an existing ` + data.FhirResource + ` resource compliant with HL7 FHIR ` + data.FhirVersion + `` + profileTag + ` -// @Description FHIR Resource: ` + data.FhirResource + ` | Version: ` + data.FhirVersion + ` | Path: ` + data.Category + ` -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param Accept header string false "Preferred response format (application/fhir+json, application/json)" -// @Param If-Match header string false "Version-aware delete (ETag)" -// @Param id path string true "` + data.FhirResource + ` resource ID" -// @Success 204 "` + data.FhirResource + ` resource deleted successfully" -// @Success 200 {object} models.Hl7FhirOperationOutcome "Delete operation outcome" -// @Failure 400 {object} models.Hl7FhirOperationOutcome "Bad request - invalid ID" -// @Failure 404 {object} models.Hl7FhirOperationOutcome "Resource not found" -// @Failure 409 {object} models.Hl7FhirOperationOutcome "Version conflict" -// @Failure 412 {object} models.Hl7FhirOperationOutcome "Precondition failed" -// @Failure 500 {object} models.Hl7FhirOperationOutcome "Internal server error" -// @Router /api/v1/` + routePath + `/{id} [delete] -func (h *` + data.Name + `Handler) Delete` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - id := c.Param("id") - - h.logger.Info("Deleting HL7 FHIR ` + data.FhirResource + ` resource", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "fhir_profile": "` + data.Profile + `", - "resource_id": id, - "category_path": "` + data.Category + `", - "directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - "if_match": c.GetHeader("If-Match"), - }) - - if id == "" { - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "required", - "Resource ID is required", "", requestID) - return - } - - if !isValidHl7FhirID(id) { - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "invalid", - "Invalid resource ID format", "", requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - endpoint := fmt.Sprintf("` + strings.Replace(data.DeleteEndpoint, "{id}", "%s", 1) + `", id) - - // Handle If-Match header for version-aware deletes - ifMatch := c.GetHeader("If-Match") - versionId := "" - if ifMatch != "" { - versionId = parseETag(ifMatch) - } - - deleteResult, err := h.service.DeleteResource(ctx, endpoint, versionId) - if err != nil { - h.logger.Error("Failed to delete HL7 FHIR resource", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "resource_id": id, - "endpoint": endpoint, - "path": "` + routePath + `", - }) - - statusCode, errorCode := h.categorizeHl7FhirError(err) - h.sendHl7FhirErrorResponse(c, statusCode, errorCode, - "Failed to delete ` + data.FhirResource + ` resource", err.Error(), requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resource deleted successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "resource_id": id, - "delete_result": deleteResult, - "path": "` + routePath + `", - }) - - // FHIR delete can return either 204 (No Content) or 200 with OperationOutcome - if deleteResult.OperationOutcome != nil { - // Server returned an OperationOutcome (successful delete with info) - c.Header("Content-Type", "application/fhir+json; fhirVersion=` + data.FhirVersion + `") - c.JSON(http.StatusOK, deleteResult.OperationOutcome) - return - } - - // Standard successful delete - no content - c.Status(http.StatusNoContent) -}` -} - -func generateOptimizedHl7FhirGetMethod(data Hl7FhirHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = "hl7fhir/" + data.Category + "/" + data.NameLower - tagParts := append([]string{"HL7FHIR"}, data.CategoryParts...) - tagParts = append(tagParts, strings.Title(data.NameLower)) - tagName = strings.Join(tagParts, "-") - } else { - routePath = "hl7fhir/" + data.NameLower - tagName = "HL7FHIR-" + strings.Title(data.NameLower) - } - - profileTag := "" - if data.Profile != "" { - profileTag = fmt.Sprintf(" (%s Profile)", data.Profile) - } - - return ` - -// Get` + data.Name + ` retrieves a specific HL7 FHIR ` + data.FhirResource + ` resource by ID -// @Summary Get a specific HL7 FHIR ` + data.FhirResource + ` resource by ID -// @Description Retrieve a specific ` + data.FhirResource + ` resource compliant with HL7 FHIR ` + data.FhirVersion + `` + profileTag + ` -// @Description FHIR Resource: ` + data.FhirResource + ` | Version: ` + data.FhirVersion + ` | Path: ` + data.Category + ` -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param Accept header string false "Preferred response format (application/fhir+json, application/json)" -// @Param If-None-Match header string false "Conditional read (ETag)" -// @Param If-Modified-Since header string false "Conditional read (last modified)" -// @Param _summary query string false "Summary mode (true, text, data, count, false)" -// @Param _elements query string false "Elements to return (comma-separated)" -// @Param _format query string false "Response format override (json, xml)" -// @Param id path string true "` + data.FhirResource + ` resource ID" -// @Success 200 {object} models.` + data.Name + `Response "` + data.FhirResource + ` resource retrieved successfully" -// @Success 304 "Not modified (conditional read)" -// @Failure 400 {object} models.Hl7FhirOperationOutcome "Bad request - invalid ID" -// @Failure 404 {object} models.Hl7FhirOperationOutcome "Resource not found" -// @Failure 410 {object} models.Hl7FhirOperationOutcome "Resource deleted" -// @Failure 500 {object} models.Hl7FhirOperationOutcome "Internal server error" -// @Router /api/v1/` + routePath + `/{id} [get] -func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - id := c.Param("id") - - h.logger.Info("Getting HL7 FHIR ` + data.FhirResource + ` resource", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "fhir_profile": "` + data.Profile + `", - "resource_id": id, - "category_path": "` + data.Category + `", - "directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - "accept": c.GetHeader("Accept"), - "if_none_match": c.GetHeader("If-None-Match"), - "if_modified_since": c.GetHeader("If-Modified-Since"), - "summary": c.Query("_summary"), - "elements": c.Query("_elements"), - "format": c.Query("_format"), - }) - - if id == "" { - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "required", - "Resource ID is required", "", requestID) - return - } - - if !isValidHl7FhirID(id) { - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "invalid", - "Invalid resource ID format", "", requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - endpoint := fmt.Sprintf("` + strings.Replace(data.GetEndpoint, "{id}", "%s", 1) + `", id) - - // Build query parameters for read options - queryParams := make(map[string]string) - if summary := c.Query("_summary"); summary != "" { - if summary == "true" || summary == "text" || summary == "data" || summary == "count" || summary == "false" { - queryParams["_summary"] = summary - } - } - - if elements := c.Query("_elements"); elements != "" { - queryParams["_elements"] = elements - } - - if format := c.Query("_format"); format != "" { - queryParams["_format"] = format - } - - // Handle conditional read headers - ifNoneMatch := c.GetHeader("If-None-Match") - ifModifiedSince := c.GetHeader("If-Modified-Since") - - var fhirResponse models.Hl7FhirResponse - readResult, err := h.service.GetResource(ctx, endpoint, queryParams, ifNoneMatch, ifModifiedSince, &fhirResponse) - if err != nil { - h.logger.Error("Failed to get HL7 FHIR resource", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "resource_id": id, - "endpoint": endpoint, - "path": "` + routePath + `", - }) - - statusCode, errorCode := h.categorizeHl7FhirError(err) - h.sendHl7FhirErrorResponse(c, statusCode, errorCode, - "Failed to get ` + data.FhirResource + ` resource", err.Error(), requestID) - return - } - - // Handle conditional read responses - if readResult.NotModified { - h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resource not modified", map[string]interface{}{ - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - c.Status(http.StatusNotModified) - return - } - - if fhirResponse.ResourceType == "OperationOutcome" { - h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resource not found", map[string]interface{}{ - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - "outcome": fhirResponse, - }) - - h.sendHl7FhirOperationOutcome(c, http.StatusNotFound, fhirResponse, requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resource retrieved successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "resource_id": id, - "version_id": fhirResponse.Meta.VersionId, - "path": "` + routePath + `", - }) - - // Set response headers - if fhirResponse.Meta.VersionId != "" { - c.Header("ETag", fmt.Sprintf("W/\"%s\"", fhirResponse.Meta.VersionId)) - } - - if fhirResponse.Meta.LastUpdated != "" { - c.Header("Last-Modified", fhirResponse.Meta.LastUpdated) - } - - h.sendHl7FhirSuccessResponse(c, http.StatusOK, "` + data.FhirResource + ` resource retrieved successfully", - fhirResponse, requestID) -}` -} - -func generateOptimizedHl7FhirHistoryMethod(data Hl7FhirHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = "hl7fhir/" + data.Category + "/" + data.NameLower - tagParts := append([]string{"HL7FHIR"}, data.CategoryParts...) - tagParts = append(tagParts, strings.Title(data.NameLower)) - tagName = strings.Join(tagParts, "-") - } else { - routePath = "hl7fhir/" + data.NameLower - tagName = "HL7FHIR-" + strings.Title(data.NameLower) - } - - profileTag := "" - if data.Profile != "" { - profileTag = fmt.Sprintf(" (%s Profile)", data.Profile) - } - - return ` - -// History` + data.Name + ` retrieves the version history for a specific HL7 FHIR ` + data.FhirResource + ` resource -// @Summary Get version history for a specific HL7 FHIR ` + data.FhirResource + ` resource -// @Description Retrieve the version history for a specific ` + data.FhirResource + ` resource compliant with HL7 FHIR ` + data.FhirVersion + `` + profileTag + ` -// @Description FHIR Resource: ` + data.FhirResource + ` | Version: ` + data.FhirVersion + ` | Path: ` + data.Category + ` -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param Accept header string false "Preferred response format (application/fhir+json, application/json)" -// @Param _count query integer false "Number of history entries to return (default: 10, max: 50)" -// @Param _since query string false "Only return versions since this instant (ISO 8601)" -// @Param _at query string false "Only return versions that were current at this time (ISO 8601)" -// @Param _page query integer false "Page number for pagination (default: 1)" -// @Param _sort query string false "Sort order for history (_lastUpdated, -_lastUpdated)" -// @Param _format query string false "Response format override (json, xml)" -// @Param id path string true "` + data.FhirResource + ` resource ID" -// @Success 200 {object} models.Hl7FhirBundleResponse "` + data.FhirResource + ` resource history retrieved successfully" -// @Failure 400 {object} models.Hl7FhirOperationOutcome "Bad request - invalid parameters" -// @Failure 404 {object} models.Hl7FhirOperationOutcome "Resource not found" -// @Failure 500 {object} models.Hl7FhirOperationOutcome "Internal server error" -// @Router /api/v1/` + routePath + `/{id}/_history [get] -func (h *` + data.Name + `Handler) History` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - id := c.Param("id") - - h.logger.Info("Getting HL7 FHIR ` + data.FhirResource + ` resource history", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "fhir_profile": "` + data.Profile + `", - "resource_id": id, - "category_path": "` + data.Category + `", - "directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - "query_params": c.Request.URL.Query(), - "accept": c.GetHeader("Accept"), - }) - - if id == "" { - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "required", - "Resource ID is required", "", requestID) - return - } - - if !isValidHl7FhirID(id) { - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "invalid", - "Invalid resource ID format", "", requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - // Parse and validate history parameters - historyParams := make(map[string]string) - - // Count parameter (default: 10, max: 50) - count := 10 - if countStr := c.Query("_count"); countStr != "" { - if parsedCount, err := strconv.Atoi(countStr); err == nil { - if parsedCount > 0 && parsedCount <= 50 { - count = parsedCount - } - } - } - historyParams["_count"] = fmt.Sprintf("%d", count) - - // Page parameter - page := 1 - if pageStr := c.Query("_page"); pageStr != "" { - if parsedPage, err := strconv.Atoi(pageStr); err == nil && parsedPage > 0 { - page = parsedPage - } - } - historyParams["_page"] = fmt.Sprintf("%d", page) - - // Since parameter (ISO 8601 instant) - if since := c.Query("_since"); since != "" { - if isValidHl7FhirDateTime(since) { - historyParams["_since"] = since - } else { - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "invalid", - "Invalid _since parameter format (must be ISO 8601)", "", requestID) - return - } - } - - // At parameter (ISO 8601 instant) - if at := c.Query("_at"); at != "" { - if isValidHl7FhirDateTime(at) { - historyParams["_at"] = at - } else { - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "invalid", - "Invalid _at parameter format (must be ISO 8601)", "", requestID) - return - } - } - - // Sort parameter - if sort := c.Query("_sort"); sort != "" { - if sort == "_lastUpdated" || sort == "-_lastUpdated" { - historyParams["_sort"] = sort - } else { - h.sendHl7FhirErrorResponse(c, http.StatusBadRequest, "not-supported", - "Only _lastUpdated and -_lastUpdated sort options are supported", "", requestID) - return - } - } - - // Format parameter - if format := c.Query("_format"); format != "" { - historyParams["_format"] = format - } - - endpoint := fmt.Sprintf("` + strings.Replace(data.HistoryEndpoint, "{id}", "%s", 1) + `", id) - var fhirBundle models.Hl7FhirBundleResponse - - if err := h.service.GetHistory(ctx, endpoint, historyParams, &fhirBundle); err != nil { - h.logger.Error("Failed to get HL7 FHIR resource history", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "resource_id": id, - "endpoint": endpoint, - "history_params": historyParams, - "path": "` + routePath + `", - }) - - statusCode, errorCode := h.categorizeHl7FhirError(err) - h.sendHl7FhirErrorResponse(c, statusCode, errorCode, - "Failed to get ` + data.FhirResource + ` resource history", err.Error(), requestID) - return - } - - // Ensure bundle type is history - fhirBundle.Type = "history" - fhirBundle.ID = requestID - fhirBundle.Timestamp = startTime.Format(time.RFC3339) - - duration := time.Since(startTime) - h.logger.Info("HL7 FHIR ` + data.FhirResource + ` resource history retrieved successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "fhir_resource": "` + data.FhirResource + `", - "fhir_version": "` + data.FhirVersion + `", - "resource_id": id, - "total_versions": fhirBundle.Total, - "returned_count": len(fhirBundle.Entry), - "history_params": historyParams, - "path": "` + routePath + `", - }) - - h.sendHl7FhirBundleResponse(c, http.StatusOK, "` + data.FhirResource + ` resource history retrieved successfully", - fhirBundle, requestID) -}` -} - -func generateHl7FhirUpdateRequestModel(data Hl7FhirHandlerData) string { - return ` - -// ` + data.Name + ` UPDATE Request Structure with HL7 FHIR ` + data.FhirVersion + ` Validation -type ` + data.Name + `UpdateRequest struct { - BaseRequest - // Resource fields (same as create request but ID is required) - ResourceType string ` + "`json:\"resourceType\" binding:\"required\" validate:\"required,eq=` + data.FhirResource + `\"`" + ` - ID string ` + "`json:\"id\" binding:\"required\" validate:\"required\"`" + ` - - // Include all the same fields as CreateRequest - Meta Hl7FhirMeta ` + "`json:\"meta,omitempty\"`" + ` - ImplicitRules string ` + "`json:\"implicitRules,omitempty\" validate:\"omitempty,url\"`" + ` - Language string ` + "`json:\"language,omitempty\" validate:\"omitempty,len=2\"`" + ` - Text Hl7FhirNarrative ` + "`json:\"text,omitempty\"`" + ` - Contained []interface{} ` + "`json:\"contained,omitempty\"`" + ` - Extension []Hl7FhirExtension ` + "`json:\"extension,omitempty\"`" + ` - ModifierExtension []Hl7FhirExtension ` + "`json:\"modifierExtension,omitempty\"`" + ` - - // Resource-specific fields - Identifier []Hl7FhirIdentifier ` + "`json:\"identifier,omitempty\" validate:\"dive\"`" + ` - Active *bool ` + "`json:\"active,omitempty\"`" + ` - Name []Hl7FhirHumanName ` + "`json:\"name,omitempty\" validate:\"dive\"`" + ` - Telecom []Hl7FhirContactPoint ` + "`json:\"telecom,omitempty\" validate:\"dive\"`" + ` - Gender string ` + "`json:\"gender,omitempty\" validate:\"omitempty,oneof=male female other unknown\"`" + ` - BirthDate string ` + "`json:\"birthDate,omitempty\" validate:\"omitempty,datetime=2006-01-02\"`" + ` - Address []Hl7FhirAddress ` + "`json:\"address,omitempty\" validate:\"dive\"`" + ` - MaritalStatus Hl7FhirCodeableConcept ` + "`json:\"maritalStatus,omitempty\"`" + ` - Contact []Hl7FhirPatientContact ` + "`json:\"contact,omitempty\"`" + ` - Communication []Hl7FhirPatientCommunication ` + "`json:\"communication,omitempty\"`" + ` - GeneralPractitioner []Hl7FhirReference ` + "`json:\"generalPractitioner,omitempty\"`" + ` - ManagingOrganization Hl7FhirReference ` + "`json:\"managingOrganization,omitempty\"`" + ` -} - -// ValidateHl7Fhir validates the ` + data.Name + `UpdateRequest -func (r *` + data.Name + `UpdateRequest) ValidateHl7Fhir() error { - if r.ResourceType != "` + data.FhirResource + `" { - return fmt.Errorf("invalid resourceType: expected ` + data.FhirResource + `, got %s", r.ResourceType) - } - - if r.ID == "" { - return fmt.Errorf("resource ID is required for update operation") - } - - if !isValidHl7FhirID(r.ID) { - return fmt.Errorf("invalid resource ID format") - } - - // Same validation as create request - return nil -} - -// ValidateProfile validates profile-specific constraints for update -func (r *` + data.Name + `UpdateRequest) ValidateProfile(profile string) error { - // Same profile validation as create request - return nil -} - -// ToJSON converts struct to JSON string -func (r *` + data.Name + `UpdateRequest) ToJSON() (string, error) { - data, err := json.Marshal(r) - return string(data), err -}` -} - -func generateHl7FhirPatchRequestModel(data Hl7FhirHandlerData) string { - return ` - -// ` + data.Name + ` PATCH Request Structure with HL7 FHIR ` + data.FhirVersion + ` JSON Patch -type ` + data.Name + `PatchRequest struct { - BaseRequest - ID string ` + "`json:\"id\" binding:\"required\" validate:\"required\"`" + ` - VersionId string ` + "`json:\"versionId,omitempty\"`" + ` - Patches []Hl7FhirJsonPatch ` + "`json:\"patches\" binding:\"required\" validate:\"required,dive\"`" + ` -} - -type Hl7FhirJsonPatch struct { - Op string ` + "`json:\"op\" binding:\"required\" validate:\"required,oneof=add remove replace move copy test\"`" + ` - Path string ` + "`json:\"path\" binding:\"required\" validate:\"required\"`" + ` - Value interface{} ` + "`json:\"value,omitempty\"`" + ` - From string ` + "`json:\"from,omitempty\"`" + ` -} - -// ValidateHl7FhirPatch validates the ` + data.Name + `PatchRequest -func (r *` + data.Name + `PatchRequest) ValidateHl7FhirPatch() error { - if r.ID == "" { - return fmt.Errorf("resource ID is required for patch operation") - } - - if !isValidHl7FhirID(r.ID) { - return fmt.Errorf("invalid resource ID format") - } - - if len(r.Patches) == 0 { - return fmt.Errorf("at least one patch operation is required") - } - - // Validate each patch operation - for i, patch := range r.Patches { - if patch.Path == "" { - return fmt.Errorf("patch[%d]: path is required", i) - } - - // Validate path format (JSON Pointer) - if !strings.HasPrefix(patch.Path, "/") { - return fmt.Errorf("patch[%d]: path must be a valid JSON Pointer", i) - } - - if patch.Op == "move" || patch.Op == "copy" { - if patch.From == "" { - return fmt.Errorf("patch[%d]: from is required for %s operation", i, patch.Op) - } - if !strings.HasPrefix(patch.From, "/") { - return fmt.Errorf("patch[%d]: from must be a valid JSON Pointer", i) - } - } - - if patch.Op != "remove" && patch.Op != "test" && patch.Value == nil { - return fmt.Errorf("patch[%d]: value is required for %s operation", i, patch.Op) - } - } - - return nil -} - -// ToJSON converts struct to JSON string -func (r *` + data.Name + `PatchRequest) ToJSON() (string, error) { - data, err := json.Marshal(r) - return string(data), err -}` -} - -func generateHl7FhirCommonDataTypes(data Hl7FhirHandlerData) string { - return ` - -// ============================================================================ -// Common HL7 FHIR Data Types for ` + data.FhirVersion + ` -// These are reusable data types used across multiple FHIR resources -// Generated for: ` + data.FhirResource + ` | Path: ` + data.Category + ` -// ============================================================================ - -// Narrative - Human-readable summary of the resource -type Hl7FhirNarrative struct { - Status string ` + "`json:\"status\" validate:\"required,oneof=generated extensions additional empty\"`" + ` - Div string ` + "`json:\"div\" validate:\"required\"`" + ` // Limited XHTML content -} - -// Extension - Additional content defined by implementations -type Hl7FhirExtension struct { - URL string ` + "`json:\"url\" validate:\"required,url\"`" + ` - ValueBase64Binary *string ` + "`json:\"valueBase64Binary,omitempty\"`" + ` - ValueBoolean *bool ` + "`json:\"valueBoolean,omitempty\"`" + ` - ValueCanonical *string ` + "`json:\"valueCanonical,omitempty\" validate:\"omitempty,url\"`" + ` - ValueCode *string ` + "`json:\"valueCode,omitempty\"`" + ` - ValueDate *string ` + "`json:\"valueDate,omitempty\" validate:\"omitempty,datetime=2006-01-02\"`" + ` - ValueDateTime *string ` + "`json:\"valueDateTime,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + ` - ValueDecimal *float64 ` + "`json:\"valueDecimal,omitempty\"`" + ` - ValueId *string ` + "`json:\"valueId,omitempty\"`" + ` - ValueInstant *string ` + "`json:\"valueInstant,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + ` - ValueInteger *int ` + "`json:\"valueInteger,omitempty\"`" + ` - ValueMarkdown *string ` + "`json:\"valueMarkdown,omitempty\"`" + ` - ValueOid *string ` + "`json:\"valueOid,omitempty\"`" + ` - ValuePositiveInt *int ` + "`json:\"valuePositiveInt,omitempty\" validate:\"omitempty,min=1\"`" + ` - ValueString *string ` + "`json:\"valueString,omitempty\"`" + ` - ValueTime *string ` + "`json:\"valueTime,omitempty\" validate:\"omitempty,datetime=15:04:05\"`" + ` - ValueUnsignedInt *int ` + "`json:\"valueUnsignedInt,omitempty\" validate:\"omitempty,min=0\"`" + ` - ValueUri *string ` + "`json:\"valueUri,omitempty\" validate:\"omitempty,url\"`" + ` - ValueUrl *string ` + "`json:\"valueUrl,omitempty\" validate:\"omitempty,url\"`" + ` - ValueUuid *string ` + "`json:\"valueUuid,omitempty\" validate:\"omitempty,uuid\"`" + ` - - // Complex data types - ValueAddress *Hl7FhirAddress ` + "`json:\"valueAddress,omitempty\"`" + ` - ValueAge *Hl7FhirAge ` + "`json:\"valueAge,omitempty\"`" + ` - ValueAnnotation *Hl7FhirAnnotation ` + "`json:\"valueAnnotation,omitempty\"`" + ` - ValueAttachment *Hl7FhirAttachment ` + "`json:\"valueAttachment,omitempty\"`" + ` - ValueCodeableConcept *Hl7FhirCodeableConcept ` + "`json:\"valueCodeableConcept,omitempty\"`" + ` - ValueCoding *Hl7FhirCoding ` + "`json:\"valueCoding,omitempty\"`" + ` - ValueContactPoint *Hl7FhirContactPoint ` + "`json:\"valueContactPoint,omitempty\"`" + ` - ValueCount *Hl7FhirCount ` + "`json:\"valueCount,omitempty\"`" + ` - ValueDistance *Hl7FhirDistance ` + "`json:\"valueDistance,omitempty\"`" + ` - ValueDuration *Hl7FhirDuration ` + "`json:\"valueDuration,omitempty\"`" + ` - ValueHumanName *Hl7FhirHumanName ` + "`json:\"valueHumanName,omitempty\"`" + ` - ValueIdentifier *Hl7FhirIdentifier ` + "`json:\"valueIdentifier,omitempty\"`" + ` - ValueMoney *Hl7FhirMoney ` + "`json:\"valueMoney,omitempty\"`" + ` - ValuePeriod *Hl7FhirPeriod ` + "`json:\"valuePeriod,omitempty\"`" + ` - ValueQuantity *Hl7FhirQuantity ` + "`json:\"valueQuantity,omitempty\"`" + ` - ValueRange *Hl7FhirRange ` + "`json:\"valueRange,omitempty\"`" + ` - ValueRatio *Hl7FhirRatio ` + "`json:\"valueRatio,omitempty\"`" + ` - ValueReference *Hl7FhirReference ` + "`json:\"valueReference,omitempty\"`" + ` - ValueSampledData *Hl7FhirSampledData ` + "`json:\"valueSampledData,omitempty\"`" + ` - ValueSignature *Hl7FhirSignature ` + "`json:\"valueSignature,omitempty\"`" + ` - ValueTiming *Hl7FhirTiming ` + "`json:\"valueTiming,omitempty\"`" + ` - ValueContactDetail *Hl7FhirContactDetail ` + "`json:\"valueContactDetail,omitempty\"`" + ` - ValueContributor *Hl7FhirContributor ` + "`json:\"valueContributor,omitempty\"`" + ` - ValueDataRequirement *Hl7FhirDataRequirement ` + "`json:\"valueDataRequirement,omitempty\"`" + ` - ValueExpression *Hl7FhirExpression ` + "`json:\"valueExpression,omitempty\"`" + ` - ValueParameterDefinition *Hl7FhirParameterDefinition ` + "`json:\"valueParameterDefinition,omitempty\"`" + ` - ValueRelatedArtifact *Hl7FhirRelatedArtifact ` + "`json:\"valueRelatedArtifact,omitempty\"`" + ` - ValueTriggerDefinition *Hl7FhirTriggerDefinition ` + "`json:\"valueTriggerDefinition,omitempty\"`" + ` - ValueUsageContext *Hl7FhirUsageContext ` + "`json:\"valueUsageContext,omitempty\"`" + ` - ValueDosage *Hl7FhirDosage ` + "`json:\"valueDosage,omitempty\"`" + ` -} - -// Coding - Reference to a code defined by a terminology system -type Hl7FhirCoding struct { - System string ` + "`json:\"system,omitempty\" validate:\"omitempty,url\"`" + ` - Version string ` + "`json:\"version,omitempty\"`" + ` - Code string ` + "`json:\"code,omitempty\"`" + ` - Display string ` + "`json:\"display,omitempty\"`" + ` - UserSelected *bool ` + "`json:\"userSelected,omitempty\"`" + ` -} - -// CodeableConcept - Concept defined by Coding(s) and/or text -type Hl7FhirCodeableConcept struct { - Coding []Hl7FhirCoding ` + "`json:\"coding,omitempty\" validate:\"dive\"`" + ` - Text string ` + "`json:\"text,omitempty\"`" + ` -} - -// Reference - Reference to another resource -type Hl7FhirReference struct { - Reference string ` + "`json:\"reference,omitempty\"`" + ` - Type string ` + "`json:\"type,omitempty\"`" + ` - Identifier Hl7FhirIdentifier ` + "`json:\"identifier,omitempty\"`" + ` - Display string ` + "`json:\"display,omitempty\"`" + ` -} - -// Identifier - Identifier for a resource -type Hl7FhirIdentifier struct { - Use string ` + "`json:\"use,omitempty\" validate:\"omitempty,oneof=usual official temp secondary old\"`" + ` - Type Hl7FhirCodeableConcept ` + "`json:\"type,omitempty\"`" + ` - System string ` + "`json:\"system,omitempty\" validate:\"omitempty,url\"`" + ` - Value string ` + "`json:\"value,omitempty\"`" + ` - Period Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + ` - Assigner Hl7FhirReference ` + "`json:\"assigner,omitempty\"`" + ` -} - -// Period - Time range defined by start and end date/time -type Hl7FhirPeriod struct { - Start string ` + "`json:\"start,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + ` - End string ` + "`json:\"end,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + ` -} - -// Quantity - Measured amount (or range of measured amounts) -type Hl7FhirQuantity struct { - Value float64 ` + "`json:\"value,omitempty\"`" + ` - Comparator string ` + "`json:\"comparator,omitempty\" validate:\"omitempty,oneof=< <= >= > ad\"`" + ` - Unit string ` + "`json:\"unit,omitempty\"`" + ` - System string ` + "`json:\"system,omitempty\" validate:\"omitempty,url\"`" + ` - Code string ` + "`json:\"code,omitempty\"`" + ` -} - -// Range - Set of values bounded by low and high -type Hl7FhirRange struct { - Low Hl7FhirQuantity ` + "`json:\"low,omitempty\"`" + ` - High Hl7FhirQuantity ` + "`json:\"high,omitempty\"`" + ` -} - -// Ratio - Relationship between two quantities -type Hl7FhirRatio struct { - Numerator Hl7FhirQuantity ` + "`json:\"numerator,omitempty\"`" + ` - Denominator Hl7FhirQuantity ` + "`json:\"denominator,omitempty\"`" + ` -} - -// SampledData - Data that comes from observations -type Hl7FhirSampledData struct { - Origin Hl7FhirQuantity ` + "`json:\"origin\" validate:\"required\"`" + ` - Period float64 ` + "`json:\"period\" validate:\"required,min=0\"`" + ` - Factor float64 ` + "`json:\"factor,omitempty\"`" + ` - LowerLimit float64 ` + "`json:\"lowerLimit,omitempty\"`" + ` - UpperLimit float64 ` + "`json:\"upperLimit,omitempty\"`" + ` - Dimensions int ` + "`json:\"dimensions\" validate:\"required,min=1\"`" + ` - Data string ` + "`json:\"data,omitempty\"`" + ` -} - -// Attachment - Content in a format defined elsewhere -type Hl7FhirAttachment struct { - ContentType string ` + "`json:\"contentType,omitempty\"`" + ` - Language string ` + "`json:\"language,omitempty\" validate:\"omitempty,len=2\"`" + ` - Data string ` + "`json:\"data,omitempty\"`" + ` - Url string ` + "`json:\"url,omitempty\" validate:\"omitempty,url\"`" + ` - Size int ` + "`json:\"size,omitempty\" validate:\"omitempty,min=0\"`" + ` - Hash string ` + "`json:\"hash,omitempty\"`" + ` - Title string ` + "`json:\"title,omitempty\"`" + ` - Creation string ` + "`json:\"creation,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + ` - Height int ` + "`json:\"height,omitempty\" validate:\"omitempty,min=1\"`" + ` - Width int ` + "`json:\"width,omitempty\" validate:\"omitempty,min=1\"`" + ` - Frames int ` + "`json:\"frames,omitempty\" validate:\"omitempty,min=1\"`" + ` - Duration float64` + "`json:\"duration,omitempty\" validate:\"omitempty,min=0\"`" + ` - Pages int ` + "`json:\"pages,omitempty\" validate:\"omitempty,min=1\"`" + ` -} - -// ContactPoint - Details for contacting -type Hl7FhirContactPoint struct { - System string ` + "`json:\"system,omitempty\" validate:\"omitempty,oneof=phone fax email pager url sms other\"`" + ` - Value string ` + "`json:\"value,omitempty\"`" + ` - Use string ` + "`json:\"use,omitempty\" validate:\"omitempty,oneof=home work temp old mobile\"`" + ` - Rank int ` + "`json:\"rank,omitempty\" validate:\"omitempty,min=1\"`" + ` - Period Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + ` -} - -// HumanName - Name of a human or other living entity -type Hl7FhirHumanName struct { - Use string ` + "`json:\"use,omitempty\" validate:\"omitempty,oneof=usual official temp nickname anonymous old maiden\"`" + ` - Text string ` + "`json:\"text,omitempty\"`" + ` - Family string ` + "`json:\"family,omitempty\"`" + ` - Given []string ` + "`json:\"given,omitempty\"`" + ` - Prefix []string ` + "`json:\"prefix,omitempty\"`" + ` - Suffix []string ` + "`json:\"suffix,omitempty\"`" + ` - Period Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + ` -} - -// Address - Postal address -type Hl7FhirAddress struct { - Use string ` + "`json:\"use,omitempty\" validate:\"omitempty,oneof=home work temp old billing\"`" + ` - Type string ` + "`json:\"type,omitempty\" validate:\"omitempty,oneof=postal physical both\"`" + ` - Text string ` + "`json:\"text,omitempty\"`" + ` - Line []string ` + "`json:\"line,omitempty\"`" + ` - City string ` + "`json:\"city,omitempty\"`" + ` - District string ` + "`json:\"district,omitempty\"`" + ` - State string ` + "`json:\"state,omitempty\"`" + ` - PostalCode string ` + "`json:\"postalCode,omitempty\"`" + ` - Country string ` + "`json:\"country,omitempty\" validate:\"omitempty,len=2\"`" + ` - Period Hl7FhirPeriod ` + "`json:\"period,omitempty\"`" + ` -} - -// Age - Duration with context -type Hl7FhirAge struct { - Value float64 ` + "`json:\"value,omitempty\" validate:\"omitempty,min=0\"`" + ` - Comparator string ` + "`json:\"comparator,omitempty\" validate:\"omitempty,oneof=< <= >= >\"`" + ` - Unit string ` + "`json:\"unit,omitempty\"`" + ` - System string ` + "`json:\"system,omitempty\" validate:\"omitempty,url\"`" + ` - Code string ` + "`json:\"code,omitempty\"`" + ` -} - -// Count - Integer with units -type Hl7FhirCount struct { - Value int ` + "`json:\"value,omitempty\" validate:\"omitempty,min=0\"`" + ` - Comparator string ` + "`json:\"comparator,omitempty\" validate:\"omitempty,oneof=< <= >= >\"`" + ` - Unit string ` + "`json:\"unit,omitempty\"`" + ` - System string ` + "`json:\"system,omitempty\" validate:\"omitempty,url\"`" + ` - Code string ` + "`json:\"code,omitempty\"`" + ` -} - -// Distance - Length with units -type Hl7FhirDistance struct { - Value float64 ` + "`json:\"value,omitempty\" validate:\"omitempty,min=0\"`" + ` - Comparator string ` + "`json:\"comparator,omitempty\" validate:\"omitempty,oneof=< <= >= >\"`" + ` - Unit string ` + "`json:\"unit,omitempty\"`" + ` - System string ` + "`json:\"system,omitempty\" validate:\"omitempty,url\"`" + ` - Code string ` + "`json:\"code,omitempty\"`" + ` -} - -// Duration - Length of time -type Hl7FhirDuration struct { - Value float64 ` + "`json:\"value,omitempty\" validate:\"omitempty,min=0\"`" + ` - Comparator string ` + "`json:\"comparator,omitempty\" validate:\"omitempty,oneof=< <= >= >\"`" + ` - Unit string ` + "`json:\"unit,omitempty\"`" + ` - System string ` + "`json:\"system,omitempty\" validate:\"omitempty,url\"`" + ` - Code string ` + "`json:\"code,omitempty\"`" + ` -} - -// Money - Amount of economic utility -type Hl7FhirMoney struct { - Value float64 ` + "`json:\"value,omitempty\" validate:\"omitempty,min=0\"`" + ` - Currency string ` + "`json:\"currency,omitempty\" validate:\"omitempty,len=3\"`" + ` -} - -// Annotation - Text node with attribution -type Hl7FhirAnnotation struct { - AuthorReference Hl7FhirReference ` + "`json:\"authorReference,omitempty\"`" + ` - AuthorString string ` + "`json:\"authorString,omitempty\"`" + ` - Time string ` + "`json:\"time,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + ` - Text string ` + "`json:\"text\" validate:\"required\"`" + ` -} - -// Timing - When something happens -type Hl7FhirTiming struct { - Event []string ` + "`json:\"event,omitempty\"`" + ` - Repeat Hl7FhirTimingRepeat ` + "`json:\"repeat,omitempty\"`" + ` - Code Hl7FhirCodeableConcept` + "`json:\"code,omitempty\"`" + ` -} - -// TimingRepeat - When events repeat -type Hl7FhirTimingRepeat struct { - BoundsDuration Hl7FhirDuration ` + "`json:\"boundsDuration,omitempty\"`" + ` - BoundsRange Hl7FhirRange ` + "`json:\"boundsRange,omitempty\"`" + ` - BoundsPeriod Hl7FhirPeriod ` + "`json:\"boundsPeriod,omitempty\"`" + ` - Count int ` + "`json:\"count,omitempty\" validate:\"omitempty,min=0\"`" + ` - CountMax int ` + "`json:\"countMax,omitempty\" validate:\"omitempty,min=0\"`" + ` - Duration float64 ` + "`json:\"duration,omitempty\" validate:\"omitempty,min=0\"`" + ` - DurationMax float64 ` + "`json:\"durationMax,omitempty\" validate:\"omitempty,min=0\"`" + ` - DurationUnit string ` + "`json:\"durationUnit,omitempty\" validate:\"omitempty,oneof=s min h d wk mo a\"`" + ` - Frequency int ` + "`json:\"frequency,omitempty\" validate:\"omitempty,min=1\"`" + ` - FrequencyMax int ` + "`json:\"frequencyMax,omitempty\" validate:\"omitempty,min=1\"`" + ` - Period float64 ` + "`json:\"period,omitempty\" validate:\"omitempty,min=0\"`" + ` - PeriodMax float64 ` + "`json:\"periodMax,omitempty\" validate:\"omitempty,min=0\"`" + ` - PeriodUnit string ` + "`json:\"periodUnit,omitempty\" validate:\"omitempty,oneof=s min h d wk mo a\"`" + ` - DayOfWeek []string ` + "`json:\"dayOfWeek,omitempty\"`" + ` - TimeOfDay []string ` + "`json:\"timeOfDay,omitempty\"`" + ` - When []string ` + "`json:\"when,omitempty\"`" + ` - Offset int ` + "`json:\"offset,omitempty\" validate:\"omitempty,min=0\"`" + ` -} - -// ContactDetail - Contact details (See also ContactPoint) -type Hl7FhirContactDetail struct { - Name string ` + "`json:\"name,omitempty\"`" + ` - Telecom []Hl7FhirContactPoint ` + "`json:\"telecom,omitempty\" validate:\"dive\"`" + ` -} - -// Contributor - Contributor information -type Hl7FhirContributor struct { - Type string ` + "`json:\"type\" validate:\"required,oneof=author editor reviewer endorser\"`" + ` - Name string ` + "`json:\"name\" validate:\"required\"`" + ` - Contact []Hl7FhirContactDetail ` + "`json:\"contact,omitempty\" validate:\"dive\"`" + ` -} - -// DataRequirement - Describes a required data item -type Hl7FhirDataRequirement struct { - Type string ` + "`json:\"type\" validate:\"required\"`" + ` - Profile []string ` + "`json:\"profile,omitempty\"`" + ` - SubjectCodeableConcept Hl7FhirCodeableConcept ` + "`json:\"subjectCodeableConcept,omitempty\"`" + ` - SubjectReference Hl7FhirReference ` + "`json:\"subjectReference,omitempty\"`" + ` - MustSupport []string ` + "`json:\"mustSupport,omitempty\"`" + ` - CodeFilter []Hl7FhirDataRequirementCodeFilter ` + "`json:\"codeFilter,omitempty\"`" + ` - DateFilter []Hl7FhirDataRequirementDateFilter ` + "`json:\"dateFilter,omitempty\"`" + ` - Limit int ` + "`json:\"limit,omitempty\" validate:\"omitempty,min=1\"`" + ` - Sort []Hl7FhirDataRequirementSort ` + "`json:\"sort,omitempty\"`" + ` -} - -// DataRequirementCodeFilter - Code filters specify additional constraints -type Hl7FhirDataRequirementCodeFilter struct { - Path string ` + "`json:\"path,omitempty\"`" + ` - SearchParam string ` + "`json:\"searchParam,omitempty\"`" + ` - ValueSet string ` + "`json:\"valueSet,omitempty\" validate:\"omitempty,url\"`" + ` - Code []Hl7FhirCoding ` + "`json:\"code,omitempty\" validate:\"dive\"`" + ` -} - -// DataRequirementDateFilter - Date filters specify additional constraints -type Hl7FhirDataRequirementDateFilter struct { - Path string ` + "`json:\"path,omitempty\"`" + ` - SearchParam string ` + "`json:\"searchParam,omitempty\"`" + ` - ValueDateTime string ` + "`json:\"valueDateTime,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + ` - ValuePeriod Hl7FhirPeriod ` + "`json:\"valuePeriod,omitempty\"`" + ` - ValueDuration Hl7FhirDuration ` + "`json:\"valueDuration,omitempty\"`" + ` -} - -// DataRequirementSort - Specifies the order of the results -type Hl7FhirDataRequirementSort struct { - Path string ` + "`json:\"path\" validate:\"required\"`" + ` - Direction string ` + "`json:\"direction\" validate:\"required,oneof=ascending descending\"`" + ` -} - -// Expression - Expression language -type Hl7FhirExpression struct { - Description string ` + "`json:\"description,omitempty\"`" + ` - Name string ` + "`json:\"name,omitempty\"`" + ` - Language string ` + "`json:\"language\" validate:\"required\"`" + ` - Expression string ` + "`json:\"expression,omitempty\"`" + ` - Reference string ` + "`json:\"reference,omitempty\" validate:\"omitempty,url\"`" + ` -} - -// ParameterDefinition - Definition of a parameter to a module -type Hl7FhirParameterDefinition struct { - Name string ` + "`json:\"name,omitempty\"`" + ` - Use string ` + "`json:\"use\" validate:\"required,oneof=in out\"`" + ` - Min int ` + "`json:\"min,omitempty\" validate:\"omitempty,min=0\"`" + ` - Max string ` + "`json:\"max,omitempty\"`" + ` - Documentation string ` + "`json:\"documentation,omitempty\"`" + ` - Type string ` + "`json:\"type\" validate:\"required\"`" + ` - Profile string ` + "`json:\"profile,omitempty\" validate:\"omitempty,url\"`" + ` -} - -// RelatedArtifact - Related artifacts such as additional documentation -type Hl7FhirRelatedArtifact struct { - Type string ` + "`json:\"type\" validate:\"required,oneof=documentation justification citation predecessor successor derived-from depends-on composed-of\"`" + ` - Label string ` + "`json:\"label,omitempty\"`" + ` - Display string ` + "`json:\"display,omitempty\"`" + ` - Citation string ` + "`json:\"citation,omitempty\"`" + ` - Url string ` + "`json:\"url,omitempty\" validate:\"omitempty,url\"`" + ` - Document Hl7FhirAttachment ` + "`json:\"document,omitempty\"`" + ` - Resource string ` + "`json:\"resource,omitempty\" validate:\"omitempty,url\"`" + ` -} - -// TriggerDefinition - Defines an expected trigger for a module -type Hl7FhirTriggerDefinition struct { - Type string ` + "`json:\"type\" validate:\"required,oneof=named-event periodic data-changed data-added data-modified data-removed data-accessed data-access-ended\"`" + ` - Name string ` + "`json:\"name,omitempty\"`" + ` - TimingTiming Hl7FhirTiming ` + "`json:\"timingTiming,omitempty\"`" + ` - TimingReference Hl7FhirReference ` + "`json:\"timingReference,omitempty\"`" + ` - TimingDate string ` + "`json:\"timingDate,omitempty\" validate:\"omitempty,datetime=2006-01-02\"`" + ` - TimingDateTime string ` + "`json:\"timingDateTime,omitempty\" validate:\"omitempty,datetime=2006-01-02T15:04:05Z07:00\"`" + ` - Data []Hl7FhirDataRequirement ` + "`json:\"data,omitempty\" validate:\"dive\"`" + ` - Condition Hl7FhirExpression ` + "`json:\"condition,omitempty\"`" + ` -} - -// UsageContext - Describes the context of use for a conformance or knowledge resource -type Hl7FhirUsageContext struct { - Code Hl7FhirCoding ` + "`json:\"code\" validate:\"required\"`" + ` - ValueCodeableConcept Hl7FhirCodeableConcept ` + "`json:\"valueCodeableConcept,omitempty\"`" + ` - ValueQuantity Hl7FhirQuantity ` + "`json:\"valueQuantity,omitempty\"`" + ` - ValueRange Hl7FhirRange ` + "`json:\"valueRange,omitempty\"`" + ` - ValueReference Hl7FhirReference ` + "`json:\"valueReference,omitempty\"`" + ` -} - -// Dosage - How medication is/was taken or should be taken -type Hl7FhirDosage struct { - Sequence int ` + "`json:\"sequence,omitempty\" validate:\"omitempty,min=1\"`" + ` - Text string ` + "`json:\"text,omitempty\"`" + ` - AdditionalInstruction []Hl7FhirCodeableConcept ` + "`json:\"additionalInstruction,omitempty\" validate:\"dive\"`" + ` - PatientInstruction string ` + "`json:\"patientInstruction,omitempty\"`" + ` - Timing Hl7FhirTiming ` + "`json:\"timing,omitempty\"`" + ` - AsNeededBoolean *bool ` + "`json:\"asNeededBoolean,omitempty\"`" + ` - AsNeededCodeableConcept Hl7FhirCodeableConcept ` + "`json:\"asNeededCodeableConcept,omitempty\"`" + ` - Site Hl7FhirCodeableConcept ` + "`json:\"site,omitempty\"`" + ` - Route Hl7FhirCodeableConcept ` + "`json:\"route,omitempty\"`" + ` - Method Hl7FhirCodeableConcept ` + "`json:\"method,omitempty\"`" + ` - DoseAndRate []Hl7FhirDosageDoseAndRate ` + "`json:\"doseAndRate,omitempty\" validate:\"dive\"`" + ` - MaxDosePerPeriod []Hl7FhirRatio ` + "`json:\"maxDosePerPeriod,omitempty\" validate:\"dive\"`" + ` - MaxDosePerAdministration Hl7FhirQuantity ` + "`json:\"maxDosePerAdministration,omitempty\"`" + ` - MaxDosePerLifetime Hl7FhirQuantity ` + "`json:\"maxDosePerLifetime,omitempty\"`" + ` -} - -// DosageDoseAndRate - Amount of medication administered -type Hl7FhirDosageDoseAndRate struct { - Type Hl7FhirCodeableConcept ` + "`json:\"type,omitempty\"`" + ` - DoseRange Hl7FhirRange ` + "`json:\"doseRange,omitempty\"`" + ` - DoseQuantity Hl7FhirQuantity ` + "`json:\"doseQuantity,omitempty\"`" + ` - RateRatio Hl7FhirRatio ` + "`json:\"rateRatio,omitempty\"`" + ` - RateRange Hl7FhirRange ` + "`json:\"rateRange,omitempty\"`" + ` - RateQuantity Hl7FhirQuantity ` + "`json:\"rateQuantity,omitempty\"`" + ` -} - -// ============================================================================ -// End of Common HL7 FHIR Data Types -// ============================================================================ -` -} - -// Continue with additional methods and model generation... -func writeFile(filename, content string) { - if err := os.WriteFile(filename, []byte(content), 0644); err != nil { - fmt.Printf("āŒ Error creating file %s: %v\n", filename, err) - return - } - - fmt.Printf("āœ… Generated HL7 FHIR file: %s\n", filename) -} diff --git a/tools/satusehat/generate b/tools/satusehat/generate deleted file mode 100644 index a2f9166a..00000000 --- a/tools/satusehat/generate +++ /dev/null @@ -1,1442 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "time" -) - -// SatuSehatHandlerData contains template data for Satu Sehat handler generation -type SatuSehatHandlerData struct { - Name string - NameLower string - NameUpper string - Category string - CategoryPath string - CategoryParts []string - ModuleName string - HasGet bool - HasPost bool - HasPut bool - HasPatch bool - HasDelete bool - GetEndpoint string - PostEndpoint string - PutEndpoint string - PatchEndpoint string - DeleteEndpoint string - Timestamp string - DirectoryDepth int - FhirResource string -} - -func main() { - if len(os.Args) < 2 { - fmt.Println("Usage: go run generate-satusehat-handler.go [level1[/level2[/level3[/level4]]]]/entity [methods]") - fmt.Println("Examples:") - fmt.Println(" go run generate-satusehat-handler.go fhir/patient get post put patch") - fmt.Println(" go run generate-satusehat-handler.go master/organization get post") - fmt.Println(" go run generate-satusehat-handler.go integration/encounter/observation get post") - fmt.Println(" go run generate-satusehat-handler.go api/v1/fhir/r4/patient get post put patch delete") - fmt.Println(" go run generate-satusehat-handler.go location get") - os.Exit(1) - } - - // Parse entity path (up to 4 levels + entity) - entityPath := os.Args[1] - methods := []string{} - if len(os.Args) > 2 { - methods = os.Args[2:] - } else { - // Default methods for FHIR resources - methods = []string{"get", "post", "put", "patch"} - } - - // Parse multi-level category and entity - var categoryParts []string - var entityName string - var category string - - parts := strings.Split(entityPath, "/") - - if len(parts) > 1 && len(parts) <= 5 { // Up to 4 levels + entity - categoryParts = parts[:len(parts)-1] - entityName = parts[len(parts)-1] - category = strings.Join(categoryParts, "/") - } else if len(parts) == 1 { - category = "" - entityName = parts[0] - categoryParts = []string{} - } else { - fmt.Println("āŒ Error: Invalid path format. Use up to 4 levels like 'level1/level2/level3/level4/entity' or just 'entity'") - fmt.Printf("āŒ You provided %d levels, maximum is 4 levels + entity\n", len(parts)-1) - os.Exit(1) - } - - // Format names - entityName = strings.Title(entityName) // PascalCase entity name - entityLower := strings.ToLower(entityName) - entityUpper := strings.ToUpper(entityName) - - // FHIR Resource name (capitalize first letter only) - fhirResource := strings.Title(strings.ToLower(entityName)) - - data := SatuSehatHandlerData{ - Name: entityName, - NameLower: entityLower, - NameUpper: entityUpper, - Category: category, - CategoryPath: category, - CategoryParts: categoryParts, - ModuleName: "api-service", - DirectoryDepth: len(categoryParts), - FhirResource: fhirResource, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - } - - // Set methods and endpoints for FHIR resources - for _, m := range methods { - switch strings.ToLower(m) { - case "get": - data.HasGet = true - data.GetEndpoint = fmt.Sprintf("%s/{id}", fhirResource) - case "post": - data.HasPost = true - data.PostEndpoint = fhirResource - case "put": - data.HasPut = true - data.PutEndpoint = fmt.Sprintf("%s/{id}", fhirResource) - case "patch": - data.HasPatch = true - data.PatchEndpoint = fmt.Sprintf("%s/{id}", fhirResource) - case "delete": - data.HasDelete = true - data.DeleteEndpoint = fmt.Sprintf("%s/{id}", fhirResource) - } - } - - // Create directories with multi-level support - var handlerDir, modelDir string - if category != "" { - // Multi-level directory support - handlerDirParts := append([]string{"internal", "handlers", "satusehat"}, categoryParts...) - modelDirParts := append([]string{"internal", "models", "satusehat"}, categoryParts...) - - handlerDir = filepath.Join(handlerDirParts...) - modelDir = filepath.Join(modelDirParts...) - } else { - // No category: direct internal/handlers/satusehat/ - handlerDir = filepath.Join("internal", "handlers", "satusehat") - modelDir = filepath.Join("internal", "models", "satusehat") - } - - // Create directories - for _, d := range []string{handlerDir, modelDir} { - if err := os.MkdirAll(d, 0755); err != nil { - panic(err) - } - } - - // Generate files - generateOptimizedSatuSehatHandlerFile(data, handlerDir) - generateOptimizedSatuSehatModelFile(data, modelDir) - // updateOptimizedSatuSehatRoutesFile(data) - - fmt.Printf("āœ… Successfully generated optimized Satu Sehat handler: %s\n", entityName) - if category != "" { - fmt.Printf("šŸ“ Category Path: %s (%d levels deep)\n", category, data.DirectoryDepth) - } - fmt.Printf("šŸ“ FHIR Resource: %s\n", fhirResource) - fmt.Printf("šŸ“ Handler: %s\n", filepath.Join(handlerDir, entityLower+".go")) - fmt.Printf("šŸ“ Model: %s\n", filepath.Join(modelDir, entityLower+".go")) -} - -// ================= OPTIMIZED HANDLER GENERATION ===================== - -func generateOptimizedSatuSehatHandlerFile(data SatuSehatHandlerData, handlerDir string) { - var modelsImportPath string - if data.Category != "" { - modelsImportPath = data.ModuleName + "/internal/models/satusehat/" + data.Category - } else { - modelsImportPath = data.ModuleName + "/internal/models/satusehat" - } - - handlerContent := `package handlers - -import ( - "context" - "fmt" - "net/http" - "time" - - "` + data.ModuleName + `/internal/config" - "` + modelsImportPath + `" - services "` + data.ModuleName + `/internal/services/satusehat" - "` + data.ModuleName + `/pkg/logger" - "` + data.ModuleName + `/pkg/validator" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" - "github.com/go-playground/validator/v10" -) - -// ` + data.Name + `Handler handles ` + data.NameLower + ` Satu Sehat FHIR services with multi-level organization -// Generated for FHIR Resource: ` + data.FhirResource + ` -// Path: ` + data.Category + ` -// Directory depth: ` + fmt.Sprintf("%d", data.DirectoryDepth) + ` levels -type ` + data.Name + `Handler struct { - service services.SatuSehatService - validator *validator.Validate - logger logger.Logger - config *config.SatuSehatConfig -} - -// HandlerConfig contains configuration for ` + data.Name + `Handler -type ` + data.Name + `HandlerConfig struct { - SatuSehatConfig *config.SatuSehatConfig - Logger logger.Logger - Validator *validator.Validate -} - -// New` + data.Name + `Handler creates a new optimized ` + data.Name + `Handler for Satu Sehat FHIR -func New` + data.Name + `Handler(cfg *` + data.Name + `HandlerConfig) *` + data.Name + `Handler { - return &` + data.Name + `Handler{ - service: services.NewSatuSehatService(cfg.SatuSehatConfig), - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.SatuSehatConfig, - } -}` - - // Add optimized methods based on flags - if data.HasPost { - handlerContent += generateOptimizedSatuSehatCreateMethod(data) - } - - if data.HasPut { - handlerContent += generateOptimizedSatuSehatUpdateMethod(data) - } - - if data.HasPatch { - handlerContent += generateOptimizedSatuSehatPatchMethod(data) - } - - if data.HasDelete { - handlerContent += generateOptimizedSatuSehatDeleteMethod(data) - } - - if data.HasGet { - handlerContent += generateOptimizedSatuSehatGetMethod(data) - handlerContent += generateOptimizedSatuSehatSearchMethod(data) - } - - // Add helper methods - handlerContent += generateSatuSehatHelperMethods(data) - - writeFile(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent) -} - -func generateOptimizedSatuSehatCreateMethod(data SatuSehatHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = "satusehat/" + data.Category + "/" + data.NameLower - tagParts := append([]string{"SatuSehat"}, data.CategoryParts...) - tagParts = append(tagParts, strings.Title(data.NameLower)) - tagName = strings.Join(tagParts, "-") - } else { - routePath = "satusehat/" + data.NameLower - tagName = "SatuSehat-" + strings.Title(data.NameLower) - } - - return ` - -// Create` + data.Name + ` creates a new FHIR ` + data.FhirResource + ` resource -// @Summary Create a new FHIR ` + data.FhirResource + ` resource -// @Description Create a new ` + data.FhirResource + ` resource in Satu Sehat ecosystem with FHIR R4 compliance -// @Description FHIR Resource: ` + data.FhirResource + ` | Path: ` + data.Category + ` -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param Authorization header string true "Bearer token from Satu Sehat OAuth2" -// @Param request body models.` + data.Name + `CreateRequest true "` + data.FhirResource + ` FHIR resource creation request" -// @Success 201 {object} models.` + data.Name + `Response "` + data.FhirResource + ` resource created successfully" -// @Failure 400 {object} models.FhirOperationOutcome "Bad request - validation error" -// @Failure 401 {object} models.FhirOperationOutcome "Unauthorized - invalid or expired token" -// @Failure 422 {object} models.FhirOperationOutcome "Unprocessable entity - FHIR validation error" -// @Failure 500 {object} models.FhirOperationOutcome "Internal server error" -// @Router /api/v1/` + routePath + ` [post] -func (h *` + data.Name + `Handler) Create` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - - h.logger.Info("Creating FHIR ` + data.FhirResource + ` resource", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "fhir_resource": "` + data.FhirResource + `", - "category_path": "` + data.Category + `", - "directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - }) - - var req models.` + data.Name + `CreateRequest - req.RequestID = requestID - req.Timestamp = startTime - - // Enhanced JSON binding with FHIR validation - if err := c.ShouldBindJSON(&req); err != nil { - h.logger.Error("Failed to bind FHIR JSON", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "path": "` + routePath + `", - }) - h.sendFhirErrorResponse(c, http.StatusBadRequest, "structure", - "Invalid FHIR resource structure", err.Error(), requestID) - return - } - - // FHIR resource validation - if err := req.ValidateFhir(); err != nil { - h.logger.Error("FHIR validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "path": "` + routePath + `", - }) - h.sendFhirErrorResponse(c, http.StatusUnprocessableEntity, "business-rule", - "FHIR resource validation failed", err.Error(), requestID) - return - } - - // Struct validation - if err := h.validator.Struct(&req); err != nil { - h.logger.Error("Struct validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "path": "` + routePath + `", - }) - h.sendFhirErrorResponse(c, http.StatusBadRequest, "structure", - "Resource structure validation failed", h.formatValidationError(err), requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - var fhirResponse models.FhirResponse - if err := h.service.CreateResource(ctx, "` + data.PostEndpoint + `", req, &fhirResponse); err != nil { - h.logger.Error("Failed to create FHIR resource", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "endpoint": "` + data.PostEndpoint + `", - "path": "` + routePath + `", - }) - - statusCode, errorCode := h.categorizeFhirError(err) - h.sendFhirErrorResponse(c, statusCode, errorCode, - "Failed to create ` + data.FhirResource + ` resource", err.Error(), requestID) - return - } - - // Check for FHIR OperationOutcome - if fhirResponse.ResourceType == "OperationOutcome" { - h.logger.Warn("Satu Sehat returned OperationOutcome", map[string]interface{}{ - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "path": "` + routePath + `", - "outcome": fhirResponse, - }) - - h.sendFhirOperationOutcome(c, http.StatusUnprocessableEntity, fhirResponse, requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("FHIR ` + data.FhirResource + ` resource created successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "fhir_resource": "` + data.FhirResource + `", - "resource_id": fhirResponse.ID, - "path": "` + routePath + `", - }) - - h.sendFhirSuccessResponse(c, http.StatusCreated, "` + data.FhirResource + ` resource created successfully", - fhirResponse, requestID) -}` -} - -func generateOptimizedSatuSehatUpdateMethod(data SatuSehatHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = "satusehat/" + data.Category + "/" + data.NameLower - tagParts := append([]string{"SatuSehat"}, data.CategoryParts...) - tagParts = append(tagParts, strings.Title(data.NameLower)) - tagName = strings.Join(tagParts, "-") - } else { - routePath = "satusehat/" + data.NameLower - tagName = "SatuSehat-" + strings.Title(data.NameLower) - } - - return ` - -// Update` + data.Name + ` updates an existing FHIR ` + data.FhirResource + ` resource -// @Summary Update (replace) an existing FHIR ` + data.FhirResource + ` resource -// @Description Update an existing ` + data.FhirResource + ` resource in Satu Sehat ecosystem with FHIR R4 compliance -// @Description FHIR Resource: ` + data.FhirResource + ` | Path: ` + data.Category + ` -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param Authorization header string true "Bearer token from Satu Sehat OAuth2" -// @Param id path string true "` + data.FhirResource + ` resource ID" -// @Param request body models.` + data.Name + `UpdateRequest true "` + data.FhirResource + ` FHIR resource update request" -// @Success 200 {object} models.` + data.Name + `Response "` + data.FhirResource + ` resource updated successfully" -// @Failure 400 {object} models.FhirOperationOutcome "Bad request - validation error" -// @Failure 401 {object} models.FhirOperationOutcome "Unauthorized - invalid or expired token" -// @Failure 404 {object} models.FhirOperationOutcome "Resource not found" -// @Failure 422 {object} models.FhirOperationOutcome "Unprocessable entity - FHIR validation error" -// @Failure 500 {object} models.FhirOperationOutcome "Internal server error" -// @Router /api/v1/` + routePath + `/{id} [put] -func (h *` + data.Name + `Handler) Update` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - id := c.Param("id") - - h.logger.Info("Updating FHIR ` + data.FhirResource + ` resource", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "category_path": "` + data.Category + `", - "directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - }) - - if id == "" { - h.sendFhirErrorResponse(c, http.StatusBadRequest, "required", - "Resource ID is required", "", requestID) - return - } - - var req models.` + data.Name + `UpdateRequest - req.RequestID = requestID - req.Timestamp = startTime - req.ID = id - - if err := c.ShouldBindJSON(&req); err != nil { - h.logger.Error("Failed to bind FHIR JSON for update", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - h.sendFhirErrorResponse(c, http.StatusBadRequest, "structure", - "Invalid FHIR resource structure", err.Error(), requestID) - return - } - - if err := req.ValidateFhir(); err != nil { - h.logger.Error("FHIR update validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - h.sendFhirErrorResponse(c, http.StatusUnprocessableEntity, "business-rule", - "FHIR resource validation failed", err.Error(), requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - endpoint := fmt.Sprintf("` + strings.Replace(data.PutEndpoint, "{id}", "%s", 1) + `", id) - var fhirResponse models.FhirResponse - - if err := h.service.UpdateResource(ctx, endpoint, req, &fhirResponse); err != nil { - h.logger.Error("Failed to update FHIR resource", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - - statusCode, errorCode := h.categorizeFhirError(err) - h.sendFhirErrorResponse(c, statusCode, errorCode, - "Failed to update ` + data.FhirResource + ` resource", err.Error(), requestID) - return - } - - if fhirResponse.ResourceType == "OperationOutcome" { - h.logger.Warn("Satu Sehat returned OperationOutcome for update", map[string]interface{}{ - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - "outcome": fhirResponse, - }) - - h.sendFhirOperationOutcome(c, http.StatusUnprocessableEntity, fhirResponse, requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("FHIR ` + data.FhirResource + ` resource updated successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - - h.sendFhirSuccessResponse(c, http.StatusOK, "` + data.FhirResource + ` resource updated successfully", - fhirResponse, requestID) -}` -} - -func generateOptimizedSatuSehatPatchMethod(data SatuSehatHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = "satusehat/" + data.Category + "/" + data.NameLower - tagParts := append([]string{"SatuSehat"}, data.CategoryParts...) - tagParts = append(tagParts, strings.Title(data.NameLower)) - tagName = strings.Join(tagParts, "-") - } else { - routePath = "satusehat/" + data.NameLower - tagName = "SatuSehat-" + strings.Title(data.NameLower) - } - - return ` - -// Patch` + data.Name + ` partially updates an existing FHIR ` + data.FhirResource + ` resource -// @Summary Patch (partial update) an existing FHIR ` + data.FhirResource + ` resource -// @Description Partially update an existing ` + data.FhirResource + ` resource in Satu Sehat ecosystem with FHIR R4 compliance -// @Description FHIR Resource: ` + data.FhirResource + ` | Path: ` + data.Category + ` -// @Tags ` + tagName + ` -// @Accept json-patch+json -// @Produce json -// @Param Authorization header string true "Bearer token from Satu Sehat OAuth2" -// @Param id path string true "` + data.FhirResource + ` resource ID" -// @Param request body models.` + data.Name + `PatchRequest true "` + data.FhirResource + ` FHIR resource patch request" -// @Success 200 {object} models.` + data.Name + `Response "` + data.FhirResource + ` resource patched successfully" -// @Failure 400 {object} models.FhirOperationOutcome "Bad request - validation error" -// @Failure 401 {object} models.FhirOperationOutcome "Unauthorized - invalid or expired token" -// @Failure 404 {object} models.FhirOperationOutcome "Resource not found" -// @Failure 422 {object} models.FhirOperationOutcome "Unprocessable entity - FHIR validation error" -// @Failure 500 {object} models.FhirOperationOutcome "Internal server error" -// @Router /api/v1/` + routePath + `/{id} [patch] -func (h *` + data.Name + `Handler) Patch` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - id := c.Param("id") - - h.logger.Info("Patching FHIR ` + data.FhirResource + ` resource", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "category_path": "` + data.Category + `", - "directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - }) - - if id == "" { - h.sendFhirErrorResponse(c, http.StatusBadRequest, "required", - "Resource ID is required", "", requestID) - return - } - - var req models.` + data.Name + `PatchRequest - req.RequestID = requestID - req.Timestamp = startTime - req.ID = id - - if err := c.ShouldBindJSON(&req); err != nil { - h.logger.Error("Failed to bind FHIR patch JSON", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - h.sendFhirErrorResponse(c, http.StatusBadRequest, "structure", - "Invalid FHIR patch structure", err.Error(), requestID) - return - } - - if err := req.ValidateFhirPatch(); err != nil { - h.logger.Error("FHIR patch validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - h.sendFhirErrorResponse(c, http.StatusUnprocessableEntity, "business-rule", - "FHIR patch validation failed", err.Error(), requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - endpoint := fmt.Sprintf("` + strings.Replace(data.PatchEndpoint, "{id}", "%s", 1) + `", id) - var fhirResponse models.FhirResponse - - if err := h.service.PatchResource(ctx, endpoint, req, &fhirResponse); err != nil { - h.logger.Error("Failed to patch FHIR resource", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - - statusCode, errorCode := h.categorizeFhirError(err) - h.sendFhirErrorResponse(c, statusCode, errorCode, - "Failed to patch ` + data.FhirResource + ` resource", err.Error(), requestID) - return - } - - if fhirResponse.ResourceType == "OperationOutcome" { - h.logger.Warn("Satu Sehat returned OperationOutcome for patch", map[string]interface{}{ - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - "outcome": fhirResponse, - }) - - h.sendFhirOperationOutcome(c, http.StatusUnprocessableEntity, fhirResponse, requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("FHIR ` + data.FhirResource + ` resource patched successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - - h.sendFhirSuccessResponse(c, http.StatusOK, "` + data.FhirResource + ` resource patched successfully", - fhirResponse, requestID) -}` -} - -func generateOptimizedSatuSehatDeleteMethod(data SatuSehatHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = "satusehat/" + data.Category + "/" + data.NameLower - tagParts := append([]string{"SatuSehat"}, data.CategoryParts...) - tagParts = append(tagParts, strings.Title(data.NameLower)) - tagName = strings.Join(tagParts, "-") - } else { - routePath = "satusehat/" + data.NameLower - tagName = "SatuSehat-" + strings.Title(data.NameLower) - } - - return ` - -// Delete` + data.Name + ` deletes an existing FHIR ` + data.FhirResource + ` resource -// @Summary Delete an existing FHIR ` + data.FhirResource + ` resource -// @Description Delete an existing ` + data.FhirResource + ` resource in Satu Sehat ecosystem with FHIR R4 compliance -// @Description FHIR Resource: ` + data.FhirResource + ` | Path: ` + data.Category + ` -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param Authorization header string true "Bearer token from Satu Sehat OAuth2" -// @Param id path string true "` + data.FhirResource + ` resource ID" -// @Success 204 "` + data.FhirResource + ` resource deleted successfully" -// @Failure 400 {object} models.FhirOperationOutcome "Bad request - invalid ID" -// @Failure 401 {object} models.FhirOperationOutcome "Unauthorized - invalid or expired token" -// @Failure 404 {object} models.FhirOperationOutcome "Resource not found" -// @Failure 500 {object} models.FhirOperationOutcome "Internal server error" -// @Router /api/v1/` + routePath + `/{id} [delete] -func (h *` + data.Name + `Handler) Delete` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - id := c.Param("id") - - h.logger.Info("Deleting FHIR ` + data.FhirResource + ` resource", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "category_path": "` + data.Category + `", - "directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - }) - - if id == "" { - h.sendFhirErrorResponse(c, http.StatusBadRequest, "required", - "Resource ID is required", "", requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - endpoint := fmt.Sprintf("` + strings.Replace(data.DeleteEndpoint, "{id}", "%s", 1) + `", id) - - if err := h.service.DeleteResource(ctx, endpoint); err != nil { - h.logger.Error("Failed to delete FHIR resource", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - - statusCode, errorCode := h.categorizeFhirError(err) - h.sendFhirErrorResponse(c, statusCode, errorCode, - "Failed to delete ` + data.FhirResource + ` resource", err.Error(), requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("FHIR ` + data.FhirResource + ` resource deleted successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - - c.Status(http.StatusNoContent) -}` -} - -func generateOptimizedSatuSehatGetMethod(data SatuSehatHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = "satusehat/" + data.Category + "/" + data.NameLower - tagParts := append([]string{"SatuSehat"}, data.CategoryParts...) - tagParts = append(tagParts, strings.Title(data.NameLower)) - tagName = strings.Join(tagParts, "-") - } else { - routePath = "satusehat/" + data.NameLower - tagName = "SatuSehat-" + strings.Title(data.NameLower) - } - - return ` - -// Get` + data.Name + ` retrieves a specific FHIR ` + data.FhirResource + ` resource by ID -// @Summary Get a specific FHIR ` + data.FhirResource + ` resource by ID -// @Description Retrieve a specific ` + data.FhirResource + ` resource from Satu Sehat ecosystem with FHIR R4 compliance -// @Description FHIR Resource: ` + data.FhirResource + ` | Path: ` + data.Category + ` -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param Authorization header string true "Bearer token from Satu Sehat OAuth2" -// @Param id path string true "` + data.FhirResource + ` resource ID" -// @Success 200 {object} models.` + data.Name + `Response "` + data.FhirResource + ` resource retrieved successfully" -// @Failure 400 {object} models.FhirOperationOutcome "Bad request - invalid ID" -// @Failure 401 {object} models.FhirOperationOutcome "Unauthorized - invalid or expired token" -// @Failure 404 {object} models.FhirOperationOutcome "Resource not found" -// @Failure 500 {object} models.FhirOperationOutcome "Internal server error" -// @Router /api/v1/` + routePath + `/{id} [get] -func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - id := c.Param("id") - - h.logger.Info("Getting FHIR ` + data.FhirResource + ` resource", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "category_path": "` + data.Category + `", - "directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - }) - - if id == "" { - h.sendFhirErrorResponse(c, http.StatusBadRequest, "required", - "Resource ID is required", "", requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - endpoint := fmt.Sprintf("` + strings.Replace(data.GetEndpoint, "{id}", "%s", 1) + `", id) - var fhirResponse models.FhirResponse - - if err := h.service.GetResource(ctx, endpoint, &fhirResponse); err != nil { - h.logger.Error("Failed to get FHIR resource", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - - statusCode, errorCode := h.categorizeFhirError(err) - h.sendFhirErrorResponse(c, statusCode, errorCode, - "Failed to get ` + data.FhirResource + ` resource", err.Error(), requestID) - return - } - - if fhirResponse.ResourceType == "OperationOutcome" { - h.logger.Info("FHIR ` + data.FhirResource + ` resource not found", map[string]interface{}{ - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - "outcome": fhirResponse, - }) - - h.sendFhirOperationOutcome(c, http.StatusNotFound, fhirResponse, requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("FHIR ` + data.FhirResource + ` resource retrieved successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "fhir_resource": "` + data.FhirResource + `", - "resource_id": id, - "path": "` + routePath + `", - }) - - h.sendFhirSuccessResponse(c, http.StatusOK, "` + data.FhirResource + ` resource retrieved successfully", - fhirResponse, requestID) -}` -} - -func generateOptimizedSatuSehatSearchMethod(data SatuSehatHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = "satusehat/" + data.Category + "/" + data.NameLower - tagParts := append([]string{"SatuSehat"}, data.CategoryParts...) - tagParts = append(tagParts, strings.Title(data.NameLower)) - tagName = strings.Join(tagParts, "-") - } else { - routePath = "satusehat/" + data.NameLower - tagName = "SatuSehat-" + strings.Title(data.NameLower) - } - - return ` - -// Search` + data.Name + ` searches for FHIR ` + data.FhirResource + ` resources with parameters -// @Summary Search for FHIR ` + data.FhirResource + ` resources -// @Description Search for ` + data.FhirResource + ` resources in Satu Sehat ecosystem with FHIR R4 search parameters -// @Description FHIR Resource: ` + data.FhirResource + ` | Path: ` + data.Category + ` -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param Authorization header string true "Bearer token from Satu Sehat OAuth2" -// @Param _count query integer false "Number of results to return (default: 10)" -// @Param _page query integer false "Page number for pagination (default: 1)" -// @Param _include query string false "Include referenced resources" -// @Param _revinclude query string false "Reverse include referenced resources" -// @Success 200 {object} models.FhirBundleResponse "` + data.FhirResource + ` resources search results" -// @Failure 400 {object} models.FhirOperationOutcome "Bad request - invalid search parameters" -// @Failure 401 {object} models.FhirOperationOutcome "Unauthorized - invalid or expired token" -// @Failure 500 {object} models.FhirOperationOutcome "Internal server error" -// @Router /api/v1/` + routePath + ` [get] -func (h *` + data.Name + `Handler) Search` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - - h.logger.Info("Searching FHIR ` + data.FhirResource + ` resources", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "fhir_resource": "` + data.FhirResource + `", - "query_params": c.Request.URL.Query(), - "category_path": "` + data.Category + `", - "directory_depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - }) - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - // Build search parameters - searchParams := make(map[string]string) - for key, values := range c.Request.URL.Query() { - if len(values) > 0 { - searchParams[key] = values[0] - } - } - - var fhirBundle models.FhirBundleResponse - if err := h.service.SearchResources(ctx, "` + data.FhirResource + `", searchParams, &fhirBundle); err != nil { - h.logger.Error("Failed to search FHIR resources", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "fhir_resource": "` + data.FhirResource + `", - "search_params": searchParams, - "path": "` + routePath + `", - }) - - statusCode, errorCode := h.categorizeFhirError(err) - h.sendFhirErrorResponse(c, statusCode, errorCode, - "Failed to search ` + data.FhirResource + ` resources", err.Error(), requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("FHIR ` + data.FhirResource + ` resources search completed", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "fhir_resource": "` + data.FhirResource + `", - "total_results": fhirBundle.Total, - "search_params": searchParams, - "path": "` + routePath + `", - }) - - h.sendFhirBundleResponse(c, http.StatusOK, "` + data.FhirResource + ` resources search completed", - fhirBundle, requestID) -}` -} - -func generateSatuSehatHelperMethods(data SatuSehatHandlerData) string { - return ` - -// Helper methods for ` + data.Name + `Handler with FHIR support -func (h *` + data.Name + `Handler) sendFhirSuccessResponse(c *gin.Context, statusCode int, message string, data interface{}, requestID string) { - response := models.` + data.Name + `Response{ - FhirResponse: models.FhirResponse{ - ResourceType: "` + data.FhirResource + `", - Meta: models.FhirMeta{ - LastUpdated: time.Now().Format(time.RFC3339), - VersionId: "1", - }, - }, - BaseResponse: models.BaseResponse{ - Status: "success", - Message: message, - Data: data, - Metadata: &models.ResponseMetadata{ - Timestamp: time.Now(), - Version: "FHIR R4", - RequestID: requestID, - Path: "` + data.Category + `", - Depth: ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - FhirResource: "` + data.FhirResource + `", - }, - }, - } - c.JSON(statusCode, response) -} - -func (h *` + data.Name + `Handler) sendFhirErrorResponse(c *gin.Context, statusCode int, errorCode, message, details, requestID string) { - operationOutcome := models.FhirOperationOutcome{ - ResourceType: "OperationOutcome", - ID: requestID, - Meta: models.FhirMeta{ - LastUpdated: time.Now().Format(time.RFC3339), - VersionId: "1", - }, - Issue: []models.FhirOperationOutcomeIssue{{ - Severity: h.mapHttpStatusToSeverity(statusCode), - Code: errorCode, - Details: models.FhirCodeableConcept{ - Text: message, - }, - Diagnostics: details, - }}, - } - c.JSON(statusCode, operationOutcome) -}a - -func (h *` + data.Name + `Handler) sendFhirOperationOutcome(c *gin.Context, statusCode int, outcome models.FhirResponse, requestID string) { - c.JSON(statusCode, outcome) -} - -func (h *` + data.Name + `Handler) sendFhirBundleResponse(c *gin.Context, statusCode int, message string, bundle models.FhirBundleResponse, requestID string) { - bundle.Meta = models.FhirMeta{ - LastUpdated: time.Now().Format(time.RFC3339), - VersionId: "1", - } - c.JSON(statusCode, bundle) -} - -func (h *` + data.Name + `Handler) formatValidationError(err error) string { - if validationErrors, ok := err.(validator.ValidationErrors); ok { - var messages []string - for _, e := range validationErrors { - switch e.Tag() { - case "required": - messages = append(messages, fmt.Sprintf("Field '%s' is required", e.Field())) - case "min": - messages = append(messages, fmt.Sprintf("Field '%s' must be at least %s characters", e.Field(), e.Param())) - case "max": - messages = append(messages, fmt.Sprintf("Field '%s' must be at most %s characters", e.Field(), e.Param())) - case "oneof": - messages = append(messages, fmt.Sprintf("Field '%s' must be one of: %s", e.Field(), e.Param())) - case "url": - messages = append(messages, fmt.Sprintf("Field '%s' must be a valid URL", e.Field())) - case "uuid": - messages = append(messages, fmt.Sprintf("Field '%s' must be a valid UUID", e.Field())) - default: - messages = append(messages, fmt.Sprintf("Field '%s' is invalid", e.Field())) - } - } - return fmt.Sprintf("FHIR validation failed: %v", messages) - } - return err.Error() -} - -func (h *` + data.Name + `Handler) categorizeFhirError(err error) (int, string) { - if err == nil { - return http.StatusOK, "informational" - } - - errStr := err.Error() - - // FHIR-specific error categorization - if strings.Contains(errStr, "unauthorized") || strings.Contains(errStr, "invalid token") { - return http.StatusUnauthorized, "security" - } - - if strings.Contains(errStr, "not found") || strings.Contains(errStr, "404") { - return http.StatusNotFound, "not-found" - } - - if strings.Contains(errStr, "validation") || strings.Contains(errStr, "invalid") { - return http.StatusUnprocessableEntity, "business-rule" - } - - if h.isTimeoutError(err) { - return http.StatusRequestTimeout, "timeout" - } - - if h.isNetworkError(err) { - return http.StatusBadGateway, "transient" - } - - return http.StatusInternalServerError, "exception" -} - -func (h *` + data.Name + `Handler) mapHttpStatusToSeverity(statusCode int) string { - switch { - case statusCode >= 500: - return "fatal" - case statusCode >= 400: - return "error" - case statusCode >= 300: - return "warning" - default: - return "information" - } -} - -func (h *` + data.Name + `Handler) isTimeoutError(err error) bool { - return err != nil && (strings.Contains(err.Error(), "timeout") || - strings.Contains(err.Error(), "deadline exceeded")) -} - -func (h *` + data.Name + `Handler) isNetworkError(err error) bool { - return err != nil && (strings.Contains(err.Error(), "connection refused") || - strings.Contains(err.Error(), "no such host") || - strings.Contains(err.Error(), "network unreachable")) -}` -} -func generateOptimizedSatuSehatModelFile(data SatuSehatHandlerData, modelDir string) { - modelContent := `package models - -import ( - "encoding/json" - "fmt" - "time" - "regexp" -) - -// ` + data.Name + ` Satu Sehat FHIR R4 Models with Enhanced Multi-Level Support -// Generated at: ` + data.Timestamp + ` -// FHIR Resource: ` + data.FhirResource + ` -// Category Path: ` + data.Category + ` -// Directory Depth: ` + fmt.Sprintf("%d", data.DirectoryDepth) + ` levels - -// Base FHIR structures -type FhirResource struct { - ResourceType string ` + "`json:\"resourceType\"`" + ` - ID string ` + "`json:\"id,omitempty\"`" + ` - Meta FhirMeta ` + "`json:\"meta,omitempty\"`" + ` -} - -type FhirMeta struct { - VersionId string ` + "`json:\"versionId,omitempty\"`" + ` - LastUpdated string ` + "`json:\"lastUpdated,omitempty\"`" + ` - Profile []string ` + "`json:\"profile,omitempty\"`" + ` - Security []FhirCoding ` + "`json:\"security,omitempty\"`" + ` - Tag []FhirCoding ` + "`json:\"tag,omitempty\"`" + ` -} - -type FhirCoding struct { - System string ` + "`json:\"system,omitempty\"`" + ` - Version string ` + "`json:\"version,omitempty\"`" + ` - Code string ` + "`json:\"code,omitempty\"`" + ` - Display string ` + "`json:\"display,omitempty\"`" + ` -} - -type FhirCodeableConcept struct { - Coding []FhirCoding ` + "`json:\"coding,omitempty\"`" + ` - Text string ` + "`json:\"text,omitempty\"`" + ` -} - -type FhirReference struct { - Reference string ` + "`json:\"reference,omitempty\"`" + ` - Type string ` + "`json:\"type,omitempty\"`" + ` - Identifier FhirIdentifier ` + "`json:\"identifier,omitempty\"`" + ` - Display string ` + "`json:\"display,omitempty\"`" + ` -} - -type FhirIdentifier struct { - Use string ` + "`json:\"use,omitempty\"`" + ` - Type FhirCodeableConcept ` + "`json:\"type,omitempty\"`" + ` - System string ` + "`json:\"system,omitempty\"`" + ` - Value string ` + "`json:\"value,omitempty\"`" + ` - Period FhirPeriod ` + "`json:\"period,omitempty\"`" + ` - Assigner FhirReference ` + "`json:\"assigner,omitempty\"`" + ` -} - -type FhirPeriod struct { - Start string ` + "`json:\"start,omitempty\"`" + ` - End string ` + "`json:\"end,omitempty\"`" + ` -} - -// FHIR OperationOutcome for error handling -type FhirOperationOutcome struct { - ResourceType string ` + "`json:\"resourceType\"`" + ` - ID string ` + "`json:\"id,omitempty\"`" + ` - Meta FhirMeta ` + "`json:\"meta,omitempty\"`" + ` - Issue []FhirOperationOutcomeIssue ` + "`json:\"issue\"`" + ` -} - -type FhirOperationOutcomeIssue struct { - Severity string ` + "`json:\"severity\"`" + ` - Code string ` + "`json:\"code\"`" + ` - Details FhirCodeableConcept ` + "`json:\"details,omitempty\"`" + ` - Diagnostics string ` + "`json:\"diagnostics,omitempty\"`" + ` - Location []string ` + "`json:\"location,omitempty\"`" + ` - Expression []string ` + "`json:\"expression,omitempty\"`" + ` -} - -// FHIR Bundle for search results -type FhirBundleResponse struct { - ResourceType string ` + "`json:\"resourceType\"`" + ` - ID string ` + "`json:\"id,omitempty\"`" + ` - Meta FhirMeta ` + "`json:\"meta,omitempty\"`" + ` - Type string ` + "`json:\"type\"`" + ` - Total int ` + "`json:\"total,omitempty\"`" + ` - Link []FhirBundleLink ` + "`json:\"link,omitempty\"`" + ` - Entry []FhirBundleEntry ` + "`json:\"entry,omitempty\"`" + ` -} - -type FhirBundleLink struct { - Relation string ` + "`json:\"relation\"`" + ` - URL string ` + "`json:\"url\"`" + ` -} - -type FhirBundleEntry struct { - FullURL string ` + "`json:\"fullUrl,omitempty\"`" + ` - Resource interface{} ` + "`json:\"resource,omitempty\"`" + ` - Search FhirBundleEntrySearch ` + "`json:\"search,omitempty\"`" + ` -} - -type FhirBundleEntrySearch struct { - Mode string ` + "`json:\"mode,omitempty\"`" + ` - Score string ` + "`json:\"score,omitempty\"`" + ` -} - -// Base request/response structures with FHIR integration -type BaseRequest struct { - RequestID string ` + "`json:\"request_id,omitempty\"`" + ` - Timestamp time.Time ` + "`json:\"timestamp,omitempty\"`" + ` -} - -type BaseResponse struct { - Status string ` + "`json:\"status\"`" + ` - Message string ` + "`json:\"message\"`" + ` - Data interface{} ` + "`json:\"data,omitempty\"`" + ` - Error *ErrorResponse ` + "`json:\"error,omitempty\"`" + ` - Metadata *ResponseMetadata ` + "`json:\"metadata,omitempty\"`" + ` -} - -type ErrorResponse struct { - Code string ` + "`json:\"code\"`" + ` - Message string ` + "`json:\"message\"`" + ` - Details string ` + "`json:\"details,omitempty\"`" + ` -} - -type ResponseMetadata struct { - Timestamp time.Time ` + "`json:\"timestamp\"`" + ` - Version string ` + "`json:\"version\"`" + ` - RequestID string ` + "`json:\"request_id,omitempty\"`" + ` - Path string ` + "`json:\"path,omitempty\"`" + ` - Depth int ` + "`json:\"depth,omitempty\"`" + ` - FhirResource string ` + "`json:\"fhir_resource,omitempty\"`" + ` -} - -// ` + data.Name + ` Response Structure with FHIR integration -type ` + data.Name + `Response struct { - FhirResource - BaseResponse -} - -// Generic FHIR Response -type FhirResponse struct { - ResourceType string ` + "`json:\"resourceType\"`" + ` - ID string ` + "`json:\"id,omitempty\"`" + ` - Meta FhirMeta ` + "`json:\"meta,omitempty\"`" + ` - Content map[string]interface{} ` + "`json:\"-\"`" + ` // For dynamic content -}` - - // Add CRUD request structures based on methods - if data.HasPost { - modelContent += ` - -// ` + data.Name + ` CREATE Request Structure with FHIR R4 Validation -type ` + data.Name + `CreateRequest struct { - BaseRequest - ResourceType string ` + "`json:\"resourceType\" binding:\"required\" validate:\"required,eq=` + data.FhirResource + `\"`" + ` - - // Core FHIR ` + data.FhirResource + ` fields - customize based on specific resource - // Path: ` + data.Category + ` - Meta FhirMeta ` + "`json:\"meta,omitempty\"`" + ` - Identifier []FhirIdentifier ` + "`json:\"identifier,omitempty\" validate:\"dive\"`" + ` - Active *bool ` + "`json:\"active,omitempty\"`" + ` - Name []FhirHumanName ` + "`json:\"name,omitempty\" validate:\"dive\"`" + ` - Telecom []FhirContactPoint ` + "`json:\"telecom,omitempty\" validate:\"dive\"`" + ` - Gender string ` + "`json:\"gender,omitempty\" validate:\"omitempty,oneof=male female other unknown\"`" + ` - BirthDate string ` + "`json:\"birthDate,omitempty\" validate:\"omitempty,datetime=2006-01-02\"`" + ` - Address []FhirAddress ` + "`json:\"address,omitempty\" validate:\"dive\"`" + ` -} - -// Additional FHIR data types for ` + data.FhirResource + ` -type FhirHumanName struct { - Use string ` + "`json:\"use,omitempty\" validate:\"omitempty,oneof=usual official temp nickname anonymous old maiden\"`" + ` - Text string ` + "`json:\"text,omitempty\"`" + ` - Family string ` + "`json:\"family,omitempty\"`" + ` - Given []string ` + "`json:\"given,omitempty\"`" + ` - Prefix []string ` + "`json:\"prefix,omitempty\"`" + ` - Suffix []string ` + "`json:\"suffix,omitempty\"`" + ` - Period FhirPeriod ` + "`json:\"period,omitempty\"`" + ` -} - -type FhirContactPoint struct { - System string ` + "`json:\"system,omitempty\" validate:\"omitempty,oneof=phone fax email pager url sms other\"`" + ` - Value string ` + "`json:\"value,omitempty\"`" + ` - Use string ` + "`json:\"use,omitempty\" validate:\"omitempty,oneof=home work temp old mobile\"`" + ` - Rank int ` + "`json:\"rank,omitempty\" validate:\"omitempty,min=1\"`" + ` - Period FhirPeriod ` + "`json:\"period,omitempty\"`" + ` -} - -type FhirAddress struct { - Use string ` + "`json:\"use,omitempty\" validate:\"omitempty,oneof=home work temp old billing\"`" + ` - Type string ` + "`json:\"type,omitempty\" validate:\"omitempty,oneof=postal physical both\"`" + ` - Text string ` + "`json:\"text,omitempty\"`" + ` - Line []string ` + "`json:\"line,omitempty\"`" + ` - City string ` + "`json:\"city,omitempty\"`" + ` - District string ` + "`json:\"district,omitempty\"`" + ` - State string ` + "`json:\"state,omitempty\"`" + ` - PostalCode string ` + "`json:\"postalCode,omitempty\"`" + ` - Country string ` + "`json:\"country,omitempty\"`" + ` - Period FhirPeriod ` + "`json:\"period,omitempty\"`" + ` -} - -// ValidateFhir validates the ` + data.Name + `CreateRequest with FHIR R4 business rules -func (r *` + data.Name + `CreateRequest) ValidateFhir() error { - if r.ResourceType != "` + data.FhirResource + `" { - return fmt.Errorf("invalid resourceType: expected ` + data.FhirResource + `, got %s", r.ResourceType) - } - - // Validate required identifiers for Indonesian context - if len(r.Identifier) == 0 { - return fmt.Errorf("at least one identifier is required for ` + data.FhirResource + ` resource") - } - - // Validate NIK if present - for _, identifier := range r.Identifier { - if identifier.System == "https://fhir.kemkes.go.id/id/nik" { - if !isValidNIK(identifier.Value) { - return fmt.Errorf("invalid NIK format: %s", identifier.Value) - } - } - } - - // Path: ` + data.Category + ` - return nil -} - -// ToJSON converts struct to JSON string -func (r *` + data.Name + `CreateRequest) ToJSON() (string, error) { - data, err := json.Marshal(r) - return string(data), err -}` - } - - if data.HasPut { - modelContent += ` - -// ` + data.Name + ` UPDATE Request Structure with FHIR R4 Validation -type ` + data.Name + `UpdateRequest struct { - BaseRequest - ID string ` + "`json:\"id\" binding:\"required\" validate:\"required,uuid\"`" + ` - ResourceType string ` + "`json:\"resourceType\" binding:\"required\" validate:\"required,eq=` + data.FhirResource + `\"`" + ` - Meta FhirMeta ` + "`json:\"meta,omitempty\"`" + ` - Identifier []FhirIdentifier ` + "`json:\"identifier,omitempty\" validate:\"dive\"`" + ` - Active *bool ` + "`json:\"active,omitempty\"`" + ` - Name []FhirHumanName ` + "`json:\"name,omitempty\" validate:\"dive\"`" + ` - Telecom []FhirContactPoint ` + "`json:\"telecom,omitempty\" validate:\"dive\"`" + ` - Gender string ` + "`json:\"gender,omitempty\" validate:\"omitempty,oneof=male female other unknown\"`" + ` - BirthDate string ` + "`json:\"birthDate,omitempty\" validate:\"omitempty,datetime=2006-01-02\"`" + ` - Address []FhirAddress ` + "`json:\"address,omitempty\" validate:\"dive\"`" + ` -} - -// ValidateFhir validates the ` + data.Name + `UpdateRequest with FHIR R4 business rules -func (r *` + data.Name + `UpdateRequest) ValidateFhir() error { - if r.ResourceType != "` + data.FhirResource + `" { - return fmt.Errorf("invalid resourceType: expected ` + data.FhirResource + `, got %s", r.ResourceType) - } - - if r.ID == "" { - return fmt.Errorf("resource ID is required for update operation") - } - - // Path: ` + data.Category + ` - return nil -} - -// ToJSON converts struct to JSON string -func (r *` + data.Name + `UpdateRequest) ToJSON() (string, error) { - data, err := json.Marshal(r) - return string(data), err -}` - } - - if data.HasPatch { - modelContent += ` - -// ` + data.Name + ` PATCH Request Structure with FHIR R4 JSON Patch -type ` + data.Name + `PatchRequest struct { - BaseRequest - ID string ` + "`json:\"id\" binding:\"required\" validate:\"required,uuid\"`" + ` - Patches []FhirJsonPatch ` + "`json:\"patches\" binding:\"required\" validate:\"required,dive\"`" + ` -} - -type FhirJsonPatch struct { - Op string ` + "`json:\"op\" binding:\"required\" validate:\"required,oneof=add remove replace move copy test\"`" + ` - Path string ` + "`json:\"path\" binding:\"required\" validate:\"required\"`" + ` - Value interface{} ` + "`json:\"value,omitempty\"`" + ` - From string ` + "`json:\"from,omitempty\"`" + ` -} - -// ValidateFhirPatch validates the ` + data.Name + `PatchRequest with FHIR R4 patch rules -func (r *` + data.Name + `PatchRequest) ValidateFhirPatch() error { - if r.ID == "" { - return fmt.Errorf("resource ID is required for patch operation") - } - - if len(r.Patches) == 0 { - return fmt.Errorf("at least one patch operation is required") - } - - // Validate each patch operation - for i, patch := range r.Patches { - if patch.Path == "" { - return fmt.Errorf("patch[%d]: path is required", i) - } - - if patch.Op == "move" || patch.Op == "copy" { - if patch.From == "" { - return fmt.Errorf("patch[%d]: from is required for %s operation", i, patch.Op) - } - } - - if patch.Op != "remove" && patch.Op != "test" && patch.Value == nil { - return fmt.Errorf("patch[%d]: value is required for %s operation", i, patch.Op) - } - } - - // Path: ` + data.Category + ` - return nil -} - -// ToJSON converts struct to JSON string -func (r *` + data.Name + `PatchRequest) ToJSON() (string, error) { - data, err := json.Marshal(r) - return string(data), err -}` - } - - // Add validation helper functions - modelContent += ` - -// FHIR validation helper functions for Indonesian context -func isValidNIK(nik string) bool { - if len(nik) != 16 { - return false - } - - // Check if all characters are digits - matched, _ := regexp.MatchString("^[0-9]{16}$", nik) - return matched -} - -func isValidFhirDate(date string) bool { - // FHIR date format: YYYY, YYYY-MM, or YYYY-MM-DD - patterns := []string{ - "^[0-9]{4}$", // Year only - "^[0-9]{4}-[0-9]{2}$", // Year-Month - "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", // Full date - } - - for _, pattern := range patterns { - matched, _ := regexp.MatchString(pattern, date) - if matched { - return true - } - } - return false -} - -func isValidFhirDateTime(datetime string) bool { - // FHIR datetime format with timezone - pattern := "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?(Z|[+-][0-9]{2}:[0-9]{2})$" - matched, _ := regexp.MatchString(pattern, datetime) - return matched -} - -func isValidFhirID(id string) bool { - // FHIR ID: 1-64 characters, alphanumeric, dash, dot - if len(id) < 1 || len(id) > 64 { - return false - } - - pattern := "^[A-Za-z0-9\\-\\.]{1,64}$" - matched, _ := regexp.MatchString(pattern, id) - return matched -} - -// GetPathInfo returns information about the multi-level FHIR path -func GetFhirPathInfo() map[string]interface{} { - return map[string]interface{}{ - "fhir_resource": "` + data.FhirResource + `", - "path": "` + data.Category + `", - "depth": ` + fmt.Sprintf("%d", data.DirectoryDepth) + `, - "parts": []string{` + `"` + strings.Join(data.CategoryParts, `", "`) + `"` + `}, - "base_url": "https://api-satusehat-stg.dto.kemkes.go.id/fhir-r4/v1", - "profile_url": "https://fhir.kemkes.go.id/r4/StructureDefinition/` + data.FhirResource + `", - } -}` - - writeFile(filepath.Join(modelDir, data.NameLower+".go"), modelContent) -} - -// Continue with model generation, routes generation, and utility functions... -// [Model generation and other functions would continue here following the same pattern] - -func writeFile(filename, content string) { - if err := os.WriteFile(filename, []byte(content), 0644); err != nil { - fmt.Printf("āŒ Error creating file %s: %v\n", filename, err) - return - } - - fmt.Printf("āœ… Generated Satu Sehat FHIR file: %s\n", filename) -} diff --git a/tools/satusehat/generate-satusehat-handler.go b/tools/satusehat/generate-satusehat-handler.go deleted file mode 100644 index b643f50e..00000000 --- a/tools/satusehat/generate-satusehat-handler.go +++ /dev/null @@ -1,2118 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "regexp" - "strings" - "sync" - "text/template" - "time" - - "gopkg.in/yaml.v2" -) - -// ================== STRUCTURES FOR SEPARATION OF CONCERNS ================== -// HandlerValidation tracks validation and generation status with thread safety -type HandlerValidation struct { - ExistingFunctions map[string]bool - NewFunctions []string - UpdatedFiles []string - CreatedFiles []string - mu sync.Mutex // For thread-safe access to slices -} - -// DirectoryInfo represents directory structure for nested endpoints -type DirectoryInfo struct { - Path string - IsFile bool - Functions []FunctionInfo - Children map[string]*DirectoryInfo -} - -// FunctionInfo contains information about a function -type FunctionInfo struct { - Name string - Methods []string - Endpoint string - Config EndpointConfig -} - -// EndpointConfig contains configuration for an endpoint -type EndpointConfig struct { - Methods []string `yaml:"methods"` - GetPath string `yaml:"get_path"` - PostPath string `yaml:"post_path"` - PutPath string `yaml:"put_path"` - PatchPath string `yaml:"patch_path"` - DeletePath string `yaml:"delete_path"` - SearchPath string `yaml:"search_path"` - Model string `yaml:"model"` - ResponseModel string `yaml:"response_model"` - RequestModel string `yaml:"request_model"` - Description string `yaml:"description"` - Summary string `yaml:"summary"` - Tags []string `yaml:"tags"` - RequireAuth bool `yaml:"require_auth"` - CacheEnabled bool `yaml:"cache_enabled"` - CacheTTL int `yaml:"cache_ttl"` - FhirProfiles []string `yaml:"fhir_profiles"` - SearchParams []string `yaml:"search_params"` -} - -// GlobalConfig contains global configuration -type GlobalConfig struct { - ModuleName string `yaml:"module_name"` - OutputDir string `yaml:"output_dir"` - EnableSwagger bool `yaml:"enable_swagger"` - EnableLogging bool `yaml:"enable_logging"` - EnableMetrics bool `yaml:"enable_metrics"` - EnableAuth bool `yaml:"enable_auth"` - BaseURL string `yaml:"base_url"` - Version string `yaml:"version"` - Environment string `yaml:"environment"` - FhirVersion string `yaml:"fhir_version"` - ProfileURL string `yaml:"profile_url"` -} - -// ServiceConfig contains complete service configuration -type ServiceConfig struct { - Global GlobalConfig `yaml:"global,omitempty"` - Services map[string]Service `yaml:"services"` -} - -// Service represents a single service -type Service struct { - Name string `yaml:"name"` - Category string `yaml:"category"` - Package string `yaml:"package"` - Description string `yaml:"description"` - BaseURL string `yaml:"base_url"` - Timeout int `yaml:"timeout"` - RetryCount int `yaml:"retry_count"` - FhirResource string `yaml:"fhir_resource,omitempty"` - Validation ValidationConfig `yaml:"validation,omitempty"` - Auth AuthConfig `yaml:"authentication,omitempty"` - Endpoints map[string]EndpointGroup `yaml:"endpoints"` - Dependencies string `yaml:"dependencies,omitempty"` - Middleware string `yaml:"middleware,omitempty"` -} - -// ValidationConfig contains validation rules -type ValidationConfig struct { - EnableFhirValidation bool `yaml:"enable_fhir_validation"` - RequiredFields []string `yaml:"required_fields"` - CustomValidators []string `yaml:"custom_validators"` -} - -// AuthConfig contains authentication configuration -type AuthConfig struct { - Type string `yaml:"type"` - TokenURL string `yaml:"token_url"` - Scopes []string `yaml:"scopes"` -} - -// EndpointGroup represents a group of related endpoints - MODIFIED to match your YAML -type EndpointGroup struct { - Description string `yaml:"description"` - Basic map[string]interface{} `yaml:"basic"` - // Generated fields - HandlerFolder string - HandlerFile string - HandlerName string - Functions map[string]FunctionConfig -} - -// FunctionConfig contains individual function configuration -type FunctionConfig struct { - Methods []string `yaml:"methods"` - Path string `yaml:"path"` - Model string `yaml:"model"` - RoutesLink string `yaml:"routes_link"` - // Path for swagger documentation - GetPath string `yaml:"get_path"` - PostPath string `yaml:"post_path"` - PutPath string `yaml:"put_path"` - PatchPath string `yaml:"patch_path"` - DeletePath string `yaml:"delete_path"` - SearchPath string `yaml:"search_path"` - ResponseModel string `yaml:"response_model"` - RequestModel string `yaml:"request_model"` - Description string `yaml:"description"` - Summary string `yaml:"summary"` - Tags []string `yaml:"tags"` - RequireAuth bool `yaml:"require_auth"` - CacheEnabled bool `yaml:"cache_enabled"` - CacheTTL int `yaml:"cache_ttl"` - FhirProfiles []string `yaml:"fhir_profiles"` - SearchParams []string `yaml:"search_params"` -} - -// TemplateData contains data for template generation -type TemplateData struct { - ServiceName string - ServiceLower string - ServiceUpper string - Category string - CategoryParts []string - Package string - Description string - BaseURL string - Timeout int - RetryCount int - FhirResource string - Endpoints []EndpointData - Timestamp string - ModuleName string - HasValidator bool - HasLogger bool - HasSwagger bool - GlobalConfig GlobalConfig - ShouldGenerateStruct bool - ShouldGenerateConstructor bool - FunctionalArea string - HandlerName string - DirectoryDepth int -} - -// EndpointData contains endpoint-specific data -type EndpointData struct { - Name string - NameLower string - NameUpper string - NameCamel string - Methods []string - GetPath string - PostPath string - PutPath string - PatchPath string - DeletePath string - SearchPath string - Model string - ResponseModel string - RequestModel string - DataModel string - Description string - Summary string - Tags []string - HasGet bool - HasPost bool - HasPut bool - HasPatch bool - HasDelete bool - HasSearch bool - RequireAuth bool - CacheEnabled bool - CacheTTL int - PathParams []string - ModelPackage string - FhirResource string - CategoryPath string -} - -// ================== MAIN GENERATION FUNCTIONS ================== -// generateHandlerWithValidation generates handler with separation of concerns -func generateHandlerWithValidation( - ctx context.Context, - serviceName string, - svc Service, - gc GlobalConfig, -) error { - validation := &HandlerValidation{ - ExistingFunctions: make(map[string]bool), - NewFunctions: []string{}, - UpdatedFiles: []string{}, - CreatedFiles: []string{}, - } - - baseDir := gc.OutputDir - var wg sync.WaitGroup - errChan := make(chan error, len(svc.Endpoints)) - - // Debug: Print service information - fmt.Printf("šŸ” Debug: Processing service %s with %d endpoints\n", serviceName, len(svc.Endpoints)) - - // Process each endpoint group concurrently - for groupName, grp := range svc.Endpoints { - wg.Add(1) - go func(groupName string, grp EndpointGroup) { - defer wg.Done() - select { - case <-ctx.Done(): - errChan <- ctx.Err() - return - default: - // Debug: Print endpoint group information - fmt.Printf("šŸ” Debug: Processing endpoint group %s with %d functions\n", groupName, len(grp.Functions)) - - // Create handler folder structure - categoryParts := strings.Split(svc.Category, "/") - handlerDirParts := append([]string{baseDir}, categoryParts...) - handlerDir := filepath.Join(handlerDirParts...) - if err := os.MkdirAll(handlerDir, 0755); err != nil { - errChan <- fmt.Errorf("mkdir %s: %w", handlerDir, err) - return - } - - // Generate methods file with clear naming - methodsFileName := fmt.Sprintf("%s.go", strings.ToLower(svc.Package)) - methodsFilePath := filepath.Join(handlerDir, methodsFileName) - - // Check if methods file exists - fileExists := false - if _, err := os.Stat(methodsFilePath); err == nil { - fileExists = true - } - - if !fileExists { - // Create new methods file - err := createMethodsFileFromConfig( - methodsFilePath, svc, grp, gc, groupName, - ) - if err != nil { - errChan <- fmt.Errorf("create methods file %s: %w", methodsFilePath, err) - return - } - validation.mu.Lock() - validation.CreatedFiles = append(validation.CreatedFiles, methodsFilePath) - validation.mu.Unlock() - fmt.Printf("āœ… Created methods file: %s\n", methodsFilePath) - } else { - // Update existing methods file with new functions only - err := updateExistingMethodsFile( - methodsFilePath, svc, grp, gc, groupName, validation, - ) - if err != nil { - errChan <- fmt.Errorf("update methods file %s: %w", methodsFilePath, err) - return - } - validation.mu.Lock() - if len(validation.NewFunctions) > 0 { - validation.UpdatedFiles = append(validation.UpdatedFiles, methodsFilePath) - } - validation.mu.Unlock() - fmt.Printf("āœ… Updated methods file: %s\n", methodsFilePath) - } - } - }(groupName, grp) - } - - // Wait for all goroutines to complete - wg.Wait() - close(errChan) - - // Check for errors - for err := range errChan { - if err != nil { - return err - } - } - - // Generate models (sequential for simplicity) - if err := generateModels(ctx, svc, gc); err != nil { - return fmt.Errorf("generate models: %w", err) - } - - // Generate routes - if err := generateRoutes(serviceName, svc, gc); err != nil { - return fmt.Errorf("generate routes: %w", err) - } - - return nil -} - -// createMethodsFileFromConfig creates new methods file from configuration -func createMethodsFileFromConfig( - filePath string, - svc Service, - grp EndpointGroup, - gc GlobalConfig, - functionalArea string, -) error { - // Debug: Print function information - fmt.Printf("šŸ” Debug: Creating methods file with %d functions\n", len(grp.Functions)) - for funcName, funcConfig := range grp.Functions { - fmt.Printf("šŸ” Debug: Function %s has methods: %v\n", funcName, funcConfig.Methods) - } - - // Collect all functions for this group - var allEndpoints []EndpointData - for fname, fcfg := range grp.Functions { - td := processFunctionData(svc, grp, fname, fcfg, gc) - allEndpoints = append(allEndpoints, td.Endpoints...) - } - - templateData := TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - CategoryParts: strings.Split(svc.Category, "/"), - Package: svc.Package, - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - FhirResource: svc.FhirResource, - Endpoints: allEndpoints, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - ShouldGenerateStruct: false, // NEVER generate struct in methods file - ShouldGenerateConstructor: false, // NEVER generate constructor in methods file - FunctionalArea: functionalArea, - HandlerName: grp.HandlerName, - DirectoryDepth: len(strings.Split(svc.Category, "/")), - } - - return createMethodsFile(filePath, templateData) -} - -// updateExistingMethodsFile updates existing methods file with new functions -func updateExistingMethodsFile( - filePath string, - svc Service, - grp EndpointGroup, - gc GlobalConfig, - functionalArea string, - validation *HandlerValidation, -) error { - existingContent, err := ioutil.ReadFile(filePath) - if err != nil { - return err - } - content := string(existingContent) - - // Debug: Print function information - fmt.Printf("šŸ” Debug: Updating methods file with %d functions\n", len(grp.Functions)) - for funcName, funcConfig := range grp.Functions { - fmt.Printf("šŸ” Debug: Function %s has methods: %v\n", funcName, funcConfig.Methods) - } - - // Check for existing functions and collect new ones - var newEndpoints []EndpointData - for fname, fcfg := range grp.Functions { - functionExists := false - for _, method := range fcfg.Methods { - funcName := generateFunctionName(fname, method) - sig := fmt.Sprintf("func (h *%sHandler) %s", grp.HandlerName, funcName) - if strings.Contains(content, sig) { - fmt.Printf("āš ļø Skip existing: %s (%s)\n", fname, funcName) - functionExists = true - break - } - } - if functionExists { - continue - } - - td := processFunctionData(svc, grp, fname, fcfg, gc) - newEndpoints = append(newEndpoints, td.Endpoints...) - validation.mu.Lock() - validation.NewFunctions = append(validation.NewFunctions, fname) - validation.mu.Unlock() - fmt.Printf("āœ… Will add: %s\n", fname) - } - - if len(newEndpoints) == 0 { - fmt.Printf("ā­ļø No new functions to add\n") - return nil - } - - // Generate new functions using methods-only template - templateData := TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - CategoryParts: strings.Split(svc.Category, "/"), - Package: svc.Package, - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - FhirResource: svc.FhirResource, - Endpoints: newEndpoints, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - ShouldGenerateStruct: false, // NEVER - ShouldGenerateConstructor: false, // NEVER - FunctionalArea: functionalArea, - HandlerName: grp.HandlerName, - DirectoryDepth: len(strings.Split(svc.Category, "/")), - } - - newFunctions, err := generateNewMethodsOnly(templateData) - if err != nil { - return err - } - - // Merge content - mergedContent := mergeGoFileContent(content, newFunctions) - return ioutil.WriteFile(filePath, []byte(mergedContent), 0644) -} - -// processFunctionData processes individual function configuration -func processFunctionData( - svc Service, - grp EndpointGroup, - fname string, - fcfg FunctionConfig, - gc GlobalConfig, -) TemplateData { - fmt.Printf("šŸ” Debug: Processing function %s with methods: %v\n", fname, fcfg.Methods) - - ed := EndpointData{ - Name: toCamelCase(fname), - NameLower: strings.ToLower(fname), - NameUpper: strings.ToUpper(fname), - NameCamel: toCamelCase(fname), - Methods: fcfg.Methods, - GetPath: fcfg.GetPath, - PostPath: fcfg.PostPath, - PutPath: fcfg.PutPath, - PatchPath: fcfg.PatchPath, - DeletePath: fcfg.DeletePath, - SearchPath: fcfg.SearchPath, - Model: fcfg.Model, - ResponseModel: fcfg.ResponseModel, - RequestModel: fcfg.Model, - DataModel: strings.Replace(fcfg.ResponseModel, "Response", "Data", 1), - Description: fcfg.Description, - Summary: fcfg.Summary, - Tags: fcfg.Tags, - RequireAuth: fcfg.RequireAuth, - CacheEnabled: fcfg.CacheEnabled, - CacheTTL: fcfg.CacheTTL, - PathParams: extractPathParams(fcfg.GetPath), - ModelPackage: svc.Package, - FhirResource: svc.FhirResource, - CategoryPath: svc.Category, - } - - // Set method flags - for _, m := range fcfg.Methods { - switch strings.ToUpper(m) { - case "GET": - ed.HasGet = true - case "POST": - ed.HasPost = true - case "PUT": - ed.HasPut = true - case "PATCH": - ed.HasPatch = true - case "DELETE": - ed.HasDelete = true - case "SEARCH": - ed.HasSearch = true - } - } - - fmt.Printf("šŸ” Debug: Function %s flags - Get:%t, Post:%t, Put:%t, Patch:%t, Delete:%t, Search:%t\n", - fname, ed.HasGet, ed.HasPost, ed.HasPut, ed.HasPatch, ed.HasDelete, ed.HasSearch) - - return TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - CategoryParts: strings.Split(svc.Category, "/"), - Package: svc.Package, - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - FhirResource: svc.FhirResource, - Endpoints: []EndpointData{ed}, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - HandlerName: grp.HandlerName, - DirectoryDepth: len(strings.Split(svc.Category, "/")), - } -} - -// generateModels generates model files for the service -func generateModels(ctx context.Context, svc Service, gc GlobalConfig) error { - // Create model directory structure - categoryParts := strings.Split(svc.Category, "/") - modelDirParts := append([]string{"internal", "models"}, categoryParts...) - modelDir := filepath.Join(modelDirParts...) - if err := os.MkdirAll(modelDir, 0755); err != nil { - return fmt.Errorf("mkdir %s: %w", modelDir, err) - } - - modelFilePath := filepath.Join(modelDir, strings.ToLower(svc.Package)+".go") - - // Check if model file exists - if _, err := os.Stat(modelFilePath); err == nil { - fmt.Printf("ā­ļø Model file already exists: %s\n", modelFilePath) - return nil - } - - // Check if context was cancelled - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - - // Generate model content - templateData := TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - CategoryParts: categoryParts, - Package: svc.Package, - FhirResource: svc.FhirResource, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - DirectoryDepth: len(categoryParts), - } - - tmpl := template.New("model") - tmpl, err := tmpl.Parse(modelTemplate) - if err != nil { - return err - } - - file, err := os.Create(modelFilePath) - if err != nil { - return err - } - defer file.Close() - - err = tmpl.Execute(file, templateData) - if err != nil { - return err - } - - fmt.Printf("āœ… Created model file: %s\n", modelFilePath) - return nil -} - -// generateRoutes generates routes for the service -func generateRoutes(serviceName string, svc Service, gc GlobalConfig) error { - routesFilePath := filepath.Join(gc.OutputDir, "routes.go") - if _, err := os.Stat(routesFilePath); os.IsNotExist(err) { - // Create routes file if it doesn't exist - if err := os.MkdirAll(filepath.Dir(routesFilePath), 0755); err != nil { - return fmt.Errorf("mkdir %s: %w", filepath.Dir(routesFilePath), err) - } - - // Create a basic routes file - routesContent := fmt.Sprintf(`package handlers -import ( - "%s/internal/config" - "%s/pkg/logger" - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" -) - -// SetupRoutes sets up all routes for the application -func SetupRoutes(r *gin.Engine, cfg *config.Config) error { - // Setup routes will be added here - return nil -} -`, gc.ModuleName, gc.ModuleName) - - if err := ioutil.WriteFile(routesFilePath, []byte(routesContent), 0644); err != nil { - return fmt.Errorf("create routes file: %w", err) - } - fmt.Printf("āœ… Created routes file: %s\n", routesFilePath) - } - - routesContent, err := ioutil.ReadFile(routesFilePath) - if err != nil { - return fmt.Errorf("read routes file: %w", err) - } - routesContentStr := string(routesContent) - - // Check if routes are already registered - if strings.Contains(routesContentStr, fmt.Sprintf("Register%sRoutes", svc.Name)) { - fmt.Printf("āš ļø Routes for %s already registered\n", svc.Name) - return nil - } - - var imports []string - var allRoutes []string - - // Track handler folders that have been imported - processedFolders := make(map[string]bool) - - for groupName, grp := range svc.Endpoints { - // Import based on handler folder - if !processedFolders[grp.HandlerFolder] { - importLine := fmt.Sprintf("\t%sHandlers \"%s/internal/handlers/%s\"", - grp.HandlerFolder, gc.ModuleName, grp.HandlerFolder) - imports = append(imports, importLine) - processedFolders[grp.HandlerFolder] = true - fmt.Printf("āœ… Added import: %sHandlers\n", grp.HandlerFolder) - } - - var routesCode strings.Builder - - // Use groupName for comment and identifier - routesCode.WriteString(fmt.Sprintf("\n\t// %s (%s) routes\n", grp.Description, groupName)) - - // Handler instantiation using HandlerName from config - routesCode.WriteString(fmt.Sprintf("\t%sHandler := %sHandlers.New%sHandler(%sHandlers.%sHandlerConfig{\n", - strings.ToLower(grp.HandlerName), - grp.HandlerFolder, - grp.HandlerName, - grp.HandlerFolder, - grp.HandlerName)) - routesCode.WriteString("\t\tConfig: cfg,\n") - routesCode.WriteString("\t\tLogger: logger.Default(),\n") - routesCode.WriteString("\t\tValidator: validator.New(),\n") - routesCode.WriteString("\t})\n") - - // Use groupName for route group path - routesCode.WriteString(fmt.Sprintf("\t%sGroup := r.Group(\"/%s\")\n", - strings.ToLower(grp.HandlerName), groupName)) - - // Process functions - for fname, fcfg := range grp.Functions { - td := processFunctionData(svc, grp, fname, fcfg, gc) - for _, endpoint := range td.Endpoints { - handlerVar := strings.ToLower(grp.HandlerName) + "Handler" - groupVar := strings.ToLower(grp.HandlerName) + "Group" - - // Loop through methods and use specific routes - for _, method := range fcfg.Methods { - var cleanPath string - - // Choose path based on method - switch strings.ToUpper(method) { - case "GET": - cleanPath = fcfg.GetPath - case "POST": - cleanPath = fcfg.PostPath - case "PUT": - cleanPath = fcfg.PutPath - case "PATCH": - cleanPath = fcfg.PatchPath - case "DELETE": - cleanPath = fcfg.DeletePath - case "SEARCH": - cleanPath = fcfg.SearchPath - default: - fmt.Printf("āš ļø Unsupported HTTP method: %s for function %s\n", method, fname) - continue - } - - // Fallback to path if specific route is empty - if cleanPath == "" { - cleanPath = fcfg.Path - } - - // Clean path - remove groupName prefix if exists - if strings.HasPrefix(cleanPath, "/"+groupName) { - cleanPath = strings.TrimPrefix(cleanPath, "/"+groupName) - } - if cleanPath == "" { - cleanPath = "/" - } - - // Generate route based on method - switch strings.ToUpper(method) { - case "GET": - routesCode.WriteString(fmt.Sprintf("\t%s.GET(\"%s\", %s.Get%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - case "POST": - routesCode.WriteString(fmt.Sprintf("\t%s.POST(\"%s\", %s.Create%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - case "PUT": - routesCode.WriteString(fmt.Sprintf("\t%s.PUT(\"%s\", %s.Update%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - case "PATCH": - routesCode.WriteString(fmt.Sprintf("\t%s.PATCH(\"%s\", %s.Patch%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - case "DELETE": - routesCode.WriteString(fmt.Sprintf("\t%s.DELETE(\"%s\", %s.Delete%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - case "SEARCH": - routesCode.WriteString(fmt.Sprintf("\t%s.GET(\"%s\", %s.Search%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - } - } - } - } - - allRoutes = append(allRoutes, routesCode.String()) - } - - // Insert imports after existing imports - if len(imports) > 0 { - importSection := strings.Join(imports, "\n") + "\n" - // Find position after import section - importMarker := "import (" - if strings.Contains(routesContentStr, importMarker) { - importIndex := strings.Index(routesContentStr, importMarker) - closingParenIndex := strings.Index(routesContentStr[importIndex:], "\n)") + importIndex - if closingParenIndex > importIndex { - routesContentStr = routesContentStr[:closingParenIndex+2] + "\n" + importSection + routesContentStr[closingParenIndex+2:] - } - } - } - - // Find and insert routes - setupRoutesMarker := "func SetupRoutes" - if !strings.Contains(routesContentStr, setupRoutesMarker) { - return fmt.Errorf("SetupRoutes function not found in routes.go") - } - - funcStart := strings.Index(routesContentStr, setupRoutesMarker) - funcBodyStart := strings.Index(routesContentStr[funcStart:], "{") + funcStart + 1 - newRoutesContent := routesContentStr[:funcBodyStart+1] + "\n" + strings.Join(allRoutes, "\n") + "\n" + routesContentStr[funcBodyStart+1:] - - err = ioutil.WriteFile(routesFilePath, []byte(newRoutesContent), 0644) - if err != nil { - return fmt.Errorf("write updated routes file: %w", err) - } - - fmt.Printf("āœ… Updated routes file with %s routes\n", svc.Name) - return nil -} - -// ================== HELPER FUNCTIONS ================== -// generateFunctionName generates function name based on endpoint and method -func generateFunctionName(endpointName, method string) string { - switch strings.ToUpper(method) { - case "GET": - return fmt.Sprintf("Get%s", strings.Title(endpointName)) - case "POST": - return fmt.Sprintf("Create%s", strings.Title(endpointName)) - case "PUT": - return fmt.Sprintf("Update%s", strings.Title(endpointName)) - case "DELETE": - return fmt.Sprintf("Delete%s", strings.Title(endpointName)) - case "PATCH": - return fmt.Sprintf("Patch%s", strings.Title(endpointName)) - case "SEARCH": - return fmt.Sprintf("Search%s", strings.Title(endpointName)) - default: - return fmt.Sprintf("%s%s", strings.Title(method), strings.Title(endpointName)) - } -} - -// extractPathParams extracts path parameters from URL path -func extractPathParams(path string) []string { - var params []string - for _, part := range strings.Split(path, "/") { - if strings.HasPrefix(part, ":") { - params = append(params, strings.TrimPrefix(part, ":")) - } - } - return params -} - -// toCamelCase converts string to CamelCase -func toCamelCase(s string) string { - parts := strings.FieldsFunc(s, func(r rune) bool { - return r == '_' || r == '-' || r == ' ' - }) - for i, p := range parts { - parts[i] = strings.Title(strings.ToLower(p)) - } - return strings.Join(parts, "") -} - -// createMethodsFile creates methods file using template -func createMethodsFile(filePath string, templateData TemplateData) error { - if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { - return err - } - - // Debug: Print template data - fmt.Printf("šŸ” Debug: Creating methods file with %d endpoints\n", len(templateData.Endpoints)) - for _, endpoint := range templateData.Endpoints { - fmt.Printf("šŸ” Debug: Endpoint %s has methods: %v\n", endpoint.Name, endpoint.Methods) - } - - // Create template with basic functions - tmpl := template.New("methods").Funcs(template.FuncMap{ - "title": strings.Title, - "upper": strings.ToUpper, - "lower": strings.ToLower, - "index": func(slice []string, i int) string { - if i >= 0 && i < len(slice) { - return slice[i] - } - return "" - }, - "add": func(a, b int) int { - return a + b - }, - "sub": func(a, b int) int { - return a - b - }, - }) - - tmpl, err := tmpl.Parse(handlerTemplate) - if err != nil { - return fmt.Errorf("parse template: %w", err) - } - - file, err := os.Create(filePath) - if err != nil { - return err - } - defer file.Close() - - return tmpl.Execute(file, templateData) -} - -// generateNewMethodsOnly generates only new methods for existing files -func generateNewMethodsOnly(templateData TemplateData) (string, error) { - // Debug: Print template data - fmt.Printf("šŸ” Debug: Generating new methods with %d endpoints\n", len(templateData.Endpoints)) - for _, endpoint := range templateData.Endpoints { - fmt.Printf("šŸ” Debug: Endpoint %s has methods: %v\n", endpoint.Name, endpoint.Methods) - } - - // Create template with basic functions only - tmpl := template.New("newMethods").Funcs(template.FuncMap{ - "title": strings.Title, - "upper": strings.ToUpper, - "lower": strings.ToLower, - "index": func(slice []string, i int) string { - if i >= 0 && i < len(slice) { - return slice[i] - } - return "" - }, - }) - - tmpl, err := tmpl.Parse(methodsOnlyTemplate) - if err != nil { - return "", err - } - - var result strings.Builder - err = tmpl.Execute(&result, templateData) - if err != nil { - return "", err - } - - return result.String(), nil -} - -// mergeGoFileContent merges new functions into existing file -func mergeGoFileContent(existingContent, newFunctions string) string { - // Find last closing brace - re := regexp.MustCompile(`}\s*$`) - lastBraceIndex := re.FindStringIndex(existingContent) - if lastBraceIndex == nil { - return existingContent + "\n" + newFunctions - } - - before := existingContent[:lastBraceIndex[0]] - after := existingContent[lastBraceIndex[0]:] - - return before + "\n" + newFunctions + "\n" + after -} - -// ================== MAIN FUNCTION ================== -func main() { - if len(os.Args) < 2 { - printUsage() - os.Exit(1) - } - - configFile := os.Args[1] - var targetService string - if len(os.Args) > 2 { - targetService = os.Args[2] - } - - config, err := loadConfig(configFile) - if err != nil { - fmt.Printf("Error loading config: %v\n", err) - os.Exit(1) - } - - fmt.Println("šŸš€ Starting Handler Generation with Validation...") - fmt.Printf("šŸ“ Config file: %s\n", configFile) - if targetService != "" { - fmt.Printf("šŸŽÆ Target service: %s\n", targetService) - } - - // Create context with cancellation for graceful shutdown - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // Worker pool for concurrent processing - type result struct { - serviceName string - err error - } - results := make(chan result) - jobs := make(chan string, len(config.Services)) - - // Start worker pool - var wg sync.WaitGroup - numWorkers := 4 // Adjust based on your system - for i := 0; i < numWorkers; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for serviceName := range jobs { - select { - case <-ctx.Done(): - return - case results <- result{ - serviceName: serviceName, - err: generateHandlerWithValidation(ctx, serviceName, config.Services[serviceName], config.Global), - }: - } - } - }() - } - - // Send jobs to workers - go func() { - for serviceName := range config.Services { - if targetService != "" && serviceName != targetService { - continue - } - select { - case <-ctx.Done(): - break - case jobs <- serviceName: - } - } - close(jobs) - }() - - // Collect results - go func() { - wg.Wait() - close(results) - }() - - // Process results - generated := 0 - errors := 0 - for res := range results { - if res.err != nil { - fmt.Printf("āŒ Error generating handler for %s: %v\n", res.serviceName, res.err) - errors++ - continue - } - generated++ - } - - // Summary - fmt.Println("\nšŸ“Š Generation Summary:") - fmt.Printf("āœ… Successfully processed: %d services\n", generated) - if errors > 0 { - fmt.Printf("āŒ Failed: %d services\n", errors) - } - if generated > 0 { - fmt.Println("šŸŽ‰ Generation completed successfully!") - } -} - -// loadConfig loads configuration from YAML file -func loadConfig(filename string) (*ServiceConfig, error) { - data, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - - var config ServiceConfig - err = yaml.Unmarshal(data, &config) - if err != nil { - return nil, err - } - - // Set defaults - if config.Global.ModuleName == "" { - config.Global.ModuleName = "api-service" - } - if config.Global.OutputDir == "" { - config.Global.OutputDir = "internal/handlers" - } - - // Debug: Print loaded configuration - fmt.Printf("šŸ” Debug: Loaded %d services\n", len(config.Services)) - - // Process endpoint groups to add missing handler information - for serviceName, service := range config.Services { - fmt.Printf("šŸ” Debug: Processing service %s with %d endpoints\n", serviceName, len(service.Endpoints)) - - for endpointName, endpointGroup := range service.Endpoints { - fmt.Printf("šŸ” Debug: Processing endpoint %s with %d basic configs\n", endpointName, len(endpointGroup.Basic)) - - // Generate handler information based on service and endpoint names - endpointGroup.HandlerFolder = strings.ToLower(service.Package) - endpointGroup.HandlerFile = fmt.Sprintf("%s.go", strings.ToLower(service.Package)) - endpointGroup.HandlerName = toCamelCase(serviceName) - - // Convert basic map to functions map - endpointGroup.Functions = make(map[string]FunctionConfig) - - // Debug: Print the structure of basic configs - fmt.Printf("šŸ” Debug: Processing basic config for endpoint %s\n", endpointName) - - // Treat the entire Basic map as a single function configuration - funcConfig := FunctionConfig{} - - // Process each field in the basic config - for key, value := range endpointGroup.Basic { - keyStr := fmt.Sprintf("%v", key) - processYAMLField(keyStr, value, &funcConfig) - } - - fmt.Printf("šŸ” Debug: Final function config for %s: methods=%v, get_path=%s\n", - endpointName, funcConfig.Methods, funcConfig.GetPath) - - endpointGroup.Functions["basic"] = funcConfig - - // Update the service endpoints - service.Endpoints[endpointName] = endpointGroup - } - config.Services[serviceName] = service - } - - return &config, nil -} - -// processYAMLField processes a single YAML field and updates the function config -func processYAMLField(key string, value interface{}, funcConfig *FunctionConfig) { - fmt.Printf("šŸ” Debug: Processing key %s with value %v (type: %T)\n", key, value, value) - - switch key { - case "methods": - if methods, ok := value.([]interface{}); ok { - for _, method := range methods { - if methodStr, ok := method.(string); ok { - funcConfig.Methods = append(funcConfig.Methods, methodStr) - fmt.Printf("šŸ” Debug: Added method %s\n", methodStr) - } - } - } - case "get_path": - if path, ok := value.(string); ok { - funcConfig.GetPath = path - fmt.Printf("šŸ” Debug: Set get_path to %s\n", path) - } - case "post_path": - if path, ok := value.(string); ok { - funcConfig.PostPath = path - fmt.Printf("šŸ” Debug: Set post_path to %s\n", path) - } - case "put_path": - if path, ok := value.(string); ok { - funcConfig.PutPath = path - fmt.Printf("šŸ” Debug: Set put_path to %s\n", path) - } - case "patch_path": - if path, ok := value.(string); ok { - funcConfig.PatchPath = path - fmt.Printf("šŸ” Debug: Set patch_path to %s\n", path) - } - case "delete_path": - if path, ok := value.(string); ok { - funcConfig.DeletePath = path - fmt.Printf("šŸ” Debug: Set delete_path to %s\n", path) - } - case "search_path": - if path, ok := value.(string); ok { - funcConfig.SearchPath = path - fmt.Printf("šŸ” Debug: Set search_path to %s\n", path) - } - case "model": - if model, ok := value.(string); ok { - funcConfig.Model = model - fmt.Printf("šŸ” Debug: Set model to %s\n", model) - } - case "response_model": - if model, ok := value.(string); ok { - funcConfig.ResponseModel = model - fmt.Printf("šŸ” Debug: Set response_model to %s\n", model) - } - case "description": - if desc, ok := value.(string); ok { - funcConfig.Description = desc - fmt.Printf("šŸ” Debug: Set description to %s\n", desc) - } - case "summary": - if summary, ok := value.(string); ok { - funcConfig.Summary = summary - fmt.Printf("šŸ” Debug: Set summary to %s\n", summary) - } - case "tags": - if tags, ok := value.([]interface{}); ok { - for _, tag := range tags { - if tagStr, ok := tag.(string); ok { - funcConfig.Tags = append(funcConfig.Tags, tagStr) - fmt.Printf("šŸ” Debug: Added tag %s\n", tagStr) - } - } - } - case "require_auth": - if auth, ok := value.(bool); ok { - funcConfig.RequireAuth = auth - fmt.Printf("šŸ” Debug: Set require_auth to %t\n", auth) - } - case "cache_enabled": - if cache, ok := value.(bool); ok { - funcConfig.CacheEnabled = cache - fmt.Printf("šŸ” Debug: Set cache_enabled to %t\n", cache) - } - case "cache_ttl": - if ttl, ok := value.(int); ok { - funcConfig.CacheTTL = ttl - fmt.Printf("šŸ” Debug: Set cache_ttl to %d\n", ttl) - } - case "fhir_profiles": - if profiles, ok := value.([]interface{}); ok { - for _, profile := range profiles { - if profileStr, ok := profile.(string); ok { - funcConfig.FhirProfiles = append(funcConfig.FhirProfiles, profileStr) - fmt.Printf("šŸ” Debug: Added fhir_profile %s\n", profileStr) - } - } - } - case "search_params": - if params, ok := value.([]interface{}); ok { - for _, param := range params { - if paramStr, ok := param.(string); ok { - funcConfig.SearchParams = append(funcConfig.SearchParams, paramStr) - fmt.Printf("šŸ” Debug: Added search_param %s\n", paramStr) - } - } - } - default: - fmt.Printf("āš ļø Debug: Unknown key %s\n", key) - } -} - -// printUsage prints usage information -func printUsage() { - fmt.Println("Satu Sehat FHIR Handler Generator") - fmt.Println() - fmt.Println("Usage:") - fmt.Println(" go run generate-satusehat-handler.go [service-name]") - fmt.Println() - fmt.Println("Examples:") - fmt.Println(" go run generate-satusehat-handler.go services-config-satusehat.yaml") - fmt.Println(" go run generate-satusehat-handler.go services-config-satusehat.yaml patient") -} - -// ================== TEMPLATES ================== -const handlerTemplate = `// Package {{.Package}} handles {{.HandlerName}} services -// Generated on: {{.Timestamp}} -package {{.Package}} - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strconv" - "strings" - "time" - "{{.ModuleName}}/internal/config" - "{{.ModuleName}}/internal/models/{{.Package}}" - "{{.ModuleName}}/pkg/logger" - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" - "github.com/google/uuid" -) - -// {{.HandlerName}} handles {{.HandlerName}} services -type {{.HandlerName}} struct { - service interface{} - validator *validator.Validate - logger logger.Logger - config *config.Config -} - -// {{.HandlerName}}Config contains configuration for {{.HandlerName}} -type {{.HandlerName}}Config struct { - Config *config.Config - Logger logger.Logger - Validator *validator.Validate -} - -// New{{.HandlerName}} creates a new {{.HandlerName}} -func New{{.HandlerName}}(cfg {{.HandlerName}}Config) *{{.HandlerName}} { - return &{{.HandlerName}}{ - service: nil, // Initialize with appropriate service - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.Config, - } -} - -{{range .Endpoints}} -{{if .HasPost}} -// Create{{.Name}} creates a new resource -// @Summary Create a new resource -// @Description Create a new resource in the system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json -// @Param Authorization header string true "Bearer token" -// @Param request body models.{{.Name}}CreateRequest true "Resource creation request" -// @Success 201 {object} models.{{.Name}}Response "Resource created successfully" -// @Failure 400 {object} models.ErrorResponse "Bad request" -// @Failure 401 {object} models.ErrorResponse "Unauthorized" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router {{.PostPath}} [post] -func (h *{{$.HandlerName}}) Create{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PostPath}}", - }) - {{end}} - - // Bind and validate request body - var req models.{{.Name}}CreateRequest - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // TODO: Implement service call - // Example: resp, err := h.service.CreateResource(ctx, req) - - // For now, return a mock response - response := models.{{.Name}}Response{ - Status: "success", - Message: "{{.Name}} created successfully", - RequestID: requestID, - Data: nil, // Replace with actual data - } - - {{if $.HasLogger}} - h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusCreated, response) -} -{{end}} -{{if .HasGet}} -// Get{{.Name}} retrieves a specific resource by ID -// @Summary Get a specific resource by ID -// @Description Retrieve a specific resource from the system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json -// @Param Authorization header string true "Bearer token" -// @Param id path string true "Resource ID" -// @Success 200 {object} models.{{.Name}}Response "Resource retrieved successfully" -// @Failure 400 {object} models.ErrorResponse "Bad request" -// @Failure 401 {object} models.ErrorResponse "Unauthorized" -// @Failure 404 {object} models.ErrorResponse "Resource not found" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router {{.GetPath}} [get] -func (h *{{$.HandlerName}}) Get{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - id := c.Param("id") - if id == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter id", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Missing required parameter id", - RequestID: requestID, - }) - return - } - - {{if $.HasLogger}} - h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.GetPath}}", - "id": id, - }) - {{end}} - - // TODO: Implement service call - // Example: resp, err := h.service.GetResource(ctx, id) - - // For now, return a mock response - response := models.{{.Name}}Response{ - Status: "success", - Message: "{{.Name}} retrieved successfully", - RequestID: requestID, - Data: nil, // Replace with actual data - } - - {{if $.HasLogger}} - h.logger.Info("Successfully retrieved {{.Name}}", map[string]interface{}{ - "request_id": requestID, - "id": id, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{if .HasPut}} -// Update{{.Name}} updates an existing resource -// @Summary Update an existing resource -// @Description Update an existing resource in the system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json -// @Param Authorization header string true "Bearer token" -// @Param id path string true "Resource ID" -// @Param request body models.{{.Name}}UpdateRequest true "Resource update request" -// @Success 200 {object} models.{{.Name}}Response "Resource updated successfully" -// @Failure 400 {object} models.ErrorResponse "Bad request" -// @Failure 401 {object} models.ErrorResponse "Unauthorized" -// @Failure 404 {object} models.ErrorResponse "Resource not found" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router {{.PutPath}} [put] -func (h *{{$.HandlerName}}) Update{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - id := c.Param("id") - if id == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter id", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Missing required parameter id", - RequestID: requestID, - }) - return - } - - {{if $.HasLogger}} - h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PutPath}}", - "id": id, - }) - {{end}} - - // Bind and validate request body - var req models.{{.Name}}UpdateRequest - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // TODO: Implement service call - // Example: resp, err := h.service.UpdateResource(ctx, id, req) - - // For now, return a mock response - response := models.{{.Name}}Response{ - Status: "success", - Message: "{{.Name}} updated successfully", - RequestID: requestID, - Data: nil, // Replace with actual data - } - - {{if $.HasLogger}} - h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{ - "request_id": requestID, - "id": id, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{if .HasDelete}} -// Delete{{.Name}} deletes a resource -// @Summary Delete a resource -// @Description Delete a resource from the system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json -// @Param Authorization header string true "Bearer token" -// @Param id path string true "Resource ID" -// @Success 200 {object} models.SuccessResponse "Resource deleted successfully" -// @Failure 400 {object} models.ErrorResponse "Bad request" -// @Failure 401 {object} models.ErrorResponse "Unauthorized" -// @Failure 404 {object} models.ErrorResponse "Resource not found" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router {{.DeletePath}} [delete] -func (h *{{$.HandlerName}}) Delete{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - id := c.Param("id") - if id == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter id", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Missing required parameter id", - RequestID: requestID, - }) - return - } - - {{if $.HasLogger}} - h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.DeletePath}}", - "id": id, - }) - {{end}} - - // TODO: Implement service call - // Example: err := h.service.DeleteResource(ctx, id) - - // For now, return a mock response - response := models.SuccessResponse{ - Status: "success", - Message: "{{.Name}} deleted successfully", - RequestID: requestID, - } - - {{if $.HasLogger}} - h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{ - "request_id": requestID, - "id": id, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{if .HasSearch}} -// Search{{.Name}} searches for resources -// @Summary Search for resources -// @Description Search for resources in the system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json -// @Param Authorization header string true "Bearer token" -// @Param query query string false "Search query" -// @Param limit query int false "Limit results" default(10) -// @Param offset query int false "Offset results" default(0) -// @Success 200 {object} models.{{.Name}}SearchResponse "Search results" -// @Failure 400 {object} models.ErrorResponse "Bad request" -// @Failure 401 {object} models.ErrorResponse "Unauthorized" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router {{.SearchPath}} [get] -func (h *{{$.HandlerName}}) Search{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - // Get query parameters - query := c.Query("query") - limit := 10 - if l := c.Query("limit"); l != "" { - if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 { - limit = parsed - } - } - offset := 0 - if o := c.Query("offset"); o != "" { - if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 { - offset = parsed - } - } - - {{if $.HasLogger}} - h.logger.Info("Processing Search{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.SearchPath}}", - "query": query, - "limit": limit, - "offset": offset, - }) - {{end}} - - // TODO: Implement service call - // Example: resp, err := h.service.SearchResources(ctx, query, limit, offset) - - // For now, return a mock response - response := models.{{.Name}}SearchResponse{ - Status: "success", - Message: "Search completed successfully", - RequestID: requestID, - Data: models.SearchData{ - Results: []interface{}{}, // Replace with actual results - Total: 0, - Limit: limit, - Offset: offset, - }, - } - - {{if $.HasLogger}} - h.logger.Info("Successfully completed search", map[string]interface{}{ - "request_id": requestID, - "query": query, - "results": len(response.Data.Results), - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{end}}` - -const methodsOnlyTemplate = `{{range .Endpoints}} -{{if .HasPost}} -// Create{{.Name}} creates a new resource -func (h *{{$.HandlerName}}) Create{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PostPath}}", - }) - {{end}} - - // Bind and validate request body - var req models.{{.Name}}CreateRequest - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // TODO: Implement service call - // Example: resp, err := h.service.CreateResource(ctx, req) - // For now, return a mock response - response := models.{{.Name}}Response{ - Status: "success", - Message: "{{.Name}} created successfully", - RequestID: requestID, - Data: nil, // Replace with actual data - } - - {{if $.HasLogger}} - h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusCreated, response) -} -{{end}} -{{if .HasGet}} -// Get{{.Name}} retrieves a specific resource by ID -func (h *{{$.HandlerName}}) Get{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - id := c.Param("id") - if id == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter id", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Missing required parameter id", - RequestID: requestID, - }) - return - } - - {{if $.HasLogger}} - h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.GetPath}}", - "id": id, - }) - {{end}} - - // TODO: Implement service call - // Example: resp, err := h.service.GetResource(ctx, id) - // For now, return a mock response - response := models.{{.Name}}Response{ - Status: "success", - Message: "{{.Name}} retrieved successfully", - RequestID: requestID, - Data: nil, // Replace with actual data - } - - {{if $.HasLogger}} - h.logger.Info("Successfully retrieved {{.Name}}", map[string]interface{}{ - "request_id": requestID, - "id": id, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{if .HasPut}} -// Update{{.Name}} updates an existing resource -func (h *{{$.HandlerName}}) Update{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - id := c.Param("id") - if id == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter id", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Missing required parameter id", - RequestID: requestID, - }) - return - } - - {{if $.HasLogger}} - h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PutPath}}", - "id": id, - }) - {{end}} - - // Bind and validate request body - var req models.{{.Name}}UpdateRequest - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // TODO: Implement service call - // Example: resp, err := h.service.UpdateResource(ctx, id, req) - // For now, return a mock response - response := models.{{.Name}}Response{ - Status: "success", - Message: "{{.Name}} updated successfully", - RequestID: requestID, - Data: nil, // Replace with actual data - } - - {{if $.HasLogger}} - h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{ - "request_id": requestID, - "id": id, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{if .HasDelete}} -// Delete{{.Name}} deletes a resource -func (h *{{$.HandlerName}}) Delete{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - id := c.Param("id") - if id == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter id", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Missing required parameter id", - RequestID: requestID, - }) - return - } - - {{if $.HasLogger}} - h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.DeletePath}}", - "id": id, - }) - {{end}} - - // TODO: Implement service call - // Example: err := h.service.DeleteResource(ctx, id) - // For now, return a mock response - response := models.SuccessResponse{ - Status: "success", - Message: "{{.Name}} deleted successfully", - RequestID: requestID, - } - - {{if $.HasLogger}} - h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{ - "request_id": requestID, - "id": id, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{if .HasSearch}} -// Search{{.Name}} searches for resources -func (h *{{$.HandlerName}}) Search{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - // Get query parameters - query := c.Query("query") - limit := 10 - if l := c.Query("limit"); l != "" { - if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 { - limit = parsed - } - } - offset := 0 - if o := c.Query("offset"); o != "" { - if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 { - offset = parsed - } - } - - {{if $.HasLogger}} - h.logger.Info("Processing Search{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.SearchPath}}", - "query": query, - "limit": limit, - "offset": offset, - }) - {{end}} - - // TODO: Implement service call - // Example: resp, err := h.service.SearchResources(ctx, query, limit, offset) - // For now, return a mock response - response := models.{{.Name}}SearchResponse{ - Status: "success", - Message: "Search completed successfully", - RequestID: requestID, - Data: models.SearchData{ - Results: []interface{}{}, // Replace with actual results - Total: 0, - Limit: limit, - Offset: offset, - }, - } - - {{if $.HasLogger}} - h.logger.Info("Successfully completed search", map[string]interface{}{ - "request_id": requestID, - "query": query, - "results": len(response.Data.Results), - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{end}}` - -const modelTemplate = `package models - -import ( - "time" - "github.com/go-playground/validator/v10" -) - -// BaseRequest contains common fields for all requests -type BaseRequest struct { - RequestID string ` + "`json:\"request_id,omitempty\"`" + ` - Timestamp time.Time ` + "`json:\"timestamp,omitempty\"`" + ` -} - -// BaseResponse contains common fields for all responses -type BaseResponse struct { - Status string ` + "`json:\"status\"`" + ` - Message string ` + "`json:\"message\"`" + ` - Data interface{} ` + "`json:\"data,omitempty\"`" + ` - RequestID string ` + "`json:\"request_id,omitempty\"`" + ` -} - -// ErrorResponse represents an error response -type ErrorResponse struct { - Status string ` + "`json:\"status\"`" + ` - Message string ` + "`json:\"message\"`" + ` - RequestID string ` + "`json:\"request_id,omitempty\"`" + ` -} - -// SuccessResponse represents a success response -type SuccessResponse struct { - Status string ` + "`json:\"status\"`" + ` - Message string ` + "`json:\"message\"`" + ` - RequestID string ` + "`json:\"request_id,omitempty\"`" + ` -} - -// SearchData contains search results -type SearchData struct { - Results []interface{} ` + "`json:\"results\"`" + ` - Total int ` + "`json:\"total\"`" + ` - Limit int ` + "`json:\"limit\"`" + ` - Offset int ` + "`json:\"offset\"`" + ` -} - -// {{.ServiceName}} Models -// Generated at: {{.Timestamp}} - -// {{.ServiceName}}CreateRequest represents a request to create a {{.ServiceName}} -type {{.ServiceName}}CreateRequest struct { - BaseRequest - // Add fields specific to {{.ServiceName}} creation - Name string ` + "`json:\"name\" validate:\"required\"`" + ` - Description string ` + "`json:\"description\" validate:\"omitempty\"`" + ` -} - -// {{.ServiceName}}UpdateRequest represents a request to update a {{.ServiceName}} -type {{.ServiceName}}UpdateRequest struct { - BaseRequest - ID string ` + "`json:\"id\" validate:\"required,uuid\"`" + ` - Name string ` + "`json:\"name\" validate:\"required\"`" + ` - Description string ` + "`json:\"description\" validate:\"omitempty\"`" + ` -} - -// {{.ServiceName}}Response represents a response for {{.ServiceName}} operations -type {{.ServiceName}}Response struct { - BaseResponse -} - -// {{.ServiceName}}SearchResponse represents a response for {{.ServiceName}} search -type {{.ServiceName}}SearchResponse struct { - BaseResponse - Data SearchData ` + "`json:\"data\"`" + ` -} - -// {{.ServiceName}}Data represents the data structure for {{.ServiceName}} -type {{.ServiceName}}Data struct { - ID string ` + "`json:\"id\"`" + ` - Name string ` + "`json:\"name\"`" + ` - Description string ` + "`json:\"description,omitempty\"`" + ` - CreatedAt time.Time ` + "`json:\"created_at\"`" + ` - UpdatedAt time.Time ` + "`json:\"updated_at,omitempty\"`" + ` -} - -// Validate validates the {{.ServiceName}}CreateRequest -func (r *{{.ServiceName}}CreateRequest) Validate() error { - validate := validator.New() - return validate.Struct(r) -} - -// Validate validates the {{.ServiceName}}UpdateRequest -func (r *{{.ServiceName}}UpdateRequest) Validate() error { - validate := validator.New() - return validate.Struct(r) -} - -// New{{.ServiceName}}Data creates a new {{.ServiceName}}Data with default values -func New{{.ServiceName}}Data(name, description string) *{{.ServiceName}}Data { - now := time.Now() - return &{{.ServiceName}}Data{ - ID: "", // Will be set by the database - Name: name, - Description: description, - CreatedAt: now, - UpdatedAt: now, - } -}` diff --git a/tools/satusehat/newgenerete b/tools/satusehat/newgenerete deleted file mode 100644 index 6dd58cc8..00000000 --- a/tools/satusehat/newgenerete +++ /dev/null @@ -1,2119 +0,0 @@ -package main - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "regexp" - "strings" - "sync" - "text/template" - "time" - - "gopkg.in/yaml.v2" -) - -// ================== STRUCTURES FOR SEPARATION OF CONCERNS ================== -// HandlerValidation tracks validation and generation status with thread safety -type HandlerValidation struct { - ExistingFunctions map[string]bool - NewFunctions []string - UpdatedFiles []string - CreatedFiles []string - mu sync.Mutex // For thread-safe access to slices -} - -// DirectoryInfo represents directory structure for nested endpoints -type DirectoryInfo struct { - Path string - IsFile bool - Functions []FunctionInfo - Children map[string]*DirectoryInfo -} - -// FunctionInfo contains information about a function -type FunctionInfo struct { - Name string - Methods []string - Endpoint string - Config EndpointConfig -} - -// EndpointConfig contains configuration for an endpoint -type EndpointConfig struct { - Methods []string `yaml:"methods"` - GetPath string `yaml:"get_path"` - PostPath string `yaml:"post_path"` - PutPath string `yaml:"put_path"` - PatchPath string `yaml:"patch_path"` - DeletePath string `yaml:"delete_path"` - SearchPath string `yaml:"search_path"` - Model string `yaml:"model"` - ResponseModel string `yaml:"response_model"` - RequestModel string `yaml:"request_model"` - Description string `yaml:"description"` - Summary string `yaml:"summary"` - Tags []string `yaml:"tags"` - RequireAuth bool `yaml:"require_auth"` - CacheEnabled bool `yaml:"cache_enabled"` - CacheTTL int `yaml:"cache_ttl"` - FhirProfiles []string `yaml:"fhir_profiles"` - SearchParams []string `yaml:"search_params"` -} - -// GlobalConfig contains global configuration -type GlobalConfig struct { - ModuleName string `yaml:"module_name"` - OutputDir string `yaml:"output_dir"` - EnableSwagger bool `yaml:"enable_swagger"` - EnableLogging bool `yaml:"enable_logging"` - EnableMetrics bool `yaml:"enable_metrics"` - EnableAuth bool `yaml:"enable_auth"` - BaseURL string `yaml:"base_url"` - Version string `yaml:"version"` - Environment string `yaml:"environment"` - FhirVersion string `yaml:"fhir_version"` - ProfileURL string `yaml:"profile_url"` -} - -// ServiceConfig contains complete service configuration -type ServiceConfig struct { - Global GlobalConfig `yaml:"global,omitempty"` - Services map[string]Service `yaml:"services"` -} - -// Service represents a single service -type Service struct { - Name string `yaml:"name"` - Category string `yaml:"category"` - Package string `yaml:"package"` - Description string `yaml:"description"` - BaseURL string `yaml:"base_url"` - Timeout int `yaml:"timeout"` - RetryCount int `yaml:"retry_count"` - FhirResource string `yaml:"fhir_resource,omitempty"` - Validation ValidationConfig `yaml:"validation,omitempty"` - Auth AuthConfig `yaml:"authentication,omitempty"` - Endpoints map[string]EndpointGroup `yaml:"endpoints"` - Dependencies string `yaml:"dependencies,omitempty"` - Middleware string `yaml:"middleware,omitempty"` -} - -// ValidationConfig contains validation rules -type ValidationConfig struct { - EnableFhirValidation bool `yaml:"enable_fhir_validation"` - RequiredFields []string `yaml:"required_fields"` - CustomValidators []string `yaml:"custom_validators"` -} - -// AuthConfig contains authentication configuration -type AuthConfig struct { - Type string `yaml:"type"` - TokenURL string `yaml:"token_url"` - Scopes []string `yaml:"scopes"` -} - -// EndpointGroup represents a group of related endpoints - MODIFIED to match your YAML -type EndpointGroup struct { - Description string `yaml:"description"` - Basic map[string]interface{} `yaml:"basic"` - // Generated fields - HandlerFolder string - HandlerFile string - HandlerName string - Functions map[string]FunctionConfig -} - -// FunctionConfig contains individual function configuration -type FunctionConfig struct { - Methods []string `yaml:"methods"` - Path string `yaml:"path"` - Model string `yaml:"model"` - RoutesLink string `yaml:"routes_link"` - // Path for swagger documentation - GetPath string `yaml:"get_path"` - PostPath string `yaml:"post_path"` - PutPath string `yaml:"put_path"` - PatchPath string `yaml:"patch_path"` - DeletePath string `yaml:"delete_path"` - SearchPath string `yaml:"search_path"` - ResponseModel string `yaml:"response_model"` - RequestModel string `yaml:"request_model"` - Description string `yaml:"description"` - Summary string `yaml:"summary"` - Tags []string `yaml:"tags"` - RequireAuth bool `yaml:"require_auth"` - CacheEnabled bool `yaml:"cache_enabled"` - CacheTTL int `yaml:"cache_ttl"` - FhirProfiles []string `yaml:"fhir_profiles"` - SearchParams []string `yaml:"search_params"` -} - -// TemplateData contains data for template generation -type TemplateData struct { - ServiceName string - ServiceLower string - ServiceUpper string - Category string - CategoryParts []string - Package string - Description string - BaseURL string - Timeout int - RetryCount int - FhirResource string - Endpoints []EndpointData - Timestamp string - ModuleName string - HasValidator bool - HasLogger bool - HasSwagger bool - GlobalConfig GlobalConfig - ShouldGenerateStruct bool - ShouldGenerateConstructor bool - FunctionalArea string - HandlerName string - DirectoryDepth int -} - -// EndpointData contains endpoint-specific data -type EndpointData struct { - Name string - NameLower string - NameUpper string - NameCamel string - Methods []string - GetPath string - PostPath string - PutPath string - PatchPath string - DeletePath string - SearchPath string - Model string - ResponseModel string - RequestModel string - DataModel string - Description string - Summary string - Tags []string - HasGet bool - HasPost bool - HasPut bool - HasPatch bool - HasDelete bool - HasSearch bool - RequireAuth bool - CacheEnabled bool - CacheTTL int - PathParams []string - ModelPackage string - FhirResource string - CategoryPath string -} - -// ================== MAIN GENERATION FUNCTIONS ================== -// generateHandlerWithValidation generates handler with separation of concerns -func generateHandlerWithValidation( - ctx context.Context, - serviceName string, - svc Service, - gc GlobalConfig, -) error { - validation := &HandlerValidation{ - ExistingFunctions: make(map[string]bool), - NewFunctions: []string{}, - UpdatedFiles: []string{}, - CreatedFiles: []string{}, - } - - baseDir := gc.OutputDir - var wg sync.WaitGroup - errChan := make(chan error, len(svc.Endpoints)) - - // Debug: Print service information - fmt.Printf("šŸ” Debug: Processing service %s with %d endpoints\n", serviceName, len(svc.Endpoints)) - - // Process each endpoint group concurrently - for groupName, grp := range svc.Endpoints { - wg.Add(1) - go func(groupName string, grp EndpointGroup) { - defer wg.Done() - select { - case <-ctx.Done(): - errChan <- ctx.Err() - return - default: - // Debug: Print endpoint group information - fmt.Printf("šŸ” Debug: Processing endpoint group %s with %d functions\n", groupName, len(grp.Functions)) - - // Create handler folder structure - categoryParts := strings.Split(svc.Category, "/") - handlerDirParts := append([]string{baseDir}, categoryParts...) - handlerDir := filepath.Join(handlerDirParts...) - if err := os.MkdirAll(handlerDir, 0755); err != nil { - errChan <- fmt.Errorf("mkdir %s: %w", handlerDir, err) - return - } - - // Generate methods file with clear naming - methodsFileName := fmt.Sprintf("%s.go", strings.ToLower(svc.Package)) - methodsFilePath := filepath.Join(handlerDir, methodsFileName) - - // Check if methods file exists - fileExists := false - if _, err := os.Stat(methodsFilePath); err == nil { - fileExists = true - } - - if !fileExists { - // Create new methods file - err := createMethodsFileFromConfig( - methodsFilePath, svc, grp, gc, groupName, - ) - if err != nil { - errChan <- fmt.Errorf("create methods file %s: %w", methodsFilePath, err) - return - } - validation.mu.Lock() - validation.CreatedFiles = append(validation.CreatedFiles, methodsFilePath) - validation.mu.Unlock() - fmt.Printf("āœ… Created methods file: %s\n", methodsFilePath) - } else { - // Update existing methods file with new functions only - err := updateExistingMethodsFile( - methodsFilePath, svc, grp, gc, groupName, validation, - ) - if err != nil { - errChan <- fmt.Errorf("update methods file %s: %w", methodsFilePath, err) - return - } - validation.mu.Lock() - if len(validation.NewFunctions) > 0 { - validation.UpdatedFiles = append(validation.UpdatedFiles, methodsFilePath) - } - validation.mu.Unlock() - fmt.Printf("āœ… Updated methods file: %s\n", methodsFilePath) - } - } - }(groupName, grp) - } - - // Wait for all goroutines to complete - wg.Wait() - close(errChan) - - // Check for errors - for err := range errChan { - if err != nil { - return err - } - } - - // Generate models (sequential for simplicity) - if err := generateModels(ctx, svc, gc); err != nil { - return fmt.Errorf("generate models: %w", err) - } - - // Generate routes - if err := generateRoutes(serviceName, svc, gc); err != nil { - return fmt.Errorf("generate routes: %w", err) - } - - return nil -} - -// createMethodsFileFromConfig creates new methods file from configuration -func createMethodsFileFromConfig( - filePath string, - svc Service, - grp EndpointGroup, - gc GlobalConfig, - functionalArea string, -) error { - // Debug: Print function information - fmt.Printf("šŸ” Debug: Creating methods file with %d functions\n", len(grp.Functions)) - for funcName, funcConfig := range grp.Functions { - fmt.Printf("šŸ” Debug: Function %s has methods: %v\n", funcName, funcConfig.Methods) - } - - // Collect all functions for this group - var allEndpoints []EndpointData - for fname, fcfg := range grp.Functions { - td := processFunctionData(svc, grp, fname, fcfg, gc) - allEndpoints = append(allEndpoints, td.Endpoints...) - } - - templateData := TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - CategoryParts: strings.Split(svc.Category, "/"), - Package: svc.Package, - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - FhirResource: svc.FhirResource, - Endpoints: allEndpoints, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - ShouldGenerateStruct: false, // NEVER generate struct in methods file - ShouldGenerateConstructor: false, // NEVER generate constructor in methods file - FunctionalArea: functionalArea, - HandlerName: grp.HandlerName, - DirectoryDepth: len(strings.Split(svc.Category, "/")), - } - - return createMethodsFile(filePath, templateData) -} - -// updateExistingMethodsFile updates existing methods file with new functions -func updateExistingMethodsFile( - filePath string, - svc Service, - grp EndpointGroup, - gc GlobalConfig, - functionalArea string, - validation *HandlerValidation, -) error { - existingContent, err := ioutil.ReadFile(filePath) - if err != nil { - return err - } - content := string(existingContent) - - // Debug: Print function information - fmt.Printf("šŸ” Debug: Updating methods file with %d functions\n", len(grp.Functions)) - for funcName, funcConfig := range grp.Functions { - fmt.Printf("šŸ” Debug: Function %s has methods: %v\n", funcName, funcConfig.Methods) - } - - // Check for existing functions and collect new ones - var newEndpoints []EndpointData - for fname, fcfg := range grp.Functions { - functionExists := false - for _, method := range fcfg.Methods { - funcName := generateFunctionName(fname, method) - sig := fmt.Sprintf("func (h *%sHandler) %s", grp.HandlerName, funcName) - if strings.Contains(content, sig) { - fmt.Printf("āš ļø Skip existing: %s (%s)\n", fname, funcName) - functionExists = true - break - } - } - if functionExists { - continue - } - - td := processFunctionData(svc, grp, fname, fcfg, gc) - newEndpoints = append(newEndpoints, td.Endpoints...) - validation.mu.Lock() - validation.NewFunctions = append(validation.NewFunctions, fname) - validation.mu.Unlock() - fmt.Printf("āœ… Will add: %s\n", fname) - } - - if len(newEndpoints) == 0 { - fmt.Printf("ā­ļø No new functions to add\n") - return nil - } - - // Generate new functions using methods-only template - templateData := TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - CategoryParts: strings.Split(svc.Category, "/"), - Package: svc.Package, - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - FhirResource: svc.FhirResource, - Endpoints: newEndpoints, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - ShouldGenerateStruct: false, // NEVER - ShouldGenerateConstructor: false, // NEVER - FunctionalArea: functionalArea, - HandlerName: grp.HandlerName, - DirectoryDepth: len(strings.Split(svc.Category, "/")), - } - - newFunctions, err := generateNewMethodsOnly(templateData) - if err != nil { - return err - } - - // Merge content - mergedContent := mergeGoFileContent(content, newFunctions) - return ioutil.WriteFile(filePath, []byte(mergedContent), 0644) -} - -// processFunctionData processes individual function configuration -func processFunctionData( - svc Service, - grp EndpointGroup, - fname string, - fcfg FunctionConfig, - gc GlobalConfig, -) TemplateData { - fmt.Printf("šŸ” Debug: Processing function %s with methods: %v\n", fname, fcfg.Methods) - - ed := EndpointData{ - Name: toCamelCase(fname), - NameLower: strings.ToLower(fname), - NameUpper: strings.ToUpper(fname), - NameCamel: toCamelCase(fname), - Methods: fcfg.Methods, - GetPath: fcfg.GetPath, - PostPath: fcfg.PostPath, - PutPath: fcfg.PutPath, - PatchPath: fcfg.PatchPath, - DeletePath: fcfg.DeletePath, - SearchPath: fcfg.SearchPath, - Model: fcfg.Model, - ResponseModel: fcfg.ResponseModel, - RequestModel: fcfg.Model, - DataModel: strings.Replace(fcfg.ResponseModel, "Response", "Data", 1), - Description: fcfg.Description, - Summary: fcfg.Summary, - Tags: fcfg.Tags, - RequireAuth: fcfg.RequireAuth, - CacheEnabled: fcfg.CacheEnabled, - CacheTTL: fcfg.CacheTTL, - PathParams: extractPathParams(fcfg.GetPath), - ModelPackage: svc.Package, - FhirResource: svc.FhirResource, - CategoryPath: svc.Category, - } - - // Set method flags - for _, m := range fcfg.Methods { - switch strings.ToUpper(m) { - case "GET": - ed.HasGet = true - case "POST": - ed.HasPost = true - case "PUT": - ed.HasPut = true - case "PATCH": - ed.HasPatch = true - case "DELETE": - ed.HasDelete = true - case "SEARCH": - ed.HasSearch = true - } - } - - fmt.Printf("šŸ” Debug: Function %s flags - Get:%t, Post:%t, Put:%t, Patch:%t, Delete:%t, Search:%t\n", - fname, ed.HasGet, ed.HasPost, ed.HasPut, ed.HasPatch, ed.HasDelete, ed.HasSearch) - - return TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - CategoryParts: strings.Split(svc.Category, "/"), - Package: svc.Package, - Description: svc.Description, - BaseURL: svc.BaseURL, - Timeout: svc.Timeout, - RetryCount: svc.RetryCount, - FhirResource: svc.FhirResource, - Endpoints: []EndpointData{ed}, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - HasValidator: true, - HasLogger: gc.EnableLogging, - HasSwagger: gc.EnableSwagger, - GlobalConfig: gc, - HandlerName: grp.HandlerName, - DirectoryDepth: len(strings.Split(svc.Category, "/")), - } -} - -// generateModels generates model files for the service -func generateModels(ctx context.Context, svc Service, gc GlobalConfig) error { - // Create model directory structure - categoryParts := strings.Split(svc.Category, "/") - modelDirParts := append([]string{"internal", "models"}, categoryParts...) - modelDir := filepath.Join(modelDirParts...) - if err := os.MkdirAll(modelDir, 0755); err != nil { - return fmt.Errorf("mkdir %s: %w", modelDir, err) - } - - modelFilePath := filepath.Join(modelDir, strings.ToLower(svc.Package)+".go") - - // Check if model file exists - if _, err := os.Stat(modelFilePath); err == nil { - fmt.Printf("ā­ļø Model file already exists: %s\n", modelFilePath) - return nil - } - - // Check if context was cancelled - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - - // Generate model content - templateData := TemplateData{ - ServiceName: svc.Name, - ServiceLower: strings.ToLower(svc.Name), - ServiceUpper: strings.ToUpper(svc.Name), - Category: svc.Category, - CategoryParts: categoryParts, - Package: svc.Package, - FhirResource: svc.FhirResource, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - ModuleName: gc.ModuleName, - DirectoryDepth: len(categoryParts), - } - - tmpl := template.New("model") - tmpl, err := tmpl.Parse(modelTemplate) - if err != nil { - return err - } - - file, err := os.Create(modelFilePath) - if err != nil { - return err - } - defer file.Close() - - err = tmpl.Execute(file, templateData) - if err != nil { - return err - } - - fmt.Printf("āœ… Created model file: %s\n", modelFilePath) - return nil -} - -// generateRoutes generates routes for the service -func generateRoutes(serviceName string, svc Service, gc GlobalConfig) error { - routesFilePath := filepath.Join(gc.OutputDir, "routes.go") - if _, err := os.Stat(routesFilePath); os.IsNotExist(err) { - // Create routes file if it doesn't exist - if err := os.MkdirAll(filepath.Dir(routesFilePath), 0755); err != nil { - return fmt.Errorf("mkdir %s: %w", filepath.Dir(routesFilePath), err) - } - - // Create a basic routes file - routesContent := fmt.Sprintf(`package handlers -import ( - "%s/internal/config" - "%s/pkg/logger" - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" -) - -// SetupRoutes sets up all routes for the application -func SetupRoutes(r *gin.Engine, cfg *config.Config) error { - // Setup routes will be added here - return nil -} -`, gc.ModuleName, gc.ModuleName) - - if err := ioutil.WriteFile(routesFilePath, []byte(routesContent), 0644); err != nil { - return fmt.Errorf("create routes file: %w", err) - } - fmt.Printf("āœ… Created routes file: %s\n", routesFilePath) - } - - routesContent, err := ioutil.ReadFile(routesFilePath) - if err != nil { - return fmt.Errorf("read routes file: %w", err) - } - routesContentStr := string(routesContent) - - // Check if routes are already registered - if strings.Contains(routesContentStr, fmt.Sprintf("Register%sRoutes", svc.Name)) { - fmt.Printf("āš ļø Routes for %s already registered\n", svc.Name) - return nil - } - - var imports []string - var allRoutes []string - - // Track handler folders that have been imported - processedFolders := make(map[string]bool) - - for groupName, grp := range svc.Endpoints { - // Import based on handler folder - if !processedFolders[grp.HandlerFolder] { - importLine := fmt.Sprintf("\t%sHandlers \"%s/internal/handlers/%s\"", - grp.HandlerFolder, gc.ModuleName, grp.HandlerFolder) - imports = append(imports, importLine) - processedFolders[grp.HandlerFolder] = true - fmt.Printf("āœ… Added import: %sHandlers\n", grp.HandlerFolder) - } - - var routesCode strings.Builder - - // Use groupName for comment and identifier - routesCode.WriteString(fmt.Sprintf("\n\t// %s (%s) routes\n", grp.Description, groupName)) - - // Handler instantiation using HandlerName from config - routesCode.WriteString(fmt.Sprintf("\t%sHandler := %sHandlers.New%sHandler(%sHandlers.%sHandlerConfig{\n", - strings.ToLower(grp.HandlerName), - grp.HandlerFolder, - grp.HandlerName, - grp.HandlerFolder, - grp.HandlerName)) - routesCode.WriteString("\t\tConfig: cfg,\n") - routesCode.WriteString("\t\tLogger: logger.Default(),\n") - routesCode.WriteString("\t\tValidator: validator.New(),\n") - routesCode.WriteString("\t})\n") - - // Use groupName for route group path - routesCode.WriteString(fmt.Sprintf("\t%sGroup := r.Group(\"/%s\")\n", - strings.ToLower(grp.HandlerName), groupName)) - - // Process functions - for fname, fcfg := range grp.Functions { - td := processFunctionData(svc, grp, fname, fcfg, gc) - for _, endpoint := range td.Endpoints { - handlerVar := strings.ToLower(grp.HandlerName) + "Handler" - groupVar := strings.ToLower(grp.HandlerName) + "Group" - - // Loop through methods and use specific routes - for _, method := range fcfg.Methods { - var cleanPath string - - // Choose path based on method - switch strings.ToUpper(method) { - case "GET": - cleanPath = fcfg.GetPath - case "POST": - cleanPath = fcfg.PostPath - case "PUT": - cleanPath = fcfg.PutPath - case "PATCH": - cleanPath = fcfg.PatchPath - case "DELETE": - cleanPath = fcfg.DeletePath - case "SEARCH": - cleanPath = fcfg.SearchPath - default: - fmt.Printf("āš ļø Unsupported HTTP method: %s for function %s\n", method, fname) - continue - } - - // Fallback to path if specific route is empty - if cleanPath == "" { - cleanPath = fcfg.Path - } - - // Clean path - remove groupName prefix if exists - if strings.HasPrefix(cleanPath, "/"+groupName) { - cleanPath = strings.TrimPrefix(cleanPath, "/"+groupName) - } - if cleanPath == "" { - cleanPath = "/" - } - - // Generate route based on method - switch strings.ToUpper(method) { - case "GET": - routesCode.WriteString(fmt.Sprintf("\t%s.GET(\"%s\", %s.Get%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - case "POST": - routesCode.WriteString(fmt.Sprintf("\t%s.POST(\"%s\", %s.Create%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - case "PUT": - routesCode.WriteString(fmt.Sprintf("\t%s.PUT(\"%s\", %s.Update%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - case "PATCH": - routesCode.WriteString(fmt.Sprintf("\t%s.PATCH(\"%s\", %s.Patch%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - case "DELETE": - routesCode.WriteString(fmt.Sprintf("\t%s.DELETE(\"%s\", %s.Delete%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - case "SEARCH": - routesCode.WriteString(fmt.Sprintf("\t%s.GET(\"%s\", %s.Search%s)\n", - groupVar, cleanPath, handlerVar, endpoint.Name)) - } - } - } - } - - allRoutes = append(allRoutes, routesCode.String()) - } - - // Insert imports after existing imports - if len(imports) > 0 { - importSection := strings.Join(imports, "\n") + "\n" - // Find position after import section - importMarker := "import (" - if strings.Contains(routesContentStr, importMarker) { - importIndex := strings.Index(routesContentStr, importMarker) - closingParenIndex := strings.Index(routesContentStr[importIndex:], "\n)") + importIndex - if closingParenIndex > importIndex { - routesContentStr = routesContentStr[:closingParenIndex+2] + "\n" + importSection + routesContentStr[closingParenIndex+2:] - } - } - } - - // Find and insert routes - setupRoutesMarker := "func SetupRoutes" - if !strings.Contains(routesContentStr, setupRoutesMarker) { - return fmt.Errorf("SetupRoutes function not found in routes.go") - } - - funcStart := strings.Index(routesContentStr, setupRoutesMarker) - funcBodyStart := strings.Index(routesContentStr[funcStart:], "{") + funcStart + 1 - newRoutesContent := routesContentStr[:funcBodyStart+1] + "\n" + strings.Join(allRoutes, "\n") + "\n" + routesContentStr[funcBodyStart+1:] - - err = ioutil.WriteFile(routesFilePath, []byte(newRoutesContent), 0644) - if err != nil { - return fmt.Errorf("write updated routes file: %w", err) - } - - fmt.Printf("āœ… Updated routes file with %s routes\n", svc.Name) - return nil -} - -// ================== HELPER FUNCTIONS ================== -// generateFunctionName generates function name based on endpoint and method -func generateFunctionName(endpointName, method string) string { - switch strings.ToUpper(method) { - case "GET": - return fmt.Sprintf("Get%s", strings.Title(endpointName)) - case "POST": - return fmt.Sprintf("Create%s", strings.Title(endpointName)) - case "PUT": - return fmt.Sprintf("Update%s", strings.Title(endpointName)) - case "DELETE": - return fmt.Sprintf("Delete%s", strings.Title(endpointName)) - case "PATCH": - return fmt.Sprintf("Patch%s", strings.Title(endpointName)) - case "SEARCH": - return fmt.Sprintf("Search%s", strings.Title(endpointName)) - default: - return fmt.Sprintf("%s%s", strings.Title(method), strings.Title(endpointName)) - } -} - -// extractPathParams extracts path parameters from URL path -func extractPathParams(path string) []string { - var params []string - for _, part := range strings.Split(path, "/") { - if strings.HasPrefix(part, ":") { - params = append(params, strings.TrimPrefix(part, ":")) - } - } - return params -} - -// toCamelCase converts string to CamelCase -func toCamelCase(s string) string { - parts := strings.FieldsFunc(s, func(r rune) bool { - return r == '_' || r == '-' || r == ' ' - }) - for i, p := range parts { - parts[i] = strings.Title(strings.ToLower(p)) - } - return strings.Join(parts, "") -} - -// createMethodsFile creates methods file using template -func createMethodsFile(filePath string, templateData TemplateData) error { - if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil { - return err - } - - // Debug: Print template data - fmt.Printf("šŸ” Debug: Creating methods file with %d endpoints\n", len(templateData.Endpoints)) - for _, endpoint := range templateData.Endpoints { - fmt.Printf("šŸ” Debug: Endpoint %s has methods: %v\n", endpoint.Name, endpoint.Methods) - } - - // Create template with basic functions - tmpl := template.New("methods").Funcs(template.FuncMap{ - "title": strings.Title, - "upper": strings.ToUpper, - "lower": strings.ToLower, - "index": func(slice []string, i int) string { - if i >= 0 && i < len(slice) { - return slice[i] - } - return "" - }, - "add": func(a, b int) int { - return a + b - }, - "sub": func(a, b int) int { - return a - b - }, - }) - - tmpl, err := tmpl.Parse(handlerTemplate) - if err != nil { - return fmt.Errorf("parse template: %w", err) - } - - file, err := os.Create(filePath) - if err != nil { - return err - } - defer file.Close() - - return tmpl.Execute(file, templateData) -} - -// generateNewMethodsOnly generates only new methods for existing files -func generateNewMethodsOnly(templateData TemplateData) (string, error) { - // Debug: Print template data - fmt.Printf("šŸ” Debug: Generating new methods with %d endpoints\n", len(templateData.Endpoints)) - for _, endpoint := range templateData.Endpoints { - fmt.Printf("šŸ” Debug: Endpoint %s has methods: %v\n", endpoint.Name, endpoint.Methods) - } - - // Create template with basic functions only - tmpl := template.New("newMethods").Funcs(template.FuncMap{ - "title": strings.Title, - "upper": strings.ToUpper, - "lower": strings.ToLower, - "index": func(slice []string, i int) string { - if i >= 0 && i < len(slice) { - return slice[i] - } - return "" - }, - }) - - tmpl, err := tmpl.Parse(methodsOnlyTemplate) - if err != nil { - return "", err - } - - var result strings.Builder - err = tmpl.Execute(&result, templateData) - if err != nil { - return "", err - } - - return result.String(), nil -} - -// mergeGoFileContent merges new functions into existing file -func mergeGoFileContent(existingContent, newFunctions string) string { - // Find last closing brace - re := regexp.MustCompile(`}\s*$`) - lastBraceIndex := re.FindStringIndex(existingContent) - if lastBraceIndex == nil { - return existingContent + "\n" + newFunctions - } - - before := existingContent[:lastBraceIndex[0]] - after := existingContent[lastBraceIndex[0]:] - - return before + "\n" + newFunctions + "\n" + after -} - -// ================== MAIN FUNCTION ================== -func main() { - if len(os.Args) < 2 { - printUsage() - os.Exit(1) - } - - configFile := os.Args[1] - var targetService string - if len(os.Args) > 2 { - targetService = os.Args[2] - } - - config, err := loadConfig(configFile) - if err != nil { - fmt.Printf("Error loading config: %v\n", err) - os.Exit(1) - } - - fmt.Println("šŸš€ Starting Handler Generation with Validation...") - fmt.Printf("šŸ“ Config file: %s\n", configFile) - if targetService != "" { - fmt.Printf("šŸŽÆ Target service: %s\n", targetService) - } - - // Create context with cancellation for graceful shutdown - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // Worker pool for concurrent processing - type result struct { - serviceName string - err error - } - results := make(chan result) - jobs := make(chan string, len(config.Services)) - - // Start worker pool - var wg sync.WaitGroup - numWorkers := 4 // Adjust based on your system - for i := 0; i < numWorkers; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for serviceName := range jobs { - select { - case <-ctx.Done(): - return - case results <- result{ - serviceName: serviceName, - err: generateHandlerWithValidation(ctx, serviceName, config.Services[serviceName], config.Global), - }: - } - } - }() - } - - // Send jobs to workers - go func() { - for serviceName := range config.Services { - if targetService != "" && serviceName != targetService { - continue - } - select { - case <-ctx.Done(): - break - case jobs <- serviceName: - } - } - close(jobs) - }() - - // Collect results - go func() { - wg.Wait() - close(results) - }() - - // Process results - generated := 0 - errors := 0 - for res := range results { - if res.err != nil { - fmt.Printf("āŒ Error generating handler for %s: %v\n", res.serviceName, res.err) - errors++ - continue - } - generated++ - } - - // Summary - fmt.Println("\nšŸ“Š Generation Summary:") - fmt.Printf("āœ… Successfully processed: %d services\n", generated) - if errors > 0 { - fmt.Printf("āŒ Failed: %d services\n", errors) - } - if generated > 0 { - fmt.Println("šŸŽ‰ Generation completed successfully!") - } -} - -// loadConfig loads configuration from YAML file -func loadConfig(filename string) (*ServiceConfig, error) { - data, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - - var config ServiceConfig - err = yaml.Unmarshal(data, &config) - if err != nil { - return nil, err - } - - // Set defaults - if config.Global.ModuleName == "" { - config.Global.ModuleName = "api-service" - } - if config.Global.OutputDir == "" { - config.Global.OutputDir = "internal/handlers" - } - - // Debug: Print loaded configuration - fmt.Printf("šŸ” Debug: Loaded %d services\n", len(config.Services)) - - // Process endpoint groups to add missing handler information - for serviceName, service := range config.Services { - fmt.Printf("šŸ” Debug: Processing service %s with %d endpoints\n", serviceName, len(service.Endpoints)) - - for endpointName, endpointGroup := range service.Endpoints { - fmt.Printf("šŸ” Debug: Processing endpoint %s with %d basic configs\n", endpointName, len(endpointGroup.Basic)) - - // Generate handler information based on service and endpoint names - endpointGroup.HandlerFolder = strings.ToLower(service.Package) - endpointGroup.HandlerFile = fmt.Sprintf("%s.go", strings.ToLower(service.Package)) - endpointGroup.HandlerName = toCamelCase(serviceName) - - // Convert basic map to functions map - endpointGroup.Functions = make(map[string]FunctionConfig) - - // Debug: Print the structure of basic configs - fmt.Printf("šŸ” Debug: Processing basic config for endpoint %s\n", endpointName) - - // Treat the entire Basic map as a single function configuration - funcConfig := FunctionConfig{} - - // Process each field in the basic config - for key, value := range endpointGroup.Basic { - keyStr := fmt.Sprintf("%v", key) - processYAMLField(keyStr, value, &funcConfig) - } - - fmt.Printf("šŸ” Debug: Final function config for %s: methods=%v, get_path=%s\n", - endpointName, funcConfig.Methods, funcConfig.GetPath) - - endpointGroup.Functions["basic"] = funcConfig - - // Update the service endpoints - service.Endpoints[endpointName] = endpointGroup - } - config.Services[serviceName] = service - } - - return &config, nil -} - -// processYAMLField processes a single YAML field and updates the function config -func processYAMLField(key string, value interface{}, funcConfig *FunctionConfig) { - fmt.Printf("šŸ” Debug: Processing key %s with value %v (type: %T)\n", key, value, value) - - switch key { - case "methods": - if methods, ok := value.([]interface{}); ok { - for _, method := range methods { - if methodStr, ok := method.(string); ok { - funcConfig.Methods = append(funcConfig.Methods, methodStr) - fmt.Printf("šŸ” Debug: Added method %s\n", methodStr) - } - } - } - case "get_path": - if path, ok := value.(string); ok { - funcConfig.GetPath = path - fmt.Printf("šŸ” Debug: Set get_path to %s\n", path) - } - case "post_path": - if path, ok := value.(string); ok { - funcConfig.PostPath = path - fmt.Printf("šŸ” Debug: Set post_path to %s\n", path) - } - case "put_path": - if path, ok := value.(string); ok { - funcConfig.PutPath = path - fmt.Printf("šŸ” Debug: Set put_path to %s\n", path) - } - case "patch_path": - if path, ok := value.(string); ok { - funcConfig.PatchPath = path - fmt.Printf("šŸ” Debug: Set patch_path to %s\n", path) - } - case "delete_path": - if path, ok := value.(string); ok { - funcConfig.DeletePath = path - fmt.Printf("šŸ” Debug: Set delete_path to %s\n", path) - } - case "search_path": - if path, ok := value.(string); ok { - funcConfig.SearchPath = path - fmt.Printf("šŸ” Debug: Set search_path to %s\n", path) - } - case "model": - if model, ok := value.(string); ok { - funcConfig.Model = model - fmt.Printf("šŸ” Debug: Set model to %s\n", model) - } - case "response_model": - if model, ok := value.(string); ok { - funcConfig.ResponseModel = model - fmt.Printf("šŸ” Debug: Set response_model to %s\n", model) - } - case "description": - if desc, ok := value.(string); ok { - funcConfig.Description = desc - fmt.Printf("šŸ” Debug: Set description to %s\n", desc) - } - case "summary": - if summary, ok := value.(string); ok { - funcConfig.Summary = summary - fmt.Printf("šŸ” Debug: Set summary to %s\n", summary) - } - case "tags": - if tags, ok := value.([]interface{}); ok { - for _, tag := range tags { - if tagStr, ok := tag.(string); ok { - funcConfig.Tags = append(funcConfig.Tags, tagStr) - fmt.Printf("šŸ” Debug: Added tag %s\n", tagStr) - } - } - } - case "require_auth": - if auth, ok := value.(bool); ok { - funcConfig.RequireAuth = auth - fmt.Printf("šŸ” Debug: Set require_auth to %t\n", auth) - } - case "cache_enabled": - if cache, ok := value.(bool); ok { - funcConfig.CacheEnabled = cache - fmt.Printf("šŸ” Debug: Set cache_enabled to %t\n", cache) - } - case "cache_ttl": - if ttl, ok := value.(int); ok { - funcConfig.CacheTTL = ttl - fmt.Printf("šŸ” Debug: Set cache_ttl to %d\n", ttl) - } - case "fhir_profiles": - if profiles, ok := value.([]interface{}); ok { - for _, profile := range profiles { - if profileStr, ok := profile.(string); ok { - funcConfig.FhirProfiles = append(funcConfig.FhirProfiles, profileStr) - fmt.Printf("šŸ” Debug: Added fhir_profile %s\n", profileStr) - } - } - } - case "search_params": - if params, ok := value.([]interface{}); ok { - for _, param := range params { - if paramStr, ok := param.(string); ok { - funcConfig.SearchParams = append(funcConfig.SearchParams, paramStr) - fmt.Printf("šŸ” Debug: Added search_param %s\n", paramStr) - } - } - } - default: - fmt.Printf("āš ļø Debug: Unknown key %s\n", key) - } -} - -// printUsage prints usage information -func printUsage() { - fmt.Println("Satu Sehat FHIR Handler Generator") - fmt.Println() - fmt.Println("Usage:") - fmt.Println(" go run generate-satusehat-handler.go [service-name]") - fmt.Println() - fmt.Println("Examples:") - fmt.Println(" go run generate-satusehat-handler.go services-config-satusehat.yaml") - fmt.Println(" go run generate-satusehat-handler.go services-config-satusehat.yaml patient") -} - -// ================== TEMPLATES ================== -const handlerTemplate = `// Package {{.Package}} handles {{.HandlerName}} services -// Generated on: {{.Timestamp}} -package {{.Package}} - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strconv" - "strings" - "time" - "{{.ModuleName}}/internal/config" - "{{.ModuleName}}/internal/models/{{.Package}}" - "{{.ModuleName}}/pkg/logger" - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" - "github.com/google/uuid" -) - -// {{.HandlerName}} handles {{.HandlerName}} services -type {{.HandlerName}} struct { - service interface{} - validator *validator.Validate - logger logger.Logger - config *config.Config -} - -// {{.HandlerName}}Config contains configuration for {{.HandlerName}} -type {{.HandlerName}}Config struct { - Config *config.Config - Logger logger.Logger - Validator *validator.Validate -} - -// New{{.HandlerName}} creates a new {{.HandlerName}} -func New{{.HandlerName}}(cfg {{.HandlerName}}Config) *{{.HandlerName}} { - return &{{.HandlerName}}{ - service: nil, // Initialize with appropriate service - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.Config, - } -} - -{{range .Endpoints}} -{{if .HasPost}} -// Create{{.Name}} creates a new resource -// @Summary Create a new resource -// @Description Create a new resource in the system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json -// @Param Authorization header string true "Bearer token" -// @Param request body models.{{.Name}}CreateRequest true "Resource creation request" -// @Success 201 {object} models.{{.Name}}Response "Resource created successfully" -// @Failure 400 {object} models.ErrorResponse "Bad request" -// @Failure 401 {object} models.ErrorResponse "Unauthorized" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router {{.PostPath}} [post] -func (h *{{$.HandlerName}}) Create{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PostPath}}", - }) - {{end}} - - // Bind and validate request body - var req models.{{.Name}}CreateRequest - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // TODO: Implement service call - // Example: resp, err := h.service.CreateResource(ctx, req) - - // For now, return a mock response - response := models.{{.Name}}Response{ - Status: "success", - Message: "{{.Name}} created successfully", - RequestID: requestID, - Data: nil, // Replace with actual data - } - - {{if $.HasLogger}} - h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusCreated, response) -} -{{end}} -{{if .HasGet}} -// Get{{.Name}} retrieves a specific resource by ID -// @Summary Get a specific resource by ID -// @Description Retrieve a specific resource from the system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json -// @Param Authorization header string true "Bearer token" -// @Param id path string true "Resource ID" -// @Success 200 {object} models.{{.Name}}Response "Resource retrieved successfully" -// @Failure 400 {object} models.ErrorResponse "Bad request" -// @Failure 401 {object} models.ErrorResponse "Unauthorized" -// @Failure 404 {object} models.ErrorResponse "Resource not found" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router {{.GetPath}} [get] -func (h *{{$.HandlerName}}) Get{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - id := c.Param("id") - if id == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter id", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Missing required parameter id", - RequestID: requestID, - }) - return - } - - {{if $.HasLogger}} - h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.GetPath}}", - "id": id, - }) - {{end}} - - // TODO: Implement service call - // Example: resp, err := h.service.GetResource(ctx, id) - - // For now, return a mock response - response := models.{{.Name}}Response{ - Status: "success", - Message: "{{.Name}} retrieved successfully", - RequestID: requestID, - Data: nil, // Replace with actual data - } - - {{if $.HasLogger}} - h.logger.Info("Successfully retrieved {{.Name}}", map[string]interface{}{ - "request_id": requestID, - "id": id, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{if .HasPut}} -// Update{{.Name}} updates an existing resource -// @Summary Update an existing resource -// @Description Update an existing resource in the system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json -// @Param Authorization header string true "Bearer token" -// @Param id path string true "Resource ID" -// @Param request body models.{{.Name}}UpdateRequest true "Resource update request" -// @Success 200 {object} models.{{.Name}}Response "Resource updated successfully" -// @Failure 400 {object} models.ErrorResponse "Bad request" -// @Failure 401 {object} models.ErrorResponse "Unauthorized" -// @Failure 404 {object} models.ErrorResponse "Resource not found" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router {{.PutPath}} [put] -func (h *{{$.HandlerName}}) Update{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - id := c.Param("id") - if id == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter id", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Missing required parameter id", - RequestID: requestID, - }) - return - } - - {{if $.HasLogger}} - h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PutPath}}", - "id": id, - }) - {{end}} - - // Bind and validate request body - var req models.{{.Name}}UpdateRequest - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // TODO: Implement service call - // Example: resp, err := h.service.UpdateResource(ctx, id, req) - - // For now, return a mock response - response := models.{{.Name}}Response{ - Status: "success", - Message: "{{.Name}} updated successfully", - RequestID: requestID, - Data: nil, // Replace with actual data - } - - {{if $.HasLogger}} - h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{ - "request_id": requestID, - "id": id, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{if .HasDelete}} -// Delete{{.Name}} deletes a resource -// @Summary Delete a resource -// @Description Delete a resource from the system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json -// @Param Authorization header string true "Bearer token" -// @Param id path string true "Resource ID" -// @Success 200 {object} models.SuccessResponse "Resource deleted successfully" -// @Failure 400 {object} models.ErrorResponse "Bad request" -// @Failure 401 {object} models.ErrorResponse "Unauthorized" -// @Failure 404 {object} models.ErrorResponse "Resource not found" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router {{.DeletePath}} [delete] -func (h *{{$.HandlerName}}) Delete{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - id := c.Param("id") - if id == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter id", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Missing required parameter id", - RequestID: requestID, - }) - return - } - - {{if $.HasLogger}} - h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.DeletePath}}", - "id": id, - }) - {{end}} - - // TODO: Implement service call - // Example: err := h.service.DeleteResource(ctx, id) - - // For now, return a mock response - response := models.SuccessResponse{ - Status: "success", - Message: "{{.Name}} deleted successfully", - RequestID: requestID, - } - - {{if $.HasLogger}} - h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{ - "request_id": requestID, - "id": id, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{if .HasSearch}} -// Search{{.Name}} searches for resources -// @Summary Search for resources -// @Description Search for resources in the system -// @Tags {{index .Tags 0}} -// @Accept json -// @Produce json -// @Param Authorization header string true "Bearer token" -// @Param query query string false "Search query" -// @Param limit query int false "Limit results" default(10) -// @Param offset query int false "Offset results" default(0) -// @Success 200 {object} models.{{.Name}}SearchResponse "Search results" -// @Failure 400 {object} models.ErrorResponse "Bad request" -// @Failure 401 {object} models.ErrorResponse "Unauthorized" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router {{.SearchPath}} [get] -func (h *{{$.HandlerName}}) Search{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - // Get query parameters - query := c.Query("query") - limit := 10 - if l := c.Query("limit"); l != "" { - if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 { - limit = parsed - } - } - offset := 0 - if o := c.Query("offset"); o != "" { - if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 { - offset = parsed - } - } - - {{if $.HasLogger}} - h.logger.Info("Processing Search{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.SearchPath}}", - "query": query, - "limit": limit, - "offset": offset, - }) - {{end}} - - // TODO: Implement service call - // Example: resp, err := h.service.SearchResources(ctx, query, limit, offset) - - // For now, return a mock response - response := models.{{.Name}}SearchResponse{ - Status: "success", - Message: "Search completed successfully", - RequestID: requestID, - Data: models.SearchData{ - Results: []interface{}{}, // Replace with actual results - Total: 0, - Limit: limit, - Offset: offset, - }, - } - - {{if $.HasLogger}} - h.logger.Info("Successfully completed search", map[string]interface{}{ - "request_id": requestID, - "query": query, - "results": len(response.Data.Results), - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{end}}` - -const methodsOnlyTemplate = `{{range .Endpoints}} -{{if .HasPost}} -// Create{{.Name}} creates a new resource -func (h *{{$.HandlerName}}) Create{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PostPath}}", - }) - {{end}} - - // Bind and validate request body - var req models.{{.Name}}CreateRequest - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // TODO: Implement service call - // Example: resp, err := h.service.CreateResource(ctx, req) - // For now, return a mock response - response := models.{{.Name}}Response{ - Status: "success", - Message: "{{.Name}} created successfully", - RequestID: requestID, - Data: nil, // Replace with actual data - } - - {{if $.HasLogger}} - h.logger.Info("Successfully created {{.Name}}", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - c.JSON(http.StatusCreated, response) -} -{{end}} -{{if .HasGet}} -// Get{{.Name}} retrieves a specific resource by ID -func (h *{{$.HandlerName}}) Get{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - id := c.Param("id") - if id == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter id", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Missing required parameter id", - RequestID: requestID, - }) - return - } - - {{if $.HasLogger}} - h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.GetPath}}", - "id": id, - }) - {{end}} - - // TODO: Implement service call - // Example: resp, err := h.service.GetResource(ctx, id) - // For now, return a mock response - response := models.{{.Name}}Response{ - Status: "success", - Message: "{{.Name}} retrieved successfully", - RequestID: requestID, - Data: nil, // Replace with actual data - } - - {{if $.HasLogger}} - h.logger.Info("Successfully retrieved {{.Name}}", map[string]interface{}{ - "request_id": requestID, - "id": id, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{if .HasPut}} -// Update{{.Name}} updates an existing resource -func (h *{{$.HandlerName}}) Update{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - id := c.Param("id") - if id == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter id", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Missing required parameter id", - RequestID: requestID, - }) - return - } - - {{if $.HasLogger}} - h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.PutPath}}", - "id": id, - }) - {{end}} - - // Bind and validate request body - var req models.{{.Name}}UpdateRequest - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to bind request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request structure - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Request validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // TODO: Implement service call - // Example: resp, err := h.service.UpdateResource(ctx, id, req) - // For now, return a mock response - response := models.{{.Name}}Response{ - Status: "success", - Message: "{{.Name}} updated successfully", - RequestID: requestID, - Data: nil, // Replace with actual data - } - - {{if $.HasLogger}} - h.logger.Info("Successfully updated {{.Name}}", map[string]interface{}{ - "request_id": requestID, - "id": id, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{if .HasDelete}} -// Delete{{.Name}} deletes a resource -func (h *{{$.HandlerName}}) Delete{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - id := c.Param("id") - if id == "" { - {{if $.HasLogger}} - h.logger.Error("Missing required parameter id", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusBadRequest, models.ErrorResponse{ - Status: "error", - Message: "Missing required parameter id", - RequestID: requestID, - }) - return - } - - {{if $.HasLogger}} - h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.DeletePath}}", - "id": id, - }) - {{end}} - - // TODO: Implement service call - // Example: err := h.service.DeleteResource(ctx, id) - // For now, return a mock response - response := models.SuccessResponse{ - Status: "success", - Message: "{{.Name}} deleted successfully", - RequestID: requestID, - } - - {{if $.HasLogger}} - h.logger.Info("Successfully deleted {{.Name}}", map[string]interface{}{ - "request_id": requestID, - "id": id, - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{if .HasSearch}} -// Search{{.Name}} searches for resources -func (h *{{$.HandlerName}}) Search{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() - - // Generate request ID if not present - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - // Get query parameters - query := c.Query("query") - limit := 10 - if l := c.Query("limit"); l != "" { - if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 { - limit = parsed - } - } - offset := 0 - if o := c.Query("offset"); o != "" { - if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 { - offset = parsed - } - } - - {{if $.HasLogger}} - h.logger.Info("Processing Search{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "{{.SearchPath}}", - "query": query, - "limit": limit, - "offset": offset, - }) - {{end}} - - // TODO: Implement service call - // Example: resp, err := h.service.SearchResources(ctx, query, limit, offset) - // For now, return a mock response - response := models.{{.Name}}SearchResponse{ - Status: "success", - Message: "Search completed successfully", - RequestID: requestID, - Data: models.SearchData{ - Results: []interface{}{}, // Replace with actual results - Total: 0, - Limit: limit, - Offset: offset, - }, - } - - {{if $.HasLogger}} - h.logger.Info("Successfully completed search", map[string]interface{}{ - "request_id": requestID, - "query": query, - "results": len(response.Data.Results), - }) - {{end}} - - c.JSON(http.StatusOK, response) -} -{{end}} -{{end}}` - -// FIXED: Using string concatenation to avoid backtick conflicts -const modelTemplate = `package models - -import ( - "time" - "github.com/go-playground/validator/v10" -) - -// BaseRequest contains common fields for all requests -type BaseRequest struct { - RequestID string ` + "`" + `json:"request_id,omitempty"` + "`" + ` - Timestamp time.Time ` + "`" + `json:"timestamp,omitempty"` + "`" + ` -} - -// BaseResponse contains common fields for all responses -type BaseResponse struct { - Status string ` + "`" + `json:"status"` + "`" + ` - Message string ` + "`" + `json:"message"` + "`" + ` - Data interface{} ` + "`" + `json:"data,omitempty"` + "`" + ` - RequestID string ` + "`" + `json:"request_id,omitempty"` + "`" + ` -} - -// ErrorResponse represents an error response -type ErrorResponse struct { - Status string ` + "`" + `json:"status"` + "`" + ` - Message string ` + "`" + `json:"message"` + "`" + ` - RequestID string ` + "`" + `json:"request_id,omitempty"` + "`" + ` -} - -// SuccessResponse represents a success response -type SuccessResponse struct { - Status string ` + "`" + `json:"status"` + "`" + ` - Message string ` + "`" + `json:"message"` + "`" + ` - RequestID string ` + "`" + `json:"request_id,omitempty"` + "`" + ` -} - -// SearchData contains search results -type SearchData struct { - Results []interface{} ` + "`" + `json:"results"` + "`" + ` - Total int ` + "`" + `json:"total"` + "`" + ` - Limit int ` + "`" + `json:"limit"` + "`" + ` - Offset int ` + "`" + `json:"offset"` + "`" + ` -} - -// {{.ServiceName}} Models -// Generated at: {{.Timestamp}} - -// {{.ServiceName}}CreateRequest represents a request to create a {{.ServiceName}} -type {{.ServiceName}}CreateRequest struct { - BaseRequest - // Add fields specific to {{.ServiceName}} creation - Name string ` + "`" + `json:"name" validate:"required"` + "`" + ` - Description string ` + "`" + `json:"description" validate:"omitempty"` + "`" + ` -} - -// {{.ServiceName}}UpdateRequest represents a request to update a {{.ServiceName}} -type {{.ServiceName}}UpdateRequest struct { - BaseRequest - ID string ` + "`" + `json:"id" validate:"required,uuid"` + "`" + ` - Name string ` + "`" + `json:"name" validate:"required"` + "`" + ` - Description string ` + "`" + `json:"description" validate:"omitempty"` + "`" + ` -} - -// {{.ServiceName}}Response represents a response for {{.ServiceName}} operations -type {{.ServiceName}}Response struct { - BaseResponse -} - -// {{.ServiceName}}SearchResponse represents a response for {{.ServiceName}} search -type {{.ServiceName}}SearchResponse struct { - BaseResponse - Data SearchData ` + "`" + `json:"data"` + "`" + ` -} - -// {{.ServiceName}}Data represents the data structure for {{.ServiceName}} -type {{.ServiceName}}Data struct { - ID string ` + "`" + `json:"id"` + "`" + ` - Name string ` + "`" + `json:"name"` + "`" + ` - Description string ` + "`" + `json:"description,omitempty"` + "`" + ` - CreatedAt time.Time ` + "`" + `json:"created_at"` + "`" + ` - UpdatedAt time.Time ` + "`" + `json:"updated_at,omitempty"` + "`" + ` -} - -// Validate validates the {{.ServiceName}}CreateRequest -func (r *{{.ServiceName}}CreateRequest) Validate() error { - validate := validator.New() - return validate.Struct(r) -} - -// Validate validates the {{.ServiceName}}UpdateRequest -func (r *{{.ServiceName}}UpdateRequest) Validate() error { - validate := validator.New() - return validate.Struct(r) -} - -// New{{.ServiceName}}Data creates a new {{.ServiceName}}Data with default values -func New{{.ServiceName}}Data(name, description string) *{{.ServiceName}}Data { - now := time.Now() - return &{{.ServiceName}}Data{ - ID: "", // Will be set by the database - Name: name, - Description: description, - CreatedAt: now, - UpdatedAt: now, - } -}` diff --git a/tools/satusehat/services-config-satusehat.yaml b/tools/satusehat/services-config-satusehat.yaml deleted file mode 100644 index cff7da45..00000000 --- a/tools/satusehat/services-config-satusehat.yaml +++ /dev/null @@ -1,199 +0,0 @@ -# Satu Sehat FHIR Services Configuration -global: - module_name: "api-service" - output_dir: "internal/handlers" - enable_swagger: true - enable_logging: true - enable_metrics: true - enable_auth: true - base_url: "https://api-satusehat-stg.dto.kemkes.go.id/fhir-r4/v1" - version: "1.0.0" - environment: "staging" - fhir_version: "FHIR R4" - profile_url: "https://fhir.kemkes.go.id/r4/StructureDefinition" - -services: - patient: - name: "Patient" - category: "patient" - package: "patient" - description: "FHIR Patient resource management for Satu Sehat ecosystem" - base_url: "https://api-satusehat-stg.dto.kemkes.go.id/fhir-r4/v1" - timeout: 30 - retry_count: 3 - fhir_resource: "Patient" - validation: - enable_fhir_validation: true - required_fields: ["resourceType", "identifier"] - custom_validators: ["validateNIK", "validateKTP"] - authentication: - type: "oauth2" - token_url: "https://api-satusehat-stg.dto.kemkes.go.id/oauth2/v1/accesstoken" - scopes: ["patient.read", "patient.write"] - endpoints: - patient: - basic: - methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "SEARCH"] - get_path: "/:id" - post_path: "" - put_path: "/:id" - patch_path: "/:id" - delete_path: "/:id" - search_path: "" - model: "PatientCreateRequest" - response_model: "PatientResponse" - description: "Manage FHIR Patient resources" - summary: "Patient Resource Management" - tags: ["Patient", "FHIR"] - require_auth: true - cache_enabled: true - cache_ttl: 300 - fhir_profiles: ["https://fhir.kemkes.go.id/r4/StructureDefinition/Patient"] - search_params: ["identifier", "name", "gender", "birthdate", "address"] - - organization: - name: "Organization" - category: "organization" - package: "organization" - description: "FHIR Organization resource management for Satu Sehat ecosystem" - base_url: "https://api-satusehat-stg.dto.kemkes.go.id/fhir-r4/v1" - timeout: 30 - retry_count: 3 - fhir_resource: "Organization" - validation: - enable_fhir_validation: true - required_fields: ["resourceType", "name"] - authentication: - type: "oauth2" - token_url: "https://api-satusehat-stg.dto.kemkes.go.id/oauth2/v1/accesstoken" - scopes: ["organization.read", "organization.write"] - endpoints: - organization: - basic: - methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "SEARCH"] - get_path: "/:id" - post_path: "" - put_path: "/:id" - patch_path: "/:id" - delete_path: "/:id" - search_path: "" - model: "OrganizationCreateRequest" - response_model: "OrganizationResponse" - description: "Manage FHIR Organization resources" - summary: "Organization Resource Management" - tags: ["Organization", "FHIR"] - require_auth: true - cache_enabled: true - cache_ttl: 600 - fhir_profiles: ["https://fhir.kemkes.go.id/r4/StructureDefinition/Organization"] - search_params: ["identifier", "name", "type", "address"] - - practitioner: - name: "Practitioner" - category: "practitioner" - package: "practitioner" - description: "FHIR Practitioner resource management for Satu Sehat ecosystem" - base_url: "https://api-satusehat-stg.dto.kemkes.go.id/fhir-r4/v1" - timeout: 30 - retry_count: 3 - fhir_resource: "Practitioner" - validation: - enable_fhir_validation: true - required_fields: ["resourceType", "name"] - authentication: - type: "oauth2" - token_url: "https://api-satusehat-stg.dto.kemkes.go.id/oauth2/v1/accesstoken" - scopes: ["practitioner.read", "practitioner.write"] - endpoints: - practitioner: - basic: - methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "SEARCH"] - get_path: "/:id" - post_path: "" - put_path: "/:id" - patch_path: "/:id" - delete_path: "/:id" - search_path: "" - model: "PractitionerCreateRequest" - response_model: "PractitionerResponse" - description: "Manage FHIR Practitioner resources" - summary: "Practitioner Resource Management" - tags: ["Practitioner", "FHIR"] - require_auth: true - cache_enabled: true - cache_ttl: 600 - fhir_profiles: ["https://fhir.kemkes.go.id/r4/StructureDefinition/Practitioner"] - search_params: ["identifier", "name", "qualification"] - - encounter: - name: "Encounter" - category: "encounter" - package: "encounter" - description: "FHIR Encounter resource management for Satu Sehat ecosystem" - base_url: "https://api-satusehat-stg.dto.kemkes.go.id/fhir-r4/v1" - timeout: 45 - retry_count: 3 - fhir_resource: "Encounter" - validation: - enable_fhir_validation: true - required_fields: ["resourceType", "status", "subject"] - authentication: - type: "oauth2" - token_url: "https://api-satusehat-stg.dto.kemkes.go.id/oauth2/v1/accesstoken" - scopes: ["encounter.read", "encounter.write"] - endpoints: - encounter: - basic: - methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "SEARCH"] - get_path: "/:id" - post_path: "" - put_path: "/:id" - patch_path: "/:id" - delete_path: "/:id" - search_path: "" - model: "EncounterCreateRequest" - response_model: "EncounterResponse" - description: "Manage FHIR Encounter resources" - summary: "Encounter Resource Management" - tags: ["Encounter", "FHIR"] - require_auth: true - cache_enabled: false - fhir_profiles: ["https://fhir.kemkes.go.id/r4/StructureDefinition/Encounter"] - search_params: ["patient", "subject", "status", "date", "practitioner"] - - observation: - name: "Observation" - category: "observation" - package: "observation" - description: "FHIR Observation resource management for Satu Sehat ecosystem" - base_url: "https://api-satusehat-stg.dto.kemkes.go.id/fhir-r4/v1" - timeout: 30 - retry_count: 3 - fhir_resource: "Observation" - validation: - enable_fhir_validation: true - required_fields: ["resourceType", "status", "code", "subject"] - authentication: - type: "oauth2" - token_url: "https://api-satusehat-stg.dto.kemkes.go.id/oauth2/v1/accesstoken" - scopes: ["observation.read", "observation.write"] - endpoints: - observation: - basic: - methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "SEARCH"] - get_path: "/:id" - post_path: "" - put_path: "/:id" - patch_path: "/:id" - delete_path: "/:id" - search_path: "" - model: "ObservationCreateRequest" - response_model: "ObservationResponse" - description: "Manage FHIR Observation resources" - summary: "Observation Resource Management" - tags: ["Observation", "FHIR"] - require_auth: true - cache_enabled: true - cache_ttl: 180 - fhir_profiles: ["https://fhir.kemkes.go.id/r4/StructureDefinition/Observation"] - search_params: ["patient", "subject", "code", "date", "category"]