From a14562cfe0178bb2307eb9eaad71e2c2a94dbad0 Mon Sep 17 00:00:00 2001 From: Annisa Rachmadiyanti Date: Mon, 17 Nov 2025 15:04:27 +0700 Subject: [PATCH] Initial commit --- docker-compose.yml | 54 +- docs/docs.go | 1870 +------------------- docs/swagger.json | 1870 +------------------- docs/swagger.yaml | 1260 +------------ examples/clientsocket/client.html | 4 +- internal/config/config.go | 60 +- internal/handlers/retribusi/retribusi.go | 1401 --------------- internal/handlers/websocket/websocket.go | 135 +- internal/models/retribusi/retribusi.go | 228 --- internal/routes/v1/routes.go | 114 +- internal/services/websocket/broadcaster.go | 59 + internal/services/websocket/handlers.go | 10 +- internal/services/websocket/hub.go | 6 +- tools/general/services-config.yaml | 242 ++- 14 files changed, 640 insertions(+), 6673 deletions(-) delete mode 100644 internal/handlers/retribusi/retribusi.go delete mode 100644 internal/models/retribusi/retribusi.go diff --git a/docker-compose.yml b/docker-compose.yml index 4355f7a..0bbd9d7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,25 +1,26 @@ services: # Main Application app: + container_name: websocket-qris build: context: . dockerfile: Dockerfile target: prod restart: unless-stopped ports: - - "8070:8070" + - "8030:8030" environment: # Server Configuration APP_ENV: production - PORT: 8070 + PORT: 8030 GIN_MODE: release # Default Database Configuration (PostgreSQL) DB_CONNECTION: postgres - DB_USERNAME: stim - DB_PASSWORD: stim*RS54 - DB_HOST: 10.10.123.165 - DB_DATABASE: satu_db + DB_USERNAME: simtest + DB_PASSWORD: 12345 + DB_HOST: 10.10.123.223 + DB_DATABASE: simrsbackup DB_PORT: 5432 DB_SSLMODE: disable @@ -61,30 +62,30 @@ services: # MYSQL_MEDICAL_SSLMODE: disable # Keycloak Configuration - KEYCLOAK_ISSUER: https://auth.rssa.top/realms/sandbox - KEYCLOAK_AUDIENCE: nuxtsim-pendaftaran - KEYCLOAK_JWKS_URL: https://auth.rssa.top/realms/sandbox/protocol/openid-connect/certs - KEYCLOAK_ENABLED: "true" + # KEYCLOAK_ISSUER: https://auth.rssa.top/realms/sandbox + # KEYCLOAK_AUDIENCE: nuxtsim-pendaftaran + # KEYCLOAK_JWKS_URL: https://auth.rssa.top/realms/sandbox/protocol/openid-connect/certs + # KEYCLOAK_ENABLED: "true" # BPJS Configuration - BPJS_BASEURL: https://apijkn.bpjs-kesehatan.go.id/vclaim-rest - BPJS_CONSID: 5257 - BPJS_USERKEY: 4cf1cbef8c008440bbe9ef9ba789e482 - BPJS_SECRETKEY: 1bV363512D + # BPJS_BASEURL: https://apijkn.bpjs-kesehatan.go.id/vclaim-rest + # BPJS_CONSID: 5257 + # BPJS_USERKEY: 4cf1cbef8c008440bbe9ef9ba789e482 + # BPJS_SECRETKEY: 1bV363512D # SatuSehat Configuration - BRIDGING_SATUSEHAT_ORG_ID: 100026555 - BRIDGING_SATUSEHAT_FASYAKES_ID: 3573011 - BRIDGING_SATUSEHAT_CLIENT_ID: l1ZgJGW6K5pnrqGUikWM7fgIoquA2AQ5UUG0U8WqHaq2VEyZ - BRIDGING_SATUSEHAT_CLIENT_SECRET: Al3PTYAW6axPiAFwaFlpn8qShLFW5YGMgG8w1qhexgCc7lGTEjjcR6zxa06ThPDy - BRIDGING_SATUSEHAT_AUTH_URL: https://api-satusehat.kemkes.go.id/oauth2/v1 - BRIDGING_SATUSEHAT_BASE_URL: https://api-satusehat.kemkes.go.id/fhir-r4/v1 - BRIDGING_SATUSEHAT_CONSENT_URL: https://api-satusehat.dto.kemkes.go.id/consent/v1 - BRIDGING_SATUSEHAT_KFA_URL: https://api-satusehat.kemkes.go.id/kfa-v2 + # BRIDGING_SATUSEHAT_ORG_ID: 100026555 + # BRIDGING_SATUSEHAT_FASYAKES_ID: 3573011 + # BRIDGING_SATUSEHAT_CLIENT_ID: l1ZgJGW6K5pnrqGUikWM7fgIoquA2AQ5UUG0U8WqHaq2VEyZ + # BRIDGING_SATUSEHAT_CLIENT_SECRET: Al3PTYAW6axPiAFwaFlpn8qShLFW5YGMgG8w1qhexgCc7lGTEjjcR6zxa06ThPDy + # BRIDGING_SATUSEHAT_AUTH_URL: https://api-satusehat.kemkes.go.id/oauth2/v1 + # BRIDGING_SATUSEHAT_BASE_URL: https://api-satusehat.kemkes.go.id/fhir-r4/v1 + # BRIDGING_SATUSEHAT_CONSENT_URL: https://api-satusehat.dto.kemkes.go.id/consent/v1 + # BRIDGING_SATUSEHAT_KFA_URL: https://api-satusehat.kemkes.go.id/kfa-v2 # Swagger Configuration - SWAGGER_TITLE: My Custom API Service - SWAGGER_DESCRIPTION: This is a custom API service for managing various resources + SWAGGER_TITLE: API Service QRIS + SWAGGER_DESCRIPTION: Documentation SWAGGER API Service QRIS SWAGGER_VERSION: 2.0.0 SWAGGER_CONTACT_NAME: Support Team SWAGGER_HOST: api.mycompany.com:8080 @@ -92,9 +93,10 @@ services: SWAGGER_SCHEMES: https # API Configuration - API_TITLE: API Service UJICOBA - API_DESCRIPTION: Dokumentation SWAGGER + API_TITLE: API Service UJICOBA QRIS + API_DESCRIPTION: Documentation SWAGGER API_VERSION: 3.0.0 + # WebSocket Configuration WS_READ_TIMEOUT: 300s WS_WRITE_TIMEOUT: 30s diff --git a/docs/docs.go b/docs/docs.go index 12f27b3..dac397d 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -24,542 +24,6 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { - "/Peserta/nik/:nik": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get participant eligibility information by NIK", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Peserta" - ], - "summary": "Get Bynik 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 Bynik data", - "schema": { - "$ref": "#/definitions/peserta.PesertaResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Bynik not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - } - }, - "/Peserta/nokartu/:nokartu": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get participant eligibility information by card number", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Peserta" - ], - "summary": "Get Bynokartu 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 Bynokartu data", - "schema": { - "$ref": "#/definitions/peserta.PesertaResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Bynokartu not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - } - }, - "/Rujukan/:norujukan": { - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Update existing Rujukan in BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Update existing Rujukan", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "Rujukan update data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/rujukan.RujukanRequest" - } - } - ], - "responses": { - "200": { - "description": "Successfully updated Rujukan", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Rujukan not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict - update conflict occurred", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - }, - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Create new Rujukan in BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Create new Rujukan", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "Rujukan data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/rujukan.RujukanRequest" - } - } - ], - "responses": { - "201": { - "description": "Successfully created Rujukan", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - }, - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete existing Rujukan from BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Delete existing Rujukan", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - } - ], - "responses": { - "200": { - "description": "Successfully deleted Rujukan", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Rujukan not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - } - }, - "/Rujukanbalik/:norujukan": { - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Update existing Rujukanbalik in BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Update existing Rujukanbalik", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "Rujukanbalik update data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/rujukan.RujukanRequest" - } - } - ], - "responses": { - "200": { - "description": "Successfully updated Rujukanbalik", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Rujukanbalik not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict - update conflict occurred", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - }, - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Create new Rujukanbalik in BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Create new Rujukanbalik", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "Rujukanbalik data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/rujukan.RujukanRequest" - } - } - ], - "responses": { - "201": { - "description": "Successfully created Rujukanbalik", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - }, - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete existing Rujukanbalik from BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Delete existing Rujukanbalik", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - } - ], - "responses": { - "200": { - "description": "Successfully deleted Rujukanbalik", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Rujukanbalik not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - } - }, "/api/v1/auth/login": { "post": { "description": "Authenticate user with username and password to receive JWT token", @@ -580,7 +44,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.LoginRequest" + "$ref": "#/definitions/api-service_internal_models_auth.LoginRequest" } } ], @@ -588,7 +52,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.TokenResponse" + "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" } }, "400": { @@ -631,7 +95,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.User" + "$ref": "#/definitions/api-service_internal_models_auth.User" } }, "401": { @@ -677,7 +141,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.TokenResponse" + "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" } }, "400": { @@ -750,393 +214,6 @@ const docTemplate = `{ } } }, - "/api/v1/retribusi/{id}": { - "get": { - "description": "Returns a single retribusi by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Retribusi" - ], - "summary": "Get Retribusi by ID", - "parameters": [ - { - "type": "string", - "description": "Retribusi ID (UUID)", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Success response", - "schema": { - "$ref": "#/definitions/retribusi.RetribusiGetByIDResponse" - } - }, - "400": { - "description": "Invalid ID format", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "404": { - "description": "Retribusi not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - } - } - }, - "put": { - "description": "Updates an existing retribusi record", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Retribusi" - ], - "summary": "Update retribusi", - "parameters": [ - { - "type": "string", - "description": "Retribusi ID (UUID)", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Retribusi update request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/retribusi.RetribusiUpdateRequest" - } - } - ], - "responses": { - "200": { - "description": "Retribusi updated successfully", - "schema": { - "$ref": "#/definitions/retribusi.RetribusiUpdateResponse" - } - }, - "400": { - "description": "Bad request or validation error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "404": { - "description": "Retribusi not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - } - } - }, - "delete": { - "description": "Soft deletes a retribusi by setting status to 'deleted'", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Retribusi" - ], - "summary": "Delete retribusi", - "parameters": [ - { - "type": "string", - "description": "Retribusi ID (UUID)", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Retribusi deleted successfully", - "schema": { - "$ref": "#/definitions/retribusi.RetribusiDeleteResponse" - } - }, - "400": { - "description": "Invalid ID format", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "404": { - "description": "Retribusi not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - } - } - } - }, - "/api/v1/retribusis": { - "get": { - "description": "Returns a paginated list of retribusis with optional summary statistics", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Retribusi" - ], - "summary": "Get retribusi with pagination and optional aggregation", - "parameters": [ - { - "type": "integer", - "default": 10, - "description": "Limit (max 100)", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "default": 0, - "description": "Offset", - "name": "offset", - "in": "query" - }, - { - "type": "boolean", - "default": false, - "description": "Include aggregation summary", - "name": "include_summary", - "in": "query" - }, - { - "type": "string", - "description": "Filter by status", - "name": "status", - "in": "query" - }, - { - "type": "string", - "description": "Filter by jenis", - "name": "jenis", - "in": "query" - }, - { - "type": "string", - "description": "Filter by dinas", - "name": "dinas", - "in": "query" - }, - { - "type": "string", - "description": "Search in multiple fields", - "name": "search", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Success response", - "schema": { - "$ref": "#/definitions/retribusi.RetribusiGetResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - } - } - }, - "post": { - "description": "Creates a new retribusi record", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Retribusi" - ], - "summary": "Create retribusi", - "parameters": [ - { - "description": "Retribusi creation request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/retribusi.RetribusiCreateRequest" - } - } - ], - "responses": { - "201": { - "description": "Retribusi created successfully", - "schema": { - "$ref": "#/definitions/retribusi.RetribusiCreateResponse" - } - }, - "400": { - "description": "Bad request or validation error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - } - } - } - }, - "/api/v1/retribusis/dynamic": { - "get": { - "description": "Returns retribusis with advanced dynamic filtering like Directus", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Retribusi" - ], - "summary": "Get retribusi with dynamic filtering", - "parameters": [ - { - "type": "string", - "description": "Fields to select (e.g., fields=*.*)", - "name": "fields", - "in": "query" - }, - { - "type": "string", - "description": "Dynamic filters (e.g., filter[Jenis][_eq]=value)", - "name": "filter[column][operator]", - "in": "query" - }, - { - "type": "string", - "description": "Sort fields (e.g., sort=date_created,-Jenis)", - "name": "sort", - "in": "query" - }, - { - "type": "integer", - "default": 10, - "description": "Limit", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "default": 0, - "description": "Offset", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Success response", - "schema": { - "$ref": "#/definitions/retribusi.RetribusiGetResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - } - } - } - }, - "/api/v1/retribusis/stats": { - "get": { - "description": "Returns comprehensive statistics about retribusi data", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Retribusi" - ], - "summary": "Get retribusi statistics", - "parameters": [ - { - "type": "string", - "description": "Filter statistics by status", - "name": "status", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Statistics data", - "schema": { - "$ref": "#/definitions/models.AggregateData" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - } - } - } - }, "/api/v1/token/generate": { "post": { "description": "Generate a JWT token for a user", @@ -1157,7 +234,7 @@ const docTemplate = `{ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.LoginRequest" + "$ref": "#/definitions/api-service_internal_models_auth.LoginRequest" } } ], @@ -1165,7 +242,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.TokenResponse" + "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" } }, "400": { @@ -1220,7 +297,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.TokenResponse" + "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" } }, "400": { @@ -1235,14 +312,9 @@ const docTemplate = `{ } } }, - "/bynokartu/:nokartu": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get rujukan by card number", + "/api/v1/ws/broadcast/check": { + "post": { + "description": "Creates and broadcasts a WebSocket message with the specified type and data", "consumes": [ "application/json" ], @@ -1250,67 +322,50 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Rujukan" + "WebSocket QRIS" ], - "summary": "Get Bynokartu data", + "summary": "Broadcast a WebSocket message", "parameters": [ { "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "type": "string", - "example": "\"example_value\"", - "description": "nokartu", - "name": "nokartu", + "description": "Type of the message to broadcast", + "name": "messageType", "in": "path", "required": true + }, + { + "description": "Data payload for the message", + "name": "data", + "in": "body", + "required": true, + "schema": {} } ], "responses": { "200": { - "description": "Successfully retrieved Bynokartu data", + "description": "Message successfully queued for broadcast", "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Bynokartu not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "type": "object", + "additionalProperties": { + "type": "string" + } } }, "500": { - "description": "Internal server error", + "description": "Failed to queue message (queue full)", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } } }, - "/bynorujukan/:norujukan": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get rujukan by nomor rujukan", + "/api/v1/ws/broadcast/qris": { + "post": { + "description": "Creates and broadcasts a WebSocket message with the specified type and data for QRIS operations", "consumes": [ "application/json" ], @@ -1318,54 +373,42 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Rujukan" + "WebSocket QRIS" ], - "summary": "Get Bynorujukan data", + "summary": "Broadcast a QRIS-related WebSocket message", "parameters": [ { "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "type": "string", - "example": "\"example_value\"", - "description": "norujukan", - "name": "norujukan", + "description": "Type of the QRIS message to broadcast", + "name": "messageType", "in": "path", "required": true + }, + { + "description": "QRIS data payload for the message", + "name": "data", + "in": "body", + "required": true, + "schema": {} } ], "responses": { "200": { - "description": "Successfully retrieved Bynorujukan data", + "description": "QRIS message successfully queued for broadcast", "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Bynorujukan not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "type": "object", + "additionalProperties": { + "type": "string" + } } }, "500": { - "description": "Internal server error", + "description": "Failed to queue QRIS message (queue full)", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -1373,86 +416,7 @@ const docTemplate = `{ } }, "definitions": { - "models.AggregateData": { - "type": "object", - "properties": { - "by_dinas": { - "type": "object", - "additionalProperties": { - "type": "integer" - } - }, - "by_jenis": { - "type": "object", - "additionalProperties": { - "type": "integer" - } - }, - "by_status": { - "type": "object", - "additionalProperties": { - "type": "integer" - } - }, - "created_today": { - "type": "integer" - }, - "last_updated": { - "type": "string" - }, - "total_active": { - "type": "integer" - }, - "total_draft": { - "type": "integer" - }, - "total_inactive": { - "type": "integer" - }, - "updated_today": { - "type": "integer" - } - } - }, - "models.ErrorResponse": { - "type": "object", - "properties": { - "code": { - "type": "integer" - }, - "error": { - "type": "string" - }, - "message": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - } - }, - "models.ErrorResponseBpjs": { - "type": "object", - "properties": { - "code": { - "type": "string" - }, - "errors": { - "type": "object", - "additionalProperties": true - }, - "message": { - "type": "string" - }, - "request_id": { - "type": "string" - }, - "status": { - "type": "string" - } - } - }, - "models.LoginRequest": { + "api-service_internal_models_auth.LoginRequest": { "type": "object", "required": [ "password", @@ -1467,66 +431,7 @@ const docTemplate = `{ } } }, - "models.MetaResponse": { - "type": "object", - "properties": { - "current_page": { - "type": "integer" - }, - "has_next": { - "type": "boolean" - }, - "has_prev": { - "type": "boolean" - }, - "limit": { - "type": "integer" - }, - "offset": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "total_pages": { - "type": "integer" - } - } - }, - "models.NullableInt32": { - "type": "object", - "properties": { - "int32": { - "type": "integer" - }, - "valid": { - "type": "boolean" - } - } - }, - "models.NullableString": { - "type": "object", - "properties": { - "string": { - "type": "string" - }, - "valid": { - "type": "boolean" - } - } - }, - "models.NullableTime": { - "type": "object", - "properties": { - "time": { - "type": "string" - }, - "valid": { - "type": "boolean" - } - } - }, - "models.TokenResponse": { + "api-service_internal_models_auth.TokenResponse": { "type": "object", "properties": { "access_token": { @@ -1540,7 +445,7 @@ const docTemplate = `{ } } }, - "models.User": { + "api-service_internal_models_auth.User": { "type": "object", "properties": { "email": { @@ -1556,665 +461,6 @@ const docTemplate = `{ "type": "string" } } - }, - "peserta.PesertaData": { - "type": "object", - "properties": { - "cob": { - "type": "object", - "properties": { - "nmAsuransi": {}, - "noAsuransi": {}, - "tglTAT": {}, - "tglTMT": {} - } - }, - "hakKelas": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "informasi": { - "type": "object", - "properties": { - "dinsos": {}, - "eSEP": {}, - "noSKTM": {}, - "prolanisPRB": { - "type": "string" - } - } - }, - "jenisPeserta": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "mr": { - "type": "object", - "properties": { - "noMR": { - "type": "string" - }, - "noTelepon": { - "type": "string" - } - } - }, - "nama": { - "type": "string" - }, - "nik": { - "type": "string" - }, - "noKartu": { - "type": "string" - }, - "pisa": { - "type": "string" - }, - "provUmum": { - "type": "object", - "properties": { - "kdProvider": { - "type": "string" - }, - "nmProvider": { - "type": "string" - } - } - }, - "raw_response": { - "type": "string" - }, - "sex": { - "type": "string" - }, - "statusPeserta": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "tglCetakKartu": { - "type": "string" - }, - "tglLahir": { - "type": "string" - }, - "tglTAT": { - "type": "string" - }, - "tglTMT": { - "type": "string" - }, - "umur": { - "type": "object", - "properties": { - "umurSaatPelayanan": { - "type": "string" - }, - "umurSekarang": { - "type": "string" - } - } - } - } - }, - "peserta.PesertaResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/peserta.PesertaData" - }, - "message": { - "type": "string" - }, - "metaData": {}, - "request_id": { - "type": "string" - }, - "status": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - } - }, - "retribusi.Retribusi": { - "type": "object", - "properties": { - "date_created": { - "$ref": "#/definitions/models.NullableTime" - }, - "date_updated": { - "$ref": "#/definitions/models.NullableTime" - }, - "dinas": { - "$ref": "#/definitions/models.NullableString" - }, - "id": { - "type": "string" - }, - "jenis": { - "$ref": "#/definitions/models.NullableString" - }, - "kelompok_obyek": { - "$ref": "#/definitions/models.NullableString" - }, - "kode_tarif": { - "$ref": "#/definitions/models.NullableString" - }, - "pelayanan": { - "$ref": "#/definitions/models.NullableString" - }, - "rekening_denda": { - "$ref": "#/definitions/models.NullableString" - }, - "rekening_pokok": { - "$ref": "#/definitions/models.NullableString" - }, - "satuan": { - "$ref": "#/definitions/models.NullableString" - }, - "satuan_overtime": { - "$ref": "#/definitions/models.NullableString" - }, - "sort": { - "$ref": "#/definitions/models.NullableInt32" - }, - "status": { - "type": "string" - }, - "tarif": { - "$ref": "#/definitions/models.NullableString" - }, - "tarif_overtime": { - "$ref": "#/definitions/models.NullableString" - }, - "uraian_1": { - "$ref": "#/definitions/models.NullableString" - }, - "uraian_2": { - "$ref": "#/definitions/models.NullableString" - }, - "uraian_3": { - "$ref": "#/definitions/models.NullableString" - }, - "user_created": { - "$ref": "#/definitions/models.NullableString" - }, - "user_updated": { - "$ref": "#/definitions/models.NullableString" - } - } - }, - "retribusi.RetribusiCreateRequest": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "dinas": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "jenis": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kelompok_obyek": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kode_tarif": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "pelayanan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_denda": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_pokok": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan_overtime": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "status": { - "type": "string", - "enum": [ - "draft", - "active", - "inactive" - ] - }, - "tarif": { - "type": "string" - }, - "tarif_overtime": { - "type": "string" - }, - "uraian_1": { - "type": "string" - }, - "uraian_2": { - "type": "string" - }, - "uraian_3": { - "type": "string" - } - } - }, - "retribusi.RetribusiCreateResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "retribusi.RetribusiDeleteResponse": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "message": { - "type": "string" - } - } - }, - "retribusi.RetribusiGetByIDResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "retribusi.RetribusiGetResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/retribusi.Retribusi" - } - }, - "message": { - "type": "string" - }, - "meta": { - "$ref": "#/definitions/models.MetaResponse" - }, - "summary": { - "$ref": "#/definitions/models.AggregateData" - } - } - }, - "retribusi.RetribusiUpdateRequest": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "dinas": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "jenis": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kelompok_obyek": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kode_tarif": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "pelayanan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_denda": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_pokok": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan_overtime": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "status": { - "type": "string", - "enum": [ - "draft", - "active", - "inactive" - ] - }, - "tarif": { - "type": "string" - }, - "tarif_overtime": { - "type": "string" - }, - "uraian_1": { - "type": "string" - }, - "uraian_2": { - "type": "string" - }, - "uraian_3": { - "type": "string" - } - } - }, - "retribusi.RetribusiUpdateResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "rujukan.DataPeserta": { - "type": "object", - "properties": { - "cob": { - "type": "object", - "properties": { - "nmAsuransi": {}, - "noAsuransi": {}, - "tglTAT": {}, - "tglTMT": {} - } - }, - "hakKelas": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "informasi": { - "type": "object", - "properties": { - "dinsos": {}, - "noSKTM": {}, - "prolanisPRB": {} - } - }, - "jenisPeserta": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "mr": { - "type": "object", - "properties": { - "noMR": { - "type": "string" - }, - "noTelepon": {} - } - }, - "nama": { - "type": "string" - }, - "nik": { - "type": "string" - }, - "noKartu": { - "type": "string" - }, - "pisa": { - "type": "string" - }, - "provUmum": { - "type": "object", - "properties": { - "kdProvider": { - "type": "string" - }, - "nmProvider": { - "type": "string" - } - } - }, - "sex": { - "type": "string" - }, - "statusPeserta": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "tglCetakKartu": { - "type": "string" - }, - "tglLahir": { - "type": "string" - }, - "tglTAT": { - "type": "string" - }, - "tglTMT": { - "type": "string" - }, - "umur": { - "type": "object", - "properties": { - "umurSaatPelayanan": { - "type": "string" - }, - "umurSekarang": { - "type": "string" - } - } - } - } - }, - "rujukan.DiagnosaData": { - "type": "object", - "properties": { - "kode": { - "type": "string" - }, - "nama": { - "type": "string" - } - } - }, - "rujukan.PelayananData": { - "type": "object", - "properties": { - "kode": { - "type": "string" - }, - "nama": { - "type": "string" - } - } - }, - "rujukan.PoliRujukanData": { - "type": "object", - "properties": { - "kode": { - "type": "string" - }, - "nama": { - "type": "string" - } - } - }, - "rujukan.ProvPerujukData": { - "type": "object", - "properties": { - "kode": { - "type": "string" - }, - "nama": { - "type": "string" - } - } - }, - "rujukan.RujukanData": { - "type": "object", - "properties": { - "diagnosa": { - "$ref": "#/definitions/rujukan.DiagnosaData" - }, - "keluhan": { - "type": "string" - }, - "noKunjungan": { - "type": "string" - }, - "pelayanan": { - "$ref": "#/definitions/rujukan.PelayananData" - }, - "peserta": { - "$ref": "#/definitions/rujukan.DataPeserta" - }, - "poliRujukan": { - "$ref": "#/definitions/rujukan.PoliRujukanData" - }, - "provPerujuk": { - "$ref": "#/definitions/rujukan.ProvPerujukData" - }, - "tglKunjungan": { - "type": "string" - } - } - }, - "rujukan.RujukanRequest": { - "type": "object", - "required": [ - "noRujukan" - ], - "properties": { - "noKartu": { - "type": "string" - }, - "noRujukan": { - "type": "string" - }, - "request_id": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - } - }, - "rujukan.RujukanResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/rujukan.RujukanData" - }, - "list": { - "type": "array", - "items": { - "$ref": "#/definitions/rujukan.RujukanData" - } - }, - "message": { - "type": "string" - }, - "metaData": {}, - "request_id": { - "type": "string" - }, - "status": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - } } } }` diff --git a/docs/swagger.json b/docs/swagger.json index 47b3b1c..dda1125 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -22,542 +22,6 @@ "host": "localhost:8080", "basePath": "/api/v1", "paths": { - "/Peserta/nik/:nik": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get participant eligibility information by NIK", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Peserta" - ], - "summary": "Get Bynik 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 Bynik data", - "schema": { - "$ref": "#/definitions/peserta.PesertaResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Bynik not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - } - }, - "/Peserta/nokartu/:nokartu": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get participant eligibility information by card number", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Peserta" - ], - "summary": "Get Bynokartu 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 Bynokartu data", - "schema": { - "$ref": "#/definitions/peserta.PesertaResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Bynokartu not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - } - }, - "/Rujukan/:norujukan": { - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Update existing Rujukan in BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Update existing Rujukan", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "Rujukan update data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/rujukan.RujukanRequest" - } - } - ], - "responses": { - "200": { - "description": "Successfully updated Rujukan", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Rujukan not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict - update conflict occurred", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - }, - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Create new Rujukan in BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Create new Rujukan", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "Rujukan data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/rujukan.RujukanRequest" - } - } - ], - "responses": { - "201": { - "description": "Successfully created Rujukan", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - }, - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete existing Rujukan from BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Delete existing Rujukan", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - } - ], - "responses": { - "200": { - "description": "Successfully deleted Rujukan", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Rujukan not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - } - }, - "/Rujukanbalik/:norujukan": { - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Update existing Rujukanbalik in BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Update existing Rujukanbalik", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "Rujukanbalik update data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/rujukan.RujukanRequest" - } - } - ], - "responses": { - "200": { - "description": "Successfully updated Rujukanbalik", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Rujukanbalik not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict - update conflict occurred", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - }, - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Create new Rujukanbalik in BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Create new Rujukanbalik", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "description": "Rujukanbalik data", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/rujukan.RujukanRequest" - } - } - ], - "responses": { - "201": { - "description": "Successfully created Rujukanbalik", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "409": { - "description": "Conflict", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - }, - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete existing Rujukanbalik from BPJS system", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Rujukan" - ], - "summary": "Delete existing Rujukanbalik", - "parameters": [ - { - "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - } - ], - "responses": { - "200": { - "description": "Successfully deleted Rujukanbalik", - "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Rujukanbalik not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - } - } - } - }, "/api/v1/auth/login": { "post": { "description": "Authenticate user with username and password to receive JWT token", @@ -578,7 +42,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.LoginRequest" + "$ref": "#/definitions/api-service_internal_models_auth.LoginRequest" } } ], @@ -586,7 +50,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.TokenResponse" + "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" } }, "400": { @@ -629,7 +93,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.User" + "$ref": "#/definitions/api-service_internal_models_auth.User" } }, "401": { @@ -675,7 +139,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.TokenResponse" + "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" } }, "400": { @@ -748,393 +212,6 @@ } } }, - "/api/v1/retribusi/{id}": { - "get": { - "description": "Returns a single retribusi by ID", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Retribusi" - ], - "summary": "Get Retribusi by ID", - "parameters": [ - { - "type": "string", - "description": "Retribusi ID (UUID)", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Success response", - "schema": { - "$ref": "#/definitions/retribusi.RetribusiGetByIDResponse" - } - }, - "400": { - "description": "Invalid ID format", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "404": { - "description": "Retribusi not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - } - } - }, - "put": { - "description": "Updates an existing retribusi record", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Retribusi" - ], - "summary": "Update retribusi", - "parameters": [ - { - "type": "string", - "description": "Retribusi ID (UUID)", - "name": "id", - "in": "path", - "required": true - }, - { - "description": "Retribusi update request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/retribusi.RetribusiUpdateRequest" - } - } - ], - "responses": { - "200": { - "description": "Retribusi updated successfully", - "schema": { - "$ref": "#/definitions/retribusi.RetribusiUpdateResponse" - } - }, - "400": { - "description": "Bad request or validation error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "404": { - "description": "Retribusi not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - } - } - }, - "delete": { - "description": "Soft deletes a retribusi by setting status to 'deleted'", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Retribusi" - ], - "summary": "Delete retribusi", - "parameters": [ - { - "type": "string", - "description": "Retribusi ID (UUID)", - "name": "id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Retribusi deleted successfully", - "schema": { - "$ref": "#/definitions/retribusi.RetribusiDeleteResponse" - } - }, - "400": { - "description": "Invalid ID format", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "404": { - "description": "Retribusi not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - } - } - } - }, - "/api/v1/retribusis": { - "get": { - "description": "Returns a paginated list of retribusis with optional summary statistics", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Retribusi" - ], - "summary": "Get retribusi with pagination and optional aggregation", - "parameters": [ - { - "type": "integer", - "default": 10, - "description": "Limit (max 100)", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "default": 0, - "description": "Offset", - "name": "offset", - "in": "query" - }, - { - "type": "boolean", - "default": false, - "description": "Include aggregation summary", - "name": "include_summary", - "in": "query" - }, - { - "type": "string", - "description": "Filter by status", - "name": "status", - "in": "query" - }, - { - "type": "string", - "description": "Filter by jenis", - "name": "jenis", - "in": "query" - }, - { - "type": "string", - "description": "Filter by dinas", - "name": "dinas", - "in": "query" - }, - { - "type": "string", - "description": "Search in multiple fields", - "name": "search", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Success response", - "schema": { - "$ref": "#/definitions/retribusi.RetribusiGetResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - } - } - }, - "post": { - "description": "Creates a new retribusi record", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Retribusi" - ], - "summary": "Create retribusi", - "parameters": [ - { - "description": "Retribusi creation request", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/retribusi.RetribusiCreateRequest" - } - } - ], - "responses": { - "201": { - "description": "Retribusi created successfully", - "schema": { - "$ref": "#/definitions/retribusi.RetribusiCreateResponse" - } - }, - "400": { - "description": "Bad request or validation error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - } - } - } - }, - "/api/v1/retribusis/dynamic": { - "get": { - "description": "Returns retribusis with advanced dynamic filtering like Directus", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Retribusi" - ], - "summary": "Get retribusi with dynamic filtering", - "parameters": [ - { - "type": "string", - "description": "Fields to select (e.g., fields=*.*)", - "name": "fields", - "in": "query" - }, - { - "type": "string", - "description": "Dynamic filters (e.g., filter[Jenis][_eq]=value)", - "name": "filter[column][operator]", - "in": "query" - }, - { - "type": "string", - "description": "Sort fields (e.g., sort=date_created,-Jenis)", - "name": "sort", - "in": "query" - }, - { - "type": "integer", - "default": 10, - "description": "Limit", - "name": "limit", - "in": "query" - }, - { - "type": "integer", - "default": 0, - "description": "Offset", - "name": "offset", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Success response", - "schema": { - "$ref": "#/definitions/retribusi.RetribusiGetResponse" - } - }, - "400": { - "description": "Bad request", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - } - } - } - }, - "/api/v1/retribusis/stats": { - "get": { - "description": "Returns comprehensive statistics about retribusi data", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Retribusi" - ], - "summary": "Get retribusi statistics", - "parameters": [ - { - "type": "string", - "description": "Filter statistics by status", - "name": "status", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Statistics data", - "schema": { - "$ref": "#/definitions/models.AggregateData" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "$ref": "#/definitions/models.ErrorResponse" - } - } - } - } - }, "/api/v1/token/generate": { "post": { "description": "Generate a JWT token for a user", @@ -1155,7 +232,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/models.LoginRequest" + "$ref": "#/definitions/api-service_internal_models_auth.LoginRequest" } } ], @@ -1163,7 +240,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.TokenResponse" + "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" } }, "400": { @@ -1218,7 +295,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.TokenResponse" + "$ref": "#/definitions/api-service_internal_models_auth.TokenResponse" } }, "400": { @@ -1233,14 +310,9 @@ } } }, - "/bynokartu/:nokartu": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get rujukan by card number", + "/api/v1/ws/broadcast/check": { + "post": { + "description": "Creates and broadcasts a WebSocket message with the specified type and data", "consumes": [ "application/json" ], @@ -1248,67 +320,50 @@ "application/json" ], "tags": [ - "Rujukan" + "WebSocket QRIS" ], - "summary": "Get Bynokartu data", + "summary": "Broadcast a WebSocket message", "parameters": [ { "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "type": "string", - "example": "\"example_value\"", - "description": "nokartu", - "name": "nokartu", + "description": "Type of the message to broadcast", + "name": "messageType", "in": "path", "required": true + }, + { + "description": "Data payload for the message", + "name": "data", + "in": "body", + "required": true, + "schema": {} } ], "responses": { "200": { - "description": "Successfully retrieved Bynokartu data", + "description": "Message successfully queued for broadcast", "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Bynokartu not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "type": "object", + "additionalProperties": { + "type": "string" + } } }, "500": { - "description": "Internal server error", + "description": "Failed to queue message (queue full)", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } } }, - "/bynorujukan/:norujukan": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get rujukan by nomor rujukan", + "/api/v1/ws/broadcast/qris": { + "post": { + "description": "Creates and broadcasts a WebSocket message with the specified type and data for QRIS operations", "consumes": [ "application/json" ], @@ -1316,54 +371,42 @@ "application/json" ], "tags": [ - "Rujukan" + "WebSocket QRIS" ], - "summary": "Get Bynorujukan data", + "summary": "Broadcast a QRIS-related WebSocket message", "parameters": [ { "type": "string", - "description": "Request ID for tracking", - "name": "X-Request-ID", - "in": "header" - }, - { - "type": "string", - "example": "\"example_value\"", - "description": "norujukan", - "name": "norujukan", + "description": "Type of the QRIS message to broadcast", + "name": "messageType", "in": "path", "required": true + }, + { + "description": "QRIS data payload for the message", + "name": "data", + "in": "body", + "required": true, + "schema": {} } ], "responses": { "200": { - "description": "Successfully retrieved Bynorujukan data", + "description": "QRIS message successfully queued for broadcast", "schema": { - "$ref": "#/definitions/rujukan.RujukanResponse" - } - }, - "400": { - "description": "Bad request - invalid parameters", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "401": { - "description": "Unauthorized - invalid API credentials", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" - } - }, - "404": { - "description": "Not found - Bynorujukan not found", - "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "type": "object", + "additionalProperties": { + "type": "string" + } } }, "500": { - "description": "Internal server error", + "description": "Failed to queue QRIS message (queue full)", "schema": { - "$ref": "#/definitions/models.ErrorResponseBpjs" + "type": "object", + "additionalProperties": { + "type": "string" + } } } } @@ -1371,86 +414,7 @@ } }, "definitions": { - "models.AggregateData": { - "type": "object", - "properties": { - "by_dinas": { - "type": "object", - "additionalProperties": { - "type": "integer" - } - }, - "by_jenis": { - "type": "object", - "additionalProperties": { - "type": "integer" - } - }, - "by_status": { - "type": "object", - "additionalProperties": { - "type": "integer" - } - }, - "created_today": { - "type": "integer" - }, - "last_updated": { - "type": "string" - }, - "total_active": { - "type": "integer" - }, - "total_draft": { - "type": "integer" - }, - "total_inactive": { - "type": "integer" - }, - "updated_today": { - "type": "integer" - } - } - }, - "models.ErrorResponse": { - "type": "object", - "properties": { - "code": { - "type": "integer" - }, - "error": { - "type": "string" - }, - "message": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - } - }, - "models.ErrorResponseBpjs": { - "type": "object", - "properties": { - "code": { - "type": "string" - }, - "errors": { - "type": "object", - "additionalProperties": true - }, - "message": { - "type": "string" - }, - "request_id": { - "type": "string" - }, - "status": { - "type": "string" - } - } - }, - "models.LoginRequest": { + "api-service_internal_models_auth.LoginRequest": { "type": "object", "required": [ "password", @@ -1465,66 +429,7 @@ } } }, - "models.MetaResponse": { - "type": "object", - "properties": { - "current_page": { - "type": "integer" - }, - "has_next": { - "type": "boolean" - }, - "has_prev": { - "type": "boolean" - }, - "limit": { - "type": "integer" - }, - "offset": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "total_pages": { - "type": "integer" - } - } - }, - "models.NullableInt32": { - "type": "object", - "properties": { - "int32": { - "type": "integer" - }, - "valid": { - "type": "boolean" - } - } - }, - "models.NullableString": { - "type": "object", - "properties": { - "string": { - "type": "string" - }, - "valid": { - "type": "boolean" - } - } - }, - "models.NullableTime": { - "type": "object", - "properties": { - "time": { - "type": "string" - }, - "valid": { - "type": "boolean" - } - } - }, - "models.TokenResponse": { + "api-service_internal_models_auth.TokenResponse": { "type": "object", "properties": { "access_token": { @@ -1538,7 +443,7 @@ } } }, - "models.User": { + "api-service_internal_models_auth.User": { "type": "object", "properties": { "email": { @@ -1554,665 +459,6 @@ "type": "string" } } - }, - "peserta.PesertaData": { - "type": "object", - "properties": { - "cob": { - "type": "object", - "properties": { - "nmAsuransi": {}, - "noAsuransi": {}, - "tglTAT": {}, - "tglTMT": {} - } - }, - "hakKelas": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "informasi": { - "type": "object", - "properties": { - "dinsos": {}, - "eSEP": {}, - "noSKTM": {}, - "prolanisPRB": { - "type": "string" - } - } - }, - "jenisPeserta": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "mr": { - "type": "object", - "properties": { - "noMR": { - "type": "string" - }, - "noTelepon": { - "type": "string" - } - } - }, - "nama": { - "type": "string" - }, - "nik": { - "type": "string" - }, - "noKartu": { - "type": "string" - }, - "pisa": { - "type": "string" - }, - "provUmum": { - "type": "object", - "properties": { - "kdProvider": { - "type": "string" - }, - "nmProvider": { - "type": "string" - } - } - }, - "raw_response": { - "type": "string" - }, - "sex": { - "type": "string" - }, - "statusPeserta": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "tglCetakKartu": { - "type": "string" - }, - "tglLahir": { - "type": "string" - }, - "tglTAT": { - "type": "string" - }, - "tglTMT": { - "type": "string" - }, - "umur": { - "type": "object", - "properties": { - "umurSaatPelayanan": { - "type": "string" - }, - "umurSekarang": { - "type": "string" - } - } - } - } - }, - "peserta.PesertaResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/peserta.PesertaData" - }, - "message": { - "type": "string" - }, - "metaData": {}, - "request_id": { - "type": "string" - }, - "status": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - } - }, - "retribusi.Retribusi": { - "type": "object", - "properties": { - "date_created": { - "$ref": "#/definitions/models.NullableTime" - }, - "date_updated": { - "$ref": "#/definitions/models.NullableTime" - }, - "dinas": { - "$ref": "#/definitions/models.NullableString" - }, - "id": { - "type": "string" - }, - "jenis": { - "$ref": "#/definitions/models.NullableString" - }, - "kelompok_obyek": { - "$ref": "#/definitions/models.NullableString" - }, - "kode_tarif": { - "$ref": "#/definitions/models.NullableString" - }, - "pelayanan": { - "$ref": "#/definitions/models.NullableString" - }, - "rekening_denda": { - "$ref": "#/definitions/models.NullableString" - }, - "rekening_pokok": { - "$ref": "#/definitions/models.NullableString" - }, - "satuan": { - "$ref": "#/definitions/models.NullableString" - }, - "satuan_overtime": { - "$ref": "#/definitions/models.NullableString" - }, - "sort": { - "$ref": "#/definitions/models.NullableInt32" - }, - "status": { - "type": "string" - }, - "tarif": { - "$ref": "#/definitions/models.NullableString" - }, - "tarif_overtime": { - "$ref": "#/definitions/models.NullableString" - }, - "uraian_1": { - "$ref": "#/definitions/models.NullableString" - }, - "uraian_2": { - "$ref": "#/definitions/models.NullableString" - }, - "uraian_3": { - "$ref": "#/definitions/models.NullableString" - }, - "user_created": { - "$ref": "#/definitions/models.NullableString" - }, - "user_updated": { - "$ref": "#/definitions/models.NullableString" - } - } - }, - "retribusi.RetribusiCreateRequest": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "dinas": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "jenis": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kelompok_obyek": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kode_tarif": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "pelayanan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_denda": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_pokok": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan_overtime": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "status": { - "type": "string", - "enum": [ - "draft", - "active", - "inactive" - ] - }, - "tarif": { - "type": "string" - }, - "tarif_overtime": { - "type": "string" - }, - "uraian_1": { - "type": "string" - }, - "uraian_2": { - "type": "string" - }, - "uraian_3": { - "type": "string" - } - } - }, - "retribusi.RetribusiCreateResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "retribusi.RetribusiDeleteResponse": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "message": { - "type": "string" - } - } - }, - "retribusi.RetribusiGetByIDResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "retribusi.RetribusiGetResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/retribusi.Retribusi" - } - }, - "message": { - "type": "string" - }, - "meta": { - "$ref": "#/definitions/models.MetaResponse" - }, - "summary": { - "$ref": "#/definitions/models.AggregateData" - } - } - }, - "retribusi.RetribusiUpdateRequest": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "dinas": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "jenis": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kelompok_obyek": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "kode_tarif": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "pelayanan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_denda": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "rekening_pokok": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "satuan_overtime": { - "type": "string", - "maxLength": 255, - "minLength": 1 - }, - "status": { - "type": "string", - "enum": [ - "draft", - "active", - "inactive" - ] - }, - "tarif": { - "type": "string" - }, - "tarif_overtime": { - "type": "string" - }, - "uraian_1": { - "type": "string" - }, - "uraian_2": { - "type": "string" - }, - "uraian_3": { - "type": "string" - } - } - }, - "retribusi.RetribusiUpdateResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/retribusi.Retribusi" - }, - "message": { - "type": "string" - } - } - }, - "rujukan.DataPeserta": { - "type": "object", - "properties": { - "cob": { - "type": "object", - "properties": { - "nmAsuransi": {}, - "noAsuransi": {}, - "tglTAT": {}, - "tglTMT": {} - } - }, - "hakKelas": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "informasi": { - "type": "object", - "properties": { - "dinsos": {}, - "noSKTM": {}, - "prolanisPRB": {} - } - }, - "jenisPeserta": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "mr": { - "type": "object", - "properties": { - "noMR": { - "type": "string" - }, - "noTelepon": {} - } - }, - "nama": { - "type": "string" - }, - "nik": { - "type": "string" - }, - "noKartu": { - "type": "string" - }, - "pisa": { - "type": "string" - }, - "provUmum": { - "type": "object", - "properties": { - "kdProvider": { - "type": "string" - }, - "nmProvider": { - "type": "string" - } - } - }, - "sex": { - "type": "string" - }, - "statusPeserta": { - "type": "object", - "properties": { - "keterangan": { - "type": "string" - }, - "kode": { - "type": "string" - } - } - }, - "tglCetakKartu": { - "type": "string" - }, - "tglLahir": { - "type": "string" - }, - "tglTAT": { - "type": "string" - }, - "tglTMT": { - "type": "string" - }, - "umur": { - "type": "object", - "properties": { - "umurSaatPelayanan": { - "type": "string" - }, - "umurSekarang": { - "type": "string" - } - } - } - } - }, - "rujukan.DiagnosaData": { - "type": "object", - "properties": { - "kode": { - "type": "string" - }, - "nama": { - "type": "string" - } - } - }, - "rujukan.PelayananData": { - "type": "object", - "properties": { - "kode": { - "type": "string" - }, - "nama": { - "type": "string" - } - } - }, - "rujukan.PoliRujukanData": { - "type": "object", - "properties": { - "kode": { - "type": "string" - }, - "nama": { - "type": "string" - } - } - }, - "rujukan.ProvPerujukData": { - "type": "object", - "properties": { - "kode": { - "type": "string" - }, - "nama": { - "type": "string" - } - } - }, - "rujukan.RujukanData": { - "type": "object", - "properties": { - "diagnosa": { - "$ref": "#/definitions/rujukan.DiagnosaData" - }, - "keluhan": { - "type": "string" - }, - "noKunjungan": { - "type": "string" - }, - "pelayanan": { - "$ref": "#/definitions/rujukan.PelayananData" - }, - "peserta": { - "$ref": "#/definitions/rujukan.DataPeserta" - }, - "poliRujukan": { - "$ref": "#/definitions/rujukan.PoliRujukanData" - }, - "provPerujuk": { - "$ref": "#/definitions/rujukan.ProvPerujukData" - }, - "tglKunjungan": { - "type": "string" - } - } - }, - "rujukan.RujukanRequest": { - "type": "object", - "required": [ - "noRujukan" - ], - "properties": { - "noKartu": { - "type": "string" - }, - "noRujukan": { - "type": "string" - }, - "request_id": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - } - }, - "rujukan.RujukanResponse": { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/rujukan.RujukanData" - }, - "list": { - "type": "array", - "items": { - "$ref": "#/definitions/rujukan.RujukanData" - } - }, - "message": { - "type": "string" - }, - "metaData": {}, - "request_id": { - "type": "string" - }, - "status": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - } } } } \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 84640d0..edef21f 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,58 +1,6 @@ basePath: /api/v1 definitions: - models.AggregateData: - properties: - by_dinas: - additionalProperties: - type: integer - type: object - by_jenis: - additionalProperties: - type: integer - type: object - by_status: - additionalProperties: - type: integer - type: object - created_today: - type: integer - last_updated: - type: string - total_active: - type: integer - total_draft: - type: integer - total_inactive: - type: integer - updated_today: - type: integer - type: object - models.ErrorResponse: - properties: - code: - type: integer - error: - type: string - message: - type: string - timestamp: - type: string - type: object - models.ErrorResponseBpjs: - properties: - code: - type: string - errors: - additionalProperties: true - type: object - message: - type: string - request_id: - type: string - status: - type: string - type: object - models.LoginRequest: + api-service_internal_models_auth.LoginRequest: properties: password: type: string @@ -62,45 +10,7 @@ definitions: - password - username type: object - models.MetaResponse: - properties: - current_page: - type: integer - has_next: - type: boolean - has_prev: - type: boolean - limit: - type: integer - offset: - type: integer - total: - type: integer - total_pages: - type: integer - type: object - models.NullableInt32: - properties: - int32: - type: integer - valid: - type: boolean - type: object - models.NullableString: - properties: - string: - type: string - valid: - type: boolean - type: object - models.NullableTime: - properties: - time: - type: string - valid: - type: boolean - type: object - models.TokenResponse: + api-service_internal_models_auth.TokenResponse: properties: access_token: type: string @@ -109,7 +19,7 @@ definitions: token_type: type: string type: object - models.User: + api-service_internal_models_auth.User: properties: email: type: string @@ -120,453 +30,6 @@ definitions: username: type: string type: object - peserta.PesertaData: - properties: - cob: - properties: - nmAsuransi: {} - noAsuransi: {} - tglTAT: {} - tglTMT: {} - type: object - hakKelas: - properties: - keterangan: - type: string - kode: - type: string - type: object - informasi: - properties: - dinsos: {} - eSEP: {} - noSKTM: {} - prolanisPRB: - type: string - type: object - jenisPeserta: - properties: - keterangan: - type: string - kode: - type: string - type: object - mr: - properties: - noMR: - type: string - noTelepon: - type: string - type: object - nama: - type: string - nik: - type: string - noKartu: - type: string - pisa: - type: string - provUmum: - properties: - kdProvider: - type: string - nmProvider: - type: string - type: object - raw_response: - type: string - sex: - type: string - statusPeserta: - properties: - keterangan: - type: string - kode: - type: string - type: object - tglCetakKartu: - type: string - tglLahir: - type: string - tglTAT: - type: string - tglTMT: - type: string - umur: - properties: - umurSaatPelayanan: - type: string - umurSekarang: - type: string - type: object - type: object - peserta.PesertaResponse: - properties: - data: - $ref: '#/definitions/peserta.PesertaData' - message: - type: string - metaData: {} - request_id: - type: string - status: - type: string - timestamp: - type: string - type: object - retribusi.Retribusi: - properties: - date_created: - $ref: '#/definitions/models.NullableTime' - date_updated: - $ref: '#/definitions/models.NullableTime' - dinas: - $ref: '#/definitions/models.NullableString' - id: - type: string - jenis: - $ref: '#/definitions/models.NullableString' - kelompok_obyek: - $ref: '#/definitions/models.NullableString' - kode_tarif: - $ref: '#/definitions/models.NullableString' - pelayanan: - $ref: '#/definitions/models.NullableString' - rekening_denda: - $ref: '#/definitions/models.NullableString' - rekening_pokok: - $ref: '#/definitions/models.NullableString' - satuan: - $ref: '#/definitions/models.NullableString' - satuan_overtime: - $ref: '#/definitions/models.NullableString' - sort: - $ref: '#/definitions/models.NullableInt32' - status: - type: string - tarif: - $ref: '#/definitions/models.NullableString' - tarif_overtime: - $ref: '#/definitions/models.NullableString' - uraian_1: - $ref: '#/definitions/models.NullableString' - uraian_2: - $ref: '#/definitions/models.NullableString' - uraian_3: - $ref: '#/definitions/models.NullableString' - user_created: - $ref: '#/definitions/models.NullableString' - user_updated: - $ref: '#/definitions/models.NullableString' - type: object - retribusi.RetribusiCreateRequest: - properties: - dinas: - maxLength: 255 - minLength: 1 - type: string - jenis: - maxLength: 255 - minLength: 1 - type: string - kelompok_obyek: - maxLength: 255 - minLength: 1 - type: string - kode_tarif: - maxLength: 255 - minLength: 1 - type: string - pelayanan: - maxLength: 255 - minLength: 1 - type: string - rekening_denda: - maxLength: 255 - minLength: 1 - type: string - rekening_pokok: - maxLength: 255 - minLength: 1 - type: string - satuan: - maxLength: 255 - minLength: 1 - type: string - satuan_overtime: - maxLength: 255 - minLength: 1 - type: string - status: - enum: - - draft - - active - - inactive - type: string - tarif: - type: string - tarif_overtime: - type: string - uraian_1: - type: string - uraian_2: - type: string - uraian_3: - type: string - required: - - status - type: object - retribusi.RetribusiCreateResponse: - properties: - data: - $ref: '#/definitions/retribusi.Retribusi' - message: - type: string - type: object - retribusi.RetribusiDeleteResponse: - properties: - id: - type: string - message: - type: string - type: object - retribusi.RetribusiGetByIDResponse: - properties: - data: - $ref: '#/definitions/retribusi.Retribusi' - message: - type: string - type: object - retribusi.RetribusiGetResponse: - properties: - data: - items: - $ref: '#/definitions/retribusi.Retribusi' - type: array - message: - type: string - meta: - $ref: '#/definitions/models.MetaResponse' - summary: - $ref: '#/definitions/models.AggregateData' - type: object - retribusi.RetribusiUpdateRequest: - properties: - dinas: - maxLength: 255 - minLength: 1 - type: string - jenis: - maxLength: 255 - minLength: 1 - type: string - kelompok_obyek: - maxLength: 255 - minLength: 1 - type: string - kode_tarif: - maxLength: 255 - minLength: 1 - type: string - pelayanan: - maxLength: 255 - minLength: 1 - type: string - rekening_denda: - maxLength: 255 - minLength: 1 - type: string - rekening_pokok: - maxLength: 255 - minLength: 1 - type: string - satuan: - maxLength: 255 - minLength: 1 - type: string - satuan_overtime: - maxLength: 255 - minLength: 1 - type: string - status: - enum: - - draft - - active - - inactive - type: string - tarif: - type: string - tarif_overtime: - type: string - uraian_1: - type: string - uraian_2: - type: string - uraian_3: - type: string - required: - - status - type: object - retribusi.RetribusiUpdateResponse: - properties: - data: - $ref: '#/definitions/retribusi.Retribusi' - message: - type: string - type: object - rujukan.DataPeserta: - properties: - cob: - properties: - nmAsuransi: {} - noAsuransi: {} - tglTAT: {} - tglTMT: {} - type: object - hakKelas: - properties: - keterangan: - type: string - kode: - type: string - type: object - informasi: - properties: - dinsos: {} - noSKTM: {} - prolanisPRB: {} - type: object - jenisPeserta: - properties: - keterangan: - type: string - kode: - type: string - type: object - mr: - properties: - noMR: - type: string - noTelepon: {} - type: object - nama: - type: string - nik: - type: string - noKartu: - type: string - pisa: - type: string - provUmum: - properties: - kdProvider: - type: string - nmProvider: - type: string - type: object - sex: - type: string - statusPeserta: - properties: - keterangan: - type: string - kode: - type: string - type: object - tglCetakKartu: - type: string - tglLahir: - type: string - tglTAT: - type: string - tglTMT: - type: string - umur: - properties: - umurSaatPelayanan: - type: string - umurSekarang: - type: string - type: object - type: object - rujukan.DiagnosaData: - properties: - kode: - type: string - nama: - type: string - type: object - rujukan.PelayananData: - properties: - kode: - type: string - nama: - type: string - type: object - rujukan.PoliRujukanData: - properties: - kode: - type: string - nama: - type: string - type: object - rujukan.ProvPerujukData: - properties: - kode: - type: string - nama: - type: string - type: object - rujukan.RujukanData: - properties: - diagnosa: - $ref: '#/definitions/rujukan.DiagnosaData' - keluhan: - type: string - noKunjungan: - type: string - pelayanan: - $ref: '#/definitions/rujukan.PelayananData' - peserta: - $ref: '#/definitions/rujukan.DataPeserta' - poliRujukan: - $ref: '#/definitions/rujukan.PoliRujukanData' - provPerujuk: - $ref: '#/definitions/rujukan.ProvPerujukData' - tglKunjungan: - type: string - type: object - rujukan.RujukanRequest: - properties: - noKartu: - type: string - noRujukan: - type: string - request_id: - type: string - timestamp: - type: string - required: - - noRujukan - type: object - rujukan.RujukanResponse: - properties: - data: - $ref: '#/definitions/rujukan.RujukanData' - list: - items: - $ref: '#/definitions/rujukan.RujukanData' - type: array - message: - type: string - metaData: {} - request_id: - type: string - status: - type: string - timestamp: - type: string - type: object host: localhost:8080 info: contact: @@ -581,350 +44,6 @@ info: title: API Service version: 1.0.0 paths: - /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 Bynik data - schema: - $ref: '#/definitions/peserta.PesertaResponse' - "400": - description: Bad request - invalid parameters - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "404": - description: Not found - Bynik not found - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Get Bynik data - tags: - - Peserta - /Peserta/nokartu/: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 Bynokartu data - schema: - $ref: '#/definitions/peserta.PesertaResponse' - "400": - description: Bad request - invalid parameters - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "404": - description: Not found - Bynokartu not found - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Get Bynokartu data - tags: - - Peserta - /Rujukan/:norujukan: - delete: - consumes: - - application/json - description: Delete existing Rujukan from BPJS system - parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - produces: - - application/json - responses: - "200": - description: Successfully deleted Rujukan - schema: - $ref: '#/definitions/rujukan.RujukanResponse' - "400": - description: Bad request - invalid parameters - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "404": - description: Not found - Rujukan not found - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Delete existing Rujukan - tags: - - Rujukan - post: - consumes: - - application/json - description: Create new Rujukan in BPJS system - parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - - description: Rujukan data - in: body - name: request - required: true - schema: - $ref: '#/definitions/rujukan.RujukanRequest' - produces: - - application/json - responses: - "201": - description: Successfully created Rujukan - schema: - $ref: '#/definitions/rujukan.RujukanResponse' - "400": - description: Bad request - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "409": - description: Conflict - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Create new Rujukan - tags: - - Rujukan - put: - consumes: - - application/json - description: Update existing Rujukan in BPJS system - parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - - description: Rujukan update data - in: body - name: request - required: true - schema: - $ref: '#/definitions/rujukan.RujukanRequest' - produces: - - application/json - responses: - "200": - description: Successfully updated Rujukan - schema: - $ref: '#/definitions/rujukan.RujukanResponse' - "400": - description: Bad request - invalid parameters - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "404": - description: Not found - Rujukan not found - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "409": - description: Conflict - update conflict occurred - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Update existing Rujukan - tags: - - Rujukan - /Rujukanbalik/:norujukan: - delete: - consumes: - - application/json - description: Delete existing Rujukanbalik from BPJS system - parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - produces: - - application/json - responses: - "200": - description: Successfully deleted Rujukanbalik - schema: - $ref: '#/definitions/rujukan.RujukanResponse' - "400": - description: Bad request - invalid parameters - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "404": - description: Not found - Rujukanbalik not found - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Delete existing Rujukanbalik - tags: - - Rujukan - post: - consumes: - - application/json - description: Create new Rujukanbalik in BPJS system - parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - - description: Rujukanbalik data - in: body - name: request - required: true - schema: - $ref: '#/definitions/rujukan.RujukanRequest' - produces: - - application/json - responses: - "201": - description: Successfully created Rujukanbalik - schema: - $ref: '#/definitions/rujukan.RujukanResponse' - "400": - description: Bad request - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "409": - description: Conflict - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Create new Rujukanbalik - tags: - - Rujukan - put: - consumes: - - application/json - description: Update existing Rujukanbalik in BPJS system - parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - - description: Rujukanbalik update data - in: body - name: request - required: true - schema: - $ref: '#/definitions/rujukan.RujukanRequest' - produces: - - application/json - responses: - "200": - description: Successfully updated Rujukanbalik - schema: - $ref: '#/definitions/rujukan.RujukanResponse' - "400": - description: Bad request - invalid parameters - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "404": - description: Not found - Rujukanbalik not found - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "409": - description: Conflict - update conflict occurred - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Update existing Rujukanbalik - tags: - - Rujukan /api/v1/auth/login: post: consumes: @@ -936,14 +55,14 @@ paths: name: login required: true schema: - $ref: '#/definitions/models.LoginRequest' + $ref: '#/definitions/api-service_internal_models_auth.LoginRequest' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/models.TokenResponse' + $ref: '#/definitions/api-service_internal_models_auth.TokenResponse' "400": description: Bad request schema: @@ -968,7 +87,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/models.User' + $ref: '#/definitions/api-service_internal_models_auth.User' "401": description: Unauthorized schema: @@ -1000,7 +119,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/models.TokenResponse' + $ref: '#/definitions/api-service_internal_models_auth.TokenResponse' "400": description: Bad request schema: @@ -1048,263 +167,6 @@ paths: summary: Register new user tags: - Authentication - /api/v1/retribusi/{id}: - delete: - consumes: - - application/json - description: Soft deletes a retribusi by setting status to 'deleted' - parameters: - - description: Retribusi ID (UUID) - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: Retribusi deleted successfully - schema: - $ref: '#/definitions/retribusi.RetribusiDeleteResponse' - "400": - description: Invalid ID format - schema: - $ref: '#/definitions/models.ErrorResponse' - "404": - description: Retribusi not found - schema: - $ref: '#/definitions/models.ErrorResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponse' - summary: Delete retribusi - tags: - - Retribusi - get: - consumes: - - application/json - description: Returns a single retribusi by ID - parameters: - - description: Retribusi ID (UUID) - in: path - name: id - required: true - type: string - produces: - - application/json - responses: - "200": - description: Success response - schema: - $ref: '#/definitions/retribusi.RetribusiGetByIDResponse' - "400": - description: Invalid ID format - schema: - $ref: '#/definitions/models.ErrorResponse' - "404": - description: Retribusi not found - schema: - $ref: '#/definitions/models.ErrorResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponse' - summary: Get Retribusi by ID - tags: - - Retribusi - put: - consumes: - - application/json - description: Updates an existing retribusi record - parameters: - - description: Retribusi ID (UUID) - in: path - name: id - required: true - type: string - - description: Retribusi update request - in: body - name: request - required: true - schema: - $ref: '#/definitions/retribusi.RetribusiUpdateRequest' - produces: - - application/json - responses: - "200": - description: Retribusi updated successfully - schema: - $ref: '#/definitions/retribusi.RetribusiUpdateResponse' - "400": - description: Bad request or validation error - schema: - $ref: '#/definitions/models.ErrorResponse' - "404": - description: Retribusi not found - schema: - $ref: '#/definitions/models.ErrorResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponse' - summary: Update retribusi - tags: - - Retribusi - /api/v1/retribusis: - get: - consumes: - - application/json - description: Returns a paginated list of retribusis with optional summary statistics - parameters: - - default: 10 - description: Limit (max 100) - in: query - name: limit - type: integer - - default: 0 - description: Offset - in: query - name: offset - type: integer - - default: false - description: Include aggregation summary - in: query - name: include_summary - type: boolean - - description: Filter by status - in: query - name: status - type: string - - description: Filter by jenis - in: query - name: jenis - type: string - - description: Filter by dinas - in: query - name: dinas - type: string - - description: Search in multiple fields - in: query - name: search - type: string - produces: - - application/json - responses: - "200": - description: Success response - schema: - $ref: '#/definitions/retribusi.RetribusiGetResponse' - "400": - description: Bad request - schema: - $ref: '#/definitions/models.ErrorResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponse' - summary: Get retribusi with pagination and optional aggregation - tags: - - Retribusi - post: - consumes: - - application/json - description: Creates a new retribusi record - parameters: - - description: Retribusi creation request - in: body - name: request - required: true - schema: - $ref: '#/definitions/retribusi.RetribusiCreateRequest' - produces: - - application/json - responses: - "201": - description: Retribusi created successfully - schema: - $ref: '#/definitions/retribusi.RetribusiCreateResponse' - "400": - description: Bad request or validation error - schema: - $ref: '#/definitions/models.ErrorResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponse' - summary: Create retribusi - tags: - - Retribusi - /api/v1/retribusis/dynamic: - get: - consumes: - - application/json - description: Returns retribusis with advanced dynamic filtering like Directus - parameters: - - description: Fields to select (e.g., fields=*.*) - in: query - name: fields - type: string - - description: Dynamic filters (e.g., filter[Jenis][_eq]=value) - in: query - name: filter[column][operator] - type: string - - description: Sort fields (e.g., sort=date_created,-Jenis) - in: query - name: sort - type: string - - default: 10 - description: Limit - in: query - name: limit - type: integer - - default: 0 - description: Offset - in: query - name: offset - type: integer - produces: - - application/json - responses: - "200": - description: Success response - schema: - $ref: '#/definitions/retribusi.RetribusiGetResponse' - "400": - description: Bad request - schema: - $ref: '#/definitions/models.ErrorResponse' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponse' - summary: Get retribusi with dynamic filtering - tags: - - Retribusi - /api/v1/retribusis/stats: - get: - consumes: - - application/json - description: Returns comprehensive statistics about retribusi data - parameters: - - description: Filter statistics by status - in: query - name: status - type: string - produces: - - application/json - responses: - "200": - description: Statistics data - schema: - $ref: '#/definitions/models.AggregateData' - "500": - description: Internal server error - schema: - $ref: '#/definitions/models.ErrorResponse' - summary: Get retribusi statistics - tags: - - Retribusi /api/v1/token/generate: post: consumes: @@ -1316,14 +178,14 @@ paths: name: token required: true schema: - $ref: '#/definitions/models.LoginRequest' + $ref: '#/definitions/api-service_internal_models_auth.LoginRequest' produces: - application/json responses: "200": description: OK schema: - $ref: '#/definitions/models.TokenResponse' + $ref: '#/definitions/api-service_internal_models_auth.TokenResponse' "400": description: Bad request schema: @@ -1360,7 +222,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/models.TokenResponse' + $ref: '#/definitions/api-service_internal_models_auth.TokenResponse' "400": description: Bad request schema: @@ -1370,94 +232,76 @@ paths: summary: Generate token directly tags: - Token - /bynokartu/:nokartu: - get: + /api/v1/ws/broadcast/check: + post: consumes: - application/json - description: Get rujukan by card number + description: Creates and broadcasts a WebSocket message with the specified type + and data parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - - description: nokartu - example: '"example_value"' + - description: Type of the message to broadcast in: path - name: nokartu + name: messageType required: true type: string + - description: Data payload for the message + in: body + name: data + required: true + schema: {} produces: - application/json responses: "200": - description: Successfully retrieved Bynokartu data + description: Message successfully queued for broadcast schema: - $ref: '#/definitions/rujukan.RujukanResponse' - "400": - description: Bad request - invalid parameters - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "404": - description: Not found - Bynokartu not found - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' + additionalProperties: + type: string + type: object "500": - description: Internal server error + description: Failed to queue message (queue full) schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Get Bynokartu data + additionalProperties: + type: string + type: object + summary: Broadcast a WebSocket message tags: - - Rujukan - /bynorujukan/:norujukan: - get: + - WebSocket QRIS + /api/v1/ws/broadcast/qris: + post: consumes: - application/json - description: Get rujukan by nomor rujukan + description: Creates and broadcasts a WebSocket message with the specified type + and data for QRIS operations parameters: - - description: Request ID for tracking - in: header - name: X-Request-ID - type: string - - description: norujukan - example: '"example_value"' + - description: Type of the QRIS message to broadcast in: path - name: norujukan + name: messageType required: true type: string + - description: QRIS data payload for the message + in: body + name: data + required: true + schema: {} produces: - application/json responses: "200": - description: Successfully retrieved Bynorujukan data + description: QRIS message successfully queued for broadcast schema: - $ref: '#/definitions/rujukan.RujukanResponse' - "400": - description: Bad request - invalid parameters - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "401": - description: Unauthorized - invalid API credentials - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - "404": - description: Not found - Bynorujukan not found - schema: - $ref: '#/definitions/models.ErrorResponseBpjs' + additionalProperties: + type: string + type: object "500": - description: Internal server error + description: Failed to queue QRIS message (queue full) schema: - $ref: '#/definitions/models.ErrorResponseBpjs' - security: - - ApiKeyAuth: [] - summary: Get Bynorujukan data + additionalProperties: + type: string + type: object + summary: Broadcast a QRIS-related WebSocket message tags: - - Rujukan + - WebSocket QRIS schemes: - http - https diff --git a/examples/clientsocket/client.html b/examples/clientsocket/client.html index d61fd50..b2bad00 100644 --- a/examples/clientsocket/client.html +++ b/examples/clientsocket/client.html @@ -821,9 +821,7 @@ ); const ipBased = document.getElementById("ipBasedCheck").checked; - let url = `ws://meninjar.dev.rssa.id:8070/api/v1/ws?user_id=${encodeURIComponent( - userId - )}&room=${encodeURIComponent(room)}`; + let url = `ws://localhost:8080/api/v1/ws?user_id=QRIS&room=BANKJATIM`; if (ipBased) { url += "&ip_based=true"; diff --git a/internal/config/config.go b/internal/config/config.go index 6a864ea..43e52a2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -761,18 +761,18 @@ func (c *Config) Validate() error { } } - if c.Bpjs.BaseURL == "" { - log.Fatal("BPJS Base URL is required") - } - if c.Bpjs.ConsID == "" { - log.Fatal("BPJS Consumer ID is required") - } - if c.Bpjs.UserKey == "" { - log.Fatal("BPJS User Key is required") - } - if c.Bpjs.SecretKey == "" { - log.Fatal("BPJS Secret Key is required") - } + // if c.Bpjs.BaseURL == "" { + // log.Fatal("BPJS Base URL is required") + // } + // if c.Bpjs.ConsID == "" { + // log.Fatal("BPJS Consumer ID is required") + // } + // if c.Bpjs.UserKey == "" { + // log.Fatal("BPJS User Key is required") + // } + // if c.Bpjs.SecretKey == "" { + // log.Fatal("BPJS Secret Key is required") + // } // Validate Keycloak configuration if enabled if c.Keycloak.Enabled { @@ -788,24 +788,24 @@ func (c *Config) Validate() error { } // Validate SatuSehat configuration - if c.SatuSehat.OrgID == "" { - log.Fatal("SatuSehat Organization ID is required") - } - if c.SatuSehat.FasyakesID == "" { - log.Fatal("SatuSehat Fasyankes ID is required") - } - if c.SatuSehat.ClientID == "" { - log.Fatal("SatuSehat Client ID is required") - } - if c.SatuSehat.ClientSecret == "" { - log.Fatal("SatuSehat Client Secret is required") - } - if c.SatuSehat.AuthURL == "" { - log.Fatal("SatuSehat Auth URL is required") - } - if c.SatuSehat.BaseURL == "" { - log.Fatal("SatuSehat Base URL is required") - } + // if c.SatuSehat.OrgID == "" { + // log.Fatal("SatuSehat Organization ID is required") + // } + // if c.SatuSehat.FasyakesID == "" { + // log.Fatal("SatuSehat Fasyankes ID is required") + // } + // if c.SatuSehat.ClientID == "" { + // log.Fatal("SatuSehat Client ID is required") + // } + // if c.SatuSehat.ClientSecret == "" { + // log.Fatal("SatuSehat Client Secret is required") + // } + // if c.SatuSehat.AuthURL == "" { + // log.Fatal("SatuSehat Auth URL is required") + // } + // if c.SatuSehat.BaseURL == "" { + // log.Fatal("SatuSehat Base URL is required") + // } // Validate WebSocket configuration if c.WebSocket.ReadTimeout <= 0 { diff --git a/internal/handlers/retribusi/retribusi.go b/internal/handlers/retribusi/retribusi.go deleted file mode 100644 index b5e9a94..0000000 --- a/internal/handlers/retribusi/retribusi.go +++ /dev/null @@ -1,1401 +0,0 @@ -package handlers - -import ( - "api-service/internal/config" - "api-service/internal/database" - models "api-service/internal/models" - "api-service/internal/models/retribusi" - utils "api-service/internal/utils/filters" - "api-service/internal/utils/validation" - "api-service/pkg/logger" - "context" - "database/sql" - "fmt" - "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("retribusi_status", validateRetribusiStatus) - - if db == nil { - logger.Fatal("Failed to initialize database connection") - } - }) -} - -// Custom validation for retribusi status -func validateRetribusiStatus(fl validator.FieldLevel) bool { - return models.IsValidStatus(fl.Field().String()) -} - -// RetribusiHandler handles retribusi services -type RetribusiHandler struct { - db database.Service -} - -// NewRetribusiHandler creates a new RetribusiHandler -func NewRetribusiHandler() *RetribusiHandler { - return &RetribusiHandler{ - db: db, - } -} - -// GetRetribusi godoc -// @Summary Get retribusi with pagination and optional aggregation -// @Description Returns a paginated list of retribusis with optional summary statistics -// @Tags Retribusi -// @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 jenis query string false "Filter by jenis" -// @Param dinas query string false "Filter by dinas" -// @Param search query string false "Search in multiple fields" -// @Success 200 {object} retribusi.RetribusiGetResponse "Success response" -// @Failure 400 {object} models.ErrorResponse "Bad request" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router /api/v1/retribusis [get] -func (h *RetribusiHandler) GetRetribusi(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 ( - retribusis []retribusi.Retribusi - 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.fetchRetribusis(ctx, dbConn, filter, limit, offset) - mu.Lock() - if err != nil { - errChan <- fmt.Errorf("failed to fetch data: %w", err) - } else { - retribusis = 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 := retribusi.RetribusiGetResponse{ - Message: "Data retribusi berhasil diambil", - Data: retribusis, - Meta: meta, - } - - if includeAggregation && aggregateData != nil { - response.Summary = aggregateData - } - - c.JSON(http.StatusOK, response) -} - -// GetRetribusiByID godoc -// @Summary Get Retribusi by ID -// @Description Returns a single retribusi by ID -// @Tags Retribusi -// @Accept json -// @Produce json -// @Param id path string true "Retribusi ID (UUID)" -// @Success 200 {object} retribusi.RetribusiGetByIDResponse "Success response" -// @Failure 400 {object} models.ErrorResponse "Invalid ID format" -// @Failure 404 {object} models.ErrorResponse "Retribusi not found" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router /api/v1/retribusi/{id} [get] -func (h *RetribusiHandler) GetRetribusiByID(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() - - dataretribusi, err := h.getRetribusiByID(ctx, dbConn, id) - if err != nil { - if err == sql.ErrNoRows { - h.respondError(c, "Retribusi not found", err, http.StatusNotFound) - } else { - h.logAndRespondError(c, "Failed to get retribusi", err, http.StatusInternalServerError) - } - return - } - - response := retribusi.RetribusiGetByIDResponse{ - Message: "Retribusi details retrieved successfully", - Data: dataretribusi, - } - - c.JSON(http.StatusOK, response) -} - -// GetRetribusiDynamic godoc -// @Summary Get retribusi with dynamic filtering -// @Description Returns retribusis with advanced dynamic filtering like Directus -// @Tags Retribusi -// @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[Jenis][_eq]=value)" -// @Param sort query string false "Sort fields (e.g., sort=date_created,-Jenis)" -// @Param limit query int false "Limit" default(10) -// @Param offset query int false "Offset" default(0) -// @Success 200 {object} retribusi.RetribusiGetResponse "Success response" -// @Failure 400 {object} models.ErrorResponse "Bad request" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router /api/v1/retribusis/dynamic [get] -func (h *RetribusiHandler) GetRetribusiDynamic(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 - retribusis, total, err := h.fetchRetribusisDynamic(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 := retribusi.RetribusiGetResponse{ - Message: "Data retribusi berhasil diambil", - Data: retribusis, - Meta: meta, - } - - c.JSON(http.StatusOK, response) -} - -// fetchRetribusisDynamic executes dynamic query -func (h *RetribusiHandler) fetchRetribusisDynamic(ctx context.Context, dbConn *sql.DB, query utils.DynamicQuery) ([]retribusi.Retribusi, int, error) { - // Setup query builder - countBuilder := utils.NewQueryBuilder("data_retribusi"). - SetColumnMapping(map[string]string{ - "jenis": "Jenis", - "pelayanan": "Pelayanan", - "dinas": "Dinas", - "kelompok_obyek": "Kelompok_obyek", - "Kode_tarif": "Kode_tarif", - "kode_tarif": "Kode_tarif", - "tarif": "Tarif", - "satuan": "Satuan", - "tarif_overtime": "Tarif_overtime", - "satuan_overtime": "Satuan_overtime", - "rekening_pokok": "Rekening_pokok", - "rekening_denda": "Rekening_denda", - "uraian_1": "Uraian_1", - "uraian_2": "Uraian_2", - "uraian_3": "Uraian_3", - }). - SetAllowedColumns([]string{ - "id", "status", "sort", "user_created", "date_created", - "user_updated", "date_updated", "Jenis", "Pelayanan", - "Dinas", "Kelompok_obyek", "Kode_tarif", "Tarif", "Satuan", - "Tarif_overtime", "Satuan_overtime", "Rekening_pokok", - "Rekening_denda", "Uraian_1", "Uraian_2", "Uraian_3", - }) - - mainBuilder := utils.NewQueryBuilder("data_retribusi"). - SetColumnMapping(map[string]string{ - "jenis": "Jenis", - "pelayanan": "Pelayanan", - "dinas": "Dinas", - "kelompok_obyek": "Kelompok_obyek", - "Kode_tarif": "Kode_tarif", - "kode_tarif": "Kode_tarif", - "tarif": "Tarif", - "satuan": "Satuan", - "tarif_overtime": "Tarif_overtime", - "satuan_overtime": "Satuan_overtime", - "rekening_pokok": "Rekening_pokok", - "rekening_denda": "Rekening_denda", - "uraian_1": "Uraian_1", - "uraian_2": "Uraian_2", - "uraian_3": "Uraian_3", - }). - SetAllowedColumns([]string{ - "id", "status", "sort", "user_created", "date_created", - "user_updated", "date_updated", "Jenis", "Pelayanan", - "Dinas", "Kelompok_obyek", "Kode_tarif", "Tarif", "Satuan", - "Tarif_overtime", "Satuan_overtime", "Rekening_pokok", - "Rekening_denda", "Uraian_1", "Uraian_2", "Uraian_3", - }) - - // Add default filter to exclude deleted records - if len(query.Filters) > 0 { - query.Filters = append([]utils.FilterGroup{{ - Filters: []utils.DynamicFilter{{ - Column: "status", - Operator: utils.OpNotEqual, - Value: "deleted", - }}, - LogicOp: "AND", - }}, query.Filters...) - } else { - query.Filters = []utils.FilterGroup{{ - Filters: []utils.DynamicFilter{{ - Column: "status", - Operator: utils.OpNotEqual, - Value: "deleted", - }}, - LogicOp: "AND", - }} - } - - // Execute queries sequentially to avoid race conditions - var total int - var retribusis []retribusi.Retribusi - - // 1. Get total count first - countQuery := query - countQuery.Limit = 0 - countQuery.Offset = 0 - - countSQL, countArgs, err := countBuilder.BuildCountQuery(countQuery) - if err != nil { - return nil, 0, fmt.Errorf("failed to build count query: %w", err) - } - - if err := dbConn.QueryRowContext(ctx, countSQL, countArgs...).Scan(&total); err != nil { - return nil, 0, fmt.Errorf("failed to get total count: %w", err) - } - - // 2. Get main data - mainSQL, mainArgs, err := mainBuilder.BuildQuery(query) - if err != nil { - return nil, 0, fmt.Errorf("failed to build main query: %w", err) - } - - rows, err := dbConn.QueryContext(ctx, mainSQL, mainArgs...) - if err != nil { - return nil, 0, fmt.Errorf("failed to execute main query: %w", err) - } - defer rows.Close() - - for rows.Next() { - retribusi, err := h.scanRetribusi(rows) - if err != nil { - return nil, 0, fmt.Errorf("failed to scan retribusi: %w", err) - } - retribusis = append(retribusis, retribusi) - } - - if err := rows.Err(); err != nil { - return nil, 0, fmt.Errorf("rows iteration error: %w", err) - } - - return retribusis, total, nil -} - -// SearchRetribusiAdvanced provides advanced search capabilities -func (h *RetribusiHandler) SearchRetribusiAdvanced(c *gin.Context) { - // Parse complex search parameters - searchQuery := c.Query("q") - if searchQuery == "" { - // If no search query provided, return all records with default sorting - query := utils.DynamicQuery{ - Fields: []string{"*"}, - Filters: []utils.FilterGroup{}, // Empty filters - fetchRetribusisDynamic will add default deleted filter - 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 query to get all records - retribusis, total, err := h.fetchRetribusisDynamic(ctx, dbConn, query) - if err != nil { - h.logAndRespondError(c, "Failed to fetch data", err, http.StatusInternalServerError) - return - } - - // Build response - meta := h.calculateMeta(query.Limit, query.Offset, total) - response := retribusi.RetribusiGetResponse{ - Message: "All records retrieved (no search query provided)", - Data: retribusis, - Meta: meta, - } - - c.JSON(http.StatusOK, response) - return - } - - // Build dynamic query for search - query := utils.DynamicQuery{ - Fields: []string{"*"}, - Filters: []utils.FilterGroup{{ - Filters: []utils.DynamicFilter{ - { - Column: "Jenis", - Operator: utils.OpContains, - Value: searchQuery, - LogicOp: "OR", - }, - { - Column: "Pelayanan", - Operator: utils.OpContains, - Value: searchQuery, - LogicOp: "OR", - }, - { - Column: "Dinas", - Operator: utils.OpContains, - Value: searchQuery, - LogicOp: "OR", - }, - { - Column: "Uraian_1", - 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 - retribusis, total, err := h.fetchRetribusisDynamic(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 := retribusi.RetribusiGetResponse{ - Message: fmt.Sprintf("Search results for '%s'", searchQuery), - Data: retribusis, - Meta: meta, - } - - c.JSON(http.StatusOK, response) -} - -// CreateRetribusi godoc -// @Summary Create retribusi -// @Description Creates a new retribusi record -// @Tags Retribusi -// @Accept json -// @Produce json -// @Param request body retribusi.RetribusiCreateRequest true "Retribusi creation request" -// @Success 201 {object} retribusi.RetribusiCreateResponse "Retribusi created successfully" -// @Failure 400 {object} models.ErrorResponse "Bad request or validation error" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router /api/v1/retribusis [post] -func (h *RetribusiHandler) CreateRetribusi(c *gin.Context) { - var req retribusi.RetribusiCreateRequest - - 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.validateRetribusiSubmission(ctx, dbConn, &req); err != nil { - h.respondError(c, "Validation failed", err, http.StatusBadRequest) - return - } - - dataretribusi, err := h.createRetribusi(ctx, dbConn, &req) - if err != nil { - h.logAndRespondError(c, "Failed to create retribusi", err, http.StatusInternalServerError) - return - } - - response := retribusi.RetribusiCreateResponse{ - Message: "Retribusi berhasil dibuat", - Data: dataretribusi, - } - - c.JSON(http.StatusCreated, response) -} - -// UpdateRetribusi godoc -// @Summary Update retribusi -// @Description Updates an existing retribusi record -// @Tags Retribusi -// @Accept json -// @Produce json -// @Param id path string true "Retribusi ID (UUID)" -// @Param request body retribusi.RetribusiUpdateRequest true "Retribusi update request" -// @Success 200 {object} retribusi.RetribusiUpdateResponse "Retribusi updated successfully" -// @Failure 400 {object} models.ErrorResponse "Bad request or validation error" -// @Failure 404 {object} models.ErrorResponse "Retribusi not found" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router /api/v1/retribusi/{id} [put] -func (h *RetribusiHandler) UpdateRetribusi(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 retribusi.RetribusiUpdateRequest - 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() - - dataretribusi, err := h.updateRetribusi(ctx, dbConn, &req) - if err != nil { - if err == sql.ErrNoRows { - h.respondError(c, "Retribusi not found", err, http.StatusNotFound) - } else { - h.logAndRespondError(c, "Failed to update retribusi", err, http.StatusInternalServerError) - } - return - } - - response := retribusi.RetribusiUpdateResponse{ - Message: "Retribusi berhasil diperbarui", - Data: dataretribusi, - } - - c.JSON(http.StatusOK, response) -} - -// DeleteRetribusi godoc -// @Summary Delete retribusi -// @Description Soft deletes a retribusi by setting status to 'deleted' -// @Tags Retribusi -// @Accept json -// @Produce json -// @Param id path string true "Retribusi ID (UUID)" -// @Success 200 {object} retribusi.RetribusiDeleteResponse "Retribusi deleted successfully" -// @Failure 400 {object} models.ErrorResponse "Invalid ID format" -// @Failure 404 {object} models.ErrorResponse "Retribusi not found" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router /api/v1/retribusi/{id} [delete] -func (h *RetribusiHandler) DeleteRetribusi(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.deleteRetribusi(ctx, dbConn, id) - if err != nil { - if err == sql.ErrNoRows { - h.respondError(c, "Retribusi not found", err, http.StatusNotFound) - } else { - h.logAndRespondError(c, "Failed to delete retribusi", err, http.StatusInternalServerError) - } - return - } - - response := retribusi.RetribusiDeleteResponse{ - Message: "Retribusi berhasil dihapus", - ID: id, - } - - c.JSON(http.StatusOK, response) -} - -// GetRetribusiStats godoc -// @Summary Get retribusi statistics -// @Description Returns comprehensive statistics about retribusi data -// @Tags Retribusi -// @Accept json -// @Produce json -// @Param status query string false "Filter statistics by status" -// @Success 200 {object} models.AggregateData "Statistics data" -// @Failure 500 {object} models.ErrorResponse "Internal server error" -// @Router /api/v1/retribusis/stats [get] -func (h *RetribusiHandler) GetRetribusiStats(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 retribusi berhasil diambil", - "data": aggregateData, - }) -} - -// Get retribusi by ID -func (h *RetribusiHandler) getRetribusiByID(ctx context.Context, dbConn *sql.DB, id string) (*retribusi.Retribusi, error) { - query := ` - SELECT - id, status, sort, user_created, date_created, user_updated, date_updated, - "Jenis", "Pelayanan", "Dinas", "Kelompok_obyek", "Kode_tarif", - "Tarif", "Satuan", "Tarif_overtime", "Satuan_overtime", - "Rekening_pokok", "Rekening_denda", "Uraian_1", "Uraian_2", "Uraian_3" - FROM data_retribusi - WHERE id = $1 AND status != 'deleted'` - - row := dbConn.QueryRowContext(ctx, query, id) - - var retribusi retribusi.Retribusi - err := row.Scan( - &retribusi.ID, &retribusi.Status, &retribusi.Sort, &retribusi.UserCreated, - &retribusi.DateCreated, &retribusi.UserUpdated, &retribusi.DateUpdated, - &retribusi.Jenis, &retribusi.Pelayanan, &retribusi.Dinas, &retribusi.KelompokObyek, - &retribusi.KodeTarif, &retribusi.Tarif, &retribusi.Satuan, &retribusi.TarifOvertime, - &retribusi.SatuanOvertime, &retribusi.RekeningPokok, &retribusi.RekeningDenda, - &retribusi.Uraian1, &retribusi.Uraian2, &retribusi.Uraian3, - ) - - if err != nil { - return nil, err - } - - return &retribusi, nil -} - -// Create retribusi -func (h *RetribusiHandler) createRetribusi(ctx context.Context, dbConn *sql.DB, req *retribusi.RetribusiCreateRequest) (*retribusi.Retribusi, error) { - id := uuid.New().String() - now := time.Now() - - query := ` - INSERT INTO data_retribusi ( - id, status, date_created, date_updated, - "Jenis", "Pelayanan", "Dinas", "Kelompok_obyek", "Kode_tarif", - "Tarif", "Satuan", "Tarif_overtime", "Satuan_overtime", - "Rekening_pokok", "Rekening_denda", "Uraian_1", "Uraian_2", "Uraian_3" - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) - RETURNING - id, status, sort, user_created, date_created, user_updated, date_updated, - "Jenis", "Pelayanan", "Dinas", "Kelompok_obyek", "Kode_tarif", - "Tarif", "Satuan", "Tarif_overtime", "Satuan_overtime", - "Rekening_pokok", "Rekening_denda", "Uraian_1", "Uraian_2", "Uraian_3"` - - row := dbConn.QueryRowContext(ctx, query, - id, req.Status, now, now, - req.Jenis, req.Pelayanan, req.Dinas, req.KelompokObyek, req.KodeTarif, - req.Tarif, req.Satuan, req.TarifOvertime, req.SatuanOvertime, - req.RekeningPokok, req.RekeningDenda, req.Uraian1, req.Uraian2, req.Uraian3, - ) - - var retribusi retribusi.Retribusi - err := row.Scan( - &retribusi.ID, &retribusi.Status, &retribusi.Sort, &retribusi.UserCreated, - &retribusi.DateCreated, &retribusi.UserUpdated, &retribusi.DateUpdated, - &retribusi.Jenis, &retribusi.Pelayanan, &retribusi.Dinas, &retribusi.KelompokObyek, - &retribusi.KodeTarif, &retribusi.Tarif, &retribusi.Satuan, &retribusi.TarifOvertime, - &retribusi.SatuanOvertime, &retribusi.RekeningPokok, &retribusi.RekeningDenda, - &retribusi.Uraian1, &retribusi.Uraian2, &retribusi.Uraian3, - ) - - if err != nil { - return nil, fmt.Errorf("failed to create retribusi: %w", err) - } - - return &retribusi, nil -} - -// Update retribusi -func (h *RetribusiHandler) updateRetribusi(ctx context.Context, dbConn *sql.DB, req *retribusi.RetribusiUpdateRequest) (*retribusi.Retribusi, error) { - now := time.Now() - - query := ` - UPDATE data_retribusi SET - status = $2, date_updated = $3, - "Jenis" = $4, "Pelayanan" = $5, "Dinas" = $6, "Kelompok_obyek" = $7, "Kode_tarif" = $8, - "Tarif" = $9, "Satuan" = $10, "Tarif_overtime" = $11, "Satuan_overtime" = $12, - "Rekening_pokok" = $13, "Rekening_denda" = $14, "Uraian_1" = $15, "Uraian_2" = $16, "Uraian_3" = $17 - WHERE id = $1 AND status != 'deleted' - RETURNING - id, status, sort, user_created, date_created, user_updated, date_updated, - "Jenis", "Pelayanan", "Dinas", "Kelompok_obyek", "Kode_tarif", - "Tarif", "Satuan", "Tarif_overtime", "Satuan_overtime", - "Rekening_pokok", "Rekening_denda", "Uraian_1", "Uraian_2", "Uraian_3"` - - row := dbConn.QueryRowContext(ctx, query, - req.ID, req.Status, now, - req.Jenis, req.Pelayanan, req.Dinas, req.KelompokObyek, req.KodeTarif, - req.Tarif, req.Satuan, req.TarifOvertime, req.SatuanOvertime, - req.RekeningPokok, req.RekeningDenda, req.Uraian1, req.Uraian2, req.Uraian3, - ) - - var retribusi retribusi.Retribusi - err := row.Scan( - &retribusi.ID, &retribusi.Status, &retribusi.Sort, &retribusi.UserCreated, - &retribusi.DateCreated, &retribusi.UserUpdated, &retribusi.DateUpdated, - &retribusi.Jenis, &retribusi.Pelayanan, &retribusi.Dinas, &retribusi.KelompokObyek, - &retribusi.KodeTarif, &retribusi.Tarif, &retribusi.Satuan, &retribusi.TarifOvertime, - &retribusi.SatuanOvertime, &retribusi.RekeningPokok, &retribusi.RekeningDenda, - &retribusi.Uraian1, &retribusi.Uraian2, &retribusi.Uraian3, - ) - - if err != nil { - return nil, fmt.Errorf("failed to update retribusi: %w", err) - } - - return &retribusi, nil -} - -// Soft delete retribusi -func (h *RetribusiHandler) deleteRetribusi(ctx context.Context, dbConn *sql.DB, id string) error { - now := time.Now() - - query := `UPDATE data_retribusi 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 retribusi: %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 -} - -// Enhanced error handling -func (h *RetribusiHandler) logAndRespondError(c *gin.Context, message string, err error, statusCode int) { - logger.Error(message, map[string]interface{}{ - "error": err.Error(), - "status_code": statusCode, - }) - h.respondError(c, message, err, statusCode) -} - -func (h *RetribusiHandler) 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 *RetribusiHandler) 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 - } - - logger.Debug("Pagination parameters", map[string]interface{}{ - "limit": limit, - "offset": offset, - }) - return limit, offset, nil -} - -// Build WHERE clause dengan filter parameters -func (h *RetribusiHandler) buildWhereClause(filter retribusi.RetribusiFilter) (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.Jenis != nil { - conditions = append(conditions, fmt.Sprintf(`"Jenis" ILIKE $%d`, paramCount)) - args = append(args, "%"+*filter.Jenis+"%") - paramCount++ - } - - if filter.Dinas != nil { - conditions = append(conditions, fmt.Sprintf(`"Dinas" ILIKE $%d`, paramCount)) - args = append(args, "%"+*filter.Dinas+"%") - paramCount++ - } - - if filter.KelompokObyek != nil { - conditions = append(conditions, fmt.Sprintf(`"Kelompok_obyek" ILIKE $%d`, paramCount)) - args = append(args, "%"+*filter.KelompokObyek+"%") - paramCount++ - } - - if filter.Search != nil { - searchCondition := fmt.Sprintf(`( - "Jenis" ILIKE $%d OR - "Pelayanan" ILIKE $%d OR - "Dinas" ILIKE $%d OR - "Kode_tarif" ILIKE $%d OR - "Uraian_1" ILIKE $%d OR - "Uraian_2" ILIKE $%d OR - "Uraian_3" ILIKE $%d - )`, paramCount, paramCount, paramCount, paramCount, paramCount, paramCount, 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)) // End of day - paramCount++ - } - - return strings.Join(conditions, " AND "), args -} - -// Optimized scanning function yang menggunakan sql.Null* types langsung -func (h *RetribusiHandler) scanRetribusi(rows *sql.Rows) (retribusi.Retribusi, error) { - var retribusi retribusi.Retribusi - - return retribusi, rows.Scan( - &retribusi.ID, - &retribusi.Status, - &retribusi.Sort, - &retribusi.UserCreated, - &retribusi.DateCreated, - &retribusi.UserUpdated, - &retribusi.DateUpdated, - &retribusi.Jenis, - &retribusi.Pelayanan, - &retribusi.Dinas, - &retribusi.KelompokObyek, - &retribusi.KodeTarif, - &retribusi.Tarif, - &retribusi.Satuan, - &retribusi.TarifOvertime, - &retribusi.SatuanOvertime, - &retribusi.RekeningPokok, - &retribusi.RekeningDenda, - &retribusi.Uraian1, - &retribusi.Uraian2, - &retribusi.Uraian3, - ) -} - -// Parse filter parameters dari query string -func (h *RetribusiHandler) parseFilterParams(c *gin.Context) retribusi.RetribusiFilter { - filter := retribusi.RetribusiFilter{} - - if status := c.Query("status"); status != "" { - if models.IsValidStatus(status) { - filter.Status = &status - } - } - - if jenis := c.Query("jenis"); jenis != "" { - filter.Jenis = &jenis - } - - if dinas := c.Query("dinas"); dinas != "" { - filter.Dinas = &dinas - } - - if kelompokObyek := c.Query("kelompok_obyek"); kelompokObyek != "" { - filter.KelompokObyek = &kelompokObyek - } - - 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 -} - -// Get comprehensive aggregate data dengan filter support -func (h *RetribusiHandler) getAggregateData(ctx context.Context, dbConn *sql.DB, filter retribusi.RetribusiFilter) (*models.AggregateData, error) { - aggregate := &models.AggregateData{ - ByStatus: make(map[string]int), - ByDinas: make(map[string]int), - ByJenis: 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_retribusi - 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. Count by Dinas - wg.Add(1) - go func() { - defer wg.Done() - dinasQuery := fmt.Sprintf(` - SELECT COALESCE("Dinas", 'Unknown') as dinas, COUNT(*) - FROM data_retribusi - WHERE %s AND "Dinas" IS NOT NULL AND TRIM("Dinas") != '' - GROUP BY "Dinas" - ORDER BY COUNT(*) DESC - LIMIT 10`, whereClause) - - rows, err := dbConn.QueryContext(ctx, dinasQuery, args...) - if err != nil { - errChan <- fmt.Errorf("dinas query failed: %w", err) - return - } - defer rows.Close() - - mu.Lock() - for rows.Next() { - var dinas string - var count int - if err := rows.Scan(&dinas, &count); err != nil { - mu.Unlock() - errChan <- fmt.Errorf("dinas scan failed: %w", err) - return - } - aggregate.ByDinas[dinas] = count - } - mu.Unlock() - - if err := rows.Err(); err != nil { - errChan <- fmt.Errorf("dinas iteration error: %w", err) - } - }() - - // 3. Count by Jenis - wg.Add(1) - go func() { - defer wg.Done() - jenisQuery := fmt.Sprintf(` - SELECT COALESCE("Jenis", 'Unknown') as jenis, COUNT(*) - FROM data_retribusi - WHERE %s AND "Jenis" IS NOT NULL AND TRIM("Jenis") != '' - GROUP BY "Jenis" - ORDER BY COUNT(*) DESC - LIMIT 10`, whereClause) - - rows, err := dbConn.QueryContext(ctx, jenisQuery, args...) - if err != nil { - errChan <- fmt.Errorf("jenis query failed: %w", err) - return - } - defer rows.Close() - - mu.Lock() - for rows.Next() { - var jenis string - var count int - if err := rows.Scan(&jenis, &count); err != nil { - mu.Unlock() - errChan <- fmt.Errorf("jenis scan failed: %w", err) - return - } - aggregate.ByJenis[jenis] = count - } - mu.Unlock() - - if err := rows.Err(); err != nil { - errChan <- fmt.Errorf("jenis iteration error: %w", err) - } - }() - - // 4. 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_retribusi - 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_retribusi - 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 -} - -// Get total count dengan filter support -func (h *RetribusiHandler) getTotalCount(ctx context.Context, dbConn *sql.DB, filter retribusi.RetribusiFilter, total *int) error { - whereClause, args := h.buildWhereClause(filter) - countQuery := fmt.Sprintf(`SELECT COUNT(*) FROM data_retribusi WHERE %s`, whereClause) - - if err := dbConn.QueryRowContext(ctx, countQuery, args...).Scan(total); err != nil { - return fmt.Errorf("total count query failed: %w", err) - } - - return nil -} - -// Enhanced fetchRetribusis dengan filter support -func (h *RetribusiHandler) fetchRetribusis(ctx context.Context, dbConn *sql.DB, filter retribusi.RetribusiFilter, limit, offset int) ([]retribusi.Retribusi, error) { - whereClause, args := h.buildWhereClause(filter) - - // Build the main query with pagination - query := fmt.Sprintf(` - SELECT - id, status, sort, user_created, date_created, user_updated, date_updated, - "Jenis", "Pelayanan", "Dinas", "Kelompok_obyek", "Kode_tarif", - "Tarif", "Satuan", "Tarif_overtime", "Satuan_overtime", - "Rekening_pokok", "Rekening_denda", "Uraian_1", "Uraian_2", "Uraian_3" - FROM data_retribusi - WHERE %s - ORDER BY date_created DESC NULLS LAST - LIMIT $%d OFFSET $%d`, - whereClause, len(args)+1, len(args)+2) - - // Add pagination parameters - args = append(args, limit, offset) - - rows, err := dbConn.QueryContext(ctx, query, args...) - if err != nil { - return nil, fmt.Errorf("fetch retribusis query failed: %w", err) - } - defer rows.Close() - - // Pre-allocate slice dengan kapasitas yang tepat - retribusis := make([]retribusi.Retribusi, 0, limit) - - for rows.Next() { - retribusi, err := h.scanRetribusi(rows) - if err != nil { - return nil, fmt.Errorf("scan retribusi failed: %w", err) - } - retribusis = append(retribusis, retribusi) - } - - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("rows iteration error: %w", err) - } - - logger.Info("Successfully fetched retribusis", map[string]interface{}{ - "count": len(retribusis), - "limit": limit, - "offset": offset, - }) - return retribusis, nil -} - -// Calculate pagination metadata -func (h *RetribusiHandler) 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, - } -} - -// validateRetribusiSubmission performs validation for duplicate entries and daily submission limits -func (h *RetribusiHandler) validateRetribusiSubmission(ctx context.Context, dbConn *sql.DB, req *retribusi.RetribusiCreateRequest) error { - // Import the validation utility - validator := validation.NewDuplicateValidator(dbConn) - - // Use default retribusi configuration - config := validation.DefaultRetribusiConfig() - - // 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_retribusi", "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 *RetribusiHandler) validateWithCustomConfig(ctx context.Context, dbConn *sql.DB, req *retribusi.RetribusiCreateRequest) error { - // Create validator instance - validator := validation.NewDuplicateValidator(dbConn) - - // Use custom configuration - config := validation.ValidationConfig{ - TableName: "data_retribusi", - IDColumn: "id", - StatusColumn: "status", - DateColumn: "date_created", - ActiveStatuses: []string{"active", "draft"}, - AdditionalFields: map[string]interface{}{ - "jenis": req.Jenis, - "dinas": req.Dinas, - }, - } - - // Validate with custom fields - fields := map[string]interface{}{ - "jenis": *req.Jenis, - "dinas": *req.Dinas, - } - - err := validator.ValidateDuplicateWithCustomFields(ctx, config, fields) - if err != nil { - return fmt.Errorf("custom validation failed: %w", err) - } - - return nil -} - -// GetLastSubmissionTime example -func (h *RetribusiHandler) getLastSubmissionTimeExample(ctx context.Context, dbConn *sql.DB, identifier string) (*time.Time, error) { - validator := validation.NewDuplicateValidator(dbConn) - return validator.GetLastSubmissionTime(ctx, "data_retribusi", "id", "date_created", identifier) -} diff --git a/internal/handlers/websocket/websocket.go b/internal/handlers/websocket/websocket.go index 757d4bf..f9a0621 100644 --- a/internal/handlers/websocket/websocket.go +++ b/internal/handlers/websocket/websocket.go @@ -3,6 +3,8 @@ package websocket import ( "api-service/internal/config" ws "api-service/internal/services/websocket" + "context" + "database/sql" "fmt" "net/http" "time" @@ -88,7 +90,7 @@ func (h *WebSocketHandler) TestWebSocketConnection(c *gin.Context) { "user_id": "optional, for user identification", "room": "optional, for room-based messaging", }, - "example": "ws://meninjar.dev.rssa.id:8070/api/v1/ws?client_id=test_client&room=test_room", + // "example": "ws://meninjar.dev.rssa.id:8070/api/v1/ws?client_id=test_client&room=test_room", "timestamp": time.Now().Unix(), }) } @@ -134,3 +136,134 @@ func (h *WebSocketHandler) CleanupInactiveClients(c *gin.Context) { "timestamp": time.Now().Unix(), }) } + +func (h *WebSocketHandler) BroadcastQris(c *gin.Context) { + var req struct { + Data map[string]interface{} `json:"data"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + dbConn, err := h.hub.GetDatabaseConnection("simrs_backup") + if err != nil || dbConn == nil { + c.JSON(500, gin.H{"error": "Database connection failed"}) + return + } + + ip, _ := req.Data["ip"].(string) + ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second) + defer cancel() + + posDevices, err := h.fetchPosDeviceByIP(ctx, dbConn, ip) + + broadcaster := ws.NewBroadcaster(h.hub) + + if err != nil || len(posDevices) == 0 { + broadcaster.BroadcastQris("qris_posdevice", map[string]interface{}{ + "data": req.Data, + "posdevice": nil, + "message": "No posdevice found for this IP", + "timestamp": time.Now().Unix(), + }) + + c.JSON(404, gin.H{ + "error": "No posdevice found for this IP", + "data": req.Data, + "timestamp": time.Now().Unix(), + }) + return + } + + broadcaster.BroadcastQris("qris_posdevice", map[string]interface{}{ + "data": req.Data, + "posdevice": posDevices, + "timestamp": time.Now().Unix(), + }) + + c.JSON(200, gin.H{ + "status": "broadcast sent", + "data": req.Data, + "posdevice": posDevices, + "timestamp": time.Now().Unix(), + }) +} + +func (h *WebSocketHandler) BroadcastCheck(c *gin.Context) { + var req struct { + Data map[string]interface{} `json:"data"` + } + + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + dbConn, err := h.hub.GetDatabaseConnection("simrs_backup") + if err != nil || dbConn == nil { + c.JSON(500, gin.H{"error": "Database connection failed"}) + return + } + + ip, _ := req.Data["ip"].(string) + ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second) + defer cancel() + + posDevices, err := h.fetchPosDeviceByIP(ctx, dbConn, ip) + + broadcaster := ws.NewBroadcaster(h.hub) + + if err != nil || len(posDevices) == 0 { + broadcaster.BroadcastCheck("qris_check", map[string]interface{}{ + "data": req.Data, + "posdevice": nil, + "message": "No posdevice found for this IP", + "timestamp": time.Now().Unix(), + }) + + c.JSON(404, gin.H{ + "error": "No posdevice found for this IP", + "data": req.Data, + "timestamp": time.Now().Unix(), + }) + return + } + + broadcaster.BroadcastCheck("qris_check", map[string]interface{}{ + "data": req.Data, + "posdevice": posDevices, + "timestamp": time.Now().Unix(), + }) + + c.JSON(200, gin.H{ + "status": "broadcast sent", + "data": req.Data, + "posdevice": posDevices, + "timestamp": time.Now().Unix(), + }) +} + +type Qris struct { + PosDevice string `json:"posdevice"` +} + +func (h *WebSocketHandler) fetchPosDeviceByIP(ctx context.Context, db *sql.DB, ip string) ([]string, error) { + query := `SELECT posdevice FROM m_deviceqris WHERE ip = $1 AND status = '1'` + rows, err := db.QueryContext(ctx, query, ip) + if err != nil { + return nil, err + } + defer rows.Close() + + var posDevices []string + for rows.Next() { + var posDevice string + if err := rows.Scan(&posDevice); err != nil { + return nil, err + } + posDevices = append(posDevices, posDevice) + } + return posDevices, nil +} diff --git a/internal/models/retribusi/retribusi.go b/internal/models/retribusi/retribusi.go deleted file mode 100644 index 7907527..0000000 --- a/internal/models/retribusi/retribusi.go +++ /dev/null @@ -1,228 +0,0 @@ -package retribusi - -import ( - "api-service/internal/models" - "encoding/json" - "time" -) - -// Retribusi represents the data structure for the retribusi table -// with proper null handling and optimized JSON marshaling -type Retribusi struct { - ID string `json:"id" db:"id"` - Status string `json:"status" db:"status"` - Sort models.NullableInt32 `json:"sort,omitempty" db:"sort"` - UserCreated models.NullableString `json:"user_created,omitempty" db:"user_created"` - DateCreated models.NullableTime `json:"date_created,omitempty" db:"date_created"` - UserUpdated models.NullableString `json:"user_updated,omitempty" db:"user_updated"` - DateUpdated models.NullableTime `json:"date_updated,omitempty" db:"date_updated"` - Jenis models.NullableString `json:"jenis,omitempty" db:"Jenis"` - Pelayanan models.NullableString `json:"pelayanan,omitempty" db:"Pelayanan"` - Dinas models.NullableString `json:"dinas,omitempty" db:"Dinas"` - KelompokObyek models.NullableString `json:"kelompok_obyek,omitempty" db:"Kelompok_obyek"` - KodeTarif models.NullableString `json:"kode_tarif,omitempty" db:"Kode_tarif"` - Tarif models.NullableString `json:"tarif,omitempty" db:"Tarif"` - Satuan models.NullableString `json:"satuan,omitempty" db:"Satuan"` - TarifOvertime models.NullableString `json:"tarif_overtime,omitempty" db:"Tarif_overtime"` - SatuanOvertime models.NullableString `json:"satuan_overtime,omitempty" db:"Satuan_overtime"` - RekeningPokok models.NullableString `json:"rekening_pokok,omitempty" db:"Rekening_pokok"` - RekeningDenda models.NullableString `json:"rekening_denda,omitempty" db:"Rekening_denda"` - Uraian1 models.NullableString `json:"uraian_1,omitempty" db:"Uraian_1"` - Uraian2 models.NullableString `json:"uraian_2,omitempty" db:"Uraian_2"` - Uraian3 models.NullableString `json:"uraian_3,omitempty" db:"Uraian_3"` -} - -// Custom JSON marshaling untuk Retribusi agar NULL values tidak muncul di response -func (r Retribusi) MarshalJSON() ([]byte, error) { - type Alias Retribusi - 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"` - Jenis *string `json:"jenis,omitempty"` - Pelayanan *string `json:"pelayanan,omitempty"` - Dinas *string `json:"dinas,omitempty"` - KelompokObyek *string `json:"kelompok_obyek,omitempty"` - KodeTarif *string `json:"kode_tarif,omitempty"` - Tarif *string `json:"tarif,omitempty"` - Satuan *string `json:"satuan,omitempty"` - TarifOvertime *string `json:"tarif_overtime,omitempty"` - SatuanOvertime *string `json:"satuan_overtime,omitempty"` - RekeningPokok *string `json:"rekening_pokok,omitempty"` - RekeningDenda *string `json:"rekening_denda,omitempty"` - Uraian1 *string `json:"uraian_1,omitempty"` - Uraian2 *string `json:"uraian_2,omitempty"` - Uraian3 *string `json:"uraian_3,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.Jenis.Valid { - aux.Jenis = &r.Jenis.String - } - if r.Pelayanan.Valid { - aux.Pelayanan = &r.Pelayanan.String - } - if r.Dinas.Valid { - aux.Dinas = &r.Dinas.String - } - if r.KelompokObyek.Valid { - aux.KelompokObyek = &r.KelompokObyek.String - } - if r.KodeTarif.Valid { - aux.KodeTarif = &r.KodeTarif.String - } - if r.Tarif.Valid { - aux.Tarif = &r.Tarif.String - } - if r.Satuan.Valid { - aux.Satuan = &r.Satuan.String - } - if r.TarifOvertime.Valid { - aux.TarifOvertime = &r.TarifOvertime.String - } - if r.SatuanOvertime.Valid { - aux.SatuanOvertime = &r.SatuanOvertime.String - } - if r.RekeningPokok.Valid { - aux.RekeningPokok = &r.RekeningPokok.String - } - if r.RekeningDenda.Valid { - aux.RekeningDenda = &r.RekeningDenda.String - } - if r.Uraian1.Valid { - aux.Uraian1 = &r.Uraian1.String - } - if r.Uraian2.Valid { - aux.Uraian2 = &r.Uraian2.String - } - if r.Uraian3.Valid { - aux.Uraian3 = &r.Uraian3.String - } - - return json.Marshal(aux) -} - -// Helper methods untuk mendapatkan nilai yang aman -func (r *Retribusi) GetJenis() string { - if r.Jenis.Valid { - return r.Jenis.String - } - return "" -} - -func (r *Retribusi) GetDinas() string { - if r.Dinas.Valid { - return r.Dinas.String - } - return "" -} - -func (r *Retribusi) GetTarif() string { - if r.Tarif.Valid { - return r.Tarif.String - } - return "" -} - -// Response struct untuk GET by ID - diperbaiki struktur -type RetribusiGetByIDResponse struct { - Message string `json:"message"` - Data *Retribusi `json:"data"` -} - -// Request struct untuk create - dioptimalkan dengan validasi -type RetribusiCreateRequest struct { - Status string `json:"status" validate:"required,oneof=draft active inactive"` - Jenis *string `json:"jenis,omitempty" validate:"omitempty,min=1,max=255"` - Pelayanan *string `json:"pelayanan,omitempty" validate:"omitempty,min=1,max=255"` - Dinas *string `json:"dinas,omitempty" validate:"omitempty,min=1,max=255"` - KelompokObyek *string `json:"kelompok_obyek,omitempty" validate:"omitempty,min=1,max=255"` - KodeTarif *string `json:"kode_tarif,omitempty" validate:"omitempty,min=1,max=255"` - Uraian1 *string `json:"uraian_1,omitempty"` - Uraian2 *string `json:"uraian_2,omitempty"` - Uraian3 *string `json:"uraian_3,omitempty"` - Tarif *string `json:"tarif,omitempty" validate:"omitempty,numeric"` - Satuan *string `json:"satuan,omitempty" validate:"omitempty,min=1,max=255"` - TarifOvertime *string `json:"tarif_overtime,omitempty" validate:"omitempty,numeric"` - SatuanOvertime *string `json:"satuan_overtime,omitempty" validate:"omitempty,min=1,max=255"` - RekeningPokok *string `json:"rekening_pokok,omitempty" validate:"omitempty,min=1,max=255"` - RekeningDenda *string `json:"rekening_denda,omitempty" validate:"omitempty,min=1,max=255"` -} - -// Response struct untuk create -type RetribusiCreateResponse struct { - Message string `json:"message"` - Data *Retribusi `json:"data"` -} - -// Update request - sama seperti create tapi dengan ID -type RetribusiUpdateRequest struct { - ID string `json:"-" validate:"required,uuid4"` // ID dari URL path - Status string `json:"status" validate:"required,oneof=draft active inactive"` - Jenis *string `json:"jenis,omitempty" validate:"omitempty,min=1,max=255"` - Pelayanan *string `json:"pelayanan,omitempty" validate:"omitempty,min=1,max=255"` - Dinas *string `json:"dinas,omitempty" validate:"omitempty,min=1,max=255"` - KelompokObyek *string `json:"kelompok_obyek,omitempty" validate:"omitempty,min=1,max=255"` - KodeTarif *string `json:"kode_tarif,omitempty" validate:"omitempty,min=1,max=255"` - Uraian1 *string `json:"uraian_1,omitempty"` - Uraian2 *string `json:"uraian_2,omitempty"` - Uraian3 *string `json:"uraian_3,omitempty"` - Tarif *string `json:"tarif,omitempty" validate:"omitempty,numeric"` - Satuan *string `json:"satuan,omitempty" validate:"omitempty,min=1,max=255"` - TarifOvertime *string `json:"tarif_overtime,omitempty" validate:"omitempty,numeric"` - SatuanOvertime *string `json:"satuan_overtime,omitempty" validate:"omitempty,min=1,max=255"` - RekeningPokok *string `json:"rekening_pokok,omitempty" validate:"omitempty,min=1,max=255"` - RekeningDenda *string `json:"rekening_denda,omitempty" validate:"omitempty,min=1,max=255"` -} - -// Response struct untuk update -type RetribusiUpdateResponse struct { - Message string `json:"message"` - Data *Retribusi `json:"data"` -} - -// Response struct untuk delete -type RetribusiDeleteResponse struct { - Message string `json:"message"` - ID string `json:"id"` -} - -// Enhanced GET response dengan pagination dan aggregation -type RetribusiGetResponse struct { - Message string `json:"message"` - Data []Retribusi `json:"data"` - Meta models.MetaResponse `json:"meta"` - Summary *models.AggregateData `json:"summary,omitempty"` -} - -// Filter struct untuk query parameters -type RetribusiFilter struct { - Status *string `json:"status,omitempty" form:"status"` - Jenis *string `json:"jenis,omitempty" form:"jenis"` - Dinas *string `json:"dinas,omitempty" form:"dinas"` - KelompokObyek *string `json:"kelompok_obyek,omitempty" form:"kelompok_obyek"` - Search *string `json:"search,omitempty" form:"search"` - DateFrom *time.Time `json:"date_from,omitempty" form:"date_from"` - DateTo *time.Time `json:"date_to,omitempty" form:"date_to"` -} diff --git a/internal/routes/v1/routes.go b/internal/routes/v1/routes.go index 017af8e..da8e510 100644 --- a/internal/routes/v1/routes.go +++ b/internal/routes/v1/routes.go @@ -3,9 +3,7 @@ package v1 import ( "api-service/internal/config" "api-service/internal/database" - authHandlers "api-service/internal/handlers/auth" healthcheckHandlers "api-service/internal/handlers/healthcheck" - retribusiHandlers "api-service/internal/handlers/retribusi" websocketHandlers "api-service/internal/handlers/websocket" "api-service/internal/middleware" services "api-service/internal/services/auth" @@ -22,7 +20,7 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine { router := gin.New() // Initialize auth middleware configuration - middleware.InitializeAuth(cfg) + middleware.AuthJWTMiddleware() // Add global middleware router.Use(middleware.CORSConfig()) @@ -90,17 +88,17 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine { // ============================================================================= // Authentication routes - authHandler := authHandlers.NewAuthHandler(authService) - tokenHandler := authHandlers.NewTokenHandler(authService) + // authHandler := authHandlers.NewAuthHandler(authService) + // tokenHandler := authHandlers.NewTokenHandler(authService) // Basic auth routes - v1.POST("/auth/login", authHandler.Login) - v1.POST("/auth/register", authHandler.Register) - v1.POST("/auth/refresh", authHandler.RefreshToken) + // v1.POST("/auth/login", authHandler.Login) + // v1.POST("/auth/register", authHandler.Register) + // v1.POST("/auth/refresh", authHandler.RefreshToken) // Token generation routes - v1.POST("/token/generate", tokenHandler.GenerateToken) - v1.POST("/token/generate-direct", tokenHandler.GenerateTokenDirect) + // v1.POST("/token/generate", tokenHandler.GenerateToken) + // v1.POST("/token/generate-direct", tokenHandler.GenerateTokenDirect) // ============================================================================= // WEBSOCKET ROUTES @@ -123,52 +121,72 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine { v1.GET("/ws", websocketHandler.HandleWebSocket) v1.GET("/ws/test", websocketHandler.TestWebSocketConnection) v1.GET("/ws/stats", websocketHandler.GetWebSocketStats) + v1.POST("/ws/broadcast/qris", websocketHandler.BroadcastQris) + v1.POST("/ws/broadcast/check", websocketHandler.BroadcastCheck) // Retribusi endpoints with WebSocket notifications - retribusiHandler := retribusiHandlers.NewRetribusiHandler() - retribusiGroup := v1.Group("/retribusi") - { - retribusiGroup.GET("", retribusiHandler.GetRetribusi) - retribusiGroup.GET("/dynamic", retribusiHandler.GetRetribusiDynamic) - retribusiGroup.GET("/search", retribusiHandler.SearchRetribusiAdvanced) - retribusiGroup.GET("/id/:id", retribusiHandler.GetRetribusiByID) + // retribusiHandler := retribusiHandlers.NewRetribusiHandler() + // retribusiGroup := v1.Group("/retribusi") + // { + // retribusiGroup.GET("", retribusiHandler.GetRetribusi) + // retribusiGroup.GET("/dynamic", retribusiHandler.GetRetribusiDynamic) + // retribusiGroup.GET("/search", retribusiHandler.SearchRetribusiAdvanced) + // retribusiGroup.GET("/id/:id", retribusiHandler.GetRetribusiByID) - // POST/PUT/DELETE with automatic WebSocket notifications - retribusiGroup.POST("", func(c *gin.Context) { - retribusiHandler.CreateRetribusi(c) + // // POST/PUT/DELETE with automatic WebSocket notifications + // retribusiGroup.POST("", func(c *gin.Context) { + // retribusiHandler.CreateRetribusi(c) - // Trigger WebSocket notification after successful creation - if c.Writer.Status() == 200 || c.Writer.Status() == 201 { - // Notify database change via WebSocket - // websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes", - // fmt.Sprintf(`{"action": "created", "timestamp": "%s"}`, time.Now().Format(time.RFC3339))) - } - }) + // // Trigger WebSocket notification after successful creation + // if c.Writer.Status() == 200 || c.Writer.Status() == 201 { + // // Notify database change via WebSocket + // // websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes", + // // fmt.Sprintf(`{"action": "created", "timestamp": "%s"}`, time.Now().Format(time.RFC3339))) + // } + // }) - retribusiGroup.PUT("/id/:id", func(c *gin.Context) { - // id := c.Param("id") - retribusiHandler.UpdateRetribusi(c) + // retribusiGroup.PUT("/id/:id", func(c *gin.Context) { + // // id := c.Param("id") + // retribusiHandler.UpdateRetribusi(c) - // Trigger WebSocket notification after successful update - if c.Writer.Status() == 200 { - // Notify database change via WebSocket - // websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes", - // fmt.Sprintf(`{"action": "updated", "id": "%s", "timestamp": "%s"}`, id, time.Now().Format(time.RFC3339))) - } - }) + // // Trigger WebSocket notification after successful update + // if c.Writer.Status() == 200 { + // // Notify database change via WebSocket + // // websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes", + // // fmt.Sprintf(`{"action": "updated", "id": "%s", "timestamp": "%s"}`, id, time.Now().Format(time.RFC3339))) + // } + // }) - retribusiGroup.DELETE("/id/:id", func(c *gin.Context) { - // id := c.Param("id") - retribusiHandler.DeleteRetribusi(c) + // retribusiGroup.DELETE("/id/:id", func(c *gin.Context) { + // // id := c.Param("id") + // retribusiHandler.DeleteRetribusi(c) - // Trigger WebSocket notification after successful deletion - if c.Writer.Status() == 200 { - // Notify database change via WebSocket - // websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes", - // fmt.Sprintf(`{"action": "deleted", "id": "%s", "timestamp": "%s"}`, id, time.Now().Format(time.RFC3339))) - } - }) - } + // // Trigger WebSocket notification after successful deletion + // if c.Writer.Status() == 200 { + // // Notify database change via WebSocket + // // websocketHub.NotifyDatabaseChange("postgres_satudata", "retribusi_changes", + // // fmt.Sprintf(`{"action": "deleted", "id": "%s", "timestamp": "%s"}`, id, time.Now().Format(time.RFC3339))) + // } + // }) + // } + + // ============================================================================= + // PROTECTED ROUTES (Authentication Required) + // ============================================================================= + + // Create protected group with configurable authentication + protected := v1.Group("/") + protected.Use(middleware.AuthJWTMiddleware()) // Use configurable authentication + + // User profile (protected) + // protected.GET("/auth/me", authHandler.Me) + + // Retribusi endpoints (CRUD operations - should be protected) + // protectedQris := protected.Group("/ws") + // { + // protectedQris.POST("/qris/broadcast", websocketHandler.BroadcastQris) // POST /api/v1/ws/ + // protectedQris.POST("/check/broadcast", websocketHandler.BroadcastCheck) // POST /api/v1/ws/ + // } return router } diff --git a/internal/services/websocket/broadcaster.go b/internal/services/websocket/broadcaster.go index 34d76ff..820e2d6 100644 --- a/internal/services/websocket/broadcaster.go +++ b/internal/services/websocket/broadcaster.go @@ -1,6 +1,7 @@ package websocket import ( + "api-service/pkg/logger" "time" ) @@ -108,6 +109,64 @@ func (b *Broadcaster) BroadcastMessage(messageType string, data interface{}) { } } +// BroadcastQris godoc +// @Summary Broadcast a QRIS-related WebSocket message +// @Description Creates and broadcasts a WebSocket message with the specified type and data for QRIS operations +// @Tags WebSocket QRIS +// @Accept json +// @Produce json +// @Param messageType path string true "Type of the QRIS message to broadcast" +// @Param data body interface{} true "QRIS data payload for the message" +// @Success 200 {object} map[string]string "QRIS message successfully queued for broadcast" +// @Failure 500 {object} map[string]string "Failed to queue QRIS message (queue full)" +// @Router /api/v1/ws/broadcast/qris [post] +func (b *Broadcaster) BroadcastQris(messageType string, data interface{}) { + msg := NewWebSocketMessage(MessageType(messageType), data, "", "") + + select { + case b.hub.messageQueue <- msg: + default: + // Antrian penuh, abaikan pesan + logger.Error("Message queue full, dropping message") + } + + // Show posdevice if present + // if m, ok := data.(map[string]interface{}); ok { + // fmt.Println("BroadcastQris called with IP display: ", m["posdevice"]) + // } + + //tabel m_deviceqris + //kolom posdevice dari nama lokasi jadi ip display + //kolom ip dari ip simrs +} + +// BroadcastCheck godoc +// @Summary Broadcast a WebSocket message +// @Description Creates and broadcasts a WebSocket message with the specified type and data +// @Tags WebSocket QRIS +// @Accept json +// @Produce json +// @Param messageType path string true "Type of the message to broadcast" +// @Param data body interface{} true "Data payload for the message" +// @Success 200 {object} map[string]string "Message successfully queued for broadcast" +// @Failure 500 {object} map[string]string "Failed to queue message (queue full)" +// @Router /api/v1/ws/broadcast/check [post] +func (b *Broadcaster) BroadcastCheck(messageType string, data interface{}) { + msg := NewWebSocketMessage(MessageType(messageType), data, "", "") + + select { + case b.hub.messageQueue <- msg: + default: + // Antrian penuh, abaikan pesan + logger.Error("Message queue full, dropping message") + } + + // Show posdevice if present + // if m, ok := data.(map[string]interface{}); ok { + // fmt.Println("BroadcastCheck called with IP display: ", m["posdevice"]) + // } +} + // BroadcastToRoom mengirim pesan ke ruangan tertentu func (b *Broadcaster) BroadcastToRoom(room string, messageType string, data interface{}) { msg := NewWebSocketMessage( diff --git a/internal/services/websocket/handlers.go b/internal/services/websocket/handlers.go index 62051bd..40c13e8 100644 --- a/internal/services/websocket/handlers.go +++ b/internal/services/websocket/handlers.go @@ -40,7 +40,7 @@ func (h *DatabaseHandler) handleDatabaseInsert(client *Client, message WebSocket return nil } - table, ok := data["table"].(string) + table, ok := data["m_deviceqris"].(string) if !ok || table == "" { client.sendErrorResponse("Invalid table name", "table is required") return nil @@ -55,7 +55,7 @@ func (h *DatabaseHandler) handleDatabaseInsert(client *Client, message WebSocket // Perform actual database insert if h.hub.dbService != nil { // Get database connection - db, err := h.hub.GetDatabaseConnection("postgres_satudata") + db, err := h.hub.GetDatabaseConnection("simrs_backup") if err != nil { client.sendErrorResponse("Database connection error", err.Error()) return nil @@ -110,14 +110,14 @@ func (h *DatabaseHandler) handleDatabaseQuery(client *Client, message WebSocketM return nil } - table, ok := data["table"].(string) + table, ok := data["m_deviceqris"].(string) if !ok || table == "" { client.sendErrorResponse("Invalid table name", "table is required") return nil } // Execute query - results, err := h.hub.ExecuteDatabaseQuery("postgres_satudata", fmt.Sprintf("SELECT * FROM %s LIMIT 100", table)) + results, err := h.hub.ExecuteDatabaseQuery("simrs_backup", fmt.Sprintf("SELECT * FROM %s LIMIT 100", table)) if err != nil { client.sendErrorResponse("Database query error", err.Error()) return nil @@ -142,7 +142,7 @@ func (h *DatabaseHandler) handleDatabaseCustomQuery(client *Client, message WebS database, ok := data["database"].(string) if !ok || database == "" { - database = "postgres_satudata" + database = "simrs_backup" } query, ok := data["query"].(string) diff --git a/internal/services/websocket/hub.go b/internal/services/websocket/hub.go index d4da478..ee52213 100644 --- a/internal/services/websocket/hub.go +++ b/internal/services/websocket/hub.go @@ -64,7 +64,7 @@ type ActivityLog struct { // DatabaseService mendefinisikan interface untuk layanan database type DatabaseService interface { Health() map[string]interface{} - ListDBs() []string + //ListDBs() []string ListenForChanges(ctx context.Context, dbName string, channels []string, callback func(channel, payload string)) error NotifyChange(dbName, channel, payload string) error GetDB(name string) (*sql.DB, error) @@ -345,9 +345,9 @@ func (h *Hub) GetStats() map[string]interface{} { // setupDatabaseListeners sets up database change listeners for real-time updates func (h *Hub) setupDatabaseListeners() { // Listen for changes on retribusi table - channels := []string{"retribusi_changes", "data_changes"} + channels := []string{"data_changes"} - err := h.dbService.ListenForChanges(h.ctx, "postgres_satudata", channels, func(channel, payload string) { + err := h.dbService.ListenForChanges(h.ctx, "simrs_backup", channels, func(channel, payload string) { h.handleDatabaseChange(channel, payload) }) diff --git a/tools/general/services-config.yaml b/tools/general/services-config.yaml index 40944b3..7488d23 100644 --- a/tools/general/services-config.yaml +++ b/tools/general/services-config.yaml @@ -4,17 +4,67 @@ global: enable_swagger: true enable_logging: true +# services: +# retribusi: +# name: "Retribusi" +# category: "retribusi" +# package: "retribusi" +# description: "Retribusi service for tariff and billing management" +# base_url: "" +# timeout: 30 +# retry_count: 3 + services: - retribusi: - name: "Retribusi" - category: "retribusi" - package: "retribusi" - description: "Retribusi service for tariff and billing management" + qris: + name: "QRIS" + category: "qris" + package: "qris" + description: "QRIS service for QR code payment management" base_url: "" timeout: 30 retry_count: 3 endpoints: + qris: + description: "QRIS management" + handler_folder: "qris" + handler_file: "qris.go" + handler_name: "QRIS" + table_name: "m_deviceqris" + functions: + create: + methods: ["POST"] + path: "/broadcast/qris" + post_routes: "/broadcast/qris" + post_path: "/broadcast/qris" + model: "QrisCreateRequest" + response_model: "QrisCreateResponse" + request_model: "QrisCreateRequest" + description: "Send new QRIS broadcast" + summary: "Send QRIS" + tags: ["QRIS"] + require_auth: true + cache_enabled: false + enable_database: true + cache_ttl: 0 + + create: + methods: ["POST"] + path: "/broadcast/check" + post_routes: "/broadcast/check" + post_path: "/broadcast/check" + model: "QrisCreateRequest" + response_model: "QrisCreateResponse" + request_model: "QrisCreateRequest" + description: "Send new QRIS check broadcast" + summary: "Send QRIS check" + tags: ["QRIS"] + require_auth: true + cache_enabled: false + enable_database: true + cache_ttl: 0 + +# endpoints: # retribusi: # description: "Retribusi tariff management" # handler_folder: "retribusi" @@ -152,99 +202,99 @@ services: # has_stats: true # Example of another service - user: - name: "User" - category: "user" - package: "user" - description: "User management service" - base_url: "" - timeout: 30 - retry_count: 3 + # user: + # name: "User" + # category: "user" + # package: "user" + # description: "User management service" + # base_url: "" + # timeout: 30 + # retry_count: 3 - endpoints: - user: - description: "User management endpoints" - handler_folder: "retribusi" - handler_file: "user.go" - handler_name: "User" - table_name: "data_user" - functions: - list: - methods: ["GET"] - path: "/" - get_routes: "/" - get_path: "/" - model: "User" - response_model: "UserGetResponse" - description: "Get user list with pagination" - summary: "Get User List" - tags: ["User"] - require_auth: true - cache_enabled: true - enable_database: true - cache_ttl: 300 - has_pagination: true - has_filter: true - has_search: true + # endpoints: + # user: + # description: "User management endpoints" + # handler_folder: "retribusi" + # handler_file: "user.go" + # handler_name: "User" + # table_name: "data_user" + # functions: + # list: + # methods: ["GET"] + # path: "/" + # get_routes: "/" + # get_path: "/" + # model: "User" + # response_model: "UserGetResponse" + # description: "Get user list with pagination" + # summary: "Get User List" + # tags: ["User"] + # require_auth: true + # cache_enabled: true + # enable_database: true + # cache_ttl: 300 + # has_pagination: true + # has_filter: true + # has_search: true - get: - methods: ["GET"] - path: "/:id" - get_routes: "/:id" - get_path: "/:id" - model: "User" - response_model: "UserGetByIDResponse" - description: "Get user by ID" - summary: "Get User by ID" - tags: ["User"] - require_auth: true - cache_enabled: true - enable_database: true - cache_ttl: 300 + # get: + # methods: ["GET"] + # path: "/:id" + # get_routes: "/:id" + # get_path: "/:id" + # model: "User" + # response_model: "UserGetByIDResponse" + # description: "Get user by ID" + # summary: "Get User by ID" + # tags: ["User"] + # require_auth: true + # cache_enabled: true + # enable_database: true + # cache_ttl: 300 - create: - methods: ["POST"] - path: "/" - post_routes: "/" - post_path: "/" - model: "UserCreateRequest" - response_model: "UserCreateResponse" - request_model: "UserCreateRequest" - description: "Create new user" - summary: "Create User" - tags: ["User"] - require_auth: true - cache_enabled: false - enable_database: true - cache_ttl: 0 + # create: + # methods: ["POST"] + # path: "/" + # post_routes: "/" + # post_path: "/" + # model: "UserCreateRequest" + # response_model: "UserCreateResponse" + # request_model: "UserCreateRequest" + # description: "Create new user" + # summary: "Create User" + # tags: ["User"] + # require_auth: true + # cache_enabled: false + # enable_database: true + # cache_ttl: 0 - update: - methods: ["PUT"] - path: "/:id" - put_routes: "/:id" - put_path: "/:id" - model: "UserUpdateRequest" - response_model: "UserUpdateResponse" - request_model: "UserUpdateRequest" - description: "Update user" - summary: "Update User" - tags: ["User"] - require_auth: true - cache_enabled: false - enable_database: true - cache_ttl: 0 + # update: + # methods: ["PUT"] + # path: "/:id" + # put_routes: "/:id" + # put_path: "/:id" + # model: "UserUpdateRequest" + # response_model: "UserUpdateResponse" + # request_model: "UserUpdateRequest" + # description: "Update user" + # summary: "Update User" + # tags: ["User"] + # require_auth: true + # cache_enabled: false + # enable_database: true + # cache_ttl: 0 - delete: - methods: ["DELETE"] - path: "/:id" - delete_routes: "/:id" - delete_path: "/:id" - model: "User" - response_model: "UserDeleteResponse" - description: "Delete user" - summary: "Delete User" - tags: ["User"] - require_auth: true - cache_enabled: false - enable_database: true - cache_ttl: 0 + # delete: + # methods: ["DELETE"] + # path: "/:id" + # delete_routes: "/:id" + # delete_path: "/:id" + # model: "User" + # response_model: "UserDeleteResponse" + # description: "Delete user" + # summary: "Delete User" + # tags: ["User"] + # require_auth: true + # cache_enabled: false + # enable_database: true + # cache_ttl: 0