From 2db9195c8993def0d604b183eed177ecec10f2fb Mon Sep 17 00:00:00 2001 From: Meninjar Mulyono Date: Mon, 1 Sep 2025 20:29:25 +0700 Subject: [PATCH] clear tool generete bpjs --- docs/docs.go | 212 ++- docs/swagger.json | 212 ++- docs/swagger.yaml | 152 +- generetebpjs | 847 --------- genet | 625 ------- go.mod | 2 +- internal/handlers/test.txt/sw | 510 ------ internal/handlers/test.txt/vclaim.backup | 543 ------ internal/handlers/vclaim/peserta/peserta.go | 232 +++ .../{vclaim_handler.go => vclaim/sep/sep.go} | 241 +-- internal/routes/v1/routes.go | 47 + services-config-bpjs.yaml | 9 +- tools/bpjs/generate-handler.go | 377 ++-- tools/bpjs/generete | 1029 +++++++++++ tools/bpjsgenerate-handler.backup | 1132 ------------ tools/generate-bpjs-handler.go.backup | 644 ------- tools/generate-handler.go.backup | 1590 ----------------- tools/generete-handler.go.banckupnew | 0 vclaimhandlerexemple | 414 ----- 19 files changed, 2128 insertions(+), 6690 deletions(-) delete mode 100644 generetebpjs delete mode 100644 genet delete mode 100644 internal/handlers/test.txt/sw delete mode 100644 internal/handlers/test.txt/vclaim.backup create mode 100644 internal/handlers/vclaim/peserta/peserta.go rename internal/handlers/{vclaim_handler.go => vclaim/sep/sep.go} (65%) delete mode 100644 tools/bpjsgenerate-handler.backup delete mode 100644 tools/generate-bpjs-handler.go.backup delete mode 100644 tools/generate-handler.go.backup delete mode 100644 tools/generete-handler.go.banckupnew delete mode 100644 vclaimhandlerexemple diff --git a/docs/docs.go b/docs/docs.go index 8662564..1013bf8 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -702,11 +702,43 @@ const docTemplate = `{ }, "/peserta/:nokartu": { "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get participant eligibility information by card number", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Peserta" + ], + "summary": "Get PesertaBynokartu data", + "parameters": [ + { + "type": "string", + "description": "Request ID for tracking", + "name": "X-Request-ID", + "in": "header" + }, + { + "type": "string", + "example": "\"example_value\"", + "description": "nokartu", + "name": "nokartu", + "in": "path", + "required": true + } + ], "responses": { "200": { "description": "Successfully retrieved PesertaBynokartu data", "schema": { - "$ref": "#/definitions/peserta.PesertaResponse" + "$ref": "#/definitions/api-service_internal_models_vclaim_peserta.PesertaResponse" } }, "400": { @@ -738,11 +770,43 @@ const docTemplate = `{ }, "/peserta/nik/:nik": { "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get participant eligibility information by NIK", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Peserta" + ], + "summary": "Get PesertaBynik data", + "parameters": [ + { + "type": "string", + "description": "Request ID for tracking", + "name": "X-Request-ID", + "in": "header" + }, + { + "type": "string", + "example": "\"example_value\"", + "description": "nik", + "name": "nik", + "in": "path", + "required": true + } + ], "responses": { "200": { "description": "Successfully retrieved PesertaBynik data", "schema": { - "$ref": "#/definitions/peserta.PesertaResponse" + "$ref": "#/definitions/api-service_internal_models_vclaim_peserta.PesertaResponse" } }, "400": { @@ -774,6 +838,22 @@ const docTemplate = `{ }, "/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", @@ -787,7 +867,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/sep.SepRequest" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepRequest" } } ], @@ -795,7 +875,7 @@ const docTemplate = `{ "201": { "description": "Successfully created SepSep", "schema": { - "$ref": "#/definitions/sep.SepResponse" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepResponse" } }, "400": { @@ -827,11 +907,43 @@ const docTemplate = `{ }, "/sep/:nosep": { "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Manage SEP (Surat Eligibilitas Peserta)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Sep" + ], + "summary": "Get SepSep data", + "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 retrieved SepSep data", "schema": { - "$ref": "#/definitions/sep.SepResponse" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepResponse" } }, "400": { @@ -861,14 +973,44 @@ const docTemplate = `{ } }, "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/sep.SepRequest" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepRequest" } } ], @@ -876,7 +1018,7 @@ const docTemplate = `{ "200": { "description": "Successfully updated SepSep", "schema": { - "$ref": "#/definitions/sep.SepResponse" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepResponse" } }, "400": { @@ -906,11 +1048,43 @@ const docTemplate = `{ } }, "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/sep.SepResponse" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepResponse" } }, "400": { @@ -1388,7 +1562,7 @@ const docTemplate = `{ } } }, - "peserta.PesertaData": { + "api-service_internal_models_vclaim_peserta.PesertaData": { "type": "object", "properties": { "aktif": { @@ -1476,11 +1650,11 @@ const docTemplate = `{ } } }, - "peserta.PesertaResponse": { + "api-service_internal_models_vclaim_peserta.PesertaResponse": { "type": "object", "properties": { "data": { - "$ref": "#/definitions/peserta.PesertaData" + "$ref": "#/definitions/api-service_internal_models_vclaim_peserta.PesertaData" }, "message": { "type": "string" @@ -1497,7 +1671,7 @@ const docTemplate = `{ } } }, - "sep.SepData": { + "api-service_internal_models_vclaim_sep.SepData": { "type": "object", "properties": { "catatan": { @@ -1536,20 +1710,20 @@ const docTemplate = `{ "type": "string" }, "peserta": { - "$ref": "#/definitions/peserta.PesertaData" + "$ref": "#/definitions/api-service_internal_models_vclaim_peserta.PesertaData" }, "poli": { "type": "string" }, "rujukan": { - "$ref": "#/definitions/sep.SepRujukan" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepRujukan" }, "tglSep": { "type": "string" } } }, - "sep.SepRequest": { + "api-service_internal_models_vclaim_sep.SepRequest": { "type": "object", "required": [ "diagnosa", @@ -1603,7 +1777,7 @@ const docTemplate = `{ "type": "string" }, "rujukan": { - "$ref": "#/definitions/sep.SepRujukan" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepRujukan" }, "tglSep": { "type": "string" @@ -1616,11 +1790,11 @@ const docTemplate = `{ } } }, - "sep.SepResponse": { + "api-service_internal_models_vclaim_sep.SepResponse": { "type": "object", "properties": { "data": { - "$ref": "#/definitions/sep.SepData" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepData" }, "message": { "type": "string" @@ -1636,7 +1810,7 @@ const docTemplate = `{ } } }, - "sep.SepRujukan": { + "api-service_internal_models_vclaim_sep.SepRujukan": { "type": "object", "required": [ "asalRujukan", diff --git a/docs/swagger.json b/docs/swagger.json index d1de435..74ce152 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -699,11 +699,43 @@ }, "/peserta/:nokartu": { "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get participant eligibility information by card number", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Peserta" + ], + "summary": "Get PesertaBynokartu data", + "parameters": [ + { + "type": "string", + "description": "Request ID for tracking", + "name": "X-Request-ID", + "in": "header" + }, + { + "type": "string", + "example": "\"example_value\"", + "description": "nokartu", + "name": "nokartu", + "in": "path", + "required": true + } + ], "responses": { "200": { "description": "Successfully retrieved PesertaBynokartu data", "schema": { - "$ref": "#/definitions/peserta.PesertaResponse" + "$ref": "#/definitions/api-service_internal_models_vclaim_peserta.PesertaResponse" } }, "400": { @@ -735,11 +767,43 @@ }, "/peserta/nik/:nik": { "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get participant eligibility information by NIK", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Peserta" + ], + "summary": "Get PesertaBynik data", + "parameters": [ + { + "type": "string", + "description": "Request ID for tracking", + "name": "X-Request-ID", + "in": "header" + }, + { + "type": "string", + "example": "\"example_value\"", + "description": "nik", + "name": "nik", + "in": "path", + "required": true + } + ], "responses": { "200": { "description": "Successfully retrieved PesertaBynik data", "schema": { - "$ref": "#/definitions/peserta.PesertaResponse" + "$ref": "#/definitions/api-service_internal_models_vclaim_peserta.PesertaResponse" } }, "400": { @@ -771,6 +835,22 @@ }, "/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", @@ -784,7 +864,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/sep.SepRequest" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepRequest" } } ], @@ -792,7 +872,7 @@ "201": { "description": "Successfully created SepSep", "schema": { - "$ref": "#/definitions/sep.SepResponse" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepResponse" } }, "400": { @@ -824,11 +904,43 @@ }, "/sep/:nosep": { "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Manage SEP (Surat Eligibilitas Peserta)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Sep" + ], + "summary": "Get SepSep data", + "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 retrieved SepSep data", "schema": { - "$ref": "#/definitions/sep.SepResponse" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepResponse" } }, "400": { @@ -858,14 +970,44 @@ } }, "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/sep.SepRequest" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepRequest" } } ], @@ -873,7 +1015,7 @@ "200": { "description": "Successfully updated SepSep", "schema": { - "$ref": "#/definitions/sep.SepResponse" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepResponse" } }, "400": { @@ -903,11 +1045,43 @@ } }, "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/sep.SepResponse" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepResponse" } }, "400": { @@ -1385,7 +1559,7 @@ } } }, - "peserta.PesertaData": { + "api-service_internal_models_vclaim_peserta.PesertaData": { "type": "object", "properties": { "aktif": { @@ -1473,11 +1647,11 @@ } } }, - "peserta.PesertaResponse": { + "api-service_internal_models_vclaim_peserta.PesertaResponse": { "type": "object", "properties": { "data": { - "$ref": "#/definitions/peserta.PesertaData" + "$ref": "#/definitions/api-service_internal_models_vclaim_peserta.PesertaData" }, "message": { "type": "string" @@ -1494,7 +1668,7 @@ } } }, - "sep.SepData": { + "api-service_internal_models_vclaim_sep.SepData": { "type": "object", "properties": { "catatan": { @@ -1533,20 +1707,20 @@ "type": "string" }, "peserta": { - "$ref": "#/definitions/peserta.PesertaData" + "$ref": "#/definitions/api-service_internal_models_vclaim_peserta.PesertaData" }, "poli": { "type": "string" }, "rujukan": { - "$ref": "#/definitions/sep.SepRujukan" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepRujukan" }, "tglSep": { "type": "string" } } }, - "sep.SepRequest": { + "api-service_internal_models_vclaim_sep.SepRequest": { "type": "object", "required": [ "diagnosa", @@ -1600,7 +1774,7 @@ "type": "string" }, "rujukan": { - "$ref": "#/definitions/sep.SepRujukan" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepRujukan" }, "tglSep": { "type": "string" @@ -1613,11 +1787,11 @@ } } }, - "sep.SepResponse": { + "api-service_internal_models_vclaim_sep.SepResponse": { "type": "object", "properties": { "data": { - "$ref": "#/definitions/sep.SepData" + "$ref": "#/definitions/api-service_internal_models_vclaim_sep.SepData" }, "message": { "type": "string" @@ -1633,7 +1807,7 @@ } } }, - "sep.SepRujukan": { + "api-service_internal_models_vclaim_sep.SepRujukan": { "type": "object", "required": [ "asalRujukan", diff --git a/docs/swagger.yaml b/docs/swagger.yaml index c6a87d1..4101dea 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -306,7 +306,7 @@ definitions: message: type: string type: object - peserta.PesertaData: + api-service_internal_models_vclaim_peserta.PesertaData: properties: aktif: type: string @@ -364,10 +364,10 @@ definitions: tglTunggak: type: string type: object - peserta.PesertaResponse: + api-service_internal_models_vclaim_peserta.PesertaResponse: properties: data: - $ref: '#/definitions/peserta.PesertaData' + $ref: '#/definitions/api-service_internal_models_vclaim_peserta.PesertaData' message: type: string metaData: {} @@ -378,7 +378,7 @@ definitions: timestamp: type: string type: object - sep.SepData: + api-service_internal_models_vclaim_sep.SepData: properties: catatan: type: string @@ -404,15 +404,15 @@ definitions: noSep: type: string peserta: - $ref: '#/definitions/peserta.PesertaData' + $ref: '#/definitions/api-service_internal_models_vclaim_peserta.PesertaData' poli: type: string rujukan: - $ref: '#/definitions/sep.SepRujukan' + $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepRujukan' tglSep: type: string type: object - sep.SepRequest: + api-service_internal_models_vclaim_sep.SepRequest: properties: catatan: type: string @@ -442,7 +442,7 @@ definitions: request_id: type: string rujukan: - $ref: '#/definitions/sep.SepRujukan' + $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepRujukan' tglSep: type: string timestamp: @@ -460,10 +460,10 @@ definitions: - tglSep - user type: object - sep.SepResponse: + api-service_internal_models_vclaim_sep.SepResponse: properties: data: - $ref: '#/definitions/sep.SepData' + $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepData' message: type: string request_id: @@ -473,7 +473,7 @@ definitions: timestamp: type: string type: object - sep.SepRujukan: + api-service_internal_models_vclaim_sep.SepRujukan: properties: asalRujukan: enum: @@ -969,11 +969,27 @@ paths: - Token /peserta/:nokartu: get: + consumes: + - application/json + description: Get participant eligibility information by card number + parameters: + - description: Request ID for tracking + in: header + name: X-Request-ID + type: string + - description: nokartu + example: '"example_value"' + in: path + name: nokartu + required: true + type: string + produces: + - application/json responses: "200": description: Successfully retrieved PesertaBynokartu data schema: - $ref: '#/definitions/peserta.PesertaResponse' + $ref: '#/definitions/api-service_internal_models_vclaim_peserta.PesertaResponse' "400": description: Bad request - invalid parameters schema: @@ -990,13 +1006,34 @@ paths: description: Internal server error schema: $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + security: + - ApiKeyAuth: [] + summary: Get PesertaBynokartu data + tags: + - Peserta /peserta/nik/:nik: get: + consumes: + - application/json + description: Get participant eligibility information by NIK + parameters: + - description: Request ID for tracking + in: header + name: X-Request-ID + type: string + - description: nik + example: '"example_value"' + in: path + name: nik + required: true + type: string + produces: + - application/json responses: "200": description: Successfully retrieved PesertaBynik data schema: - $ref: '#/definitions/peserta.PesertaResponse' + $ref: '#/definitions/api-service_internal_models_vclaim_peserta.PesertaResponse' "400": description: Bad request - invalid parameters schema: @@ -1013,8 +1050,16 @@ paths: description: Internal server error schema: $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + security: + - ApiKeyAuth: [] + summary: Get PesertaBynik data + tags: + - Peserta /sep: post: + consumes: + - application/json + description: Manage SEP (Surat Eligibilitas Peserta) parameters: - description: Request ID for tracking in: header @@ -1025,12 +1070,14 @@ paths: name: request required: true schema: - $ref: '#/definitions/sep.SepRequest' + $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepRequest' + produces: + - application/json responses: "201": description: Successfully created SepSep schema: - $ref: '#/definitions/sep.SepResponse' + $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepResponse' "400": description: Bad request - invalid request body or validation error schema: @@ -1047,13 +1094,34 @@ paths: 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/sep.SepResponse' + $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepResponse' "400": description: Bad request - invalid parameters schema: @@ -1070,12 +1138,33 @@ paths: description: Internal server error schema: $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + security: + - ApiKeyAuth: [] + summary: Delete existing SepSep + tags: + - Sep get: + 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 retrieved SepSep data schema: - $ref: '#/definitions/sep.SepResponse' + $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepResponse' "400": description: Bad request - invalid parameters schema: @@ -1092,19 +1181,39 @@ paths: description: Internal server error schema: $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + security: + - ApiKeyAuth: [] + summary: Get SepSep 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/sep.SepRequest' + $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepRequest' + produces: + - application/json responses: "200": description: Successfully updated SepSep schema: - $ref: '#/definitions/sep.SepResponse' + $ref: '#/definitions/api-service_internal_models_vclaim_sep.SepResponse' "400": description: Bad request - invalid parameters or request body schema: @@ -1121,6 +1230,11 @@ paths: description: Internal server error schema: $ref: '#/definitions/api-service_internal_models.ErrorResponseBpjs' + security: + - ApiKeyAuth: [] + summary: Update existing SepSep + tags: + - Sep schemes: - http - https diff --git a/generetebpjs b/generetebpjs deleted file mode 100644 index 69d85eb..0000000 --- a/generetebpjs +++ /dev/null @@ -1,847 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strings" - "text/template" - "time" - - "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() -} - -// ServiceConfig represents the main configuration structure -type ServiceConfig struct { - Services map[string]Service `yaml:"services"` - Global GlobalConfig `yaml:"global,omitempty"` -} - -// 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 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]SubEndpoints `yaml:"endpoints"` - Middleware []string `yaml:"middleware,omitempty"` // FIXED: Changed to []string - Dependencies []string `yaml:"dependencies,omitempty"` // FIXED: Changed to []string -} - -// 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"` -} - -// SubEndpoints represents nested endpoint configuration for tree structure -type SubEndpoints map[string]Endpoint - -// 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 -} - -// 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 - 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.) -} - -// Updated template for merged handler structure -const handlerTemplate = ` -// Service: {{.ServiceName}} ({{.Category}}) -// Description: {{.Description}} - -package handlers - -import ( - "context" - "strings" - "net/http" - "time" - - "{{.ModuleName}}/internal/config" - "{{.ModuleName}}/internal/models" - "{{.ModuleName}}/internal/models/vclaim/peserta" - "{{.ModuleName}}/internal/models/vclaim/sep" - "{{.ModuleName}}/internal/services/bpjs" - "{{.ModuleName}}/pkg/logger" - - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" - "github.com/google/uuid" -) - -// {{.ServiceName}}Handler handles {{.ServiceName}} BPJS services -type {{.ServiceName}}Handler struct { - service services.{{.ServiceName}}Service - validator *validator.Validate - logger logger.Logger - config config.BpjsConfig -} - -// {{.ServiceName}}HandlerConfig contains configuration for {{.ServiceName}}Handler -type {{.ServiceName}}HandlerConfig struct { - BpjsConfig config.BpjsConfig - Logger logger.Logger - Validator *validator.Validate -} - -// New{{.ServiceName}}Handler creates a new {{.ServiceName}}Handler -func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig) *{{.ServiceName}}Handler { - return &{{.ServiceName}}Handler{ - service: services.NewService(cfg.BpjsConfig), - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.BpjsConfig, - } -} - -{{range .Endpoints}} -{{if .HasGet}} -// Get{{.NameUpper}} retrieves {{.Name}} data -{{if $.HasSwagger}} -// @Summary Get {{.Name}} data -// @Description {{.Description}} -// @Tags {{join .Tags ","}} -// @Accept json -// @Produce json -{{if .RequireAuth}} -// @Security ApiKeyAuth -{{end}} -{{range .PathParams}} -// @Param {{.}} path string true "{{.}}" -{{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} -// @Failure 400 {object} models.ErrorResponseBpjs -// @Failure 500 {object} models.ErrorResponseBpjs -// @Router {{.GetPath}} [get] -{{end}} -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}} - err := h.service.Get(ctx, endpoint, &response) - {{else}} - err := h.service.Get(ctx, "{{.GetPath}}", &response) - {{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 - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasPost}} -// Create{{.NameUpper}} creates new {{.Name}} -{{if $.HasSwagger}} -// @Summary Create {{.Name}} -// @Description {{.Description}} -// @Tags {{join .Tags ","}} -// @Accept json -// @Produce json -{{if .RequireAuth}} -// @Security ApiKeyAuth -{{end}} -// @Param request body {{.ModelPackage}}.{{.Model}} true "{{.Name}} data" -// @Success 201 {object} {{.ModelPackage}}.{{.ResponseModel}} -// @Failure 400 {object} models.ErrorResponseBpjs -// @Failure 500 {object} models.ErrorResponseBpjs -// @Router {{.PostPath}} [post] -{{end}} -func (h *{{$.ServiceName}}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) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - }) - {{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 - } - - // 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 - } - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - err := h.service.Post(ctx, "{{.PostPath}}", &req, &response) - 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 - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusCreated, response) -} -{{end}} - -{{if .HasPut}} -// Update{{.NameUpper}} updates existing {{.Name}} -{{if $.HasSwagger}} -// @Summary Update {{.Name}} -// @Description {{.Description}} -// @Tags {{join .Tags ","}} -// @Accept json -// @Produce json -{{if .RequireAuth}} -// @Security ApiKeyAuth -{{end}} -{{range .PathParams}} -// @Param {{.}} path string true "{{.}}" -{{end}} -// @Param request body {{.ModelPackage}}.{{.Model}} true "{{.Name}} data" -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} -// @Failure 400 {object} models.ErrorResponseBpjs -// @Failure 500 {object} models.ErrorResponseBpjs -// @Router {{.PutPath}} [put] -{{end}} -func (h *{{$.ServiceName}}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) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Update{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - }) - {{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}} - - 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 - } - - // 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 - } - - // Call service method - var response {{.ModelPackage}}.{{.ResponseModel}} - {{if .PathParams}} - endpoint := "{{.PutPath}}" - {{range .PathParams}} - endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) - {{end}} - err := h.service.Put(ctx, endpoint, &req, &response) - {{else}} - err := h.service.Put(ctx, "{{.PutPath}}", &req, &response) - {{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 - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasDelete}} -// Delete{{.NameUpper}} deletes existing {{.Name}} -{{if $.HasSwagger}} -// @Summary Delete {{.Name}} -// @Description {{.Description}} -// @Tags {{join .Tags ","}} -// @Accept json -// @Produce json -{{if .RequireAuth}} -// @Security ApiKeyAuth -{{end}} -{{range .PathParams}} -// @Param {{.}} path string true "{{.}}" -{{end}} -// @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} -// @Failure 400 {object} models.ErrorResponseBpjs -// @Failure 500 {object} models.ErrorResponseBpjs -// @Router {{.DeletePath}} [delete] -{{end}} -func (h *{{$.ServiceName}}Handler) Delete{{.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) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Delete{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - }) - {{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}} - err := h.service.Delete(ctx, endpoint, &response) - {{else}} - err := h.service.Delete(ctx, "{{.DeletePath}}", &response) - {{end}} - 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 - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} -{{end}} -{{end}} -` - -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] - } - - // Load configuration - 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...") - fmt.Printf("šŸ“ Config file: %s\n", configFile) - if targetService != "" { - fmt.Printf("šŸŽÆ Target service: %s\n", targetService) - } - - 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) - if err != nil { - fmt.Printf("āŒ Error generating handler for %s: %v\n", serviceName, err) - errors++ - continue - } - fmt.Printf("āœ… Generated handler: %s_handler.go\n", strings.ToLower(serviceName)) - generated++ - } - - // Summary - fmt.Println("\nšŸ“Š Generation Summary:") - fmt.Printf(" āœ… Successfully generated: %d handlers\n", generated) - if errors > 0 { - fmt.Printf(" āŒ Failed: %d handlers\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") -} - -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) - } - - var config ServiceConfig - err = yaml.Unmarshal(data, &config) - if err != nil { - return nil, fmt.Errorf("failed to parse YAML config: %w", err) - } - - // Set default values - if config.Global.ModuleName == "" { - config.Global.ModuleName = "api-service" - } - if config.Global.OutputDir == "" { - config.Global.OutputDir = "internal/handlers" - } - - return &config, nil -} - -func generateHandler(serviceName string, service Service, globalConfig GlobalConfig) error { - // Prepare template data - templateData := TemplateData{ - ServiceName: service.Name, - ServiceLower: strings.ToLower(service.Name), - ServiceUpper: strings.ToUpper(service.Name), - Category: service.Category, - Package: service.Package, - 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, // Now []string - Middleware: service.Middleware, // Now []string - GlobalConfig: globalConfig, - } - - // Check for advanced features - for _, subEndpoints := range service.Endpoints { - for _, endpoint := range subEndpoints { - if endpoint.RequireAuth { - templateData.HasAuth = true - } - if endpoint.CacheEnabled { - templateData.HasCache = true - } - } - } - - // Process endpoints - for endpointName, subEndpoints := range service.Endpoints { - for subEndpointName, endpoint := range subEndpoints { - // Compose full endpoint name with sub-endpoint name - fullEndpointName := endpointName - if subEndpointName != "" { - fullEndpointName = fullEndpointName + strings.Title(subEndpointName) - } - endpointData := processEndpoint(fullEndpointName, endpoint, endpointName) - templateData.Endpoints = append(templateData.Endpoints, endpointData) - } - } - - // Create output directory - outputDir := globalConfig.OutputDir - if outputDir == "" { - outputDir = "internal/handlers" - } - if err := os.MkdirAll(outputDir, 0755); err != nil { - return fmt.Errorf("failed to create output directory: %w", err) - } - - // Generate handler file - filename := filepath.Join(outputDir, fmt.Sprintf("%s_handler.go", strings.ToLower(serviceName))) - - // 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) - } - - file, err := os.Create(filename) - if err != nil { - return fmt.Errorf("failed to create file: %w", err) - } - defer file.Close() - - err = tmpl.Execute(file, templateData) - if err != nil { - return fmt.Errorf("failed to execute template: %w", err) - } - - return nil -} - -func processEndpoint(name string, endpoint Endpoint, endpointGroup string) EndpointData { - data := EndpointData{ - Name: strings.Title(name), - NameLower: strings.ToLower(name), - NameUpper: strings.ToUpper(name), - NameCamel: toCamelCase(name), - Methods: endpoint.Methods, - GetPath: endpoint.GetPath, - PostPath: endpoint.PostPath, - PutPath: endpoint.PutPath, - DeletePath: endpoint.DeletePath, - PatchPath: endpoint.PatchPath, - Model: endpoint.Model, - ResponseModel: endpoint.ResponseModel, - Description: endpoint.Description, - Summary: endpoint.Summary, - Tags: endpoint.Tags, - RequireAuth: endpoint.RequireAuth, - RateLimit: endpoint.RateLimit, - CacheEnabled: endpoint.CacheEnabled, - CacheTTL: getOrDefault(endpoint.CacheTTL, 300), - CustomHeaders: endpoint.CustomHeaders, - ModelPackage: endpointGroup, // Set the model package based on endpoint group - } - - // Set method flags and extract path parameters - for _, method := range endpoint.Methods { - switch strings.ToUpper(method) { - case "GET": - data.HasGet = true - data.PathParams = extractPathParams(endpoint.GetPath) - case "POST": - data.HasPost = true - case "PUT": - data.HasPut = true - data.PathParams = extractPathParams(endpoint.PutPath) - case "DELETE": - data.HasDelete = true - data.PathParams = extractPathParams(endpoint.DeletePath) - case "PATCH": - data.HasPatch = true - data.PathParams = extractPathParams(endpoint.PatchPath) - } - } - - return data -} - -func extractPathParams(path string) []string { - if path == "" { - return nil - } - - var params []string - parts := strings.Split(path, "/") - for _, part := range parts { - if strings.HasPrefix(part, ":") { - params = append(params, strings.TrimPrefix(part, ":")) - } - } - - return params -} - -func toCamelCase(str string) string { - words := strings.FieldsFunc(str, func(c rune) bool { - return c == '_' || c == '-' || c == ' ' - }) - - if len(words) == 0 { - return str - } - - result := strings.ToLower(words[0]) - for _, word := range words[1:] { - result += strings.Title(strings.ToLower(word)) - } - - return result -} - -func getOrDefault(value, defaultValue int) int { - if value == 0 { - return defaultValue - } - return value -} diff --git a/genet b/genet deleted file mode 100644 index f6a57ed..0000000 --- a/genet +++ /dev/null @@ -1,625 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - "text/template" - "time" - - "gopkg.in/yaml.v2" -) - -// ServiceConfig represents the main configuration structure -type ServiceConfig struct { - Services map[string]Service `yaml:"services"` - Global GlobalConfig `yaml:"global,omitempty"` -} - -// 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 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]Endpoint `yaml:"endpoints"` - Middleware []string `yaml:"middleware,omitempty"` // FIXED: Changed to []string - Dependencies []string `yaml:"dependencies,omitempty"` // FIXED: Changed to []string -} - -// 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"` // ADDED: Missing field -} - -// 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 -} - -// 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 - 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 -} - -// Template remains the same as before... -const handlerTemplate = ` -// Code generated by generate-dynamic-handler.go; DO NOT EDIT. -// Generated at: {{.Timestamp}} -// Service: {{.ServiceName}} ({{.Category}}) -// Description: {{.Description}} - -package handlers - -import ( - "context" - "net/http" - "time" - - "{{.ModuleName}}/internal/config" - "{{.ModuleName}}/internal/models/reference" - "{{.ModuleName}}/pkg/logger" - - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" - "github.com/google/uuid" -) - -// {{.ServiceName}}Service defines {{.ServiceName}} service interface -type {{.ServiceName}}Service interface { - {{range .Endpoints}} - {{if .HasGet}} - Get{{.Name}}(ctx context.Context{{range .PathParams}}, {{.}} string{{end}}) (*reference.{{.Name}}Data, error) - {{end}} - {{if .HasPost}} - Create{{.Name}}(ctx context.Context, req *reference.{{.Model}}) (*reference.{{.Name}}Data, error) - {{end}} - {{if .HasPut}} - Update{{.Name}}(ctx context.Context{{range .PathParams}}, {{.}} string{{end}}, req *reference.{{.Model}}) (*reference.{{.Name}}Data, error) - {{end}} - {{if .HasDelete}} - Delete{{.Name}}(ctx context.Context{{range .PathParams}}, {{.}} string{{end}}) error - {{end}} - {{end}} -} - -// {{.ServiceName}}Handler handles {{.ServiceName}} BPJS services -type {{.ServiceName}}Handler struct { - service {{.ServiceName}}Service - validator *validator.Validate - logger logger.Logger - config config.BpjsConfig -} - -// {{.ServiceName}}HandlerConfig contains configuration for {{.ServiceName}}Handler -type {{.ServiceName}}HandlerConfig struct { - BpjsConfig config.BpjsConfig - Logger logger.Logger - Validator *validator.Validate -} - -// New{{.ServiceName}}Handler creates a new {{.ServiceName}}Handler -func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig, service {{.ServiceName}}Service) *{{.ServiceName}}Handler { - return &{{.ServiceName}}Handler{ - service: service, - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.BpjsConfig, - } -} - -{{range .Endpoints}} -{{if .HasGet}} -// Get{{.NameUpper}} retrieves {{.Name}} data -{{if $.HasSwagger}} -// @Summary Get {{.Name}} data -// @Description {{.Description}} -// @Tags {{join .Tags ","}} -// @Accept json -// @Produce json -{{if .RequireAuth}} -// @Security ApiKeyAuth -{{end}} -{{range .PathParams}} -// @Param {{.}} path string true "{{.}}" -{{end}} -// @Success 200 {object} reference.{{.ResponseModel}} -// @Failure 400 {object} reference.ErrorResponse -// @Failure 500 {object} reference.ErrorResponse -// @Router {{.GetPath}} [get] -{{end}} -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, reference.ErrorResponse{ - Status: "error", - Message: "Missing required parameter {{.}}", - RequestID: requestID, - }) - return - } - {{end}} - - // Call service method - var response reference.{{.ResponseModel}} - {{if .PathParams}} - result, err := h.service.Get{{.Name}}(ctx{{range .PathParams}}, {{.}}{{end}}) - {{else}} - result, err := h.service.Get{{.Name}}(ctx) - {{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, reference.ErrorResponse{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - response.Data = result - c.JSON(http.StatusOK, response) -} -{{end}} - -{{if .HasPost}} -// Create{{.NameUpper}} creates new {{.Name}} -{{if $.HasSwagger}} -// @Summary Create {{.Name}} -// @Description {{.Description}} -// @Tags {{join .Tags ","}} -// @Accept json -// @Produce json -{{if .RequireAuth}} -// @Security ApiKeyAuth -{{end}} -// @Param request body reference.{{.Model}} true "{{.Name}} data" -// @Success 201 {object} reference.{{.ResponseModel}} -// @Failure 400 {object} reference.ErrorResponse -// @Failure 500 {object} reference.ErrorResponse -// @Router {{.PostPath}} [post] -{{end}} -func (h *{{$.ServiceName}}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) - } - - {{if $.HasLogger}} - h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{ - "request_id": requestID, - }) - {{end}} - - var req reference.{{.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, reference.ErrorResponse{ - 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, reference.ErrorResponse{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Call service method - var response reference.{{.ResponseModel}} - result, err := h.service.Create{{.Name}}(ctx, &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, reference.ErrorResponse{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - response.Data = result - c.JSON(http.StatusCreated, response) -} -{{end}} -{{end}} -` - -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] - } - - // Load configuration - 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...") - fmt.Printf("šŸ“ Config file: %s\n", configFile) - if targetService != "" { - fmt.Printf("šŸŽÆ Target service: %s\n", targetService) - } - - 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) - if err != nil { - fmt.Printf("āŒ Error generating handler for %s: %v\n", serviceName, err) - errors++ - continue - } - fmt.Printf("āœ… Generated handler: %s_handler.go\n", strings.ToLower(serviceName)) - generated++ - } - - // Summary - fmt.Println("\nšŸ“Š Generation Summary:") - fmt.Printf(" āœ… Successfully generated: %d handlers\n", generated) - if errors > 0 { - fmt.Printf(" āŒ Failed: %d handlers\n", errors) - } - - if generated > 0 { - fmt.Println("šŸŽ‰ Generation completed 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") -} - -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) - } - - var config ServiceConfig - err = yaml.Unmarshal(data, &config) - if err != nil { - return nil, fmt.Errorf("failed to parse YAML config: %w", err) - } - - // Set default values - if config.Global.ModuleName == "" { - config.Global.ModuleName = "api-service" - } - if config.Global.OutputDir == "" { - config.Global.OutputDir = "internal/handlers" - } - - return &config, nil -} - -func generateHandler(serviceName string, service Service, globalConfig GlobalConfig) error { - // Prepare template data - templateData := TemplateData{ - ServiceName: service.Name, - ServiceLower: strings.ToLower(service.Name), - ServiceUpper: strings.ToUpper(service.Name), - Category: service.Category, - Package: service.Package, - 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, // Now []string - Middleware: service.Middleware, // Now []string - GlobalConfig: globalConfig, - } - - // Check for advanced features - for _, endpoint := range service.Endpoints { - if endpoint.RequireAuth { - templateData.HasAuth = true - } - if endpoint.CacheEnabled { - templateData.HasCache = true - } - } - - // Process endpoints - for endpointName, endpoint := range service.Endpoints { - endpointData := processEndpoint(endpointName, endpoint) - templateData.Endpoints = append(templateData.Endpoints, endpointData) - } - - // Create output directory - outputDir := globalConfig.OutputDir - if outputDir == "" { - outputDir = "internal/handlers" - } - if err := os.MkdirAll(outputDir, 0755); err != nil { - return fmt.Errorf("failed to create output directory: %w", err) - } - - // Generate handler file - filename := filepath.Join(outputDir, fmt.Sprintf("%s_handler.go", strings.ToLower(serviceName))) - - // 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) - } - - file, err := os.Create(filename) - if err != nil { - return fmt.Errorf("failed to create file: %w", err) - } - defer file.Close() - - err = tmpl.Execute(file, templateData) - if err != nil { - return fmt.Errorf("failed to execute template: %w", err) - } - - return nil -} - -func processEndpoint(name string, endpoint Endpoint) EndpointData { - data := EndpointData{ - Name: strings.Title(name), - NameLower: strings.ToLower(name), - NameUpper: strings.ToUpper(name), - NameCamel: toCamelCase(name), - Methods: endpoint.Methods, - GetPath: endpoint.GetPath, - PostPath: endpoint.PostPath, - PutPath: endpoint.PutPath, - DeletePath: endpoint.DeletePath, - PatchPath: endpoint.PatchPath, - Model: endpoint.Model, - ResponseModel: endpoint.ResponseModel, - Description: endpoint.Description, - Summary: endpoint.Summary, - Tags: endpoint.Tags, - RequireAuth: endpoint.RequireAuth, - RateLimit: endpoint.RateLimit, - CacheEnabled: endpoint.CacheEnabled, - CacheTTL: getOrDefault(endpoint.CacheTTL, 300), - CustomHeaders: endpoint.CustomHeaders, - } - - // 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/go.mod b/go.mod index 0c3514b..4c23603 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/swaggo/gin-swagger v1.6.0 github.com/swaggo/swag v1.16.6 github.com/tidwall/gjson v1.18.0 + gopkg.in/yaml.v2 v2.4.0 ) require ( @@ -82,7 +83,6 @@ require ( golang.org/x/text v0.28.0 // indirect golang.org/x/tools v0.35.0 // indirect google.golang.org/protobuf v1.36.7 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/gorm v1.30.0 // indirect ) diff --git a/internal/handlers/test.txt/sw b/internal/handlers/test.txt/sw deleted file mode 100644 index 1d6499d..0000000 --- a/internal/handlers/test.txt/sw +++ /dev/null @@ -1,510 +0,0 @@ -// Code generated by generate-dynamic-handler.go DO NOT EDIT. -// Generated at 2025-09-01 13:38:57 -// Service: VClaim (vclaim) -// Description: BPJS VClaim service for eligibility and SEP management - -package handlers - -import ( - "context" - "net/http" - "strings" - "time" - - "api-service/internal/config" - "api-service/internal/models" - "api-service/internal/models/vclaim/peserta" - "api-service/internal/models/vclaim/sep" - "api-service/internal/services" - "api-service/pkg/logger" - - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" - "github.com/google/uuid" -) - -// VClaimHandler handles VClaim BPJS services -type VClaimHandler struct { - service services.VClaimService - validator *validator.Validate - logger logger.Logger - config config.BpjsConfig -} - -// VClaimHandlerConfig contains configuration for VClaimHandler -type VClaimHandlerConfig struct { - BpjsConfig config.BpjsConfig - Logger logger.Logger - Validator *validator.Validate -} - -// NewVClaimHandler creates a new VClaimHandler -func NewVClaimHandler(cfg VClaimHandlerConfig) *VClaimHandler { - return &VClaimHandler{ - service: services.NewService(cfg.BpjsConfig), - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.BpjsConfig, - } -} - -// GetPesertaBynokartu godoc -// @Summary Get participant data by card number -// @Description Get participant eligibility information from BPJS by card number -// @Tags vclaim,peserta -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param nokartu path string true "BPJS card number" example("0000054321654") -// @Success 200 {object} peserta.PesertaResponse "Successfully retrieved participant data" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid card number format" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - participant not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router /peserta/{nokartu} [get] -func (h *VClaimHandler) GetPesertaBynokartu(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 GetPesertaBynokartu 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) - - err := h.service.Get(ctx, endpoint, &response) - if err != nil { - h.logger.Error("Failed to get PesertaBynokartu", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - c.JSON(http.StatusOK, response) -} - -// GetPesertaBynik godoc -// @Summary Get participant data by NIK -// @Description Get participant eligibility information from BPJS by National ID Number (NIK) -// @Tags vclaim,peserta -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param nik path string true "National ID Number (NIK)" example("3201234567890123") -// @Success 200 {object} peserta.PesertaResponse "Successfully retrieved participant data" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid NIK format" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - participant not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router /peserta/nik/{nik} [get] -func (h *VClaimHandler) GetPesertaBynik(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 GetPesertaBynik 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) - - err := h.service.Get(ctx, endpoint, &response) - if err != nil { - h.logger.Error("Failed to get PesertaBynik", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - c.JSON(http.StatusOK, response) -} - -// GetSepSep godoc -// @Summary Get SEP data by number -// @Description Get SEP (Surat Eligibilitas Peserta) information by SEP number -// @Tags vclaim,sep -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param nosep path string true "SEP Number" example("0301R0010717V000001") -// @Success 200 {object} sep.SepResponse "Successfully retrieved SEP data" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid SEP number format" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - SEP not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router /sep/{nosep} [get] -func (h *VClaimHandler) GetSepSep(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 GetSepSep request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "sep/{nosep}", - "nosep": c.Param("nosep"), - }) - - // Extract path parameters - nosep := c.Param("nosep") - if nosep == "" { - h.logger.Error("Missing required parameter: nosep", map[string]interface{}{ - "request_id": requestID, - }) - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter: nosep", - RequestID: requestID, - }) - return - } - - // Call service method - var response sep.SepResponse - endpoint := "sep/{nosep}" - endpoint = strings.Replace(endpoint, "{nosep}", nosep, 1) - - err := h.service.Get(ctx, endpoint, &response) - if err != nil { - h.logger.Error("Failed to get SepSep", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - c.JSON(http.StatusOK, response) -} - -// CreateSepSep godoc -// @Summary Create new SEP -// @Description Create a new SEP (Surat Eligibilitas Peserta) for participant -// @Tags vclaim,sep -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param request body sep.SepRequest true "SEP creation data" -// @Success 201 {object} sep.SepResponse "Successfully created SEP" -// @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 - SEP already exists" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router /sep [post] -func (h *VClaimHandler) CreateSepSep(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - h.logger.Info("Processing CreateSepSep request", map[string]interface{}{ - "request_id": requestID, - }) - - var req sep.SepRequest - if err := c.ShouldBindJSON(&req); err != nil { - h.logger.Error("Invalid 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 - if err := h.validator.Struct(&req); err != nil { - h.logger.Error("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 sep.SepResponse - err := h.service.Post(ctx, "sep", &req, &response) - if err != nil { - h.logger.Error("Failed to create SepSep", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - c.JSON(http.StatusCreated, response) -} - -// UpdateSepSep godoc -// @Summary Update existing SEP -// @Description Update an existing SEP (Surat Eligibilitas Peserta) by SEP number -// @Tags vclaim,sep -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param nosep path string true "SEP Number to update" example("0301R0010717V000001") -// @Param request body sep.SepRequest true "SEP update data" -// @Success 200 {object} sep.SepResponse "Successfully updated SEP" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid request body or validation error" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - SEP not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router /sep/{nosep} [put] -func (h *VClaimHandler) UpdateSepSep(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - h.logger.Info("Processing UpdateSepSep request", map[string]interface{}{ - "request_id": requestID, - }) - - nosep := c.Param("nosep") - if nosep == "" { - h.logger.Error("Missing required parameter: nosep", map[string]interface{}{ - "request_id": requestID, - }) - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter: nosep", - RequestID: requestID, - }) - return - } - - var req sep.SepRequest - if err := c.ShouldBindJSON(&req); err != nil { - h.logger.Error("Invalid 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 - if err := h.validator.Struct(&req); err != nil { - h.logger.Error("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 sep.SepResponse - endpoint := "sep/{nosep}" - endpoint = strings.Replace(endpoint, "{nosep}", nosep, 1) - - err := h.service.Put(ctx, endpoint, &req, &response) - if err != nil { - h.logger.Error("Failed to update SepSep", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - c.JSON(http.StatusOK, response) -} - -// DeleteSepSep godoc -// @Summary Delete SEP -// @Description Delete an existing SEP (Surat Eligibilitas Peserta) by SEP number -// @Tags vclaim,sep -// @Accept json -// @Produce json -// @Security ApiKeyAuth -// @Param X-Request-ID header string false "Request ID for tracking" -// @Param nosep path string true "SEP Number to delete" example("0301R0010717V000001") -// @Success 200 {object} sep.SepResponse "Successfully deleted SEP" -// @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid SEP number format" -// @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" -// @Failure 404 {object} models.ErrorResponseBpjs "Not found - SEP not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router /sep/{nosep} [delete] -func (h *VClaimHandler) DeleteSepSep(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - h.logger.Info("Processing DeleteSepSep request", map[string]interface{}{ - "request_id": requestID, - }) - - nosep := c.Param("nosep") - if nosep == "" { - h.logger.Error("Missing required parameter: nosep", map[string]interface{}{ - "request_id": requestID, - }) - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter: nosep", - RequestID: requestID, - }) - return - } - - // Call service method - var response sep.SepResponse - endpoint := "sep/{nosep}" - endpoint = strings.Replace(endpoint, "{nosep}", nosep, 1) - - err := h.service.Delete(ctx, endpoint, &response) - if err != nil { - h.logger.Error("Failed to delete SepSep", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - - c.JSON(http.StatusOK, response) -} diff --git a/internal/handlers/test.txt/vclaim.backup b/internal/handlers/test.txt/vclaim.backup deleted file mode 100644 index cffc5bf..0000000 --- a/internal/handlers/test.txt/vclaim.backup +++ /dev/null @@ -1,543 +0,0 @@ -// Code generated by generate-dynamic-handler.go; DO NOT EDIT. -// Generated at: 2025-09-01 13:38:57 -// Service: VClaim (vclaim) -// Description: BPJS VClaim service for eligibility and SEP management - -package handlers - -import ( - "context" - "net/http" - "strings" - "time" - - "api-service/internal/config" - "api-service/internal/models" - "api-service/internal/models/vclaim/peserta" - "api-service/internal/models/vclaim/sep" - services "api-service/internal/services/bpjs" - "api-service/pkg/logger" - - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" - "github.com/google/uuid" -) - -// VClaimHandler handles VClaim BPJS services -type VClaimHandler struct { - service services.VClaimService - validator *validator.Validate - logger logger.Logger - config config.BpjsConfig -} - -// VClaimHandlerConfig contains configuration for VClaimHandler -type VClaimHandlerConfig struct { - BpjsConfig config.BpjsConfig - Logger logger.Logger - Validator *validator.Validate -} - -// NewVClaimHandler creates a new VClaimHandler -func NewVClaimHandler(cfg VClaimHandlerConfig) *VClaimHandler { - return &VClaimHandler{ - service: services.NewService(cfg.BpjsConfig), - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.BpjsConfig, - } -} - -/* -GetPesertaBynokartu godoc -@Summary Get PesertaBynokartu data -@Description Get participant eligibility information by card number -@Tags vclaim,peserta,nokartu -@Accept json -@Produce json -@Param nokartu path string true "nokartu" -@Success 200 {object} peserta.PesertaResponse -@Failure 400 {object} models.ErrorResponseBpjs -@Failure 500 {object} models.ErrorResponseBpjs -@Router /peserta/:nokartu [get] -*/ -func (h *VClaimHandler) GetPesertaBynokartu(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 GetPesertaBynokartu 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) - - err := h.service.Get(ctx, endpoint, &response) - - if err != nil { - - h.logger.Error("Failed to get PesertaBynokartu", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} - -/* -GetPesertaBynik godoc -@Summary Get PesertaBynik data -@Description Get participant eligibility information by NIK -@Tags vclaim,peserta,nik -@Accept json -@Produce json -@Param nik path string true "nik" -@Success 200 {object} peserta.PesertaResponse -@Failure 400 {object} models.ErrorResponseBpjs -@Failure 500 {object} models.ErrorResponseBpjs -@Router /peserta/nik/:nik [get] -*/ -func (h *VClaimHandler) GetPesertaBynik(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 GetPesertaBynik 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) - - err := h.service.Get(ctx, endpoint, &response) - - if err != nil { - - h.logger.Error("Failed to get PesertaBynik", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} - -/* -GetSepSep godoc -@Summary Get SepSep data -@Description Manage SEP (Surat Eligibilitas Peserta) -@Tags vclaim,sep -@Accept json -@Produce json -@Param nosep path string true "nosep" -@Success 200 {object} sep.SepResponse -@Failure 400 {object} models.ErrorResponseBpjs -@Failure 500 {object} models.ErrorResponseBpjs -@Router /sep/:nosep [get] -*/ -func (h *VClaimHandler) GetSepSep(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 GetSepSep request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "/sep/:nosep", - - "nosep": c.Param("nosep"), - }) - - // Extract path parameters - - nosep := c.Param("nosep") - if nosep == "" { - - h.logger.Error("Missing required parameter nosep", map[string]interface{}{ - "request_id": requestID, - }) - - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter nosep", - RequestID: requestID, - }) - return - } - - // Call service method - var response sep.SepResponse - - endpoint := "/sep/:nosep" - - endpoint = strings.Replace(endpoint, ":nosep", nosep, 1) - - err := h.service.Get(ctx, endpoint, &response) - - if err != nil { - - h.logger.Error("Failed to get SepSep", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} - -/* -CreateSepSep godoc -@Summary Create SepSep -@Description Manage SEP (Surat Eligibilitas Peserta) -@Tags vclaim,sep -@Accept json -@Produce json -@Param request body sep.SepRequest true "SepSep data" -@Success 201 {object} sep.SepResponse -@Failure 400 {object} models.ErrorResponseBpjs -@Failure 500 {object} models.ErrorResponseBpjs -@Router /sep [post] -*/ -func (h *VClaimHandler) CreateSepSep(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - h.logger.Info("Processing CreateSepSep request", map[string]interface{}{ - "request_id": requestID, - }) - - var req sep.SepRequest - if err := c.ShouldBindJSON(&req); err != nil { - - h.logger.Error("Invalid 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 - if err := h.validator.Struct(&req); err != nil { - - h.logger.Error("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 sep.SepResponse - err := h.service.Post(ctx, "/sep", &req, &response) - if err != nil { - - h.logger.Error("Failed to create SepSep", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusCreated, response) -} - -/* -UpdateSepSep godoc -@Summary Update SepSep -@Description Manage SEP (Surat Eligibilitas Peserta) -@Tags vclaim,sep -@Accept json -@Produce json -@Param nosep path string true "nosep" -@Param request body sep.SepRequest true "SepSep data" -@Success 200 {object} sep.SepResponse -@Failure 400 {object} models.ErrorResponseBpjs -@Failure 500 {object} models.ErrorResponseBpjs -@Router /sep/:nosep [put] -*/ -func (h *VClaimHandler) UpdateSepSep(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - h.logger.Info("Processing UpdateSepSep request", map[string]interface{}{ - "request_id": requestID, - }) - - nosep := c.Param("nosep") - if nosep == "" { - - h.logger.Error("Missing required parameter nosep", map[string]interface{}{ - "request_id": requestID, - }) - - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter nosep", - RequestID: requestID, - }) - return - } - - var req sep.SepRequest - if err := c.ShouldBindJSON(&req); err != nil { - - h.logger.Error("Invalid 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 - if err := h.validator.Struct(&req); err != nil { - - h.logger.Error("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 sep.SepResponse - - endpoint := "/sep/:nosep" - - endpoint = strings.Replace(endpoint, ":nosep", nosep, 1) - - err := h.service.Put(ctx, endpoint, &req, &response) - - if err != nil { - - h.logger.Error("Failed to update SepSep", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} - -/* -DeleteSepSep godoc -@Summary Delete SepSep -@Description Manage SEP (Surat Eligibilitas Peserta) -@Tags vclaim,sep -@Accept json -@Produce json -@Param nosep path string true "nosep" -@Success 200 {object} sep.SepResponse -@Failure 400 {object} models.ErrorResponseBpjs -@Failure 500 {object} models.ErrorResponseBpjs -@Router /sep/:nosep [delete] -*/ -func (h *VClaimHandler) DeleteSepSep(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - h.logger.Info("Processing DeleteSepSep request", map[string]interface{}{ - "request_id": requestID, - }) - - nosep := c.Param("nosep") - if nosep == "" { - - h.logger.Error("Missing required parameter nosep", map[string]interface{}{ - "request_id": requestID, - }) - - c.JSON(http.StatusBadRequest, models.ErrorResponseBpjs{ - Status: "error", - Message: "Missing required parameter nosep", - RequestID: requestID, - }) - return - } - - // Call service method - var response sep.SepResponse - - endpoint := "/sep/:nosep" - - endpoint = strings.Replace(endpoint, ":nosep", nosep, 1) - - err := h.service.Delete(ctx, endpoint, &response) - - if err != nil { - - h.logger.Error("Failed to delete SepSep", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} diff --git a/internal/handlers/vclaim/peserta/peserta.go b/internal/handlers/vclaim/peserta/peserta.go new file mode 100644 index 0000000..e582782 --- /dev/null +++ b/internal/handlers/vclaim/peserta/peserta.go @@ -0,0 +1,232 @@ + +// Service: VClaim (vclaim) +// Description: BPJS VClaim service for eligibility and SEP management + +package peserta + +import ( + "context" + "strings" + "net/http" + "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" +) + +// VClaimHandler handles VClaim BPJS services +type VClaimHandler struct { + service services.VClaimService + validator *validator.Validate + logger logger.Logger + config config.BpjsConfig +} + +// VClaimHandlerConfig contains configuration for VClaimHandler +type VClaimHandlerConfig struct { + BpjsConfig config.BpjsConfig + Logger logger.Logger + Validator *validator.Validate +} + +// NewVClaimHandler creates a new VClaimHandler +func NewVClaimHandler(cfg VClaimHandlerConfig) *VClaimHandler { + return &VClaimHandler{ + service: services.NewService(cfg.BpjsConfig), + validator: cfg.Validator, + logger: cfg.Logger, + config: cfg.BpjsConfig, + } +} + + +// GetPesertaBynokartu godoc +// @Summary Get PesertaBynokartu 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 PesertaBynokartu 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 - PesertaBynokartu not found" +// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" +// @Router /peserta/:nokartu [get] +func (h *VClaimHandler) GetPesertaBynokartu(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 GetPesertaBynokartu 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) + + err := h.service.Get(ctx, endpoint, &response) + + if err != nil { + + h.logger.Error("Failed to get PesertaBynokartu", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + c.JSON(http.StatusOK, response) +} + + + + + + + + + +// GetPesertaBynik godoc +// @Summary Get PesertaBynik 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 PesertaBynik 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 - PesertaBynik not found" +// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" +// @Router /peserta/nik/:nik [get] +func (h *VClaimHandler) GetPesertaBynik(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 GetPesertaBynik 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) + + err := h.service.Get(ctx, endpoint, &response) + + if err != nil { + + h.logger.Error("Failed to get PesertaBynik", map[string]interface{}{ + "error": err.Error(), + "request_id": requestID, + }) + + c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ + Status: "error", + Message: "Internal server error", + RequestID: requestID, + }) + return + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + c.JSON(http.StatusOK, response) +} + + + + + + + + diff --git a/internal/handlers/vclaim_handler.go b/internal/handlers/vclaim/sep/sep.go similarity index 65% rename from internal/handlers/vclaim_handler.go rename to internal/handlers/vclaim/sep/sep.go index 3b2b58a..677c625 100644 --- a/internal/handlers/vclaim_handler.go +++ b/internal/handlers/vclaim/sep/sep.go @@ -2,7 +2,7 @@ // Service: VClaim (vclaim) // Description: BPJS VClaim service for eligibility and SEP management -package handlers +package sep import ( "context" @@ -12,7 +12,6 @@ import ( "api-service/internal/config" "api-service/internal/models" - "api-service/internal/models/vclaim/peserta" "api-service/internal/models/vclaim/sep" "api-service/internal/services/bpjs" "api-service/pkg/logger" @@ -48,205 +47,15 @@ func NewVClaimHandler(cfg VClaimHandlerConfig) *VClaimHandler { } -// GetPESERTABYNIK godoc -// @Summary Get PesertaBynik data -// @Description Get participant eligibility information by NIK -// @Tags vclaim,peserta,nik -// @Accept json -// @Produce json - -// @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 PesertaBynik 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 - PesertaBynik not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router /peserta/nik/:nik [get] -func (h *VClaimHandler) GetPesertaBynik(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 GetPesertaBynik 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) - - err := h.service.Get(ctx, endpoint, &response) - - if err != nil { - - h.logger.Error("Failed to get PesertaBynik", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} - - - - - - - - - -// GetPESERTABYNOKARTU godoc -// @Summary Get PesertaBynokartu data -// @Description Get participant eligibility information by card number -// @Tags vclaim,peserta,nokartu -// @Accept json -// @Produce json - -// @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 PesertaBynokartu 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 - PesertaBynokartu not found" -// @Failure 500 {object} models.ErrorResponseBpjs "Internal server error" -// @Router /peserta/:nokartu [get] -func (h *VClaimHandler) GetPesertaBynokartu(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 GetPesertaBynokartu 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) - - err := h.service.Get(ctx, endpoint, &response) - - if err != nil { - - h.logger.Error("Failed to get PesertaBynokartu", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusInternalServerError, models.ErrorResponseBpjs{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Ensure response has proper fields - response.Status = "success" - response.RequestID = requestID - c.JSON(http.StatusOK, response) -} - - - - - - - - - -// GetSEPSEP godoc +// GetSepSep godoc // @Summary Get SepSep data // @Description Manage SEP (Surat Eligibilitas Peserta) -// @Tags vclaim,sep +// @Tags Sep // @Accept json -// @Produce json - -// @Param X-Request-ID header string false "Request ID for tracking" - -// @Param nosep path string true "nosep" example("example_value") - +// @Produce json +// @Security ApiKeyAuth +// @Param X-Request-ID header string false "Request ID for tracking" +// @Param nosep path string true "nosep" example("example_value") // @Success 200 {object} sep.SepResponse "Successfully retrieved SepSep data" // @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" // @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" @@ -324,13 +133,13 @@ func (h *VClaimHandler) GetSepSep(c *gin.Context) { -// CreateSEPSEP godoc +// CreateSepSep godoc // @Summary Create new SepSep // @Description Manage SEP (Surat Eligibilitas Peserta) -// @Tags vclaim,sep +// @Tags Sep // @Accept json -// @Produce json - +// @Produce json +// @Security ApiKeyAuth // @Param X-Request-ID header string false "Request ID for tracking" // @Param request body sep.SepRequest true "SepSep data" // @Success 201 {object} sep.SepResponse "Successfully created SepSep" @@ -413,17 +222,15 @@ func (h *VClaimHandler) CreateSepSep(c *gin.Context) { -// UpdateSEPSEP godoc +// UpdateSepSep godoc // @Summary Update existing SepSep // @Description Manage SEP (Surat Eligibilitas Peserta) -// @Tags vclaim,sep +// @Tags Sep // @Accept json -// @Produce json - -// @Param X-Request-ID header string false "Request ID for tracking" - -// @Param nosep path string true "nosep" example("example_value") - +// @Produce json +// @Security ApiKeyAuth +// @Param X-Request-ID header string false "Request ID for tracking" +// @Param nosep path string true "nosep" example("example_value") // @Param request body sep.SepRequest true "SepSep data" // @Success 200 {object} sep.SepResponse "Successfully updated SepSep" // @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters or request body" @@ -528,17 +335,15 @@ func (h *VClaimHandler) UpdateSepSep(c *gin.Context) { -// DeleteSEPSEP godoc +// DeleteSepSep godoc // @Summary Delete existing SepSep // @Description Manage SEP (Surat Eligibilitas Peserta) -// @Tags vclaim,sep +// @Tags Sep // @Accept json -// @Produce json - -// @Param X-Request-ID header string false "Request ID for tracking" - -// @Param nosep path string true "nosep" example("example_value") - +// @Produce json +// @Security ApiKeyAuth +// @Param X-Request-ID header string false "Request ID for tracking" +// @Param nosep path string true "nosep" example("example_value") // @Success 200 {object} sep.SepResponse "Successfully deleted SepSep" // @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters" // @Failure 401 {object} models.ErrorResponseBpjs "Unauthorized - invalid API credentials" diff --git a/internal/routes/v1/routes.go b/internal/routes/v1/routes.go index 0761faa..0698df3 100644 --- a/internal/routes/v1/routes.go +++ b/internal/routes/v1/routes.go @@ -6,6 +6,8 @@ import ( authHandlers "api-service/internal/handlers/auth" healthcheckHandlers "api-service/internal/handlers/healthcheck" retribusiHandlers "api-service/internal/handlers/retribusi" + "api-service/internal/handlers/vclaim/peserta" + "api-service/internal/handlers/vclaim/sep" "api-service/internal/middleware" services "api-service/internal/services/auth" "api-service/pkg/logger" @@ -66,6 +68,28 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine { // ============= PUBLISHED ROUTES =============================================== + // Peserta routes + pesertaHandler := peserta.NewVClaimHandler(peserta.VClaimHandlerConfig{ + BpjsConfig: cfg.Bpjs, + Logger: *logger.Default(), + Validator: nil, + }) + pesertaGroup := v1.Group("/peserta") + pesertaGroup.GET("/peserta/:nokartu", pesertaHandler.GetPesertaBynokartu) + pesertaGroup.GET("/peserta/nik/:nik", pesertaHandler.GetPesertaBynik) + + // 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) + // // Retribusi endpoints // retribusiHandler := retribusiHandlers.NewRetribusiHandler() // retribusiGroup := v1.Group("/retribusi") @@ -100,5 +124,28 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine { protectedRetribusi.DELETE("/:id", retribusiHandler.DeleteRetribusi) // DELETE /api/v1/retribusi/:id } + // // BPJS VClaim endpoints (require authentication) + // // Peserta routes + // pesertaHandler := peserta.NewVClaimHandler(peserta.VClaimHandlerConfig{ + // BpjsConfig: cfg.Bpjs, + // Logger: *logger.Default(), + // Validator: nil, + // }) + // protectedPeserta := protected.Group("/peserta") + // protectedPeserta.GET("/peserta/:nokartu", pesertaHandler.GetPesertaBynokartu) + // protectedPeserta.GET("/peserta/nik/:nik", pesertaHandler.GetPesertaBynik) + + // // Sep routes + // sepHandler := sep.NewVClaimHandler(sep.VClaimHandlerConfig{ + // BpjsConfig: cfg.Bpjs, + // Logger: *logger.Default(), + // Validator: nil, + // }) + // protectedSep := protected.Group("/sep") + // protectedSep.GET("/sep/:nosep", sepHandler.GetSepSep) + // protectedSep.POST("/sep", sepHandler.CreateSepSep) + // protectedSep.PUT("/sep/:nosep", sepHandler.UpdateSepSep) + // protectedSep.DELETE("/sep/:nosep", sepHandler.DeleteSepSep) + return router } diff --git a/services-config-bpjs.yaml b/services-config-bpjs.yaml index 7787768..1614fad 100644 --- a/services-config-bpjs.yaml +++ b/services-config-bpjs.yaml @@ -23,7 +23,8 @@ services: response_model: "PesertaResponse" description: "Get participant eligibility information by card number" summary: "Get Participant Info by No Kartu" - tags: ["vclaim", "peserta", "nokartu"] + tags: ["Peserta"] + require_auth: true cache_enabled: true cache_ttl: 300 bynik: @@ -33,7 +34,8 @@ services: response_model: "PesertaResponse" description: "Get participant eligibility information by NIK" summary: "Get Participant Info by NIK" - tags: ["vclaim", "peserta", "nik"] + tags: ["Peserta"] + require_auth: true cache_enabled: true cache_ttl: 300 sep: @@ -47,6 +49,7 @@ services: response_model: "SepResponse" description: "Manage SEP (Surat Eligibilitas Peserta)" summary: "SEP Management" - tags: ["vclaim", "sep"] + tags: ["Sep"] + require_auth: true cache_enabled: true cache_ttl: 180 diff --git a/tools/bpjs/generate-handler.go b/tools/bpjs/generate-handler.go index 7367eda..f87c00e 100644 --- a/tools/bpjs/generate-handler.go +++ b/tools/bpjs/generate-handler.go @@ -138,7 +138,7 @@ const handlerTemplate = ` // Service: {{.ServiceName}} ({{.Category}}) // Description: {{.Description}} -package handlers +package {{.Package}} import ( "context" @@ -148,8 +148,7 @@ import ( "{{.ModuleName}}/internal/config" "{{.ModuleName}}/internal/models" - "{{.ModuleName}}/internal/models/vclaim/peserta" - "{{.ModuleName}}/internal/models/vclaim/sep" + "{{.ModuleName}}/internal/models/vclaim/{{.Package}}" "{{.ModuleName}}/internal/services/bpjs" "{{.ModuleName}}/pkg/logger" @@ -184,19 +183,15 @@ func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig) *{{.ServiceNa } {{range .Endpoints}} {{if .HasGet}} -// Get{{.NameUpper}} godoc +// Get{{.Name}} godoc // @Summary Get {{.Name}} data // @Description {{.Description}} // @Tags {{join .Tags ","}} // @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}} +// @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" @@ -276,15 +271,13 @@ func (h *{{$.ServiceName}}Handler) Get{{.Name}}(c *gin.Context) { {{end}} {{if .HasPost}} -// Create{{.NameUpper}} godoc +// Create{{.Name}} godoc // @Summary Create new {{.Name}} // @Description {{.Description}} // @Tags {{join .Tags ","}} // @Accept json -// @Produce json -{{if .RequireAuth}} -// @Security ApiKeyAuth -{{end}} +// @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}}" @@ -367,19 +360,15 @@ func (h *{{$.ServiceName}}Handler) Create{{.Name}}(c *gin.Context) { {{end}} {{if .HasPut}} -// Update{{.NameUpper}} godoc +// Update{{.Name}} godoc // @Summary Update existing {{.Name}} // @Description {{.Description}} // @Tags {{join .Tags ","}} // @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}} +// @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}}.{{.Model}} true "{{.Name}} data" // @Success 200 {object} {{.ModelPackage}}.{{.ResponseModel}} "Successfully updated {{.Name}}" // @Failure 400 {object} models.ErrorResponseBpjs "Bad request - invalid parameters or request body" @@ -486,19 +475,15 @@ func (h *{{$.ServiceName}}Handler) Update{{.Name}}(c *gin.Context) { {{end}} {{if .HasDelete}} -// Delete{{.NameUpper}} godoc +// Delete{{.Name}} godoc // @Summary Delete existing {{.Name}} // @Description {{.Description}} // @Tags {{join .Tags ","}} // @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}} +// @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" @@ -573,6 +558,46 @@ func (h *{{$.ServiceName}}Handler) Delete{{.Name}}(c *gin.Context) { {{end}} ` +// Template for generating routes +const routesTemplate = ` +package v1 + +import ( + "{{.ModuleName}}/internal/config" + "{{.ModuleName}}/internal/middleware" + "{{.ModuleName}}/internal/handlers/{{.Category}}/{{.Package}}" + "github.com/gin-gonic/gin" +) + +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, + }) + + group := router.Group("/{{.ServiceLower}}") + + {{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}} +} +` + func main() { if len(os.Args) < 2 { printUsage() @@ -614,15 +639,24 @@ func main() { errors++ continue } - fmt.Printf("āœ… Generated handler: %s_handler.go\n", strings.ToLower(serviceName)) + 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\n", generated) + fmt.Printf(" āœ… Successfully generated: %d handlers and routes\n", generated) if errors > 0 { - fmt.Printf(" āŒ Failed: %d handlers\n", errors) + fmt.Printf(" āŒ Failed: %d handlers/routes\n", errors) } if generated > 0 { @@ -675,65 +709,6 @@ func loadConfig(filename string) (*ServiceConfig, error) { } func generateHandler(serviceName string, service Service, globalConfig GlobalConfig) error { - // Prepare template data - templateData := TemplateData{ - ServiceName: service.Name, - ServiceLower: strings.ToLower(service.Name), - ServiceUpper: strings.ToUpper(service.Name), - Category: service.Category, - Package: service.Package, - 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, // Now []string - Middleware: service.Middleware, // Now []string - GlobalConfig: globalConfig, - } - - // Check for advanced features - for _, subEndpoints := range service.Endpoints { - for _, endpoint := range subEndpoints { - if endpoint.RequireAuth { - templateData.HasAuth = true - } - if endpoint.CacheEnabled { - templateData.HasCache = true - } - } - } - - // Process endpoints - for endpointName, subEndpoints := range service.Endpoints { - for subEndpointName, endpoint := range subEndpoints { - // Compose full endpoint name with sub-endpoint name - fullEndpointName := endpointName - if subEndpointName != "" { - fullEndpointName = fullEndpointName + strings.Title(subEndpointName) - } - endpointData := processEndpoint(fullEndpointName, endpoint, endpointName) - templateData.Endpoints = append(templateData.Endpoints, endpointData) - } - } - - // Create output directory - outputDir := globalConfig.OutputDir - if outputDir == "" { - outputDir = "internal/handlers" - } - if err := os.MkdirAll(outputDir, 0755); err != nil { - return fmt.Errorf("failed to create output directory: %w", err) - } - - // Generate handler file - filename := filepath.Join(outputDir, fmt.Sprintf("%s_handler.go", strings.ToLower(serviceName))) - // Create template with custom functions tmpl := template.New("handler").Funcs(template.FuncMap{ "contains": strings.Contains, @@ -747,17 +722,207 @@ func generateHandler(serviceName string, service Service, globalConfig GlobalCon return fmt.Errorf("failed to parse template: %w", err) } - file, err := os.Create(filename) - if err != nil { - return fmt.Errorf("failed to create file: %w", err) - } - defer file.Close() - - err = tmpl.Execute(file, templateData) - if err != nil { - return fmt.Errorf("failed to execute template: %w", err) + 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 generateRoutes(serviceName string, service Service, globalConfig GlobalConfig) error { + // Read the main routes.go file + routesFilePath := "internal/routes/v1/routes.go" + routesContent, err := ioutil.ReadFile(routesFilePath) + if err != nil { + return fmt.Errorf("failed to read routes file: %w", err) + } + + routesContentStr := string(routesContent) + + // Check if routes are already registered + if strings.Contains(routesContentStr, fmt.Sprintf("Register%sRoutes", service.Name)) { + fmt.Printf("āš ļø Routes for %s already registered in main routes file\n", service.Name) + return nil + } + + // Prepare template data for all groups + var allRoutes []string + + // Loop over each module group in service.Endpoints + for groupName, subEndpoints := range service.Endpoints { + // Prepare template data for this group + templateData := TemplateData{ + ServiceName: service.Name, + ServiceLower: strings.ToLower(service.Name), + ServiceUpper: strings.ToUpper(service.Name), + Category: service.Category, + Package: groupName, // package name is group name + Description: service.Description, + BaseURL: service.BaseURL, + Timeout: getOrDefault(service.Timeout, 30), + RetryCount: getOrDefault(service.RetryCount, 3), + Timestamp: time.Now().Format("2006-01-02 15:04:05"), + ModuleName: globalConfig.ModuleName, + HasValidator: true, + HasLogger: globalConfig.EnableLogging, + HasMetrics: globalConfig.EnableMetrics, + HasSwagger: globalConfig.EnableSwagger, + Dependencies: service.Dependencies, + Middleware: service.Middleware, + GlobalConfig: globalConfig, + } + + // Process endpoints for this group + for subEndpointName, endpoint := range subEndpoints { + fullEndpointName := groupName + if subEndpointName != "" { + fullEndpointName = fullEndpointName + strings.Title(subEndpointName) + } + endpointData := processEndpoint(fullEndpointName, endpoint, groupName) + templateData.Endpoints = append(templateData.Endpoints, endpointData) + } + + // Generate routes code for this group + var routesCode strings.Builder + + routesCode.WriteString(fmt.Sprintf("\n\t// %s routes\n", strings.Title(groupName))) + routesCode.WriteString(fmt.Sprintf("\t%sHandler := %s.New%sHandler(%s.%sHandlerConfig{\n", groupName, groupName, service.Name, groupName, service.Name)) + routesCode.WriteString("\t\tBpjsConfig: cfg.Bpjs,\n") + routesCode.WriteString("\t\tLogger: *logger.Default(),\n") + routesCode.WriteString("\t\tValidator: nil,\n") + routesCode.WriteString("\t})\n") + routesCode.WriteString(fmt.Sprintf("\t%sGroup := v1.Group(\"/%s\")\n", groupName, groupName)) + + for _, endpoint := range templateData.Endpoints { + if endpoint.HasGet { + routesCode.WriteString(fmt.Sprintf("\t%sGroup.GET(\"%s\", %sHandler.Get%s)\n", groupName, endpoint.GetPath, groupName, endpoint.Name)) + } + if endpoint.HasPost { + routesCode.WriteString(fmt.Sprintf("\t%sGroup.POST(\"%s\", %sHandler.Create%s)\n", groupName, endpoint.PostPath, groupName, endpoint.Name)) + } + if endpoint.HasPut { + routesCode.WriteString(fmt.Sprintf("\t%sGroup.PUT(\"%s\", %sHandler.Update%s)\n", groupName, endpoint.PutPath, groupName, endpoint.Name)) + } + if endpoint.HasDelete { + routesCode.WriteString(fmt.Sprintf("\t%sGroup.DELETE(\"%s\", %sHandler.Delete%s)\n", groupName, endpoint.DeletePath, groupName, endpoint.Name)) + } + if endpoint.HasPatch { + routesCode.WriteString(fmt.Sprintf("\t%sGroup.PATCH(\"%s\", %sHandler.Patch%s)\n", groupName, endpoint.PatchPath, groupName, endpoint.Name)) + } + } + + allRoutes = append(allRoutes, routesCode.String()) + } + + // Find the PUBLISHED ROUTES section and insert the routes + publishedRoutesMarker := "// ============= PUBLISHED ROUTES ===============================================" + if !strings.Contains(routesContentStr, publishedRoutesMarker) { + return fmt.Errorf("PUBLISHED ROUTES marker not found in routes.go") + } + + // Insert the routes after the marker + insertionPoint := strings.Index(routesContentStr, publishedRoutesMarker) + len(publishedRoutesMarker) + newRoutesContent := routesContentStr[:insertionPoint] + "\n" + strings.Join(allRoutes, "\n") + "\n" + routesContentStr[insertionPoint:] + + // Write back the modified routes file + err = ioutil.WriteFile(routesFilePath, []byte(newRoutesContent), 0644) + if err != nil { + return fmt.Errorf("failed to write updated routes file: %w", err) + } + + fmt.Printf("āœ… Updated main routes file with %s routes\n", service.Name) return nil } diff --git a/tools/bpjs/generete b/tools/bpjs/generete index e69de29..7487e72 100644 --- a/tools/bpjs/generete +++ b/tools/bpjs/generete @@ -0,0 +1,1029 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "text/template" + "time" + + "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() +} + +// ServiceConfig represents the main configuration structure +type ServiceConfig struct { + Services map[string]Service `yaml:"services"` + Global GlobalConfig `yaml:"global,omitempty"` +} + +// 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 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]SubEndpoints `yaml:"endpoints"` + Middleware []string `yaml:"middleware,omitempty"` // FIXED: Changed to []string + Dependencies []string `yaml:"dependencies,omitempty"` // FIXED: Changed to []string +} + +// 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"` +} + +// SubEndpoints represents nested endpoint configuration for tree structure +type SubEndpoints map[string]Endpoint + +// 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 +} + +// 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 + 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.) +} + +// Updated template for merged handler structure +const handlerTemplate = ` +// Service: {{.ServiceName}} ({{.Category}}) +// Description: {{.Description}} + +package {{.Package}} + +import ( + "context" + "strings" + "net/http" + "time" + + "{{.ModuleName}}/internal/config" + "{{.ModuleName}}/internal/models" + "{{.ModuleName}}/internal/models/vclaim/{{.Package}}" + "{{.ModuleName}}/internal/services/bpjs" + "{{.ModuleName}}/pkg/logger" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + "github.com/google/uuid" +) + +// {{.ServiceName}}Handler handles {{.ServiceName}} BPJS services +type {{.ServiceName}}Handler struct { + service services.{{.ServiceName}}Service + validator *validator.Validate + logger logger.Logger + config config.BpjsConfig +} + +// {{.ServiceName}}HandlerConfig contains configuration for {{.ServiceName}}Handler +type {{.ServiceName}}HandlerConfig struct { + BpjsConfig config.BpjsConfig + Logger logger.Logger + Validator *validator.Validate +} + +// New{{.ServiceName}}Handler creates a new {{.ServiceName}}Handler +func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig) *{{.ServiceName}}Handler { + return &{{.ServiceName}}Handler{ + service: services.NewService(cfg.BpjsConfig), + validator: cfg.Validator, + logger: cfg.Logger, + config: cfg.BpjsConfig, + } +} +{{range .Endpoints}} +{{if .HasGet}} +// Get{{.Name}} godoc +// @Summary Get {{.Name}} data +// @Description {{.Description}} +// @Tags {{join .Tags ","}} +// @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}} + err := h.service.Get(ctx, endpoint, &response) + {{else}} + err := h.service.Get(ctx, "{{.GetPath}}", &response) + {{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 + } + + // 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}} +// @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() + + 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}} + + 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 + } + + // 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 + } + + // Call service method + var response {{.ModelPackage}}.{{.ResponseModel}} + err := h.service.Post(ctx, "{{.PostPath}}", &req, &response) + 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 + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + 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}} +// @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}}.{{.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() + + 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}} + + {{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 + } + + // 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 + } + + // Call service method + var response {{.ModelPackage}}.{{.ResponseModel}} + {{if .PathParams}} + endpoint := "{{.PutPath}}" + {{range .PathParams}} + endpoint = strings.Replace(endpoint, ":{{.}}", {{.}}, 1) + {{end}} + err := h.service.Put(ctx, endpoint, &req, &response) + {{else}} + err := h.service.Put(ctx, "{{.PutPath}}", &req, &response) + {{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 + } + + // Ensure response has proper fields + response.Status = "success" + response.RequestID = requestID + c.JSON(http.StatusOK, response) +} +{{end}} + +{{if .HasDelete}} +// Delete{{.Name}} godoc +// @Summary Delete existing {{.Name}} +// @Description {{.Description}} +// @Tags {{join .Tags ","}} +// @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() + + 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, + }) + {{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}} + err := h.service.Delete(ctx, endpoint, &response) + {{else}} + err := h.service.Delete(ctx, "{{.DeletePath}}", &response) + {{end}} + 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 + } + + // 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 + +import ( + "{{.ModuleName}}/internal/config" + "{{.ModuleName}}/internal/middleware" + "{{.ModuleName}}/internal/handlers/{{.Category}}/{{.Package}}" + "github.com/gin-gonic/gin" +) + +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, + }) + + group := router.Group("/{{.ServiceLower}}") + + {{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}} +} +` + +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] + } + + // Load configuration + 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...") + fmt.Printf("šŸ“ Config file: %s\n", configFile) + if targetService != "" { + fmt.Printf("šŸŽÆ Target service: %s\n", targetService) + } + + 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) + 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) + if errors > 0 { + fmt.Printf(" āŒ Failed: %d handlers/routes\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") +} + +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) + } + + var config ServiceConfig + err = yaml.Unmarshal(data, &config) + if err != nil { + return nil, fmt.Errorf("failed to parse YAML config: %w", err) + } + + // Set default values + if config.Global.ModuleName == "" { + config.Global.ModuleName = "api-service" + } + if config.Global.OutputDir == "" { + config.Global.OutputDir = "internal/handlers" + } + + 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) + } + + 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 generateRoutes(serviceName string, service Service, globalConfig GlobalConfig) error { + // Read the main routes.go file + routesFilePath := "internal/routes/v1/routes.go" + routesContent, err := ioutil.ReadFile(routesFilePath) + if err != nil { + return fmt.Errorf("failed to read routes file: %w", err) + } + + routesContentStr := string(routesContent) + + // Check if routes are already registered + if strings.Contains(routesContentStr, fmt.Sprintf("Register%sRoutes", service.Name)) { + fmt.Printf("āš ļø Routes for %s already registered in main routes file\n", service.Name) + return nil + } + + // Prepare template data for all groups + var allRoutes []string + + // Loop over each module group in service.Endpoints + for groupName, subEndpoints := range service.Endpoints { + // Prepare template data for this group + templateData := TemplateData{ + ServiceName: service.Name, + ServiceLower: strings.ToLower(service.Name), + ServiceUpper: strings.ToUpper(service.Name), + Category: service.Category, + Package: groupName, // package name is group name + Description: service.Description, + BaseURL: service.BaseURL, + Timeout: getOrDefault(service.Timeout, 30), + RetryCount: getOrDefault(service.RetryCount, 3), + Timestamp: time.Now().Format("2006-01-02 15:04:05"), + ModuleName: globalConfig.ModuleName, + HasValidator: true, + HasLogger: globalConfig.EnableLogging, + HasMetrics: globalConfig.EnableMetrics, + HasSwagger: globalConfig.EnableSwagger, + Dependencies: service.Dependencies, + Middleware: service.Middleware, + GlobalConfig: globalConfig, + } + + // Process endpoints for this group + for subEndpointName, endpoint := range subEndpoints { + fullEndpointName := groupName + if subEndpointName != "" { + fullEndpointName = fullEndpointName + strings.Title(subEndpointName) + } + endpointData := processEndpoint(fullEndpointName, endpoint, groupName) + templateData.Endpoints = append(templateData.Endpoints, endpointData) + } + + // Generate routes code for this group + var routesCode strings.Builder + + routesCode.WriteString(fmt.Sprintf("\n\t// %s routes\n", strings.Title(groupName))) + routesCode.WriteString(fmt.Sprintf("\t%sHandler := %s.New%sHandler(%s.%sHandlerConfig{\n", groupName, groupName, service.Name, groupName, service.Name)) + routesCode.WriteString("\t\tBpjsConfig: cfg.Bpjs,\n") + routesCode.WriteString("\t\tLogger: *logger.Default(),\n") + routesCode.WriteString("\t\tValidator: nil,\n") + routesCode.WriteString("\t})\n") + routesCode.WriteString(fmt.Sprintf("\t%sGroup := v1.Group(\"/%s\")\n", groupName, groupName)) + + for _, endpoint := range templateData.Endpoints { + if endpoint.HasGet { + routesCode.WriteString(fmt.Sprintf("\t%sGroup.GET(\"%s\", %sHandler.Get%s)\n", groupName, endpoint.GetPath, groupName, endpoint.Name)) + } + if endpoint.HasPost { + routesCode.WriteString(fmt.Sprintf("\t%sGroup.POST(\"%s\", %sHandler.Create%s)\n", groupName, endpoint.PostPath, groupName, endpoint.Name)) + } + if endpoint.HasPut { + routesCode.WriteString(fmt.Sprintf("\t%sGroup.PUT(\"%s\", %sHandler.Update%s)\n", groupName, endpoint.PutPath, groupName, endpoint.Name)) + } + if endpoint.HasDelete { + routesCode.WriteString(fmt.Sprintf("\t%sGroup.DELETE(\"%s\", %sHandler.Delete%s)\n", groupName, endpoint.DeletePath, groupName, endpoint.Name)) + } + if endpoint.HasPatch { + routesCode.WriteString(fmt.Sprintf("\t%sGroup.PATCH(\"%s\", %sHandler.Patch%s)\n", groupName, endpoint.PatchPath, groupName, endpoint.Name)) + } + } + + allRoutes = append(allRoutes, routesCode.String()) + } + + // Find the PUBLISHED ROUTES section and insert the routes + publishedRoutesMarker := "// ============= PUBLISHED ROUTES ===============================================" + if !strings.Contains(routesContentStr, publishedRoutesMarker) { + return fmt.Errorf("PUBLISHED ROUTES marker not found in routes.go") + } + + // Insert the routes after the marker + insertionPoint := strings.Index(routesContentStr, publishedRoutesMarker) + len(publishedRoutesMarker) + newRoutesContent := routesContentStr[:insertionPoint] + "\n" + strings.Join(allRoutes, "\n") + "\n" + routesContentStr[insertionPoint:] + + // Write back the modified routes file + err = ioutil.WriteFile(routesFilePath, []byte(newRoutesContent), 0644) + if err != nil { + return fmt.Errorf("failed to write updated routes file: %w", err) + } + + fmt.Printf("āœ… Updated main routes file with %s routes\n", service.Name) + return nil +} + +func processEndpoint(name string, endpoint Endpoint, endpointGroup string) EndpointData { + data := EndpointData{ + Name: strings.Title(name), + NameLower: strings.ToLower(name), + NameUpper: strings.ToUpper(name), + NameCamel: toCamelCase(name), + Methods: endpoint.Methods, + GetPath: endpoint.GetPath, + PostPath: endpoint.PostPath, + PutPath: endpoint.PutPath, + DeletePath: endpoint.DeletePath, + PatchPath: endpoint.PatchPath, + Model: endpoint.Model, + ResponseModel: endpoint.ResponseModel, + Description: endpoint.Description, + Summary: endpoint.Summary, + Tags: endpoint.Tags, + RequireAuth: endpoint.RequireAuth, + RateLimit: endpoint.RateLimit, + CacheEnabled: endpoint.CacheEnabled, + CacheTTL: getOrDefault(endpoint.CacheTTL, 300), + CustomHeaders: endpoint.CustomHeaders, + ModelPackage: endpointGroup, // Set the model package based on endpoint group + } + + // Set method flags and extract path parameters + for _, method := range endpoint.Methods { + switch strings.ToUpper(method) { + case "GET": + data.HasGet = true + data.PathParams = extractPathParams(endpoint.GetPath) + case "POST": + data.HasPost = true + case "PUT": + data.HasPut = true + data.PathParams = extractPathParams(endpoint.PutPath) + case "DELETE": + data.HasDelete = true + data.PathParams = extractPathParams(endpoint.DeletePath) + case "PATCH": + data.HasPatch = true + data.PathParams = extractPathParams(endpoint.PatchPath) + } + } + + return data +} + +func extractPathParams(path string) []string { + if path == "" { + return nil + } + + var params []string + parts := strings.Split(path, "/") + for _, part := range parts { + if strings.HasPrefix(part, ":") { + params = append(params, strings.TrimPrefix(part, ":")) + } + } + + return params +} + +func toCamelCase(str string) string { + words := strings.FieldsFunc(str, func(c rune) bool { + return c == '_' || c == '-' || c == ' ' + }) + + if len(words) == 0 { + return str + } + + result := strings.ToLower(words[0]) + for _, word := range words[1:] { + result += strings.Title(strings.ToLower(word)) + } + + return result +} + +func getOrDefault(value, defaultValue int) int { + if value == 0 { + return defaultValue + } + return value +} diff --git a/tools/bpjsgenerate-handler.backup b/tools/bpjsgenerate-handler.backup deleted file mode 100644 index 10761ef..0000000 --- a/tools/bpjsgenerate-handler.backup +++ /dev/null @@ -1,1132 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "time" -) - -// BpjsHandlerData contains template data for BPJS handler generation -type BpjsHandlerData struct { - Name string - NameLower string - NameUpper string - Category string - CategoryPath string - ModuleName string - HasGet bool - HasPost bool - HasPut bool - HasDelete bool - GetEndpoint string - PostEndpoint string - PutEndpoint string - DeleteEndpoint string - Timestamp string -} - -func main() { - if len(os.Args) < 2 { - fmt.Println("Usage: go run generate-bpjs-handler.go [category/]entity [methods]") - fmt.Println("Examples:") - fmt.Println(" go run generate-bpjs-handler.go vclaim/sep get post put delete") - fmt.Println(" go run generate-bpjs-handler.go eclaim/klaim get post") - fmt.Println(" go run generate-bpjs-handler.go peserta get") - os.Exit(1) - } - - // Parse entity path - entityPath := os.Args[1] - methods := []string{} - if len(os.Args) > 2 { - methods = os.Args[2:] - } else { - methods = []string{"get", "post", "put", "delete"} - } - - // Parse category and entity - var category, entityName string - if strings.Contains(entityPath, "/") { - parts := strings.Split(entityPath, "/") - if len(parts) != 2 { - fmt.Println("āŒ Error: Invalid path format. Use 'category/entity' or just 'entity'") - os.Exit(1) - } - category = parts[0] - entityName = parts[1] - } else { - category = "" - entityName = entityPath - } - - // Format names - entityName = strings.Title(entityName) - entityLower := strings.ToLower(entityName) - entityUpper := strings.ToUpper(entityName) - - data := BpjsHandlerData{ - Name: entityName, - NameLower: entityLower, - NameUpper: entityUpper, - Category: category, - CategoryPath: category, - ModuleName: "api-service", - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - } - - // Set methods and endpoints - for _, m := range methods { - switch strings.ToLower(m) { - case "get": - data.HasGet = true - data.GetEndpoint = fmt.Sprintf("%s/{id}", entityUpper) - case "post": - data.HasPost = true - data.PostEndpoint = fmt.Sprintf("%s/2.0/insert", entityUpper) - case "put": - data.HasPut = true - data.PutEndpoint = fmt.Sprintf("%s/2.0/update", entityUpper) - case "delete": - data.HasDelete = true - data.DeleteEndpoint = fmt.Sprintf("%s/2.0/delete", entityUpper) - } - } - - // Create directories - var handlerDir, modelDir string - if category != "" { - handlerDir = filepath.Join("internal", "handlers", category) - modelDir = filepath.Join("internal", "models", category) - } else { - handlerDir = filepath.Join("internal", "handlers") - modelDir = filepath.Join("internal", "models") - } - - for _, d := range []string{handlerDir, modelDir} { - if err := os.MkdirAll(d, 0755); err != nil { - panic(err) - } - } - - // Generate files - generateOptimizedBpjsHandlerFile(data, handlerDir) - generateOptimizedBpjsModelFile(data, modelDir) - updateOptimizedBpjsRoutesFile(data) - - fmt.Printf("āœ… Successfully generated optimized BPJS handler: %s\n", entityName) - if category != "" { - fmt.Printf("šŸ“ Category: %s\n", category) - } - fmt.Printf("šŸ“ Handler: %s\n", filepath.Join(handlerDir, entityLower+".go")) - fmt.Printf("šŸ“ Model: %s\n", filepath.Join(modelDir, entityLower+".go")) -} - -// ================= OPTIMIZED HANDLER GENERATION ===================== - -func generateOptimizedBpjsHandlerFile(data BpjsHandlerData, handlerDir string) { - var modelsImportPath string - if data.Category != "" { - modelsImportPath = data.ModuleName + "/internal/models/" + data.Category - } else { - modelsImportPath = data.ModuleName + "/internal/models" - } - - handlerContent := `package handlers - -import ( - "context" - "fmt" - "net/http" - "time" - - "` + data.ModuleName + `/internal/config" - "` + modelsImportPath + `" - services "` + data.ModuleName + `/internal/services/bpjs" - "` + data.ModuleName + `/pkg/logger" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" - "github.com/go-playground/validator/v10" -) - -// ` + data.Name + `Handler handles ` + data.NameLower + ` BPJS services with optimized error handling and logging -type ` + data.Name + `Handler struct { - service services.VClaimService - validator *validator.Validate - logger logger.Logger - config *config.BpjsConfig -} - -// HandlerConfig contains configuration for ` + data.Name + `Handler -type ` + data.Name + `HandlerConfig struct { - BpjsConfig *config.BpjsConfig - Logger logger.Logger - Validator *validator.Validate -} - -// New` + data.Name + `Handler creates a new optimized ` + data.Name + `Handler -func New` + data.Name + `Handler(cfg *` + data.Name + `HandlerConfig) *` + data.Name + `Handler { - return &` + data.Name + `Handler{ - service: services.NewService(*cfg.BpjsConfig), - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.BpjsConfig, - } -}` - - // Add optimized methods based on flags - if data.HasPost { - handlerContent += generateOptimizedBpjsCreateMethod(data) - } - - if data.HasPut { - handlerContent += generateOptimizedBpjsUpdateMethod(data) - } - - if data.HasDelete { - handlerContent += generateOptimizedBpjsDeleteMethod(data) - } - - if data.HasGet { - handlerContent += generateOptimizedBpjsGetMethod(data) - } - - // Add helper methods - handlerContent += generateHelperMethods(data) - - writeFile(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent) -} - -func generateOptimizedBpjsCreateMethod(data BpjsHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - tagName = strings.Title(data.Category) + "-" + strings.Title(data.NameLower) - } else { - routePath = data.NameLower - tagName = strings.Title(data.NameLower) - } - - return ` - -// Create` + data.Name + ` creates a new ` + data.Name + ` with comprehensive error handling and validation -// @Summary Create a new ` + data.NameUpper + ` -// @Description Create a new ` + data.Name + ` in BPJS system with enhanced validation and logging -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param request body models.` + data.Name + `PostRequest true "` + data.Name + ` creation request" -// @Success 200 {object} models.` + data.Name + `Response "` + data.Name + ` created successfully" -// @Failure 400 {object} models.` + data.Name + `Response "Bad request - validation error" -// @Failure 422 {object} models.` + data.Name + `Response "Unprocessable entity - business logic error" -// @Failure 500 {object} models.` + data.Name + `Response "Internal server error" -// @Router /api/v1/` + routePath + ` [post] -func (h *` + data.Name + `Handler) Create` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - - h.logger.Info("Creating ` + data.Name + `", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - }) - - var req models.` + data.Name + `PostRequest - req.RequestID = requestID - req.Timestamp = startTime - - // Bind and validate JSON - if err := c.ShouldBindJSON(&req); err != nil { - h.logger.Error("Failed to bind JSON", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - h.sendErrorResponse(c, http.StatusBadRequest, "INVALID_REQUEST_FORMAT", - "Format request tidak valid", err.Error(), requestID) - return - } - - // Custom validation - if err := req.Validate(); err != nil { - h.logger.Error("Custom validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", - "Validasi gagal", err.Error(), requestID) - return - } - - // Struct validation - if err := h.validator.Struct(&req); err != nil { - h.logger.Error("Struct validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", - "Validasi struktur gagal", h.formatValidationError(err), requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - var rawResponse models.BpjsRawResponse - if err := h.service.Post(ctx, "` + data.PostEndpoint + `", req, &rawResponse); err != nil { - h.logger.Error("Failed to call BPJS service", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "endpoint": "` + data.PostEndpoint + `", - }) - - statusCode, errorCode := h.categorizeError(err) - h.sendErrorResponse(c, statusCode, errorCode, - "Gagal membuat ` + data.Name + `", err.Error(), requestID) - return - } - - // Check BPJS response - if rawResponse.MetaData.Code != "200" { - h.logger.Warn("BPJS returned error", map[string]interface{}{ - "bpjs_code": rawResponse.MetaData.Code, - "bpjs_message": rawResponse.MetaData.Message, - "request_id": requestID, - }) - - statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) - h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, - rawResponse.MetaData.Message, "", requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("` + data.Name + ` created successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - }) - - h.sendSuccessResponse(c, "` + data.Name + ` berhasil dibuat", rawResponse.Response, requestID) -}` -} - -func generateOptimizedBpjsUpdateMethod(data BpjsHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - tagName = strings.Title(data.Category) + "-" + strings.Title(data.NameLower) - } else { - routePath = data.NameLower - tagName = strings.Title(data.NameLower) - } - - return ` - -// Update` + data.Name + ` updates an existing ` + data.Name + ` with comprehensive validation -// @Summary Update an existing ` + data.NameUpper + ` -// @Description Update an existing ` + data.Name + ` in BPJS system with enhanced validation and logging -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param request body models.` + data.Name + `PutRequest true "` + data.Name + ` update request" -// @Success 200 {object} models.` + data.Name + `Response "` + data.Name + ` updated successfully" -// @Failure 400 {object} models.` + data.Name + `Response "Bad request - validation error" -// @Failure 422 {object} models.` + data.Name + `Response "Unprocessable entity - business logic error" -// @Failure 500 {object} models.` + data.Name + `Response "Internal server error" -// @Router /api/v1/` + routePath + ` [put] -func (h *` + data.Name + `Handler) Update` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - - h.logger.Info("Updating ` + data.Name + `", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - }) - - var req models.` + data.Name + `PutRequest - req.RequestID = requestID - req.Timestamp = startTime - - if err := c.ShouldBindJSON(&req); err != nil { - h.logger.Error("Failed to bind JSON for update", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - h.sendErrorResponse(c, http.StatusBadRequest, "INVALID_REQUEST_FORMAT", - "Format request tidak valid", err.Error(), requestID) - return - } - - if err := req.Validate(); err != nil { - h.logger.Error("Update validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", - "Validasi gagal", err.Error(), requestID) - return - } - - if err := h.validator.Struct(&req); err != nil { - h.logger.Error("Struct validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", - "Validasi struktur gagal", h.formatValidationError(err), requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - var rawResponse models.BpjsRawResponse - if err := h.service.Put(ctx, "` + data.PutEndpoint + `", req, &rawResponse); err != nil { - h.logger.Error("Failed to update ` + data.Name + `", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - statusCode, errorCode := h.categorizeError(err) - h.sendErrorResponse(c, statusCode, errorCode, - "Gagal memperbarui ` + data.Name + `", err.Error(), requestID) - return - } - - if rawResponse.MetaData.Code != "200" { - h.logger.Warn("BPJS update returned error", map[string]interface{}{ - "bpjs_code": rawResponse.MetaData.Code, - "bpjs_message": rawResponse.MetaData.Message, - "request_id": requestID, - }) - - statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) - h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, - rawResponse.MetaData.Message, "", requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("` + data.Name + ` updated successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - }) - - h.sendSuccessResponse(c, "` + data.Name + ` berhasil diperbarui", rawResponse.Response, requestID) -}` -} - -func generateOptimizedBpjsDeleteMethod(data BpjsHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - tagName = strings.Title(data.Category) + "-" + strings.Title(data.NameLower) - } else { - routePath = data.NameLower - tagName = strings.Title(data.NameLower) - } - - return ` - -// Delete` + data.Name + ` deletes an existing ` + data.Name + ` with comprehensive validation -// @Summary Delete an existing ` + data.NameUpper + ` -// @Description Delete a ` + data.Name + ` by ID with enhanced validation and logging -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param id path string true "` + data.Name + ` ID" -// @Param user query string true "User identifier" -// @Success 200 {object} models.` + data.Name + `Response "` + data.Name + ` deleted successfully" -// @Failure 400 {object} models.` + data.Name + `Response "Bad request - missing parameters" -// @Failure 422 {object} models.` + data.Name + `Response "Unprocessable entity - business logic error" -// @Failure 500 {object} models.` + data.Name + `Response "Internal server error" -// @Router /api/v1/` + routePath + `/{id} [delete] -func (h *` + data.Name + `Handler) Delete` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - id := c.Param("id") - user := c.Query("user") - - h.logger.Info("Deleting ` + data.Name + `", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "id": id, - "user": user, - }) - - // Validate parameters - if id == "" { - h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER", - "Parameter ID wajib diisi", "", requestID) - return - } - - if user == "" { - h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER", - "Parameter user wajib diisi", "", requestID) - return - } - - req := models.` + data.Name + `DeleteRequest{ - BaseRequest: models.BaseRequest{ - RequestID: requestID, - Timestamp: startTime, - }, - T` + data.Name + `: models.` + data.Name + `DeleteData{ - ID: id, - User: user, - }, - } - - if err := req.Validate(); err != nil { - h.logger.Error("Delete validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - h.sendErrorResponse(c, http.StatusBadRequest, "VALIDATION_ERROR", - "Validasi gagal", err.Error(), requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - var rawResponse models.BpjsRawResponse - if err := h.service.Delete(ctx, "` + data.DeleteEndpoint + `", req); err != nil { - h.logger.Error("Failed to delete ` + data.Name + `", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "id": id, - }) - - statusCode, errorCode := h.categorizeError(err) - h.sendErrorResponse(c, statusCode, errorCode, - "Gagal menghapus ` + data.Name + `", err.Error(), requestID) - return - } - - if rawResponse.MetaData.Code != "200" { - h.logger.Warn("BPJS delete returned error", map[string]interface{}{ - "bpjs_code": rawResponse.MetaData.Code, - "bpjs_message": rawResponse.MetaData.Message, - "request_id": requestID, - "id": id, - }) - - statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) - h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, - rawResponse.MetaData.Message, "", requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("` + data.Name + ` deleted successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "id": id, - }) - - h.sendSuccessResponse(c, "` + data.Name + ` berhasil dihapus", rawResponse.Response, requestID) -}` -} - -func generateOptimizedBpjsGetMethod(data BpjsHandlerData) string { - var routePath, tagName string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - tagName = strings.Title(data.Category) + "-" + strings.Title(data.NameLower) - } else { - routePath = data.NameLower - tagName = strings.Title(data.NameLower) - } - - return ` - -// Get` + data.Name + ` retrieves ` + data.Name + ` details with comprehensive error handling -// @Summary Get an existing ` + data.NameUpper + ` -// @Description Retrieve a ` + data.Name + ` by ID with enhanced validation and logging -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param id path string true "` + data.Name + ` ID" -// @Success 200 {object} models.` + data.Name + `Response "Data ` + data.Name + ` retrieved successfully" -// @Failure 400 {object} models.` + data.Name + `Response "Bad request - invalid ID" -// @Failure 404 {object} models.` + data.Name + `Response "` + data.Name + ` not found" -// @Failure 500 {object} models.` + data.Name + `Response "Internal server error" -// @Router /api/v1/` + routePath + `/{id} [get] -func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) { - requestID := uuid.New().String() - startTime := time.Now() - id := c.Param("id") - - h.logger.Info("Getting ` + data.Name + `", map[string]interface{}{ - "request_id": requestID, - "timestamp": startTime, - "id": id, - }) - - if id == "" { - h.sendErrorResponse(c, http.StatusBadRequest, "MISSING_PARAMETER", - "Parameter ID wajib diisi", "", requestID) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - endpoint := fmt.Sprintf("` + strings.Replace(data.GetEndpoint, "{id}", "%s", 1) + `", id) - var rawResponse models.BpjsRawResponse - - if err := h.service.Get(ctx, endpoint, &rawResponse); err != nil { - h.logger.Error("Failed to get ` + data.Name + `", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - "id": id, - }) - - statusCode, errorCode := h.categorizeError(err) - h.sendErrorResponse(c, statusCode, errorCode, - "Gagal mengambil data ` + data.Name + `", err.Error(), requestID) - return - } - - if rawResponse.MetaData.Code != "200" { - // Handle specific BPJS error codes - if rawResponse.MetaData.Code == "201" { - h.logger.Info("` + data.Name + ` not found", map[string]interface{}{ - "request_id": requestID, - "id": id, - }) - h.sendErrorResponse(c, http.StatusNotFound, "DATA_NOT_FOUND", - "Data ` + data.Name + ` tidak ditemukan", rawResponse.MetaData.Message, requestID) - return - } - - h.logger.Warn("BPJS get returned error", map[string]interface{}{ - "bpjs_code": rawResponse.MetaData.Code, - "bpjs_message": rawResponse.MetaData.Message, - "request_id": requestID, - "id": id, - }) - - statusCode := h.mapBpjsCodeToHttpStatus(rawResponse.MetaData.Code) - h.sendErrorResponse(c, statusCode, rawResponse.MetaData.Code, - rawResponse.MetaData.Message, "", requestID) - return - } - - duration := time.Since(startTime) - h.logger.Info("` + data.Name + ` retrieved successfully", map[string]interface{}{ - "request_id": requestID, - "duration": duration.String(), - "id": id, - }) - - h.sendSuccessResponse(c, "Data ` + data.Name + ` berhasil diambil", rawResponse.Response, requestID) -}` -} - -func generateHelperMethods(data BpjsHandlerData) string { - return ` - -// Helper methods for ` + data.Name + `Handler -func (h *` + data.Name + `Handler) sendSuccessResponse(c *gin.Context, message string, data interface{}, requestID string) { - response := models.` + data.Name + `Response{ - BaseResponse: models.BaseResponse{ - Status: "success", - Message: message, - Data: data, - Metadata: &models.ResponseMetadata{ - Timestamp: time.Now(), - Version: "2.0", - RequestID: requestID, - }, - }, - } - c.JSON(http.StatusOK, response) -} - -func (h *` + data.Name + `Handler) sendErrorResponse(c *gin.Context, statusCode int, errorCode, message, details, requestID string) { - response := models.` + data.Name + `Response{ - BaseResponse: models.BaseResponse{ - Status: "error", - Message: message, - Error: &models.ErrorResponse{ - Code: errorCode, - Message: message, - Details: details, - }, - Metadata: &models.ResponseMetadata{ - Timestamp: time.Now(), - Version: "2.0", - RequestID: requestID, - }, - }, - } - c.JSON(statusCode, response) -} - -func (h *` + data.Name + `Handler) formatValidationError(err error) string { - if validationErrors, ok := err.(validator.ValidationErrors); ok { - var messages []string - for _, e := range validationErrors { - switch e.Tag() { - case "required": - messages = append(messages, fmt.Sprintf("%s wajib diisi", e.Field())) - case "min": - messages = append(messages, fmt.Sprintf("%s minimal %s karakter", e.Field(), e.Param())) - case "max": - messages = append(messages, fmt.Sprintf("%s maksimal %s karakter", e.Field(), e.Param())) - case "oneof": - messages = append(messages, fmt.Sprintf("%s harus salah satu dari: %s", e.Field(), e.Param())) - default: - messages = append(messages, fmt.Sprintf("%s tidak valid", e.Field())) - } - } - return fmt.Sprintf("Validasi gagal: %v", messages) - } - return err.Error() -} - -func (h *` + data.Name + `Handler) categorizeError(err error) (int, string) { - if err == nil { - return http.StatusOK, "SUCCESS" - } - - errStr := err.Error() - - if h.isTimeoutError(err) { - return http.StatusRequestTimeout, "REQUEST_TIMEOUT" - } - - if h.isNetworkError(err) { - return http.StatusBadGateway, "NETWORK_ERROR" - } - - if h.isAuthError(errStr) { - return http.StatusUnauthorized, "AUTH_ERROR" - } - - return http.StatusInternalServerError, "INTERNAL_ERROR" -} - -func (h *` + data.Name + `Handler) mapBpjsCodeToHttpStatus(bpjsCode string) int { - switch bpjsCode { - case "200": - return http.StatusOK - case "201": - return http.StatusNotFound - case "202": - return http.StatusBadRequest - case "400": - return http.StatusBadRequest - case "401": - return http.StatusUnauthorized - case "403": - return http.StatusForbidden - case "404": - return http.StatusNotFound - case "500": - return http.StatusInternalServerError - default: - return http.StatusUnprocessableEntity - } -} - -func (h *` + data.Name + `Handler) isTimeoutError(err error) bool { - return err != nil && (err.Error() == "context deadline exceeded" || - err.Error() == "timeout") -} - -func (h *` + data.Name + `Handler) isNetworkError(err error) bool { - return err != nil && (err.Error() == "connection refused" || - err.Error() == "no such host") -} - -func (h *` + data.Name + `Handler) isAuthError(errStr string) bool { - return errStr == "unauthorized" || errStr == "invalid credentials" -}` -} - -// ================= OPTIMIZED MODEL GENERATION ===================== - -func generateOptimizedBpjsModelFile(data BpjsHandlerData, modelDir string) { - modelContent := `package models - -import ( - "encoding/json" - "fmt" - "time" -) - -// ` + data.Name + ` BPJS Models with Enhanced Validation -// Generated at: ` + data.Timestamp + ` -// Category: ` + data.Category + ` - -// Base request/response structures -type BaseRequest struct { - RequestID string ` + "`json:\"request_id,omitempty\"`" + ` - Timestamp time.Time ` + "`json:\"timestamp,omitempty\"`" + ` -} - -type BaseResponse struct { - Status string ` + "`json:\"status\"`" + ` - Message string ` + "`json:\"message\"`" + ` - Data interface{} ` + "`json:\"data,omitempty\"`" + ` - Error *ErrorResponse ` + "`json:\"error,omitempty\"`" + ` - Metadata *ResponseMetadata ` + "`json:\"metadata,omitempty\"`" + ` -} - -type ErrorResponse struct { - Code string ` + "`json:\"code\"`" + ` - Message string ` + "`json:\"message\"`" + ` - Details string ` + "`json:\"details,omitempty\"`" + ` -} - -type ResponseMetadata struct { - Timestamp time.Time ` + "`json:\"timestamp\"`" + ` - Version string ` + "`json:\"version\"`" + ` - RequestID string ` + "`json:\"request_id,omitempty\"`" + ` -} - -// ` + data.Name + ` Response Structure -type ` + data.Name + `Response struct { - BaseResponse -} - -// BPJS Raw Response Structure -type BpjsRawResponse struct { - MetaData struct { - Code string ` + "`json:\"code\"`" + ` - Message string ` + "`json:\"message\"`" + ` - } ` + "`json:\"metaData\"`" + ` - Response interface{} ` + "`json:\"response\"`" + ` -}` - - if data.HasPost { - modelContent += ` - -// ` + data.Name + ` POST Request Structure with Enhanced Validation -type ` + data.Name + `PostRequest struct { - BaseRequest - T` + data.Name + ` ` + data.Name + `Post ` + "`json:\"tsep\" binding:\"required\" validate:\"required\"`" + ` -} - -type ` + data.Name + `Post struct { - // Core BPJS fields - customize based on your specific requirements - NoKartu string ` + "`json:\"noKartu\" binding:\"required\" validate:\"required,min=13,max=13\"`" + ` - TglLayanan string ` + "`json:\"tglLayanan\" binding:\"required\" validate:\"required\"`" + ` - JnsPelayanan string ` + "`json:\"jnsPelayanan\" binding:\"required\" validate:\"required,oneof=1 2\"`" + ` - PpkPelayanan string ` + "`json:\"ppkPelayanan\" binding:\"required\" validate:\"required\"`" + ` - Catatan string ` + "`json:\"catatan\" validate:\"omitempty,max=200\"`" + ` - User string ` + "`json:\"user\" binding:\"required\" validate:\"required\"`" + ` -} - -// Validate validates the ` + data.Name + `PostRequest -func (r *` + data.Name + `PostRequest) Validate() error { - if r.T` + data.Name + `.NoKartu == "" { - return fmt.Errorf("nomor kartu tidak boleh kosong") - } - - if len(r.T` + data.Name + `.NoKartu) != 13 { - return fmt.Errorf("nomor kartu harus 13 digit") - } - - if _, err := time.Parse("2006-01-02", r.T` + data.Name + `.TglLayanan); err != nil { - return fmt.Errorf("format tanggal layanan tidak valid, gunakan yyyy-MM-dd") - } - - return nil -} - -// ToJSON converts struct to JSON string -func (r *` + data.Name + `PostRequest) ToJSON() (string, error) { - data, err := json.Marshal(r) - return string(data), err -}` - } - - if data.HasPut { - modelContent += ` - -// ` + data.Name + ` PUT Request Structure with Enhanced Validation -type ` + data.Name + `PutRequest struct { - BaseRequest - T` + data.Name + ` ` + data.Name + `Put ` + "`json:\"tsep\" binding:\"required\" validate:\"required\"`" + ` -} - -type ` + data.Name + `Put struct { - ID string ` + "`json:\"id\" binding:\"required\" validate:\"required\"`" + ` - NoKartu string ` + "`json:\"noKartu\" validate:\"omitempty,min=13,max=13\"`" + ` - TglLayanan string ` + "`json:\"tglLayanan\" validate:\"omitempty\"`" + ` - JnsPelayanan string ` + "`json:\"jnsPelayanan\" validate:\"omitempty,oneof=1 2\"`" + ` - PpkPelayanan string ` + "`json:\"ppkPelayanan\" validate:\"omitempty\"`" + ` - Catatan string ` + "`json:\"catatan\" validate:\"omitempty,max=200\"`" + ` - User string ` + "`json:\"user\" binding:\"required\" validate:\"required\"`" + ` -} - -// Validate validates the ` + data.Name + `PutRequest -func (r *` + data.Name + `PutRequest) Validate() error { - if r.T` + data.Name + `.ID == "" { - return fmt.Errorf("ID tidak boleh kosong") - } - - if r.T` + data.Name + `.NoKartu != "" && len(r.T` + data.Name + `.NoKartu) != 13 { - return fmt.Errorf("nomor kartu harus 13 digit") - } - - return nil -} - -// ToJSON converts struct to JSON string -func (r *` + data.Name + `PutRequest) ToJSON() (string, error) { - data, err := json.Marshal(r) - return string(data), err -}` - } - - if data.HasDelete { - modelContent += ` - -// ` + data.Name + ` DELETE Request Structure with Enhanced Validation -type ` + data.Name + `DeleteRequest struct { - BaseRequest - T` + data.Name + ` ` + data.Name + `DeleteData ` + "`json:\"tsep\" binding:\"required\" validate:\"required\"`" + ` -} - -type ` + data.Name + `DeleteData struct { - ID string ` + "`json:\"id\" binding:\"required\" validate:\"required\"`" + ` - User string ` + "`json:\"user\" binding:\"required\" validate:\"required\"`" + ` -} - -// Validate validates the ` + data.Name + `DeleteRequest -func (r *` + data.Name + `DeleteRequest) Validate() error { - if r.T` + data.Name + `.ID == "" { - return fmt.Errorf("ID tidak boleh kosong") - } - - if r.T` + data.Name + `.User == "" { - return fmt.Errorf("User tidak boleh kosong") - } - - return nil -} - -// ToJSON converts struct to JSON string -func (r *` + data.Name + `DeleteRequest) ToJSON() (string, error) { - data, err := json.Marshal(r) - return string(data), err -}` - } - - // Add common helper structures - modelContent += ` - -// Common Helper Structures for BPJS -type Flag struct { - Flag string ` + "`json:\"flag\" binding:\"required\" validate:\"required,oneof=0 1\"`" + ` -} - -type Poli struct { - Tujuan string ` + "`json:\"tujuan\" binding:\"required\" validate:\"required\"`" + ` - Eksekutif string ` + "`json:\"eksekutif\" binding:\"required\" validate:\"required,oneof=0 1\"`" + ` -} - -type KlsRawat struct { - KlsRawatHak string ` + "`json:\"klsRawatHak\" binding:\"required\" validate:\"required,oneof=1 2 3\"`" + ` - KlsRawatNaik string ` + "`json:\"klsRawatNaik\" validate:\"omitempty,oneof=1 2 3 4 5 6 7\"`" + ` - Pembiayaan string ` + "`json:\"pembiayaan\" validate:\"omitempty,oneof=1 2 3\"`" + ` - PenanggungJawab string ` + "`json:\"penanggungJawab\" validate:\"omitempty,max=100\"`" + ` -} - -// Validation helper functions -func IsValidStatus(status string) bool { - validStatuses := []string{"active", "inactive", "pending", "processed"} - for _, v := range validStatuses { - if v == status { - return true - } - } - return false -} - -func IsValidJnsPelayanan(jns string) bool { - return jns == "1" || jns == "2" // 1: rawat jalan, 2: rawat inap -} - -func IsValidKlsRawat(kls string) bool { - validKelas := []string{"1", "2", "3"} - for _, v := range validKelas { - if v == kls { - return true - } - } - return false -}` - - writeFile(filepath.Join(modelDir, data.NameLower+".go"), modelContent) -} - -// ================= OPTIMIZED ROUTES GENERATION ===================== - -func updateOptimizedBpjsRoutesFile(data BpjsHandlerData) { - routesFile := "internal/routes/v1/routes.go" - content, err := os.ReadFile(routesFile) - if err != nil { - fmt.Printf("āš ļø Could not read routes.go: %v\n", err) - fmt.Printf("šŸ“ Please manually add these optimized routes to your routes.go file:\n") - printOptimizedBpjsRoutesSample(data) - return - } - - routesContent := string(content) - - var importPath, importAlias string - if data.Category != "" { - importPath = fmt.Sprintf("%s/internal/handlers/%s", data.ModuleName, data.Category) - importAlias = data.NameLower + "Handlers" - } else { - importPath = fmt.Sprintf("%s/internal/handlers", data.ModuleName) - importAlias = data.NameLower + "Handlers" - } - - // Check and add import - importPattern := fmt.Sprintf("%s \"%s\"", importAlias, importPath) - if !strings.Contains(routesContent, importPattern) { - importToAdd := fmt.Sprintf("\t%s \"%s\"", importAlias, importPath) - if strings.Contains(routesContent, "import (") { - routesContent = strings.Replace(routesContent, "import (", - "import (\n"+importToAdd, 1) - } - } - - // Build optimized routes - newRoutes := fmt.Sprintf("\t\t// Optimized %s endpoints with enhanced error handling\n", data.Name) - if data.Category != "" { - newRoutes = fmt.Sprintf("\t\t// Optimized %s %s endpoints with enhanced error handling\n", data.Category, data.Name) - } - - newRoutes += fmt.Sprintf("\t\t%sHandlerConfig := &%s.%sHandlerConfig{\n", data.NameLower, importAlias, data.Name) - newRoutes += fmt.Sprintf("\t\t\tBpjsConfig: &config.LoadConfig().Bpjs,\n") - newRoutes += fmt.Sprintf("\t\t\tLogger: logger.GetLogger(),\n") - newRoutes += fmt.Sprintf("\t\t\tValidator: validator.New(),\n") - newRoutes += fmt.Sprintf("\t\t}\n") - newRoutes += fmt.Sprintf("\t\t%sHandler := %s.New%sHandler(%sHandlerConfig)\n", - data.NameLower, importAlias, data.Name, data.NameLower) - - var routePath string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - } else { - routePath = data.NameLower - } - - if data.HasGet { - newRoutes += fmt.Sprintf("\t\tv1.GET(\"/%s/:id\", %sHandler.Get%s)\n", - routePath, data.NameLower, data.Name) - } - - if data.HasPost { - newRoutes += fmt.Sprintf("\t\tv1.POST(\"/%s\", %sHandler.Create%s)\n", - routePath, data.NameLower, data.Name) - } - - if data.HasPut { - newRoutes += fmt.Sprintf("\t\tv1.PUT(\"/%s\", %sHandler.Update%s)\n", - routePath, data.NameLower, data.Name) - } - - if data.HasDelete { - newRoutes += fmt.Sprintf("\t\tv1.DELETE(\"/%s/:id\", %sHandler.Delete%s)\n", - routePath, data.NameLower, data.Name) - } - - newRoutes += "\n" - - // Insert routes - insertMarker := "\t\tprotected := v1.Group(\"/\")" - if strings.Contains(routesContent, insertMarker) { - if !strings.Contains(routesContent, fmt.Sprintf("New%sHandler", data.Name)) { - routesContent = strings.Replace(routesContent, insertMarker, - newRoutes+insertMarker, 1) - } else { - fmt.Printf("āœ… Optimized routes for %s already exist, skipping...\n", data.Name) - return - } - } - - if err := os.WriteFile(routesFile, []byte(routesContent), 0644); err != nil { - fmt.Printf("Error writing routes.go: %v\n", err) - return - } - - if data.Category != "" { - fmt.Printf("āœ… Updated routes.go with optimized %s %s endpoints\n", data.Category, data.Name) - } else { - fmt.Printf("āœ… Updated routes.go with optimized %s endpoints\n", data.Name) - } -} - -func printOptimizedBpjsRoutesSample(data BpjsHandlerData) { - var importAlias string - if data.Category != "" { - importAlias = data.NameLower + "Handlers" - } else { - importAlias = data.NameLower + "Handlers" - } - - var routePath string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - } else { - routePath = data.NameLower - } - - if data.Category != "" { - fmt.Printf(` -// Optimized %s %s endpoints with enhanced error handling -%sHandlerConfig := &%s.%sHandlerConfig{ - BpjsConfig: &config.LoadConfig().Bpjs, - Logger: logger.GetLogger(), - Validator: validator.New(), -} -%sHandler := %s.New%sHandler(%sHandlerConfig) -`, data.Category, data.Name, data.NameLower, importAlias, data.Name, data.NameLower, importAlias, data.Name, data.NameLower) - } else { - fmt.Printf(` -// Optimized %s endpoints with enhanced error handling -%sHandlerConfig := &%s.%sHandlerConfig{ - BpjsConfig: &config.LoadConfig().Bpjs, - Logger: logger.GetLogger(), - Validator: validator.New(), -} -%sHandler := %s.New%sHandler(%sHandlerConfig) -`, data.Name, data.NameLower, importAlias, data.Name, data.NameLower, importAlias, data.Name, data.NameLower) - } - - if data.HasGet { - fmt.Printf("\tv1.GET(\"/%s/:id\", %sHandler.Get%s)\n", routePath, data.NameLower, data.Name) - } - if data.HasPost { - fmt.Printf("\tv1.POST(\"/%s\", %sHandler.Create%s)\n", routePath, data.NameLower, data.Name) - } - if data.HasPut { - fmt.Printf("\tv1.PUT(\"/%s\", %sHandler.Update%s)\n", routePath, data.NameLower, data.Name) - } - if data.HasDelete { - fmt.Printf("\tv1.DELETE(\"/%s/:id\", %sHandler.Delete%s)\n", routePath, data.NameLower, data.Name) - } - - fmt.Println() -} - -// ================= UTILITY FUNCTIONS ===================== - -func writeFile(filename, content string) { - if err := os.WriteFile(filename, []byte(content), 0644); err != nil { - fmt.Printf("āŒ Error creating file %s: %v\n", filename, err) - return - } - - fmt.Printf("āœ… Generated optimized file: %s\n", filename) -} diff --git a/tools/generate-bpjs-handler.go.backup b/tools/generate-bpjs-handler.go.backup deleted file mode 100644 index e6c3c66..0000000 --- a/tools/generate-bpjs-handler.go.backup +++ /dev/null @@ -1,644 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "time" -) - -// BpjsHandlerData contains template data for BPJS handler generation -type BpjsHandlerData struct { - Name string - NameLower string - NameUpper string - Category string - CategoryPath string - ModuleName string - HasGet bool - HasPost bool - HasPut bool - HasDelete bool - GetEndpoint string - PostEndpoint string - PutEndpoint string - DeleteEndpoint string - Timestamp string -} - -func main() { - if len(os.Args) < 2 { - fmt.Println("Usage: go run generate-bpjs-handler.go [category/]entity [methods]") - fmt.Println("Examples:") - fmt.Println(" go run generate-bpjs-handler.go vclaim/peserta get post put delete") - fmt.Println(" go run generate-bpjs-handler.go eclaim/sep get post") - fmt.Println(" go run generate-bpjs-handler.go peserta get") - os.Exit(1) - } - - // Parse entity path (could be "entity" or "category/entity") - entityPath := os.Args[1] - methods := []string{} - if len(os.Args) > 2 { - methods = os.Args[2:] - } else { - // Default methods if none specified - methods = []string{"get", "post", "put", "delete"} - } - - // Parse category and entity - var category, entityName string - if strings.Contains(entityPath, "/") { - parts := strings.Split(entityPath, "/") - if len(parts) != 2 { - fmt.Println("āŒ Error: Invalid path format. Use 'category/entity' or just 'entity'") - os.Exit(1) - } - category = parts[0] - entityName = parts[1] - } else { - category = "" - entityName = entityPath - } - - // Format names - entityName = strings.Title(entityName) // PascalCase entity name - entityLower := strings.ToLower(entityName) - entityUpper := strings.ToUpper(entityName) - - data := BpjsHandlerData{ - Name: entityName, - NameLower: entityLower, - NameUpper: entityUpper, - Category: category, - CategoryPath: category, - ModuleName: "api-service", - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - } - - // Set methods and endpoints based on arguments - for _, m := range methods { - switch strings.ToLower(m) { - case "get": - data.HasGet = true - data.GetEndpoint = fmt.Sprintf("/%s/{id}", entityUpper) - case "post": - data.HasPost = true - data.PostEndpoint = fmt.Sprintf("/%s/2.0/insert", entityUpper) - case "put": - data.HasPut = true - data.PutEndpoint = fmt.Sprintf("/%s/2.0/update", entityUpper) - case "delete": - data.HasDelete = true - data.DeleteEndpoint = fmt.Sprintf("/%s/2.0/delete", entityUpper) - } - } - - // Create directories with dynamic logic (sama seperti generate-handler.go) - var handlerDir, modelDir string - if category != "" { - // Dengan kategori: internal/handlers/category/ - handlerDir = filepath.Join("internal", "handlers", category) - modelDir = filepath.Join("internal", "models", category) - } else { - // Tanpa kategori: langsung internal/handlers/ - handlerDir = filepath.Join("internal", "handlers") - modelDir = filepath.Join("internal", "models") - } - - // Create directories - for _, d := range []string{handlerDir, modelDir} { - if err := os.MkdirAll(d, 0755); err != nil { - panic(err) - } - } - - // Generate files - generateBpjsHandlerFile(data, handlerDir) - generateBpjsModelFile(data, modelDir) - updateBpjsRoutesFile(data) - - fmt.Printf("āœ… Successfully generated BPJS handler: %s\n", entityName) - if category != "" { - fmt.Printf("šŸ“ Category: %s\n", category) - } - fmt.Printf("šŸ“ Handler: %s\n", filepath.Join(handlerDir, entityLower+".go")) - fmt.Printf("šŸ“ Model: %s\n", filepath.Join(modelDir, entityLower+".go")) -} - -// ================= HANDLER GENERATION ===================== - -func generateBpjsHandlerFile(data BpjsHandlerData, handlerDir string) { - // Build import path based on category (sama seperti generate-handler.go) - var modelsImportPath string - if data.Category != "" { - modelsImportPath = data.ModuleName + "/internal/models/" + data.Category - } else { - modelsImportPath = data.ModuleName + "/internal/models" - } - - handlerContent := `package handlers - -import ( - "context" - "fmt" - "net/http" - "time" - - "` + data.ModuleName + `/internal/config" - models "` + modelsImportPath + `" - services "` + data.ModuleName + `/internal/services/bpjs" - - "github.com/gin-gonic/gin" -) - -// ` + data.Name + `Handler handles ` + data.NameLower + ` BPJS services -type ` + data.Name + `Handler struct { - service services.VClaimService -} - -// New` + data.Name + `Handler creates a new ` + data.Name + `Handler -func New` + data.Name + `Handler(cfg config.BpjsConfig) *` + data.Name + `Handler { - return &` + data.Name + `Handler{ - service: services.NewService(cfg), - } -}` - - // Add methods based on flags - if data.HasPost { - handlerContent += generateBpjsCreateMethod(data) - } - - if data.HasPut { - handlerContent += generateBpjsUpdateMethod(data) - } - - if data.HasDelete { - handlerContent += generateBpjsDeleteMethod(data) - } - - if data.HasGet { - handlerContent += generateBpjsGetMethod(data) - } - - writeFileBpjs(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent) -} - -func generateBpjsCreateMethod(data BpjsHandlerData) string { - // Build route path based on category (dynamic, tidak hardcode bpjs/) - var routePath string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - } else { - routePath = data.NameLower - } - - // Tag untuk swagger - var tagName string - if data.Category != "" { - tagName = data.Category + "-" + data.NameLower - } else { - tagName = data.NameLower - } - - return ` - -// Create` + data.Name + ` godoc -// @Summary Create a new ` + data.NameUpper + ` -// @Description Create a new ` + data.Name + ` in BPJS system -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param request body models.` + data.Name + `PostRequest true "` + data.Name + ` creation request" -// @Success 200 {object} models.` + data.Name + `Response "` + data.Name + ` created successfully" -// @Failure 400 {object} gin.H "Invalid request" -// @Failure 500 {object} gin.H "Internal server error" -// @Router /api/v1/` + routePath + ` [post] -func (h *` + data.Name + `Handler) Create` + data.Name + `(c *gin.Context) { - var req models.` + data.Name + `PostRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body", "message": err.Error()}) - return - } - - ctx, cancel := context.WithTimeout(c, 30*time.Second) - defer cancel() - - var result map[string]interface{} - if err := h.service.Post(ctx, "` + data.PostEndpoint + `", req, &result); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "create failed", "message": err.Error()}) - return - } - - c.JSON(http.StatusOK, models.` + data.Name + `Response{ - Message: "` + data.Name + ` berhasil dibuat", - Data: result, - }) -}` -} - -func generateBpjsUpdateMethod(data BpjsHandlerData) string { - var routePath string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - } else { - routePath = data.NameLower - } - - var tagName string - if data.Category != "" { - tagName = data.Category + "-" + data.NameLower - } else { - tagName = data.NameLower - } - - return ` - -// Update` + data.Name + ` godoc -// @Summary Update an existing ` + data.NameUpper + ` -// @Description Update an existing ` + data.Name + ` in BPJS system -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param request body models.` + data.Name + `PutRequest true "` + data.Name + ` update request" -// @Success 200 {object} models.` + data.Name + `Response "` + data.Name + ` updated successfully" -// @Failure 400 {object} gin.H "Invalid request" -// @Failure 500 {object} gin.H "Internal server error" -// @Router /api/v1/` + routePath + ` [put] -func (h *` + data.Name + `Handler) Update` + data.Name + `(c *gin.Context) { - var req models.` + data.Name + `PutRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid body", "message": err.Error()}) - return - } - - ctx, cancel := context.WithTimeout(c, 30*time.Second) - defer cancel() - - var result map[string]interface{} - if err := h.service.Put(ctx, "` + data.PutEndpoint + `", req, &result); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "update failed", "message": err.Error()}) - return - } - - c.JSON(http.StatusOK, models.` + data.Name + `Response{ - Message: "` + data.Name + ` berhasil diperbarui", - Data: result, - }) -}` -} - -func generateBpjsDeleteMethod(data BpjsHandlerData) string { - var routePath string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - } else { - routePath = data.NameLower - } - - var tagName string - if data.Category != "" { - tagName = data.Category + "-" + data.NameLower - } else { - tagName = data.NameLower - } - - return ` - -// Delete` + data.Name + ` godoc -// @Summary Delete an existing ` + data.NameUpper + ` -// @Description Delete a ` + data.Name + ` by ID -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param id path string true "` + data.Name + ` ID" -// @Param user query string true "User" -// @Success 200 {object} models.` + data.Name + `Response "` + data.Name + ` deleted successfully" -// @Failure 400 {object} gin.H "Invalid request" -// @Failure 500 {object} gin.H "Internal server error" -// @Router /api/v1/` + routePath + `/{id} [delete] -func (h *` + data.Name + `Handler) Delete` + data.Name + `(c *gin.Context) { - id := c.Param("id") - user := c.Query("user") - if id == "" || user == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "id & user required"}) - return - } - - body := models.` + data.Name + `DeleteRequest{} - body.T` + data.Name + `.ID = id - body.T` + data.Name + `.User = user - - ctx, cancel := context.WithTimeout(c, 30*time.Second) - defer cancel() - - if err := h.service.Delete(ctx, "` + data.DeleteEndpoint + `", body); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "delete failed", "message": err.Error()}) - return - } - - c.JSON(http.StatusOK, models.` + data.Name + `Response{ - Message: "` + data.Name + ` berhasil dihapus", - Data: nil, - }) -}` -} - -func generateBpjsGetMethod(data BpjsHandlerData) string { - var routePath string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - } else { - routePath = data.NameLower - } - - var tagName string - if data.Category != "" { - tagName = data.Category + "-" + data.NameLower - } else { - tagName = data.NameLower - } - - return ` - -// Get` + data.Name + ` godoc -// @Summary Get an existing ` + data.NameUpper + ` -// @Description Retrieve a ` + data.Name + ` by ID -// @Tags ` + tagName + ` -// @Accept json -// @Produce json -// @Param id path string true "` + data.Name + ` ID" -// @Success 200 {object} models.` + data.Name + `Response "Data ` + data.Name + ` retrieved successfully" -// @Failure 400 {object} gin.H "Invalid request" -// @Failure 500 {object} gin.H "Internal server error" -// @Router /api/v1/` + routePath + `/{id} [get] -func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) { - id := c.Param("id") - if id == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "id required"}) - return - } - - ctx, cancel := context.WithTimeout(c, 30*time.Second) - defer cancel() - - endpoint := fmt.Sprintf("` + strings.Replace(data.GetEndpoint, "{id}", "%s", 1) + `", id) - var result map[string]interface{} - if err := h.service.Get(ctx, endpoint, &result); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "fetch failed", "message": err.Error()}) - return - } - - c.JSON(http.StatusOK, models.` + data.Name + `Response{ - Message: "Data ` + data.Name + ` berhasil diambil", - Data: result, - }) -}` -} - -// ================= MODEL GENERATION ===================== - -func generateBpjsModelFile(data BpjsHandlerData, modelDir string) { - modelContent := `package models - -// ` + data.Name + ` BPJS Models -// Generated at: ` + data.Timestamp + ` -// Category: ` + data.Category + ` - -// Common Response Structure -type ` + data.Name + `Response struct { - Message string ` + "`json:\"message\"`" + ` - Data map[string]interface{} ` + "`json:\"data,omitempty\"`" + ` -} - -type ` + data.Name + `RawResponse struct { - MetaData struct { - Code string ` + "`json:\"code\"`" + ` - Message string ` + "`json:\"message\"`" + ` - } ` + "`json:\"metaData\"`" + ` - Response interface{} ` + "`json:\"response\"`" + ` -}` - - if data.HasPost { - modelContent += ` - -// ` + data.Name + ` POST Request Structure -type ` + data.Name + `PostRequest struct { - T` + data.Name + ` ` + data.Name + `Post ` + "`json:\"t_" + data.NameLower + "\" binding:\"required\"`" + ` -} - -type ` + data.Name + `Post struct { - // Add your specific fields here based on BPJS API requirements - NoKartu string ` + "`json:\"noKartu\" binding:\"required\"`" + ` - TglLayanan string ` + "`json:\"tglLayanan\" binding:\"required\"`" + ` - JnsPelayanan string ` + "`json:\"jnsPelayanan\" binding:\"required\"`" + ` - User string ` + "`json:\"user\" binding:\"required\"`" + ` -}` - } - - if data.HasPut { - modelContent += ` - -// ` + data.Name + ` PUT Request Structure -type ` + data.Name + `PutRequest struct { - T` + data.Name + ` ` + data.Name + `Put ` + "`json:\"t_" + data.NameLower + "\" binding:\"required\"`" + ` -} - -type ` + data.Name + `Put struct { - ID string ` + "`json:\"id\" binding:\"required\"`" + ` - NoKartu string ` + "`json:\"noKartu\"`" + ` - TglLayanan string ` + "`json:\"tglLayanan\"`" + ` - JnsPelayanan string ` + "`json:\"jnsPelayanan\"`" + ` - User string ` + "`json:\"user\" binding:\"required\"`" + ` -}` - } - - if data.HasDelete { - modelContent += ` - -// ` + data.Name + ` DELETE Request Structure -type ` + data.Name + `DeleteRequest struct { - T` + data.Name + ` struct { - ID string ` + "`json:\"id\" binding:\"required\"`" + ` - User string ` + "`json:\"user\" binding:\"required\"`" + ` - } ` + "`json:\"t_" + data.NameLower + "\" binding:\"required\"`" + ` -}` - } - - // Add helper structures - modelContent += ` - -// Common Helper Structures -type Flag struct { - Flag string ` + "`json:\"flag\" binding:\"required\"`" + ` -} - -type Poli struct { - Tujuan string ` + "`json:\"tujuan\"`" + ` - Eksekutif string ` + "`json:\"eksekutif\" binding:\"required\"`" + ` -} - -// Validation helpers -func IsValidStatus(status string) bool { - validStatuses := []string{"active", "inactive", "pending", "processed"} - for _, v := range validStatuses { - if v == status { - return true - } - } - return false -}` - - writeFileBpjs(filepath.Join(modelDir, data.NameLower+".go"), modelContent) -} - -// ================= ROUTES GENERATION ===================== - -func updateBpjsRoutesFile(data BpjsHandlerData) { - routesFile := "internal/routes/v1/routes.go" - content, err := os.ReadFile(routesFile) - if err != nil { - fmt.Printf("āš ļø Could not read routes.go: %v\n", err) - fmt.Printf("šŸ“ Please manually add these routes to your routes.go file:\n") - printBpjsRoutesSample(data) - return - } - - routesContent := string(content) - - // Build import path berdasarkan category (sama seperti generate-handler.go) - var importPath, importAlias string - if data.Category != "" { - importPath = fmt.Sprintf("%s/internal/handlers/%s", data.ModuleName, data.Category) - importAlias = data.NameLower + "Handlers" - } else { - importPath = fmt.Sprintf("%s/internal/handlers", data.ModuleName) - importAlias = data.NameLower + "Handlers" - } - - // Check and add import - importPattern := fmt.Sprintf("%s \"%s\"", importAlias, importPath) - if !strings.Contains(routesContent, importPattern) { - importToAdd := fmt.Sprintf("\t%s \"%s\"", importAlias, importPath) - if strings.Contains(routesContent, "import (") { - routesContent = strings.Replace(routesContent, "import (", - "import (\n"+importToAdd, 1) - } - } - - // Build routes - newRoutes := fmt.Sprintf("\t\t// %s endpoints\n", data.Name) - if data.Category != "" { - newRoutes = fmt.Sprintf("\t\t// %s %s endpoints\n", data.Category, data.Name) - } - newRoutes += fmt.Sprintf("\t\t%sHandler := %s.New%sHandler(config.LoadConfig().Bpjs)\n", - data.NameLower, importAlias, data.Name) - - // Build route paths berdasarkan category (dynamic, tidak hardcode) - var routePath string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - } else { - routePath = data.NameLower - } - - if data.HasGet { - newRoutes += fmt.Sprintf("\t\tv1.GET(\"/%s/:id\", %sHandler.Get%s)\n", - routePath, data.NameLower, data.Name) - } - - if data.HasPost { - newRoutes += fmt.Sprintf("\t\tv1.POST(\"/%s\", %sHandler.Create%s)\n", - routePath, data.NameLower, data.Name) - } - - if data.HasPut { - newRoutes += fmt.Sprintf("\t\tv1.PUT(\"/%s\", %sHandler.Update%s)\n", - routePath, data.NameLower, data.Name) - } - - if data.HasDelete { - newRoutes += fmt.Sprintf("\t\tv1.DELETE(\"/%s/:id\", %sHandler.Delete%s)\n", - routePath, data.NameLower, data.Name) - } - - newRoutes += "\n" - - // Insert routes - insertMarker := "\t\tprotected := v1.Group(\"/\")" - if strings.Contains(routesContent, insertMarker) { - if !strings.Contains(routesContent, fmt.Sprintf("New%sHandler", data.Name)) { - routesContent = strings.Replace(routesContent, insertMarker, - newRoutes+insertMarker, 1) - } else { - fmt.Printf("āœ… Routes for %s already exist, skipping...\n", data.Name) - return - } - } - - if err := os.WriteFile(routesFile, []byte(routesContent), 0644); err != nil { - fmt.Printf("Error writing routes.go: %v\n", err) - return - } - - if data.Category != "" { - fmt.Printf("āœ… Updated routes.go with %s %s endpoints\n", data.Category, data.Name) - } else { - fmt.Printf("āœ… Updated routes.go with %s endpoints\n", data.Name) - } -} - -func printBpjsRoutesSample(data BpjsHandlerData) { - var importAlias string - if data.Category != "" { - importAlias = data.NameLower + "Handlers" - } else { - importAlias = data.NameLower + "Handlers" - } - - var routePath string - if data.Category != "" { - routePath = data.Category + "/" + data.NameLower - } else { - routePath = data.NameLower - } - - if data.Category != "" { - fmt.Printf(` -// %s %s endpoints -%sHandler := %s.New%sHandler(config.LoadConfig().Bpjs) -`, data.Category, data.Name, data.NameLower, importAlias, data.Name) - } else { - fmt.Printf(` -// %s endpoints -%sHandler := %s.New%sHandler(config.LoadConfig().Bpjs) -`, data.Name, data.NameLower, importAlias, data.Name) - } - - if data.HasGet { - fmt.Printf("\tv1.GET(\"/%s/:id\", %sHandler.Get%s)\n", routePath, data.NameLower, data.Name) - } - if data.HasPost { - fmt.Printf("\tv1.POST(\"/%s\", %sHandler.Create%s)\n", routePath, data.NameLower, data.Name) - } - if data.HasPut { - fmt.Printf("\tv1.PUT(\"/%s\", %sHandler.Update%s)\n", routePath, data.NameLower, data.Name) - } - if data.HasDelete { - fmt.Printf("\tv1.DELETE(\"/%s/:id\", %sHandler.Delete%s)\n", routePath, data.NameLower, data.Name) - } - - fmt.Println() -} - -// ================= UTILITY FUNCTIONS ===================== - -func writeFileBpjs(filename, content string) { - if err := os.WriteFile(filename, []byte(content), 0644); err != nil { - fmt.Printf("āŒ Error creating file %s: %v\n", filename, err) - return - } - - fmt.Printf("āœ… Generated: %s\n", filename) -} diff --git a/tools/generate-handler.go.backup b/tools/generate-handler.go.backup deleted file mode 100644 index 730fa33..0000000 --- a/tools/generate-handler.go.backup +++ /dev/null @@ -1,1590 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "time" -) - -// HandlerData contains template data for handler generation -type HandlerData struct { - Name string - NameLower string - NamePlural string - Category string - CategoryPath string - ModuleName string - TableName string - HasGet bool - HasPost bool - HasPut bool - HasDelete bool - HasStats bool - HasDynamic bool - HasSearch bool - HasFilter bool - HasPagination bool - Timestamp string -} - -func main() { - if len(os.Args) < 2 { - fmt.Println("Usage: go run generate-handler.go [category/]entity [methods]") - fmt.Println("Examples:") - fmt.Println(" go run generate-handler.go product get post put delete") - fmt.Println(" go run generate-handler.go retribusi/tarif get post put delete dynamic search") - os.Exit(1) - } - - // Parse entity path (could be "entity" or "category/entity") - entityPath := os.Args[1] - methods := []string{} - if len(os.Args) > 2 { - methods = os.Args[2:] - } else { - // Default methods with advanced features - methods = []string{"get", "post", "put", "delete", "dynamic", "search"} - } - - // Parse category and entity - var category, entityName string - if strings.Contains(entityPath, "/") { - parts := strings.Split(entityPath, "/") - if len(parts) != 2 { - fmt.Println("āŒ Error: Invalid path format. Use 'category/entity' or just 'entity'") - os.Exit(1) - } - category = parts[0] - entityName = parts[1] - } else { - category = "" - entityName = entityPath - } - - // Format names - entityName = strings.Title(entityName) // PascalCase entity name - entityLower := strings.ToLower(entityName) - entityPlural := entityLower + "s" - - // Table name: include category if exists - var tableName string - if category != "" { - tableName = "data_" + category + "_" + entityLower - } else { - tableName = "data_" + entityLower - } - - data := HandlerData{ - Name: entityName, - NameLower: entityLower, - NamePlural: entityPlural, - Category: category, - CategoryPath: category, - ModuleName: "api-service", - TableName: tableName, - HasPagination: true, - HasFilter: true, - Timestamp: time.Now().Format("2006-01-02 15:04:05"), - } - - // Set methods based on arguments - for _, m := range methods { - switch strings.ToLower(m) { - case "get": - data.HasGet = true - case "post": - data.HasPost = true - case "put": - data.HasPut = true - case "delete": - data.HasDelete = true - case "stats": - data.HasStats = true - case "dynamic": - data.HasDynamic = true - case "search": - data.HasSearch = true - } - } - - // Always add stats if we have get - if data.HasGet { - data.HasStats = true - } - - // Create directories with improved logic - var handlerDir, modelDir string - if category != "" { - handlerDir = filepath.Join("internal", "handlers", category) - modelDir = filepath.Join("internal", "models", category) - } else { - handlerDir = filepath.Join("internal", "handlers") - modelDir = filepath.Join("internal", "models") - } - - // Create directories - for _, d := range []string{handlerDir, modelDir} { - if err := os.MkdirAll(d, 0755); err != nil { - panic(err) - } - } - - // Generate files - generateHandlerFile(data, handlerDir) - generateModelFile(data, modelDir) - updateRoutesFile(data) - - fmt.Printf("āœ… Successfully generated handler: %s\n", entityName) - if category != "" { - fmt.Printf("šŸ“ Category: %s\n", category) - } - fmt.Printf("šŸ“ Handler: %s\n", filepath.Join(handlerDir, entityLower+".go")) - fmt.Printf("šŸ“ Model: %s\n", filepath.Join(modelDir, entityLower+".go")) -} - -// ================= HANDLER GENERATION ===================== -func generateHandlerFile(data HandlerData, handlerDir string) { - // Build import path based on category - var modelsImportPath string - if data.Category != "" { - modelsImportPath = data.ModuleName + "/internal/models/" + data.Category - } else { - modelsImportPath = data.ModuleName + "/internal/models" - } - - handlerContent := `package handlers - -import ( - "` + data.ModuleName + `/internal/config" - "` + data.ModuleName + `/internal/database" - models "` + data.ModuleName + `/internal/models" - models` + data.NameLower + ` "` + modelsImportPath + `"` - - // Add conditional imports for dynamic and search functionality - if data.HasDynamic || data.HasSearch { - handlerContent += ` - utils "` + data.ModuleName + `/internal/utils/filters"` - } - - handlerContent += ` - "` + data.ModuleName + `/internal/utils/validation" - "context" - "database/sql" - "fmt" - "log" - "net/http" - "strconv" - "strings" - "sync" - "time" - - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" - "github.com/google/uuid" -) - -var ( - db database.Service - once sync.Once - validate *validator.Validate -) - -// Initialize the database connection and validator -func init() { - once.Do(func() { - db = database.New(config.LoadConfig()) - validate = validator.New() - // Register custom validations if needed - validate.RegisterValidation("` + data.NameLower + `_status", validate` + data.Name + `Status) - if db == nil { - log.Fatal("Failed to initialize database connection") - } - }) -} - -// Custom validation for ` + data.NameLower + ` status -func validate` + data.Name + `Status(fl validator.FieldLevel) bool { - return models.IsValidStatus(fl.Field().String()) -} - -// ` + data.Name + `Handler handles ` + data.NameLower + ` services -type ` + data.Name + `Handler struct { - db database.Service -} - -// New` + data.Name + `Handler creates a new ` + data.Name + `Handler -func New` + data.Name + `Handler() *` + data.Name + `Handler { - return &` + data.Name + `Handler{ - db: db, - } -}` - - // Add methods - if data.HasGet { - handlerContent += generateGetMethods(data) - } - if data.HasDynamic { - handlerContent += generateDynamicMethod(data) - } - if data.HasSearch { - handlerContent += generateSearchMethod(data) - } - if data.HasPost { - handlerContent += generateCreateMethod(data) - } - if data.HasPut { - handlerContent += generateUpdateMethod(data) - } - if data.HasDelete { - handlerContent += generateDeleteMethod(data) - } - if data.HasStats { - handlerContent += generateStatsMethod(data) - } - - // Add helper methods - handlerContent += generateHelperMethods(data) - - writeFile(filepath.Join(handlerDir, data.NameLower+".go"), handlerContent) - -} - -func generateGetMethods(data HandlerData) string { - return ` - -// Get` + data.Name + ` godoc -// @Summary Get ` + data.NameLower + ` with pagination and optional aggregation -// @Description Returns a paginated list of ` + data.NamePlural + ` with optional summary statistics -// @Tags ` + data.NameLower + ` -// @Accept json -// @Produce json -// @Param limit query int false "Limit (max 100)" default(10) -// @Param offset query int false "Offset" default(0) -// @Param include_summary query bool false "Include aggregation summary" default(false) -// @Param status query string false "Filter by status" -// @Param search query string false "Search in multiple fields" -// @Success 200 {object} models` + data.NameLower + `.` + data.Name + `GetResponse "Success response" -// @Failure 400 {object} models` + data.NameLower + `.ErrorResponse "Bad request" -// @Failure 500 {object} models` + data.NameLower + `.ErrorResponse "Internal server error" -// @Router /api/v1/` + data.NamePlural + ` [get] -func (h *` + data.Name + `Handler) Get` + data.Name + `(c *gin.Context) { - // Parse pagination parameters - limit, offset, err := h.parsePaginationParams(c) - if err != nil { - h.respondError(c, "Invalid pagination parameters", err, http.StatusBadRequest) - return - } - - // Parse filter parameters - filter := h.parseFilterParams(c) - includeAggregation := c.Query("include_summary") == "true" - - // Get database connection - dbConn, err := h.db.GetDB("postgres_satudata") - if err != nil { - h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError) - return - } - - // Create context with timeout - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - // Execute concurrent operations - var ( - items []models` + data.NameLower + `.` + data.Name + ` - total int - aggregateData *models.AggregateData - wg sync.WaitGroup - errChan = make(chan error, 3) - mu sync.Mutex - ) - - // Fetch total count - wg.Add(1) - go func() { - defer wg.Done() - if err := h.getTotalCount(ctx, dbConn, filter, &total); err != nil { - mu.Lock() - errChan <- fmt.Errorf("failed to get total count: %w", err) - mu.Unlock() - } - }() - - // Fetch main data - wg.Add(1) - go func() { - defer wg.Done() - result, err := h.fetch` + data.Name + `s(ctx, dbConn, filter, limit, offset) - mu.Lock() - if err != nil { - errChan <- fmt.Errorf("failed to fetch data: %w", err) - } else { - items = result - } - mu.Unlock() - }() - - // Fetch aggregation data if requested - if includeAggregation { - wg.Add(1) - go func() { - defer wg.Done() - result, err := h.getAggregateData(ctx, dbConn, filter) - mu.Lock() - if err != nil { - errChan <- fmt.Errorf("failed to get aggregate data: %w", err) - } else { - aggregateData = result - } - mu.Unlock() - }() - } - - // Wait for all goroutines - wg.Wait() - close(errChan) - - // Check for errors - for err := range errChan { - if err != nil { - h.logAndRespondError(c, "Data processing failed", err, http.StatusInternalServerError) - return - } - } - - // Build response - meta := h.calculateMeta(limit, offset, total) - response := models` + data.NameLower + `.` + data.Name + `GetResponse{ - Message: "Data ` + data.NameLower + ` berhasil diambil", - Data: items, - Meta: meta, - } - - if includeAggregation && aggregateData != nil { - response.Summary = aggregateData - } - - c.JSON(http.StatusOK, response) -} - -// Get` + data.Name + `ByID godoc -// @Summary Get ` + data.Name + ` by ID -// @Description Returns a single ` + data.NameLower + ` by ID -// @Tags ` + data.NameLower + ` -// @Accept json -// @Produce json -// @Param id path string true "` + data.Name + ` ID (UUID)" -// @Success 200 {object} models` + data.NameLower + `.` + data.Name + `GetByIDResponse "Success response" -// @Failure 400 {object} models` + data.NameLower + `.ErrorResponse "Invalid ID format" -// @Failure 404 {object} models` + data.NameLower + `.ErrorResponse "` + data.Name + ` not found" -// @Failure 500 {object} models` + data.NameLower + `.ErrorResponse "Internal server error" -// @Router /api/v1/` + data.NameLower + `/{id} [get] -func (h *` + data.Name + `Handler) Get` + data.Name + `ByID(c *gin.Context) { - id := c.Param("id") - - // Validate UUID format - if _, err := uuid.Parse(id); err != nil { - h.respondError(c, "Invalid ID format", err, http.StatusBadRequest) - return - } - - dbConn, err := h.db.GetDB("postgres_satudata") - if err != nil { - h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second) - defer cancel() - - item, err := h.get` + data.Name + `ByID(ctx, dbConn, id) - if err != nil { - if err == sql.ErrNoRows { - h.respondError(c, "` + data.Name + ` not found", err, http.StatusNotFound) - } else { - h.logAndRespondError(c, "Failed to get ` + data.NameLower + `", err, http.StatusInternalServerError) - } - return - } - - response := models` + data.NameLower + `.` + data.Name + `GetByIDResponse{ - Message: "` + data.Name + ` details retrieved successfully", - Data: item, - } - - c.JSON(http.StatusOK, response) -}` -} - -func generateDynamicMethod(data HandlerData) string { - return ` - -// Get` + data.Name + `Dynamic godoc -// @Summary Get ` + data.NameLower + ` with dynamic filtering -// @Description Returns ` + data.NamePlural + ` with advanced dynamic filtering like Directus -// @Tags ` + data.NameLower + ` -// @Accept json -// @Produce json -// @Param fields query string false "Fields to select (e.g., fields=*.*)" -// @Param filter[column][operator] query string false "Dynamic filters (e.g., filter[name][_eq]=value)" -// @Param sort query string false "Sort fields (e.g., sort=date_created,-name)" -// @Param limit query int false "Limit" default(10) -// @Param offset query int false "Offset" default(0) -// @Success 200 {object} models` + data.NameLower + `.` + data.Name + `GetResponse "Success response" -// @Failure 400 {object} models` + data.NameLower + `.ErrorResponse "Bad request" -// @Failure 500 {object} models` + data.NameLower + `.ErrorResponse "Internal server error" -// @Router /api/v1/` + data.NamePlural + `/dynamic [get] -func (h *` + data.Name + `Handler) Get` + data.Name + `Dynamic(c *gin.Context) { - // Parse query parameters - parser := utils.NewQueryParser().SetLimits(10, 100) - dynamicQuery, err := parser.ParseQuery(c.Request.URL.Query()) - if err != nil { - h.respondError(c, "Invalid query parameters", err, http.StatusBadRequest) - return - } - - // Get database connection - dbConn, err := h.db.GetDB("postgres_satudata") - if err != nil { - h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError) - return - } - - // Create context with timeout - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - // Execute query with dynamic filtering - items, total, err := h.fetch` + data.Name + `sDynamic(ctx, dbConn, dynamicQuery) - if err != nil { - h.logAndRespondError(c, "Failed to fetch data", err, http.StatusInternalServerError) - return - } - - // Build response - meta := h.calculateMeta(dynamicQuery.Limit, dynamicQuery.Offset, total) - response := models` + data.NameLower + `.` + data.Name + `GetResponse{ - Message: "Data ` + data.NameLower + ` berhasil diambil", - Data: items, - Meta: meta, - } - - c.JSON(http.StatusOK, response) -}` -} - -func generateSearchMethod(data HandlerData) string { - return ` - -// Search` + data.Name + `Advanced provides advanced search capabilities -func (h *` + data.Name + `Handler) Search` + data.Name + `Advanced(c *gin.Context) { - // Parse complex search parameters - searchQuery := c.Query("q") - if searchQuery == "" { - h.respondError(c, "Search query is required", fmt.Errorf("empty search query"), http.StatusBadRequest) - return - } - - // Build dynamic query for search - query := utils.DynamicQuery{ - Fields: []string{"*"}, - Filters: []utils.FilterGroup{{ - Filters: []utils.DynamicFilter{ - { - Column: "status", - Operator: utils.OpNotEqual, - Value: "deleted", - }, - { - Column: "name", - Operator: utils.OpContains, - Value: searchQuery, - LogicOp: "OR", - }, - }, - LogicOp: "AND", - }}, - Sort: []utils.SortField{{ - Column: "date_created", - Order: "DESC", - }}, - Limit: 20, - Offset: 0, - } - - // Parse pagination if provided - if limit := c.Query("limit"); limit != "" { - if l, err := strconv.Atoi(limit); err == nil && l > 0 && l <= 100 { - query.Limit = l - } - } - if offset := c.Query("offset"); offset != "" { - if o, err := strconv.Atoi(offset); err == nil && o >= 0 { - query.Offset = o - } - } - - // Get database connection - dbConn, err := h.db.GetDB("postgres_satudata") - if err != nil { - h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - // Execute search - items, total, err := h.fetch` + data.Name + `sDynamic(ctx, dbConn, query) - if err != nil { - h.logAndRespondError(c, "Search failed", err, http.StatusInternalServerError) - return - } - - // Build response - meta := h.calculateMeta(query.Limit, query.Offset, total) - response := models` + data.NameLower + `.` + data.Name + `GetResponse{ - Message: fmt.Sprintf("Search results for '%s'", searchQuery), - Data: items, - Meta: meta, - } - - c.JSON(http.StatusOK, response) -}` -} - -func generateCreateMethod(data HandlerData) string { - return ` - -// Create` + data.Name + ` godoc -// @Summary Create ` + data.NameLower + ` -// @Description Creates a new ` + data.NameLower + ` record -// @Tags ` + data.NameLower + ` -// @Accept json -// @Produce json -// @Param request body models` + data.NameLower + `.` + data.Name + `CreateRequest true "` + data.Name + ` creation request" -// @Success 201 {object} models` + data.NameLower + `.` + data.Name + `CreateResponse "` + data.Name + ` created successfully" -// @Failure 400 {object} models` + data.NameLower + `.ErrorResponse "Bad request or validation error" -// @Failure 500 {object} models` + data.NameLower + `.ErrorResponse "Internal server error" -// @Router /api/v1/` + data.NamePlural + ` [post] -func (h *` + data.Name + `Handler) Create` + data.Name + `(c *gin.Context) { - var req models` + data.NameLower + `.` + data.Name + `CreateRequest - if err := c.ShouldBindJSON(&req); err != nil { - h.respondError(c, "Invalid request body", err, http.StatusBadRequest) - return - } - - // Validate request - if err := validate.Struct(&req); err != nil { - h.respondError(c, "Validation failed", err, http.StatusBadRequest) - return - } - - dbConn, err := h.db.GetDB("postgres_satudata") - if err != nil { - h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second) - defer cancel() - - // Validate duplicate and daily submission - if err := h.validate` + data.Name + `Submission(ctx, dbConn, &req); err != nil { - h.respondError(c, "Validation failed", err, http.StatusBadRequest) - return - } - - item, err := h.create` + data.Name + `(ctx, dbConn, &req) - if err != nil { - h.logAndRespondError(c, "Failed to create ` + data.NameLower + `", err, http.StatusInternalServerError) - return - } - - response := models` + data.NameLower + `.` + data.Name + `CreateResponse{ - Message: "` + data.Name + ` berhasil dibuat", - Data: item, - } - - c.JSON(http.StatusCreated, response) -}` -} - -func generateUpdateMethod(data HandlerData) string { - return ` - -// Update` + data.Name + ` godoc -// @Summary Update ` + data.NameLower + ` -// @Description Updates an existing ` + data.NameLower + ` record -// @Tags ` + data.NameLower + ` -// @Accept json -// @Produce json -// @Param id path string true "` + data.Name + ` ID (UUID)" -// @Param request body models` + data.NameLower + `.` + data.Name + `UpdateRequest true "` + data.Name + ` update request" -// @Success 200 {object} models` + data.NameLower + `.` + data.Name + `UpdateResponse "` + data.Name + ` updated successfully" -// @Failure 400 {object} models` + data.NameLower + `.ErrorResponse "Bad request or validation error" -// @Failure 404 {object} models` + data.NameLower + `.ErrorResponse "` + data.Name + ` not found" -// @Failure 500 {object} models` + data.NameLower + `.ErrorResponse "Internal server error" -// @Router /api/v1/` + data.NameLower + `/{id} [put] -func (h *` + data.Name + `Handler) Update` + data.Name + `(c *gin.Context) { - id := c.Param("id") - - // Validate UUID format - if _, err := uuid.Parse(id); err != nil { - h.respondError(c, "Invalid ID format", err, http.StatusBadRequest) - return - } - - var req models` + data.NameLower + `.` + data.Name + `UpdateRequest - if err := c.ShouldBindJSON(&req); err != nil { - h.respondError(c, "Invalid request body", err, http.StatusBadRequest) - return - } - - // Set ID from path parameter - req.ID = id - - // Validate request - if err := validate.Struct(&req); err != nil { - h.respondError(c, "Validation failed", err, http.StatusBadRequest) - return - } - - dbConn, err := h.db.GetDB("postgres_satudata") - if err != nil { - h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second) - defer cancel() - - item, err := h.update` + data.Name + `(ctx, dbConn, &req) - if err != nil { - if err == sql.ErrNoRows { - h.respondError(c, "` + data.Name + ` not found", err, http.StatusNotFound) - } else { - h.logAndRespondError(c, "Failed to update ` + data.NameLower + `", err, http.StatusInternalServerError) - } - return - } - - response := models` + data.NameLower + `.` + data.Name + `UpdateResponse{ - Message: "` + data.Name + ` berhasil diperbarui", - Data: item, - } - - c.JSON(http.StatusOK, response) -}` -} - -func generateDeleteMethod(data HandlerData) string { - return ` - -// Delete` + data.Name + ` godoc -// @Summary Delete ` + data.NameLower + ` -// @Description Soft deletes a ` + data.NameLower + ` by setting status to 'deleted' -// @Tags ` + data.NameLower + ` -// @Accept json -// @Produce json -// @Param id path string true "` + data.Name + ` ID (UUID)" -// @Success 200 {object} models` + data.NameLower + `.` + data.Name + `DeleteResponse "` + data.Name + ` deleted successfully" -// @Failure 400 {object} models` + data.NameLower + `.ErrorResponse "Invalid ID format" -// @Failure 404 {object} models` + data.NameLower + `.ErrorResponse "` + data.Name + ` not found" -// @Failure 500 {object} models` + data.NameLower + `.ErrorResponse "Internal server error" -// @Router /api/v1/` + data.NameLower + `/{id} [delete] -func (h *` + data.Name + `Handler) Delete` + data.Name + `(c *gin.Context) { - id := c.Param("id") - - // Validate UUID format - if _, err := uuid.Parse(id); err != nil { - h.respondError(c, "Invalid ID format", err, http.StatusBadRequest) - return - } - - dbConn, err := h.db.GetDB("postgres_satudata") - if err != nil { - h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second) - defer cancel() - - err = h.delete` + data.Name + `(ctx, dbConn, id) - if err != nil { - if err == sql.ErrNoRows { - h.respondError(c, "` + data.Name + ` not found", err, http.StatusNotFound) - } else { - h.logAndRespondError(c, "Failed to delete ` + data.NameLower + `", err, http.StatusInternalServerError) - } - return - } - - response := models` + data.NameLower + `.` + data.Name + `DeleteResponse{ - Message: "` + data.Name + ` berhasil dihapus", - ID: id, - } - - c.JSON(http.StatusOK, response) -}` -} - -func generateStatsMethod(data HandlerData) string { - return ` - -// Get` + data.Name + `Stats godoc -// @Summary Get ` + data.NameLower + ` statistics -// @Description Returns comprehensive statistics about ` + data.NameLower + ` data -// @Tags ` + data.NameLower + ` -// @Accept json -// @Produce json -// @Param status query string false "Filter statistics by status" -// @Success 200 {object} models.AggregateData "Statistics data" -// @Failure 500 {object} models` + data.NameLower + `.ErrorResponse "Internal server error" -// @Router /api/v1/` + data.NamePlural + `/stats [get] -func (h *` + data.Name + `Handler) Get` + data.Name + `Stats(c *gin.Context) { - dbConn, err := h.db.GetDB("postgres_satudata") - if err != nil { - h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError) - return - } - - ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second) - defer cancel() - - filter := h.parseFilterParams(c) - aggregateData, err := h.getAggregateData(ctx, dbConn, filter) - if err != nil { - h.logAndRespondError(c, "Failed to get statistics", err, http.StatusInternalServerError) - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": "Statistik ` + data.NameLower + ` berhasil diambil", - "data": aggregateData, - }) -}` -} - -func generateHelperMethods(data HandlerData) string { - helperMethods := ` - -// Database operations -func (h *` + data.Name + `Handler) get` + data.Name + `ByID(ctx context.Context, dbConn *sql.DB, id string) (*models` + data.NameLower + `.` + data.Name + `, error) { - query := "SELECT id, status, sort, user_created, date_created, user_updated, date_updated, name FROM ` + data.TableName + ` WHERE id = $1 AND status != 'deleted'" - row := dbConn.QueryRowContext(ctx, query, id) - - var item models` + data.NameLower + `.` + data.Name + ` - err := row.Scan(&item.ID, &item.Status, &item.Sort, &item.UserCreated, &item.DateCreated, &item.UserUpdated, &item.DateUpdated, &item.Name) - if err != nil { - return nil, err - } - - return &item, nil -} - -func (h *` + data.Name + `Handler) create` + data.Name + `(ctx context.Context, dbConn *sql.DB, req *models` + data.NameLower + `.` + data.Name + `CreateRequest) (*models` + data.NameLower + `.` + data.Name + `, error) { - id := uuid.New().String() - now := time.Now() - - query := "INSERT INTO ` + data.TableName + ` (id, status, date_created, date_updated, name) VALUES ($1, $2, $3, $4, $5) RETURNING id, status, sort, user_created, date_created, user_updated, date_updated, name" - row := dbConn.QueryRowContext(ctx, query, id, req.Status, now, now, req.Name) - - var item models` + data.NameLower + `.` + data.Name + ` - err := row.Scan(&item.ID, &item.Status, &item.Sort, &item.UserCreated, &item.DateCreated, &item.UserUpdated, &item.DateUpdated, &item.Name) - if err != nil { - return nil, fmt.Errorf("failed to create ` + data.NameLower + `: %w", err) - } - - return &item, nil -} - -func (h *` + data.Name + `Handler) update` + data.Name + `(ctx context.Context, dbConn *sql.DB, req *models` + data.NameLower + `.` + data.Name + `UpdateRequest) (*models` + data.NameLower + `.` + data.Name + `, error) { - now := time.Now() - - query := "UPDATE ` + data.TableName + ` SET status = $2, date_updated = $3, name = $4 WHERE id = $1 AND status != 'deleted' RETURNING id, status, sort, user_created, date_created, user_updated, date_updated, name" - row := dbConn.QueryRowContext(ctx, query, req.ID, req.Status, now, req.Name) - - var item models` + data.NameLower + `.` + data.Name + ` - err := row.Scan(&item.ID, &item.Status, &item.Sort, &item.UserCreated, &item.DateCreated, &item.UserUpdated, &item.DateUpdated, &item.Name) - if err != nil { - return nil, fmt.Errorf("failed to update ` + data.NameLower + `: %w", err) - } - - return &item, nil -} - -func (h *` + data.Name + `Handler) delete` + data.Name + `(ctx context.Context, dbConn *sql.DB, id string) error { - now := time.Now() - query := "UPDATE ` + data.TableName + ` SET status = 'deleted', date_updated = $2 WHERE id = $1 AND status != 'deleted'" - - result, err := dbConn.ExecContext(ctx, query, id, now) - if err != nil { - return fmt.Errorf("failed to delete ` + data.NameLower + `: %w", err) - } - - rowsAffected, err := result.RowsAffected() - if err != nil { - return fmt.Errorf("failed to get affected rows: %w", err) - } - - if rowsAffected == 0 { - return sql.ErrNoRows - } - - return nil -} - -func (h *` + data.Name + `Handler) fetch` + data.Name + `s(ctx context.Context, dbConn *sql.DB, filter models` + data.NameLower + `.` + data.Name + `Filter, limit, offset int) ([]models` + data.NameLower + `.` + data.Name + `, error) { - whereClause, args := h.buildWhereClause(filter) - query := fmt.Sprintf("SELECT id, status, sort, user_created, date_created, user_updated, date_updated, name FROM ` + data.TableName + ` WHERE %s ORDER BY date_created DESC NULLS LAST LIMIT $%d OFFSET $%d", whereClause, len(args)+1, len(args)+2) - args = append(args, limit, offset) - - rows, err := dbConn.QueryContext(ctx, query, args...) - if err != nil { - return nil, fmt.Errorf("fetch ` + data.NamePlural + ` query failed: %w", err) - } - defer rows.Close() - - items := make([]models` + data.NameLower + `.` + data.Name + `, 0, limit) - for rows.Next() { - item, err := h.scan` + data.Name + `(rows) - if err != nil { - return nil, fmt.Errorf("scan ` + data.Name + ` failed: %w", err) - } - items = append(items, item) - } - - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("rows iteration error: %w", err) - } - - log.Printf("Successfully fetched %d ` + data.NamePlural + ` with filters applied", len(items)) - return items, nil -}` - - // Add dynamic fetch method if needed - if data.HasDynamic { - helperMethods += ` - -// fetchRetribusisDynamic executes dynamic query -func (h *` + data.Name + `Handler) fetch` + data.Name + `sDynamic(ctx context.Context, dbConn *sql.DB, query utils.DynamicQuery) ([]models` + data.NameLower + `.` + data.Name + `, int, error) { - // Setup query builder - builder := utils.NewQueryBuilder("` + data.TableName + `"). - SetAllowedColumns([]string{ - "id", "status", "sort", "user_created", "date_created", - "user_updated", "date_updated", "name", - }) - - // Add default filter to exclude deleted records - query.Filters = append([]utils.FilterGroup{{ - Filters: []utils.DynamicFilter{{ - Column: "status", - Operator: utils.OpNotEqual, - Value: "deleted", - }}, - LogicOp: "AND", - }}, query.Filters...) - - // Execute concurrent queries - var ( - items []models` + data.NameLower + `.` + data.Name + ` - total int - wg sync.WaitGroup - errChan = make(chan error, 2) - mu sync.Mutex - ) - - // Fetch total count - wg.Add(1) - go func() { - defer wg.Done() - countQuery := query - countQuery.Limit = 0 - countQuery.Offset = 0 - countSQL, countArgs, err := builder.BuildCountQuery(countQuery) - if err != nil { - errChan <- fmt.Errorf("failed to build count query: %w", err) - return - } - if err := dbConn.QueryRowContext(ctx, countSQL, countArgs...).Scan(&total); err != nil { - errChan <- fmt.Errorf("failed to get total count: %w", err) - return - } - }() - - // Fetch main data - wg.Add(1) - go func() { - defer wg.Done() - mainSQL, mainArgs, err := builder.BuildQuery(query) - if err != nil { - errChan <- fmt.Errorf("failed to build main query: %w", err) - return - } - - rows, err := dbConn.QueryContext(ctx, mainSQL, mainArgs...) - if err != nil { - errChan <- fmt.Errorf("failed to execute main query: %w", err) - return - } - defer rows.Close() - - var results []models` + data.NameLower + `.` + data.Name + ` - for rows.Next() { - item, err := h.scan` + data.Name + `(rows) - if err != nil { - errChan <- fmt.Errorf("failed to scan ` + data.NameLower + `: %w", err) - return - } - results = append(results, item) - } - - if err := rows.Err(); err != nil { - errChan <- fmt.Errorf("rows iteration error: %w", err) - return - } - - mu.Lock() - items = results - mu.Unlock() - }() - - // Wait for all goroutines - wg.Wait() - close(errChan) - - // Check for errors - for err := range errChan { - if err != nil { - return nil, 0, err - } - } - - return items, total, nil -} - -// Optimized scanning function yang menggunakan sql.Null* types langsung -func (h *` + data.Name + `Handler) scan` + data.Name + `(rows *sql.Rows) (models` + data.NameLower + `.` + data.Name + `, error) { - var item models` + data.NameLower + `.` + data.Name + ` - return item, rows.Scan( - &item.ID, &item.Status, &item.Sort, &item.UserCreated, - &item.DateCreated, &item.UserUpdated, &item.DateUpdated, &item.Name, - ) -}` - } - - helperMethods += ` - -func (h *` + data.Name + `Handler) getTotalCount(ctx context.Context, dbConn *sql.DB, filter models` + data.NameLower + `.` + data.Name + `Filter, total *int) error { - whereClause, args := h.buildWhereClause(filter) - countQuery := fmt.Sprintf("SELECT COUNT(*) FROM ` + data.TableName + ` WHERE %s", whereClause) - if err := dbConn.QueryRowContext(ctx, countQuery, args...).Scan(total); err != nil { - return fmt.Errorf("total count query failed: %w", err) - } - return nil -} - -// Get comprehensive aggregate data dengan filter support -func (h *` + data.Name + `Handler) getAggregateData(ctx context.Context, dbConn *sql.DB, filter models` + data.NameLower + `.` + data.Name + `Filter) (*models.AggregateData, error) { - aggregate := &models.AggregateData{ - ByStatus: make(map[string]int), - } - - // Build where clause untuk filter - whereClause, args := h.buildWhereClause(filter) - - // Use concurrent execution untuk performance - var wg sync.WaitGroup - var mu sync.Mutex - errChan := make(chan error, 4) - - // 1. Count by status - wg.Add(1) - go func() { - defer wg.Done() - statusQuery := fmt.Sprintf("SELECT status, COUNT(*) FROM ` + data.TableName + ` WHERE %s GROUP BY status ORDER BY status", whereClause) - - rows, err := dbConn.QueryContext(ctx, statusQuery, args...) - if err != nil { - errChan <- fmt.Errorf("status query failed: %w", err) - return - } - defer rows.Close() - - mu.Lock() - for rows.Next() { - var status string - var count int - if err := rows.Scan(&status, &count); err != nil { - mu.Unlock() - errChan <- fmt.Errorf("status scan failed: %w", err) - return - } - aggregate.ByStatus[status] = count - switch status { - case "active": - aggregate.TotalActive = count - case "draft": - aggregate.TotalDraft = count - case "inactive": - aggregate.TotalInactive = count - } - } - mu.Unlock() - - if err := rows.Err(); err != nil { - errChan <- fmt.Errorf("status iteration error: %w", err) - } - }() - - // 2. Get last updated time dan today statistics - wg.Add(1) - go func() { - defer wg.Done() - - // Last updated - lastUpdatedQuery := fmt.Sprintf("SELECT MAX(date_updated) FROM ` + data.TableName + ` WHERE %s AND date_updated IS NOT NULL", whereClause) - var lastUpdated sql.NullTime - if err := dbConn.QueryRowContext(ctx, lastUpdatedQuery, args...).Scan(&lastUpdated); err != nil { - errChan <- fmt.Errorf("last updated query failed: %w", err) - return - } - - // Today statistics - today := time.Now().Format("2006-01-02") - todayStatsQuery := fmt.Sprintf(` + "`" + ` - SELECT - SUM(CASE WHEN DATE(date_created) = $%d THEN 1 ELSE 0 END) as created_today, - SUM(CASE WHEN DATE(date_updated) = $%d AND DATE(date_created) != $%d THEN 1 ELSE 0 END) as updated_today - FROM ` + data.TableName + ` - WHERE %s` + "`" + `, len(args)+1, len(args)+1, len(args)+1, whereClause) - - todayArgs := append(args, today) - var createdToday, updatedToday int - if err := dbConn.QueryRowContext(ctx, todayStatsQuery, todayArgs...).Scan(&createdToday, &updatedToday); err != nil { - errChan <- fmt.Errorf("today stats query failed: %w", err) - return - } - - mu.Lock() - if lastUpdated.Valid { - aggregate.LastUpdated = &lastUpdated.Time - } - aggregate.CreatedToday = createdToday - aggregate.UpdatedToday = updatedToday - mu.Unlock() - }() - - // Wait for all goroutines - wg.Wait() - close(errChan) - - // Check for errors - for err := range errChan { - if err != nil { - return nil, err - } - } - - return aggregate, nil -} - -// Enhanced error handling -func (h *` + data.Name + `Handler) logAndRespondError(c *gin.Context, message string, err error, statusCode int) { - log.Printf("[ERROR] %s: %v", message, err) - h.respondError(c, message, err, statusCode) -} - -func (h *` + data.Name + `Handler) respondError(c *gin.Context, message string, err error, statusCode int) { - errorMessage := message - if gin.Mode() == gin.ReleaseMode { - errorMessage = "Internal server error" - } - - c.JSON(statusCode, models.ErrorResponse{ - Error: errorMessage, - Code: statusCode, - Message: err.Error(), - Timestamp: time.Now(), - }) -} - -// Parse pagination parameters dengan validation yang lebih ketat -func (h *` + data.Name + `Handler) parsePaginationParams(c *gin.Context) (int, int, error) { - limit := 10 // Default limit - offset := 0 // Default offset - - if limitStr := c.Query("limit"); limitStr != "" { - parsedLimit, err := strconv.Atoi(limitStr) - if err != nil { - return 0, 0, fmt.Errorf("invalid limit parameter: %s", limitStr) - } - if parsedLimit <= 0 { - return 0, 0, fmt.Errorf("limit must be greater than 0") - } - if parsedLimit > 100 { - return 0, 0, fmt.Errorf("limit cannot exceed 100") - } - limit = parsedLimit - } - - if offsetStr := c.Query("offset"); offsetStr != "" { - parsedOffset, err := strconv.Atoi(offsetStr) - if err != nil { - return 0, 0, fmt.Errorf("invalid offset parameter: %s", offsetStr) - } - if parsedOffset < 0 { - return 0, 0, fmt.Errorf("offset cannot be negative") - } - offset = parsedOffset - } - - log.Printf("Pagination - Limit: %d, Offset: %d", limit, offset) - return limit, offset, nil -} - -func (h *` + data.Name + `Handler) parseFilterParams(c *gin.Context) models` + data.NameLower + `.` + data.Name + `Filter { - filter := models` + data.NameLower + `.` + data.Name + `Filter{} - - if status := c.Query("status"); status != "" { - if models.IsValidStatus(status) { - filter.Status = &status - } - } - - if search := c.Query("search"); search != "" { - filter.Search = &search - } - - // Parse date filters - if dateFromStr := c.Query("date_from"); dateFromStr != "" { - if dateFrom, err := time.Parse("2006-01-02", dateFromStr); err == nil { - filter.DateFrom = &dateFrom - } - } - - if dateToStr := c.Query("date_to"); dateToStr != "" { - if dateTo, err := time.Parse("2006-01-02", dateToStr); err == nil { - filter.DateTo = &dateTo - } - } - - return filter -} - -// Build WHERE clause dengan filter parameters -func (h *` + data.Name + `Handler) buildWhereClause(filter models` + data.NameLower + `.` + data.Name + `Filter) (string, []interface{}) { - conditions := []string{"status != 'deleted'"} - args := []interface{}{} - paramCount := 1 - - if filter.Status != nil { - conditions = append(conditions, fmt.Sprintf("status = $%d", paramCount)) - args = append(args, *filter.Status) - paramCount++ - } - - if filter.Search != nil { - searchCondition := fmt.Sprintf("name ILIKE $%d", paramCount) - conditions = append(conditions, searchCondition) - searchTerm := "%" + *filter.Search + "%" - args = append(args, searchTerm) - paramCount++ - } - - if filter.DateFrom != nil { - conditions = append(conditions, fmt.Sprintf("date_created >= $%d", paramCount)) - args = append(args, *filter.DateFrom) - paramCount++ - } - - if filter.DateTo != nil { - conditions = append(conditions, fmt.Sprintf("date_created <= $%d", paramCount)) - args = append(args, filter.DateTo.Add(24*time.Hour-time.Nanosecond)) - paramCount++ - } - - return strings.Join(conditions, " AND "), args -} - -func (h *` + data.Name + `Handler) calculateMeta(limit, offset, total int) models.MetaResponse { - totalPages := 0 - currentPage := 1 - if limit > 0 { - totalPages = (total + limit - 1) / limit // Ceiling division - currentPage = (offset / limit) + 1 - } - - return models.MetaResponse{ - Limit: limit, - Offset: offset, - Total: total, - TotalPages: totalPages, - CurrentPage: currentPage, - HasNext: offset+limit < total, - HasPrev: offset > 0, - } -} - -// validate` + data.Name + `Submission performs validation for duplicate entries and daily submission limits -func (h *` + data.Name + `Handler) validate` + data.Name + `Submission(ctx context.Context, dbConn *sql.DB, req *models` + data.NameLower + `.` + data.Name + `CreateRequest) error { - // Import the validation utility - validator := validation.NewDuplicateValidator(dbConn) - - // Use default configuration - config := validation.ValidationConfig{ - TableName: "` + data.TableName + `", - IDColumn: "id", - StatusColumn: "status", - DateColumn: "date_created", - ActiveStatuses: []string{"active", "draft"}, - } - - // Validate duplicate entries with active status for today - err := validator.ValidateDuplicate(ctx, config, "dummy_id") - if err != nil { - return fmt.Errorf("validation failed: %w", err) - } - - // Validate once per day submission - err = validator.ValidateOncePerDay(ctx, "` + data.TableName + `", "id", "date_created", "daily_limit") - if err != nil { - return fmt.Errorf("daily submission limit exceeded: %w", err) - } - - return nil -} - -// Example usage of the validation utility with custom configuration -func (h *` + data.Name + `Handler) validateWithCustomConfig(ctx context.Context, dbConn *sql.DB, req *models` + data.NameLower + `.` + data.Name + `CreateRequest) error { - // Create validator instance - validator := validation.NewDuplicateValidator(dbConn) - - // Use custom configuration - config := validation.ValidationConfig{ - TableName: "` + data.TableName + `", - IDColumn: "id", - StatusColumn: "status", - DateColumn: "date_created", - ActiveStatuses: []string{"active", "draft"}, - AdditionalFields: map[string]interface{}{ - "name": req.Name, - }, - } - - // Validate with custom fields - fields := map[string]interface{}{ - "name": *req.Name, - } - - err := validator.ValidateDuplicateWithCustomFields(ctx, config, fields) - if err != nil { - return fmt.Errorf("custom validation failed: %w", err) - } - - return nil -} - -// GetLastSubmissionTime example -func (h *` + data.Name + `Handler) getLastSubmissionTimeExample(ctx context.Context, dbConn *sql.DB, identifier string) (*time.Time, error) { - validator := validation.NewDuplicateValidator(dbConn) - return validator.GetLastSubmissionTime(ctx, "` + data.TableName + `", "id", "date_created", identifier) -}` - - return helperMethods -} - -// Keep existing functions for model generation and routes... -// (The remaining functions stay the same as in the original file) - -// ================= MODEL GENERATION ===================== -func generateModelFile(data HandlerData, modelDir string) { - modelContent := `package models - -import ( - "` + data.ModuleName + `/internal/models" - "database/sql" - "encoding/json" - "time" -) - -// ` + data.Name + ` represents the data structure for the ` + data.NameLower + ` table -// with proper null handling and optimized JSON marshaling -type ` + data.Name + ` 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\"`" + ` - Name sql.NullString ` + "`json:\"name,omitempty\" db:\"name\"`" + ` -} - -// Custom JSON marshaling untuk ` + data.Name + ` agar NULL values tidak muncul di response -func (r ` + data.Name + `) MarshalJSON() ([]byte, error) { - type Alias ` + data.Name + ` - aux := &struct { - Sort *int ` + "`json:\"sort,omitempty\"`" + ` - UserCreated *string ` + "`json:\"user_created,omitempty\"`" + ` - DateCreated *time.Time ` + "`json:\"date_created,omitempty\"`" + ` - UserUpdated *string ` + "`json:\"user_updated,omitempty\"`" + ` - DateUpdated *time.Time ` + "`json:\"date_updated,omitempty\"`" + ` - Name *string ` + "`json:\"name,omitempty\"`" + ` - *Alias - }{ - Alias: (*Alias)(&r), - } - - // Convert NullableInt32 to pointer - if r.Sort.Valid { - sort := int(r.Sort.Int32) - aux.Sort = &sort - } - - if r.UserCreated.Valid { - aux.UserCreated = &r.UserCreated.String - } - - if r.DateCreated.Valid { - aux.DateCreated = &r.DateCreated.Time - } - - if r.UserUpdated.Valid { - aux.UserUpdated = &r.UserUpdated.String - } - - if r.DateUpdated.Valid { - aux.DateUpdated = &r.DateUpdated.Time - } - - if r.Name.Valid { - aux.Name = &r.Name.String - } - - return json.Marshal(aux) -} - -// Helper methods untuk mendapatkan nilai yang aman -func (r *` + data.Name + `) GetName() string { - if r.Name.Valid { - return r.Name.String - } - return "" -}` - - // Add request/response structs based on enabled methods - if data.HasGet { - modelContent += ` - -// Response struct untuk GET by ID - diperbaiki struktur -type ` + data.Name + `GetByIDResponse struct { - Message string ` + "`json:\"message\"`" + ` - Data *` + data.Name + ` ` + "`json:\"data\"`" + ` -} - -// Enhanced GET response dengan pagination dan aggregation -type ` + data.Name + `GetResponse struct { - Message string ` + "`json:\"message\"`" + ` - Data []` + data.Name + ` ` + "`json:\"data\"`" + ` - Meta models.MetaResponse ` + "`json:\"meta\"`" + ` - Summary *models.AggregateData ` + "`json:\"summary,omitempty\"`" + ` -}` - } - - if data.HasPost { - modelContent += ` - -// Request struct untuk create - dioptimalkan dengan validasi -type ` + data.Name + `CreateRequest struct { - Status string ` + "`json:\"status\" validate:\"required,oneof=draft active inactive\"`" + ` - Name *string ` + "`json:\"name,omitempty\" validate:\"omitempty,min=1,max=255\"`" + ` -} - -// Response struct untuk create -type ` + data.Name + `CreateResponse struct { - Message string ` + "`json:\"message\"`" + ` - Data *` + data.Name + ` ` + "`json:\"data\"`" + ` -}` - } - - if data.HasPut { - modelContent += ` - -// Update request - sama seperti create tapi dengan ID -type ` + data.Name + `UpdateRequest struct { - ID string ` + "`json:\"-\" validate:\"required,uuid4\"`" + ` // ID dari URL path - Status string ` + "`json:\"status\" validate:\"required,oneof=draft active inactive\"`" + ` - Name *string ` + "`json:\"name,omitempty\" validate:\"omitempty,min=1,max=255\"`" + ` -} - -// Response struct untuk update -type ` + data.Name + `UpdateResponse struct { - Message string ` + "`json:\"message\"`" + ` - Data *` + data.Name + ` ` + "`json:\"data\"`" + ` -}` - } - - if data.HasDelete { - modelContent += ` - -// Response struct untuk delete -type ` + data.Name + `DeleteResponse struct { - Message string ` + "`json:\"message\"`" + ` - ID string ` + "`json:\"id\"`" + ` -}` - } - - // Add filter struct - modelContent += ` - -// Filter struct untuk query parameters -type ` + data.Name + `Filter struct { - Status *string ` + "`json:\"status,omitempty\" form:\"status\"`" + ` - Search *string ` + "`json:\"search,omitempty\" form:\"search\"`" + ` - DateFrom *time.Time ` + "`json:\"date_from,omitempty\" form:\"date_from\"`" + ` - DateTo *time.Time ` + "`json:\"date_to,omitempty\" form:\"date_to\"`" + ` -}` - - writeFile(filepath.Join(modelDir, data.NameLower+".go"), modelContent) -} - -// ================= ROUTES GENERATION ===================== -func updateRoutesFile(data HandlerData) { - routesFile := "internal/routes/v1/routes.go" - content, err := os.ReadFile(routesFile) - if err != nil { - fmt.Printf("āš ļø Could not read routes.go: %v\n", err) - fmt.Printf("šŸ“ Please manually add these routes to your routes.go file:\n") - printRoutesSample(data) - return - } - - routesContent := string(content) - - // Build import path - var importPath, importAlias string - if data.Category != "" { - importPath = fmt.Sprintf("%s/internal/handlers", data.ModuleName) - importAlias = data.NameLower + "Handlers" - } else { - importPath = fmt.Sprintf("%s/internal/handlers", data.ModuleName) - importAlias = data.NameLower + "Handlers" - } - - // Add import - importPattern := fmt.Sprintf("%s \"%s\"", importAlias, importPath) - if !strings.Contains(routesContent, importPattern) { - importToAdd := fmt.Sprintf("\t%s \"%s\"", importAlias, importPath) - if strings.Contains(routesContent, "import (") { - routesContent = strings.Replace(routesContent, "import (", - "import (\n"+importToAdd, 1) - } - } - - // Build new routes in protected group format - newRoutes := generateProtectedRouteBlock(data) - - // Insert above protected routes marker - insertMarker := "// ============= PUBLISHED ROUTES ===============================================" - if strings.Contains(routesContent, insertMarker) { - if !strings.Contains(routesContent, fmt.Sprintf("New%sHandler", data.Name)) { - // Insert before the marker - routesContent = strings.Replace(routesContent, insertMarker, - newRoutes+"\n\t"+insertMarker, 1) - } else { - fmt.Printf("āœ… Routes for %s already exist, skipping...\n", data.Name) - return - } - } else { - // Fallback: insert at end of setupV1Routes function - setupFuncEnd := "\treturn r" - if strings.Contains(routesContent, setupFuncEnd) { - routesContent = strings.Replace(routesContent, setupFuncEnd, - newRoutes+"\n\n\t"+setupFuncEnd, 1) - } - } - - if err := os.WriteFile(routesFile, []byte(routesContent), 0644); err != nil { - fmt.Printf("Error writing routes.go: %v\n", err) - return - } - - fmt.Printf("āœ… Updated routes.go with %s endpoints\n", data.Name) -} - -func generateProtectedRouteBlock(data HandlerData) string { - routes := fmt.Sprintf(` - // %s endpoints - %sHandler := %sHandlers.New%sHandler() - %sGroup := v1.Group("/%s") - { - %sGroup.GET("", %sHandler.Get%s)`, - strings.Title(data.NamePlural), data.NameLower, data.NameLower, data.Name, - data.NameLower, data.NameLower, - data.NameLower, data.NameLower, data.Name) - - if data.HasDynamic { - routes += fmt.Sprintf(` - %sGroup.GET("/dynamic", %sHandler.Get%sDynamic) // Route baru`, - data.NameLower, data.NameLower, data.Name) - } - - if data.HasSearch { - routes += fmt.Sprintf(` - %sGroup.GET("/search", %sHandler.Search%sAdvanced) // Route pencarian`, - data.NameLower, data.NameLower, data.Name) - } - - routes += fmt.Sprintf(` - %sGroup.GET("/:id", %sHandler.Get%sByID)`, - data.NameLower, data.NameLower, data.Name) - - if data.HasPost { - routes += fmt.Sprintf(` - %sGroup.POST("", %sHandler.Create%s)`, - data.NameLower, data.NameLower, data.Name) - } - - if data.HasPut { - routes += fmt.Sprintf(` - %sGroup.PUT("/:id", %sHandler.Update%s)`, - data.NameLower, data.NameLower, data.Name) - } - - if data.HasDelete { - routes += fmt.Sprintf(` - %sGroup.DELETE("/:id", %sHandler.Delete%s)`, - data.NameLower, data.NameLower, data.Name) - } - - if data.HasStats { - routes += fmt.Sprintf(` - %sGroup.GET("/stats", %sHandler.Get%sStats)`, - data.NameLower, data.NameLower, data.Name) - } - - routes += ` - }` - - return routes -} - -func printRoutesSample(data HandlerData) { - fmt.Print(generateProtectedRouteBlock(data)) - fmt.Println() -} - -// ================= UTILITY FUNCTIONS ===================== -func writeFile(filename, content string) { - if err := os.WriteFile(filename, []byte(content), 0644); err != nil { - fmt.Printf("āŒ Error creating file %s: %v\n", filename, err) - return - } - fmt.Printf("āœ… Generated: %s\n", filename) -} diff --git a/tools/generete-handler.go.banckupnew b/tools/generete-handler.go.banckupnew deleted file mode 100644 index e69de29..0000000 diff --git a/vclaimhandlerexemple b/vclaimhandlerexemple deleted file mode 100644 index 652003e..0000000 --- a/vclaimhandlerexemple +++ /dev/null @@ -1,414 +0,0 @@ -// Code generated by generate-dynamic-handler.go; DO NOT EDIT. -// Generated at: 2025-08-29 13:02:57 -// Service: VClaim (vclaim) -// Description: BPJS VClaim service for eligibility and SEP management - -package handlers - -import ( - "context" - "fmt" - "net/http" - "time" - - "api-service/internal/config" - "api-service/internal/models/reference" - services "api-service/internal/services/bpjs" - "api-service/pkg/logger" - - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" - "github.com/google/uuid" -) - -// VClaimHandler handles VClaim BPJS services -type VClaimHandler struct { - service services.VClaimService - validator *validator.Validate - logger logger.Logger - config config.BpjsConfig -} - -// VClaimHandlerConfig contains configuration for VClaimHandler -type VClaimHandlerConfig struct { - BpjsConfig config.BpjsConfig - Logger logger.Logger - Validator *validator.Validate -} - -// NewVClaimHandler creates a new VClaimHandler -func NewVClaimHandler(cfg VClaimHandlerConfig) *VClaimHandler { - return &VClaimHandler{ - service: services.NewService(cfg.BpjsConfig), - validator: cfg.Validator, - logger: cfg.Logger, - config: cfg.BpjsConfig, - } -} - -// GetPESERTA retrieves Peserta data -// @Summary Get Peserta data -// @Description Get participant eligibility information -// @Tags vclaim,peserta -// @Accept json -// @Produce json -// @Param nokartu path string true "nokartu" -// @Success 200 {object} reference.PesertaResponse -// @Failure 400 {object} reference.ErrorResponse -// @Failure 500 {object} reference.ErrorResponse -// @Router /peserta/:nokartu [get] - -func (h *VClaimHandler) GetPESERTA(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 GetPeserta 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, reference.ErrorResponse{ - Status: "error", - Message: "Missing required parameter nokartu", - RequestID: requestID, - }) - return - } - - // Call service method - var response reference.PesertaResponse - - result, err := h.GetPeserta(ctx, nokartu) - if err != nil { - h.logger.Error("Failed to get Peserta", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Assign result to response - response.Status = "success" - response.RequestID = requestID - response.Data = result - c.JSON(http.StatusOK, response) -} - -// GetSEP retrieves Sep data - -// @Summary Get Sep data -// @Description Manage SEP (Surat Eligibilitas Peserta) -// @Tags vclaim,sep -// @Accept json -// @Produce json -// @Param nosep path string true "nosep" -// @Success 200 {object} reference.SEPResponse -// @Failure 400 {object} reference.ErrorResponse -// @Failure 500 {object} reference.ErrorResponse -// @Router /sep/:nosep [get] - -func (h *VClaimHandler) GetSEPHandler(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 GetSep request", map[string]interface{}{ - "request_id": requestID, - "endpoint": "/sep/:nosep", - - "nosep": c.Param("nosep"), - }) - - // Extract path parameters - - nosep := c.Param("nosep") - if nosep == "" { - - h.logger.Error("Missing required parameter nosep", map[string]interface{}{ - "request_id": requestID, - }) - - c.JSON(http.StatusBadRequest, reference.ErrorResponse{ - Status: "error", - Message: "Missing required parameter nosep", - RequestID: requestID, - }) - return - } - - // Call service method - var response reference.SEPResponse - - result, err := h.GetSEP(ctx, nosep) - if err != nil { - h.logger.Error("Failed to get Sep", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Assign result to response - response.Status = "success" - response.RequestID = requestID - response.Data = result - c.JSON(http.StatusOK, response) -} - -// CreateSEP creates new Sep - -// @Summary Create Sep -// @Description Manage SEP (Surat Eligibilitas Peserta) -// @Tags vclaim,sep -// @Accept json -// @Produce json -// @Param request body reference.SEPRequest true "Sep data" -// @Success 201 {object} reference.SEPResponse -// @Failure 400 {object} reference.ErrorResponse -// @Failure 500 {object} reference.ErrorResponse -// @Router /sep [post] - -func (h *VClaimHandler) CreateSEPHandler(c *gin.Context) { - ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) - defer cancel() - - requestID := c.GetHeader("X-Request-ID") - if requestID == "" { - requestID = uuid.New().String() - c.Header("X-Request-ID", requestID) - } - - h.logger.Info("Processing CreateSep request", map[string]interface{}{ - "request_id": requestID, - }) - - var req reference.SEPRequest - if err := c.ShouldBindJSON(&req); err != nil { - - h.logger.Error("Invalid request body", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusBadRequest, reference.ErrorResponse{ - Status: "error", - Message: "Invalid request body: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Validate request - if err := h.validator.Struct(&req); err != nil { - - h.logger.Error("Validation failed", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusBadRequest, reference.ErrorResponse{ - Status: "error", - Message: "Validation failed: " + err.Error(), - RequestID: requestID, - }) - return - } - - // Call service method - var response reference.SEPResponse - result, err := h.CreateSEP(ctx, &req) - if err != nil { - h.logger.Error("Failed to create Sep", map[string]interface{}{ - "error": err.Error(), - "request_id": requestID, - }) - - c.JSON(http.StatusInternalServerError, reference.ErrorResponse{ - Status: "error", - Message: "Internal server error", - RequestID: requestID, - }) - return - } - - // Assign result to response - response.Status = "success" - response.RequestID = requestID - response.Data = result - c.JSON(http.StatusCreated, response) -} - -// Handler for Helpers - -// GetPeserta implements reference.VClaimService.GetPeserta -func (h *VClaimHandler) GetPeserta(ctx context.Context, noKartu string) (*reference.PesertaData, error) { - var response struct { - MetaData struct { - Code string `json:"code"` - Message string `json:"message"` - } `json:"metaData"` - Response reference.PesertaData `json:"response"` - } - - endpoint := fmt.Sprintf("/Peserta/%s", noKartu) - err := h.service.Get(ctx, endpoint, &response) - if err != nil { - return nil, err - } - - if response.MetaData.Code != "200" { - return nil, fmt.Errorf("BPJS API error: %s - %s", response.MetaData.Code, response.MetaData.Message) - } - - return &response.Response, nil -} - -// GetSEP implements reference.VClaimService.GetSEP -func (h *VClaimHandler) GetSEP(ctx context.Context, noSep string) (*reference.SEPData, error) { - var response struct { - MetaData struct { - Code string `json:"code"` - Message string `json:"message"` - } `json:"metaData"` - Response reference.SEPData `json:"response"` - } - - endpoint := fmt.Sprintf("/SEP/%s", noSep) - err := h.service.Get(ctx, endpoint, &response) - if err != nil { - return nil, err - } - - if response.MetaData.Code != "200" { - return nil, fmt.Errorf("BPJS API error: %s - %s", response.MetaData.Code, response.MetaData.Message) - } - - return &response.Response, nil -} - -// CreateSEP implements reference.VClaimService.CreateSEP -func (h *VClaimHandler) CreateSEP(ctx context.Context, req *reference.SEPRequest) (*reference.SEPData, error) { - var response struct { - MetaData struct { - Code string `json:"code"` - Message string `json:"message"` - } `json:"metaData"` - Response reference.SEPData `json:"response"` - } - - endpoint := "/SEP/2.0/insert" - err := h.service.Post(ctx, endpoint, req, &response) - if err != nil { - return nil, err - } - - if response.MetaData.Code != "200" { - return nil, fmt.Errorf("BPJS API error: %s - %s", response.MetaData.Code, response.MetaData.Message) - } - - return &response.Response, nil -} - -// UpdateSEP implements reference.VClaimService.UpdateSEP -func (h *VClaimHandler) UpdateSEP(ctx context.Context, noSep string, req *reference.SEPRequest) (*reference.SEPData, error) { - var response struct { - MetaData struct { - Code string `json:"code"` - Message string `json:"message"` - } `json:"metaData"` - Response reference.SEPData `json:"response"` - } - - endpoint := fmt.Sprintf("/SEP/%s", noSep) - err := h.service.Put(ctx, endpoint, req, &response) - if err != nil { - return nil, err - } - - if response.MetaData.Code != "200" { - return nil, fmt.Errorf("BPJS API error: %s - %s", response.MetaData.Code, response.MetaData.Message) - } - - return &response.Response, nil -} - -// DeleteSEP implements reference.VClaimService.DeleteSEP -func (h *VClaimHandler) DeleteSEP(ctx context.Context, noSep string) error { - var response struct { - MetaData struct { - Code string `json:"code"` - Message string `json:"message"` - } `json:"metaData"` - } - - endpoint := fmt.Sprintf("/SEP/%s", noSep) - err := h.service.Delete(ctx, endpoint, &response) - if err != nil { - return err - } - - if response.MetaData.Code != "200" { - return fmt.Errorf("BPJS API error: %s - %s", response.MetaData.Code, response.MetaData.Message) - } - - return nil -} - -// GetRujukan implements reference.VClaimService.GetRujukan -func (h *VClaimHandler) GetRujukan(ctx context.Context, noRujukan string) (*reference.RujukanData, error) { - var response struct { - MetaData struct { - Code string `json:"code"` - Message string `json:"message"` - } `json:"metaData"` - Response reference.RujukanData `json:"response"` - } - - endpoint := fmt.Sprintf("/Rujukan/%s", noRujukan) - err := h.service.Get(ctx, endpoint, &response) - if err != nil { - return nil, err - } - - if response.MetaData.Code != "200" { - return nil, fmt.Errorf("BPJS API error: %s - %s", response.MetaData.Code, response.MetaData.Message) - } - - return &response.Response, nil -}