diff --git a/README.md b/README.md index 16e878cc..8a3ca3ad 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,8 @@ make build # Build production ### Update Swagger ```bash -swag init -g cmd/api/main.go --parseDependency --parseInternal +swag init -g cmd/api/main.go --parseDependency --parseInternal # Alternative Kedua +swag init -g cmd/api/main.go -o docs/ ``` ## šŸ”§ Environment Variables diff --git a/docs/docs.go b/docs/docs.go index 0a0937d6..85e298ca 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,5 +1,4 @@ -// Code generated by swaggo/swag. DO NOT EDIT. - +// Package docs Code generated by swaggo/swag. DO NOT EDIT package docs import "github.com/swaggo/swag" @@ -25,7 +24,7 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { - "/Peserta/nik/nik/:nik/tglSEP/:tglsep": { + "/Peserta/nik/:nik": { "get": { "security": [ { @@ -42,7 +41,7 @@ const docTemplate = `{ "tags": [ "Peserta" ], - "summary": "Get PesertaBynik data", + "summary": "Get Bynik data", "parameters": [ { "type": "string", @@ -61,46 +60,46 @@ const docTemplate = `{ ], "responses": { "200": { - "description": "Successfully retrieved PesertaBynik data", + "description": "Successfully retrieved Bynik data", "schema": { - "$ref": "#/definitions/internal_handlers_vclaim_peserta.PesertaResponse" + "$ref": "#/definitions/peserta.PesertaResponse" } }, "400": { "description": "Bad request - invalid parameters", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "401": { "description": "Unauthorized - invalid API credentials", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "404": { - "description": "Not found - PesertaBynik not found", + "description": "Not found - Bynik not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } } } } }, - "/Rujukan/:norujukan": { + "/Peserta/nokartu/:nokartu": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "Manage rujukan", + "description": "Get participant eligibility information by card number", "consumes": [ "application/json" ], @@ -108,77 +107,9 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Rujukan" + "Peserta" ], - "summary": "Get RujukanBynorujukan data", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "type": "string", - "example": "\"example_value\"", - "description": "norujukan", - "name": "norujukan", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Successfully retrieved RujukanBynorujukan data", - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim_rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - RujukanBynorujukan not found", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - } - } - } - }, - "/Rujukan/Peserta/:nokartu": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Manage rujukan", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Get RujukanBynokartu data", + "summary": "Get Bynokartu data", "parameters": [ { "type": "string", @@ -197,33 +128,433 @@ const docTemplate = `{ ], "responses": { "200": { - "description": "Successfully retrieved RujukanBynokartu data", + "description": "Successfully retrieved Bynokartu data", "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim_rujukan.RujukanResponse" + "$ref": "#/definitions/peserta.PesertaResponse" } }, "400": { "description": "Bad request - invalid parameters", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "401": { "description": "Unauthorized - invalid API credentials", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "404": { - "description": "Not found - RujukanBynokartu not found", + "description": "Not found - Bynokartu not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$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" } } } @@ -249,7 +580,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.LoginRequest" + "$ref": "#/definitions/models.LoginRequest" } } ], @@ -257,7 +588,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" + "$ref": "#/definitions/models.TokenResponse" } }, "400": { @@ -300,7 +631,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.User" + "$ref": "#/definitions/models.User" } }, "401": { @@ -346,7 +677,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" + "$ref": "#/definitions/models.TokenResponse" } }, "400": { @@ -445,25 +776,25 @@ const docTemplate = `{ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetByIDResponse" + "$ref": "#/definitions/retribusi.RetribusiGetByIDResponse" } }, "400": { "description": "Invalid ID format", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -494,7 +825,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiUpdateRequest" + "$ref": "#/definitions/retribusi.RetribusiUpdateRequest" } } ], @@ -502,25 +833,25 @@ const docTemplate = `{ "200": { "description": "Retribusi updated successfully", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiUpdateResponse" + "$ref": "#/definitions/retribusi.RetribusiUpdateResponse" } }, "400": { "description": "Bad request or validation error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -550,25 +881,25 @@ const docTemplate = `{ "200": { "description": "Retribusi deleted successfully", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiDeleteResponse" + "$ref": "#/definitions/retribusi.RetribusiDeleteResponse" } }, "400": { "description": "Invalid ID format", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -638,19 +969,19 @@ const docTemplate = `{ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse" + "$ref": "#/definitions/retribusi.RetribusiGetResponse" } }, "400": { "description": "Bad request", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -674,7 +1005,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiCreateRequest" + "$ref": "#/definitions/retribusi.RetribusiCreateRequest" } } ], @@ -682,19 +1013,19 @@ const docTemplate = `{ "201": { "description": "Retribusi created successfully", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiCreateResponse" + "$ref": "#/definitions/retribusi.RetribusiCreateResponse" } }, "400": { "description": "Bad request or validation error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -751,19 +1082,19 @@ const docTemplate = `{ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse" + "$ref": "#/definitions/retribusi.RetribusiGetResponse" } }, "400": { "description": "Bad request", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -794,13 +1125,13 @@ const docTemplate = `{ "200": { "description": "Statistics data", "schema": { - "$ref": "#/definitions/api-service_internal_models.AggregateData" + "$ref": "#/definitions/models.AggregateData" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -826,7 +1157,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.LoginRequest" + "$ref": "#/definitions/models.LoginRequest" } } ], @@ -834,7 +1165,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" + "$ref": "#/definitions/models.TokenResponse" } }, "400": { @@ -889,7 +1220,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" + "$ref": "#/definitions/models.TokenResponse" } }, "400": { @@ -904,14 +1235,14 @@ const docTemplate = `{ } } }, - "/peserta/nokartu/:nokartu/tglSEP/:tglsep": { + "/bynokartu/:nokartu": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "Get participant eligibility information by card number", + "description": "Get rujukan by card number", "consumes": [ "application/json" ], @@ -919,9 +1250,9 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Peserta" + "Rujukan" ], - "summary": "Get PesertaBynokartu data", + "summary": "Get Bynokartu data", "parameters": [ { "type": "string", @@ -940,115 +1271,46 @@ const docTemplate = `{ ], "responses": { "200": { - "description": "Successfully retrieved PesertaBynokartu data", + "description": "Successfully retrieved Bynokartu data", "schema": { - "$ref": "#/definitions/internal_handlers_vclaim_peserta.PesertaResponse" + "$ref": "#/definitions/rujukan.RujukanResponse" } }, "400": { "description": "Bad request - invalid parameters", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "401": { "description": "Unauthorized - invalid API credentials", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "404": { - "description": "Not found - PesertaBynokartu not found", + "description": "Not found - Bynokartu not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } } } } }, - "/sep": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Manage SEP (Surat Eligibilitas Peserta)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Sep" - ], - "summary": "Create new SepSep", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "SepSep data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepRequest" - } - } - ], - "responses": { - "201": { - "description": "Successfully created SepSep", - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepResponse" - } - }, - "400": { - "description": "Bad request - invalid request body or validation error", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict - SepSep already exists", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - } - } - } - }, - "/sep/:nosep": { + "/bynorujukan/:norujukan": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "Manage SEP (Surat Eligibilitas Peserta)", + "description": "Get rujukan by nomor rujukan", "consumes": [ "application/json" ], @@ -1056,9 +1318,9 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Sep" + "Rujukan" ], - "summary": "Get SepSep data", + "summary": "Get Bynorujukan data", "parameters": [ { "type": "string", @@ -1069,182 +1331,41 @@ const docTemplate = `{ { "type": "string", "example": "\"example_value\"", - "description": "nosep", - "name": "nosep", + "description": "norujukan", + "name": "norujukan", "in": "path", "required": true } ], "responses": { "200": { - "description": "Successfully retrieved SepSep data", + "description": "Successfully retrieved Bynorujukan data", "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepResponse" + "$ref": "#/definitions/rujukan.RujukanResponse" } }, "400": { "description": "Bad request - invalid parameters", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "401": { "description": "Unauthorized - invalid API credentials", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "404": { - "description": "Not found - SepSep not found", + "description": "Not found - Bynorujukan not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - } - } - }, - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Manage SEP (Surat Eligibilitas Peserta)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Sep" - ], - "summary": "Update existing SepSep", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "type": "string", - "example": "\"example_value\"", - "description": "nosep", - "name": "nosep", - "in": "path", - "required": true - }, - { - "description": "SepSep data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepRequest" - } - } - ], - "responses": { - "200": { - "description": "Successfully updated SepSep", - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters or request body", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - SepSep not found", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - } - } - }, - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Manage SEP (Surat Eligibilitas Peserta)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Sep" - ], - "summary": "Delete existing SepSep", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "type": "string", - "example": "\"example_value\"", - "description": "nosep", - "name": "nosep", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Successfully deleted SepSep", - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - SepSep not found", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } } } @@ -1252,7 +1373,7 @@ const docTemplate = `{ } }, "definitions": { - "api-service_internal_models.AggregateData": { + "models.AggregateData": { "type": "object", "properties": { "by_dinas": { @@ -1293,7 +1414,7 @@ const docTemplate = `{ } } }, - "api-service_internal_models.ErrorResponse": { + "models.ErrorResponse": { "type": "object", "properties": { "code": { @@ -1310,7 +1431,7 @@ const docTemplate = `{ } } }, - "api-service_internal_models.ErrorResponseBpjs": { + "models.ErrorResponseBpjs": { "type": "object", "properties": { "code": { @@ -1331,7 +1452,22 @@ const docTemplate = `{ } } }, - "api-service_internal_models.MetaResponse": { + "models.LoginRequest": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "models.MetaResponse": { "type": "object", "properties": { "current_page": { @@ -1357,7 +1493,7 @@ const docTemplate = `{ } } }, - "api-service_internal_models.NullableInt32": { + "models.NullableInt32": { "type": "object", "properties": { "int32": { @@ -1368,22 +1504,29 @@ const docTemplate = `{ } } }, - "api-service_internal_models_auth.LoginRequest": { + "models.NullableString": { "type": "object", - "required": [ - "password", - "username" - ], "properties": { - "password": { + "string": { "type": "string" }, - "username": { - "type": "string" + "valid": { + "type": "boolean" } } }, - "api-service_internal_models_auth.TokenResponse": { + "models.NullableTime": { + "type": "object", + "properties": { + "time": { + "type": "string" + }, + "valid": { + "type": "boolean" + } + } + }, + "models.TokenResponse": { "type": "object", "properties": { "access_token": { @@ -1397,7 +1540,7 @@ const docTemplate = `{ } } }, - "api-service_internal_models_auth.User": { + "models.User": { "type": "object", "properties": { "email": { @@ -1414,291 +1557,7 @@ const docTemplate = `{ } } }, - "api-service_internal_models_retribusi.Retribusi": { - "type": "object", - "properties": { - "date_created": { - "$ref": "#/definitions/sql.NullTime" - }, - "date_updated": { - "$ref": "#/definitions/sql.NullTime" - }, - "dinas": { - "$ref": "#/definitions/sql.NullString" - }, - "id": { - "type": "string" - }, - "jenis": { - "$ref": "#/definitions/sql.NullString" - }, - "kelompok_obyek": { - "$ref": "#/definitions/sql.NullString" - }, - "kode_tarif": { - "$ref": "#/definitions/sql.NullString" - }, - "pelayanan": { - "$ref": "#/definitions/sql.NullString" - }, - "rekening_denda": { - "$ref": "#/definitions/sql.NullString" - }, - "rekening_pokok": { - "$ref": "#/definitions/sql.NullString" - }, - "satuan": { - "$ref": "#/definitions/sql.NullString" - }, - "satuan_overtime": { - "$ref": "#/definitions/sql.NullString" - }, - "sort": { - "$ref": "#/definitions/api-service_internal_models.NullableInt32" - }, - "status": { - "type": "string" - }, - "tarif": { - "$ref": "#/definitions/sql.NullString" - }, - "tarif_overtime": { - "$ref": "#/definitions/sql.NullString" - }, - "uraian_1": { - "$ref": "#/definitions/sql.NullString" - }, - "uraian_2": { - "$ref": "#/definitions/sql.NullString" - }, - "uraian_3": { - "$ref": "#/definitions/sql.NullString" - }, - "user_created": { - "$ref": "#/definitions/sql.NullString" - }, - "user_updated": { - "$ref": "#/definitions/sql.NullString" - } - } - }, - "api-service_internal_models_retribusi.RetribusiCreateRequest": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "dinas": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "jenis": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kelompok_obyek": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kode_tarif": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "pelayanan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_denda": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_pokok": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan_overtime": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "status": { - "type": "string", - "enum": [ - "draft", - "active", - "inactive" - ] - }, - "tarif": { - "type": "string" - }, - "tarif_overtime": { - "type": "string" - }, - "uraian_1": { - "type": "string" - }, - "uraian_2": { - "type": "string" - }, - "uraian_3": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiCreateResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/api-service_internal_models_retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiDeleteResponse": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "message": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiGetByIDResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/api-service_internal_models_retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiGetResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/api-service_internal_models_retribusi.Retribusi" - } - }, - "message": { - "type": "string" - }, - "meta": { - "$ref": "#/definitions/api-service_internal_models.MetaResponse" - }, - "summary": { - "$ref": "#/definitions/api-service_internal_models.AggregateData" - } - } - }, - "api-service_internal_models_retribusi.RetribusiUpdateRequest": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "dinas": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "jenis": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kelompok_obyek": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kode_tarif": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "pelayanan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_denda": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_pokok": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan_overtime": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "status": { - "type": "string", - "enum": [ - "draft", - "active", - "inactive" - ] - }, - "tarif": { - "type": "string" - }, - "tarif_overtime": { - "type": "string" - }, - "uraian_1": { - "type": "string" - }, - "uraian_2": { - "type": "string" - }, - "uraian_3": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiUpdateResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/api-service_internal_models_retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "api-service_internal_models_vclaim_peserta.PesertaData": { + "peserta.PesertaData": { "type": "object", "properties": { "cob": { @@ -1816,380 +1675,11 @@ const docTemplate = `{ } } }, - "api-service_internal_models_vclaim_rujukan.RujukanData": { - "type": "object", - "properties": { - "diagnosa": { - "type": "object", - "properties": { - "kdDiagnosa": { - "type": "string" - }, - "nmDiagnosa": { - "type": "string" - } - } - }, - "kelasRawat": { - "type": "string" - }, - "nama": { - "type": "string" - }, - "noKartu": { - "type": "string" - }, - "noRujukan": { - "type": "string" - }, - "pelayanan": { - "type": "string" - }, - "poliRujukan": { - "type": "object", - "properties": { - "kdPoli": { - "type": "string" - }, - "nmPoli": { - "type": "string" - } - } - }, - "provPerujuk": { - "type": "object", - "properties": { - "kdProvider": { - "type": "string" - }, - "nmProvider": { - "type": "string" - } - } - }, - "statusRujukan": { - "type": "string" - }, - "tglRujukan": { - "type": "string" - } - } - }, - "api-service_internal_models_vclaim_rujukan.RujukanResponse": { + "peserta.PesertaResponse": { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api-service_internal_models_vclaim_rujukan.RujukanData" - }, - "list": { - "type": "array", - "items": { - "$ref": "#/definitions/api-service_internal_models_vclaim_rujukan.RujukanData" - } - }, - "message": { - "type": "string" - }, - "request_id": { - "type": "string" - }, - "status": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - } - }, - "api-service_internal_models_vclaim_sep.SepData": { - "type": "object", - "properties": { - "catatan": { - "type": "string" - }, - "diagnosa": { - "type": "string" - }, - "informasi": { - "type": "object", - "properties": { - "dpjpLayan": { - "type": "string" - }, - "noSKDP": { - "type": "string" - }, - "noTelp": { - "type": "string" - }, - "subSpesialis": { - "type": "string" - } - } - }, - "jnsPelayanan": { - "type": "string" - }, - "klsRawat": { - "type": "string" - }, - "noMR": { - "type": "string" - }, - "noSep": { - "type": "string" - }, - "peserta": { - "$ref": "#/definitions/api-service_internal_models_vclaim_peserta.PesertaData" - }, - "poli": { - "type": "string" - }, - "rujukan": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepRujukan" - }, - "tglSep": { - "type": "string" - } - } - }, - "api-service_internal_models_vclaim_sep.SepRequest": { - "type": "object", - "required": [ - "diagnosa", - "jnsPelayanan", - "klsRawat", - "noKartu", - "noMR", - "poli", - "ppkPelayanan", - "tglSep", - "user" - ], - "properties": { - "catatan": { - "type": "string" - }, - "diagnosa": { - "type": "string" - }, - "jnsPelayanan": { - "type": "string", - "enum": [ - "1", - "2" - ] - }, - "klsRawat": { - "type": "string", - "enum": [ - "1", - "2", - "3" - ] - }, - "noKartu": { - "type": "string" - }, - "noMR": { - "type": "string" - }, - "noTelp": { - "type": "string" - }, - "poli": { - "type": "string" - }, - "ppkPelayanan": { - "type": "string" - }, - "request_id": { - "type": "string" - }, - "rujukan": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepRujukan" - }, - "tglSep": { - "type": "string" - }, - "timestamp": { - "type": "string" - }, - "user": { - "type": "string" - } - } - }, - "api-service_internal_models_vclaim_sep.SepResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepData" - }, - "message": { - "type": "string" - }, - "request_id": { - "type": "string" - }, - "status": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - } - }, - "api-service_internal_models_vclaim_sep.SepRujukan": { - "type": "object", - "required": [ - "asalRujukan", - "noRujukan", - "ppkRujukan", - "tglRujukan" - ], - "properties": { - "asalRujukan": { - "type": "string", - "enum": [ - "1", - "2" - ] - }, - "noRujukan": { - "type": "string" - }, - "ppkRujukan": { - "type": "string" - }, - "tglRujukan": { - "type": "string" - } - } - }, - "internal_handlers_vclaim_peserta.PesertaData": { - "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": {}, - "eSEP": {}, - "noSKTM": {}, - "prolanisPRB": { - "type": "string" - } - } - }, - "jenisPeserta": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "mr": { - "type": "object", - "properties": { - "noMR": { - "type": "string" - }, - "noTelepon": { - "type": "string" - } - } - }, - "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" - } - } - } - } - }, - "internal_handlers_vclaim_peserta.PesertaResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/internal_handlers_vclaim_peserta.PesertaData" + "$ref": "#/definitions/peserta.PesertaData" }, "message": { "type": "string" @@ -2206,27 +1696,520 @@ const docTemplate = `{ } } }, - "sql.NullString": { + "retribusi.Retribusi": { "type": "object", "properties": { - "string": { + "date_created": { + "$ref": "#/definitions/models.NullableTime" + }, + "date_updated": { + "$ref": "#/definitions/models.NullableTime" + }, + "dinas": { + "$ref": "#/definitions/models.NullableString" + }, + "id": { "type": "string" }, - "valid": { - "description": "Valid is true if String is not NULL", - "type": "boolean" + "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" } } }, - "sql.NullTime": { + "retribusi.RetribusiCreateRequest": { "type": "object", + "required": [ + "status" + ], "properties": { - "time": { + "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" }, - "valid": { - "description": "Valid is true if Time is not NULL", - "type": "boolean" + "tarif_overtime": { + "type": "string" + }, + "uraian_1": { + "type": "string" + }, + "uraian_2": { + "type": "string" + }, + "uraian_3": { + "type": "string" + } + } + }, + "retribusi.RetribusiCreateResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/retribusi.Retribusi" + }, + "message": { + "type": "string" + } + } + }, + "retribusi.RetribusiDeleteResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "retribusi.RetribusiGetByIDResponse": { + "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": { + "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" } } } @@ -2243,6 +2226,8 @@ var SwaggerInfo = &swag.Spec{ Description: "A comprehensive Go API service with Swagger documentation", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", } func init() { diff --git a/docs/swagger.json b/docs/swagger.json index 1c847b7d..4636193f 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -22,7 +22,7 @@ "host": "localhost:8080", "basePath": "/api/v1", "paths": { - "/Peserta/nik/nik/:nik/tglSEP/:tglsep": { + "/Peserta/nik/:nik": { "get": { "security": [ { @@ -39,7 +39,7 @@ "tags": [ "Peserta" ], - "summary": "Get PesertaBynik data", + "summary": "Get Bynik data", "parameters": [ { "type": "string", @@ -58,46 +58,46 @@ ], "responses": { "200": { - "description": "Successfully retrieved PesertaBynik data", + "description": "Successfully retrieved Bynik data", "schema": { - "$ref": "#/definitions/internal_handlers_vclaim_peserta.PesertaResponse" + "$ref": "#/definitions/peserta.PesertaResponse" } }, "400": { "description": "Bad request - invalid parameters", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "401": { "description": "Unauthorized - invalid API credentials", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "404": { - "description": "Not found - PesertaBynik not found", + "description": "Not found - Bynik not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } } } } }, - "/Rujukan/:norujukan": { + "/Peserta/nokartu/:nokartu": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "Manage rujukan", + "description": "Get participant eligibility information by card number", "consumes": [ "application/json" ], @@ -105,77 +105,9 @@ "application/json" ], "tags": [ - "Rujukan" + "Peserta" ], - "summary": "Get RujukanBynorujukan data", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "type": "string", - "example": "\"example_value\"", - "description": "norujukan", - "name": "norujukan", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Successfully retrieved RujukanBynorujukan data", - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim_rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - RujukanBynorujukan not found", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - } - } - } - }, - "/Rujukan/Peserta/:nokartu": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Manage rujukan", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Get RujukanBynokartu data", + "summary": "Get Bynokartu data", "parameters": [ { "type": "string", @@ -194,33 +126,433 @@ ], "responses": { "200": { - "description": "Successfully retrieved RujukanBynokartu data", + "description": "Successfully retrieved Bynokartu data", "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim_rujukan.RujukanResponse" + "$ref": "#/definitions/peserta.PesertaResponse" } }, "400": { "description": "Bad request - invalid parameters", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "401": { "description": "Unauthorized - invalid API credentials", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "404": { - "description": "Not found - RujukanBynokartu not found", + "description": "Not found - Bynokartu not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$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" } } } @@ -246,7 +578,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.LoginRequest" + "$ref": "#/definitions/models.LoginRequest" } } ], @@ -254,7 +586,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" + "$ref": "#/definitions/models.TokenResponse" } }, "400": { @@ -297,7 +629,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.User" + "$ref": "#/definitions/models.User" } }, "401": { @@ -343,7 +675,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" + "$ref": "#/definitions/models.TokenResponse" } }, "400": { @@ -442,25 +774,25 @@ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetByIDResponse" + "$ref": "#/definitions/retribusi.RetribusiGetByIDResponse" } }, "400": { "description": "Invalid ID format", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -491,7 +823,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiUpdateRequest" + "$ref": "#/definitions/retribusi.RetribusiUpdateRequest" } } ], @@ -499,25 +831,25 @@ "200": { "description": "Retribusi updated successfully", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiUpdateResponse" + "$ref": "#/definitions/retribusi.RetribusiUpdateResponse" } }, "400": { "description": "Bad request or validation error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -547,25 +879,25 @@ "200": { "description": "Retribusi deleted successfully", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiDeleteResponse" + "$ref": "#/definitions/retribusi.RetribusiDeleteResponse" } }, "400": { "description": "Invalid ID format", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "404": { "description": "Retribusi not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -635,19 +967,19 @@ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse" + "$ref": "#/definitions/retribusi.RetribusiGetResponse" } }, "400": { "description": "Bad request", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -671,7 +1003,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiCreateRequest" + "$ref": "#/definitions/retribusi.RetribusiCreateRequest" } } ], @@ -679,19 +1011,19 @@ "201": { "description": "Retribusi created successfully", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiCreateResponse" + "$ref": "#/definitions/retribusi.RetribusiCreateResponse" } }, "400": { "description": "Bad request or validation error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -748,19 +1080,19 @@ "200": { "description": "Success response", "schema": { - "$ref": "#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse" + "$ref": "#/definitions/retribusi.RetribusiGetResponse" } }, "400": { "description": "Bad request", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -791,13 +1123,13 @@ "200": { "description": "Statistics data", "schema": { - "$ref": "#/definitions/api-service_internal_models.AggregateData" + "$ref": "#/definitions/models.AggregateData" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponse" + "$ref": "#/definitions/models.ErrorResponse" } } } @@ -823,7 +1155,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.LoginRequest" + "$ref": "#/definitions/models.LoginRequest" } } ], @@ -831,7 +1163,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" + "$ref": "#/definitions/models.TokenResponse" } }, "400": { @@ -886,7 +1218,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" + "$ref": "#/definitions/models.TokenResponse" } }, "400": { @@ -901,14 +1233,14 @@ } } }, - "/peserta/nokartu/:nokartu/tglSEP/:tglsep": { + "/bynokartu/:nokartu": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "Get participant eligibility information by card number", + "description": "Get rujukan by card number", "consumes": [ "application/json" ], @@ -916,9 +1248,9 @@ "application/json" ], "tags": [ - "Peserta" + "Rujukan" ], - "summary": "Get PesertaBynokartu data", + "summary": "Get Bynokartu data", "parameters": [ { "type": "string", @@ -937,115 +1269,46 @@ ], "responses": { "200": { - "description": "Successfully retrieved PesertaBynokartu data", + "description": "Successfully retrieved Bynokartu data", "schema": { - "$ref": "#/definitions/internal_handlers_vclaim_peserta.PesertaResponse" + "$ref": "#/definitions/rujukan.RujukanResponse" } }, "400": { "description": "Bad request - invalid parameters", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "401": { "description": "Unauthorized - invalid API credentials", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "404": { - "description": "Not found - PesertaBynokartu not found", + "description": "Not found - Bynokartu not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } } } } }, - "/sep": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Manage SEP (Surat Eligibilitas Peserta)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Sep" - ], - "summary": "Create new SepSep", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "SepSep data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepRequest" - } - } - ], - "responses": { - "201": { - "description": "Successfully created SepSep", - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepResponse" - } - }, - "400": { - "description": "Bad request - invalid request body or validation error", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict - SepSep already exists", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - } - } - } - }, - "/sep/:nosep": { + "/bynorujukan/:norujukan": { "get": { "security": [ { "ApiKeyAuth": [] } ], - "description": "Manage SEP (Surat Eligibilitas Peserta)", + "description": "Get rujukan by nomor rujukan", "consumes": [ "application/json" ], @@ -1053,9 +1316,9 @@ "application/json" ], "tags": [ - "Sep" + "Rujukan" ], - "summary": "Get SepSep data", + "summary": "Get Bynorujukan data", "parameters": [ { "type": "string", @@ -1066,182 +1329,41 @@ { "type": "string", "example": "\"example_value\"", - "description": "nosep", - "name": "nosep", + "description": "norujukan", + "name": "norujukan", "in": "path", "required": true } ], "responses": { "200": { - "description": "Successfully retrieved SepSep data", + "description": "Successfully retrieved Bynorujukan data", "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepResponse" + "$ref": "#/definitions/rujukan.RujukanResponse" } }, "400": { "description": "Bad request - invalid parameters", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "401": { "description": "Unauthorized - invalid API credentials", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "404": { - "description": "Not found - SepSep not found", + "description": "Not found - Bynorujukan not found", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } }, "500": { "description": "Internal server error", "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - } - } - }, - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Manage SEP (Surat Eligibilitas Peserta)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Sep" - ], - "summary": "Update existing SepSep", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "type": "string", - "example": "\"example_value\"", - "description": "nosep", - "name": "nosep", - "in": "path", - "required": true - }, - { - "description": "SepSep data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepRequest" - } - } - ], - "responses": { - "200": { - "description": "Successfully updated SepSep", - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters or request body", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - SepSep not found", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - } - } - }, - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Manage SEP (Surat Eligibilitas Peserta)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Sep" - ], - "summary": "Delete existing SepSep", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "type": "string", - "example": "\"example_value\"", - "description": "nosep", - "name": "nosep", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Successfully deleted SepSep", - "schema": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - SepSep not found", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/api-service_internal_models.ErrorResponseBpjs" + "$ref": "#/definitions/models.ErrorResponseBpjs" } } } @@ -1249,7 +1371,7 @@ } }, "definitions": { - "api-service_internal_models.AggregateData": { + "models.AggregateData": { "type": "object", "properties": { "by_dinas": { @@ -1290,7 +1412,7 @@ } } }, - "api-service_internal_models.ErrorResponse": { + "models.ErrorResponse": { "type": "object", "properties": { "code": { @@ -1307,7 +1429,7 @@ } } }, - "api-service_internal_models.ErrorResponseBpjs": { + "models.ErrorResponseBpjs": { "type": "object", "properties": { "code": { @@ -1328,7 +1450,22 @@ } } }, - "api-service_internal_models.MetaResponse": { + "models.LoginRequest": { + "type": "object", + "required": [ + "password", + "username" + ], + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "models.MetaResponse": { "type": "object", "properties": { "current_page": { @@ -1354,7 +1491,7 @@ } } }, - "api-service_internal_models.NullableInt32": { + "models.NullableInt32": { "type": "object", "properties": { "int32": { @@ -1365,22 +1502,29 @@ } } }, - "api-service_internal_models_auth.LoginRequest": { + "models.NullableString": { "type": "object", - "required": [ - "password", - "username" - ], "properties": { - "password": { + "string": { "type": "string" }, - "username": { - "type": "string" + "valid": { + "type": "boolean" } } }, - "api-service_internal_models_auth.TokenResponse": { + "models.NullableTime": { + "type": "object", + "properties": { + "time": { + "type": "string" + }, + "valid": { + "type": "boolean" + } + } + }, + "models.TokenResponse": { "type": "object", "properties": { "access_token": { @@ -1394,7 +1538,7 @@ } } }, - "api-service_internal_models_auth.User": { + "models.User": { "type": "object", "properties": { "email": { @@ -1411,291 +1555,7 @@ } } }, - "api-service_internal_models_retribusi.Retribusi": { - "type": "object", - "properties": { - "date_created": { - "$ref": "#/definitions/sql.NullTime" - }, - "date_updated": { - "$ref": "#/definitions/sql.NullTime" - }, - "dinas": { - "$ref": "#/definitions/sql.NullString" - }, - "id": { - "type": "string" - }, - "jenis": { - "$ref": "#/definitions/sql.NullString" - }, - "kelompok_obyek": { - "$ref": "#/definitions/sql.NullString" - }, - "kode_tarif": { - "$ref": "#/definitions/sql.NullString" - }, - "pelayanan": { - "$ref": "#/definitions/sql.NullString" - }, - "rekening_denda": { - "$ref": "#/definitions/sql.NullString" - }, - "rekening_pokok": { - "$ref": "#/definitions/sql.NullString" - }, - "satuan": { - "$ref": "#/definitions/sql.NullString" - }, - "satuan_overtime": { - "$ref": "#/definitions/sql.NullString" - }, - "sort": { - "$ref": "#/definitions/api-service_internal_models.NullableInt32" - }, - "status": { - "type": "string" - }, - "tarif": { - "$ref": "#/definitions/sql.NullString" - }, - "tarif_overtime": { - "$ref": "#/definitions/sql.NullString" - }, - "uraian_1": { - "$ref": "#/definitions/sql.NullString" - }, - "uraian_2": { - "$ref": "#/definitions/sql.NullString" - }, - "uraian_3": { - "$ref": "#/definitions/sql.NullString" - }, - "user_created": { - "$ref": "#/definitions/sql.NullString" - }, - "user_updated": { - "$ref": "#/definitions/sql.NullString" - } - } - }, - "api-service_internal_models_retribusi.RetribusiCreateRequest": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "dinas": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "jenis": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kelompok_obyek": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kode_tarif": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "pelayanan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_denda": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_pokok": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan_overtime": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "status": { - "type": "string", - "enum": [ - "draft", - "active", - "inactive" - ] - }, - "tarif": { - "type": "string" - }, - "tarif_overtime": { - "type": "string" - }, - "uraian_1": { - "type": "string" - }, - "uraian_2": { - "type": "string" - }, - "uraian_3": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiCreateResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/api-service_internal_models_retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiDeleteResponse": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "message": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiGetByIDResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/api-service_internal_models_retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiGetResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/api-service_internal_models_retribusi.Retribusi" - } - }, - "message": { - "type": "string" - }, - "meta": { - "$ref": "#/definitions/api-service_internal_models.MetaResponse" - }, - "summary": { - "$ref": "#/definitions/api-service_internal_models.AggregateData" - } - } - }, - "api-service_internal_models_retribusi.RetribusiUpdateRequest": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "dinas": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "jenis": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kelompok_obyek": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kode_tarif": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "pelayanan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_denda": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_pokok": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan_overtime": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "status": { - "type": "string", - "enum": [ - "draft", - "active", - "inactive" - ] - }, - "tarif": { - "type": "string" - }, - "tarif_overtime": { - "type": "string" - }, - "uraian_1": { - "type": "string" - }, - "uraian_2": { - "type": "string" - }, - "uraian_3": { - "type": "string" - } - } - }, - "api-service_internal_models_retribusi.RetribusiUpdateResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/api-service_internal_models_retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "api-service_internal_models_vclaim_peserta.PesertaData": { + "peserta.PesertaData": { "type": "object", "properties": { "cob": { @@ -1813,380 +1673,11 @@ } } }, - "api-service_internal_models_vclaim_rujukan.RujukanData": { - "type": "object", - "properties": { - "diagnosa": { - "type": "object", - "properties": { - "kdDiagnosa": { - "type": "string" - }, - "nmDiagnosa": { - "type": "string" - } - } - }, - "kelasRawat": { - "type": "string" - }, - "nama": { - "type": "string" - }, - "noKartu": { - "type": "string" - }, - "noRujukan": { - "type": "string" - }, - "pelayanan": { - "type": "string" - }, - "poliRujukan": { - "type": "object", - "properties": { - "kdPoli": { - "type": "string" - }, - "nmPoli": { - "type": "string" - } - } - }, - "provPerujuk": { - "type": "object", - "properties": { - "kdProvider": { - "type": "string" - }, - "nmProvider": { - "type": "string" - } - } - }, - "statusRujukan": { - "type": "string" - }, - "tglRujukan": { - "type": "string" - } - } - }, - "api-service_internal_models_vclaim_rujukan.RujukanResponse": { + "peserta.PesertaResponse": { "type": "object", "properties": { "data": { - "$ref": "#/definitions/api-service_internal_models_vclaim_rujukan.RujukanData" - }, - "list": { - "type": "array", - "items": { - "$ref": "#/definitions/api-service_internal_models_vclaim_rujukan.RujukanData" - } - }, - "message": { - "type": "string" - }, - "request_id": { - "type": "string" - }, - "status": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - } - }, - "api-service_internal_models_vclaim_sep.SepData": { - "type": "object", - "properties": { - "catatan": { - "type": "string" - }, - "diagnosa": { - "type": "string" - }, - "informasi": { - "type": "object", - "properties": { - "dpjpLayan": { - "type": "string" - }, - "noSKDP": { - "type": "string" - }, - "noTelp": { - "type": "string" - }, - "subSpesialis": { - "type": "string" - } - } - }, - "jnsPelayanan": { - "type": "string" - }, - "klsRawat": { - "type": "string" - }, - "noMR": { - "type": "string" - }, - "noSep": { - "type": "string" - }, - "peserta": { - "$ref": "#/definitions/api-service_internal_models_vclaim_peserta.PesertaData" - }, - "poli": { - "type": "string" - }, - "rujukan": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepRujukan" - }, - "tglSep": { - "type": "string" - } - } - }, - "api-service_internal_models_vclaim_sep.SepRequest": { - "type": "object", - "required": [ - "diagnosa", - "jnsPelayanan", - "klsRawat", - "noKartu", - "noMR", - "poli", - "ppkPelayanan", - "tglSep", - "user" - ], - "properties": { - "catatan": { - "type": "string" - }, - "diagnosa": { - "type": "string" - }, - "jnsPelayanan": { - "type": "string", - "enum": [ - "1", - "2" - ] - }, - "klsRawat": { - "type": "string", - "enum": [ - "1", - "2", - "3" - ] - }, - "noKartu": { - "type": "string" - }, - "noMR": { - "type": "string" - }, - "noTelp": { - "type": "string" - }, - "poli": { - "type": "string" - }, - "ppkPelayanan": { - "type": "string" - }, - "request_id": { - "type": "string" - }, - "rujukan": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepRujukan" - }, - "tglSep": { - "type": "string" - }, - "timestamp": { - "type": "string" - }, - "user": { - "type": "string" - } - } - }, - "api-service_internal_models_vclaim_sep.SepResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepData" - }, - "message": { - "type": "string" - }, - "request_id": { - "type": "string" - }, - "status": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - } - }, - "api-service_internal_models_vclaim_sep.SepRujukan": { - "type": "object", - "required": [ - "asalRujukan", - "noRujukan", - "ppkRujukan", - "tglRujukan" - ], - "properties": { - "asalRujukan": { - "type": "string", - "enum": [ - "1", - "2" - ] - }, - "noRujukan": { - "type": "string" - }, - "ppkRujukan": { - "type": "string" - }, - "tglRujukan": { - "type": "string" - } - } - }, - "internal_handlers_vclaim_peserta.PesertaData": { - "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": {}, - "eSEP": {}, - "noSKTM": {}, - "prolanisPRB": { - "type": "string" - } - } - }, - "jenisPeserta": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "mr": { - "type": "object", - "properties": { - "noMR": { - "type": "string" - }, - "noTelepon": { - "type": "string" - } - } - }, - "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" - } - } - } - } - }, - "internal_handlers_vclaim_peserta.PesertaResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/internal_handlers_vclaim_peserta.PesertaData" + "$ref": "#/definitions/peserta.PesertaData" }, "message": { "type": "string" @@ -2203,27 +1694,520 @@ } } }, - "sql.NullString": { + "retribusi.Retribusi": { "type": "object", "properties": { - "string": { + "date_created": { + "$ref": "#/definitions/models.NullableTime" + }, + "date_updated": { + "$ref": "#/definitions/models.NullableTime" + }, + "dinas": { + "$ref": "#/definitions/models.NullableString" + }, + "id": { "type": "string" }, - "valid": { - "description": "Valid is true if String is not NULL", - "type": "boolean" + "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" } } }, - "sql.NullTime": { + "retribusi.RetribusiCreateRequest": { "type": "object", + "required": [ + "status" + ], "properties": { - "time": { + "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" }, - "valid": { - "description": "Valid is true if Time is not NULL", - "type": "boolean" + "tarif_overtime": { + "type": "string" + }, + "uraian_1": { + "type": "string" + }, + "uraian_2": { + "type": "string" + }, + "uraian_3": { + "type": "string" + } + } + }, + "retribusi.RetribusiCreateResponse": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/retribusi.Retribusi" + }, + "message": { + "type": "string" + } + } + }, + "retribusi.RetribusiDeleteResponse": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "retribusi.RetribusiGetByIDResponse": { + "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": { + "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" } } } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 03796b60..596dcf7d 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,6 +1,6 @@ basePath: /api/v1 definitions: - api-service_internal_models.AggregateData: + models.AggregateData: properties: by_dinas: additionalProperties: @@ -27,7 +27,7 @@ definitions: updated_today: type: integer type: object - api-service_internal_models.ErrorResponse: + models.ErrorResponse: properties: code: type: integer @@ -38,7 +38,7 @@ definitions: timestamp: type: string type: object - api-service_internal_models.ErrorResponseBpjs: + models.ErrorResponseBpjs: properties: code: type: string @@ -52,7 +52,17 @@ definitions: status: type: string type: object - api-service_internal_models.MetaResponse: + models.LoginRequest: + properties: + password: + type: string + username: + type: string + required: + - password + - username + type: object + models.MetaResponse: properties: current_page: type: integer @@ -69,24 +79,28 @@ definitions: total_pages: type: integer type: object - api-service_internal_models.NullableInt32: + models.NullableInt32: properties: int32: type: integer valid: type: boolean type: object - api-service_internal_models_auth.LoginRequest: + models.NullableString: properties: - password: + string: type: string - username: - type: string - required: - - password - - username + valid: + type: boolean type: object - api-service_internal_models_auth.TokenResponse: + models.NullableTime: + properties: + time: + type: string + valid: + type: boolean + type: object + models.TokenResponse: properties: access_token: type: string @@ -95,7 +109,7 @@ definitions: token_type: type: string type: object - api-service_internal_models_auth.User: + models.User: properties: email: type: string @@ -106,207 +120,7 @@ definitions: username: type: string type: object - api-service_internal_models_retribusi.Retribusi: - properties: - date_created: - $ref: '#/definitions/sql.NullTime' - date_updated: - $ref: '#/definitions/sql.NullTime' - dinas: - $ref: '#/definitions/sql.NullString' - id: - type: string - jenis: - $ref: '#/definitions/sql.NullString' - kelompok_obyek: - $ref: '#/definitions/sql.NullString' - kode_tarif: - $ref: '#/definitions/sql.NullString' - pelayanan: - $ref: '#/definitions/sql.NullString' - rekening_denda: - $ref: '#/definitions/sql.NullString' - rekening_pokok: - $ref: '#/definitions/sql.NullString' - satuan: - $ref: '#/definitions/sql.NullString' - satuan_overtime: - $ref: '#/definitions/sql.NullString' - sort: - $ref: '#/definitions/api-service_internal_models.NullableInt32' - status: - type: string - tarif: - $ref: '#/definitions/sql.NullString' - tarif_overtime: - $ref: '#/definitions/sql.NullString' - uraian_1: - $ref: '#/definitions/sql.NullString' - uraian_2: - $ref: '#/definitions/sql.NullString' - uraian_3: - $ref: '#/definitions/sql.NullString' - user_created: - $ref: '#/definitions/sql.NullString' - user_updated: - $ref: '#/definitions/sql.NullString' - type: object - api-service_internal_models_retribusi.RetribusiCreateRequest: - properties: - dinas: - maxLength: 255 - minLength: 1 - type: string - jenis: - maxLength: 255 - minLength: 1 - type: string - kelompok_obyek: - maxLength: 255 - minLength: 1 - type: string - kode_tarif: - maxLength: 255 - minLength: 1 - type: string - pelayanan: - maxLength: 255 - minLength: 1 - type: string - rekening_denda: - maxLength: 255 - minLength: 1 - type: string - rekening_pokok: - maxLength: 255 - minLength: 1 - type: string - satuan: - maxLength: 255 - minLength: 1 - type: string - satuan_overtime: - maxLength: 255 - minLength: 1 - type: string - status: - enum: - - draft - - active - - inactive - type: string - tarif: - type: string - tarif_overtime: - type: string - uraian_1: - type: string - uraian_2: - type: string - uraian_3: - type: string - required: - - status - type: object - api-service_internal_models_retribusi.RetribusiCreateResponse: - properties: - data: - $ref: '#/definitions/api-service_internal_models_retribusi.Retribusi' - message: - type: string - type: object - api-service_internal_models_retribusi.RetribusiDeleteResponse: - properties: - id: - type: string - message: - type: string - type: object - api-service_internal_models_retribusi.RetribusiGetByIDResponse: - properties: - data: - $ref: '#/definitions/api-service_internal_models_retribusi.Retribusi' - message: - type: string - type: object - api-service_internal_models_retribusi.RetribusiGetResponse: - properties: - data: - items: - $ref: '#/definitions/api-service_internal_models_retribusi.Retribusi' - type: array - message: - type: string - meta: - $ref: '#/definitions/api-service_internal_models.MetaResponse' - summary: - $ref: '#/definitions/api-service_internal_models.AggregateData' - type: object - api-service_internal_models_retribusi.RetribusiUpdateRequest: - properties: - dinas: - maxLength: 255 - minLength: 1 - type: string - jenis: - maxLength: 255 - minLength: 1 - type: string - kelompok_obyek: - maxLength: 255 - minLength: 1 - type: string - kode_tarif: - maxLength: 255 - minLength: 1 - type: string - pelayanan: - maxLength: 255 - minLength: 1 - type: string - rekening_denda: - maxLength: 255 - minLength: 1 - type: string - rekening_pokok: - maxLength: 255 - minLength: 1 - type: string - satuan: - maxLength: 255 - minLength: 1 - type: string - satuan_overtime: - maxLength: 255 - minLength: 1 - type: string - status: - enum: - - draft - - active - - inactive - type: string - tarif: - type: string - tarif_overtime: - type: string - uraian_1: - type: string - uraian_2: - type: string - uraian_3: - type: string - required: - - status - type: object - api-service_internal_models_retribusi.RetribusiUpdateResponse: - properties: - data: - $ref: '#/definitions/api-service_internal_models_retribusi.Retribusi' - message: - type: string - type: object - api-service_internal_models_vclaim_peserta.PesertaData: + peserta.PesertaData: properties: cob: properties: @@ -384,257 +198,10 @@ definitions: type: string type: object type: object - api-service_internal_models_vclaim_rujukan.RujukanData: - properties: - diagnosa: - properties: - kdDiagnosa: - type: string - nmDiagnosa: - type: string - type: object - kelasRawat: - type: string - nama: - type: string - noKartu: - type: string - noRujukan: - type: string - pelayanan: - type: string - poliRujukan: - properties: - kdPoli: - type: string - nmPoli: - type: string - type: object - provPerujuk: - properties: - kdProvider: - type: string - nmProvider: - type: string - type: object - statusRujukan: - type: string - tglRujukan: - type: string - type: object - api-service_internal_models_vclaim_rujukan.RujukanResponse: + peserta.PesertaResponse: properties: data: - $ref: '#/definitions/api-service_internal_models_vclaim_rujukan.RujukanData' - list: - items: - $ref: '#/definitions/api-service_internal_models_vclaim_rujukan.RujukanData' - type: array - message: - type: string - request_id: - type: string - status: - type: string - timestamp: - type: string - type: object - api-service_internal_models_vclaim_sep.SepData: - properties: - catatan: - type: string - diagnosa: - type: string - informasi: - properties: - dpjpLayan: - type: string - noSKDP: - type: string - noTelp: - type: string - subSpesialis: - type: string - type: object - jnsPelayanan: - type: string - klsRawat: - type: string - noMR: - type: string - noSep: - type: string - peserta: - $ref: '#/definitions/api-service_internal_models_vclaim_peserta.PesertaData' - poli: - type: string - rujukan: - $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepRujukan' - tglSep: - type: string - type: object - api-service_internal_models_vclaim_sep.SepRequest: - properties: - catatan: - type: string - diagnosa: - type: string - jnsPelayanan: - enum: - - "1" - - "2" - type: string - klsRawat: - enum: - - "1" - - "2" - - "3" - type: string - noKartu: - type: string - noMR: - type: string - noTelp: - type: string - poli: - type: string - ppkPelayanan: - type: string - request_id: - type: string - rujukan: - $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepRujukan' - tglSep: - type: string - timestamp: - type: string - user: - type: string - required: - - diagnosa - - jnsPelayanan - - klsRawat - - noKartu - - noMR - - poli - - ppkPelayanan - - tglSep - - user - type: object - api-service_internal_models_vclaim_sep.SepResponse: - properties: - data: - $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepData' - message: - type: string - request_id: - type: string - status: - type: string - timestamp: - type: string - type: object - api-service_internal_models_vclaim_sep.SepRujukan: - properties: - asalRujukan: - enum: - - "1" - - "2" - type: string - noRujukan: - type: string - ppkRujukan: - type: string - tglRujukan: - type: string - required: - - asalRujukan - - noRujukan - - ppkRujukan - - tglRujukan - type: object - internal_handlers_vclaim_peserta.PesertaData: - properties: - cob: - properties: - nmAsuransi: {} - noAsuransi: {} - tglTAT: {} - tglTMT: {} - type: object - hakKelas: - properties: - keterangan: - type: string - kode: - type: string - type: object - informasi: - properties: - dinsos: {} - eSEP: {} - noSKTM: {} - prolanisPRB: - type: string - type: object - jenisPeserta: - properties: - keterangan: - type: string - kode: - type: string - type: object - mr: - properties: - noMR: - type: string - noTelepon: - type: string - 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 - internal_handlers_vclaim_peserta.PesertaResponse: - properties: - data: - $ref: '#/definitions/internal_handlers_vclaim_peserta.PesertaData' + $ref: '#/definitions/peserta.PesertaData' message: type: string metaData: {} @@ -645,21 +212,358 @@ definitions: timestamp: type: string type: object - sql.NullString: + retribusi.Retribusi: properties: - string: + date_created: + $ref: '#/definitions/models.NullableTime' + date_updated: + $ref: '#/definitions/models.NullableTime' + dinas: + $ref: '#/definitions/models.NullableString' + id: type: string - valid: - description: Valid is true if String is not NULL - type: boolean + 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' type: object - sql.NullTime: + retribusi.RetribusiCreateRequest: properties: - time: + 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.RetribusiCreateResponse: + properties: + data: + $ref: '#/definitions/retribusi.Retribusi' + message: + type: string + type: object + retribusi.RetribusiDeleteResponse: + properties: + id: + type: string + message: + type: string + type: object + retribusi.RetribusiGetByIDResponse: + 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: type: string - valid: - description: Valid is true if Time is not NULL - type: boolean type: object host: localhost:8080 info: @@ -675,7 +579,7 @@ info: title: API Service version: 1.0.0 paths: - /Peserta/nik/nik/:nik/tglSEP/:tglsep: + /Peserta/nik/:nik: get: consumes: - application/json @@ -695,79 +599,35 @@ paths: - application/json responses: "200": - description: Successfully retrieved PesertaBynik data + description: Successfully retrieved Bynik data schema: - $ref: '#/definitions/internal_handlers_vclaim_peserta.PesertaResponse' + $ref: '#/definitions/peserta.PesertaResponse' "400": description: Bad request - invalid parameters schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + $ref: '#/definitions/models.ErrorResponseBpjs' "401": description: Unauthorized - invalid API credentials schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + $ref: '#/definitions/models.ErrorResponseBpjs' "404": - description: Not found - PesertaBynik not found + description: Not found - Bynik not found schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + $ref: '#/definitions/models.ErrorResponseBpjs' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + $ref: '#/definitions/models.ErrorResponseBpjs' security: - ApiKeyAuth: [] - summary: Get PesertaBynik data + summary: Get Bynik data tags: - Peserta - /Rujukan/:norujukan: + /Peserta/nokartu/:nokartu: get: consumes: - application/json - description: Manage 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 RujukanBynorujukan data - schema: - $ref: '#/definitions/api-service_internal_models_vclaim_rujukan.RujukanResponse' - "400": - description: Bad request - invalid parameters - schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' - "404": - description: Not found - RujukanBynorujukan not found - schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Get RujukanBynorujukan data - tags: - - Rujukan - /Rujukan/Peserta/:nokartu: - get: - consumes: - - application/json - description: Manage rujukan + description: Get participant eligibility information by card number parameters: - description: Request ID for tracking in: header @@ -783,28 +643,284 @@ paths: - application/json responses: "200": - description: Successfully retrieved RujukanBynokartu data + description: Successfully retrieved Bynokartu data schema: - $ref: '#/definitions/api-service_internal_models_vclaim_rujukan.RujukanResponse' + $ref: '#/definitions/peserta.PesertaResponse' "400": description: Bad request - invalid parameters schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + $ref: '#/definitions/models.ErrorResponseBpjs' "401": description: Unauthorized - invalid API credentials schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + $ref: '#/definitions/models.ErrorResponseBpjs' "404": - description: Not found - RujukanBynokartu not found + description: Not found - Bynokartu not found schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + $ref: '#/definitions/models.ErrorResponseBpjs' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + $ref: '#/definitions/models.ErrorResponseBpjs' security: - ApiKeyAuth: [] - summary: Get RujukanBynokartu data + 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: @@ -818,14 +934,14 @@ paths: name: login required: true schema: - $ref: '#/definitions/api-service_internal_models_auth.LoginRequest' + $ref: '#/definitions/models.LoginRequest' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/api-service_internal_models_auth.TokenResponse' + $ref: '#/definitions/models.TokenResponse' "400": description: Bad request schema: @@ -850,7 +966,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/api-service_internal_models_auth.User' + $ref: '#/definitions/models.User' "401": description: Unauthorized schema: @@ -882,7 +998,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/api-service_internal_models_auth.TokenResponse' + $ref: '#/definitions/models.TokenResponse' "400": description: Bad request schema: @@ -947,19 +1063,19 @@ paths: "200": description: Retribusi deleted successfully schema: - $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiDeleteResponse' + $ref: '#/definitions/retribusi.RetribusiDeleteResponse' "400": description: Invalid ID format schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "404": description: Retribusi not found schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' summary: Delete retribusi tags: - Retribusi @@ -979,19 +1095,19 @@ paths: "200": description: Success response schema: - $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiGetByIDResponse' + $ref: '#/definitions/retribusi.RetribusiGetByIDResponse' "400": description: Invalid ID format schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "404": description: Retribusi not found schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' summary: Get Retribusi by ID tags: - Retribusi @@ -1010,26 +1126,26 @@ paths: name: request required: true schema: - $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiUpdateRequest' + $ref: '#/definitions/retribusi.RetribusiUpdateRequest' produces: - application/json responses: "200": description: Retribusi updated successfully schema: - $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiUpdateResponse' + $ref: '#/definitions/retribusi.RetribusiUpdateResponse' "400": description: Bad request or validation error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "404": description: Retribusi not found schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' summary: Update retribusi tags: - Retribusi @@ -1076,15 +1192,15 @@ paths: "200": description: Success response schema: - $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse' + $ref: '#/definitions/retribusi.RetribusiGetResponse' "400": description: Bad request schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' summary: Get retribusi with pagination and optional aggregation tags: - Retribusi @@ -1098,22 +1214,22 @@ paths: name: request required: true schema: - $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiCreateRequest' + $ref: '#/definitions/retribusi.RetribusiCreateRequest' produces: - application/json responses: "201": description: Retribusi created successfully schema: - $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiCreateResponse' + $ref: '#/definitions/retribusi.RetribusiCreateResponse' "400": description: Bad request or validation error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' summary: Create retribusi tags: - Retribusi @@ -1151,15 +1267,15 @@ paths: "200": description: Success response schema: - $ref: '#/definitions/api-service_internal_models_retribusi.RetribusiGetResponse' + $ref: '#/definitions/retribusi.RetribusiGetResponse' "400": description: Bad request schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' summary: Get retribusi with dynamic filtering tags: - Retribusi @@ -1179,11 +1295,11 @@ paths: "200": description: Statistics data schema: - $ref: '#/definitions/api-service_internal_models.AggregateData' + $ref: '#/definitions/models.AggregateData' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponse' + $ref: '#/definitions/models.ErrorResponse' summary: Get retribusi statistics tags: - Retribusi @@ -1198,14 +1314,14 @@ paths: name: token required: true schema: - $ref: '#/definitions/api-service_internal_models_auth.LoginRequest' + $ref: '#/definitions/models.LoginRequest' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/api-service_internal_models_auth.TokenResponse' + $ref: '#/definitions/models.TokenResponse' "400": description: Bad request schema: @@ -1242,7 +1358,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/api-service_internal_models_auth.TokenResponse' + $ref: '#/definitions/models.TokenResponse' "400": description: Bad request schema: @@ -1252,11 +1368,11 @@ paths: summary: Generate token directly tags: - Token - /peserta/nokartu/:nokartu/tglSEP/:tglsep: + /bynokartu/:nokartu: get: consumes: - application/json - description: Get participant eligibility information by card number + description: Get rujukan by card number parameters: - description: Request ID for tracking in: header @@ -1272,210 +1388,74 @@ paths: - application/json responses: "200": - description: Successfully retrieved PesertaBynokartu data + description: Successfully retrieved Bynokartu data schema: - $ref: '#/definitions/internal_handlers_vclaim_peserta.PesertaResponse' + $ref: '#/definitions/rujukan.RujukanResponse' "400": description: Bad request - invalid parameters schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + $ref: '#/definitions/models.ErrorResponseBpjs' "401": description: Unauthorized - invalid API credentials schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + $ref: '#/definitions/models.ErrorResponseBpjs' "404": - description: Not found - PesertaBynokartu not found + description: Not found - Bynokartu not found schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + $ref: '#/definitions/models.ErrorResponseBpjs' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + $ref: '#/definitions/models.ErrorResponseBpjs' security: - ApiKeyAuth: [] - summary: Get PesertaBynokartu data + summary: Get Bynokartu data tags: - - Peserta - /sep: - post: - consumes: - - application/json - description: Manage SEP (Surat Eligibilitas Peserta) - parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - - description: SepSep data - in: body - name: request - required: true - schema: - $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepRequest' - produces: - - application/json - responses: - "201": - description: Successfully created SepSep - schema: - $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepResponse' - "400": - description: Bad request - invalid request body or validation error - schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' - "409": - description: Conflict - SepSep already exists - schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Create new SepSep - tags: - - Sep - /sep/:nosep: - delete: - consumes: - - application/json - description: Manage SEP (Surat Eligibilitas Peserta) - parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - - description: nosep - example: '"example_value"' - in: path - name: nosep - required: true - type: string - produces: - - application/json - responses: - "200": - description: Successfully deleted SepSep - schema: - $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepResponse' - "400": - description: Bad request - invalid parameters - schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' - "404": - description: Not found - SepSep not found - schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Delete existing SepSep - tags: - - Sep + - Rujukan + /bynorujukan/:norujukan: get: consumes: - application/json - description: Manage SEP (Surat Eligibilitas Peserta) + description: Get rujukan by nomor rujukan parameters: - description: Request ID for tracking in: header name: X-Request-ID type: string - - description: nosep + - description: norujukan example: '"example_value"' in: path - name: nosep + name: norujukan required: true type: string produces: - application/json responses: "200": - description: Successfully retrieved SepSep data + description: Successfully retrieved Bynorujukan data schema: - $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepResponse' + $ref: '#/definitions/rujukan.RujukanResponse' "400": description: Bad request - invalid parameters schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + $ref: '#/definitions/models.ErrorResponseBpjs' "401": description: Unauthorized - invalid API credentials schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + $ref: '#/definitions/models.ErrorResponseBpjs' "404": - description: Not found - SepSep not found + description: Not found - Bynorujukan not found schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + $ref: '#/definitions/models.ErrorResponseBpjs' "500": description: Internal server error schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + $ref: '#/definitions/models.ErrorResponseBpjs' security: - ApiKeyAuth: [] - summary: Get SepSep data + summary: Get Bynorujukan data tags: - - Sep - put: - consumes: - - application/json - description: Manage SEP (Surat Eligibilitas Peserta) - parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - - description: nosep - example: '"example_value"' - in: path - name: nosep - required: true - type: string - - description: SepSep data - in: body - name: request - required: true - schema: - $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepRequest' - produces: - - application/json - responses: - "200": - description: Successfully updated SepSep - schema: - $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepResponse' - "400": - description: Bad request - invalid parameters or request body - schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' - "404": - description: Not found - SepSep not found - schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Update existing SepSep - tags: - - Sep + - Rujukan schemes: - http - https diff --git a/internal/handlers/peserta/peserta.go b/internal/handlers/peserta/peserta.go new file mode 100644 index 00000000..8d6652f2 --- /dev/null +++ b/internal/handlers/peserta/peserta.go @@ -0,0 +1,291 @@ +// Package peserta handles Peserta 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/peserta" + "api-service/internal/services/bpjs" + "api-service/pkg/logger" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + "github.com/google/uuid" +) +// PesertaHandler handles Peserta BPJS services +type PesertaHandler struct { + service services.VClaimService + validator *validator.Validate + logger logger.Logger + config config.BpjsConfig +} +// PesertaHandlerConfig contains configuration for PesertaHandler +type PesertaHandlerConfig struct { + BpjsConfig config.BpjsConfig + Logger logger.Logger + Validator *validator.Validate +} +// NewPesertaHandler creates a new PesertaHandler +func NewPesertaHandler(cfg PesertaHandlerConfig) *PesertaHandler { + return &PesertaHandler{ + service: services.NewService(cfg.BpjsConfig), + validator: cfg.Validator, + logger: cfg.Logger, + config: cfg.BpjsConfig, + } +} + + +// GetBynik godoc +// @Summary Get Bynik data +// @Description Get participant eligibility information by NIK +// @Tags Peserta +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Param X-Request-ID header string false "Request ID for tracking" +// @Param nik path string true "nik" example("example_value") +// @Success 200 {object} peserta.PesertaResponse "Successfully retrieved Bynik 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 - Bynik not found" +// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" +// @Router /Peserta/nik/:nik [get] +func (h *PesertaHandler) GetBynik(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 GetBynik request", map[string]interface{}{ + "request_id": requestID, + "endpoint": "/peserta/nik/:nik", + + "nik": c.Param("nik"), + + }) + + + // Extract path parameters + + nik := c.Param("nik") + if nik == "" { + + h.logger.Error("Missing required parameter nik", map[string]interface{}{ + "request_id": requestID, + }) + + c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ + Status: "error", + Message: "Missing required parameter nik", + RequestID: requestID, + }) + return + } + + + // Call service method + var response peserta.PesertaResponse + + endpoint := "/peserta/nik/:nik" + + endpoint = strings.Replace(endpoint, ":nik", nik, 1) + + resp, err := h.service.GetRawResponse(ctx, endpoint) + + if err != nil { + + h.logger.Error("Failed to get Bynik", 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 = &peserta.PesertaData{} + 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["peserta"]; 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 participant eligibility information by card number +// @Tags Peserta +// @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} peserta.PesertaResponse "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 /Peserta/nokartu/:nokartu [get] +func (h *PesertaHandler) 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": "/peserta/: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 peserta.PesertaResponse + + endpoint := "/peserta/: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 = &peserta.PesertaData{} + 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["peserta"]; 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/handlers/retribusi/retribusi.go b/internal/handlers/retribusi/retribusi.go index c4f1cf6b..bc32d503 100644 --- a/internal/handlers/retribusi/retribusi.go +++ b/internal/handlers/retribusi/retribusi.go @@ -4,7 +4,7 @@ import ( "api-service/internal/config" "api-service/internal/database" models "api-service/internal/models" - modelsretribusi "api-service/internal/models/retribusi" + "api-service/internal/models/retribusi" utils "api-service/internal/utils/filters" "api-service/internal/utils/validation" "api-service/pkg/logger" @@ -73,7 +73,7 @@ func NewRetribusiHandler() *RetribusiHandler { // @Param jenis query string false "Filter by jenis" // @Param dinas query string false "Filter by dinas" // @Param search query string false "Search in multiple fields" -// @Success 200 {object} modelsretribusi.RetribusiGetResponse "Success response" +// @Success 200 {object} retribusi.RetribusiGetResponse "Success response" // @Failure 400 {object} models.ErrorResponse "Bad request" // @Failure 500 {object} models.ErrorResponse "Internal server error" // @Router /api/v1/retribusis [get] @@ -102,7 +102,7 @@ func (h *RetribusiHandler) GetRetribusi(c *gin.Context) { // Execute concurrent operations var ( - retribusis []modelsretribusi.Retribusi + retribusis []retribusi.Retribusi total int aggregateData *models.AggregateData wg sync.WaitGroup @@ -165,7 +165,7 @@ func (h *RetribusiHandler) GetRetribusi(c *gin.Context) { // Build response meta := h.calculateMeta(limit, offset, total) - response := modelsretribusi.RetribusiGetResponse{ + response := retribusi.RetribusiGetResponse{ Message: "Data retribusi berhasil diambil", Data: retribusis, Meta: meta, @@ -185,7 +185,7 @@ func (h *RetribusiHandler) GetRetribusi(c *gin.Context) { // @Accept json // @Produce json // @Param id path string true "Retribusi ID (UUID)" -// @Success 200 {object} modelsretribusi.RetribusiGetByIDResponse "Success response" +// @Success 200 {object} retribusi.RetribusiGetByIDResponse "Success response" // @Failure 400 {object} models.ErrorResponse "Invalid ID format" // @Failure 404 {object} models.ErrorResponse "Retribusi not found" // @Failure 500 {object} models.ErrorResponse "Internal server error" @@ -208,7 +208,7 @@ func (h *RetribusiHandler) GetRetribusiByID(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second) defer cancel() - retribusi, err := h.getRetribusiByID(ctx, dbConn, id) + dataretribusi, err := h.getRetribusiByID(ctx, dbConn, id) if err != nil { if err == sql.ErrNoRows { h.respondError(c, "Retribusi not found", err, http.StatusNotFound) @@ -218,9 +218,9 @@ func (h *RetribusiHandler) GetRetribusiByID(c *gin.Context) { return } - response := modelsretribusi.RetribusiGetByIDResponse{ + response := retribusi.RetribusiGetByIDResponse{ Message: "Retribusi details retrieved successfully", - Data: retribusi, + Data: dataretribusi, } c.JSON(http.StatusOK, response) @@ -237,7 +237,7 @@ func (h *RetribusiHandler) GetRetribusiByID(c *gin.Context) { // @Param sort query string false "Sort fields (e.g., sort=date_created,-Jenis)" // @Param limit query int false "Limit" default(10) // @Param offset query int false "Offset" default(0) -// @Success 200 {object} modelsretribusi.RetribusiGetResponse "Success response" +// @Success 200 {object} retribusi.RetribusiGetResponse "Success response" // @Failure 400 {object} models.ErrorResponse "Bad request" // @Failure 500 {object} models.ErrorResponse "Internal server error" // @Router /api/v1/retribusis/dynamic [get] @@ -270,7 +270,7 @@ func (h *RetribusiHandler) GetRetribusiDynamic(c *gin.Context) { // Build response meta := h.calculateMeta(dynamicQuery.Limit, dynamicQuery.Offset, total) - response := modelsretribusi.RetribusiGetResponse{ + response := retribusi.RetribusiGetResponse{ Message: "Data retribusi berhasil diambil", Data: retribusis, Meta: meta, @@ -280,7 +280,7 @@ func (h *RetribusiHandler) GetRetribusiDynamic(c *gin.Context) { } // fetchRetribusisDynamic executes dynamic query -func (h *RetribusiHandler) fetchRetribusisDynamic(ctx context.Context, dbConn *sql.DB, query utils.DynamicQuery) ([]modelsretribusi.Retribusi, int, error) { +func (h *RetribusiHandler) fetchRetribusisDynamic(ctx context.Context, dbConn *sql.DB, query utils.DynamicQuery) ([]retribusi.Retribusi, int, error) { // Setup query builder builder := utils.NewQueryBuilder("data_retribusi"). SetColumnMapping(map[string]string{ @@ -319,7 +319,7 @@ func (h *RetribusiHandler) fetchRetribusisDynamic(ctx context.Context, dbConn *s // Execute concurrent queries var ( - retribusis []modelsretribusi.Retribusi + retribusis []retribusi.Retribusi total int wg sync.WaitGroup errChan = make(chan error, 2) @@ -364,7 +364,7 @@ func (h *RetribusiHandler) fetchRetribusisDynamic(ctx context.Context, dbConn *s } defer rows.Close() - var results []modelsretribusi.Retribusi + var results []retribusi.Retribusi for rows.Next() { retribusi, err := h.scanRetribusi(rows) if err != nil { @@ -484,7 +484,7 @@ func (h *RetribusiHandler) SearchRetribusiAdvanced(c *gin.Context) { // Build response meta := h.calculateMeta(query.Limit, query.Offset, total) - response := modelsretribusi.RetribusiGetResponse{ + response := retribusi.RetribusiGetResponse{ Message: fmt.Sprintf("Search results for '%s'", searchQuery), Data: retribusis, Meta: meta, @@ -499,13 +499,13 @@ func (h *RetribusiHandler) SearchRetribusiAdvanced(c *gin.Context) { // @Tags Retribusi // @Accept json // @Produce json -// @Param request body modelsretribusi.RetribusiCreateRequest true "Retribusi creation request" -// @Success 201 {object} modelsretribusi.RetribusiCreateResponse "Retribusi created successfully" +// @Param request body retribusi.RetribusiCreateRequest true "Retribusi creation request" +// @Success 201 {object} retribusi.RetribusiCreateResponse "Retribusi created successfully" // @Failure 400 {object} models.ErrorResponse "Bad request or validation error" // @Failure 500 {object} models.ErrorResponse "Internal server error" // @Router /api/v1/retribusis [post] func (h *RetribusiHandler) CreateRetribusi(c *gin.Context) { - var req modelsretribusi.RetribusiCreateRequest + var req retribusi.RetribusiCreateRequest if err := c.ShouldBindJSON(&req); err != nil { h.respondError(c, "Invalid request body", err, http.StatusBadRequest) @@ -533,15 +533,15 @@ func (h *RetribusiHandler) CreateRetribusi(c *gin.Context) { return } - retribusi, err := h.createRetribusi(ctx, dbConn, &req) + dataretribusi, err := h.createRetribusi(ctx, dbConn, &req) if err != nil { h.logAndRespondError(c, "Failed to create retribusi", err, http.StatusInternalServerError) return } - response := modelsretribusi.RetribusiCreateResponse{ + response := retribusi.RetribusiCreateResponse{ Message: "Retribusi berhasil dibuat", - Data: retribusi, + Data: dataretribusi, } c.JSON(http.StatusCreated, response) @@ -554,8 +554,8 @@ func (h *RetribusiHandler) CreateRetribusi(c *gin.Context) { // @Accept json // @Produce json // @Param id path string true "Retribusi ID (UUID)" -// @Param request body modelsretribusi.RetribusiUpdateRequest true "Retribusi update request" -// @Success 200 {object} modelsretribusi.RetribusiUpdateResponse "Retribusi updated successfully" +// @Param request body retribusi.RetribusiUpdateRequest true "Retribusi update request" +// @Success 200 {object} retribusi.RetribusiUpdateResponse "Retribusi updated successfully" // @Failure 400 {object} models.ErrorResponse "Bad request or validation error" // @Failure 404 {object} models.ErrorResponse "Retribusi not found" // @Failure 500 {object} models.ErrorResponse "Internal server error" @@ -569,7 +569,7 @@ func (h *RetribusiHandler) UpdateRetribusi(c *gin.Context) { return } - var req modelsretribusi.RetribusiUpdateRequest + var req retribusi.RetribusiUpdateRequest if err := c.ShouldBindJSON(&req); err != nil { h.respondError(c, "Invalid request body", err, http.StatusBadRequest) return @@ -593,7 +593,7 @@ func (h *RetribusiHandler) UpdateRetribusi(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second) defer cancel() - retribusi, err := h.updateRetribusi(ctx, dbConn, &req) + dataretribusi, err := h.updateRetribusi(ctx, dbConn, &req) if err != nil { if err == sql.ErrNoRows { h.respondError(c, "Retribusi not found", err, http.StatusNotFound) @@ -603,9 +603,9 @@ func (h *RetribusiHandler) UpdateRetribusi(c *gin.Context) { return } - response := modelsretribusi.RetribusiUpdateResponse{ + response := retribusi.RetribusiUpdateResponse{ Message: "Retribusi berhasil diperbarui", - Data: retribusi, + Data: dataretribusi, } c.JSON(http.StatusOK, response) @@ -618,7 +618,7 @@ func (h *RetribusiHandler) UpdateRetribusi(c *gin.Context) { // @Accept json // @Produce json // @Param id path string true "Retribusi ID (UUID)" -// @Success 200 {object} modelsretribusi.RetribusiDeleteResponse "Retribusi deleted successfully" +// @Success 200 {object} retribusi.RetribusiDeleteResponse "Retribusi deleted successfully" // @Failure 400 {object} models.ErrorResponse "Invalid ID format" // @Failure 404 {object} models.ErrorResponse "Retribusi not found" // @Failure 500 {object} models.ErrorResponse "Internal server error" @@ -651,7 +651,7 @@ func (h *RetribusiHandler) DeleteRetribusi(c *gin.Context) { return } - response := modelsretribusi.RetribusiDeleteResponse{ + response := retribusi.RetribusiDeleteResponse{ Message: "Retribusi berhasil dihapus", ID: id, } @@ -693,7 +693,7 @@ func (h *RetribusiHandler) GetRetribusiStats(c *gin.Context) { } // Get retribusi by ID -func (h *RetribusiHandler) getRetribusiByID(ctx context.Context, dbConn *sql.DB, id string) (*modelsretribusi.Retribusi, error) { +func (h *RetribusiHandler) getRetribusiByID(ctx context.Context, dbConn *sql.DB, id string) (*retribusi.Retribusi, error) { query := ` SELECT id, status, sort, user_created, date_created, user_updated, date_updated, @@ -705,7 +705,7 @@ func (h *RetribusiHandler) getRetribusiByID(ctx context.Context, dbConn *sql.DB, row := dbConn.QueryRowContext(ctx, query, id) - var retribusi modelsretribusi.Retribusi + var retribusi retribusi.Retribusi err := row.Scan( &retribusi.ID, &retribusi.Status, &retribusi.Sort, &retribusi.UserCreated, &retribusi.DateCreated, &retribusi.UserUpdated, &retribusi.DateUpdated, @@ -723,7 +723,7 @@ func (h *RetribusiHandler) getRetribusiByID(ctx context.Context, dbConn *sql.DB, } // Create retribusi -func (h *RetribusiHandler) createRetribusi(ctx context.Context, dbConn *sql.DB, req *modelsretribusi.RetribusiCreateRequest) (*modelsretribusi.Retribusi, error) { +func (h *RetribusiHandler) createRetribusi(ctx context.Context, dbConn *sql.DB, req *retribusi.RetribusiCreateRequest) (*retribusi.Retribusi, error) { id := uuid.New().String() now := time.Now() @@ -747,7 +747,7 @@ func (h *RetribusiHandler) createRetribusi(ctx context.Context, dbConn *sql.DB, req.RekeningPokok, req.RekeningDenda, req.Uraian1, req.Uraian2, req.Uraian3, ) - var retribusi modelsretribusi.Retribusi + var retribusi retribusi.Retribusi err := row.Scan( &retribusi.ID, &retribusi.Status, &retribusi.Sort, &retribusi.UserCreated, &retribusi.DateCreated, &retribusi.UserUpdated, &retribusi.DateUpdated, @@ -765,7 +765,7 @@ func (h *RetribusiHandler) createRetribusi(ctx context.Context, dbConn *sql.DB, } // Update retribusi -func (h *RetribusiHandler) updateRetribusi(ctx context.Context, dbConn *sql.DB, req *modelsretribusi.RetribusiUpdateRequest) (*modelsretribusi.Retribusi, error) { +func (h *RetribusiHandler) updateRetribusi(ctx context.Context, dbConn *sql.DB, req *retribusi.RetribusiUpdateRequest) (*retribusi.Retribusi, error) { now := time.Now() query := ` @@ -788,7 +788,7 @@ func (h *RetribusiHandler) updateRetribusi(ctx context.Context, dbConn *sql.DB, req.RekeningPokok, req.RekeningDenda, req.Uraian1, req.Uraian2, req.Uraian3, ) - var retribusi modelsretribusi.Retribusi + var retribusi retribusi.Retribusi err := row.Scan( &retribusi.ID, &retribusi.Status, &retribusi.Sort, &retribusi.UserCreated, &retribusi.DateCreated, &retribusi.UserUpdated, &retribusi.DateUpdated, @@ -889,7 +889,7 @@ func (h *RetribusiHandler) parsePaginationParams(c *gin.Context) (int, int, erro } // Build WHERE clause dengan filter parameters -func (h *RetribusiHandler) buildWhereClause(filter modelsretribusi.RetribusiFilter) (string, []interface{}) { +func (h *RetribusiHandler) buildWhereClause(filter retribusi.RetribusiFilter) (string, []interface{}) { conditions := []string{"status != 'deleted'"} args := []interface{}{} paramCount := 1 @@ -950,8 +950,8 @@ func (h *RetribusiHandler) buildWhereClause(filter modelsretribusi.RetribusiFilt } // Optimized scanning function yang menggunakan sql.Null* types langsung -func (h *RetribusiHandler) scanRetribusi(rows *sql.Rows) (modelsretribusi.Retribusi, error) { - var retribusi modelsretribusi.Retribusi +func (h *RetribusiHandler) scanRetribusi(rows *sql.Rows) (retribusi.Retribusi, error) { + var retribusi retribusi.Retribusi return retribusi, rows.Scan( &retribusi.ID, @@ -979,8 +979,8 @@ func (h *RetribusiHandler) scanRetribusi(rows *sql.Rows) (modelsretribusi.Retrib } // Parse filter parameters dari query string -func (h *RetribusiHandler) parseFilterParams(c *gin.Context) modelsretribusi.RetribusiFilter { - filter := modelsretribusi.RetribusiFilter{} +func (h *RetribusiHandler) parseFilterParams(c *gin.Context) retribusi.RetribusiFilter { + filter := retribusi.RetribusiFilter{} if status := c.Query("status"); status != "" { if models.IsValidStatus(status) { @@ -1021,7 +1021,7 @@ func (h *RetribusiHandler) parseFilterParams(c *gin.Context) modelsretribusi.Ret } // Get comprehensive aggregate data dengan filter support -func (h *RetribusiHandler) getAggregateData(ctx context.Context, dbConn *sql.DB, filter modelsretribusi.RetribusiFilter) (*models.AggregateData, error) { +func (h *RetribusiHandler) getAggregateData(ctx context.Context, dbConn *sql.DB, filter retribusi.RetribusiFilter) (*models.AggregateData, error) { aggregate := &models.AggregateData{ ByStatus: make(map[string]int), ByDinas: make(map[string]int), @@ -1211,7 +1211,7 @@ func (h *RetribusiHandler) getAggregateData(ctx context.Context, dbConn *sql.DB, } // Get total count dengan filter support -func (h *RetribusiHandler) getTotalCount(ctx context.Context, dbConn *sql.DB, filter modelsretribusi.RetribusiFilter, total *int) error { +func (h *RetribusiHandler) getTotalCount(ctx context.Context, dbConn *sql.DB, filter retribusi.RetribusiFilter, total *int) error { whereClause, args := h.buildWhereClause(filter) countQuery := fmt.Sprintf(`SELECT COUNT(*) FROM data_retribusi WHERE %s`, whereClause) @@ -1223,7 +1223,7 @@ func (h *RetribusiHandler) getTotalCount(ctx context.Context, dbConn *sql.DB, fi } // Enhanced fetchRetribusis dengan filter support -func (h *RetribusiHandler) fetchRetribusis(ctx context.Context, dbConn *sql.DB, filter modelsretribusi.RetribusiFilter, limit, offset int) ([]modelsretribusi.Retribusi, error) { +func (h *RetribusiHandler) fetchRetribusis(ctx context.Context, dbConn *sql.DB, filter retribusi.RetribusiFilter, limit, offset int) ([]retribusi.Retribusi, error) { whereClause, args := h.buildWhereClause(filter) // Build the main query with pagination @@ -1249,7 +1249,7 @@ func (h *RetribusiHandler) fetchRetribusis(ctx context.Context, dbConn *sql.DB, defer rows.Close() // Pre-allocate slice dengan kapasitas yang tepat - retribusis := make([]modelsretribusi.Retribusi, 0, limit) + retribusis := make([]retribusi.Retribusi, 0, limit) for rows.Next() { retribusi, err := h.scanRetribusi(rows) @@ -1293,7 +1293,7 @@ func (h *RetribusiHandler) calculateMeta(limit, offset, total int) models.MetaRe } // validateRetribusiSubmission performs validation for duplicate entries and daily submission limits -func (h *RetribusiHandler) validateRetribusiSubmission(ctx context.Context, dbConn *sql.DB, req *modelsretribusi.RetribusiCreateRequest) error { +func (h *RetribusiHandler) validateRetribusiSubmission(ctx context.Context, dbConn *sql.DB, req *retribusi.RetribusiCreateRequest) error { // Import the validation utility validator := validation.NewDuplicateValidator(dbConn) @@ -1316,7 +1316,7 @@ func (h *RetribusiHandler) validateRetribusiSubmission(ctx context.Context, dbCo } // Example usage of the validation utility with custom configuration -func (h *RetribusiHandler) validateWithCustomConfig(ctx context.Context, dbConn *sql.DB, req *modelsretribusi.RetribusiCreateRequest) error { +func (h *RetribusiHandler) validateWithCustomConfig(ctx context.Context, dbConn *sql.DB, req *retribusi.RetribusiCreateRequest) error { // Create validator instance validator := validation.NewDuplicateValidator(dbConn) diff --git a/internal/handlers/rujukan/rujukan.go b/internal/handlers/rujukan/rujukan.go new file mode 100644 index 00000000..f6076598 --- /dev/null +++ b/internal/handlers/rujukan/rujukan.go @@ -0,0 +1,885 @@ +// 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 new file mode 100644 index 00000000..3f41afa5 --- /dev/null +++ b/internal/handlers/rujukan/search.go @@ -0,0 +1,291 @@ +// 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/models.go b/internal/models/models.go index f114b0ec..dea08e4e 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -6,7 +6,7 @@ import ( "time" ) -// NullableInt32 is a custom type to replace sql.NullInt32 for swagger compatibility +// NullableInt32 - your existing implementation type NullableInt32 struct { Int32 int32 `json:"int32,omitempty"` Valid bool `json:"valid"` @@ -31,6 +31,56 @@ func (n NullableInt32) Value() (driver.Value, error) { return n.Int32, nil } +// NullableString provides consistent nullable string handling +type NullableString struct { + String string `json:"string,omitempty"` + Valid bool `json:"valid"` +} + +// Scan implements the sql.Scanner interface for NullableString +func (n *NullableString) Scan(value interface{}) error { + var ns sql.NullString + if err := ns.Scan(value); err != nil { + return err + } + n.String = ns.String + n.Valid = ns.Valid + return nil +} + +// Value implements the driver.Valuer interface for NullableString +func (n NullableString) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.String, nil +} + +// NullableTime provides consistent nullable time handling +type NullableTime struct { + Time time.Time `json:"time,omitempty"` + Valid bool `json:"valid"` +} + +// Scan implements the sql.Scanner interface for NullableTime +func (n *NullableTime) Scan(value interface{}) error { + var nt sql.NullTime + if err := nt.Scan(value); err != nil { + return err + } + n.Time = nt.Time + n.Valid = nt.Valid + return nil +} + +// Value implements the driver.Valuer interface for NullableTime +func (n NullableTime) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Time, nil +} + // Metadata untuk pagination - dioptimalkan type MetaResponse struct { Limit int `json:"limit"` diff --git a/internal/models/retribusi/retribusi.go b/internal/models/retribusi/retribusi.go index ca772d75..79075273 100644 --- a/internal/models/retribusi/retribusi.go +++ b/internal/models/retribusi/retribusi.go @@ -1,8 +1,7 @@ -package models +package retribusi import ( "api-service/internal/models" - "database/sql" "encoding/json" "time" ) @@ -10,27 +9,27 @@ import ( // Retribusi represents the data structure for the retribusi table // with proper null handling and optimized JSON marshaling type Retribusi struct { - ID string `json:"id" db:"id"` - Status string `json:"status" db:"status"` - Sort models.NullableInt32 `json:"sort,omitempty" db:"sort"` - UserCreated sql.NullString `json:"user_created,omitempty" db:"user_created"` - DateCreated sql.NullTime `json:"date_created,omitempty" db:"date_created"` - UserUpdated sql.NullString `json:"user_updated,omitempty" db:"user_updated"` - DateUpdated sql.NullTime `json:"date_updated,omitempty" db:"date_updated"` - Jenis sql.NullString `json:"jenis,omitempty" db:"Jenis"` - Pelayanan sql.NullString `json:"pelayanan,omitempty" db:"Pelayanan"` - Dinas sql.NullString `json:"dinas,omitempty" db:"Dinas"` - KelompokObyek sql.NullString `json:"kelompok_obyek,omitempty" db:"Kelompok_obyek"` - KodeTarif sql.NullString `json:"kode_tarif,omitempty" db:"Kode_tarif"` - Tarif sql.NullString `json:"tarif,omitempty" db:"Tarif"` - Satuan sql.NullString `json:"satuan,omitempty" db:"Satuan"` - TarifOvertime sql.NullString `json:"tarif_overtime,omitempty" db:"Tarif_overtime"` - SatuanOvertime sql.NullString `json:"satuan_overtime,omitempty" db:"Satuan_overtime"` - RekeningPokok sql.NullString `json:"rekening_pokok,omitempty" db:"Rekening_pokok"` - RekeningDenda sql.NullString `json:"rekening_denda,omitempty" db:"Rekening_denda"` - Uraian1 sql.NullString `json:"uraian_1,omitempty" db:"Uraian_1"` - Uraian2 sql.NullString `json:"uraian_2,omitempty" db:"Uraian_2"` - Uraian3 sql.NullString `json:"uraian_3,omitempty" db:"Uraian_3"` + ID string `json:"id" db:"id"` + Status string `json:"status" db:"status"` + Sort models.NullableInt32 `json:"sort,omitempty" db:"sort"` + UserCreated models.NullableString `json:"user_created,omitempty" db:"user_created"` + DateCreated models.NullableTime `json:"date_created,omitempty" db:"date_created"` + UserUpdated models.NullableString `json:"user_updated,omitempty" db:"user_updated"` + DateUpdated models.NullableTime `json:"date_updated,omitempty" db:"date_updated"` + Jenis models.NullableString `json:"jenis,omitempty" db:"Jenis"` + Pelayanan models.NullableString `json:"pelayanan,omitempty" db:"Pelayanan"` + Dinas models.NullableString `json:"dinas,omitempty" db:"Dinas"` + KelompokObyek models.NullableString `json:"kelompok_obyek,omitempty" db:"Kelompok_obyek"` + KodeTarif models.NullableString `json:"kode_tarif,omitempty" db:"Kode_tarif"` + Tarif models.NullableString `json:"tarif,omitempty" db:"Tarif"` + Satuan models.NullableString `json:"satuan,omitempty" db:"Satuan"` + TarifOvertime models.NullableString `json:"tarif_overtime,omitempty" db:"Tarif_overtime"` + SatuanOvertime models.NullableString `json:"satuan_overtime,omitempty" db:"Satuan_overtime"` + RekeningPokok models.NullableString `json:"rekening_pokok,omitempty" db:"Rekening_pokok"` + RekeningDenda models.NullableString `json:"rekening_denda,omitempty" db:"Rekening_denda"` + Uraian1 models.NullableString `json:"uraian_1,omitempty" db:"Uraian_1"` + Uraian2 models.NullableString `json:"uraian_2,omitempty" db:"Uraian_2"` + Uraian3 models.NullableString `json:"uraian_3,omitempty" db:"Uraian_3"` } // Custom JSON marshaling untuk Retribusi agar NULL values tidak muncul di response diff --git a/internal/models/vclaim/rujukan/rujukan.go b/internal/models/vclaim/rujukan/rujukan.go index 2379ed73..ee69b3f3 100644 --- a/internal/models/vclaim/rujukan/rujukan.go +++ b/internal/models/vclaim/rujukan/rujukan.go @@ -17,7 +17,7 @@ type RujukanData struct { Keluhan string `json:"keluhan"` NoKunjungan string `json:"noKunjungan"` Pelayanan PelayananData `json:"pelayanan"` - Peserta PesertaData `json:"peserta"` + Peserta DataPeserta `json:"peserta"` PoliRujukan PoliRujukanData `json:"poliRujukan"` ProvPerujuk ProvPerujukData `json:"provPerujuk"` TglKunjungan string `json:"tglKunjungan"` @@ -43,7 +43,7 @@ type ProvPerujukData struct { Nama string `json:"nama"` } -type PesertaData struct { +type DataPeserta struct { NoKartu string `json:"noKartu"` NIK string `json:"nik"` Nama string `json:"nama"` diff --git a/internal/routes/v1/routes.go b/internal/routes/v1/routes.go index 40316ebd..aec21307 100644 --- a/internal/routes/v1/routes.go +++ b/internal/routes/v1/routes.go @@ -5,12 +5,15 @@ import ( "api-service/internal/database" authHandlers "api-service/internal/handlers/auth" healthcheckHandlers "api-service/internal/handlers/healthcheck" + pesertaHandlers "api-service/internal/handlers/peserta" retribusiHandlers "api-service/internal/handlers/retribusi" + rujukanHandlers "api-service/internal/handlers/rujukan" "api-service/internal/middleware" services "api-service/internal/services/auth" "api-service/pkg/logger" "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" ) @@ -42,7 +45,19 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine { sistem.GET("/health", healthCheckHandler.CheckHealth) // Swagger UI route - router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + router.GET("/swagger/*any", ginSwagger.WrapHandler( + swaggerFiles.Handler, // Models configuration + ginSwagger.DefaultModelsExpandDepth(-1), // Hide models completely + // ginSwagger.DefaultModelExpandDepth(0), // Keep individual models collapsed + + // General UI configuration + // ginSwagger.DocExpansion("none"), // Collapse all sections + ginSwagger.DeepLinking(true), // Enable deep linking + // ginSwagger.PersistAuthorization(true), // Persist auth between refreshes + + // // Optional: Custom title + // ginSwagger.InstanceName("API Service v1.0.0"), + )) // API v1 group v1 := router.Group("/api/v1") @@ -66,37 +81,39 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine { // ============= PUBLISHED ROUTES =============================================== - // // Rujukan routes - // rujukanHandler := rujukan.NewVClaimHandler(rujukan.VClaimHandlerConfig{ - // BpjsConfig: cfg.Bpjs, - // Logger: *logger.Default(), - // Validator: nil, - // }) - // rujukanGroup := v1.Group("/rujukan") - // rujukanGroup.GET("/nokartu/:nokartu", rujukanHandler.GetRujukanBynokartu) - // rujukanGroup.GET("/norujukan/:norujukan", rujukanHandler.GetRujukanBynorujukan) + // Participant eligibility information (peserta) routes + pesertaHandler := pesertaHandlers.NewPesertaHandler(pesertaHandlers.PesertaHandlerConfig{ + BpjsConfig: cfg.Bpjs, + Logger: *logger.Default(), + Validator: validator.New(), + }) + pesertaGroup := v1.Group("/peserta") + pesertaGroup.GET("/Peserta/nokartu/:nokartu", pesertaHandler.GetBynokartu) + pesertaGroup.GET("/Peserta/nik/:nik", pesertaHandler.GetBynik) - // // Peserta routes - // pesertaHandler := peserta.NewVClaimHandler(peserta.VClaimHandlerConfig{ - // BpjsConfig: cfg.Bpjs, - // Logger: *logger.Default(), - // Validator: nil, - // }) - // pesertaGroup := v1.Group("/peserta") - // pesertaGroup.GET("/nokartu/:nokartu/tglSEP/:tglsep", pesertaHandler.GetPesertaBynokartu) - // pesertaGroup.GET("/nik/:nik/tglSEP/:tglsep", pesertaHandler.GetPesertaBynik) + // Rujukan management endpoints (rujukan) routes + rujukanHandler := rujukanHandlers.NewRujukanHandler(rujukanHandlers.RujukanHandlerConfig{ + BpjsConfig: cfg.Bpjs, + Logger: *logger.Default(), + Validator: validator.New(), + }) + rujukanGroup := v1.Group("/rujukan") + rujukanGroup.POST("/Rujukan/:norujukan", rujukanHandler.CreateRujukan) + rujukanGroup.PUT("/Rujukan/:norujukan", rujukanHandler.UpdateRujukan) + rujukanGroup.DELETE("/Rujukan/:norujukan", rujukanHandler.DeleteRujukan) + rujukanGroup.POST("/Rujukanbalik/:norujukan", rujukanHandler.CreateRujukanbalik) + rujukanGroup.PUT("/Rujukanbalik/:norujukan", rujukanHandler.UpdateRujukanbalik) + rujukanGroup.DELETE("/Rujukanbalik/:norujukan", rujukanHandler.DeleteRujukanbalik) - // // Sep routes - // sepHandler := sep.NewVClaimHandler(sep.VClaimHandlerConfig{ - // BpjsConfig: cfg.Bpjs, - // Logger: *logger.Default(), - // Validator: nil, - // }) - // sepGroup := v1.Group("/sep") - // sepGroup.GET("/sep/:nosep", sepHandler.GetSepSep) - // sepGroup.POST("/sep", sepHandler.CreateSepSep) - // sepGroup.PUT("/sep/:nosep", sepHandler.UpdateSepSep) - // sepGroup.DELETE("/sep/:nosep", sepHandler.DeleteSepSep) + // Search for rujukan endpoints (search) routes + searchHandler := rujukanHandlers.NewSearchHandler(rujukanHandlers.SearchHandlerConfig{ + BpjsConfig: cfg.Bpjs, + Logger: *logger.Default(), + Validator: validator.New(), + }) + searchGroup := v1.Group("/search") + searchGroup.GET("/bynorujukan/:norujukan", searchHandler.GetBynorujukan) + searchGroup.GET("/bynokartu/:nokartu", searchHandler.GetBynokartu) // // Retribusi endpoints // retribusiHandler := retribusiHandlers.NewRetribusiHandler() diff --git a/services-config-bpjs.yaml b/services-config-bpjs.yaml index 9b26a76f..9839460b 100644 --- a/services-config-bpjs.yaml +++ b/services-config-bpjs.yaml @@ -19,12 +19,22 @@ services: description: "Participant eligibility information" handler_folder: "peserta" handler_file: "peserta.go" + handler_name: "Peserta" functions: bynokartu: methods: ["GET"] path: "/peserta/:nokartu" + get_routes: "/Peserta/nokartu/:nokartu" + # post_routes: "/Peserta/nokartu/:nokartu" + # put_routes: "/Peserta/nokartu/:nokartu" + # delete_routes: "/Peserta/nokartu/:nokartu" + get_path: "/peserta/:nokartu" + # 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"] @@ -35,8 +45,17 @@ services: bynik: methods: ["GET"] path: "/peserta/nik/:nik" + get_routes: "/Peserta/nik/:nik" + # post_routes: "/Peserta/nik/:nik" + # put_routes: "/Peserta/nik/:nik" + # delete_routes: "/Peserta/nik/:nik" + get_path: "/peserta/nik/:nik" + # 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"] @@ -48,23 +67,42 @@ services: 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 - rujukan: - methods: ["GET"] - path: "/Rujukan" + 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"] @@ -76,12 +114,22 @@ services: 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"] @@ -92,11 +140,21 @@ services: 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/bpjs/generate-handler.go b/tools/bpjs/generate-handler.go index c3fabfca..77cfe328 100644 --- a/tools/bpjs/generate-handler.go +++ b/tools/bpjs/generate-handler.go @@ -2,10 +2,13 @@ package main import ( "fmt" + "go/ast" + "go/parser" + "go/token" "io/ioutil" "os" - "os/exec" "path/filepath" + "regexp" "strings" "text/template" "time" @@ -13,140 +16,910 @@ import ( "gopkg.in/yaml.v2" ) -// runSwagInit runs the swag init command to generate swagger docs -func runSwagInit() error { - cmd := exec.Command("swag", "init", "-g", "../../cmd/api/main.go", "--parseDependency", "--parseInternal") - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() +// Enhanced structures untuk validasi +type HandlerValidation struct { + ExistingFunctions map[string]bool + NewFunctions []string + UpdatedFiles []string + CreatedFiles []string } -// ServiceConfig represents the main configuration structure -type ServiceConfig struct { - Services map[string]Service `yaml:"services"` - Global GlobalConfig `yaml:"global,omitempty"` +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"` } -// GlobalConfig contains global configuration type GlobalConfig struct { ModuleName string `yaml:"module_name"` OutputDir string `yaml:"output_dir"` - PackagePrefix string `yaml:"package_prefix"` EnableSwagger bool `yaml:"enable_swagger"` EnableLogging bool `yaml:"enable_logging"` - EnableMetrics bool `yaml:"enable_metrics"` } -// Service represents individual service configuration +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"` - SubPackage string `yaml:"sub_package"` - Description string `yaml:"description"` - BaseURL string `yaml:"base_url"` - Timeout int `yaml:"timeout"` - RetryCount int `yaml:"retry_count"` - Endpoints map[string]SubEndpoints `yaml:"endpoints"` - Middleware []string `yaml:"middleware,omitempty"` // FIXED: Changed to []string - Dependencies []string `yaml:"dependencies,omitempty"` // FIXED: Changed to []string + 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"` } -// Endpoint represents endpoint configuration -type Endpoint struct { - Methods []string `yaml:"methods"` - GetPath string `yaml:"get_path,omitempty"` - PostPath string `yaml:"post_path,omitempty"` - PutPath string `yaml:"put_path,omitempty"` - DeletePath string `yaml:"delete_path,omitempty"` - PatchPath string `yaml:"patch_path,omitempty"` - Model string `yaml:"model"` - ResponseModel string `yaml:"response_model"` - Description string `yaml:"description"` - Summary string `yaml:"summary"` - Tags []string `yaml:"tags"` - RequireAuth bool `yaml:"require_auth"` - RateLimit int `yaml:"rate_limit,omitempty"` - CacheEnabled bool `yaml:"cache_enabled"` - CacheTTL int `yaml:"cache_ttl,omitempty"` - CustomHeaders map[string]string `yaml:"custom_headers,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"` } -// SubEndpoints represents nested endpoint configuration for tree structure -type SubEndpoints map[string]Endpoint +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"` +} -// TemplateData holds data for generating handlers 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 - HasMetrics bool - HasSwagger bool - HasAuth bool - HasCache bool - Dependencies []string // FIXED: Changed to []string - Middleware []string // FIXED: Changed to []string - GlobalConfig GlobalConfig + 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 } -// EndpointData represents processed endpoint data type EndpointData struct { - Name string - NameLower string - NameUpper string - NameCamel string - Methods []string - GetPath string - PostPath string - PutPath string - DeletePath string - PatchPath string - Model string - ResponseModel string - DataModel string // Data model name (e.g., PesertaData) - Description string - Summary string - Tags []string - HasGet bool - HasPost bool - HasPut bool - HasDelete bool - HasPatch bool - RequireAuth bool - RateLimit int - CacheEnabled bool - CacheTTL int - PathParams []string - QueryParams []string - RequiredFields []string - OptionalFields []string - CustomHeaders map[string]string - ModelPackage string // Package name for the model (peserta, sep, etc.) + 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 } -// Updated template for merged handler structure -const handlerTemplate = ` -// Service: {{.ServiceName}} ({{.Category}}) -// Description: {{.Description}} +// 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) + // } -package {{.Package}} + // 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" - "strings" "net/http" + "strings" "time" "{{.ModuleName}}/internal/config" @@ -159,25 +932,22 @@ import ( "github.com/go-playground/validator/v10" "github.com/google/uuid" ) - -// {{.ServiceName}}Handler handles {{.ServiceName}} BPJS services -type {{.ServiceName}}Handler struct { - service services.{{.ServiceName}}Service +// {{.HandlerName}}Handler handles {{.HandlerName}} BPJS services +type {{.HandlerName}}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 { +// {{.HandlerName}}HandlerConfig contains configuration for {{.HandlerName}}Handler +type {{.HandlerName}}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{ +// 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, @@ -189,7 +959,7 @@ func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig) *{{.ServiceNa // Get{{.Name}} godoc // @Summary Get {{.Name}} data // @Description {{.Description}} -// @Tags {{join .Tags ","}} +// @Tags {{index .Tags 0}} // @Accept json // @Produce json {{if .RequireAuth}} // @Security ApiKeyAuth {{end}} @@ -200,479 +970,1937 @@ func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig) *{{.ServiceNa // @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() +// @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) - } + // 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}} + {{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}} + // 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}} - rawResponse, err := h.service.GetRawResponse(ctx, endpoint) - {{else}} - rawResponse, 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 - } + // 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 raw response to structured data - var data {{.ModelPackage}}.{{.DataModel}} - if err := json.Unmarshal(rawResponse, &data); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to unmarshal {{.Name}} data", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Failed to process response data", - 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) + } + } + } - // Set response data - response.Data = data - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) + // 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 {{.Description}} -// @Tags {{join .Tags ","}} -// @Accept json -// @Produce json {{if .RequireAuth}} +// @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}}.{{.Model}} true "{{.Name}} data" -// @Success 201 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully created {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid request body or validation error" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict - {{.Name}} already exists" -// @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() +// @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() - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } + // 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, - }) - {{end}} + {{if $.HasLogger}} + h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ + "request_id": requestID, + "endpoint": "{{.PostPath}}", + }) + {{end}} - var req {{.ModelPackage}}.{{.Model}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Invalid 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 - } + // 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 - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("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 - } + // 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}} - rawResponse, err := h.service.PostRawResponse(ctx, "{{.PostPath}}", &req) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to create {{.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 - } + {{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}} - // Map raw response to structured data - var data {{.ModelPackage}}.{{.DataModel}} - if err := json.Unmarshal(rawResponse, &data); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to unmarshal {{.Name}} data", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Failed to process response data", - RequestID: requestID, - }) - return - } + endpoint := "{{.PostPath}}" + {{range .PathParams}} + endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) + {{end}} + {{end}} - // Set response data - response.Data = data - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusCreated, response) + // 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 {{.Description}} -// @Tags {{join .Tags ","}} -// @Accept json -// @Produce json {{if .RequireAuth}} +// @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 X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} // @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Param request body {{.ModelPackage}}.{{.Model}} true "{{.Name}} data" -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully updated {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters or request body" -// @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 {{.PutPath}} [put] -func (h *{{$.ServiceName}}Handler) Update{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() +// @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() - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } + // 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, - }) - {{end}} + {{if $.HasLogger}} + h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ + "request_id": requestID, + "endpoint": "{{.PutPath}}", + {{range .PathParams}} + "{{.}}": c.Param("{{.}}"), + {{end}} + }) + {{end}} - {{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}} + // 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}} - var req {{.ModelPackage}}.{{.Model}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Invalid 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 - } + // 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 - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("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 - } + // 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}} - rawResponse, err := h.service.PutRawResponse(ctx, endpoint, &req) - {{else}} - rawResponse, 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}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server 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 raw response to structured data - var data {{.ModelPackage}}.{{.DataModel}} - if err := json.Unmarshal(rawResponse, &data); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to unmarshal {{.Name}} data", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Failed to process response data", - 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) + } + } + } - // Set response data - response.Data = data - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) + // 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}} +// @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 {{join .Tags ","}} +// @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}}" +// @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 {{.DeletePath}} [delete] -func (h *{{$.ServiceName}}Handler) Delete{{.Name}}(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) - defer cancel() +// @Router {{.GetRoutes}} [get] +func (h *{{$.HandlerName}}Handler) Get{{.Name}}(c *gin.Context) { + ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) + defer cancel() - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) + // 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 } - {{if $.HasLogger}} - h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ - "request_id": requestID, + // 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 "" + }, }) - {{end}} - {{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}} - rawResponse, err := h.service.DeleteRawResponse(ctx, endpoint) - {{else}} - rawResponse, err := h.service.DeleteRawResponse(ctx, "{{.DeletePath}}") - {{end}} + tmpl, err := tmpl.Parse(handlerTemplate) if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to delete {{.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 + return err } - // Map raw response to structured data - var data {{.ModelPackage}}.{{.DataModel}} - if err := json.Unmarshal(rawResponse, &data); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to unmarshal {{.Name}} data", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Failed to process response data", - RequestID: requestID, - }) - return + // 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 } - // Set response data - response.Data = data - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) + // 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}} -{{end}} -` -// Template for generating routes -const routesTemplate = ` -package v1 +{{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() -import ( - "{{.ModuleName}}/internal/config" - "{{.ModuleName}}/internal/middleware" - "{{.ModuleName}}/internal/handlers/{{.Category}}/{{.Package}}" - "github.com/gin-gonic/gin" -) + // Generate request ID if not present + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } -func Register{{.ServiceName}}Routes(router *gin.RouterGroup, cfg *config.Config) { - handler := {{.Package}}.New{{.ServiceName}}Handler({{.Package}}.{{.ServiceName}}HandlerConfig{ - BpjsConfig: cfg.Bpjs, - Logger: *logger.Default(), - Validator: nil, - }) + {{if $.HasLogger}} + h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ + "request_id": requestID, + "endpoint": "{{.PostPath}}", + }) + {{end}} - group := router.Group("/{{.ServiceLower}}") + // 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 + } - {{range .Endpoints}} - {{if .HasGet}} - group.GET("{{.GetPath}}", handler.Get{{.Name}}) - {{end}} - {{if .HasPost}} - group.POST("{{.PostPath}}", handler.Create{{.Name}}) - {{end}} - {{if .HasPut}} - group.PUT("{{.PutPath}}", handler.Update{{.Name}}) - {{end}} - {{if .HasDelete}} - group.DELETE("{{.DeletePath}}", handler.Delete{{.Name}}) - {{end}} - {{if .HasPatch}} - group.PATCH("{{.PatchPath}}", handler.Patch{{.Name}}) - {{end}} - {{end}} + // 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() @@ -685,14 +2913,13 @@ func main() { targetService = os.Args[2] } - // Load configuration config, err := loadConfig(configFile) if err != nil { - fmt.Printf("āŒ Error loading config: %v\n", err) + fmt.Printf("Error loading config: %v\n", err) os.Exit(1) } - fmt.Println("šŸš€ Starting BPJS Dynamic Handler Generation...") + 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) @@ -701,78 +2928,47 @@ func main() { generated := 0 errors := 0 - // Generate handlers for serviceName, service := range config.Services { if targetService != "" && serviceName != targetService { continue } - fmt.Printf("šŸ”§ Generating handler for service: %s (%s)\n", service.Name, service.Category) - err := generateHandler(serviceName, service, config.Global) + err := generateHandlerWithValidation(serviceName, service, config.Global) if err != nil { fmt.Printf("āŒ Error generating handler for %s: %v\n", serviceName, err) errors++ continue } - fmt.Printf("āœ… Generated handler: %s.go\n", strings.ToLower(serviceName)) - // Generate routes for this service - // err = generateRoutes(serviceName, service, config.Global) - // if err != nil { - // fmt.Printf("āŒ Error generating routes for %s: %v\n", serviceName, err) - // errors++ - // continue - // } - // fmt.Printf("āœ… Generated routes: %s_routes.go\n", strings.ToLower(serviceName)) - // generated++ + generated++ } // Summary fmt.Println("\nšŸ“Š Generation Summary:") - fmt.Printf(" āœ… Successfully generated: %d handlers and routes\n", generated) + fmt.Printf("āœ… Successfully processed: %d services\n", generated) if errors > 0 { - fmt.Printf(" āŒ Failed: %d handlers/routes\n", errors) + fmt.Printf("āŒ Failed: %d services\n", errors) } if generated > 0 { fmt.Println("šŸŽ‰ Generation completed successfully!") - - // Generate Swagger documentation if enabled - // if config.Global.EnableSwagger { - // fmt.Println("šŸ“š Generating Swagger documentation...") - // if err := runSwagInit(); err != nil { - // fmt.Printf("āš ļø Warning: Failed to generate Swagger docs: %v\n", err) - // } else { - // fmt.Println("āœ… Swagger documentation generated successfully!") - // } - // } } } -func printUsage() { - fmt.Println("BPJS Dynamic Handler Generator") - fmt.Println() - fmt.Println("Usage:") - fmt.Println(" go run generate-dynamic-handler.go [service-name]") - fmt.Println() - fmt.Println("Examples:") - fmt.Println(" go run generate-dynamic-handler.go services-config.yaml") - fmt.Println(" go run generate-dynamic-handler.go services-config.yaml vclaim") -} - +// Helper functions func loadConfig(filename string) (*ServiceConfig, error) { data, err := ioutil.ReadFile(filename) if err != nil { - return nil, fmt.Errorf("failed to read config file: %w", err) + return nil, err } var config ServiceConfig err = yaml.Unmarshal(data, &config) if err != nil { - return nil, fmt.Errorf("failed to parse YAML config: %w", err) + return nil, err } - // Set default values + // Set defaults if config.Global.ModuleName == "" { config.Global.ModuleName = "api-service" } @@ -783,121 +2979,26 @@ func loadConfig(filename string) (*ServiceConfig, error) { return &config, nil } -func generateHandler(serviceName string, service Service, globalConfig GlobalConfig) error { - // Create template with custom functions - tmpl := template.New("handler").Funcs(template.FuncMap{ - "contains": strings.Contains, - "join": strings.Join, - "title": strings.Title, - "trimPrefix": strings.TrimPrefix, - }) - - tmpl, err := tmpl.Parse(handlerTemplate) - if err != nil { - return fmt.Errorf("failed to parse template: %w", err) +func getOrDefault(value, defaultValue int) int { + if value == 0 { + return defaultValue } - - outputDir := globalConfig.OutputDir - if outputDir == "" { - outputDir = "internal/handlers" - } - - generatedFiles := 0 - errorsCount := 0 - - // 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, - } - - // Check for advanced features in this group - for _, endpoint := range subEndpoints { - if endpoint.RequireAuth { - templateData.HasAuth = true - } - if endpoint.CacheEnabled { - templateData.HasCache = true - } - } - - // 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) - } - - // Create output directory for this group: outputDir/service.Package/groupName - groupOutputDir := filepath.Join(outputDir, service.Package, groupName) - if err := os.MkdirAll(groupOutputDir, 0755); err != nil { - errorsCount++ - fmt.Printf("āŒ Failed to create directory %s: %v\n", groupOutputDir, err) - continue - } - - // Generate handler file for this group - filename := filepath.Join(groupOutputDir, fmt.Sprintf("%s.go", groupName)) - - // Check if file already exists, skip generation if so - if _, err := os.Stat(filename); err == nil { - fmt.Printf("āš ļø Skipping generation for existing file: %s\n", filename) - continue - } - - file, err := os.Create(filename) - if err != nil { - errorsCount++ - fmt.Printf("āŒ Failed to create file %s: %v\n", filename, err) - continue - } - - err = tmpl.Execute(file, templateData) - file.Close() - if err != nil { - errorsCount++ - fmt.Printf("āŒ Failed to execute template for %s: %v\n", filename, err) - continue - } - - fmt.Printf("āœ… Generated handler: %s\n", filename) - generatedFiles++ - } - - if errorsCount > 0 { - return fmt.Errorf("generation completed with %d errors", errorsCount) - } - - if generatedFiles == 0 { - return fmt.Errorf("no handlers generated") - } - - return nil + return value } -func generateRoutes(serviceName string, service Service, globalConfig GlobalConfig) error { - // Read the main routes.go file +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 { @@ -907,185 +3008,169 @@ func generateRoutes(serviceName string, service Service, globalConfig GlobalConf 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) + 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 } - // Prepare template data for all groups + var imports []string 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, + // āœ… 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) } - // 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)) + // 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: nil,\n") + routesCode.WriteString("\t\tValidator: validator.New(),\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)) + // āœ… 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()) } - // Find the PUBLISHED ROUTES section and insert the routes + // āœ… 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") } - // 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) + fmt.Printf("āœ… Updated main routes file with %s routes\n", svc.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, - DataModel: strings.Replace(endpoint.ResponseModel, "Response", "Data", 1), - 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/generete b/tools/bpjs/generete index 8432f6ff..570f2d07 100644 --- a/tools/bpjs/generete +++ b/tools/bpjs/generete @@ -2,10 +2,13 @@ package main import ( "fmt" + "go/ast" + "go/parser" + "go/token" "io/ioutil" "os" - "os/exec" "path/filepath" + "regexp" "strings" "text/template" "time" @@ -13,69 +16,91 @@ import ( "gopkg.in/yaml.v2" ) -// runSwagInit runs the swag init command to generate swagger docs -func runSwagInit() error { - cmd := exec.Command("swag", "init", "-g", "../../cmd/api/main.go", "--parseDependency", "--parseInternal") - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() +// Enhanced structures untuk validasi +type HandlerValidation struct { + ExistingFunctions map[string]bool + NewFunctions []string + UpdatedFiles []string + CreatedFiles []string } -// ServiceConfig represents the main configuration structure -type ServiceConfig struct { - Services map[string]Service `yaml:"services"` - Global GlobalConfig `yaml:"global,omitempty"` +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"` } -// GlobalConfig contains global configuration type GlobalConfig struct { ModuleName string `yaml:"module_name"` OutputDir string `yaml:"output_dir"` - PackagePrefix string `yaml:"package_prefix"` EnableSwagger bool `yaml:"enable_swagger"` EnableLogging bool `yaml:"enable_logging"` - EnableMetrics bool `yaml:"enable_metrics"` } -// Service represents individual service configuration +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"` - SubPackage string `yaml:"sub_package"` - Description string `yaml:"description"` - BaseURL string `yaml:"base_url"` - Timeout int `yaml:"timeout"` - RetryCount int `yaml:"retry_count"` - Endpoints map[string]SubEndpoints `yaml:"endpoints"` - Middleware []string `yaml:"middleware,omitempty"` // FIXED: Changed to []string - Dependencies []string `yaml:"dependencies,omitempty"` // FIXED: Changed to []string + 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"` } -// Endpoint represents endpoint configuration -type Endpoint struct { - Methods []string `yaml:"methods"` - GetPath string `yaml:"get_path,omitempty"` - PostPath string `yaml:"post_path,omitempty"` - PutPath string `yaml:"put_path,omitempty"` - DeletePath string `yaml:"delete_path,omitempty"` - PatchPath string `yaml:"patch_path,omitempty"` - Model string `yaml:"model"` - ResponseModel string `yaml:"response_model"` - Description string `yaml:"description"` - Summary string `yaml:"summary"` - Tags []string `yaml:"tags"` - RequireAuth bool `yaml:"require_auth"` - RateLimit int `yaml:"rate_limit,omitempty"` - CacheEnabled bool `yaml:"cache_enabled"` - CacheTTL int `yaml:"cache_ttl,omitempty"` - CustomHeaders map[string]string `yaml:"custom_headers,omitempty"` +type EndpointGroup struct { + Description string `yaml:"description"` + HandlerFolder string `yaml:"handler_folder"` + HandlerFile string `yaml:"handler_file"` + Functions map[string]FunctionConfig `yaml:"functions"` } -// SubEndpoints represents nested endpoint configuration for tree structure -type SubEndpoints map[string]Endpoint +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"` +} -// TemplateData holds data for generating handlers type TemplateData struct { ServiceName string ServiceLower string @@ -91,62 +116,556 @@ type TemplateData struct { ModuleName string HasValidator bool HasLogger bool - HasMetrics bool HasSwagger bool - HasAuth bool - HasCache bool - Dependencies []string // FIXED: Changed to []string - Middleware []string // FIXED: Changed to []string GlobalConfig GlobalConfig } -// EndpointData represents processed endpoint data type EndpointData struct { - Name string - NameLower string - NameUpper string - NameCamel string - Methods []string - GetPath string - PostPath string - PutPath string - DeletePath string - PatchPath string - Model string - ResponseModel string - DataModel string // Data model name (e.g., PesertaData) - Description string - Summary string - Tags []string - HasGet bool - HasPost bool - HasPut bool - HasDelete bool - HasPatch bool - RequireAuth bool - RateLimit int - CacheEnabled bool - CacheTTL int - PathParams []string - QueryParams []string - RequiredFields []string - OptionalFields []string - CustomHeaders map[string]string - ModelPackage string // Package name for the model (peserta, sep, etc.) + 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 } -// Updated template for merged handler structure -const handlerTemplate = ` -// Service: {{.ServiceName}} ({{.Category}}) -// Description: {{.Description}} +// 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) -package {{.Package}} + // 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" - "strings" "net/http" + "strings" "time" "{{.ModuleName}}/internal/config" @@ -162,7 +681,7 @@ import ( // {{.ServiceName}}Handler handles {{.ServiceName}} BPJS services type {{.ServiceName}}Handler struct { - service services.{{.ServiceName}}Service + service services.VClaimService validator *validator.Validate logger logger.Logger config config.BpjsConfig @@ -184,12 +703,13 @@ func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig) *{{.ServiceNa config: cfg.BpjsConfig, } } + {{range .Endpoints}} {{if .HasGet}} // Get{{.Name}} godoc // @Summary Get {{.Name}} data // @Description {{.Description}} -// @Tags {{join .Tags ","}} +// @Tags {{index .Tags 0}} // @Accept json // @Produce json {{if .RequireAuth}} // @Security ApiKeyAuth {{end}} @@ -202,477 +722,1935 @@ func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig) *{{.ServiceNa // @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() + 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) - } + // 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}} + {{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}} + // 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}} - rawResponse, err := h.service.GetRawResponse(ctx, endpoint) - {{else}} - rawResponse, 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 - } + // 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 raw response to structured data - var data {{.ModelPackage}}.{{.DataModel}} - if err := json.Unmarshal(rawResponse, &data); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to unmarshal {{.Name}} data", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Failed to process response data", - 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) + } + } + } - // Set response data - response.Data = data - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) + // 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 {{.Description}} -// @Tags {{join .Tags ","}} -// @Accept json -// @Produce json {{if .RequireAuth}} +// @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}}.{{.Model}} true "{{.Name}} data" -// @Success 201 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully created {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid request body or validation error" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 409 {object} models.ErrorResponseBpjs "Conflict - {{.Name}} already exists" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router {{.PostPath}} [post] +// @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() + ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) + defer cancel() - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } + // 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, - }) - {{end}} + {{if $.HasLogger}} + h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ + "request_id": requestID, + "endpoint": "{{.PostPath}}", + }) + {{end}} - var req {{.ModelPackage}}.{{.Model}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Invalid 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 - } + // 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 - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("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 - } + // 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}} - rawResponse, err := h.service.PostRawResponse(ctx, "{{.PostPath}}", &req) - if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to create {{.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 - } + {{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}} - // Map raw response to structured data - var data {{.ModelPackage}}.{{.DataModel}} - if err := json.Unmarshal(rawResponse, &data); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to unmarshal {{.Name}} data", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Failed to process response data", - RequestID: requestID, - }) - return - } + endpoint := "{{.PostPath}}" + {{range .PathParams}} + endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) + {{end}} + {{end}} - // Set response data - response.Data = data - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusCreated, response) + // 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 {{.Description}} -// @Tags {{join .Tags ","}} -// @Accept json -// @Produce json {{if .RequireAuth}} +// @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 X-Request-ID header string false "Request ID for tracking" {{range .PathParams}} // @Param {{.}} path string true "{{.}}" example("example_value") {{end}} -// @Param request body {{.ModelPackage}}.{{.Model}} true "{{.Name}} data" -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully updated {{.Name}}" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters or request body" -// @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 {{.PutPath}} [put] +// @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() + ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) + defer cancel() - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } + // 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, - }) - {{end}} + {{if $.HasLogger}} + h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ + "request_id": requestID, + "endpoint": "{{.PutPath}}", + {{range .PathParams}} + "{{.}}": c.Param("{{.}}"), + {{end}} + }) + {{end}} - {{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}} + // 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}} - var req {{.ModelPackage}}.{{.Model}} - if err := c.ShouldBindJSON(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("Invalid 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 - } + // 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 - if err := h.validator.Struct(&req); err != nil { - {{if $.HasLogger}} - h.logger.Error("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 - } + // 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}} - rawResponse, err := h.service.PutRawResponse(ctx, endpoint, &req) - {{else}} - rawResponse, 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}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server 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 raw response to structured data - var data {{.ModelPackage}}.{{.DataModel}} - if err := json.Unmarshal(rawResponse, &data); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to unmarshal {{.Name}} data", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Failed to process response data", - 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) + } + } + } - // Set response data - response.Data = data - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) + // 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}} +// @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 {{join .Tags ","}} +// @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}}" +// @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 {{.DeletePath}} [delete] +// @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() + ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second) + defer cancel() - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) + // 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 } - {{if $.HasLogger}} - h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ - "request_id": requestID, + // 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 "" + }, }) - {{end}} - {{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}} - rawResponse, err := h.service.DeleteRawResponse(ctx, endpoint) - {{else}} - rawResponse, err := h.service.DeleteRawResponse(ctx, "{{.DeletePath}}") - {{end}} + tmpl, err := tmpl.Parse(handlerTemplate) if err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to delete {{.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 + return err } - // Map raw response to structured data - var data {{.ModelPackage}}.{{.DataModel}} - if err := json.Unmarshal(rawResponse, &data); err != nil { - {{if $.HasLogger}} - h.logger.Error("Failed to unmarshal {{.Name}} data", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - {{end}} - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Failed to process response data", - RequestID: requestID, - }) - return + // 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 } - // Set response data - response.Data = data - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) + // 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}} -{{end}} -` -// Template for generating routes -const routesTemplate = ` -package v1 +{{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() -import ( - "{{.ModuleName}}/internal/config" - "{{.ModuleName}}/internal/middleware" - "{{.ModuleName}}/internal/handlers/{{.Category}}/{{.Package}}" - "github.com/gin-gonic/gin" -) + // Generate request ID if not present + requestID := c.GetHeader("X-Request-ID") + if requestID == "" { + requestID = uuid.New().String() + c.Header("X-Request-ID", requestID) + } -func Register{{.ServiceName}}Routes(router *gin.RouterGroup, cfg *config.Config) { - handler := {{.Package}}.New{{.ServiceName}}Handler({{.Package}}.{{.ServiceName}}HandlerConfig{ - BpjsConfig: cfg.Bpjs, - Logger: *logger.Default(), - Validator: nil, - }) + {{if $.HasLogger}} + h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ + "request_id": requestID, + "endpoint": "{{.PostPath}}", + }) + {{end}} - group := router.Group("/{{.ServiceLower}}") + // 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 + } - {{range .Endpoints}} - {{if .HasGet}} - group.GET("{{.GetPath}}", handler.Get{{.Name}}) - {{end}} - {{if .HasPost}} - group.POST("{{.PostPath}}", handler.Create{{.Name}}) - {{end}} - {{if .HasPut}} - group.PUT("{{.PutPath}}", handler.Update{{.Name}}) - {{end}} - {{if .HasDelete}} - group.DELETE("{{.DeletePath}}", handler.Delete{{.Name}}) - {{end}} - {{if .HasPatch}} - group.PATCH("{{.PatchPath}}", handler.Patch{{.Name}}) - {{end}} - {{end}} + // 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() @@ -685,14 +2663,13 @@ func main() { targetService = os.Args[2] } - // Load configuration config, err := loadConfig(configFile) if err != nil { - fmt.Printf("āŒ Error loading config: %v\n", err) + fmt.Printf("Error loading config: %v\n", err) os.Exit(1) } - fmt.Println("šŸš€ Starting BPJS Dynamic Handler Generation...") + 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) @@ -701,78 +2678,47 @@ func main() { generated := 0 errors := 0 - // Generate handlers for serviceName, service := range config.Services { if targetService != "" && serviceName != targetService { continue } - fmt.Printf("šŸ”§ Generating handler for service: %s (%s)\n", service.Name, service.Category) - err := generateHandler(serviceName, service, config.Global) + err := generateHandlerWithValidation(serviceName, service, config.Global) if err != nil { fmt.Printf("āŒ Error generating handler for %s: %v\n", serviceName, err) errors++ continue } - fmt.Printf("āœ… Generated handler: %s.go\n", strings.ToLower(serviceName)) - // Generate routes for this service - err = generateRoutes(serviceName, service, config.Global) - if err != nil { - fmt.Printf("āŒ Error generating routes for %s: %v\n", serviceName, err) - errors++ - continue - } - fmt.Printf("āœ… Generated routes: %s_routes.go\n", strings.ToLower(serviceName)) generated++ } // Summary fmt.Println("\nšŸ“Š Generation Summary:") - fmt.Printf(" āœ… Successfully generated: %d handlers and routes\n", generated) + fmt.Printf("āœ… Successfully processed: %d services\n", generated) if errors > 0 { - fmt.Printf(" āŒ Failed: %d handlers/routes\n", errors) + fmt.Printf("āŒ Failed: %d services\n", errors) } if generated > 0 { fmt.Println("šŸŽ‰ Generation completed successfully!") - - // Generate Swagger documentation if enabled - // if config.Global.EnableSwagger { - // fmt.Println("šŸ“š Generating Swagger documentation...") - // if err := runSwagInit(); err != nil { - // fmt.Printf("āš ļø Warning: Failed to generate Swagger docs: %v\n", err) - // } else { - // fmt.Println("āœ… Swagger documentation generated successfully!") - // } - // } } } -func printUsage() { - fmt.Println("BPJS Dynamic Handler Generator") - fmt.Println() - fmt.Println("Usage:") - fmt.Println(" go run generate-dynamic-handler.go [service-name]") - fmt.Println() - fmt.Println("Examples:") - fmt.Println(" go run generate-dynamic-handler.go services-config.yaml") - fmt.Println(" go run generate-dynamic-handler.go services-config.yaml vclaim") -} - +// Helper functions func loadConfig(filename string) (*ServiceConfig, error) { data, err := ioutil.ReadFile(filename) if err != nil { - return nil, fmt.Errorf("failed to read config file: %w", err) + return nil, err } var config ServiceConfig err = yaml.Unmarshal(data, &config) if err != nil { - return nil, fmt.Errorf("failed to parse YAML config: %w", err) + return nil, err } - // Set default values + // Set defaults if config.Global.ModuleName == "" { config.Global.ModuleName = "api-service" } @@ -783,117 +2729,22 @@ func loadConfig(filename string) (*ServiceConfig, error) { return &config, nil } -func generateHandler(serviceName string, service Service, globalConfig GlobalConfig) error { - // Create template with custom functions - tmpl := template.New("handler").Funcs(template.FuncMap{ - "contains": strings.Contains, - "join": strings.Join, - "title": strings.Title, - "trimPrefix": strings.TrimPrefix, - }) - - tmpl, err := tmpl.Parse(handlerTemplate) - if err != nil { - return fmt.Errorf("failed to parse template: %w", err) +func getOrDefault(value, defaultValue int) int { + if value == 0 { + return defaultValue } + return value +} - outputDir := globalConfig.OutputDir - if outputDir == "" { - outputDir = "internal/handlers" - } - - generatedFiles := 0 - errorsCount := 0 - - // 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, - } - - // Check for advanced features in this group - for _, endpoint := range subEndpoints { - if endpoint.RequireAuth { - templateData.HasAuth = true - } - if endpoint.CacheEnabled { - templateData.HasCache = true - } - } - - // 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) - } - - // Create output directory for this group: outputDir/service.Package/groupName - groupOutputDir := filepath.Join(outputDir, service.Package, groupName) - if err := os.MkdirAll(groupOutputDir, 0755); err != nil { - errorsCount++ - fmt.Printf("āŒ Failed to create directory %s: %v\n", groupOutputDir, err) - continue - } - - // Generate handler file for this group - filename := filepath.Join(groupOutputDir, fmt.Sprintf("%s.go", groupName)) - - // Check if file already exists, skip generation if so - if _, err := os.Stat(filename); err == nil { - fmt.Printf("āš ļø Skipping generation for existing file: %s\n", filename) - continue - } - - file, err := os.Create(filename) - if err != nil { - errorsCount++ - fmt.Printf("āŒ Failed to create file %s: %v\n", filename, err) - continue - } - - err = tmpl.Execute(file, templateData) - file.Close() - if err != nil { - errorsCount++ - fmt.Printf("āŒ Failed to execute template for %s: %v\n", filename, err) - continue - } - - fmt.Printf("āœ… Generated handler: %s\n", filename) - generatedFiles++ - } - - if errorsCount > 0 { - return fmt.Errorf("generation completed with %d errors", errorsCount) - } - - if generatedFiles == 0 { - return fmt.Errorf("no handlers generated") - } - - return nil +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 { @@ -1015,7 +2866,6 @@ func processEndpoint(name string, endpoint Endpoint, endpointGroup string) Endpo PatchPath: endpoint.PatchPath, Model: endpoint.Model, ResponseModel: endpoint.ResponseModel, - DataModel: strings.Replace(endpoint.ResponseModel, "Response", "Data", 1), Description: endpoint.Description, Summary: endpoint.Summary, Tags: endpoint.Tags, diff --git a/tools/bpjs/newgenerete b/tools/bpjs/newgenerete new file mode 100644 index 00000000..2cf0d0bb --- /dev/null +++ b/tools/bpjs/newgenerete @@ -0,0 +1,3230 @@ +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)) + } +}