From a6b62803abc4641c9fd4d9af55cce681343a35c6 Mon Sep 17 00:00:00 2001 From: Gitea CI Date: Wed, 12 Aug 2026 14:35:42 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20MCP=20SSE=20=ED=86=B5=EC=8B=A0=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0,=20=ED=98=B8=EC=8A=A4=ED=8A=B8=20=EB=B0=94?= =?UTF-8?q?=EC=9D=B8=EB=94=A9=20=EB=B0=8F=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=8B=A0=EA=B7=9C=20=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dap-gateway/build.gradle | 4 + .../document/DocumentGenerationRequest.java | 11 + .../document/DocumentGeneratorController.java | 56 + .../document/DocumentGeneratorService.java | 534 +++++++ .../dap/mcg/document/GeneratedDocument.java | 4 + .../DtoDownloadProxyController.java | 72 + ...ustomWebMvcSseServerTransportProvider.java | 2 +- .../dap/mcg/sync/DynamicMcpServerManager.java | 1 + .../excel/interface-definition-template.xlsx | Bin 0 -> 7170 bytes .../excel/program-definition-template.xlsx | Bin 0 -> 7065 bytes .../src/main/resources/static/lib/chart.js | 14 + .../main/resources/static/lib/tailwindcss.js | 83 ++ .../src/main/resources/static/tester.html | 876 +++++++++++ .../resources/static/vendor/MARKED-LICENSE.md | 44 + .../resources/static/vendor/marked.umd.js | 74 + .../DocumentGeneratorControllerTest.java | 57 + .../DocumentGeneratorServiceTest.java | 130 ++ .../presentation/McpRouterControllerTest.java | 38 + .../ScaffoldingControllerToolDraftTest.java | 48 + .../shinhanlife/dap/mcc/dto/ToolMetadata.java | 2 + .../usecase/ToolRegistryHeartbeatSender.java | 2 +- dap-was-lib/build.gradle | 55 + .../dap/lib/adapter/dto/ErrorDetail.java | 32 + .../dap/lib/adapter/dto/JsonRpcRequest.java | 27 + .../dap/lib/adapter/dto/JsonRpcResponse.java | 30 + .../dap/lib/adapter/dto/Params.java | 38 + .../lib/adapter/test/MockEimsHttpServer.java | 61 + .../util/PiiMaskingLogbackConverter.java | 35 + .../dap/lib/adapter/util/PiiMaskingUtils.java | 77 + .../dap/lib/annotation/McpOutputSchema.java | 14 + .../dap/lib/annotation/ToolHint.java | 32 + .../dap/lib/aop/ToolSlaMonitoringAspect.java | 83 ++ .../lib/config/AxhubHttpConfiguration.java | 29 + .../dap/lib/config/CorsConfig.java | 34 + .../config/GlowCommunicationProperties.java | 85 ++ .../dap/lib/config/McpProperties.java | 37 + .../dap/lib/config/MybatisConfig.java | 37 + .../dap/lib/config/P6SpySqlFormatter.java | 54 + .../lib/config/ToolSchemaConfiguration.java | 24 + .../dap/lib/dto/OperationType.java | 20 + .../lib/integration/GlowIntegrationCall.java | 105 ++ .../integration/dto/SampleGlowMessage.java | 84 ++ .../eai/component/AxhubEaiComponent.java | 99 ++ .../http/component/AxhubHttpComponent.java | 140 ++ .../http/component/AxhubHttpProperties.java | 34 + .../mci/component/AxhubMciComponent.java | 168 +++ .../mci/config/GlowMockConfig.java | 40 + .../config/ShinhanIntegrationProperties.java | 40 + .../mci/dto/MciRequestWrapper.java | 34 + .../mci/dto/MciResponseWrapper.java | 32 + .../mci/dto/OlCommonHeaderDto.java | 90 ++ .../mci/dto/ShinhanCommonHeaderDto.java | 72 + .../mci/dto/ShinhanMessageDto.java | 51 + .../mci/dto/ShinhanTelegramWrapper.java | 38 + .../mci/dto/SlCommonHeaderDto.java | 84 ++ .../mci/enums/IndvCtinRoleTyp.java | 27 + .../lib/manifest/ToolManifestAnnotations.java | 10 + .../dap/lib/manifest/ToolManifestItem.java | 15 + .../dap/lib/manifest/ToolManifestMeta.java | 5 + .../lib/manifest/ToolManifestResponse.java | 7 + .../dap/lib/manifest/ToolManifestService.java | 131 ++ .../dap/lib/mcp/McpRequestHeaderContext.java | 21 + .../dap/lib/mcp/McpRequestHeaderFilter.java | 34 + .../dap/lib/mcp/McpRequestHeaders.java | 9 + .../dap/lib/mcp/McpToolExecutionService.java | 101 ++ .../dap/lib/mcp/McpToolMethodRegistry.java | 87 ++ .../dap/lib/mcp/ToolExecutionResult.java | 15 + .../lib/mcp/ToolMcpServerConfiguration.java | 38 + .../lib/mcp/ToolPodMcpToolSynchronizer.java | 93 ++ .../lib/mcp/ToolRegistryHeartbeatSender.java | 199 +++ .../dap/lib/mcp/config/CacheConfig.java | 32 + .../dap/lib/mcp/config/JacksonConfig.java | 41 + .../dap/lib/mcp/config/KafkaLocalConfig.java | 58 + .../dap/lib/mcp/config/SwaggerConfig.java | 47 + .../dap/lib/mcp/config/WebConfig.java | 54 + .../mcp/exception/GlobalExceptionHandler.java | 60 + .../dap/lib/mcp/filter/MdcLoggingFilter.java | 56 + .../lib/mcp/security/ApiKeyInterceptor.java | 80 ++ .../lib/mcp/security/SecurityProperties.java | 40 + .../dap/lib/session/dto/SessionDto.java | 90 ++ .../dap/lib/util/JsonSchemaGenerator.java | 205 +++ .../dap/lib/util/PodScaffolder.java | 372 +++++ .../shinhanlife/dap/lib/util/SessionUtil.java | 45 + .../dap/lib/util/ToolScaffolder.java | 1276 +++++++++++++++++ .../dap/lib/util/ToolSchemaResolver.java | 78 + .../dap/lib/util/ToolSourceUpdater.java | 140 ++ .../McpToolNameValidationRunner.java | 21 + .../lib/validation/McpToolNameValidator.java | 173 +++ .../ToolArgumentSchemaValidator.java | 30 + .../shinhanlife/dap/mcc/dto/ToolMetadata.java | 133 ++ .../presentation/BusinessToolController.java | 50 + .../presentation/ToolManifestController.java | 32 + .../io/shinhanlife/glow/BaseException.java | 43 + .../io/shinhanlife/glow/BaseResponse.java | 39 + .../io/shinhanlife/glow/BizException.java | 20 + .../io/shinhanlife/glow/GlowAppServiceId.java | 28 + .../io/shinhanlife/glow/GlowControllerId.java | 20 + .../io/shinhanlife/glow/GlowIndexPaging.java | 24 + .../io/shinhanlife/glow/GlowLogTarget.java | 29 + .../java/io/shinhanlife/glow/GlowLogger.java | 33 + .../io/shinhanlife/glow/GlowMciFieldInfo.java | 44 + .../shinhanlife/glow/GlowMybatisMapper.java | 33 + .../shinhanlife/glow/GlowServiceGroupId.java | 22 + .../io/shinhanlife/glow/GlowTrgmField.java | 41 + .../java/io/shinhanlife/glow/PageInfo.java | 83 ++ .../io/shinhanlife/glow/ResponseCode.java | 45 + .../io/shinhanlife/glow/ResponseUtil.java | 125 ++ .../glow/communication/ICommunication.java | 6 + .../annotation/GlowTrgmField.java | 26 + .../glow/communication/dto/CommonHeader.java | 8 + .../communication/dto/HeaderDefaults.java | 6 + .../glow/communication/dto/Transfer.java | 20 + .../exception/ItrfException.java | 10 + .../eai/component/GlowEaiComponent.java | 26 + .../http/component/GlowHttpComponent.java | 98 ++ .../module/http/dto/HttpBody.java | 5 + .../module/http/dto/HttpHeader.java | 18 + .../module/http/dto/HttpTransfer.java | 19 + .../mci/component/GlowMciComponent.java | 18 + .../util/CommonHeaderFactory.java | 18 + .../io/shinhanlife/glow/db/dto/AuditInfo.java | 40 + .../shinhanlife/glow/util/GlowMciParser.java | 105 ++ .../shinhanlife/glow/util/GlowTrgmParser.java | 92 ++ .../resources/glow/application-glow-dev.yml | 19 + .../resources/glow/application-glow-local.yml | 41 + .../resources/glow/application-glow-prod.yml | 19 + .../resources/glow/application-glow-test.yml | 19 + .../main/resources/glow/application-glow.yml | 41 + .../src/main/resources/mock-responses.json | 106 ++ .../resources/static/tool-test-console.html | 338 +++++ .../adapter/test/MockEimsHttpServerTest.java | 19 + .../adapter/sender/ShinhanMciSenderTest.java | 60 + .../config/AxhubHttpConfigurationTest.java | 31 + .../config/ToolSchemaConfigurationTest.java | 22 + .../component/AxhubHttpComponentTest.java | 67 + .../lib/mcp/McpToolMethodRegistryTest.java | 57 + .../dap/lib/mcp/ToolExecutionServiceTest.java | 62 + .../mcp/ToolRegistryHeartbeatSenderTest.java | 56 + .../dap/lib/util/JsonSchemaGeneratorTest.java | 120 ++ .../dap/lib/util/PodScaffolderTest.java | 53 + .../dap/lib/util/ToolScaffolderTest.java | 258 ++++ .../dap/lib/util/ToolSchemaResolverTest.java | 83 ++ .../dap/lib/util/ToolSourceUpdaterTest.java | 37 + .../validation/McpToolNameValidatorTest.java | 112 ++ .../mcc/manifest/ToolManifestServiceTest.java | 100 ++ .../mcc/mcp/McpRequestHeaderFilterTest.java | 60 + .../mcp/ToolMcpServerConfigurationTest.java | 23 + ...inessToolControllerHeaderContractTest.java | 32 + .../ToolArgumentSchemaValidatorTest.java | 26 + .../ToolTestConsoleResourceTest.java | 24 + .../annotation/GlowTrgmFieldContractTest.java | 27 + .../glow/util/GlowMciParserTest.java | 65 + .../mock-responses/cmm_memo_retriever.json | 3 + dap-was-oth/Dockerfile | 8 + dap-was-oth/build.gradle | 10 + .../converter/MetaCommonCodeConverter.java | 31 + .../biz/cmm/converter/MetaTableConverter.java | 31 + .../biz/cmm/dto/MciSampleStringResponse.java | 32 + .../mcc/biz/cmm/dto/MciSampleTargetDto.java | 20 + .../biz/cmm/dto/MetaCommonCodeRequest.java | 39 + .../biz/cmm/dto/MetaCommonCodeResponse.java | 37 + .../dap/mcc/biz/cmm/dto/MetaTableRequest.java | 40 + .../mcc/biz/cmm/dto/MetaTableResponse.java | 37 + .../mcc/biz/cmm/dto/SampleStringRequest.java | 27 + .../mcc/biz/cmm/dto/SampleStringResponse.java | 38 + .../biz/cmm/dto/TemplateDownloadRequest.java | 38 + .../cmm/usecase/MetaCommonCodeUseCase.java | 26 + .../mcc/biz/cmm/usecase/MetaTableUseCase.java | 26 + .../cmm/usecase/TemplateUtilityUseCase.java | 14 + .../impl/MetaCommonCodeUseCaseImpl.java | 89 ++ .../usecase/impl/MetaTableUseCaseImpl.java | 89 ++ .../impl/TemplateUtilityUseCaseImpl.java | 57 + .../InsuranceClaimProcessorConverter.java | 17 + .../dto/InsuranceClaimProcessorRequest.java | 19 + .../dto/InsuranceClaimProcessorResponse.java | 16 + .../InsuranceClaimProcessorUseCase.java | 27 + .../InsuranceClaimProcessorUseCaseImpl.java | 30 + .../biz/oth/converter/Onnba3011Converter.java | 29 + .../dap/mcc/biz/oth/dto/Onnba3011Request.java | 119 ++ .../mcc/biz/oth/usecase/Onnba3011UseCase.java | 11 + .../usecase/impl/Onnba3011UseCaseImpl.java | 58 + .../mcc/biz/smp/dto/DailyQuoteRequest.java | 19 + .../mcc/biz/smp/dto/DailyQuoteResponse.java | 3 + .../mcc/biz/smp/dto/ExchangeRateRequest.java | 18 + .../mcc/biz/smp/dto/ExchangeRateResponse.java | 3 + .../mcc/biz/smp/dto/TeamMemberRequest.java | 32 + .../mcc/biz/smp/dto/TeamMemberResponse.java | 24 + .../dap/mcc/biz/smp/dto/WeatherRequest.java | 27 + .../dap/mcc/biz/smp/dto/WeatherResponse.java | 24 + .../smp/usecase/DailyQuoteToolUseCase.java | 13 + .../smp/usecase/ExchangeRateToolUseCase.java | 15 + .../biz/smp/usecase/TeamMemberUseCase.java | 12 + .../biz/smp/usecase/WeatherToolUseCase.java | 12 + .../impl/DailyQuoteToolUseCaseImpl.java | 45 + .../impl/ExchangeRateToolUseCaseImpl.java | 49 + .../usecase/impl/TeamMemberUseCaseImpl.java | 53 + .../usecase/impl/WeatherToolUseCaseImpl.java | 104 ++ .../sol/converter/SolReqDetailConverter.java | 31 + .../sol/converter/SolReqListConverter.java | 33 + .../mcc/biz/sol/dto/SolReqDetailRequest.java | 33 + .../mcc/biz/sol/dto/SolReqDetailResponse.java | 33 + .../mcc/biz/sol/dto/SolReqListRequest.java | 39 + .../mcc/biz/sol/dto/SolReqListResponse.java | 37 + .../biz/sol/usecase/SolReqDetailUseCase.java | 27 + .../biz/sol/usecase/SolReqListUseCase.java | 12 + .../usecase/impl/SolReqDetailUseCaseImpl.java | 101 ++ .../usecase/impl/SolReqListUseCaseImpl.java | 80 ++ .../itrf/http/insurance/InsuranceClient.java | 17 + .../InsuranceClaimProcessorHttpRequest.java | 19 + .../InsuranceClaimProcessorHttpResponse.java | 16 + .../infra/itrf/mci/cfp/a/MciCfpaClient.java | 72 + .../itrf/mci/cfp/a/io/CLCNNB00001_I.java | 91 ++ .../itrf/mci/cfp/a/io/CLCNNB00001_O.java | 16 + .../infra/itrf/mci/ncl/g/MciNclgClient.java | 30 + .../itrf/mci/ncl/g/io/SOLG00000001_I.java | 24 + .../itrf/mci/ncl/g/io/SOLG00000001_O.java | 34 + .../itrf/mci/ncl/g/io/SOLG00000002_I.java | 23 + .../itrf/mci/ncl/g/io/SOLG00000002_O.java | 31 + .../infra/itrf/mci/ncm/d/io/ONCMD0030_O.java | 68 + .../dap/mcc/oth/DapWasOthApplication.java | 33 + .../DtoExcelDownloadController.java | 424 ++++++ .../src/main/resources/application-dev.yml | 15 + .../src/main/resources/application-local.yml | 32 + .../src/main/resources/application-prod.yml | 16 + .../src/main/resources/application-test.yml | 16 + .../src/main/resources/application.yml | 19 + .../src/main/resources/logback-spring.xml | 39 + .../ins_insurance_processor.json | 4 + .../InsuranceClaimProcessorUseCaseTest.java | 16 + .../impl/Onnba3011UseCaseImplTest.java | 46 + .../impl/SolReqDetailUseCaseImplTest.java | 45 + .../impl/SolReqListUseCaseImplTest.java | 39 + .../Onnba3011MciRequestConverterTest.java | 55 + dap-was-sms/Dockerfile | 8 + dap-was-sms/build.gradle | 9 + .../cmm/converter/ClaimSearchConverter.java | 14 + .../converter/MemoListRetrieverConverter.java | 17 + .../mcc/biz/cmm/dto/ClaimSearchRequest.java | 16 + .../mcc/biz/cmm/dto/ClaimSearchResponse.java | 22 + .../biz/cmm/dto/MemoListRetrieverRequest.java | 16 + .../cmm/dto/MemoListRetrieverResponse.java | 13 + .../biz/cmm/usecase/ClaimSearchUseCase.java | 29 + .../cmm/usecase/MemoListRetrieverUseCase.java | 27 + .../usecase/impl/ClaimSearchUseCaseImpl.java | 68 + .../impl/MemoListRetrieverUseCaseImpl.java | 30 + .../mcc/infra/itrf/http/memo/MemoClient.java | 17 + .../memo/io/MemoListRetrieverHttpRequest.java | 16 + .../io/MemoListRetrieverHttpResponse.java | 13 + .../infra/itrf/mci/ncla/MciNclaClient.java | 30 + .../infra/itrf/mci/ncla/io/CLCNNB00001_I.java | 14 + .../infra/itrf/mci/ncla/io/CLCNNB00001_O.java | 17 + .../dap/mcc/sms/DapWasSmsApplication.java | 30 + .../src/main/resources/application-dev.yml | 15 + .../src/main/resources/application-local.yml | 28 + .../src/main/resources/application-prod.yml | 16 + .../src/main/resources/application-test.yml | 16 + .../src/main/resources/application.yml | 19 + .../src/main/resources/logback-spring.xml | 39 + .../mock-responses/cmm_memo_retriever.json | 3 + .../mock-responses/sms_cmm_claim_search.json | 5 + .../claim-search-resource-input-schema.json | 11 + .../claim-search-resource-output-schema.json | 16 + .../cmm/usecase/ClaimSearchUseCaseTest.java | 16 + .../usecase/MemoListRetrieverUseCaseTest.java | 16 + docker-compose.yml | 2 + ...fe-internal-network-migration-checklist.md | 96 ++ manifest_output.json | 606 ++++++++ mci-mock/__files/cmm_memo_retriever.json | 3 + mci-mock/__files/ins_insurance_processor.json | 4 + mci-mock/mappings/cmm_memo_retriever.json | 13 + .../mappings/ins_insurance_processor.json | 13 + 271 files changed, 15471 insertions(+), 2 deletions(-) create mode 100644 dap-gateway/src/main/java/io/shinhanlife/dap/mcg/document/DocumentGenerationRequest.java create mode 100644 dap-gateway/src/main/java/io/shinhanlife/dap/mcg/document/DocumentGeneratorController.java create mode 100644 dap-gateway/src/main/java/io/shinhanlife/dap/mcg/document/DocumentGeneratorService.java create mode 100644 dap-gateway/src/main/java/io/shinhanlife/dap/mcg/document/GeneratedDocument.java create mode 100644 dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/DtoDownloadProxyController.java create mode 100644 dap-gateway/src/main/resources/document-templates/excel/interface-definition-template.xlsx create mode 100644 dap-gateway/src/main/resources/document-templates/excel/program-definition-template.xlsx create mode 100644 dap-gateway/src/main/resources/static/lib/chart.js create mode 100644 dap-gateway/src/main/resources/static/lib/tailwindcss.js create mode 100644 dap-gateway/src/main/resources/static/tester.html create mode 100644 dap-gateway/src/main/resources/static/vendor/MARKED-LICENSE.md create mode 100644 dap-gateway/src/main/resources/static/vendor/marked.umd.js create mode 100644 dap-gateway/src/test/java/io/shinhanlife/dap/mcg/document/DocumentGeneratorControllerTest.java create mode 100644 dap-gateway/src/test/java/io/shinhanlife/dap/mcg/document/DocumentGeneratorServiceTest.java create mode 100644 dap-gateway/src/test/java/io/shinhanlife/dap/mcg/presentation/McpRouterControllerTest.java create mode 100644 dap-gateway/src/test/java/io/shinhanlife/dap/mcg/presentation/ScaffoldingControllerToolDraftTest.java create mode 100644 dap-was-lib/build.gradle create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/ErrorDetail.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/JsonRpcRequest.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/JsonRpcResponse.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/Params.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/test/MockEimsHttpServer.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/util/PiiMaskingLogbackConverter.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/util/PiiMaskingUtils.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpOutputSchema.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/ToolHint.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/aop/ToolSlaMonitoringAspect.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/AxhubHttpConfiguration.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/CorsConfig.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/GlowCommunicationProperties.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/McpProperties.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/MybatisConfig.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/P6SpySqlFormatter.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/ToolSchemaConfiguration.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/dto/OperationType.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/GlowIntegrationCall.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/dto/SampleGlowMessage.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/eai/component/AxhubEaiComponent.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpComponent.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpProperties.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/component/AxhubMciComponent.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/config/GlowMockConfig.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/config/ShinhanIntegrationProperties.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/MciRequestWrapper.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/MciResponseWrapper.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/OlCommonHeaderDto.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanCommonHeaderDto.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanMessageDto.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanTelegramWrapper.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/SlCommonHeaderDto.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/enums/IndvCtinRoleTyp.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestAnnotations.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestItem.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestMeta.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestResponse.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestService.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderContext.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderFilter.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaders.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolExecutionService.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistry.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolExecutionResult.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMcpServerConfiguration.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolPodMcpToolSynchronizer.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/CacheConfig.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/JacksonConfig.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/KafkaLocalConfig.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/SwaggerConfig.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/WebConfig.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/exception/GlobalExceptionHandler.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/filter/MdcLoggingFilter.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/ApiKeyInterceptor.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/SecurityProperties.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/session/dto/SessionDto.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/JsonSchemaGenerator.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/PodScaffolder.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/SessionUtil.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSchemaResolver.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidationRunner.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidator.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/ToolArgumentSchemaValidator.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/dto/ToolMetadata.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolManifestController.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/BaseException.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/BaseResponse.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/BizException.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowAppServiceId.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowControllerId.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowIndexPaging.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowLogTarget.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowLogger.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowMciFieldInfo.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowMybatisMapper.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowServiceGroupId.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/GlowTrgmField.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/PageInfo.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/ResponseCode.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/ResponseUtil.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/ICommunication.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/annotation/GlowTrgmField.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/CommonHeader.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/HeaderDefaults.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/Transfer.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/exception/ItrfException.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/eai/component/GlowEaiComponent.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/component/GlowHttpComponent.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpBody.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpHeader.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpTransfer.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/mci/component/GlowMciComponent.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/communication/util/CommonHeaderFactory.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/db/dto/AuditInfo.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/util/GlowMciParser.java create mode 100644 dap-was-lib/src/main/java/io/shinhanlife/glow/util/GlowTrgmParser.java create mode 100644 dap-was-lib/src/main/resources/glow/application-glow-dev.yml create mode 100644 dap-was-lib/src/main/resources/glow/application-glow-local.yml create mode 100644 dap-was-lib/src/main/resources/glow/application-glow-prod.yml create mode 100644 dap-was-lib/src/main/resources/glow/application-glow-test.yml create mode 100644 dap-was-lib/src/main/resources/glow/application-glow.yml create mode 100644 dap-was-lib/src/main/resources/mock-responses.json create mode 100644 dap-was-lib/src/main/resources/static/tool-test-console.html create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/adapter/test/MockEimsHttpServerTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/common/adapter/sender/ShinhanMciSenderTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/config/AxhubHttpConfigurationTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/config/ToolSchemaConfigurationTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpComponentTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistryTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolExecutionServiceTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSenderTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/PodScaffolderTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSchemaResolverTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSourceUpdaterTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/lib/validation/McpToolNameValidatorTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/manifest/ToolManifestServiceTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/McpRequestHeaderFilterTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/BusinessToolControllerHeaderContractTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolTestConsoleResourceTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/glow/communication/annotation/GlowTrgmFieldContractTest.java create mode 100644 dap-was-lib/src/test/java/io/shinhanlife/glow/util/GlowMciParserTest.java create mode 100644 dap-was-lib/src/test/resources/mock-responses/cmm_memo_retriever.json create mode 100644 dap-was-oth/Dockerfile create mode 100644 dap-was-oth/build.gradle create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaTableConverter.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MciSampleStringResponse.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MciSampleTargetDto.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableRequest.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableResponse.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/SampleStringRequest.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/SampleStringResponse.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/TemplateDownloadRequest.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/TemplateUtilityUseCase.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaTableUseCaseImpl.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/TemplateUtilityUseCaseImpl.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/converter/InsuranceClaimProcessorConverter.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/dto/InsuranceClaimProcessorRequest.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/dto/InsuranceClaimProcessorResponse.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/InsuranceClaimProcessorUseCase.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/impl/InsuranceClaimProcessorUseCaseImpl.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/converter/Onnba3011Converter.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/dto/Onnba3011Request.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/Onnba3011UseCase.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImpl.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/DailyQuoteRequest.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/DailyQuoteResponse.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/ExchangeRateRequest.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/ExchangeRateResponse.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/TeamMemberRequest.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/TeamMemberResponse.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/WeatherRequest.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/WeatherResponse.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/DailyQuoteToolUseCase.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/ExchangeRateToolUseCase.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/TeamMemberUseCase.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/WeatherToolUseCase.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/DailyQuoteToolUseCaseImpl.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/ExchangeRateToolUseCaseImpl.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/TeamMemberUseCaseImpl.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/WeatherToolUseCaseImpl.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/converter/SolReqDetailConverter.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/converter/SolReqListConverter.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailRequest.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailResponse.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqListRequest.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqListResponse.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqListUseCase.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImpl.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqListUseCaseImpl.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/insurance/InsuranceClient.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/insurance/io/InsuranceClaimProcessorHttpRequest.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/insurance/io/InsuranceClaimProcessorHttpResponse.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/cfp/a/MciCfpaClient.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/cfp/a/io/CLCNNB00001_I.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/cfp/a/io/CLCNNB00001_O.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/MciNclgClient.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000001_I.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000001_O.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_I.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_O.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncm/d/io/ONCMD0030_O.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/oth/DapWasOthApplication.java create mode 100644 dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/presentation/DtoExcelDownloadController.java create mode 100644 dap-was-oth/src/main/resources/application-dev.yml create mode 100644 dap-was-oth/src/main/resources/application-local.yml create mode 100644 dap-was-oth/src/main/resources/application-prod.yml create mode 100644 dap-was-oth/src/main/resources/application-test.yml create mode 100644 dap-was-oth/src/main/resources/application.yml create mode 100644 dap-was-oth/src/main/resources/logback-spring.xml create mode 100644 dap-was-oth/src/main/resources/mock-responses/ins_insurance_processor.json create mode 100644 dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/ins/usecase/InsuranceClaimProcessorUseCaseTest.java create mode 100644 dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImplTest.java create mode 100644 dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImplTest.java create mode 100644 dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqListUseCaseImplTest.java create mode 100644 dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/infra/itrf/mci/cfp/a/converter/Onnba3011MciRequestConverterTest.java create mode 100644 dap-was-sms/Dockerfile create mode 100644 dap-was-sms/build.gradle create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/ClaimSearchConverter.java create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MemoListRetrieverConverter.java create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequest.java create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchResponse.java create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MemoListRetrieverRequest.java create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MemoListRetrieverResponse.java create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCase.java create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCase.java create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/ClaimSearchUseCaseImpl.java create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MemoListRetrieverUseCaseImpl.java create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/MemoClient.java create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/io/MemoListRetrieverHttpRequest.java create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/io/MemoListRetrieverHttpResponse.java create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/MciNclaClient.java create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/io/CLCNNB00001_I.java create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/io/CLCNNB00001_O.java create mode 100644 dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/sms/DapWasSmsApplication.java create mode 100644 dap-was-sms/src/main/resources/application-dev.yml create mode 100644 dap-was-sms/src/main/resources/application-local.yml create mode 100644 dap-was-sms/src/main/resources/application-prod.yml create mode 100644 dap-was-sms/src/main/resources/application-test.yml create mode 100644 dap-was-sms/src/main/resources/application.yml create mode 100644 dap-was-sms/src/main/resources/logback-spring.xml create mode 100644 dap-was-sms/src/main/resources/mock-responses/cmm_memo_retriever.json create mode 100644 dap-was-sms/src/main/resources/mock-responses/sms_cmm_claim_search.json create mode 100644 dap-was-sms/src/main/resources/tool-schemas/cmm/claim-search-resource-input-schema.json create mode 100644 dap-was-sms/src/main/resources/tool-schemas/cmm/claim-search-resource-output-schema.json create mode 100644 dap-was-sms/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCaseTest.java create mode 100644 dap-was-sms/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCaseTest.java create mode 100644 docs/shinhanlife-internal-network-migration-checklist.md create mode 100644 manifest_output.json create mode 100644 mci-mock/__files/cmm_memo_retriever.json create mode 100644 mci-mock/__files/ins_insurance_processor.json create mode 100644 mci-mock/mappings/cmm_memo_retriever.json create mode 100644 mci-mock/mappings/ins_insurance_processor.json diff --git a/dap-gateway/build.gradle b/dap-gateway/build.gradle index 609a4d72..bd5954ae 100644 --- a/dap-gateway/build.gradle +++ b/dap-gateway/build.gradle @@ -5,6 +5,10 @@ plugins { dependencies { implementation project(':dap-tool-core') + // Apache POI for Excel Generation + implementation 'org.apache.poi:poi:5.2.5' + implementation 'org.apache.poi:poi-ooxml:5.2.5' + implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-redis' diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/document/DocumentGenerationRequest.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/document/DocumentGenerationRequest.java new file mode 100644 index 00000000..dbfa7e6c --- /dev/null +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/document/DocumentGenerationRequest.java @@ -0,0 +1,11 @@ +package io.shinhanlife.dap.mcg.document; + +import io.shinhanlife.dap.mcc.dto.ToolMetadata; + +public record DocumentGenerationRequest( + ToolMetadata tool, + String version, + boolean includeProgram, + boolean includeProcess, + boolean includeRevision) { +} diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/document/DocumentGeneratorController.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/document/DocumentGeneratorController.java new file mode 100644 index 00000000..cdb296c1 --- /dev/null +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/document/DocumentGeneratorController.java @@ -0,0 +1,56 @@ +package io.shinhanlife.dap.mcg.document; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import org.springframework.http.CacheControl; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/mcp/api/v1/admin/documents") +public class DocumentGeneratorController { + + private static final MediaType XLSX = MediaType.parseMediaType( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + + private final DocumentGeneratorService documentGeneratorService; + + public DocumentGeneratorController(DocumentGeneratorService documentGeneratorService) { + this.documentGeneratorService = documentGeneratorService; + } + + @PostMapping(value = "/program", produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + public ResponseEntity generateProgram(@RequestBody DocumentGenerationRequest request) { + return download(documentGeneratorService.generateProgram(request)); + } + + @PostMapping(value = "/interface", produces = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + public ResponseEntity generateInterface(@RequestBody DocumentGenerationRequest request) { + return download(documentGeneratorService.generateInterface(request)); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity> invalidRequest(IllegalArgumentException exception) { + return ResponseEntity.badRequest().body(Map.of("error", exception.getMessage())); + } + + private ResponseEntity download(GeneratedDocument document) { + ContentDisposition disposition = ContentDisposition.attachment() + .filename(document.fileName(), StandardCharsets.UTF_8) + .build(); + return ResponseEntity.ok() + .contentType(XLSX) + .contentLength(document.content().length) + .cacheControl(CacheControl.noStore()) + .header(HttpHeaders.CONTENT_DISPOSITION, disposition.toString()) + .body(document.content()); + } +} diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/document/DocumentGeneratorService.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/document/DocumentGeneratorService.java new file mode 100644 index 00000000..8571bf02 --- /dev/null +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/document/DocumentGeneratorService.java @@ -0,0 +1,534 @@ +package io.shinhanlife.dap.mcg.document; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.time.Clock; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellReference; +import org.springframework.core.io.ClassPathResource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class DocumentGeneratorService { + + static final String PROGRAM_TEMPLATE = "document-templates/excel/program-definition-template.xlsx"; + static final String INTERFACE_TEMPLATE = "document-templates/excel/interface-definition-template.xlsx"; + + private static final DateTimeFormatter FILE_DATE = DateTimeFormatter.BASIC_ISO_DATE; + private static final DateTimeFormatter DISPLAY_DATE = DateTimeFormatter.ISO_LOCAL_DATE; + private static final Pattern PATTERN_LENGTH = Pattern.compile("\\\\d\\{(\\d+)}"); + private static final int INTERFACE_FIRST_ROW = 8; + private static final int INTERFACE_LAST_ROW = 30; + + private final ObjectMapper objectMapper; + private final Clock clock; + + @Autowired + public DocumentGeneratorService(ObjectMapper objectMapper) { + this(objectMapper, Clock.system(ZoneId.of("Asia/Seoul"))); + } + + DocumentGeneratorService(ObjectMapper objectMapper, Clock clock) { + this.objectMapper = objectMapper; + this.clock = clock; + } + + public GeneratedDocument generateProgram(DocumentGenerationRequest request) { + ToolMetadata tool = validate(request); + if (!request.includeProgram() && !request.includeProcess() && !request.includeRevision()) { + throw new IllegalArgumentException("프로그램 문서에서 한 개 이상의 시트를 선택하세요."); + } + + String version = normalizeVersion(request.version()); + LocalDate today = LocalDate.now(clock); + try (InputStream input = resource(PROGRAM_TEMPLATE); + Workbook workbook = WorkbookFactory.create(input)) { + + Sheet programSheet = requiredSheet(workbook, "프로그램정의서"); + Sheet designTemplate = requiredSheet(workbook, "입출력정의"); + Sheet revisionSheet = request.includeRevision() + ? workbook.cloneSheet(workbook.getSheetIndex(designTemplate)) + : null; + + if (request.includeProgram()) { + populateProgramSheet(programSheet, tool, version, today); + } else { + workbook.removeSheetAt(workbook.getSheetIndex(programSheet)); + } + + if (request.includeProcess()) { + workbook.setSheetName(workbook.getSheetIndex(designTemplate), "처리설계"); + populateProcessSheet(designTemplate, tool); + } else { + workbook.removeSheetAt(workbook.getSheetIndex(designTemplate)); + } + + if (revisionSheet != null) { + workbook.setSheetName(workbook.getSheetIndex(revisionSheet), "개정이력"); + populateRevisionSheet(revisionSheet, version, today); + } + + workbook.setActiveSheet(0); + return output(workbook, programFileName(tool, version, today)); + } catch (IOException exception) { + throw new IllegalStateException("프로그램정의서 Excel 생성에 실패했습니다.", exception); + } + } + + public GeneratedDocument generateInterface(DocumentGenerationRequest request) { + ToolMetadata tool = validate(request); + String version = normalizeVersion(request.version()); + LocalDate today = LocalDate.now(clock); + try (InputStream input = resource(INTERFACE_TEMPLATE); + Workbook workbook = WorkbookFactory.create(input)) { + + populateInterfaceSheet(requiredSheet(workbook, "Request In"), tool, false); + populateInterfaceSheet(requiredSheet(workbook, "Response Out"), tool, true); + workbook.setActiveSheet(0); + return output(workbook, interfaceFileName(tool, version, today)); + } catch (IOException exception) { + throw new IllegalStateException("인터페이스정의서 Excel 생성에 실패했습니다.", exception); + } + } + + private void populateProgramSheet(Sheet sheet, ToolMetadata tool, String version, LocalDate today) { + String label = label(tool); + set(sheet, "B4", documentId(tool)); + set(sheet, "E4", version); + set(sheet, "H4", DISPLAY_DATE.format(today)); + set(sheet, "B5", label); + set(sheet, "E5", value(tool.getName())); + set(sheet, "H5", "생성 완료"); + set(sheet, "B6", value(tool.getCategoryKey())); + set(sheet, "E6", "AX HUB MCP Gateway"); + set(sheet, "H6", "자동 생성"); + set(sheet, "B9", defaultValue(tool.getDescription(), "설명이 등록되지 않은 Tool입니다.")); + set(sheet, "B10", label); + set(sheet, "E10", tool.getOperationType() == null ? "-" : String.valueOf(tool.getOperationType())); + set(sheet, "H10", Boolean.FALSE.equals(tool.getVisible()) ? "비공개" : "공개"); + set(sheet, "B11", schemaFields(tool.getParametersSchema()).size() + + "개 파라미터가 Tool JSON 스키마에서 자동 매핑되었습니다."); + set(sheet, "B14", value(tool.getCategoryKey())); + set(sheet, "E14", value(tool.getPodUrl())); + set(sheet, "H14", value(tool.getIntegrationType())); + set(sheet, "B15", value(tool.getEndpoint())); + set(sheet, "E15", value(tool.getMciServiceId())); + set(sheet, "H15", formatTimeout(tool.getTimeoutMillis())); + set(sheet, "B16", yesNo(tool.getRequiresApproval())); + set(sheet, "E16", yesNo(tool.getReadOnlyHint())); + set(sheet, "H16", yesNo(tool.getIdempotentHint())); + set(sheet, "B21", label); + set(sheet, "C21", "Tool 입력 스키마에 따라 요청 파라미터를 검증합니다."); + set(sheet, "B22", defaultValue(tool.getIntegrationType(), "REST")); + set(sheet, "H22", value(tool.getMciServiceId())); + set(sheet, "A26", "※ ToolMetadata를 기준으로 자동 생성된 문서입니다. 업무 규칙과 승인 정보는 담당자 검토가 필요합니다."); + } + + private void populateProcessSheet(Sheet sheet, ToolMetadata tool) { + CellStyle title = style(sheet, "A1"); + CellStyle section = style(sheet, "A2"); + CellStyle header = style(sheet, "A3"); + CellStyle data = style(sheet, "A4"); + CellStyle note = style(sheet, "A9"); + resetSheet(sheet, 9, 9); + + mergeSet(sheet, "A1:I1", "처리설계", title); + mergeSet(sheet, "A2:I2", label(tool) + " · 공통 처리 절차", section); + setStyled(sheet, 2, 0, "단계", header); + setStyled(sheet, 2, 1, "처리 주체", header); + mergeSet(sheet, "C3:F3", "처리 내용", header); + mergeSet(sheet, "G3:H3", "성공 조건", header); + setStyled(sheet, 2, 8, "비고", header); + + List> steps = List.of( + List.of("1", "Gateway", "호출자 인증과 Tool 실행 권한을 확인합니다.", "권한 검증 성공", "공통 처리"), + List.of("2", label(tool), "Tool 입력 스키마에 따라 요청 파라미터를 검증합니다.", "스키마 검증 성공", "자동 생성"), + List.of("3", defaultValue(tool.getIntegrationType(), "REST"), "등록된 엔드포인트 또는 서비스 ID로 대상 시스템을 호출합니다.", "정상 응답 수신", value(tool.getMciServiceId())), + List.of("4", "Gateway", "응답을 MCP 표준 결과로 변환하고 정책을 검사합니다.", "응답 정책 통과", "응답 정책"), + List.of("5", "Gateway", "감사 로그를 기록하고 호출자에게 결과를 반환합니다.", "응답 전송 완료", "추적 ID 포함") + ); + for (int index = 0; index < steps.size(); index++) { + int row = 3 + index; + List step = steps.get(index); + setStyled(sheet, row, 0, step.get(0), data); + setStyled(sheet, row, 1, step.get(1), data); + mergeSet(sheet, "C" + (row + 1) + ":F" + (row + 1), step.get(2), data); + mergeSet(sheet, "G" + (row + 1) + ":H" + (row + 1), step.get(3), data); + setStyled(sheet, row, 8, step.get(4), data); + sheet.getRow(row).setHeightInPoints(38); + } + mergeSet(sheet, "A9:I9", "※ 공통 처리 흐름은 ToolMetadata와 Gateway 정책을 기준으로 자동 작성되었습니다.", note); + } + + private void populateRevisionSheet(Sheet sheet, String version, LocalDate today) { + CellStyle title = style(sheet, "A1"); + CellStyle section = style(sheet, "A2"); + CellStyle header = style(sheet, "A3"); + CellStyle data = style(sheet, "A4"); + CellStyle note = style(sheet, "A9"); + resetSheet(sheet, 9, 9); + + mergeSet(sheet, "A1:I1", "개정이력", title); + mergeSet(sheet, "A2:I2", "문서 버전 및 변경 내역", section); + setStyled(sheet, 2, 0, "버전", header); + setStyled(sheet, 2, 1, "작성일", header); + mergeSet(sheet, "C3:D3", "작성자", header); + mergeSet(sheet, "E3:H3", "변경 내용", header); + setStyled(sheet, 2, 8, "비고", header); + + setStyled(sheet, 3, 0, version, data); + setStyled(sheet, 3, 1, DISPLAY_DATE.format(today), data); + mergeSet(sheet, "C4:D4", "Document Generator", data); + mergeSet(sheet, "E4:H4", "ToolMetadata 기준 최초 생성", data); + setStyled(sheet, 3, 8, "자동 생성", data); + mergeSet(sheet, "A6:I6", "※ 배포 전 담당자의 최종 검토가 필요합니다.", note); + } + + private void populateInterfaceSheet(Sheet sheet, ToolMetadata tool, boolean response) { + String label = label(tool); + String interfaceId = interfaceId(tool); + set(sheet, "A1", label + " 인터페이스 설계서"); + set(sheet, "C2", label); + set(sheet, "C3", defaultValue(tool.getDescription(), "설명이 등록되지 않은 Tool입니다.")); + set(sheet, "D4", value(tool.getEndpoint())); + set(sheet, "D5", "운영 URL 확인 필요"); + set(sheet, "A7", interfaceId); + set(sheet, "B7", response ? "데이터 수신시스템 · 응답 (Response Out)" : "데이터 송신시스템 · 요청 (Request In)"); + + clearInterfaceRows(sheet); + if (response) { + writeInterfaceField(sheet, INTERFACE_FIRST_ROW, interfaceId, "AX HUB\nMCP Gateway", "Body", + interfaceId + "_O", new SchemaField("resultData", "object", "결과 데이터", "-", true, "", false)); + set(sheet, "C33", prettyJson(Map.of("resultData", Map.of("status", "SUCCESS")))); + } else { + List fields = schemaFields(tool.getParametersSchema()); + if (fields.size() > INTERFACE_LAST_ROW - INTERFACE_FIRST_ROW + 1) { + throw new IllegalArgumentException("인터페이스 양식은 최대 23개 요청 필드를 지원합니다."); + } + for (int index = 0; index < fields.size(); index++) { + writeInterfaceField(sheet, INTERFACE_FIRST_ROW + index, interfaceId, + defaultValue(tool.getIntegrationType(), "Tool"), "Body", interfaceId + "_I", fields.get(index)); + } + set(sheet, "C33", prettyJson(exampleFromSchema(tool.getParametersSchema()))); + } + set(sheet, "A34", "※ ToolMetadata를 기준으로 자동 생성된 검토용 문서입니다."); + } + + private void writeInterfaceField(Sheet sheet, int rowIndex, String interfaceId, String system, String level, + String store, SchemaField field) { + if (rowIndex == INTERFACE_FIRST_ROW) { + setStyled(sheet, rowIndex, 0, interfaceId, style(sheet, "A9")); + setStyled(sheet, rowIndex, 1, system, style(sheet, "B9")); + setStyled(sheet, rowIndex, 2, level, style(sheet, "C9")); + } + setStyled(sheet, rowIndex, 3, store, style(sheet, "D9")); + setStyled(sheet, rowIndex, 4, defaultValue(field.description(), field.path()), style(sheet, "E9")); + setStyled(sheet, rowIndex, 5, field.path(), style(sheet, "F9")); + setStyled(sheet, rowIndex, 6, field.type(), style(sheet, "G9")); + setStyled(sheet, rowIndex, 7, field.length(), style(sheet, "H9")); + setStyled(sheet, rowIndex, 8, "", style(sheet, "I9")); + setStyled(sheet, rowIndex, 9, field.coded() ? "Y" : "N", style(sheet, "J9")); + String note = (field.required() ? "필수 · " : "") + "ToolMetadata"; + if (!field.example().isBlank()) { + note += " · 예시: " + field.example(); + } + setStyled(sheet, rowIndex, 10, note, style(sheet, "K9")); + } + + private void clearInterfaceRows(Sheet sheet) { + for (int rowIndex = INTERFACE_FIRST_ROW; rowIndex <= INTERFACE_LAST_ROW; rowIndex++) { + for (int column = 3; column <= 10; column++) { + setStyled(sheet, rowIndex, column, "", style(sheet, "D9")); + } + } + set(sheet, "A9", ""); + set(sheet, "B9", ""); + set(sheet, "C9", ""); + } + + private List schemaFields(Map schema) { + List fields = new ArrayList<>(); + collectSchemaFields(schema, "", Set.of(), fields); + return fields; + } + + @SuppressWarnings("unchecked") + private void collectSchemaFields(Map schema, String prefix, Set inheritedRequired, + List fields) { + if (schema == null) { + return; + } + Object requiredValue = schema.get("required"); + Set required = requiredValue instanceof Collection values + ? values.stream().map(String::valueOf).collect(java.util.stream.Collectors.toSet()) + : inheritedRequired; + Object propertiesValue = schema.get("properties"); + if (!(propertiesValue instanceof Map properties)) { + return; + } + for (Map.Entry entry : properties.entrySet()) { + String name = String.valueOf(entry.getKey()); + if (!(entry.getValue() instanceof Map rawNode)) { + continue; + } + Map node = (Map) rawNode; + String path = prefix.isBlank() ? name : prefix + "." + name; + String type = String.valueOf(node.getOrDefault("type", "string")); + String description = value(node.get("description")); + String length = schemaLength(node); + String example = schemaExample(node); + boolean coded = node.get("enum") instanceof Collection values && !values.isEmpty(); + fields.add(new SchemaField(path, type, description, length, required.contains(name), example, coded)); + if ("object".equals(type)) { + collectSchemaFields(node, path, Set.of(), fields); + } else if ("array".equals(type) && node.get("items") instanceof Map items) { + collectSchemaFields((Map) items, path + "[]", Set.of(), fields); + } + } + } + + private String schemaLength(Map node) { + Object length = node.get("maxLength"); + if (length == null) { + length = node.get("length"); + } + if (length != null) { + return String.valueOf(length); + } + Object pattern = node.get("pattern"); + if (pattern != null) { + Matcher matcher = PATTERN_LENGTH.matcher(String.valueOf(pattern)); + if (matcher.find()) { + return matcher.group(1); + } + } + return "-"; + } + + private String schemaExample(Map node) { + Object example = node.get("example"); + if (example == null && node.get("examples") instanceof List examples && !examples.isEmpty()) { + example = examples.get(0); + } + if (example == null) { + example = node.get("default"); + } + return value(example); + } + + @SuppressWarnings("unchecked") + private Object exampleFromSchema(Map schema) { + if (schema == null) { + return Map.of(); + } + Object type = schema.get("type"); + if ("object".equals(type) || schema.get("properties") instanceof Map) { + Map result = new LinkedHashMap<>(); + Object propertiesValue = schema.get("properties"); + if (propertiesValue instanceof Map properties) { + for (Map.Entry entry : properties.entrySet()) { + if (entry.getValue() instanceof Map node) { + result.put(String.valueOf(entry.getKey()), exampleFromSchema((Map) node)); + } + } + } + return result; + } + Object example = schema.get("example"); + if (example == null && schema.get("examples") instanceof List examples && !examples.isEmpty()) { + example = examples.get(0); + } + if (example == null) { + example = schema.get("default"); + } + if (example != null) { + return example; + } + return switch (String.valueOf(type)) { + case "integer", "number" -> 0; + case "boolean" -> false; + case "array" -> schema.get("items") instanceof Map items + ? List.of(exampleFromSchema((Map) items)) : List.of(); + default -> ""; + }; + } + + private String prettyJson(Object value) { + try { + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(value); + } catch (JsonProcessingException exception) { + throw new IllegalStateException("JSON 예시 생성에 실패했습니다.", exception); + } + } + + private GeneratedDocument output(Workbook workbook, String fileName) throws IOException { + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + workbook.write(output); + return new GeneratedDocument(fileName, output.toByteArray()); + } + } + + private InputStream resource(String path) throws IOException { + return new ClassPathResource(path).getInputStream(); + } + + private ToolMetadata validate(DocumentGenerationRequest request) { + if (request == null || request.tool() == null) { + throw new IllegalArgumentException("ToolMetadata가 필요합니다."); + } + ToolMetadata tool = request.tool(); + if (isBlank(tool.getUid()) && isBlank(tool.getName())) { + throw new IllegalArgumentException("Tool UID 또는 Tool 이름이 필요합니다."); + } + return tool; + } + + private String programFileName(ToolMetadata tool, String version, LocalDate today) { + return safeFilename(label(tool)) + "_프로그램정의서_v" + version + "_" + FILE_DATE.format(today) + ".xlsx"; + } + + private String interfaceFileName(ToolMetadata tool, String version, LocalDate today) { + return safeFilename(label(tool)) + "_인터페이스정의서_v" + version + "_" + FILE_DATE.format(today) + ".xlsx"; + } + + private String normalizeVersion(String version) { + String normalized = isBlank(version) ? "1.0" : version.trim().replaceFirst("^[vV]", ""); + if (!normalized.matches("[0-9A-Za-z._-]+")) { + throw new IllegalArgumentException("버전은 영문, 숫자, 점, 밑줄, 하이픈만 사용할 수 있습니다."); + } + return normalized; + } + + private String safeFilename(String value) { + String result = defaultValue(value, "tool").replaceAll("[\\\\/:*?\"<>|]", "_").trim(); + return result.isEmpty() ? "tool" : result; + } + + private String label(ToolMetadata tool) { + return defaultValue(tool.getDisplayName(), defaultValue(tool.getName(), tool.getUid())); + } + + private String documentId(ToolMetadata tool) { + String category = defaultValue(tool.getCategoryKey(), "ETC").toUpperCase(Locale.ROOT); + String uid = defaultValue(tool.getUid(), tool.getName()).replaceAll("[^0-9A-Za-z]", ""); + uid = uid.length() > 7 ? uid.substring(0, 7) : uid; + return "AXHUB-FS-" + category + "-" + uid.toUpperCase(Locale.ROOT); + } + + private String interfaceId(ToolMetadata tool) { + int hash = Objects.hash(tool.getUid(), tool.getName()); + return "AXHUB" + String.format(Locale.ROOT, "%05d", Math.floorMod(hash, 100_000)); + } + + private String formatTimeout(Long timeout) { + return timeout == null ? "-" : String.format(Locale.ROOT, "%,d ms", timeout); + } + + private String yesNo(Boolean value) { + return Boolean.TRUE.equals(value) ? "Y" : "N"; + } + + private Sheet requiredSheet(Workbook workbook, String name) { + Sheet sheet = workbook.getSheet(name); + if (sheet == null) { + throw new IllegalStateException("Excel 템플릿에 '" + name + "' 시트가 없습니다."); + } + return sheet; + } + + private void resetSheet(Sheet sheet, int rows, int columns) { + for (int index = sheet.getNumMergedRegions() - 1; index >= 0; index--) { + sheet.removeMergedRegion(index); + } + for (int rowIndex = 0; rowIndex < rows; rowIndex++) { + Row row = row(sheet, rowIndex); + for (int column = 0; column < columns; column++) { + cell(row, column).setBlank(); + } + } + } + + private void mergeSet(Sheet sheet, String range, String value, CellStyle style) { + CellRangeAddress address = CellRangeAddress.valueOf(range); + sheet.addMergedRegion(address); + for (int rowIndex = address.getFirstRow(); rowIndex <= address.getLastRow(); rowIndex++) { + for (int column = address.getFirstColumn(); column <= address.getLastColumn(); column++) { + Cell target = cell(row(sheet, rowIndex), column); + target.setCellStyle(style); + if (rowIndex == address.getFirstRow() && column == address.getFirstColumn()) { + target.setCellValue(value); + } + } + } + } + + private void set(Sheet sheet, String reference, String value) { + CellReference cellReference = new CellReference(reference); + Cell target = cell(row(sheet, cellReference.getRow()), cellReference.getCol()); + target.setBlank(); + target.setCellValue(defaultValue(value, "")); + } + + private void setStyled(Sheet sheet, int rowIndex, int column, String value, CellStyle style) { + Cell target = cell(row(sheet, rowIndex), column); + target.setBlank(); + target.setCellStyle(style); + target.setCellValue(defaultValue(value, "")); + } + + private CellStyle style(Sheet sheet, String reference) { + CellReference cellReference = new CellReference(reference); + return cell(row(sheet, cellReference.getRow()), cellReference.getCol()).getCellStyle(); + } + + private Row row(Sheet sheet, int rowIndex) { + Row row = sheet.getRow(rowIndex); + return row == null ? sheet.createRow(rowIndex) : row; + } + + private Cell cell(Row row, int column) { + Cell cell = row.getCell(column); + return cell == null ? row.createCell(column) : cell; + } + + private String value(Object value) { + return value == null ? "" : String.valueOf(value); + } + + private String defaultValue(String value, String fallback) { + return isBlank(value) ? (fallback == null ? "" : fallback) : value; + } + + private boolean isBlank(String value) { + return value == null || value.isBlank(); + } + + private record SchemaField(String path, String type, String description, String length, + boolean required, String example, boolean coded) { + } +} diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/document/GeneratedDocument.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/document/GeneratedDocument.java new file mode 100644 index 00000000..6b1c8950 --- /dev/null +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/document/GeneratedDocument.java @@ -0,0 +1,4 @@ +package io.shinhanlife.dap.mcg.document; + +public record GeneratedDocument(String fileName, byte[] content) { +} diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/DtoDownloadProxyController.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/DtoDownloadProxyController.java new file mode 100644 index 00000000..53175d31 --- /dev/null +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/presentation/DtoDownloadProxyController.java @@ -0,0 +1,72 @@ +package io.shinhanlife.dap.mcg.presentation; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClientResponseException; + +/** + * Gateway 화면에서 들어온 DTO 엑셀 요청을 실제 DTO 클래스가 있는 Tool Pod로 전달한다. + * 브라우저가 동일 출처(8081)만 호출하도록 하여 CORS와 배포 주소 차이를 숨긴다. + */ +@RestController +public class DtoDownloadProxyController { + + private static final MediaType XLSX_MEDIA_TYPE = MediaType.parseMediaType( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + + private final RestClient restClient; + private final String toolPodUrl; + + public DtoDownloadProxyController( + RestClient.Builder restClientBuilder, + @Value("${mcp.gateway.fallback.default-url:http://localhost:8084}") String toolPodUrl) { + this.restClient = restClientBuilder.build(); + this.toolPodUrl = toolPodUrl.replaceAll("/+$", ""); + } + + @GetMapping("/dto-download/options") + public List options() { + // 셀렉트박스에 표시할 DTO 이름 목록을 Tool Pod에서 조회한다. + List options = restClient.get() + .uri(toolPodUrl + "/dto-download/options") + .retrieve() + .body(new ParameterizedTypeReference<>() {}); + return options == null ? List.of() : options; + } + + @GetMapping("/dto-download/{dtoName}") + public ResponseEntity download(@PathVariable String dtoName) { + byte[] workbook; + try { + // 생성된 엑셀 바이트를 그대로 브라우저에 전달한다. + workbook = restClient.get() + .uri(toolPodUrl + "/dto-download/{dtoName}", dtoName) + .retrieve() + .body(byte[].class); + } catch (RestClientResponseException error) { + // 형식 불일치 등의 상태 코드와 오류 메시지도 변경 없이 전달한다. + return ResponseEntity.status(error.getStatusCode()) + .contentType(error.getResponseHeaders() != null + && error.getResponseHeaders().getContentType() != null + ? error.getResponseHeaders().getContentType() : MediaType.TEXT_PLAIN) + .body(error.getResponseBodyAsByteArray()); + } + String fileName = dtoName + ".xlsx"; + return ResponseEntity.ok() + .contentType(XLSX_MEDIA_TYPE) + .contentLength(workbook == null ? 0 : workbook.length) + .header(HttpHeaders.CONTENT_DISPOSITION, + ContentDisposition.attachment().filename(fileName).build().toString()) + .body(workbook == null ? new byte[0] : workbook); + } +} diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/sync/CustomWebMvcSseServerTransportProvider.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/sync/CustomWebMvcSseServerTransportProvider.java index 44cfceae..a0df7722 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/sync/CustomWebMvcSseServerTransportProvider.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/sync/CustomWebMvcSseServerTransportProvider.java @@ -220,7 +220,7 @@ public class CustomWebMvcSseServerTransportProvider implements McpServerTranspor // Custom 프로토콜: 1회 요청당 1응답 후 종료 (스트림을 닫아버림) // 클라이언트가 한 번의 POST 후 응답을 받고 연결을 끊기 때문 - // this.emitter.complete(); + // // this.emitter.complete(); } } catch (Exception e) { log.error("Error sending message to SSE emitter", e); diff --git a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/sync/DynamicMcpServerManager.java b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/sync/DynamicMcpServerManager.java index 850f9c86..f21e291b 100644 --- a/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/sync/DynamicMcpServerManager.java +++ b/dap-gateway/src/main/java/io/shinhanlife/dap/mcg/sync/DynamicMcpServerManager.java @@ -55,6 +55,7 @@ public class DynamicMcpServerManager { getOrCreateServer("email"); getOrCreateServer("oth"); getOrCreateServer("sample"); + getOrCreateServer("smp"); } private McpSyncServer getOrCreateServer(String categoryKey) { diff --git a/dap-gateway/src/main/resources/document-templates/excel/interface-definition-template.xlsx b/dap-gateway/src/main/resources/document-templates/excel/interface-definition-template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..e7cba495c85d4c2d4a17c33c4e951f6fae15960f GIT binary patch literal 7170 zcmai(1z3}9+s8p*2#k;%r6?sxi=-eRDJd!4(lwCoR5}OJI7AdA1ym$Pj}gLXq+3C{ zVSIz<{XTv0dESq4>^_d|`k&Vs*LMGQo@$C%*yI=(7*{b&p5q%jXtdXmU}9i!V_{&B zpF8#x>E&$cYRKj3U|*Hkt|=FSa9$uP@cnwe(;Mb+cyP;cX9+wz7nadS#gx=lWp&Tx$b2$pV$$!Z@(xmt0xoy zCpC#jV3SOHLi2OxanHkrY9HXUYIonnhEn9;PsJVfhmx}fcl78!tRwo+afPdwv;f{4 zn6%FPKxu%pLg)DkwY@eR)4@`EyJ!j3}4~2$u+6+*-(Jj@WFKh$mG_ioP8gz|Und-+eJkb&>vG@OlspD~6?h|bF2{s4qosmD<} zoIKu4k+0YRKm#pjxwq!Bth-3@zpx)J!oCy!JuGiW3{)RF91V0>#OSN1J-3Gw*v`z! z$?p72#KbGBHuC_(&mPT-Dh@Y?zN@Zf$j^>vIl#14rx_Y_TdCaNhO^@VMuaW-)WZ^`Gs4FE-o^u zslrS_^RqZ~Rey1%!nXV4@ z7(M6@!1{aiWEs+tUFfL!uV7%%{1yFQb^SdD7d5U+=#6ZW07e*FcKBsf6Wp2|iMnb; z8)_vs*_hRdA*qu;$qU~@^BR<4w6xKewrR%hE$fAIA{X7c@%6U#0C#> zKgaY05$IDeHbgU9%_Xr`AW>T8hunb-P5Q^p&%^NUA9+s4%99SXK%|@Bw|cV4Knern zEIR`rY&me>dW~>PntpR9mf;hh>+AJ(BA0TIIxY^kLdUN`h=GCrKR*GkZeI45uIKgd z)=`ej;3277FD>(3PmB*`qA2TRQQQ4$B#`3$tTB+;oN;_K@pQN9@M`hcTE!Y=f@0g; zOF^f$A*^q;d~c?&5hw_4PoKC0FrQwF!&4MHSOfWwNH9>C@xID5B{nJx^+3hk84R0^ z0Gi|QUrD;&mCP=#2Z`p(Wb{H1bnU1#$;2ibMGho}HQt7>u)TqZWe97>UJXGJVmLOv zOk|j~D!Bi#*f(a_V}Z<5S`(QeiSVHAbg4rHTYEp9CexvQ(FM_J-Pcgn=JwlmT5&3! zEO3l?Is_?ZmAYN0B2DF#P&MvBkWWs@F+lKjq+KzO`1y+YyPHKHbzW%%ak=^oG6Ub; z6lUffm|n@&;0lyiy$)>l`Uo5vj8^RinDTPDtV&wxrYzsAmxB$S?xD(P%1u6A9XfVy z365|m6Xj@Ib*2)5`F91oe&OR@Ucb_{NNtFX5vrD_X>K66NOVv#Yb8O%GMgzCbAUz zE;MPmjQU$xcfU`k(m1SnCtoYrf#v$EiaZ+}vAi-OOzgU=dn4K(NORLs*^ekjmvN9l z<8_>S*gB)}v*WdcT%|FofbcXA689Tslad&kG60T5`A0(tdn!t{ zdKSs}s)>b0`460$q=pG48bGZ3(nP^5^nfneFA`jI3|NqxSl?Qf?*nT`$&w*8ZeQ zmEhs4rJu*Nqvo)w&Ar{nr9MT1Zg7-BJ5+pP{|h&C@Ts7l=Gc=vbx4uc4N-^%o`zBz6+eY9;{&i(#{3#Vmx{m4G4w z+JQts)d$$w0rhu<5bq(A&EBu_H2f8eRnEuHGa8;R#7%^y)&CqNPz~r)+{8=DHhV`V zL~F@JDdY8f#_!|F&_$FJ{)P&PV7%$;BJP9IOcSdoX+k1K4}ABI$tu`S_Fj|38XMKo zN6f+X*ckj`2Gm(cJ)%sf$Yo(X?BO=%Aj0NlP+KDpOL#d?ba!{Md{=+5f=zz@ND9Pd zDz1B{G~z>J*8;nf2`CNcBIA5V0wDG+FG1|n!4`KH9B2u^2y`9N`=b2(iI zo+iZ0bE9o#)$?dCcpf#l(`w@>qOx^QwEn7b_E*yI6(8DWYmjU^4 z>`{7I=N4W@mO=OfmC)YxiC3@R1uIZIY$)krMY@i$DyQVB!nq;Gu>nW|_w-$GojQ%1 zyW+e;`CSk>uH!U0){_6!>jivQ5`G}m9E8=J25M{K5fDs5Xx;19dUm*-3MpV`Q3lrG z=NBXl@G1$bi3w3Y>ANzB4>CG~%4h>)J#lzU>FM zOsh1Avfj!I+vtnhfa@MuNF9vMNFVG^;eaQIr%seVy7Zpf_U>ZA=upuR(eR0WaD?d? zNcPg^-v*P`zrOzAsuQtO+uE34{wkn3k0)Li{KhKojT1^Likylzn?7{vOxSlPd2&er zVd}YKD1-{~6nxsARD{49Tacau!y|@Qeki6FZPP`MJ_K`E%fU?4O1ZdSJVLNyxepgG zCoh0dKdg`16%JN+ciOS^^mK`)9<=E=^-@|?C&*S_`%#2g?&MtP$1+$0~Q{RpB%U(u^|+{eRj2Q`JckjFzKb2E4)VgX>cc=JFy zviTGk!TiXHia6V7H-#BaGI=yK zkWQwj%%*gA#K5za3&v||Xfbd*cPcCXJT zB~H-YED25wKKFx#C;+|PWk23pD!dK)uM4ac-iG|Q1#rjPB-EX|1NkQBxw%y!KLHS? z>FAS;#RMzGn+MTlOu^5%M^=e2@Z4}QIvz{JvYXoWI>bYR=;-G`4DHy$lpW=VS-$EO z74_KcKSIupkQT&$`@;w;b5WmSE{)iQi9CRkL`+&B4Ik#cr&nyhVhZ%=LL|202_|KnJg6EDqS-pEW1BSeE-jh$;LI`(58Z z;6=fR!(DETiw-Y>NQ$4%ma(Ypj^ z3S8bqoDIwuZzX{BHG&bpbKBO+@1-A#KYfu(C7`!%Mm(2LU?}mi>vbNX2Hk6+s`X>r z!Ny-6Bo6!9+|@l+$f;-*(7j%I>h-QAgwk)stv;>7PlOV2uNJXxwNNtxLZgFAQ8NJ6yy|@~3ihD^Uub?jpx~>S@_I z(sLydR&&;l2^*t%B(bbW!MTmm8y#3TeLZPn1JdYKLyH7)E9I*CL?$mV77ZnBx=>iUi1>fx9AJ!HMl=bU%ZK~Om7i?eKAR$iL8SmL0j%|?zoKA!3 z?lEJ%h0vXBS;N}JkZ^?mMx6$Kk}%lFAW z0-raH|ITqKNoiBQXpY-={a23TJLkCfzc?-u%Mds!!<3L*oI1+Iq3)~P&tUU;f-qWo zT#vbrY(+%oapS{zhC-xivwaJ}d=n<02{m|z#c0WNyj#4JOZmI#j+$9VneZliHY-)R zH2RmP^kxF{>58kixHr#oQg2qmuxmsSPTmtP5~~rH+PaxV+nGt$@4MbXp7mm@V}2gX z#gz5VUz+6X6z|mjT)yjsaPgH2%B#^E**aZ&`gZE{ILB;XwVw67TTRy zBL-gmS=^UvOo~`o=x_Wls zE#qL1Dm2DH1>KfWHs=`UG12>KP!^4Gdirc=jDrp6);Pw=509349HHnvbUeH>^u@)n z2R%#aYgG5;&R9c?+^q2}Odh12ep`EX`hHLf`hS*FsfA8i^>6Ze`Hz_$H5F)HEZm{hoC6GgfDQUQiKXRuLkKCgSBp9)xw9qSfjPh~ z`klDEYl7DKa^E}G7#&eekbv)8q4Qpu6$QWH8+}z`-lRQJLp#-LM9K`x2}{x99{6@JMDt3s7~ezy zX?7+qRRoi-zkuIfsM3v`q>Xtq5%CNZDy;;+!C$gzGYTO%=X_t6?eIfsg5LL*W507#a2PL9Fsdji0v3A~;2mM>y>cUvDRA0n`Bq zHTwZaC86XHfL`eqQQt%ZkDFbS@853coWb2~NV9OHz&$;3s8;{w_r zA?fTmVr96gEsmBLkM5)^#kI{;KGb&SA=dqSGG4B7u#W0xf3lohH33mK-v$-Lpjqy3 zTPVmmpbWF09B+7dRgDtep}PSAhNVF195=eDDcF>o+S_MfHl93rCvE{!sAFQ%>=Rz0 z1JO_J?VHsTc}wwe9w#}KiK*u8#q*|V0^yH&qr*+zfaWG3vgRk>lvuoW5A@wOo8d(d zk0v@{Ofa*6_M1x#$W_3bxRBuhOPR2yr!!J<@1~!wqP=L4Qx-zSdJh-KB_X23 zh7d107y4^TVMPRxqD{K!m51IOPvj(f^NfrP)KXG<=|&u)Qoi1A-*^xK%VC*8>cIAW zia#t0`<~Gm*9+I(s*GVuk9ldNFP-YtQR7(Exs%iQ$cD(}R7bjDSno626ifP=XzdTTA(DBEvQ*o?uY)Xm^?pT$Q2f6(i40^*sF94t|BV7>Jt`Cw zxNS+^{jZGzhDm@h`)_yPs3|xLb6u$96Tn}7yr4&+x4RsQx?++#)_+}~nxu~V-xefN zo`%uAc6*BELLitNQ$yDAkA$6R-+SK|bxtAZoZbpPhYppfl1@{1Aw*Z@);V@*{!V5MNy?A(pj78V#t=Fg>~rk;qb7Dc7eb48=<^HC z{GH5BR0P;;VvH_?=v-X{{71?DpG@&OBmaYjXdf>K?;m`;K*Mt%FVOHGeC+>H02gax zIj_eR@YtW_kl&U(3w0BL?#rDfEe`e^-8%lV?cRk@T|>pXc6&9_JT76*D`4rE@%<#- z(d%D3chmr4Znp1U7gYH0b4{$0u0wbLji;&#}!f~Pm%=}|DYReRs^ z!pw?LdLE{!@(f9#+fk0XMQZRNfe5DqHN&Etpj@ z^Fa`1NHb33b9k$&8~E@9F?^?6JFMK`EkEbb)~;=(mZgKSgI@8%dp+bk-ec-#$rH%J zqOST!DV|21`$PQceGx`&KWV!uUBkXvcWzU>-oZ;3Galb7a7A8s*bBeDB1*e+$WFCF zuxE`*6scXS9q;2jLKEBW$`1!$D@1v1&+X3-r@|aQmYnUW*f`j{PVO50w1@5mV_qT0 z`u8~zy8U+X7l@t@{NvQ1-u`vD}-z&q#YMh#HPiB_#Obw(3vtl-8 z0goXS-C?dG-QM(?u1kzZz;>e1ur>1{vnLU(<62zOF<8L{c|k)<$gF6rj2POyB#w-- z@Oj^Oht!x6N+!N8Lffi1x$PD@4HNIXZp647aLKuiFg{jGs`NmsDO%g^9 zURmYY$D-Z;+zY`D&o7mkxYGU#IHgc z$cdCYA*n$o)Xt9 z!~6a<`!d9r4=-3m6vCM^-MINCGztf^2F)+*BNR`3iIz<+N103WZGAG_!h5uKe$rq8CRJVYcb_oPYMH=S0LupVifw9ZP7g=p#ZEYV@8ROhptQ z_(h^qOkgyT%ftMNZL#qR)MY;~RVvQO^(^tp?#wGfpTip$i7&q6_}x86zg^Co0P}FkWJ&Ijbga8#hM6hS zK}?K$1tQZKNrORfj|lU7+Qj*NH8DMz7N!gTYucg2#uH=We4^*{lpNpb|#)NZ(cL~gmh`IqF<>&-DOtMZy zGyn>SedQZc10u4isQ>69E8t<4#OorGQT zRl!U={OWNHOwB%w9cQ+|CSd4b-iTxMlvKTw#=t$eR9=jJKS;>4DJLdu>r&_F^x0_m z$i_fd?X{!TzRF#}qUsb_mt7PMW=oFW@ek_FW)=5D=%Dg zHiY>R;5zH}YREIg!5EQeF|`Q;QaMo%xWr?a8NNevm=O9h6xN`swzNnwg4+0@t7iH^ z4;W!2AjtOUNZVWoGO^#a>TIFY|8hOGmg`IVksABjD9(kx2TjT(F5FArm_(kbfi#CW zr0LL5G$XDs!C5d#TWd^pGoz~tXkYwe*on*D$0-x=sWrT1+&IM>t*+xVGeX53)^paRQ#{|oc2L!!aA zWeG);7dWoCehhb-@ zdZv4vL#`C7q}D3ZyPpL$`1968#PlnYjAoHQTF{3Nx*6K)G{K^N)p5;y;ual@$OpmA z4o>m!9TUG;2orOSdDVvlB;TFNua2edfKx?`P>;$TR4+n<)_$brDcRm>3`vs zjy|%LCELtO%|6saRuGb*7(wDw78=(_%{?7jtK0EK;A*z0bp5fshUVhHHIL8%$lH~f zwzd98qkNr_;p8XdH0?n}Mo9`;zbF@&`#ajksmk+O?cmZC_pIFA_}ZYJS=%=q)q{?L z99t)wRn{WxY-QgG!SIBpIrHgZDc9V4=Itr&et{cE^j7}O=)1=iQ!+^L6#WfDZFE?P zlYhjT1?dJ^GNx(Ju{EVOUn(AT^fnvJhYNt#`zaH;j*}18hRSY=cC%cgKE}CE{HQ_1 zxnby_uLfv87fDY?FfXE5SccBL1Y@4S=3XV*?3zn_k83iqe)5&#P*;kPCITZH@z8lt zSfiK=7_xf)<5b!;!;rj92SWg~ulCA)_vy^50wk)lKtu@|uK?Dm(ykXv-hGl~$~AdP zhGv<8Acg(u0Dz1dKd@Bo;}+>Ej`Rb9sDue>1r~|(?Bq>-8d?p zb2Pb&V3v)r@eQ$+-{cFn*wyW;*iJ1rf%DNK$@KE*5LwzHo8q9R*b~nQDg$|?4`?yD z9H2Q4a7}Ld-Qp&yHvPyfgua98$L1ZWEm67v^hzr4F!Yhq4)h*cJUrCRqwpfy9?m=z zk);LoW+yy@cU1e=SVak09T4QVeAF>4pme~!apCq$gNyD0ZU>=FH-c+Z-lLy#)tsz! zymKq~>XKpa8ou$Idydfin~SAjy`8aqbMP}tY9N4M;km_3VER}VbH*LxNs7cZnXOeO z;)wW6n^!y%m+PG7=jS1<>S(^rco{+$Sh`q1;XGUR*P?~ry&xe2bHk?FQ*Mu`0&DvW z?=JjsFK?ZCQko(|=Ob$y+U!%~J7B+-9Tl3Zql@#}2YIqW+Mg3dn-8idejMYZfzLfV zR{KCwi37gm?IVoqIQb2m^v%dehY-JLrp0g_=V9!1F2JWWrky)PcRE|yUQo6Tk9cw7>3Od461AzTtF#n5du zDshOT`1jnbq@Z<-p^M#FsYzf+QmId zf3{#-3S15|`xF(2LsT7O?%JRG!v&y%`L4y+_r@=zyf7D>*zXm5NhB?!b{=4Sv9M3( zj#I((jjxb6*{6goH@(V@;w~K-wZWsU?*Z;HYd6?#p}6DJr;p-YUhz4Mre#TsCNgIouf8;M8$f?r2M=+md-jKSVy;^37&@6;fD$TMQ}@J-k2ZFS_12yNM_6>c zA*Faq;;08|jLqn05IRtzt4k~L0<%UeEgqkeqZYmtCr($)a38l0?$pvKE*Y%vL2Os2 z&s}GnNU4`B^VLxDR^kD%~m0=VNSdD4ll|N&%ZC23O2=A zYdgO1jA)mJj_0yYZH5is5FT;k`9OI4l2c3|UVrE7S%+*ZIX2kTy+|EX48bM&(Uu82 z3H2TwHR9f)XiqTP(A;*&X@ta9eWjzTB%&>$F%x{qInL;8H+>LMdB6724skRsQ*cNKOOw&h@cwLZDE zd7-V7mD2SAagS&c(p5jV-etP%cTIXt^A?ueI#HA)6nJQj_l9A68AMlY0*U5}&7@3$C9b*#FKz4rJIa8kte`Mx4O*e9`R^pqHt26K1Hit!;2u2e#*m!u4odhPtn)}SIPj^ zny^5FbcfMGL6|s)`6jP&z|E{vUv&UX3|8&Nj({1q=YUaW49=;c7r9v@gz~x!PZjfv zyIJ3%V{sUp$EK*D1`1#Z0wd_3OVjP52FqF0JBPayrY)pcC&?XZbRC5oaTm5Vx4P&Tg*Jl@RK z#;d|DTWRaAjIM<%aYWbw2|{6T(5V9K)#$OC|C>l=M^~od9KE!fCvz zg4wKi$2_!ctk3CM?qzzOu?8>FBb`jljHLxwuL`h-G_e3ro8bf)PUveuN|lf%SB&12omxr0^kZs$CAjH zo5_j#17uNbGS8EtSr8MM|@r8U7_zC4{1!E`dt z_i5vN|Aol5MN$dOSme?6ROOcnYgJ}!QW(9paB^5)=rW2-BL=;87(IfH5GoEW#*0yj zEH==<$&Awp10gVU!wNz%cSb`rfHKy?30MrgU3|f#ZN)F%eJ~h}s2&9{#>TxQ01XnI zVzX#c1{MY?sU7mL;>W0zu=>eT`j_)dCNQ^+{s0xWOVYCoNd7Vqta5w3*x=>IM=Wq> zjdVYWhy=&J{Frjs<84`clACv z3BNeCREt@pAV#B!VA_~Wdx=U$*wKz~44_2}1Wds9Eqg3=U-ht$4qc%WGl zS<{q9lGHLWbDpDWr*})kU6~6#GxOoKnlaic2Jq2*TJ%DS;}UXCGV77m*ZiwoBn3<$ zPA``i7KR<95~9j{0MlriFmv#eZ))@dA<~^zs^BLs4M|1rTD)q^h4Kyb*6BDT9f+Hg zzR5*1TD;(!lVn`hy+D%HzL46@(St+D{-*4vy^|T3zH3kPj3*nF(PPrRHPh0Z%u~d4 zajy@bgesz`FQs5c=+tbSB<~an&9*Oh_q6}+=GM!uC!Z*=+t*>HyYtoFP1oW3!=d-~ zrl;GA5L@@ore;mUt6!+tGw~H!2__0kF!g`i691vxKm1khQ;Zxlc!$Ryq$;g*A?v*h2$IgiOneLcb^0-6V)o1q-% zgI$H>V{;9k=wBpO{Jv8)P?y6}d81b@ov))(l- zC`Nb!?7$+T<{JrDohig5-9(7#r;oi-t>gNV{cUtcnA9grceta(uil)&1>mWFswYNIwJ9rWGxo@WrPU^ps0JyeDT7 z6)tP88Ik)s6pR;&fx|}Z1~Mf~W16tzB}x;BvKv?Y65M0niPd+`3fhB5$>Erb$k*d^ zW-?TxRTnAcOYg8Humdflc8Ggyq?yy|9s6qs6wA*dxM<@qcVtQ(O=xyU`n^74_7Mz# z3^38yMRn}TB?QKX*qt=r5oqkiTyvbb~hvOJZ^y~6fUrWv>dhRUJJ1U zr>IB(JFz$zrwW*8-UVo9g+k{-d(;;r5ymiCr;VlPa9MmA`k)V_IK0G!tc99Wu_2NV z&1$7efg<^e+Q}95-5cp{{#c-scm5k<_lh%YPxmW^=LO0PL-(SQ0Z)DH{Y!tu$D2VK zW;lu-wC@0%Hs0i%p_)Z0xlit5PHmYtvW)~rsg81EwH7rJ7@G-KWK!WLqK)$xT=Z*O zxGwC~U~OGeDA>`btel!XJ^R#7J_uQsy2V|g2weqPbQBb?U-T3ejTq(k`;R{x^SAFm z{$2jV+Yn9Fe>d=F@A|fX|62lYpzt5v{J$0d>=NI$wtq{`4Le3?l0wkAQykz`LjCT*5Ka~b~C5{KZ*YX!2h=LXKMYs6<3n~ ZXysR3*Hp#Cy1gIH%_sCG*jK+){~yN}^(g=V literal 0 HcmV?d00001 diff --git a/dap-gateway/src/main/resources/static/lib/chart.js b/dap-gateway/src/main/resources/static/lib/chart.js new file mode 100644 index 00000000..008464fa --- /dev/null +++ b/dap-gateway/src/main/resources/static/lib/chart.js @@ -0,0 +1,14 @@ +/*! + * Chart.js v4.5.1 + * https://www.chartjs.org + * (c) 2025 Chart.js Contributors + * Released under the MIT License + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";var t=Object.freeze({__proto__:null,get Colors(){return Jo},get Decimation(){return ta},get Filler(){return ba},get Legend(){return Ma},get SubTitle(){return Pa},get Title(){return ka},get Tooltip(){return Na}});function e(){}const i=(()=>{let t=0;return()=>t++})();function s(t){return null==t}function n(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.slice(0,7)&&"Array]"===e.slice(-6)}function o(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}function a(t){return("number"==typeof t||t instanceof Number)&&isFinite(+t)}function r(t,e){return a(t)?t:e}function l(t,e){return void 0===t?e:t}const h=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:+t/e,c=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function d(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function u(t,e,i,s){let a,r,l;if(n(t))if(r=t.length,s)for(a=r-1;a>=0;a--)e.call(i,t[a],a);else for(a=0;at,x:t=>t.x,y:t=>t.y};function v(t){const e=t.split("."),i=[];let s="";for(const t of e)s+=t,s.endsWith("\\")?s=s.slice(0,-1)+".":(i.push(s),s="");return i}function M(t,e){const i=y[e]||(y[e]=function(t){const e=v(t);return t=>{for(const i of e){if(""===i)break;t=t&&t[i]}return t}}(e));return i(t)}function w(t){return t.charAt(0).toUpperCase()+t.slice(1)}const k=t=>void 0!==t,S=t=>"function"==typeof t,P=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0};function D(t){return"mouseup"===t.type||"click"===t.type||"contextmenu"===t.type}const C=Math.PI,O=2*C,A=O+C,T=Number.POSITIVE_INFINITY,L=C/180,E=C/2,R=C/4,I=2*C/3,z=Math.log10,F=Math.sign;function V(t,e,i){return Math.abs(t-e)t-e)).pop(),e}function N(t){return!function(t){return"symbol"==typeof t||"object"==typeof t&&null!==t&&!(Symbol.toPrimitive in t||"toString"in t||"valueOf"in t)}(t)&&!isNaN(parseFloat(t))&&isFinite(t)}function H(t,e){const i=Math.round(t);return i-e<=t&&i+e>=t}function j(t,e,i){let s,n,o;for(s=0,n=t.length;sl&&h=Math.min(e,i)-s&&t<=Math.max(e,i)+s}function et(t,e,i){i=i||(i=>t[i]1;)s=o+n>>1,i(s)?o=s:n=s;return{lo:o,hi:n}}const it=(t,e,i,s)=>et(t,i,s?s=>{const n=t[s][e];return nt[s][e]et(t,i,(s=>t[s][e]>=i));function nt(t,e,i){let s=0,n=t.length;for(;ss&&t[n-1]>i;)n--;return s>0||n{const i="_onData"+w(e),s=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...e){const n=s.apply(this,e);return t._chartjs.listeners.forEach((t=>{"function"==typeof t[i]&&t[i](...e)})),n}})})))}function rt(t,e){const i=t._chartjs;if(!i)return;const s=i.listeners,n=s.indexOf(e);-1!==n&&s.splice(n,1),s.length>0||(ot.forEach((e=>{delete t[e]})),delete t._chartjs)}function lt(t){const e=new Set(t);return e.size===t.length?t:Array.from(e)}const ht="undefined"==typeof window?function(t){return t()}:window.requestAnimationFrame;function ct(t,e){let i=[],s=!1;return function(...n){i=n,s||(s=!0,ht.call(window,(()=>{s=!1,t.apply(e,i)})))}}function dt(t,e){let i;return function(...s){return e?(clearTimeout(i),i=setTimeout(t,e,s)):t.apply(this,s),e}}const ut=t=>"start"===t?"left":"end"===t?"right":"center",ft=(t,e,i)=>"start"===t?e:"end"===t?i:(e+i)/2,gt=(t,e,i,s)=>t===(s?"left":"right")?i:"center"===t?(e+i)/2:e;function pt(t,e,i){const n=e.length;let o=0,a=n;if(t._sorted){const{iScale:r,vScale:l,_parsed:h}=t,c=t.dataset&&t.dataset.options?t.dataset.options.spanGaps:null,d=r.axis,{min:u,max:f,minDefined:g,maxDefined:p}=r.getUserBounds();if(g){if(o=Math.min(it(h,d,u).lo,i?n:it(e,d,r.getPixelForValue(u)).lo),c){const t=h.slice(0,o+1).reverse().findIndex((t=>!s(t[l.axis])));o-=Math.max(0,t)}o=Z(o,0,n-1)}if(p){let t=Math.max(it(h,r.axis,f,!0).hi+1,i?0:it(e,d,r.getPixelForValue(f),!0).hi+1);if(c){const e=h.slice(t-1).findIndex((t=>!s(t[l.axis])));t+=Math.max(0,e)}a=Z(t,o,n)-o}else a=n-o}return{start:o,count:a}}function mt(t){const{xScale:e,yScale:i,_scaleRanges:s}=t,n={xmin:e.min,xmax:e.max,ymin:i.min,ymax:i.max};if(!s)return t._scaleRanges=n,!0;const o=s.xmin!==e.min||s.xmax!==e.max||s.ymin!==i.min||s.ymax!==i.max;return Object.assign(s,n),o}class xt{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(t,e,i,s){const n=e.listeners[s],o=e.duration;n.forEach((s=>s({chart:t,initial:e.initial,numSteps:o,currentStep:Math.min(i-e.start,o)})))}_refresh(){this._request||(this._running=!0,this._request=ht.call(window,(()=>{this._update(),this._request=null,this._running&&this._refresh()})))}_update(t=Date.now()){let e=0;this._charts.forEach(((i,s)=>{if(!i.running||!i.items.length)return;const n=i.items;let o,a=n.length-1,r=!1;for(;a>=0;--a)o=n[a],o._active?(o._total>i.duration&&(i.duration=o._total),o.tick(t),r=!0):(n[a]=n[n.length-1],n.pop());r&&(s.draw(),this._notify(s,i,t,"progress")),n.length||(i.running=!1,this._notify(s,i,t,"complete"),i.initial=!1),e+=n.length})),this._lastDate=t,0===e&&(this._running=!1)}_getAnims(t){const e=this._charts;let i=e.get(t);return i||(i={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},e.set(t,i)),i}listen(t,e,i){this._getAnims(t).listeners[e].push(i)}add(t,e){e&&e.length&&this._getAnims(t).items.push(...e)}has(t){return this._getAnims(t).items.length>0}start(t){const e=this._charts.get(t);e&&(e.running=!0,e.start=Date.now(),e.duration=e.items.reduce(((t,e)=>Math.max(t,e._duration)),0),this._refresh())}running(t){if(!this._running)return!1;const e=this._charts.get(t);return!!(e&&e.running&&e.items.length)}stop(t){const e=this._charts.get(t);if(!e||!e.items.length)return;const i=e.items;let s=i.length-1;for(;s>=0;--s)i[s].cancel();e.items=[],this._notify(t,e,Date.now(),"complete")}remove(t){return this._charts.delete(t)}}var bt=new xt; +/*! + * @kurkle/color v0.3.2 + * https://github.com/kurkle/color#readme + * (c) 2023 Jukka Kurkela + * Released under the MIT License + */function _t(t){return t+.5|0}const yt=(t,e,i)=>Math.max(Math.min(t,i),e);function vt(t){return yt(_t(2.55*t),0,255)}function Mt(t){return yt(_t(255*t),0,255)}function wt(t){return yt(_t(t/2.55)/100,0,1)}function kt(t){return yt(_t(100*t),0,100)}const St={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Pt=[..."0123456789ABCDEF"],Dt=t=>Pt[15&t],Ct=t=>Pt[(240&t)>>4]+Pt[15&t],Ot=t=>(240&t)>>4==(15&t);function At(t){var e=(t=>Ot(t.r)&&Ot(t.g)&&Ot(t.b)&&Ot(t.a))(t)?Dt:Ct;return t?"#"+e(t.r)+e(t.g)+e(t.b)+((t,e)=>t<255?e(t):"")(t.a,e):void 0}const Tt=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Lt(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function Et(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function Rt(t,e,i){const s=Lt(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function It(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=function(t,e,i,s,n){return t===n?(e-i)/s+(e>16&255,o>>8&255,255&o]}return t}(),Ht.transparent=[0,0,0,0]);const e=Ht[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}const $t=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const Yt=t=>t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,Ut=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4);function Xt(t,e,i){if(t){let s=It(t);s[e]=Math.max(0,Math.min(s[e]+s[e]*i,0===e?360:1)),s=Ft(s),t.r=s[0],t.g=s[1],t.b=s[2]}}function qt(t,e){return t?Object.assign(e||{},t):t}function Kt(t){var e={r:0,g:0,b:0,a:255};return Array.isArray(t)?t.length>=3&&(e={r:t[0],g:t[1],b:t[2],a:255},t.length>3&&(e.a=Mt(t[3]))):(e=qt(t,{r:0,g:0,b:0,a:1})).a=Mt(e.a),e}function Gt(t){return"r"===t.charAt(0)?function(t){const e=$t.exec(t);let i,s,n,o=255;if(e){if(e[7]!==i){const t=+e[7];o=e[8]?vt(t):yt(255*t,0,255)}return i=+e[1],s=+e[3],n=+e[5],i=255&(e[2]?vt(i):yt(i,0,255)),s=255&(e[4]?vt(s):yt(s,0,255)),n=255&(e[6]?vt(n):yt(n,0,255)),{r:i,g:s,b:n,a:o}}}(t):Bt(t)}class Jt{constructor(t){if(t instanceof Jt)return t;const e=typeof t;let i;var s,n,o;"object"===e?i=Kt(t):"string"===e&&(o=(s=t).length,"#"===s[0]&&(4===o||5===o?n={r:255&17*St[s[1]],g:255&17*St[s[2]],b:255&17*St[s[3]],a:5===o?17*St[s[4]]:255}:7!==o&&9!==o||(n={r:St[s[1]]<<4|St[s[2]],g:St[s[3]]<<4|St[s[4]],b:St[s[5]]<<4|St[s[6]],a:9===o?St[s[7]]<<4|St[s[8]]:255})),i=n||jt(t)||Gt(t)),this._rgb=i,this._valid=!!i}get valid(){return this._valid}get rgb(){var t=qt(this._rgb);return t&&(t.a=wt(t.a)),t}set rgb(t){this._rgb=Kt(t)}rgbString(){return this._valid?(t=this._rgb)&&(t.a<255?`rgba(${t.r}, ${t.g}, ${t.b}, ${wt(t.a)})`:`rgb(${t.r}, ${t.g}, ${t.b})`):void 0;var t}hexString(){return this._valid?At(this._rgb):void 0}hslString(){return this._valid?function(t){if(!t)return;const e=It(t),i=e[0],s=kt(e[1]),n=kt(e[2]);return t.a<255?`hsla(${i}, ${s}%, ${n}%, ${wt(t.a)})`:`hsl(${i}, ${s}%, ${n}%)`}(this._rgb):void 0}mix(t,e){if(t){const i=this.rgb,s=t.rgb;let n;const o=e===n?.5:e,a=2*o-1,r=i.a-s.a,l=((a*r==-1?a:(a+r)/(1+a*r))+1)/2;n=1-l,i.r=255&l*i.r+n*s.r+.5,i.g=255&l*i.g+n*s.g+.5,i.b=255&l*i.b+n*s.b+.5,i.a=o*i.a+(1-o)*s.a,this.rgb=i}return this}interpolate(t,e){return t&&(this._rgb=function(t,e,i){const s=Ut(wt(t.r)),n=Ut(wt(t.g)),o=Ut(wt(t.b));return{r:Mt(Yt(s+i*(Ut(wt(e.r))-s))),g:Mt(Yt(n+i*(Ut(wt(e.g))-n))),b:Mt(Yt(o+i*(Ut(wt(e.b))-o))),a:t.a+i*(e.a-t.a)}}(this._rgb,t._rgb,e)),this}clone(){return new Jt(this.rgb)}alpha(t){return this._rgb.a=Mt(t),this}clearer(t){return this._rgb.a*=1-t,this}greyscale(){const t=this._rgb,e=_t(.3*t.r+.59*t.g+.11*t.b);return t.r=t.g=t.b=e,this}opaquer(t){return this._rgb.a*=1+t,this}negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return Xt(this._rgb,2,t),this}darken(t){return Xt(this._rgb,2,-t),this}saturate(t){return Xt(this._rgb,1,t),this}desaturate(t){return Xt(this._rgb,1,-t),this}rotate(t){return function(t,e){var i=It(t);i[0]=Vt(i[0]+e),i=Ft(i),t.r=i[0],t.g=i[1],t.b=i[2]}(this._rgb,t),this}}function Zt(t){if(t&&"object"==typeof t){const e=t.toString();return"[object CanvasPattern]"===e||"[object CanvasGradient]"===e}return!1}function Qt(t){return Zt(t)?t:new Jt(t)}function te(t){return Zt(t)?t:new Jt(t).saturate(.5).darken(.1).hexString()}const ee=["x","y","borderWidth","radius","tension"],ie=["color","borderColor","backgroundColor"];const se=new Map;function ne(t,e,i){return function(t,e){e=e||{};const i=t+JSON.stringify(e);let s=se.get(i);return s||(s=new Intl.NumberFormat(t,e),se.set(i,s)),s}(e,i).format(t)}const oe={values:t=>n(t)?t:""+t,numeric(t,e,i){if(0===t)return"0";const s=this.chart.options.locale;let n,o=t;if(i.length>1){const e=Math.max(Math.abs(i[0].value),Math.abs(i[i.length-1].value));(e<1e-4||e>1e15)&&(n="scientific"),o=function(t,e){let i=e.length>3?e[2].value-e[1].value:e[1].value-e[0].value;Math.abs(i)>=1&&t!==Math.floor(t)&&(i=t-Math.floor(t));return i}(t,i)}const a=z(Math.abs(o)),r=isNaN(a)?1:Math.max(Math.min(-1*Math.floor(a),20),0),l={notation:n,minimumFractionDigits:r,maximumFractionDigits:r};return Object.assign(l,this.options.ticks.format),ne(t,s,l)},logarithmic(t,e,i){if(0===t)return"0";const s=i[e].significand||t/Math.pow(10,Math.floor(z(t)));return[1,2,3,5,10,15].includes(s)||e>.8*i.length?oe.numeric.call(this,t,e,i):""}};var ae={formatters:oe};const re=Object.create(null),le=Object.create(null);function he(t,e){if(!e)return t;const i=e.split(".");for(let e=0,s=i.length;et.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(t,e)=>te(e.backgroundColor),this.hoverBorderColor=(t,e)=>te(e.borderColor),this.hoverColor=(t,e)=>te(e.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(t),this.apply(e)}set(t,e){return ce(this,t,e)}get(t){return he(this,t)}describe(t,e){return ce(le,t,e)}override(t,e){return ce(re,t,e)}route(t,e,i,s){const n=he(this,t),a=he(this,i),r="_"+e;Object.defineProperties(n,{[r]:{value:n[e],writable:!0},[e]:{enumerable:!0,get(){const t=this[r],e=a[s];return o(t)?Object.assign({},e,t):l(t,e)},set(t){this[r]=t}}})}apply(t){t.forEach((t=>t(this)))}}var ue=new de({_scriptable:t=>!t.startsWith("on"),_indexable:t=>"events"!==t,hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}},[function(t){t.set("animation",{delay:void 0,duration:1e3,easing:"easeOutQuart",fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0}),t.describe("animation",{_fallback:!1,_indexable:!1,_scriptable:t=>"onProgress"!==t&&"onComplete"!==t&&"fn"!==t}),t.set("animations",{colors:{type:"color",properties:ie},numbers:{type:"number",properties:ee}}),t.describe("animations",{_fallback:"animation"}),t.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:t=>0|t}}}})},function(t){t.set("layout",{autoPadding:!0,padding:{top:0,right:0,bottom:0,left:0}})},function(t){t.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",clip:!0,grace:0,grid:{display:!0,lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(t,e)=>e.lineWidth,tickColor:(t,e)=>e.color,offset:!1},border:{display:!0,dash:[],dashOffset:0,width:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:ae.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),t.route("scale.ticks","color","","color"),t.route("scale.grid","color","","borderColor"),t.route("scale.border","color","","borderColor"),t.route("scale.title","color","","color"),t.describe("scale",{_fallback:!1,_scriptable:t=>!t.startsWith("before")&&!t.startsWith("after")&&"callback"!==t&&"parser"!==t,_indexable:t=>"borderDash"!==t&&"tickBorderDash"!==t&&"dash"!==t}),t.describe("scales",{_fallback:"scale"}),t.describe("scale.ticks",{_scriptable:t=>"backdropPadding"!==t&&"callback"!==t,_indexable:t=>"backdropPadding"!==t})}]);function fe(){return"undefined"!=typeof window&&"undefined"!=typeof document}function ge(t){let e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e}function pe(t,e,i){let s;return"string"==typeof t?(s=parseInt(t,10),-1!==t.indexOf("%")&&(s=s/100*e.parentNode[i])):s=t,s}const me=t=>t.ownerDocument.defaultView.getComputedStyle(t,null);function xe(t,e){return me(t).getPropertyValue(e)}const be=["top","right","bottom","left"];function _e(t,e,i){const s={};i=i?"-"+i:"";for(let n=0;n<4;n++){const o=be[n];s[o]=parseFloat(t[e+"-"+o+i])||0}return s.width=s.left+s.right,s.height=s.top+s.bottom,s}const ye=(t,e,i)=>(t>0||e>0)&&(!i||!i.shadowRoot);function ve(t,e){if("native"in t)return t;const{canvas:i,currentDevicePixelRatio:s}=e,n=me(i),o="border-box"===n.boxSizing,a=_e(n,"padding"),r=_e(n,"border","width"),{x:l,y:h,box:c}=function(t,e){const i=t.touches,s=i&&i.length?i[0]:t,{offsetX:n,offsetY:o}=s;let a,r,l=!1;if(ye(n,o,t.target))a=n,r=o;else{const t=e.getBoundingClientRect();a=s.clientX-t.left,r=s.clientY-t.top,l=!0}return{x:a,y:r,box:l}}(t,i),d=a.left+(c&&r.left),u=a.top+(c&&r.top);let{width:f,height:g}=e;return o&&(f-=a.width+r.width,g-=a.height+r.height),{x:Math.round((l-d)/f*i.width/s),y:Math.round((h-u)/g*i.height/s)}}const Me=t=>Math.round(10*t)/10;function we(t,e,i,s){const n=me(t),o=_e(n,"margin"),a=pe(n.maxWidth,t,"clientWidth")||T,r=pe(n.maxHeight,t,"clientHeight")||T,l=function(t,e,i){let s,n;if(void 0===e||void 0===i){const o=t&&ge(t);if(o){const t=o.getBoundingClientRect(),a=me(o),r=_e(a,"border","width"),l=_e(a,"padding");e=t.width-l.width-r.width,i=t.height-l.height-r.height,s=pe(a.maxWidth,o,"clientWidth"),n=pe(a.maxHeight,o,"clientHeight")}else e=t.clientWidth,i=t.clientHeight}return{width:e,height:i,maxWidth:s||T,maxHeight:n||T}}(t,e,i);let{width:h,height:c}=l;if("content-box"===n.boxSizing){const t=_e(n,"border","width"),e=_e(n,"padding");h-=e.width+t.width,c-=e.height+t.height}h=Math.max(0,h-o.width),c=Math.max(0,s?h/s:c-o.height),h=Me(Math.min(h,a,l.maxWidth)),c=Me(Math.min(c,r,l.maxHeight)),h&&!c&&(c=Me(h/2));return(void 0!==e||void 0!==i)&&s&&l.height&&c>l.height&&(c=l.height,h=Me(Math.floor(c*s))),{width:h,height:c}}function ke(t,e,i){const s=e||1,n=Me(t.height*s),o=Me(t.width*s);t.height=Me(t.height),t.width=Me(t.width);const a=t.canvas;return a.style&&(i||!a.style.height&&!a.style.width)&&(a.style.height=`${t.height}px`,a.style.width=`${t.width}px`),(t.currentDevicePixelRatio!==s||a.height!==n||a.width!==o)&&(t.currentDevicePixelRatio=s,a.height=n,a.width=o,t.ctx.setTransform(s,0,0,s,0,0),!0)}const Se=function(){let t=!1;try{const e={get passive(){return t=!0,!1}};fe()&&(window.addEventListener("test",null,e),window.removeEventListener("test",null,e))}catch(t){}return t}();function Pe(t,e){const i=xe(t,e),s=i&&i.match(/^(\d+)(\.\d+)?px$/);return s?+s[1]:void 0}function De(t){return!t||s(t.size)||s(t.family)?null:(t.style?t.style+" ":"")+(t.weight?t.weight+" ":"")+t.size+"px "+t.family}function Ce(t,e,i,s,n){let o=e[n];return o||(o=e[n]=t.measureText(n).width,i.push(n)),o>s&&(s=o),s}function Oe(t,e,i,s){let o=(s=s||{}).data=s.data||{},a=s.garbageCollect=s.garbageCollect||[];s.font!==e&&(o=s.data={},a=s.garbageCollect=[],s.font=e),t.save(),t.font=e;let r=0;const l=i.length;let h,c,d,u,f;for(h=0;hi.length){for(h=0;h0&&t.stroke()}}function Re(t,e,i){return i=i||.5,!e||t&&t.x>e.left-i&&t.xe.top-i&&t.y0&&""!==r.strokeColor;let c,d;for(t.save(),t.font=a.string,function(t,e){e.translation&&t.translate(e.translation[0],e.translation[1]),s(e.rotation)||t.rotate(e.rotation),e.color&&(t.fillStyle=e.color),e.textAlign&&(t.textAlign=e.textAlign),e.textBaseline&&(t.textBaseline=e.textBaseline)}(t,r),c=0;ct[0])){const o=i||t;void 0===s&&(s=ti("_fallback",t));const a={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:t,_rootScopes:o,_fallback:s,_getTarget:n,override:i=>je([i,...t],e,o,s)};return new Proxy(a,{deleteProperty:(e,i)=>(delete e[i],delete e._keys,delete t[0][i],!0),get:(i,s)=>qe(i,s,(()=>function(t,e,i,s){let n;for(const o of e)if(n=ti(Ue(o,t),i),void 0!==n)return Xe(t,n)?Ze(i,s,t,n):n}(s,e,t,i))),getOwnPropertyDescriptor:(t,e)=>Reflect.getOwnPropertyDescriptor(t._scopes[0],e),getPrototypeOf:()=>Reflect.getPrototypeOf(t[0]),has:(t,e)=>ei(t).includes(e),ownKeys:t=>ei(t),set(t,e,i){const s=t._storage||(t._storage=n());return t[e]=s[e]=i,delete t._keys,!0}})}function $e(t,e,i,s){const a={_cacheable:!1,_proxy:t,_context:e,_subProxy:i,_stack:new Set,_descriptors:Ye(t,s),setContext:e=>$e(t,e,i,s),override:n=>$e(t.override(n),e,i,s)};return new Proxy(a,{deleteProperty:(e,i)=>(delete e[i],delete t[i],!0),get:(t,e,i)=>qe(t,e,(()=>function(t,e,i){const{_proxy:s,_context:a,_subProxy:r,_descriptors:l}=t;let h=s[e];S(h)&&l.isScriptable(e)&&(h=function(t,e,i,s){const{_proxy:n,_context:o,_subProxy:a,_stack:r}=i;if(r.has(t))throw new Error("Recursion detected: "+Array.from(r).join("->")+"->"+t);r.add(t);let l=e(o,a||s);r.delete(t),Xe(t,l)&&(l=Ze(n._scopes,n,t,l));return l}(e,h,t,i));n(h)&&h.length&&(h=function(t,e,i,s){const{_proxy:n,_context:a,_subProxy:r,_descriptors:l}=i;if(void 0!==a.index&&s(t))return e[a.index%e.length];if(o(e[0])){const i=e,s=n._scopes.filter((t=>t!==i));e=[];for(const o of i){const i=Ze(s,n,t,o);e.push($e(i,a,r&&r[t],l))}}return e}(e,h,t,l.isIndexable));Xe(e,h)&&(h=$e(h,a,r&&r[e],l));return h}(t,e,i))),getOwnPropertyDescriptor:(e,i)=>e._descriptors.allKeys?Reflect.has(t,i)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(t,i),getPrototypeOf:()=>Reflect.getPrototypeOf(t),has:(e,i)=>Reflect.has(t,i),ownKeys:()=>Reflect.ownKeys(t),set:(e,i,s)=>(t[i]=s,delete e[i],!0)})}function Ye(t,e={scriptable:!0,indexable:!0}){const{_scriptable:i=e.scriptable,_indexable:s=e.indexable,_allKeys:n=e.allKeys}=t;return{allKeys:n,scriptable:i,indexable:s,isScriptable:S(i)?i:()=>i,isIndexable:S(s)?s:()=>s}}const Ue=(t,e)=>t?t+w(e):e,Xe=(t,e)=>o(e)&&"adapters"!==t&&(null===Object.getPrototypeOf(e)||e.constructor===Object);function qe(t,e,i){if(Object.prototype.hasOwnProperty.call(t,e)||"constructor"===e)return t[e];const s=i();return t[e]=s,s}function Ke(t,e,i){return S(t)?t(e,i):t}const Ge=(t,e)=>!0===t?e:"string"==typeof t?M(e,t):void 0;function Je(t,e,i,s,n){for(const o of e){const e=Ge(i,o);if(e){t.add(e);const o=Ke(e._fallback,i,n);if(void 0!==o&&o!==i&&o!==s)return o}else if(!1===e&&void 0!==s&&i!==s)return null}return!1}function Ze(t,e,i,s){const a=e._rootScopes,r=Ke(e._fallback,i,s),l=[...t,...a],h=new Set;h.add(s);let c=Qe(h,l,i,r||i,s);return null!==c&&((void 0===r||r===i||(c=Qe(h,l,r,c,s),null!==c))&&je(Array.from(h),[""],a,r,(()=>function(t,e,i){const s=t._getTarget();e in s||(s[e]={});const a=s[e];if(n(a)&&o(i))return i;return a||{}}(e,i,s))))}function Qe(t,e,i,s,n){for(;i;)i=Je(t,e,i,s,n);return i}function ti(t,e){for(const i of e){if(!i)continue;const e=i[t];if(void 0!==e)return e}}function ei(t){let e=t._keys;return e||(e=t._keys=function(t){const e=new Set;for(const i of t)for(const t of Object.keys(i).filter((t=>!t.startsWith("_"))))e.add(t);return Array.from(e)}(t._scopes)),e}function ii(t,e,i,s){const{iScale:n}=t,{key:o="r"}=this._parsing,a=new Array(s);let r,l,h,c;for(r=0,l=s;re"x"===t?"y":"x";function ai(t,e,i,s){const n=t.skip?e:t,o=e,a=i.skip?e:i,r=q(o,n),l=q(a,o);let h=r/(r+l),c=l/(r+l);h=isNaN(h)?0:h,c=isNaN(c)?0:c;const d=s*h,u=s*c;return{previous:{x:o.x-d*(a.x-n.x),y:o.y-d*(a.y-n.y)},next:{x:o.x+u*(a.x-n.x),y:o.y+u*(a.y-n.y)}}}function ri(t,e="x"){const i=oi(e),s=t.length,n=Array(s).fill(0),o=Array(s);let a,r,l,h=ni(t,0);for(a=0;a!t.skip))),"monotone"===e.cubicInterpolationMode)ri(t,n);else{let i=s?t[t.length-1]:t[0];for(o=0,a=t.length;o0===t||1===t,di=(t,e,i)=>-Math.pow(2,10*(t-=1))*Math.sin((t-e)*O/i),ui=(t,e,i)=>Math.pow(2,-10*t)*Math.sin((t-e)*O/i)+1,fi={linear:t=>t,easeInQuad:t=>t*t,easeOutQuad:t=>-t*(t-2),easeInOutQuad:t=>(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1),easeInCubic:t=>t*t*t,easeOutCubic:t=>(t-=1)*t*t+1,easeInOutCubic:t=>(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2),easeInQuart:t=>t*t*t*t,easeOutQuart:t=>-((t-=1)*t*t*t-1),easeInOutQuart:t=>(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2),easeInQuint:t=>t*t*t*t*t,easeOutQuint:t=>(t-=1)*t*t*t*t+1,easeInOutQuint:t=>(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2),easeInSine:t=>1-Math.cos(t*E),easeOutSine:t=>Math.sin(t*E),easeInOutSine:t=>-.5*(Math.cos(C*t)-1),easeInExpo:t=>0===t?0:Math.pow(2,10*(t-1)),easeOutExpo:t=>1===t?1:1-Math.pow(2,-10*t),easeInOutExpo:t=>ci(t)?t:t<.5?.5*Math.pow(2,10*(2*t-1)):.5*(2-Math.pow(2,-10*(2*t-1))),easeInCirc:t=>t>=1?t:-(Math.sqrt(1-t*t)-1),easeOutCirc:t=>Math.sqrt(1-(t-=1)*t),easeInOutCirc:t=>(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1),easeInElastic:t=>ci(t)?t:di(t,.075,.3),easeOutElastic:t=>ci(t)?t:ui(t,.075,.3),easeInOutElastic(t){const e=.1125;return ci(t)?t:t<.5?.5*di(2*t,e,.45):.5+.5*ui(2*t-1,e,.45)},easeInBack(t){const e=1.70158;return t*t*((e+1)*t-e)},easeOutBack(t){const e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack(t){let e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:t=>1-fi.easeOutBounce(1-t),easeOutBounce(t){const e=7.5625,i=2.75;return t<1/i?e*t*t:t<2/i?e*(t-=1.5/i)*t+.75:t<2.5/i?e*(t-=2.25/i)*t+.9375:e*(t-=2.625/i)*t+.984375},easeInOutBounce:t=>t<.5?.5*fi.easeInBounce(2*t):.5*fi.easeOutBounce(2*t-1)+.5};function gi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:t.y+i*(e.y-t.y)}}function pi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:"middle"===s?i<.5?t.y:e.y:"after"===s?i<1?t.y:e.y:i>0?e.y:t.y}}function mi(t,e,i,s){const n={x:t.cp2x,y:t.cp2y},o={x:e.cp1x,y:e.cp1y},a=gi(t,n,i),r=gi(n,o,i),l=gi(o,e,i),h=gi(a,r,i),c=gi(r,l,i);return gi(h,c,i)}const xi=/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/,bi=/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/;function _i(t,e){const i=(""+t).match(xi);if(!i||"normal"===i[1])return 1.2*e;switch(t=+i[2],i[3]){case"px":return t;case"%":t/=100}return e*t}const yi=t=>+t||0;function vi(t,e){const i={},s=o(e),n=s?Object.keys(e):e,a=o(t)?s?i=>l(t[i],t[e[i]]):e=>t[e]:()=>t;for(const t of n)i[t]=yi(a(t));return i}function Mi(t){return vi(t,{top:"y",right:"x",bottom:"y",left:"x"})}function wi(t){return vi(t,["topLeft","topRight","bottomLeft","bottomRight"])}function ki(t){const e=Mi(t);return e.width=e.left+e.right,e.height=e.top+e.bottom,e}function Si(t,e){t=t||{},e=e||ue.font;let i=l(t.size,e.size);"string"==typeof i&&(i=parseInt(i,10));let s=l(t.style,e.style);s&&!(""+s).match(bi)&&(console.warn('Invalid font style specified: "'+s+'"'),s=void 0);const n={family:l(t.family,e.family),lineHeight:_i(l(t.lineHeight,e.lineHeight),i),size:i,style:s,weight:l(t.weight,e.weight),string:""};return n.string=De(n),n}function Pi(t,e,i,s){let o,a,r,l=!0;for(o=0,a=t.length;oi&&0===t?0:t+e;return{min:a(s,-Math.abs(o)),max:a(n,o)}}function Ci(t,e){return Object.assign(Object.create(t),e)}function Oi(t,e,i){return t?function(t,e){return{x:i=>t+t+e-i,setWidth(t){e=t},textAlign:t=>"center"===t?t:"right"===t?"left":"right",xPlus:(t,e)=>t-e,leftForLtr:(t,e)=>t-e}}(e,i):{x:t=>t,setWidth(t){},textAlign:t=>t,xPlus:(t,e)=>t+e,leftForLtr:(t,e)=>t}}function Ai(t,e){let i,s;"ltr"!==e&&"rtl"!==e||(i=t.canvas.style,s=[i.getPropertyValue("direction"),i.getPropertyPriority("direction")],i.setProperty("direction",e,"important"),t.prevTextDirection=s)}function Ti(t,e){void 0!==e&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}function Li(t){return"angle"===t?{between:J,compare:K,normalize:G}:{between:tt,compare:(t,e)=>t-e,normalize:t=>t}}function Ei({start:t,end:e,count:i,loop:s,style:n}){return{start:t%i,end:e%i,loop:s&&(e-t+1)%i==0,style:n}}function Ri(t,e,i){if(!i)return[t];const{property:s,start:n,end:o}=i,a=e.length,{compare:r,between:l,normalize:h}=Li(s),{start:c,end:d,loop:u,style:f}=function(t,e,i){const{property:s,start:n,end:o}=i,{between:a,normalize:r}=Li(s),l=e.length;let h,c,{start:d,end:u,loop:f}=t;if(f){for(d+=l,u+=l,h=0,c=l;hb||l(n,x,p)&&0!==r(n,x),v=()=>!b||0===r(o,p)||l(o,x,p);for(let t=c,i=c;t<=d;++t)m=e[t%a],m.skip||(p=h(m[s]),p!==x&&(b=l(p,n,o),null===_&&y()&&(_=0===r(p,n)?t:i),null!==_&&v()&&(g.push(Ei({start:_,end:t,loop:u,count:a,style:f})),_=null),i=t,x=p));return null!==_&&g.push(Ei({start:_,end:d,loop:u,count:a,style:f})),g}function Ii(t,e){const i=[],s=t.segments;for(let n=0;nn&&t[o%e].skip;)o--;return o%=e,{start:n,end:o}}(i,n,o,s);if(!0===s)return Fi(t,[{start:a,end:r,loop:o}],i,e);return Fi(t,function(t,e,i,s){const n=t.length,o=[];let a,r=e,l=t[e];for(a=e+1;a<=i;++a){const i=t[a%n];i.skip||i.stop?l.skip||(s=!1,o.push({start:e%n,end:(a-1)%n,loop:s}),e=r=i.stop?a:null):(r=a,l.skip&&(e=a)),l=i}return null!==r&&o.push({start:e%n,end:r%n,loop:s}),o}(i,a,r!s(t[e.axis])));n.lo-=Math.max(0,a);const r=i.slice(n.hi).findIndex((t=>!s(t[e.axis])));n.hi+=Math.max(0,r)}return n}if(o._sharedOptions){const t=a[0],s="function"==typeof t.getRange&&t.getRange(e);if(s){const t=r(a,e,i-s),n=r(a,e,i+s);return{lo:t.lo,hi:n.hi}}}}return{lo:0,hi:a.length-1}}function $i(t,e,i,s,n){const o=t.getSortedVisibleDatasetMetas(),a=i[e];for(let t=0,i=o.length;t{t[a]&&t[a](e[i],n)&&(o.push({element:t,datasetIndex:s,index:l}),r=r||t.inRange(e.x,e.y,n))})),s&&!r?[]:o}var Ki={evaluateInteractionItems:$i,modes:{index(t,e,i,s){const n=ve(e,t),o=i.axis||"x",a=i.includeInvisible||!1,r=i.intersect?Yi(t,n,o,s,a):Xi(t,n,o,!1,s,a),l=[];return r.length?(t.getSortedVisibleDatasetMetas().forEach((t=>{const e=r[0].index,i=t.data[e];i&&!i.skip&&l.push({element:i,datasetIndex:t.index,index:e})})),l):[]},dataset(t,e,i,s){const n=ve(e,t),o=i.axis||"xy",a=i.includeInvisible||!1;let r=i.intersect?Yi(t,n,o,s,a):Xi(t,n,o,!1,s,a);if(r.length>0){const e=r[0].datasetIndex,i=t.getDatasetMeta(e).data;r=[];for(let t=0;tYi(t,ve(e,t),i.axis||"xy",s,i.includeInvisible||!1),nearest(t,e,i,s){const n=ve(e,t),o=i.axis||"xy",a=i.includeInvisible||!1;return Xi(t,n,o,i.intersect,s,a)},x:(t,e,i,s)=>qi(t,ve(e,t),"x",i.intersect,s),y:(t,e,i,s)=>qi(t,ve(e,t),"y",i.intersect,s)}};const Gi=["left","top","right","bottom"];function Ji(t,e){return t.filter((t=>t.pos===e))}function Zi(t,e){return t.filter((t=>-1===Gi.indexOf(t.pos)&&t.box.axis===e))}function Qi(t,e){return t.sort(((t,i)=>{const s=e?i:t,n=e?t:i;return s.weight===n.weight?s.index-n.index:s.weight-n.weight}))}function ts(t,e){const i=function(t){const e={};for(const i of t){const{stack:t,pos:s,stackWeight:n}=i;if(!t||!Gi.includes(s))continue;const o=e[t]||(e[t]={count:0,placed:0,weight:0,size:0});o.count++,o.weight+=n}return e}(t),{vBoxMaxWidth:s,hBoxMaxHeight:n}=e;let o,a,r;for(o=0,a=t.length;o{s[t]=Math.max(e[t],i[t])})),s}return s(t?["left","right"]:["top","bottom"])}function os(t,e,i,s){const n=[];let o,a,r,l,h,c;for(o=0,a=t.length,h=0;ot.box.fullSize)),!0),s=Qi(Ji(e,"left"),!0),n=Qi(Ji(e,"right")),o=Qi(Ji(e,"top"),!0),a=Qi(Ji(e,"bottom")),r=Zi(e,"x"),l=Zi(e,"y");return{fullSize:i,leftAndTop:s.concat(o),rightAndBottom:n.concat(l).concat(a).concat(r),chartArea:Ji(e,"chartArea"),vertical:s.concat(n).concat(l),horizontal:o.concat(a).concat(r)}}(t.boxes),l=r.vertical,h=r.horizontal;u(t.boxes,(t=>{"function"==typeof t.beforeLayout&&t.beforeLayout()}));const c=l.reduce(((t,e)=>e.box.options&&!1===e.box.options.display?t:t+1),0)||1,d=Object.freeze({outerWidth:e,outerHeight:i,padding:n,availableWidth:o,availableHeight:a,vBoxMaxWidth:o/2/c,hBoxMaxHeight:a/2}),f=Object.assign({},n);is(f,ki(s));const g=Object.assign({maxPadding:f,w:o,h:a,x:n.left,y:n.top},n),p=ts(l.concat(h),d);os(r.fullSize,g,d,p),os(l,g,d,p),os(h,g,d,p)&&os(l,g,d,p),function(t){const e=t.maxPadding;function i(i){const s=Math.max(e[i]-t[i],0);return t[i]+=s,s}t.y+=i("top"),t.x+=i("left"),i("right"),i("bottom")}(g),rs(r.leftAndTop,g,d,p),g.x+=g.w,g.y+=g.h,rs(r.rightAndBottom,g,d,p),t.chartArea={left:g.left,top:g.top,right:g.left+g.w,bottom:g.top+g.h,height:g.h,width:g.w},u(r.chartArea,(e=>{const i=e.box;Object.assign(i,t.chartArea),i.update(g.w,g.h,{left:0,top:0,right:0,bottom:0})}))}};class hs{acquireContext(t,e){}releaseContext(t){return!1}addEventListener(t,e,i){}removeEventListener(t,e,i){}getDevicePixelRatio(){return 1}getMaximumSize(t,e,i,s){return e=Math.max(0,e||t.width),i=i||t.height,{width:e,height:Math.max(0,s?Math.floor(e/s):i)}}isAttached(t){return!0}updateConfig(t){}}class cs extends hs{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}updateConfig(t){t.options.animation=!1}}const ds="$chartjs",us={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},fs=t=>null===t||""===t;const gs=!!Se&&{passive:!0};function ps(t,e,i){t&&t.canvas&&t.canvas.removeEventListener(e,i,gs)}function ms(t,e){for(const i of t)if(i===e||i.contains(e))return!0}function xs(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||ms(i.addedNodes,s),e=e&&!ms(i.removedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}function bs(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||ms(i.removedNodes,s),e=e&&!ms(i.addedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}const _s=new Map;let ys=0;function vs(){const t=window.devicePixelRatio;t!==ys&&(ys=t,_s.forEach(((e,i)=>{i.currentDevicePixelRatio!==t&&e()})))}function Ms(t,e,i){const s=t.canvas,n=s&&ge(s);if(!n)return;const o=ct(((t,e)=>{const s=n.clientWidth;i(t,e),s{const e=t[0],i=e.contentRect.width,s=e.contentRect.height;0===i&&0===s||o(i,s)}));return a.observe(n),function(t,e){_s.size||window.addEventListener("resize",vs),_s.set(t,e)}(t,o),a}function ws(t,e,i){i&&i.disconnect(),"resize"===e&&function(t){_s.delete(t),_s.size||window.removeEventListener("resize",vs)}(t)}function ks(t,e,i){const s=t.canvas,n=ct((e=>{null!==t.ctx&&i(function(t,e){const i=us[t.type]||t.type,{x:s,y:n}=ve(t,e);return{type:i,chart:e,native:t,x:void 0!==s?s:null,y:void 0!==n?n:null}}(e,t))}),t);return function(t,e,i){t&&t.addEventListener(e,i,gs)}(s,e,n),n}class Ss extends hs{acquireContext(t,e){const i=t&&t.getContext&&t.getContext("2d");return i&&i.canvas===t?(function(t,e){const i=t.style,s=t.getAttribute("height"),n=t.getAttribute("width");if(t[ds]={initial:{height:s,width:n,style:{display:i.display,height:i.height,width:i.width}}},i.display=i.display||"block",i.boxSizing=i.boxSizing||"border-box",fs(n)){const e=Pe(t,"width");void 0!==e&&(t.width=e)}if(fs(s))if(""===t.style.height)t.height=t.width/(e||2);else{const e=Pe(t,"height");void 0!==e&&(t.height=e)}}(t,e),i):null}releaseContext(t){const e=t.canvas;if(!e[ds])return!1;const i=e[ds].initial;["height","width"].forEach((t=>{const n=i[t];s(n)?e.removeAttribute(t):e.setAttribute(t,n)}));const n=i.style||{};return Object.keys(n).forEach((t=>{e.style[t]=n[t]})),e.width=e.width,delete e[ds],!0}addEventListener(t,e,i){this.removeEventListener(t,e);const s=t.$proxies||(t.$proxies={}),n={attach:xs,detach:bs,resize:Ms}[e]||ks;s[e]=n(t,e,i)}removeEventListener(t,e){const i=t.$proxies||(t.$proxies={}),s=i[e];if(!s)return;({attach:ws,detach:ws,resize:ws}[e]||ps)(t,e,s),i[e]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,e,i,s){return we(t,e,i,s)}isAttached(t){const e=t&&ge(t);return!(!e||!e.isConnected)}}function Ps(t){return!fe()||"undefined"!=typeof OffscreenCanvas&&t instanceof OffscreenCanvas?cs:Ss}var Ds=Object.freeze({__proto__:null,BasePlatform:hs,BasicPlatform:cs,DomPlatform:Ss,_detectPlatform:Ps});const Cs="transparent",Os={boolean:(t,e,i)=>i>.5?e:t,color(t,e,i){const s=Qt(t||Cs),n=s.valid&&Qt(e||Cs);return n&&n.valid?n.mix(s,i).hexString():e},number:(t,e,i)=>t+(e-t)*i};class As{constructor(t,e,i,s){const n=e[i];s=Pi([t.to,s,n,t.from]);const o=Pi([t.from,n,s]);this._active=!0,this._fn=t.fn||Os[t.type||typeof o],this._easing=fi[t.easing]||fi.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=e,this._prop=i,this._from=o,this._to=s,this._promises=void 0}active(){return this._active}update(t,e,i){if(this._active){this._notify(!1);const s=this._target[this._prop],n=i-this._start,o=this._duration-n;this._start=i,this._duration=Math.floor(Math.max(o,t.duration)),this._total+=n,this._loop=!!t.loop,this._to=Pi([t.to,e,s,t.from]),this._from=Pi([t.from,s,e])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(t){const e=t-this._start,i=this._duration,s=this._prop,n=this._from,o=this._loop,a=this._to;let r;if(this._active=n!==a&&(o||e1?2-r:r,r=this._easing(Math.min(1,Math.max(0,r))),this._target[s]=this._fn(n,a,r))}wait(){const t=this._promises||(this._promises=[]);return new Promise(((e,i)=>{t.push({res:e,rej:i})}))}_notify(t){const e=t?"res":"rej",i=this._promises||[];for(let t=0;t{const a=t[s];if(!o(a))return;const r={};for(const t of e)r[t]=a[t];(n(a.properties)&&a.properties||[s]).forEach((t=>{t!==s&&i.has(t)||i.set(t,r)}))}))}_animateOptions(t,e){const i=e.options,s=function(t,e){if(!e)return;let i=t.options;if(!i)return void(t.options=e);i.$shared&&(t.options=i=Object.assign({},i,{$shared:!1,$animations:{}}));return i}(t,i);if(!s)return[];const n=this._createAnimations(s,i);return i.$shared&&function(t,e){const i=[],s=Object.keys(e);for(let e=0;e{t.options=i}),(()=>{})),n}_createAnimations(t,e){const i=this._properties,s=[],n=t.$animations||(t.$animations={}),o=Object.keys(e),a=Date.now();let r;for(r=o.length-1;r>=0;--r){const l=o[r];if("$"===l.charAt(0))continue;if("options"===l){s.push(...this._animateOptions(t,e));continue}const h=e[l];let c=n[l];const d=i.get(l);if(c){if(d&&c.active()){c.update(d,h,a);continue}c.cancel()}d&&d.duration?(n[l]=c=new As(d,t,l,h),s.push(c)):t[l]=h}return s}update(t,e){if(0===this._properties.size)return void Object.assign(t,e);const i=this._createAnimations(t,e);return i.length?(bt.add(this._chart,i),!0):void 0}}function Ls(t,e){const i=t&&t.options||{},s=i.reverse,n=void 0===i.min?e:0,o=void 0===i.max?e:0;return{start:s?o:n,end:s?n:o}}function Es(t,e){const i=[],s=t._getSortedDatasetMetas(e);let n,o;for(n=0,o=s.length;n0||!i&&e<0)return n.index}return null}function Vs(t,e){const{chart:i,_cachedMeta:s}=t,n=i._stacks||(i._stacks={}),{iScale:o,vScale:a,index:r}=s,l=o.axis,h=a.axis,c=function(t,e,i){return`${t.id}.${e.id}.${i.stack||i.type}`}(o,a,s),d=e.length;let u;for(let t=0;ti[t].axis===e)).shift()}function Ws(t,e){const i=t.controller.index,s=t.vScale&&t.vScale.axis;if(s){e=e||t._parsed;for(const t of e){const e=t._stacks;if(!e||void 0===e[s]||void 0===e[s][i])return;delete e[s][i],void 0!==e[s]._visualValues&&void 0!==e[s]._visualValues[i]&&delete e[s]._visualValues[i]}}}const Ns=t=>"reset"===t||"none"===t,Hs=(t,e)=>e?t:Object.assign({},t);class js{static defaults={};static datasetElementType=null;static dataElementType=null;constructor(t,e){this.chart=t,this._ctx=t.ctx,this.index=e,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.datasetElementType=new.target.datasetElementType,this.dataElementType=new.target.dataElementType,this.initialize()}initialize(){const t=this._cachedMeta;this.configure(),this.linkScales(),t._stacked=Is(t.vScale,t),this.addElements(),this.options.fill&&!this.chart.isPluginEnabled("filler")&&console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options")}updateIndex(t){this.index!==t&&Ws(this._cachedMeta),this.index=t}linkScales(){const t=this.chart,e=this._cachedMeta,i=this.getDataset(),s=(t,e,i,s)=>"x"===t?e:"r"===t?s:i,n=e.xAxisID=l(i.xAxisID,Bs(t,"x")),o=e.yAxisID=l(i.yAxisID,Bs(t,"y")),a=e.rAxisID=l(i.rAxisID,Bs(t,"r")),r=e.indexAxis,h=e.iAxisID=s(r,n,o,a),c=e.vAxisID=s(r,o,n,a);e.xScale=this.getScaleForId(n),e.yScale=this.getScaleForId(o),e.rScale=this.getScaleForId(a),e.iScale=this.getScaleForId(h),e.vScale=this.getScaleForId(c)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const e=this._cachedMeta;return t===e.iScale?e.vScale:e.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&rt(this._data,this),t._stacked&&Ws(t)}_dataCheck(){const t=this.getDataset(),e=t.data||(t.data=[]),i=this._data;if(o(e)){const t=this._cachedMeta;this._data=function(t,e){const{iScale:i,vScale:s}=e,n="x"===i.axis?"x":"y",o="x"===s.axis?"x":"y",a=Object.keys(t),r=new Array(a.length);let l,h,c;for(l=0,h=a.length;l0&&i._parsed[t-1];if(!1===this._parsing)i._parsed=s,i._sorted=!0,d=s;else{d=n(s[t])?this.parseArrayData(i,s,t,e):o(s[t])?this.parseObjectData(i,s,t,e):this.parsePrimitiveData(i,s,t,e);const a=()=>null===c[l]||f&&c[l]t&&!e.hidden&&e._stacked&&{keys:Es(i,!0),values:null})(e,i,this.chart),h={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY},{min:c,max:d}=function(t){const{min:e,max:i,minDefined:s,maxDefined:n}=t.getUserBounds();return{min:s?e:Number.NEGATIVE_INFINITY,max:n?i:Number.POSITIVE_INFINITY}}(r);let u,f;function g(){f=s[u];const e=f[r.axis];return!a(f[t.axis])||c>e||d=0;--u)if(!g()){this.updateRangeFromParsed(h,t,f,l);break}return h}getAllParsedValues(t){const e=this._cachedMeta._parsed,i=[];let s,n,o;for(s=0,n=e.length;s=0&&tthis.getContext(i,s,e)),c);return f.$shared&&(f.$shared=r,n[o]=Object.freeze(Hs(f,r))),f}_resolveAnimations(t,e,i){const s=this.chart,n=this._cachedDataOpts,o=`animation-${e}`,a=n[o];if(a)return a;let r;if(!1!==s.options.animation){const s=this.chart.config,n=s.datasetAnimationScopeKeys(this._type,e),o=s.getOptionScopes(this.getDataset(),n);r=s.createResolver(o,this.getContext(t,i,e))}const l=new Ts(s,r&&r.animations);return r&&r._cacheable&&(n[o]=Object.freeze(l)),l}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,e){return!e||Ns(t)||this.chart._animationsDisabled}_getSharedOptions(t,e){const i=this.resolveDataElementOptions(t,e),s=this._sharedOptions,n=this.getSharedOptions(i),o=this.includeOptions(e,n)||n!==s;return this.updateSharedOptions(n,e,i),{sharedOptions:n,includeOptions:o}}updateElement(t,e,i,s){Ns(s)?Object.assign(t,i):this._resolveAnimations(e,s).update(t,i)}updateSharedOptions(t,e,i){t&&!Ns(e)&&this._resolveAnimations(void 0,e).update(t,i)}_setStyle(t,e,i,s){t.active=s;const n=this.getStyle(e,s);this._resolveAnimations(e,i,s).update(t,{options:!s&&this.getSharedOptions(n)||n})}removeHoverStyle(t,e,i){this._setStyle(t,i,"active",!1)}setHoverStyle(t,e,i){this._setStyle(t,i,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const e=this._data,i=this._cachedMeta.data;for(const[t,e,i]of this._syncList)this[t](e,i);this._syncList=[];const s=i.length,n=e.length,o=Math.min(n,s);o&&this.parse(0,o),n>s?this._insertElements(s,n-s,t):n{for(t.length+=e,a=t.length-1;a>=o;a--)t[a]=t[a-e]};for(r(n),a=t;a{s[t]=i[t]&&i[t].active()?i[t]._to:this[t]})),s}}function Ys(t,e){const i=t.options.ticks,n=function(t){const e=t.options.offset,i=t._tickSize(),s=t._length/i+(e?0:1),n=t._maxLength/i;return Math.floor(Math.min(s,n))}(t),o=Math.min(i.maxTicksLimit||n,n),a=i.major.enabled?function(t){const e=[];let i,s;for(i=0,s=t.length;io)return function(t,e,i,s){let n,o=0,a=i[0];for(s=Math.ceil(s),n=0;nn)return e}return Math.max(n,1)}(a,e,o);if(r>0){let t,i;const n=r>1?Math.round((h-l)/(r-1)):null;for(Us(e,c,d,s(n)?0:l-n,l),t=0,i=r-1;t"top"===e||"left"===e?t[e]+i:t[e]-i,qs=(t,e)=>Math.min(e||t,t);function Ks(t,e){const i=[],s=t.length/e,n=t.length;let o=0;for(;oa+r)))return h}function Js(t){return t.drawTicks?t.tickLength:0}function Zs(t,e){if(!t.display)return 0;const i=Si(t.font,e),s=ki(t.padding);return(n(t.text)?t.text.length:1)*i.lineHeight+s.height}function Qs(t,e,i){let s=ut(t);return(i&&"right"!==e||!i&&"right"===e)&&(s=(t=>"left"===t?"right":"right"===t?"left":t)(s)),s}class tn extends $s{constructor(t){super(),this.id=t.id,this.type=t.type,this.options=void 0,this.ctx=t.ctx,this.chart=t.chart,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this._margins={left:0,right:0,top:0,bottom:0},this.maxWidth=void 0,this.maxHeight=void 0,this.paddingTop=void 0,this.paddingBottom=void 0,this.paddingLeft=void 0,this.paddingRight=void 0,this.axis=void 0,this.labelRotation=void 0,this.min=void 0,this.max=void 0,this._range=void 0,this.ticks=[],this._gridLineItems=null,this._labelItems=null,this._labelSizes=null,this._length=0,this._maxLength=0,this._longestTextCache={},this._startPixel=void 0,this._endPixel=void 0,this._reversePixels=!1,this._userMax=void 0,this._userMin=void 0,this._suggestedMax=void 0,this._suggestedMin=void 0,this._ticksLength=0,this._borderValue=0,this._cache={},this._dataLimitsCached=!1,this.$context=void 0}init(t){this.options=t.setContext(this.getContext()),this.axis=t.axis,this._userMin=this.parse(t.min),this._userMax=this.parse(t.max),this._suggestedMin=this.parse(t.suggestedMin),this._suggestedMax=this.parse(t.suggestedMax)}parse(t,e){return t}getUserBounds(){let{_userMin:t,_userMax:e,_suggestedMin:i,_suggestedMax:s}=this;return t=r(t,Number.POSITIVE_INFINITY),e=r(e,Number.NEGATIVE_INFINITY),i=r(i,Number.POSITIVE_INFINITY),s=r(s,Number.NEGATIVE_INFINITY),{min:r(t,i),max:r(e,s),minDefined:a(t),maxDefined:a(e)}}getMinMax(t){let e,{min:i,max:s,minDefined:n,maxDefined:o}=this.getUserBounds();if(n&&o)return{min:i,max:s};const a=this.getMatchingVisibleMetas();for(let r=0,l=a.length;rs?s:i,s=n&&i>s?i:s,{min:r(i,r(s,i)),max:r(s,r(i,s))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]}getLabelItems(t=this.chart.chartArea){return this._labelItems||(this._labelItems=this._computeLabelItems(t))}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){d(this.options.beforeUpdate,[this])}update(t,e,i){const{beginAtZero:s,grace:n,ticks:o}=this.options,a=o.sampleSize;this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this._margins=i=Object.assign({left:0,right:0,top:0,bottom:0},i),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+i.left+i.right:this.height+i.top+i.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=Di(this,n,s),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();const r=a=n||i<=1||!this.isHorizontal())return void(this.labelRotation=s);const h=this._getLabelSizes(),c=h.widest.width,d=h.highest.height,u=Z(this.chart.width-c,0,this.maxWidth);o=t.offset?this.maxWidth/i:u/(i-1),c+6>o&&(o=u/(i-(t.offset?.5:1)),a=this.maxHeight-Js(t.grid)-e.padding-Zs(t.title,this.chart.options.font),r=Math.sqrt(c*c+d*d),l=Y(Math.min(Math.asin(Z((h.highest.height+6)/o,-1,1)),Math.asin(Z(a/r,-1,1))-Math.asin(Z(d/r,-1,1)))),l=Math.max(s,Math.min(n,l))),this.labelRotation=l}afterCalculateLabelRotation(){d(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){d(this.options.beforeFit,[this])}fit(){const t={width:0,height:0},{chart:e,options:{ticks:i,title:s,grid:n}}=this,o=this._isVisible(),a=this.isHorizontal();if(o){const o=Zs(s,e.options.font);if(a?(t.width=this.maxWidth,t.height=Js(n)+o):(t.height=this.maxHeight,t.width=Js(n)+o),i.display&&this.ticks.length){const{first:e,last:s,widest:n,highest:o}=this._getLabelSizes(),r=2*i.padding,l=$(this.labelRotation),h=Math.cos(l),c=Math.sin(l);if(a){const e=i.mirror?0:c*n.width+h*o.height;t.height=Math.min(this.maxHeight,t.height+e+r)}else{const e=i.mirror?0:h*n.width+c*o.height;t.width=Math.min(this.maxWidth,t.width+e+r)}this._calculatePadding(e,s,c,h)}}this._handleMargins(),a?(this.width=this._length=e.width-this._margins.left-this._margins.right,this.height=t.height):(this.width=t.width,this.height=this._length=e.height-this._margins.top-this._margins.bottom)}_calculatePadding(t,e,i,s){const{ticks:{align:n,padding:o},position:a}=this.options,r=0!==this.labelRotation,l="top"!==a&&"x"===this.axis;if(this.isHorizontal()){const a=this.getPixelForTick(0)-this.left,h=this.right-this.getPixelForTick(this.ticks.length-1);let c=0,d=0;r?l?(c=s*t.width,d=i*e.height):(c=i*t.height,d=s*e.width):"start"===n?d=e.width:"end"===n?c=t.width:"inner"!==n&&(c=t.width/2,d=e.width/2),this.paddingLeft=Math.max((c-a+o)*this.width/(this.width-a),0),this.paddingRight=Math.max((d-h+o)*this.width/(this.width-h),0)}else{let i=e.height/2,s=t.height/2;"start"===n?(i=0,s=t.height):"end"===n&&(i=e.height,s=0),this.paddingTop=i+o,this.paddingBottom=s+o}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){d(this.options.afterFit,[this])}isHorizontal(){const{axis:t,position:e}=this.options;return"top"===e||"bottom"===e||"x"===t}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){let e,i;for(this.beforeTickToLabelConversion(),this.generateTickLabels(t),e=0,i=t.length;e{const i=t.gc,s=i.length/2;let n;if(s>e){for(n=0;n({width:r[t]||0,height:l[t]||0});return{first:P(0),last:P(e-1),widest:P(k),highest:P(S),widths:r,heights:l}}getLabelForValue(t){return t}getPixelForValue(t,e){return NaN}getValueForPixel(t){}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getPixelForDecimal(t){this._reversePixels&&(t=1-t);const e=this._startPixel+t*this._length;return Q(this._alignToPixels?Ae(this.chart,e,0):e)}getDecimalForPixel(t){const e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:t,max:e}=this;return t<0&&e<0?e:t>0&&e>0?t:0}getContext(t){const e=this.ticks||[];if(t>=0&&ta*s?a/i:r/s:r*s0}_computeGridLineItems(t){const e=this.axis,i=this.chart,s=this.options,{grid:n,position:a,border:r}=s,h=n.offset,c=this.isHorizontal(),d=this.ticks.length+(h?1:0),u=Js(n),f=[],g=r.setContext(this.getContext()),p=g.display?g.width:0,m=p/2,x=function(t){return Ae(i,t,p)};let b,_,y,v,M,w,k,S,P,D,C,O;if("top"===a)b=x(this.bottom),w=this.bottom-u,S=b-m,D=x(t.top)+m,O=t.bottom;else if("bottom"===a)b=x(this.top),D=t.top,O=x(t.bottom)-m,w=b+m,S=this.top+u;else if("left"===a)b=x(this.right),M=this.right-u,k=b-m,P=x(t.left)+m,C=t.right;else if("right"===a)b=x(this.left),P=t.left,C=x(t.right)-m,M=b+m,k=this.left+u;else if("x"===e){if("center"===a)b=x((t.top+t.bottom)/2+.5);else if(o(a)){const t=Object.keys(a)[0],e=a[t];b=x(this.chart.scales[t].getPixelForValue(e))}D=t.top,O=t.bottom,w=b+m,S=w+u}else if("y"===e){if("center"===a)b=x((t.left+t.right)/2);else if(o(a)){const t=Object.keys(a)[0],e=a[t];b=x(this.chart.scales[t].getPixelForValue(e))}M=b-m,k=M-u,P=t.left,C=t.right}const A=l(s.ticks.maxTicksLimit,d),T=Math.max(1,Math.ceil(d/A));for(_=0;_0&&(o-=s/2)}d={left:o,top:n,width:s+e.width,height:i+e.height,color:t.backdropColor}}x.push({label:v,font:P,textOffset:O,options:{rotation:m,color:i,strokeColor:o,strokeWidth:h,textAlign:f,textBaseline:A,translation:[M,w],backdrop:d}})}return x}_getXAxisLabelAlignment(){const{position:t,ticks:e}=this.options;if(-$(this.labelRotation))return"top"===t?"left":"right";let i="center";return"start"===e.align?i="left":"end"===e.align?i="right":"inner"===e.align&&(i="inner"),i}_getYAxisLabelAlignment(t){const{position:e,ticks:{crossAlign:i,mirror:s,padding:n}}=this.options,o=t+n,a=this._getLabelSizes().widest.width;let r,l;return"left"===e?s?(l=this.right+n,"near"===i?r="left":"center"===i?(r="center",l+=a/2):(r="right",l+=a)):(l=this.right-o,"near"===i?r="right":"center"===i?(r="center",l-=a/2):(r="left",l=this.left)):"right"===e?s?(l=this.left+n,"near"===i?r="right":"center"===i?(r="center",l-=a/2):(r="left",l-=a)):(l=this.left+o,"near"===i?r="left":"center"===i?(r="center",l+=a/2):(r="right",l=this.right)):r="right",{textAlign:r,x:l}}_computeLabelArea(){if(this.options.ticks.mirror)return;const t=this.chart,e=this.options.position;return"left"===e||"right"===e?{top:0,left:this.left,bottom:t.height,right:this.right}:"top"===e||"bottom"===e?{top:this.top,left:0,bottom:this.bottom,right:t.width}:void 0}drawBackground(){const{ctx:t,options:{backgroundColor:e},left:i,top:s,width:n,height:o}=this;e&&(t.save(),t.fillStyle=e,t.fillRect(i,s,n,o),t.restore())}getLineWidthForValue(t){const e=this.options.grid;if(!this._isVisible()||!e.display)return 0;const i=this.ticks.findIndex((e=>e.value===t));if(i>=0){return e.setContext(this.getContext(i)).lineWidth}return 0}drawGrid(t){const e=this.options.grid,i=this.ctx,s=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(t));let n,o;const a=(t,e,s)=>{s.width&&s.color&&(i.save(),i.lineWidth=s.width,i.strokeStyle=s.color,i.setLineDash(s.borderDash||[]),i.lineDashOffset=s.borderDashOffset,i.beginPath(),i.moveTo(t.x,t.y),i.lineTo(e.x,e.y),i.stroke(),i.restore())};if(e.display)for(n=0,o=s.length;n{this.drawBackground(),this.drawGrid(t),this.drawTitle()}},{z:s,draw:()=>{this.drawBorder()}},{z:e,draw:t=>{this.drawLabels(t)}}]:[{z:e,draw:t=>{this.draw(t)}}]}getMatchingVisibleMetas(t){const e=this.chart.getSortedVisibleDatasetMetas(),i=this.axis+"AxisID",s=[];let n,o;for(n=0,o=e.length;n{const s=i.split("."),n=s.pop(),o=[t].concat(s).join("."),a=e[i].split("."),r=a.pop(),l=a.join(".");ue.route(o,n,l,r)}))}(e,t.defaultRoutes);t.descriptors&&ue.describe(e,t.descriptors)}(t,o,i),this.override&&ue.override(t.id,t.overrides)),o}get(t){return this.items[t]}unregister(t){const e=this.items,i=t.id,s=this.scope;i in e&&delete e[i],s&&i in ue[s]&&(delete ue[s][i],this.override&&delete re[i])}}class sn{constructor(){this.controllers=new en(js,"datasets",!0),this.elements=new en($s,"elements"),this.plugins=new en(Object,"plugins"),this.scales=new en(tn,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,e,i){[...e].forEach((e=>{const s=i||this._getRegistryForType(e);i||s.isForType(e)||s===this.plugins&&e.id?this._exec(t,s,e):u(e,(e=>{const s=i||this._getRegistryForType(e);this._exec(t,s,e)}))}))}_exec(t,e,i){const s=w(t);d(i["before"+s],[],i),e[t](i),d(i["after"+s],[],i)}_getRegistryForType(t){for(let e=0;et.filter((t=>!e.some((e=>t.plugin.id===e.plugin.id))));this._notify(s(e,i),t,"stop"),this._notify(s(i,e),t,"start")}}function an(t,e){return e||!1!==t?!0===t?{}:t:null}function rn(t,{plugin:e,local:i},s,n){const o=t.pluginScopeKeys(e),a=t.getOptionScopes(s,o);return i&&e.defaults&&a.push(e.defaults),t.createResolver(a,n,[""],{scriptable:!1,indexable:!1,allKeys:!0})}function ln(t,e){const i=ue.datasets[t]||{};return((e.datasets||{})[t]||{}).indexAxis||e.indexAxis||i.indexAxis||"x"}function hn(t){if("x"===t||"y"===t||"r"===t)return t}function cn(t,...e){if(hn(t))return t;for(const s of e){const e=s.axis||("top"===(i=s.position)||"bottom"===i?"x":"left"===i||"right"===i?"y":void 0)||t.length>1&&hn(t[0].toLowerCase());if(e)return e}var i;throw new Error(`Cannot determine type of '${t}' axis. Please provide 'axis' or 'position' option.`)}function dn(t,e,i){if(i[e+"AxisID"]===t)return{axis:e}}function un(t,e){const i=re[t.type]||{scales:{}},s=e.scales||{},n=ln(t.type,e),a=Object.create(null);return Object.keys(s).forEach((e=>{const r=s[e];if(!o(r))return console.error(`Invalid scale configuration for scale: ${e}`);if(r._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${e}`);const l=cn(e,r,function(t,e){if(e.data&&e.data.datasets){const i=e.data.datasets.filter((e=>e.xAxisID===t||e.yAxisID===t));if(i.length)return dn(t,"x",i[0])||dn(t,"y",i[0])}return{}}(e,t),ue.scales[r.type]),h=function(t,e){return t===e?"_index_":"_value_"}(l,n),c=i.scales||{};a[e]=b(Object.create(null),[{axis:l},r,c[l],c[h]])})),t.data.datasets.forEach((i=>{const n=i.type||t.type,o=i.indexAxis||ln(n,e),r=(re[n]||{}).scales||{};Object.keys(r).forEach((t=>{const e=function(t,e){let i=t;return"_index_"===t?i=e:"_value_"===t&&(i="x"===e?"y":"x"),i}(t,o),n=i[e+"AxisID"]||e;a[n]=a[n]||Object.create(null),b(a[n],[{axis:e},s[n],r[t]])}))})),Object.keys(a).forEach((t=>{const e=a[t];b(e,[ue.scales[e.type],ue.scale])})),a}function fn(t){const e=t.options||(t.options={});e.plugins=l(e.plugins,{}),e.scales=un(t,e)}function gn(t){return(t=t||{}).datasets=t.datasets||[],t.labels=t.labels||[],t}const pn=new Map,mn=new Set;function xn(t,e){let i=pn.get(t);return i||(i=e(),pn.set(t,i),mn.add(i)),i}const bn=(t,e,i)=>{const s=M(e,i);void 0!==s&&t.add(s)};class _n{constructor(t){this._config=function(t){return(t=t||{}).data=gn(t.data),fn(t),t}(t),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=gn(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){const t=this._config;this.clearCache(),fn(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return xn(t,(()=>[[`datasets.${t}`,""]]))}datasetAnimationScopeKeys(t,e){return xn(`${t}.transition.${e}`,(()=>[[`datasets.${t}.transitions.${e}`,`transitions.${e}`],[`datasets.${t}`,""]]))}datasetElementScopeKeys(t,e){return xn(`${t}-${e}`,(()=>[[`datasets.${t}.elements.${e}`,`datasets.${t}`,`elements.${e}`,""]]))}pluginScopeKeys(t){const e=t.id;return xn(`${this.type}-plugin-${e}`,(()=>[[`plugins.${e}`,...t.additionalOptionScopes||[]]]))}_cachedScopes(t,e){const i=this._scopeCache;let s=i.get(t);return s&&!e||(s=new Map,i.set(t,s)),s}getOptionScopes(t,e,i){const{options:s,type:n}=this,o=this._cachedScopes(t,i),a=o.get(e);if(a)return a;const r=new Set;e.forEach((e=>{t&&(r.add(t),e.forEach((e=>bn(r,t,e)))),e.forEach((t=>bn(r,s,t))),e.forEach((t=>bn(r,re[n]||{},t))),e.forEach((t=>bn(r,ue,t))),e.forEach((t=>bn(r,le,t)))}));const l=Array.from(r);return 0===l.length&&l.push(Object.create(null)),mn.has(e)&&o.set(e,l),l}chartOptionScopes(){const{options:t,type:e}=this;return[t,re[e]||{},ue.datasets[e]||{},{type:e},ue,le]}resolveNamedOptions(t,e,i,s=[""]){const o={$shared:!0},{resolver:a,subPrefixes:r}=yn(this._resolverCache,t,s);let l=a;if(function(t,e){const{isScriptable:i,isIndexable:s}=Ye(t);for(const o of e){const e=i(o),a=s(o),r=(a||e)&&t[o];if(e&&(S(r)||vn(r))||a&&n(r))return!0}return!1}(a,e)){o.$shared=!1;l=$e(a,i=S(i)?i():i,this.createResolver(t,i,r))}for(const t of e)o[t]=l[t];return o}createResolver(t,e,i=[""],s){const{resolver:n}=yn(this._resolverCache,t,i);return o(e)?$e(n,e,void 0,s):n}}function yn(t,e,i){let s=t.get(e);s||(s=new Map,t.set(e,s));const n=i.join();let o=s.get(n);if(!o){o={resolver:je(e,i),subPrefixes:i.filter((t=>!t.toLowerCase().includes("hover")))},s.set(n,o)}return o}const vn=t=>o(t)&&Object.getOwnPropertyNames(t).some((e=>S(t[e])));const Mn=["top","bottom","left","right","chartArea"];function wn(t,e){return"top"===t||"bottom"===t||-1===Mn.indexOf(t)&&"x"===e}function kn(t,e){return function(i,s){return i[t]===s[t]?i[e]-s[e]:i[t]-s[t]}}function Sn(t){const e=t.chart,i=e.options.animation;e.notifyPlugins("afterRender"),d(i&&i.onComplete,[t],e)}function Pn(t){const e=t.chart,i=e.options.animation;d(i&&i.onProgress,[t],e)}function Dn(t){return fe()&&"string"==typeof t?t=document.getElementById(t):t&&t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas),t}const Cn={},On=t=>{const e=Dn(t);return Object.values(Cn).filter((t=>t.canvas===e)).pop()};function An(t,e,i){const s=Object.keys(t);for(const n of s){const s=+n;if(s>=e){const o=t[n];delete t[n],(i>0||s>e)&&(t[s+i]=o)}}}class Tn{static defaults=ue;static instances=Cn;static overrides=re;static registry=nn;static version="4.5.1";static getChart=On;static register(...t){nn.add(...t),Ln()}static unregister(...t){nn.remove(...t),Ln()}constructor(t,e){const s=this.config=new _n(e),n=Dn(t),o=On(n);if(o)throw new Error("Canvas is already in use. Chart with ID '"+o.id+"' must be destroyed before the canvas with ID '"+o.canvas.id+"' can be reused.");const a=s.createResolver(s.chartOptionScopes(),this.getContext());this.platform=new(s.platform||Ps(n)),this.platform.updateConfig(s);const r=this.platform.acquireContext(n,a.aspectRatio),l=r&&r.canvas,h=l&&l.height,c=l&&l.width;this.id=i(),this.ctx=r,this.canvas=l,this.width=c,this.height=h,this._options=a,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new on,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=dt((t=>this.update(t)),a.resizeDelay||0),this._dataChanges=[],Cn[this.id]=this,r&&l?(bt.listen(this,"complete",Sn),bt.listen(this,"progress",Pn),this._initialize(),this.attached&&this.update()):console.error("Failed to create chart: can't acquire context from the given item")}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:e},width:i,height:n,_aspectRatio:o}=this;return s(t)?e&&o?o:n?i/n:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}get registry(){return nn}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():ke(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return Te(this.canvas,this.ctx),this}stop(){return bt.stop(this),this}resize(t,e){bt.running(this)?this._resizeBeforeDraw={width:t,height:e}:this._resize(t,e)}_resize(t,e){const i=this.options,s=this.canvas,n=i.maintainAspectRatio&&this.aspectRatio,o=this.platform.getMaximumSize(s,t,e,n),a=i.devicePixelRatio||this.platform.getDevicePixelRatio(),r=this.width?"resize":"attach";this.width=o.width,this.height=o.height,this._aspectRatio=this.aspectRatio,ke(this,a,!0)&&(this.notifyPlugins("resize",{size:o}),d(i.onResize,[this,o],this),this.attached&&this._doResize(r)&&this.render())}ensureScalesHaveIDs(){u(this.options.scales||{},((t,e)=>{t.id=e}))}buildOrUpdateScales(){const t=this.options,e=t.scales,i=this.scales,s=Object.keys(i).reduce(((t,e)=>(t[e]=!1,t)),{});let n=[];e&&(n=n.concat(Object.keys(e).map((t=>{const i=e[t],s=cn(t,i),n="r"===s,o="x"===s;return{options:i,dposition:n?"chartArea":o?"bottom":"left",dtype:n?"radialLinear":o?"category":"linear"}})))),u(n,(e=>{const n=e.options,o=n.id,a=cn(o,n),r=l(n.type,e.dtype);void 0!==n.position&&wn(n.position,a)===wn(e.dposition)||(n.position=e.dposition),s[o]=!0;let h=null;if(o in i&&i[o].type===r)h=i[o];else{h=new(nn.getScale(r))({id:o,type:r,ctx:this.ctx,chart:this}),i[h.id]=h}h.init(n,t)})),u(s,((t,e)=>{t||delete i[e]})),u(i,(t=>{ls.configure(this,t,t.options),ls.addBox(this,t)}))}_updateMetasets(){const t=this._metasets,e=this.data.datasets.length,i=t.length;if(t.sort(((t,e)=>t.index-e.index)),i>e){for(let t=e;te.length&&delete this._stacks,t.forEach(((t,i)=>{0===e.filter((e=>e===t._dataset)).length&&this._destroyDatasetMeta(i)}))}buildOrUpdateControllers(){const t=[],e=this.data.datasets;let i,s;for(this._removeUnreferencedMetasets(),i=0,s=e.length;i{this.getDatasetMeta(e).controller.reset()}),this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const e=this.config;e.update();const i=this._options=e.createResolver(e.chartOptionScopes(),this.getContext()),s=this._animationsDisabled=!i.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),!1===this.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0}))return;const n=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let o=0;for(let t=0,e=this.data.datasets.length;t{t.reset()})),this._updateDatasets(t),this.notifyPlugins("afterUpdate",{mode:t}),this._layers.sort(kn("z","_idx"));const{_active:a,_lastEvent:r}=this;r?this._eventHandler(r,!0):a.length&&this._updateHoverStyles(a,a,!0),this.render()}_updateScales(){u(this.scales,(t=>{ls.removeBox(this,t)})),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){const t=this.options,e=new Set(Object.keys(this._listeners)),i=new Set(t.events);P(e,i)&&!!this._responsiveListeners===t.responsive||(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){const{_hiddenIndices:t}=this,e=this._getUniformDataChanges()||[];for(const{method:i,start:s,count:n}of e){An(t,s,"_removeElements"===i?-n:n)}}_getUniformDataChanges(){const t=this._dataChanges;if(!t||!t.length)return;this._dataChanges=[];const e=this.data.datasets.length,i=e=>new Set(t.filter((t=>t[0]===e)).map(((t,e)=>e+","+t.splice(1).join(",")))),s=i(0);for(let t=1;tt.split(","))).map((t=>({method:t[1],start:+t[2],count:+t[3]})))}_updateLayout(t){if(!1===this.notifyPlugins("beforeLayout",{cancelable:!0}))return;ls.update(this,this.width,this.height,t);const e=this.chartArea,i=e.width<=0||e.height<=0;this._layers=[],u(this.boxes,(t=>{i&&"chartArea"===t.position||(t.configure&&t.configure(),this._layers.push(...t._layers()))}),this),this._layers.forEach(((t,e)=>{t._idx=e})),this.notifyPlugins("afterLayout")}_updateDatasets(t){if(!1!==this.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})){for(let t=0,e=this.data.datasets.length;t=0;--e)this._drawDataset(t[e]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const e=this.ctx,i={meta:t,index:t.index,cancelable:!0},s=Ni(this,t);!1!==this.notifyPlugins("beforeDatasetDraw",i)&&(s&&Ie(e,s),t.controller.draw(),s&&ze(e),i.cancelable=!1,this.notifyPlugins("afterDatasetDraw",i))}isPointInArea(t){return Re(t,this.chartArea,this._minPadding)}getElementsAtEventForMode(t,e,i,s){const n=Ki.modes[e];return"function"==typeof n?n(this,t,i,s):[]}getDatasetMeta(t){const e=this.data.datasets[t],i=this._metasets;let s=i.filter((t=>t&&t._dataset===e)).pop();return s||(s={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e&&e.order||0,index:t,_dataset:e,_parsed:[],_sorted:!1},i.push(s)),s}getContext(){return this.$context||(this.$context=Ci(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const e=this.data.datasets[t];if(!e)return!1;const i=this.getDatasetMeta(t);return"boolean"==typeof i.hidden?!i.hidden:!e.hidden}setDatasetVisibility(t,e){this.getDatasetMeta(t).hidden=!e}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateVisibility(t,e,i){const s=i?"show":"hide",n=this.getDatasetMeta(t),o=n.controller._resolveAnimations(void 0,s);k(e)?(n.data[e].hidden=!i,this.update()):(this.setDatasetVisibility(t,i),o.update(n,{visible:i}),this.update((e=>e.datasetIndex===t?s:void 0)))}hide(t,e){this._updateVisibility(t,e,!1)}show(t,e){this._updateVisibility(t,e,!0)}_destroyDatasetMeta(t){const e=this._metasets[t];e&&e.controller&&e.controller._destroy(),delete this._metasets[t]}_stop(){let t,e;for(this.stop(),bt.remove(this),t=0,e=this.data.datasets.length;t{e.addEventListener(this,i,s),t[i]=s},s=(t,e,i)=>{t.offsetX=e,t.offsetY=i,this._eventHandler(t)};u(this.options.events,(t=>i(t,s)))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});const t=this._responsiveListeners,e=this.platform,i=(i,s)=>{e.addEventListener(this,i,s),t[i]=s},s=(i,s)=>{t[i]&&(e.removeEventListener(this,i,s),delete t[i])},n=(t,e)=>{this.canvas&&this.resize(t,e)};let o;const a=()=>{s("attach",a),this.attached=!0,this.resize(),i("resize",n),i("detach",o)};o=()=>{this.attached=!1,s("resize",n),this._stop(),this._resize(0,0),i("attach",a)},e.isAttached(this.canvas)?a():o()}unbindEvents(){u(this._listeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._listeners={},u(this._responsiveListeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._responsiveListeners=void 0}updateHoverStyle(t,e,i){const s=i?"set":"remove";let n,o,a,r;for("dataset"===e&&(n=this.getDatasetMeta(t[0].datasetIndex),n.controller["_"+s+"DatasetHoverStyle"]()),a=0,r=t.length;a{const i=this.getDatasetMeta(t);if(!i)throw new Error("No dataset found at index "+t);return{datasetIndex:t,element:i.data[e],index:e}}));!f(i,e)&&(this._active=i,this._lastEvent=null,this._updateHoverStyles(i,e))}notifyPlugins(t,e,i){return this._plugins.notify(this,t,e,i)}isPluginEnabled(t){return 1===this._plugins._cache.filter((e=>e.plugin.id===t)).length}_updateHoverStyles(t,e,i){const s=this.options.hover,n=(t,e)=>t.filter((t=>!e.some((e=>t.datasetIndex===e.datasetIndex&&t.index===e.index)))),o=n(e,t),a=i?t:n(t,e);o.length&&this.updateHoverStyle(o,s.mode,!1),a.length&&s.mode&&this.updateHoverStyle(a,s.mode,!0)}_eventHandler(t,e){const i={event:t,replay:e,cancelable:!0,inChartArea:this.isPointInArea(t)},s=e=>(e.options.events||this.options.events).includes(t.native.type);if(!1===this.notifyPlugins("beforeEvent",i,s))return;const n=this._handleEvent(t,e,i.inChartArea);return i.cancelable=!1,this.notifyPlugins("afterEvent",i,s),(n||i.changed)&&this.render(),this}_handleEvent(t,e,i){const{_active:s=[],options:n}=this,o=e,a=this._getActiveElements(t,s,i,o),r=D(t),l=function(t,e,i,s){return i&&"mouseout"!==t.type?s?e:t:null}(t,this._lastEvent,i,r);i&&(this._lastEvent=null,d(n.onHover,[t,a,this],this),r&&d(n.onClick,[t,a,this],this));const h=!f(a,s);return(h||e)&&(this._active=a,this._updateHoverStyles(a,s,e)),this._lastEvent=l,h}_getActiveElements(t,e,i,s){if("mouseout"===t.type)return[];if(!i)return e;const n=this.options.hover;return this.getElementsAtEventForMode(t,n.mode,n,s)}}function Ln(){return u(Tn.instances,(t=>t._plugins.invalidate()))}function En(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}class Rn{static override(t){Object.assign(Rn.prototype,t)}options;constructor(t){this.options=t||{}}init(){}formats(){return En()}parse(){return En()}format(){return En()}add(){return En()}diff(){return En()}startOf(){return En()}endOf(){return En()}}var In={_date:Rn};function zn(t){const e=t.iScale,i=function(t,e){if(!t._cache.$bar){const i=t.getMatchingVisibleMetas(e);let s=[];for(let e=0,n=i.length;et-e)))}return t._cache.$bar}(e,t.type);let s,n,o,a,r=e._length;const l=()=>{32767!==o&&-32768!==o&&(k(a)&&(r=Math.min(r,Math.abs(o-a)||r)),a=o)};for(s=0,n=i.length;sMath.abs(r)&&(l=r,h=a),e[i.axis]=h,e._custom={barStart:l,barEnd:h,start:n,end:o,min:a,max:r}}(t,e,i,s):e[i.axis]=i.parse(t,s),e}function Vn(t,e,i,s){const n=t.iScale,o=t.vScale,a=n.getLabels(),r=n===o,l=[];let h,c,d,u;for(h=i,c=i+s;ht.x,i="left",s="right"):(e=t.base"spacing"!==t,_indexable:t=>"spacing"!==t&&!t.startsWith("borderDash")&&!t.startsWith("hoverBorderDash")};static overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const e=t.data,{labels:{pointStyle:i,textAlign:s,color:n,useBorderRadius:o,borderRadius:a}}=t.legend.options;return e.labels.length&&e.datasets.length?e.labels.map(((e,r)=>{const l=t.getDatasetMeta(0).controller.getStyle(r);return{text:e,fillStyle:l.backgroundColor,fontColor:n,hidden:!t.getDataVisibility(r),lineDash:l.borderDash,lineDashOffset:l.borderDashOffset,lineJoin:l.borderJoinStyle,lineWidth:l.borderWidth,strokeStyle:l.borderColor,textAlign:s,pointStyle:i,borderRadius:o&&(a||l.borderRadius),index:r}})):[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}}}};constructor(t,e){super(t,e),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,e){const i=this.getDataset().data,s=this._cachedMeta;if(!1===this._parsing)s._parsed=i;else{let n,a,r=t=>+i[t];if(o(i[t])){const{key:t="value"}=this._parsing;r=e=>+M(i[e],t)}for(n=t,a=t+e;nJ(t,r,l,!0)?1:Math.max(e,e*i,s,s*i),g=(t,e,s)=>J(t,r,l,!0)?-1:Math.min(e,e*i,s,s*i),p=f(0,h,d),m=f(E,c,u),x=g(C,h,d),b=g(C+E,c,u);s=(p-x)/2,n=(m-b)/2,o=-(p+x)/2,a=-(m+b)/2}return{ratioX:s,ratioY:n,offsetX:o,offsetY:a}}(u,d,r),x=(i.width-o)/f,b=(i.height-o)/g,_=Math.max(Math.min(x,b)/2,0),y=c(this.options.radius,_),v=(y-Math.max(y*r,0))/this._getVisibleDatasetWeightTotal();this.offsetX=p*y,this.offsetY=m*y,s.total=this.calculateTotal(),this.outerRadius=y-v*this._getRingWeightOffset(this.index),this.innerRadius=Math.max(this.outerRadius-v*l,0),this.updateElements(n,0,n.length,t)}_circumference(t,e){const i=this.options,s=this._cachedMeta,n=this._getCircumference();return e&&i.animation.animateRotate||!this.chart.getDataVisibility(t)||null===s._parsed[t]||s.data[t].hidden?0:this.calculateCircumference(s._parsed[t]*n/O)}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.chartArea,r=o.options.animation,l=(a.left+a.right)/2,h=(a.top+a.bottom)/2,c=n&&r.animateScale,d=c?0:this.innerRadius,u=c?0:this.outerRadius,{sharedOptions:f,includeOptions:g}=this._getSharedOptions(e,s);let p,m=this._getRotation();for(p=0;p0&&!isNaN(t)?O*(Math.abs(t)/e):0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=ne(e._parsed[t],i.options.locale);return{label:s[t]||"",value:n}}getMaxBorderWidth(t){let e=0;const i=this.chart;let s,n,o,a,r;if(!t)for(s=0,n=i.data.datasets.length;s{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,fontColor:s,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}};constructor(t,e){super(t,e),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=ne(e._parsed[t].r,i.options.locale);return{label:s[t]||"",value:n}}parseObjectData(t,e,i,s){return ii.bind(this)(t,e,i,s)}update(t){const e=this._cachedMeta.data;this._updateRadius(),this.updateElements(e,0,e.length,t)}getMinMax(){const t=this._cachedMeta,e={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY};return t.data.forEach(((t,i)=>{const s=this.getParsed(i).r;!isNaN(s)&&this.chart.getDataVisibility(i)&&(se.max&&(e.max=s))})),e}_updateRadius(){const t=this.chart,e=t.chartArea,i=t.options,s=Math.min(e.right-e.left,e.bottom-e.top),n=Math.max(s/2,0),o=(n-Math.max(i.cutoutPercentage?n/100*i.cutoutPercentage:1,0))/t.getVisibleDatasetCount();this.outerRadius=n-o*this.index,this.innerRadius=this.outerRadius-o}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.options.animation,r=this._cachedMeta.rScale,l=r.xCenter,h=r.yCenter,c=r.getIndexAngle(0)-.5*C;let d,u=c;const f=360/this.countVisibleElements();for(d=0;d{!isNaN(this.getParsed(i).r)&&this.chart.getDataVisibility(i)&&e++})),e}_computeAngle(t,e,i){return this.chart.getDataVisibility(t)?$(this.resolveDataElementOptions(t,e).angle||i):0}}var Un=Object.freeze({__proto__:null,BarController:class extends js{static id="bar";static defaults={datasetElementType:!1,dataElementType:"bar",categoryPercentage:.8,barPercentage:.9,grouped:!0,animations:{numbers:{type:"number",properties:["x","y","base","width","height"]}}};static overrides={scales:{_index_:{type:"category",offset:!0,grid:{offset:!0}},_value_:{type:"linear",beginAtZero:!0}}};parsePrimitiveData(t,e,i,s){return Vn(t,e,i,s)}parseArrayData(t,e,i,s){return Vn(t,e,i,s)}parseObjectData(t,e,i,s){const{iScale:n,vScale:o}=t,{xAxisKey:a="x",yAxisKey:r="y"}=this._parsing,l="x"===n.axis?a:r,h="x"===o.axis?a:r,c=[];let d,u,f,g;for(d=i,u=i+s;dt.controller.options.grouped)),o=i.options.stacked,a=[],r=this._cachedMeta.controller.getParsed(e),l=r&&r[i.axis],h=t=>{const e=t._parsed.find((t=>t[i.axis]===l)),n=e&&e[t.vScale.axis];if(s(n)||isNaN(n))return!0};for(const i of n)if((void 0===e||!h(i))&&((!1===o||-1===a.indexOf(i.stack)||void 0===o&&void 0===i.stack)&&a.push(i.stack),i.index===t))break;return a.length||a.push(void 0),a}_getStackCount(t){return this._getStacks(void 0,t).length}_getAxisCount(){return this._getAxis().length}getFirstScaleIdForIndexAxis(){const t=this.chart.scales,e=this.chart.options.indexAxis;return Object.keys(t).filter((i=>t[i].axis===e)).shift()}_getAxis(){const t={},e=this.getFirstScaleIdForIndexAxis();for(const i of this.chart.data.datasets)t[l("x"===this.chart.options.indexAxis?i.xAxisID:i.yAxisID,e)]=!0;return Object.keys(t)}_getStackIndex(t,e,i){const s=this._getStacks(t,i),n=void 0!==e?s.indexOf(e):-1;return-1===n?s.length-1:n}_getRuler(){const t=this.options,e=this._cachedMeta,i=e.iScale,s=[];let n,o;for(n=0,o=e.data.length;n=i?1:-1)}(u,e,r)*a,f===r&&(x-=u/2);const t=e.getPixelForDecimal(0),s=e.getPixelForDecimal(1),o=Math.min(t,s),h=Math.max(t,s);x=Math.max(Math.min(x,h),o),d=x+u,i&&!c&&(l._stacks[e.axis]._visualValues[n]=e.getValueForPixel(d)-e.getValueForPixel(x))}if(x===e.getPixelForValue(r)){const t=F(u)*e.getLineWidthForValue(r)/2;x+=t,u-=t}return{size:u,base:x,head:d,center:d+u/2}}_calculateBarIndexPixels(t,e){const i=e.scale,n=this.options,o=n.skipNull,a=l(n.maxBarThickness,1/0);let r,h;const c=this._getAxisCount();if(e.grouped){const i=o?this._getStackCount(t):e.stackCount,d="flex"===n.barThickness?function(t,e,i,s){const n=e.pixels,o=n[t];let a=t>0?n[t-1]:null,r=t=0;--i)e=Math.max(e,t[i].size(this.resolveDataElementOptions(i))/2);return e>0&&e}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart.data.labels||[],{xScale:s,yScale:n}=e,o=this.getParsed(t),a=s.getLabelForValue(o.x),r=n.getLabelForValue(o.y),l=o._custom;return{label:i[t]||"",value:"("+a+", "+r+(l?", "+l:"")+")"}}update(t){const e=this._cachedMeta.data;this.updateElements(e,0,e.length,t)}updateElements(t,e,i,s){const n="reset"===s,{iScale:o,vScale:a}=this._cachedMeta,{sharedOptions:r,includeOptions:l}=this._getSharedOptions(e,s),h=o.axis,c=a.axis;for(let d=e;d0&&this.getParsed(e-1);for(let i=0;i<_;++i){const g=t[i],_=x?g:{};if(i=b){_.skip=!0;continue}const v=this.getParsed(i),M=s(v[f]),w=_[u]=a.getPixelForValue(v[u],i),k=_[f]=o||M?r.getBasePixel():r.getPixelForValue(l?this.applyStack(r,v,l):v[f],i);_.skip=isNaN(w)||isNaN(k)||M,_.stop=i>0&&Math.abs(v[u]-y[u])>m,p&&(_.parsed=v,_.raw=h.data[i]),d&&(_.options=c||this.resolveDataElementOptions(i,g.active?"active":n)),x||this.updateElement(g,i,_,n),y=v}}getMaxOverflow(){const t=this._cachedMeta,e=t.dataset,i=e.options&&e.options.borderWidth||0,s=t.data||[];if(!s.length)return i;const n=s[0].size(this.resolveDataElementOptions(0)),o=s[s.length-1].size(this.resolveDataElementOptions(s.length-1));return Math.max(i,n,o)/2}draw(){const t=this._cachedMeta;t.dataset.updateControlPoints(this.chart.chartArea,t.iScale.axis),super.draw()}},PieController:class extends $n{static id="pie";static defaults={cutout:0,rotation:0,circumference:360,radius:"100%"}},PolarAreaController:Yn,RadarController:class extends js{static id="radar";static defaults={datasetElementType:"line",dataElementType:"point",indexAxis:"r",showLine:!0,elements:{line:{fill:"start"}}};static overrides={aspectRatio:1,scales:{r:{type:"radialLinear"}}};getLabelAndValue(t){const e=this._cachedMeta.vScale,i=this.getParsed(t);return{label:e.getLabels()[t],value:""+e.getLabelForValue(i[e.axis])}}parseObjectData(t,e,i,s){return ii.bind(this)(t,e,i,s)}update(t){const e=this._cachedMeta,i=e.dataset,s=e.data||[],n=e.iScale.getLabels();if(i.points=s,"resize"!==t){const e=this.resolveDatasetElementOptions(t);this.options.showLine||(e.borderWidth=0);const o={_loop:!0,_fullLoop:n.length===s.length,options:e};this.updateElement(i,void 0,o,t)}this.updateElements(s,0,s.length,t)}updateElements(t,e,i,s){const n=this._cachedMeta.rScale,o="reset"===s;for(let a=e;a0&&this.getParsed(e-1);for(let c=e;c0&&Math.abs(i[f]-_[f])>x,m&&(p.parsed=i,p.raw=h.data[c]),u&&(p.options=d||this.resolveDataElementOptions(c,e.active?"active":n)),b||this.updateElement(e,c,p,n),_=i}this.updateSharedOptions(d,n,c)}getMaxOverflow(){const t=this._cachedMeta,e=t.data||[];if(!this.options.showLine){let t=0;for(let i=e.length-1;i>=0;--i)t=Math.max(t,e[i].size(this.resolveDataElementOptions(i))/2);return t>0&&t}const i=t.dataset,s=i.options&&i.options.borderWidth||0;if(!e.length)return s;const n=e[0].size(this.resolveDataElementOptions(0)),o=e[e.length-1].size(this.resolveDataElementOptions(e.length-1));return Math.max(s,n,o)/2}}});function Xn(t,e,i,s){const n=vi(t.options.borderRadius,["outerStart","outerEnd","innerStart","innerEnd"]);const o=(i-e)/2,a=Math.min(o,s*e/2),r=t=>{const e=(i-Math.min(o,t))*s/2;return Z(t,0,Math.min(o,e))};return{outerStart:r(n.outerStart),outerEnd:r(n.outerEnd),innerStart:Z(n.innerStart,0,a),innerEnd:Z(n.innerEnd,0,a)}}function qn(t,e,i,s){return{x:i+t*Math.cos(e),y:s+t*Math.sin(e)}}function Kn(t,e,i,s,n,o){const{x:a,y:r,startAngle:l,pixelMargin:h,innerRadius:c}=e,d=Math.max(e.outerRadius+s+i-h,0),u=c>0?c+s+i+h:0;let f=0;const g=n-l;if(s){const t=((c>0?c-s:0)+(d>0?d-s:0))/2;f=(g-(0!==t?g*t/(t+s):g))/2}const p=(g-Math.max(.001,g*d-i/C)/d)/2,m=l+p+f,x=n-p-f,{outerStart:b,outerEnd:_,innerStart:y,innerEnd:v}=Xn(e,u,d,x-m),M=d-b,w=d-_,k=m+b/M,S=x-_/w,P=u+y,D=u+v,O=m+y/P,A=x-v/D;if(t.beginPath(),o){const e=(k+S)/2;if(t.arc(a,r,d,k,e),t.arc(a,r,d,e,S),_>0){const e=qn(w,S,a,r);t.arc(e.x,e.y,_,S,x+E)}const i=qn(D,x,a,r);if(t.lineTo(i.x,i.y),v>0){const e=qn(D,A,a,r);t.arc(e.x,e.y,v,x+E,A+Math.PI)}const s=(x-v/u+(m+y/u))/2;if(t.arc(a,r,u,x-v/u,s,!0),t.arc(a,r,u,s,m+y/u,!0),y>0){const e=qn(P,O,a,r);t.arc(e.x,e.y,y,O+Math.PI,m-E)}const n=qn(M,m,a,r);if(t.lineTo(n.x,n.y),b>0){const e=qn(M,k,a,r);t.arc(e.x,e.y,b,m-E,k)}}else{t.moveTo(a,r);const e=Math.cos(k)*d+a,i=Math.sin(k)*d+r;t.lineTo(e,i);const s=Math.cos(S)*d+a,n=Math.sin(S)*d+r;t.lineTo(s,n)}t.closePath()}function Gn(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r,options:l}=e,{borderWidth:h,borderJoinStyle:c,borderDash:d,borderDashOffset:u,borderRadius:f}=l,g="inner"===l.borderAlign;if(!h)return;t.setLineDash(d||[]),t.lineDashOffset=u,g?(t.lineWidth=2*h,t.lineJoin=c||"round"):(t.lineWidth=h,t.lineJoin=c||"bevel");let p=e.endAngle;if(o){Kn(t,e,i,s,p,n);for(let e=0;en?(h=n/l,t.arc(o,a,l,i+h,s-h,!0)):t.arc(o,a,n,i+E,s-E),t.closePath(),t.clip()}(t,e,p),l.selfJoin&&p-a>=C&&0===f&&"miter"!==c&&function(t,e,i){const{startAngle:s,x:n,y:o,outerRadius:a,innerRadius:r,options:l}=e,{borderWidth:h,borderJoinStyle:c}=l,d=Math.min(h/a,G(s-i));if(t.beginPath(),t.arc(n,o,a-h/2,s+d/2,i-d/2),r>0){const e=Math.min(h/r,G(s-i));t.arc(n,o,r+h/2,i-e/2,s+e/2,!0)}else{const e=Math.min(h/2,a*G(s-i));if("round"===c)t.arc(n,o,e,i-C/2,s+C/2,!0);else if("bevel"===c){const a=2*e*e,r=-a*Math.cos(i+C/2)+n,l=-a*Math.sin(i+C/2)+o,h=a*Math.cos(s+C/2)+n,c=a*Math.sin(s+C/2)+o;t.lineTo(r,l),t.lineTo(h,c)}}t.closePath(),t.moveTo(0,0),t.rect(0,0,t.canvas.width,t.canvas.height),t.clip("evenodd")}(t,e,p),o||(Kn(t,e,i,s,p,n),t.stroke())}function Jn(t,e,i=e){t.lineCap=l(i.borderCapStyle,e.borderCapStyle),t.setLineDash(l(i.borderDash,e.borderDash)),t.lineDashOffset=l(i.borderDashOffset,e.borderDashOffset),t.lineJoin=l(i.borderJoinStyle,e.borderJoinStyle),t.lineWidth=l(i.borderWidth,e.borderWidth),t.strokeStyle=l(i.borderColor,e.borderColor)}function Zn(t,e,i){t.lineTo(i.x,i.y)}function Qn(t,e,i={}){const s=t.length,{start:n=0,end:o=s-1}=i,{start:a,end:r}=e,l=Math.max(n,a),h=Math.min(o,r),c=nr&&o>r;return{count:s,start:l,loop:e.loop,ilen:h(a+(h?r-t:t))%o,_=()=>{f!==g&&(t.lineTo(m,g),t.lineTo(m,f),t.lineTo(m,p))};for(l&&(d=n[b(0)],t.moveTo(d.x,d.y)),c=0;c<=r;++c){if(d=n[b(c)],d.skip)continue;const e=d.x,i=d.y,s=0|e;s===u?(ig&&(g=i),m=(x*m+e)/++x):(_(),t.lineTo(e,i),u=s,x=0,f=g=i),p=i}_()}function io(t){const e=t.options,i=e.borderDash&&e.borderDash.length;return!(t._decimated||t._loop||e.tension||"monotone"===e.cubicInterpolationMode||e.stepped||i)?eo:to}const so="function"==typeof Path2D;function no(t,e,i,s){so&&!e.options.segment?function(t,e,i,s){let n=e._path;n||(n=e._path=new Path2D,e.path(n,i,s)&&n.closePath()),Jn(t,e.options),t.stroke(n)}(t,e,i,s):function(t,e,i,s){const{segments:n,options:o}=e,a=io(e);for(const r of n)Jn(t,o,r.style),t.beginPath(),a(t,e,r,{start:i,end:i+s-1})&&t.closePath(),t.stroke()}(t,e,i,s)}class oo extends $s{static id="line";static defaults={borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderWidth:3,capBezierPoints:!0,cubicInterpolationMode:"default",fill:!1,spanGaps:!1,stepped:!1,tension:0};static defaultRoutes={backgroundColor:"backgroundColor",borderColor:"borderColor"};static descriptors={_scriptable:!0,_indexable:t=>"borderDash"!==t&&"fill"!==t};constructor(t){super(),this.animated=!0,this.options=void 0,this._chart=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,this._datasetIndex=void 0,t&&Object.assign(this,t)}updateControlPoints(t,e){const i=this.options;if((i.tension||"monotone"===i.cubicInterpolationMode)&&!i.stepped&&!this._pointsUpdated){const s=i.spanGaps?this._loop:this._fullLoop;hi(this._points,i,t,s,e),this._pointsUpdated=!0}}set points(t){this._points=t,delete this._segments,delete this._path,this._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=zi(this,this.options.segment))}first(){const t=this.segments,e=this.points;return t.length&&e[t[0].start]}last(){const t=this.segments,e=this.points,i=t.length;return i&&e[t[i-1].end]}interpolate(t,e){const i=this.options,s=t[e],n=this.points,o=Ii(this,{property:e,start:s,end:s});if(!o.length)return;const a=[],r=function(t){return t.stepped?pi:t.tension||"monotone"===t.cubicInterpolationMode?mi:gi}(i);let l,h;for(l=0,h=o.length;l"borderDash"!==t};circumference;endAngle;fullCircles;innerRadius;outerRadius;pixelMargin;startAngle;constructor(t){super(),this.options=void 0,this.circumference=void 0,this.startAngle=void 0,this.endAngle=void 0,this.innerRadius=void 0,this.outerRadius=void 0,this.pixelMargin=0,this.fullCircles=0,t&&Object.assign(this,t)}inRange(t,e,i){const s=this.getProps(["x","y"],i),{angle:n,distance:o}=X(s,{x:t,y:e}),{startAngle:a,endAngle:r,innerRadius:h,outerRadius:c,circumference:d}=this.getProps(["startAngle","endAngle","innerRadius","outerRadius","circumference"],i),u=(this.options.spacing+this.options.borderWidth)/2,f=l(d,r-a),g=J(n,a,r)&&a!==r,p=f>=O||g,m=tt(o,h+u,c+u);return p&&m}getCenterPoint(t){const{x:e,y:i,startAngle:s,endAngle:n,innerRadius:o,outerRadius:a}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],t),{offset:r,spacing:l}=this.options,h=(s+n)/2,c=(o+a+l+r)/2;return{x:e+Math.cos(h)*c,y:i+Math.sin(h)*c}}tooltipPosition(t){return this.getCenterPoint(t)}draw(t){const{options:e,circumference:i}=this,s=(e.offset||0)/4,n=(e.spacing||0)/2,o=e.circular;if(this.pixelMargin="inner"===e.borderAlign?.33:0,this.fullCircles=i>O?Math.floor(i/O):0,0===i||this.innerRadius<0||this.outerRadius<0)return;t.save();const a=(this.startAngle+this.endAngle)/2;t.translate(Math.cos(a)*s,Math.sin(a)*s);const r=s*(1-Math.sin(Math.min(C,i||0)));t.fillStyle=e.backgroundColor,t.strokeStyle=e.borderColor,function(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r}=e;let l=e.endAngle;if(o){Kn(t,e,i,s,l,n);for(let e=0;e("string"==typeof e?(i=t.push(e)-1,s.unshift({index:i,label:e})):isNaN(e)&&(i=null),i))(t,e,i,s);return n!==t.lastIndexOf(e)?i:n}function mo(t){const e=this.getLabels();return t>=0&&ts=e?s:t,a=t=>n=i?n:t;if(t){const t=F(s),e=F(n);t<0&&e<0?a(0):t>0&&e>0&&o(0)}if(s===n){let e=0===n?1:Math.abs(.05*n);a(n+e),t||o(s-e)}this.min=s,this.max=n}getTickLimit(){const t=this.options.ticks;let e,{maxTicksLimit:i,stepSize:s}=t;return s?(e=Math.ceil(this.max/s)-Math.floor(this.min/s)+1,e>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${s} would result generating up to ${e} ticks. Limiting to 1000.`),e=1e3)):(e=this.computeTickLimit(),i=i||11),i&&(e=Math.min(i,e)),e}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const t=this.options,e=t.ticks;let i=this.getTickLimit();i=Math.max(2,i);const n=function(t,e){const i=[],{bounds:n,step:o,min:a,max:r,precision:l,count:h,maxTicks:c,maxDigits:d,includeBounds:u}=t,f=o||1,g=c-1,{min:p,max:m}=e,x=!s(a),b=!s(r),_=!s(h),y=(m-p)/(d+1);let v,M,w,k,S=B((m-p)/g/f)*f;if(S<1e-14&&!x&&!b)return[{value:p},{value:m}];k=Math.ceil(m/S)-Math.floor(p/S),k>g&&(S=B(k*S/g/f)*f),s(l)||(v=Math.pow(10,l),S=Math.ceil(S*v)/v),"ticks"===n?(M=Math.floor(p/S)*S,w=Math.ceil(m/S)*S):(M=p,w=m),x&&b&&o&&H((r-a)/o,S/1e3)?(k=Math.round(Math.min((r-a)/S,c)),S=(r-a)/k,M=a,w=r):_?(M=x?a:M,w=b?r:w,k=h-1,S=(w-M)/k):(k=(w-M)/S,k=V(k,Math.round(k),S/1e3)?Math.round(k):Math.ceil(k));const P=Math.max(U(S),U(M));v=Math.pow(10,s(l)?P:l),M=Math.round(M*v)/v,w=Math.round(w*v)/v;let D=0;for(x&&(u&&M!==a?(i.push({value:a}),Mr)break;i.push({value:t})}return b&&u&&w!==r?i.length&&V(i[i.length-1].value,r,xo(r,y,t))?i[i.length-1].value=r:i.push({value:r}):b&&w!==r||i.push({value:w}),i}({maxTicks:i,bounds:t.bounds,min:t.min,max:t.max,precision:e.precision,step:e.stepSize,count:e.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:e.minRotation||0,includeBounds:!1!==e.includeBounds},this._range||this);return"ticks"===t.bounds&&j(n,this,"value"),t.reverse?(n.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),n}configure(){const t=this.ticks;let e=this.min,i=this.max;if(super.configure(),this.options.offset&&t.length){const s=(i-e)/Math.max(t.length-1,1)/2;e-=s,i+=s}this._startValue=e,this._endValue=i,this._valueRange=i-e}getLabelForValue(t){return ne(t,this.chart.options.locale,this.options.ticks.format)}}class _o extends bo{static id="linear";static defaults={ticks:{callback:ae.formatters.numeric}};determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a(t)?t:0,this.max=a(e)?e:1,this.handleTickRangeOptions()}computeTickLimit(){const t=this.isHorizontal(),e=t?this.width:this.height,i=$(this.options.ticks.minRotation),s=(t?Math.sin(i):Math.cos(i))||.001,n=this._resolveTickFontOptions(0);return Math.ceil(e/Math.min(40,n.lineHeight/s))}getPixelForValue(t){return null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getValueForPixel(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange}}const yo=t=>Math.floor(z(t)),vo=(t,e)=>Math.pow(10,yo(t)+e);function Mo(t){return 1===t/Math.pow(10,yo(t))}function wo(t,e,i){const s=Math.pow(10,i),n=Math.floor(t/s);return Math.ceil(e/s)-n}function ko(t,{min:e,max:i}){e=r(t.min,e);const s=[],n=yo(e);let o=function(t,e){let i=yo(e-t);for(;wo(t,e,i)>10;)i++;for(;wo(t,e,i)<10;)i--;return Math.min(i,yo(t))}(e,i),a=o<0?Math.pow(10,Math.abs(o)):1;const l=Math.pow(10,o),h=n>o?Math.pow(10,n):0,c=Math.round((e-h)*a)/a,d=Math.floor((e-h)/l/10)*l*10;let u=Math.floor((c-d)/Math.pow(10,o)),f=r(t.min,Math.round((h+d+u*Math.pow(10,o))*a)/a);for(;f=10?u=u<15?15:20:u++,u>=20&&(o++,u=2,a=o>=0?1:a),f=Math.round((h+d+u*Math.pow(10,o))*a)/a;const g=r(t.max,f);return s.push({value:g,major:Mo(g),significand:u}),s}class So extends tn{static id="logarithmic";static defaults={ticks:{callback:ae.formatters.logarithmic,major:{enabled:!0}}};constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._valueRange=0}parse(t,e){const i=bo.prototype.parse.apply(this,[t,e]);if(0!==i)return a(i)&&i>0?i:null;this._zero=!0}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a(t)?Math.max(0,t):null,this.max=a(e)?Math.max(0,e):null,this.options.beginAtZero&&(this._zero=!0),this._zero&&this.min!==this._suggestedMin&&!a(this._userMin)&&(this.min=t===vo(this.min,0)?vo(this.min,-1):vo(this.min,0)),this.handleTickRangeOptions()}handleTickRangeOptions(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let i=this.min,s=this.max;const n=e=>i=t?i:e,o=t=>s=e?s:t;i===s&&(i<=0?(n(1),o(10)):(n(vo(i,-1)),o(vo(s,1)))),i<=0&&n(vo(s,-1)),s<=0&&o(vo(i,1)),this.min=i,this.max=s}buildTicks(){const t=this.options,e=ko({min:this._userMin,max:this._userMax},this);return"ticks"===t.bounds&&j(e,this,"value"),t.reverse?(e.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),e}getLabelForValue(t){return void 0===t?"0":ne(t,this.chart.options.locale,this.options.ticks.format)}configure(){const t=this.min;super.configure(),this._startValue=z(t),this._valueRange=z(this.max)-z(t)}getPixelForValue(t){return void 0!==t&&0!==t||(t=this.min),null===t||isNaN(t)?NaN:this.getPixelForDecimal(t===this.min?0:(z(t)-this._startValue)/this._valueRange)}getValueForPixel(t){const e=this.getDecimalForPixel(t);return Math.pow(10,this._startValue+e*this._valueRange)}}function Po(t){const e=t.ticks;if(e.display&&t.display){const t=ki(e.backdropPadding);return l(e.font&&e.font.size,ue.font.size)+t.height}return 0}function Do(t,e,i,s,n){return t===s||t===n?{start:e-i/2,end:e+i/2}:tn?{start:e-i,end:e}:{start:e,end:e+i}}function Co(t){const e={l:t.left+t._padding.left,r:t.right-t._padding.right,t:t.top+t._padding.top,b:t.bottom-t._padding.bottom},i=Object.assign({},e),s=[],o=[],a=t._pointLabels.length,r=t.options.pointLabels,l=r.centerPointLabels?C/a:0;for(let u=0;ue.r&&(r=(s.end-e.r)/o,t.r=Math.max(t.r,e.r+r)),n.starte.b&&(l=(n.end-e.b)/a,t.b=Math.max(t.b,e.b+l))}function Ao(t,e,i){const s=t.drawingArea,{extra:n,additionalAngle:o,padding:a,size:r}=i,l=t.getPointPosition(e,s+n+a,o),h=Math.round(Y(G(l.angle+E))),c=function(t,e,i){90===i||270===i?t-=e/2:(i>270||i<90)&&(t-=e);return t}(l.y,r.h,h),d=function(t){if(0===t||180===t)return"center";if(t<180)return"left";return"right"}(h),u=function(t,e,i){"right"===i?t-=e:"center"===i&&(t-=e/2);return t}(l.x,r.w,d);return{visible:!0,x:l.x,y:c,textAlign:d,left:u,top:c,right:u+r.w,bottom:c+r.h}}function To(t,e){if(!e)return!0;const{left:i,top:s,right:n,bottom:o}=t;return!(Re({x:i,y:s},e)||Re({x:i,y:o},e)||Re({x:n,y:s},e)||Re({x:n,y:o},e))}function Lo(t,e,i){const{left:n,top:o,right:a,bottom:r}=i,{backdropColor:l}=e;if(!s(l)){const i=wi(e.borderRadius),s=ki(e.backdropPadding);t.fillStyle=l;const h=n-s.left,c=o-s.top,d=a-n+s.width,u=r-o+s.height;Object.values(i).some((t=>0!==t))?(t.beginPath(),He(t,{x:h,y:c,w:d,h:u,radius:i}),t.fill()):t.fillRect(h,c,d,u)}}function Eo(t,e,i,s){const{ctx:n}=t;if(i)n.arc(t.xCenter,t.yCenter,e,0,O);else{let i=t.getPointPosition(0,e);n.moveTo(i.x,i.y);for(let o=1;ot,padding:5,centerPointLabels:!1}};static defaultRoutes={"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"};static descriptors={angleLines:{_fallback:"grid"}};constructor(t){super(t),this.xCenter=void 0,this.yCenter=void 0,this.drawingArea=void 0,this._pointLabels=[],this._pointLabelItems=[]}setDimensions(){const t=this._padding=ki(Po(this.options)/2),e=this.width=this.maxWidth-t.width,i=this.height=this.maxHeight-t.height;this.xCenter=Math.floor(this.left+e/2+t.left),this.yCenter=Math.floor(this.top+i/2+t.top),this.drawingArea=Math.floor(Math.min(e,i)/2)}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!1);this.min=a(t)&&!isNaN(t)?t:0,this.max=a(e)&&!isNaN(e)?e:0,this.handleTickRangeOptions()}computeTickLimit(){return Math.ceil(this.drawingArea/Po(this.options))}generateTickLabels(t){bo.prototype.generateTickLabels.call(this,t),this._pointLabels=this.getLabels().map(((t,e)=>{const i=d(this.options.pointLabels.callback,[t,e],this);return i||0===i?i:""})).filter(((t,e)=>this.chart.getDataVisibility(e)))}fit(){const t=this.options;t.display&&t.pointLabels.display?Co(this):this.setCenterPoint(0,0,0,0)}setCenterPoint(t,e,i,s){this.xCenter+=Math.floor((t-e)/2),this.yCenter+=Math.floor((i-s)/2),this.drawingArea-=Math.min(this.drawingArea/2,Math.max(t,e,i,s))}getIndexAngle(t){return G(t*(O/(this._pointLabels.length||1))+$(this.options.startAngle||0))}getDistanceFromCenterForValue(t){if(s(t))return NaN;const e=this.drawingArea/(this.max-this.min);return this.options.reverse?(this.max-t)*e:(t-this.min)*e}getValueForDistanceFromCenter(t){if(s(t))return NaN;const e=t/(this.drawingArea/(this.max-this.min));return this.options.reverse?this.max-e:this.min+e}getPointLabelContext(t){const e=this._pointLabels||[];if(t>=0&&t=0;n--){const e=t._pointLabelItems[n];if(!e.visible)continue;const o=s.setContext(t.getPointLabelContext(n));Lo(i,o,e);const a=Si(o.font),{x:r,y:l,textAlign:h}=e;Ne(i,t._pointLabels[n],r,l+a.lineHeight/2,a,{color:o.color,textAlign:h,textBaseline:"middle"})}}(this,o),s.display&&this.ticks.forEach(((t,e)=>{if(0!==e||0===e&&this.min<0){r=this.getDistanceFromCenterForValue(t.value);const i=this.getContext(e),a=s.setContext(i),l=n.setContext(i);!function(t,e,i,s,n){const o=t.ctx,a=e.circular,{color:r,lineWidth:l}=e;!a&&!s||!r||!l||i<0||(o.save(),o.strokeStyle=r,o.lineWidth=l,o.setLineDash(n.dash||[]),o.lineDashOffset=n.dashOffset,o.beginPath(),Eo(t,i,a,s),o.closePath(),o.stroke(),o.restore())}(this,a,r,o,l)}})),i.display){for(t.save(),a=o-1;a>=0;a--){const s=i.setContext(this.getPointLabelContext(a)),{color:n,lineWidth:o}=s;o&&n&&(t.lineWidth=o,t.strokeStyle=n,t.setLineDash(s.borderDash),t.lineDashOffset=s.borderDashOffset,r=this.getDistanceFromCenterForValue(e.reverse?this.min:this.max),l=this.getPointPosition(a,r),t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(l.x,l.y),t.stroke())}t.restore()}}drawBorder(){}drawLabels(){const t=this.ctx,e=this.options,i=e.ticks;if(!i.display)return;const s=this.getIndexAngle(0);let n,o;t.save(),t.translate(this.xCenter,this.yCenter),t.rotate(s),t.textAlign="center",t.textBaseline="middle",this.ticks.forEach(((s,a)=>{if(0===a&&this.min>=0&&!e.reverse)return;const r=i.setContext(this.getContext(a)),l=Si(r.font);if(n=this.getDistanceFromCenterForValue(this.ticks[a].value),r.showLabelBackdrop){t.font=l.string,o=t.measureText(s.label).width,t.fillStyle=r.backdropColor;const e=ki(r.backdropPadding);t.fillRect(-o/2-e.left,-n-l.size/2-e.top,o+e.width,l.size+e.height)}Ne(t,s.label,0,-n,l,{color:r.color,strokeColor:r.textStrokeColor,strokeWidth:r.textStrokeWidth})})),t.restore()}drawTitle(){}}const Io={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},zo=Object.keys(Io);function Fo(t,e){return t-e}function Vo(t,e){if(s(e))return null;const i=t._adapter,{parser:n,round:o,isoWeekday:r}=t._parseOpts;let l=e;return"function"==typeof n&&(l=n(l)),a(l)||(l="string"==typeof n?i.parse(l,n):i.parse(l)),null===l?null:(o&&(l="week"!==o||!N(r)&&!0!==r?i.startOf(l,o):i.startOf(l,"isoWeek",r)),+l)}function Bo(t,e,i,s){const n=zo.length;for(let o=zo.indexOf(t);o=e?i[s]:i[n]]=!0}}else t[e]=!0}function No(t,e,i){const s=[],n={},o=e.length;let a,r;for(a=0;a=0&&(e[l].major=!0);return e}(t,s,n,i):s}class Ho extends tn{static id="time";static defaults={bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{source:"auto",callback:!1,major:{enabled:!1}}};constructor(t){super(t),this._cache={data:[],labels:[],all:[]},this._unit="day",this._majorUnit=void 0,this._offsets={},this._normalized=!1,this._parseOpts=void 0}init(t,e={}){const i=t.time||(t.time={}),s=this._adapter=new In._date(t.adapters.date);s.init(e),b(i.displayFormats,s.formats()),this._parseOpts={parser:i.parser,round:i.round,isoWeekday:i.isoWeekday},super.init(t),this._normalized=e.normalized}parse(t,e){return void 0===t?null:Vo(this,t)}beforeLayout(){super.beforeLayout(),this._cache={data:[],labels:[],all:[]}}determineDataLimits(){const t=this.options,e=this._adapter,i=t.time.unit||"day";let{min:s,max:n,minDefined:o,maxDefined:r}=this.getUserBounds();function l(t){o||isNaN(t.min)||(s=Math.min(s,t.min)),r||isNaN(t.max)||(n=Math.max(n,t.max))}o&&r||(l(this._getLabelBounds()),"ticks"===t.bounds&&"labels"===t.ticks.source||l(this.getMinMax(!1))),s=a(s)&&!isNaN(s)?s:+e.startOf(Date.now(),i),n=a(n)&&!isNaN(n)?n:+e.endOf(Date.now(),i)+1,this.min=Math.min(s,n-1),this.max=Math.max(s+1,n)}_getLabelBounds(){const t=this.getLabelTimestamps();let e=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;return t.length&&(e=t[0],i=t[t.length-1]),{min:e,max:i}}buildTicks(){const t=this.options,e=t.time,i=t.ticks,s="labels"===i.source?this.getLabelTimestamps():this._generate();"ticks"===t.bounds&&s.length&&(this.min=this._userMin||s[0],this.max=this._userMax||s[s.length-1]);const n=this.min,o=nt(s,n,this.max);return this._unit=e.unit||(i.autoSkip?Bo(e.minUnit,this.min,this.max,this._getLabelCapacity(n)):function(t,e,i,s,n){for(let o=zo.length-1;o>=zo.indexOf(i);o--){const i=zo[o];if(Io[i].common&&t._adapter.diff(n,s,i)>=e-1)return i}return zo[i?zo.indexOf(i):0]}(this,o.length,e.minUnit,this.min,this.max)),this._majorUnit=i.major.enabled&&"year"!==this._unit?function(t){for(let e=zo.indexOf(t)+1,i=zo.length;e+t.value)))}initOffsets(t=[]){let e,i,s=0,n=0;this.options.offset&&t.length&&(e=this.getDecimalForValue(t[0]),s=1===t.length?1-e:(this.getDecimalForValue(t[1])-e)/2,i=this.getDecimalForValue(t[t.length-1]),n=1===t.length?i:(i-this.getDecimalForValue(t[t.length-2]))/2);const o=t.length<3?.5:.25;s=Z(s,0,o),n=Z(n,0,o),this._offsets={start:s,end:n,factor:1/(s+1+n)}}_generate(){const t=this._adapter,e=this.min,i=this.max,s=this.options,n=s.time,o=n.unit||Bo(n.minUnit,e,i,this._getLabelCapacity(e)),a=l(s.ticks.stepSize,1),r="week"===o&&n.isoWeekday,h=N(r)||!0===r,c={};let d,u,f=e;if(h&&(f=+t.startOf(f,"isoWeek",r)),f=+t.startOf(f,h?"day":o),t.diff(i,e,o)>1e5*a)throw new Error(e+" and "+i+" are too far apart with stepSize of "+a+" "+o);const g="data"===s.ticks.source&&this.getDataTimestamps();for(d=f,u=0;d+t))}getLabelForValue(t){const e=this._adapter,i=this.options.time;return i.tooltipFormat?e.format(t,i.tooltipFormat):e.format(t,i.displayFormats.datetime)}format(t,e){const i=this.options.time.displayFormats,s=this._unit,n=e||i[s];return this._adapter.format(t,n)}_tickFormatFunction(t,e,i,s){const n=this.options,o=n.ticks.callback;if(o)return d(o,[t,e,i],this);const a=n.time.displayFormats,r=this._unit,l=this._majorUnit,h=r&&a[r],c=l&&a[l],u=i[e],f=l&&c&&u&&u.major;return this._adapter.format(t,s||(f?c:h))}generateTickLabels(t){let e,i,s;for(e=0,i=t.length;e0?a:1}getDataTimestamps(){let t,e,i=this._cache.data||[];if(i.length)return i;const s=this.getMatchingVisibleMetas();if(this._normalized&&s.length)return this._cache.data=s[0].controller.getAllParsedValues(this);for(t=0,e=s.length;t=t[r].pos&&e<=t[l].pos&&({lo:r,hi:l}=it(t,"pos",e)),({pos:s,time:o}=t[r]),({pos:n,time:a}=t[l])):(e>=t[r].time&&e<=t[l].time&&({lo:r,hi:l}=it(t,"time",e)),({time:s,pos:o}=t[r]),({time:n,pos:a}=t[l]));const h=n-s;return h?o+(a-o)*(e-s)/h:o}var $o=Object.freeze({__proto__:null,CategoryScale:class extends tn{static id="category";static defaults={ticks:{callback:mo}};constructor(t){super(t),this._startValue=void 0,this._valueRange=0,this._addedLabels=[]}init(t){const e=this._addedLabels;if(e.length){const t=this.getLabels();for(const{index:i,label:s}of e)t[i]===s&&t.splice(i,1);this._addedLabels=[]}super.init(t)}parse(t,e){if(s(t))return null;const i=this.getLabels();return((t,e)=>null===t?null:Z(Math.round(t),0,e))(e=isFinite(e)&&i[e]===t?e:po(i,t,l(e,t),this._addedLabels),i.length-1)}determineDataLimits(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let{min:i,max:s}=this.getMinMax(!0);"ticks"===this.options.bounds&&(t||(i=0),e||(s=this.getLabels().length-1)),this.min=i,this.max=s}buildTicks(){const t=this.min,e=this.max,i=this.options.offset,s=[];let n=this.getLabels();n=0===t&&e===n.length-1?n:n.slice(t,e+1),this._valueRange=Math.max(n.length-(i?0:1),1),this._startValue=this.min-(i?.5:0);for(let i=t;i<=e;i++)s.push({value:i});return s}getLabelForValue(t){return mo.call(this,t)}configure(){super.configure(),this.isHorizontal()||(this._reversePixels=!this._reversePixels)}getPixelForValue(t){return"number"!=typeof t&&(t=this.parse(t)),null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getValueForPixel(t){return Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange)}getBasePixel(){return this.bottom}},LinearScale:_o,LogarithmicScale:So,RadialLinearScale:Ro,TimeScale:Ho,TimeSeriesScale:class extends Ho{static id="timeseries";static defaults=Ho.defaults;constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const t=this._getTimestampsForTable(),e=this._table=this.buildLookupTable(t);this._minPos=jo(e,this.min),this._tableRange=jo(e,this.max)-this._minPos,super.initOffsets(t)}buildLookupTable(t){const{min:e,max:i}=this,s=[],n=[];let o,a,r,l,h;for(o=0,a=t.length;o=e&&l<=i&&s.push(l);if(s.length<2)return[{time:e,pos:0},{time:i,pos:1}];for(o=0,a=s.length;ot-e))}_getTimestampsForTable(){let t=this._cache.all||[];if(t.length)return t;const e=this.getDataTimestamps(),i=this.getLabelTimestamps();return t=e.length&&i.length?this.normalize(e.concat(i)):e.length?e:i,t=this._cache.all=t,t}getDecimalForValue(t){return(jo(this._table,t)-this._minPos)/this._tableRange}getValueForPixel(t){const e=this._offsets,i=this.getDecimalForPixel(t)/e.factor-e.end;return jo(this._table,i*this._tableRange+this._minPos,!0)}}});const Yo=["rgb(54, 162, 235)","rgb(255, 99, 132)","rgb(255, 159, 64)","rgb(255, 205, 86)","rgb(75, 192, 192)","rgb(153, 102, 255)","rgb(201, 203, 207)"],Uo=Yo.map((t=>t.replace("rgb(","rgba(").replace(")",", 0.5)")));function Xo(t){return Yo[t%Yo.length]}function qo(t){return Uo[t%Uo.length]}function Ko(t){let e=0;return(i,s)=>{const n=t.getDatasetMeta(s).controller;n instanceof $n?e=function(t,e){return t.backgroundColor=t.data.map((()=>Xo(e++))),e}(i,e):n instanceof Yn?e=function(t,e){return t.backgroundColor=t.data.map((()=>qo(e++))),e}(i,e):n&&(e=function(t,e){return t.borderColor=Xo(e),t.backgroundColor=qo(e),++e}(i,e))}}function Go(t){let e;for(e in t)if(t[e].borderColor||t[e].backgroundColor)return!0;return!1}var Jo={id:"colors",defaults:{enabled:!0,forceOverride:!1},beforeLayout(t,e,i){if(!i.enabled)return;const{data:{datasets:s},options:n}=t.config,{elements:o}=n,a=Go(s)||(r=n)&&(r.borderColor||r.backgroundColor)||o&&Go(o)||"rgba(0,0,0,0.1)"!==ue.borderColor||"rgba(0,0,0,0.1)"!==ue.backgroundColor;var r;if(!i.forceOverride&&a)return;const l=Ko(t);s.forEach(l)}};function Zo(t){if(t._decimated){const e=t._data;delete t._decimated,delete t._data,Object.defineProperty(t,"data",{configurable:!0,enumerable:!0,writable:!0,value:e})}}function Qo(t){t.data.datasets.forEach((t=>{Zo(t)}))}var ta={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(t,e,i)=>{if(!i.enabled)return void Qo(t);const n=t.width;t.data.datasets.forEach(((e,o)=>{const{_data:a,indexAxis:r}=e,l=t.getDatasetMeta(o),h=a||e.data;if("y"===Pi([r,t.options.indexAxis]))return;if(!l.controller.supportsDecimation)return;const c=t.scales[l.xAxisID];if("linear"!==c.type&&"time"!==c.type)return;if(t.options.parsing)return;let{start:d,count:u}=function(t,e){const i=e.length;let s,n=0;const{iScale:o}=t,{min:a,max:r,minDefined:l,maxDefined:h}=o.getUserBounds();return l&&(n=Z(it(e,o.axis,a).lo,0,i-1)),s=h?Z(it(e,o.axis,r).hi+1,n,i)-n:i-n,{start:n,count:s}}(l,h);if(u<=(i.threshold||4*n))return void Zo(e);let f;switch(s(a)&&(e._data=h,delete e.data,Object.defineProperty(e,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(t){this._data=t}})),i.algorithm){case"lttb":f=function(t,e,i,s,n){const o=n.samples||s;if(o>=i)return t.slice(e,e+i);const a=[],r=(i-2)/(o-2);let l=0;const h=e+i-1;let c,d,u,f,g,p=e;for(a[l++]=t[p],c=0;cu&&(u=f,d=t[s],g=s);a[l++]=d,p=g}return a[l++]=t[h],a}(h,d,u,n,i);break;case"min-max":f=function(t,e,i,n){let o,a,r,l,h,c,d,u,f,g,p=0,m=0;const x=[],b=e+i-1,_=t[e].x,y=t[b].x-_;for(o=e;og&&(g=l,d=o),p=(m*p+a.x)/++m;else{const i=o-1;if(!s(c)&&!s(d)){const e=Math.min(c,d),s=Math.max(c,d);e!==u&&e!==i&&x.push({...t[e],x:p}),s!==u&&s!==i&&x.push({...t[s],x:p})}o>0&&i!==u&&x.push(t[i]),x.push(a),h=e,m=0,f=g=l,c=d=u=o}}return x}(h,d,u,n);break;default:throw new Error(`Unsupported decimation algorithm '${i.algorithm}'`)}e._decimated=f}))},destroy(t){Qo(t)}};function ea(t,e,i,s){if(s)return;let n=e[t],o=i[t];return"angle"===t&&(n=G(n),o=G(o)),{property:t,start:n,end:o}}function ia(t,e,i){for(;e>t;e--){const t=i[e];if(!isNaN(t.x)&&!isNaN(t.y))break}return e}function sa(t,e,i,s){return t&&e?s(t[i],e[i]):t?t[i]:e?e[i]:0}function na(t,e){let i=[],s=!1;return n(t)?(s=!0,i=t):i=function(t,e){const{x:i=null,y:s=null}=t||{},n=e.points,o=[];return e.segments.forEach((({start:t,end:e})=>{e=ia(t,e,n);const a=n[t],r=n[e];null!==s?(o.push({x:a.x,y:s}),o.push({x:r.x,y:s})):null!==i&&(o.push({x:i,y:a.y}),o.push({x:i,y:r.y}))})),o}(t,e),i.length?new oo({points:i,options:{tension:0},_loop:s,_fullLoop:s}):null}function oa(t){return t&&!1!==t.fill}function aa(t,e,i){let s=t[e].fill;const n=[e];let o;if(!i)return s;for(;!1!==s&&-1===n.indexOf(s);){if(!a(s))return s;if(o=t[s],!o)return!1;if(o.visible)return s;n.push(s),s=o.fill}return!1}function ra(t,e,i){const s=function(t){const e=t.options,i=e.fill;let s=l(i&&i.target,i);void 0===s&&(s=!!e.backgroundColor);if(!1===s||null===s)return!1;if(!0===s)return"origin";return s}(t);if(o(s))return!isNaN(s.value)&&s;let n=parseFloat(s);return a(n)&&Math.floor(n)===n?function(t,e,i,s){"-"!==t&&"+"!==t||(i=e+i);if(i===e||i<0||i>=s)return!1;return i}(s[0],e,n,i):["origin","start","end","stack","shape"].indexOf(s)>=0&&s}function la(t,e,i){const s=[];for(let n=0;n=0;--e){const i=n[e].$filler;i&&(i.line.updateControlPoints(o,i.axis),s&&i.fill&&ua(t.ctx,i,o))}},beforeDatasetsDraw(t,e,i){if("beforeDatasetsDraw"!==i.drawTime)return;const s=t.getSortedVisibleDatasetMetas();for(let e=s.length-1;e>=0;--e){const i=s[e].$filler;oa(i)&&ua(t.ctx,i,t.chartArea)}},beforeDatasetDraw(t,e,i){const s=e.meta.$filler;oa(s)&&"beforeDatasetDraw"===i.drawTime&&ua(t.ctx,s,t.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}};const _a=(t,e)=>{let{boxHeight:i=e,boxWidth:s=e}=t;return t.usePointStyle&&(i=Math.min(i,e),s=t.pointStyleWidth||Math.min(s,e)),{boxWidth:s,boxHeight:i,itemHeight:Math.max(e,i)}};class ya extends $s{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e,i){this.maxWidth=t,this.maxHeight=e,this._margins=i,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){const t=this.options.labels||{};let e=d(t.generateLabels,[this.chart],this)||[];t.filter&&(e=e.filter((e=>t.filter(e,this.chart.data)))),t.sort&&(e=e.sort(((e,i)=>t.sort(e,i,this.chart.data)))),this.options.reverse&&e.reverse(),this.legendItems=e}fit(){const{options:t,ctx:e}=this;if(!t.display)return void(this.width=this.height=0);const i=t.labels,s=Si(i.font),n=s.size,o=this._computeTitleHeight(),{boxWidth:a,itemHeight:r}=_a(i,n);let l,h;e.font=s.string,this.isHorizontal()?(l=this.maxWidth,h=this._fitRows(o,n,a,r)+10):(h=this.maxHeight,l=this._fitCols(o,s,a,r)+10),this.width=Math.min(l,t.maxWidth||this.maxWidth),this.height=Math.min(h,t.maxHeight||this.maxHeight)}_fitRows(t,e,i,s){const{ctx:n,maxWidth:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.lineWidths=[0],h=s+a;let c=t;n.textAlign="left",n.textBaseline="middle";let d=-1,u=-h;return this.legendItems.forEach(((t,f)=>{const g=i+e/2+n.measureText(t.text).width;(0===f||l[l.length-1]+g+2*a>o)&&(c+=h,l[l.length-(f>0?0:1)]=0,u+=h,d++),r[f]={left:0,top:u,row:d,width:g,height:s},l[l.length-1]+=g+a})),c}_fitCols(t,e,i,s){const{ctx:n,maxHeight:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.columnSizes=[],h=o-t;let c=a,d=0,u=0,f=0,g=0;return this.legendItems.forEach(((t,o)=>{const{itemWidth:p,itemHeight:m}=function(t,e,i,s,n){const o=function(t,e,i,s){let n=t.text;n&&"string"!=typeof n&&(n=n.reduce(((t,e)=>t.length>e.length?t:e)));return e+i.size/2+s.measureText(n).width}(s,t,e,i),a=function(t,e,i){let s=t;"string"!=typeof e.text&&(s=va(e,i));return s}(n,s,e.lineHeight);return{itemWidth:o,itemHeight:a}}(i,e,n,t,s);o>0&&u+m+2*a>h&&(c+=d+a,l.push({width:d,height:u}),f+=d+a,g++,d=u=0),r[o]={left:f,top:u,col:g,width:p,height:m},d=Math.max(d,p),u+=m+a})),c+=d,l.push({width:d,height:u}),c}adjustHitBoxes(){if(!this.options.display)return;const t=this._computeTitleHeight(),{legendHitBoxes:e,options:{align:i,labels:{padding:s},rtl:n}}=this,o=Oi(n,this.left,this.width);if(this.isHorizontal()){let n=0,a=ft(i,this.left+s,this.right-this.lineWidths[n]);for(const r of e)n!==r.row&&(n=r.row,a=ft(i,this.left+s,this.right-this.lineWidths[n])),r.top+=this.top+t+s,r.left=o.leftForLtr(o.x(a),r.width),a+=r.width+s}else{let n=0,a=ft(i,this.top+t+s,this.bottom-this.columnSizes[n].height);for(const r of e)r.col!==n&&(n=r.col,a=ft(i,this.top+t+s,this.bottom-this.columnSizes[n].height)),r.top=a,r.left+=this.left+s,r.left=o.leftForLtr(o.x(r.left),r.width),a+=r.height+s}}isHorizontal(){return"top"===this.options.position||"bottom"===this.options.position}draw(){if(this.options.display){const t=this.ctx;Ie(t,this),this._draw(),ze(t)}}_draw(){const{options:t,columnSizes:e,lineWidths:i,ctx:s}=this,{align:n,labels:o}=t,a=ue.color,r=Oi(t.rtl,this.left,this.width),h=Si(o.font),{padding:c}=o,d=h.size,u=d/2;let f;this.drawTitle(),s.textAlign=r.textAlign("left"),s.textBaseline="middle",s.lineWidth=.5,s.font=h.string;const{boxWidth:g,boxHeight:p,itemHeight:m}=_a(o,d),x=this.isHorizontal(),b=this._computeTitleHeight();f=x?{x:ft(n,this.left+c,this.right-i[0]),y:this.top+c+b,line:0}:{x:this.left+c,y:ft(n,this.top+b+c,this.bottom-e[0].height),line:0},Ai(this.ctx,t.textDirection);const _=m+c;this.legendItems.forEach(((y,v)=>{s.strokeStyle=y.fontColor,s.fillStyle=y.fontColor;const M=s.measureText(y.text).width,w=r.textAlign(y.textAlign||(y.textAlign=o.textAlign)),k=g+u+M;let S=f.x,P=f.y;r.setWidth(this.width),x?v>0&&S+k+c>this.right&&(P=f.y+=_,f.line++,S=f.x=ft(n,this.left+c,this.right-i[f.line])):v>0&&P+_>this.bottom&&(S=f.x=S+e[f.line].width+c,f.line++,P=f.y=ft(n,this.top+b+c,this.bottom-e[f.line].height));if(function(t,e,i){if(isNaN(g)||g<=0||isNaN(p)||p<0)return;s.save();const n=l(i.lineWidth,1);if(s.fillStyle=l(i.fillStyle,a),s.lineCap=l(i.lineCap,"butt"),s.lineDashOffset=l(i.lineDashOffset,0),s.lineJoin=l(i.lineJoin,"miter"),s.lineWidth=n,s.strokeStyle=l(i.strokeStyle,a),s.setLineDash(l(i.lineDash,[])),o.usePointStyle){const a={radius:p*Math.SQRT2/2,pointStyle:i.pointStyle,rotation:i.rotation,borderWidth:n},l=r.xPlus(t,g/2);Ee(s,a,l,e+u,o.pointStyleWidth&&g)}else{const o=e+Math.max((d-p)/2,0),a=r.leftForLtr(t,g),l=wi(i.borderRadius);s.beginPath(),Object.values(l).some((t=>0!==t))?He(s,{x:a,y:o,w:g,h:p,radius:l}):s.rect(a,o,g,p),s.fill(),0!==n&&s.stroke()}s.restore()}(r.x(S),P,y),S=gt(w,S+g+u,x?S+k:this.right,t.rtl),function(t,e,i){Ne(s,i.text,t,e+m/2,h,{strikethrough:i.hidden,textAlign:r.textAlign(i.textAlign)})}(r.x(S),P,y),x)f.x+=k+c;else if("string"!=typeof y.text){const t=h.lineHeight;f.y+=va(y,t)+c}else f.y+=_})),Ti(this.ctx,t.textDirection)}drawTitle(){const t=this.options,e=t.title,i=Si(e.font),s=ki(e.padding);if(!e.display)return;const n=Oi(t.rtl,this.left,this.width),o=this.ctx,a=e.position,r=i.size/2,l=s.top+r;let h,c=this.left,d=this.width;if(this.isHorizontal())d=Math.max(...this.lineWidths),h=this.top+l,c=ft(t.align,c,this.right-d);else{const e=this.columnSizes.reduce(((t,e)=>Math.max(t,e.height)),0);h=l+ft(t.align,this.top,this.bottom-e-t.labels.padding-this._computeTitleHeight())}const u=ft(a,c,c+d);o.textAlign=n.textAlign(ut(a)),o.textBaseline="middle",o.strokeStyle=e.color,o.fillStyle=e.color,o.font=i.string,Ne(o,e.text,u,h,i)}_computeTitleHeight(){const t=this.options.title,e=Si(t.font),i=ki(t.padding);return t.display?e.lineHeight+i.height:0}_getLegendItemAt(t,e){let i,s,n;if(tt(t,this.left,this.right)&&tt(e,this.top,this.bottom))for(n=this.legendHitBoxes,i=0;it.chart.options.color,boxWidth:40,padding:10,generateLabels(t){const e=t.data.datasets,{labels:{usePointStyle:i,pointStyle:s,textAlign:n,color:o,useBorderRadius:a,borderRadius:r}}=t.legend.options;return t._getSortedDatasetMetas().map((t=>{const l=t.controller.getStyle(i?0:void 0),h=ki(l.borderWidth);return{text:e[t.index].label,fillStyle:l.backgroundColor,fontColor:o,hidden:!t.visible,lineCap:l.borderCapStyle,lineDash:l.borderDash,lineDashOffset:l.borderDashOffset,lineJoin:l.borderJoinStyle,lineWidth:(h.width+h.height)/4,strokeStyle:l.borderColor,pointStyle:s||l.pointStyle,rotation:l.rotation,textAlign:n||l.textAlign,borderRadius:a&&(r||l.borderRadius),datasetIndex:t.index}}),this)}},title:{color:t=>t.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:t=>!t.startsWith("on"),labels:{_scriptable:t=>!["generateLabels","filter","sort"].includes(t)}}};class wa extends $s{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e){const i=this.options;if(this.left=0,this.top=0,!i.display)return void(this.width=this.height=this.right=this.bottom=0);this.width=this.right=t,this.height=this.bottom=e;const s=n(i.text)?i.text.length:1;this._padding=ki(i.padding);const o=s*Si(i.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=o:this.width=o}isHorizontal(){const t=this.options.position;return"top"===t||"bottom"===t}_drawArgs(t){const{top:e,left:i,bottom:s,right:n,options:o}=this,a=o.align;let r,l,h,c=0;return this.isHorizontal()?(l=ft(a,i,n),h=e+t,r=n-i):("left"===o.position?(l=i+t,h=ft(a,s,e),c=-.5*C):(l=n-t,h=ft(a,e,s),c=.5*C),r=s-e),{titleX:l,titleY:h,maxWidth:r,rotation:c}}draw(){const t=this.ctx,e=this.options;if(!e.display)return;const i=Si(e.font),s=i.lineHeight/2+this._padding.top,{titleX:n,titleY:o,maxWidth:a,rotation:r}=this._drawArgs(s);Ne(t,e.text,0,0,i,{color:e.color,maxWidth:a,rotation:r,textAlign:ut(e.align),textBaseline:"middle",translation:[n,o]})}}var ka={id:"title",_element:wa,start(t,e,i){!function(t,e){const i=new wa({ctx:t.ctx,options:e,chart:t});ls.configure(t,i,e),ls.addBox(t,i),t.titleBlock=i}(t,i)},stop(t){const e=t.titleBlock;ls.removeBox(t,e),delete t.titleBlock},beforeUpdate(t,e,i){const s=t.titleBlock;ls.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Sa=new WeakMap;var Pa={id:"subtitle",start(t,e,i){const s=new wa({ctx:t.ctx,options:i,chart:t});ls.configure(t,s,i),ls.addBox(t,s),Sa.set(t,s)},stop(t){ls.removeBox(t,Sa.get(t)),Sa.delete(t)},beforeUpdate(t,e,i){const s=Sa.get(t);ls.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"normal"},fullSize:!0,padding:0,position:"top",text:"",weight:1500},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Da={average(t){if(!t.length)return!1;let e,i,s=new Set,n=0,o=0;for(e=0,i=t.length;et+e))/s.size,y:n/o}},nearest(t,e){if(!t.length)return!1;let i,s,n,o=e.x,a=e.y,r=Number.POSITIVE_INFINITY;for(i=0,s=t.length;i-1?t.split("\n"):t}function Aa(t,e){const{element:i,datasetIndex:s,index:n}=e,o=t.getDatasetMeta(s).controller,{label:a,value:r}=o.getLabelAndValue(n);return{chart:t,label:a,parsed:o.getParsed(n),raw:t.data.datasets[s].data[n],formattedValue:r,dataset:o.getDataset(),dataIndex:n,datasetIndex:s,element:i}}function Ta(t,e){const i=t.chart.ctx,{body:s,footer:n,title:o}=t,{boxWidth:a,boxHeight:r}=e,l=Si(e.bodyFont),h=Si(e.titleFont),c=Si(e.footerFont),d=o.length,f=n.length,g=s.length,p=ki(e.padding);let m=p.height,x=0,b=s.reduce(((t,e)=>t+e.before.length+e.lines.length+e.after.length),0);if(b+=t.beforeBody.length+t.afterBody.length,d&&(m+=d*h.lineHeight+(d-1)*e.titleSpacing+e.titleMarginBottom),b){m+=g*(e.displayColors?Math.max(r,l.lineHeight):l.lineHeight)+(b-g)*l.lineHeight+(b-1)*e.bodySpacing}f&&(m+=e.footerMarginTop+f*c.lineHeight+(f-1)*e.footerSpacing);let _=0;const y=function(t){x=Math.max(x,i.measureText(t).width+_)};return i.save(),i.font=h.string,u(t.title,y),i.font=l.string,u(t.beforeBody.concat(t.afterBody),y),_=e.displayColors?a+2+e.boxPadding:0,u(s,(t=>{u(t.before,y),u(t.lines,y),u(t.after,y)})),_=0,i.font=c.string,u(t.footer,y),i.restore(),x+=p.width,{width:x,height:m}}function La(t,e,i,s){const{x:n,width:o}=i,{width:a,chartArea:{left:r,right:l}}=t;let h="center";return"center"===s?h=n<=(r+l)/2?"left":"right":n<=o/2?h="left":n>=a-o/2&&(h="right"),function(t,e,i,s){const{x:n,width:o}=s,a=i.caretSize+i.caretPadding;return"left"===t&&n+o+a>e.width||"right"===t&&n-o-a<0||void 0}(h,t,e,i)&&(h="center"),h}function Ea(t,e,i){const s=i.yAlign||e.yAlign||function(t,e){const{y:i,height:s}=e;return it.height-s/2?"bottom":"center"}(t,i);return{xAlign:i.xAlign||e.xAlign||La(t,e,i,s),yAlign:s}}function Ra(t,e,i,s){const{caretSize:n,caretPadding:o,cornerRadius:a}=t,{xAlign:r,yAlign:l}=i,h=n+o,{topLeft:c,topRight:d,bottomLeft:u,bottomRight:f}=wi(a);let g=function(t,e){let{x:i,width:s}=t;return"right"===e?i-=s:"center"===e&&(i-=s/2),i}(e,r);const p=function(t,e,i){let{y:s,height:n}=t;return"top"===e?s+=i:s-="bottom"===e?n+i:n/2,s}(e,l,h);return"center"===l?"left"===r?g+=h:"right"===r&&(g-=h):"left"===r?g-=Math.max(c,u)+n:"right"===r&&(g+=Math.max(d,f)+n),{x:Z(g,0,s.width-e.width),y:Z(p,0,s.height-e.height)}}function Ia(t,e,i){const s=ki(i.padding);return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-s.right:t.x+s.left}function za(t){return Ca([],Oa(t))}function Fa(t,e){const i=e&&e.dataset&&e.dataset.tooltip&&e.dataset.tooltip.callbacks;return i?t.override(i):t}const Va={beforeTitle:e,title(t){if(t.length>0){const e=t[0],i=e.chart.data.labels,s=i?i.length:0;if(this&&this.options&&"dataset"===this.options.mode)return e.dataset.label||"";if(e.label)return e.label;if(s>0&&e.dataIndex{const e={before:[],lines:[],after:[]},n=Fa(i,t);Ca(e.before,Oa(Ba(n,"beforeLabel",this,t))),Ca(e.lines,Ba(n,"label",this,t)),Ca(e.after,Oa(Ba(n,"afterLabel",this,t))),s.push(e)})),s}getAfterBody(t,e){return za(Ba(e.callbacks,"afterBody",this,t))}getFooter(t,e){const{callbacks:i}=e,s=Ba(i,"beforeFooter",this,t),n=Ba(i,"footer",this,t),o=Ba(i,"afterFooter",this,t);let a=[];return a=Ca(a,Oa(s)),a=Ca(a,Oa(n)),a=Ca(a,Oa(o)),a}_createItems(t){const e=this._active,i=this.chart.data,s=[],n=[],o=[];let a,r,l=[];for(a=0,r=e.length;at.filter(e,s,n,i)))),t.itemSort&&(l=l.sort(((e,s)=>t.itemSort(e,s,i)))),u(l,(e=>{const i=Fa(t.callbacks,e);s.push(Ba(i,"labelColor",this,e)),n.push(Ba(i,"labelPointStyle",this,e)),o.push(Ba(i,"labelTextColor",this,e))})),this.labelColors=s,this.labelPointStyles=n,this.labelTextColors=o,this.dataPoints=l,l}update(t,e){const i=this.options.setContext(this.getContext()),s=this._active;let n,o=[];if(s.length){const t=Da[i.position].call(this,s,this._eventPosition);o=this._createItems(i),this.title=this.getTitle(o,i),this.beforeBody=this.getBeforeBody(o,i),this.body=this.getBody(o,i),this.afterBody=this.getAfterBody(o,i),this.footer=this.getFooter(o,i);const e=this._size=Ta(this,i),a=Object.assign({},t,e),r=Ea(this.chart,i,a),l=Ra(i,a,r,this.chart);this.xAlign=r.xAlign,this.yAlign=r.yAlign,n={opacity:1,x:l.x,y:l.y,width:e.width,height:e.height,caretX:t.x,caretY:t.y}}else 0!==this.opacity&&(n={opacity:0});this._tooltipItems=o,this.$context=void 0,n&&this._resolveAnimations().update(this,n),t&&i.external&&i.external.call(this,{chart:this.chart,tooltip:this,replay:e})}drawCaret(t,e,i,s){const n=this.getCaretPosition(t,i,s);e.lineTo(n.x1,n.y1),e.lineTo(n.x2,n.y2),e.lineTo(n.x3,n.y3)}getCaretPosition(t,e,i){const{xAlign:s,yAlign:n}=this,{caretSize:o,cornerRadius:a}=i,{topLeft:r,topRight:l,bottomLeft:h,bottomRight:c}=wi(a),{x:d,y:u}=t,{width:f,height:g}=e;let p,m,x,b,_,y;return"center"===n?(_=u+g/2,"left"===s?(p=d,m=p-o,b=_+o,y=_-o):(p=d+f,m=p+o,b=_-o,y=_+o),x=p):(m="left"===s?d+Math.max(r,h)+o:"right"===s?d+f-Math.max(l,c)-o:this.caretX,"top"===n?(b=u,_=b-o,p=m-o,x=m+o):(b=u+g,_=b+o,p=m+o,x=m-o),y=b),{x1:p,x2:m,x3:x,y1:b,y2:_,y3:y}}drawTitle(t,e,i){const s=this.title,n=s.length;let o,a,r;if(n){const l=Oi(i.rtl,this.x,this.width);for(t.x=Ia(this,i.titleAlign,i),e.textAlign=l.textAlign(i.titleAlign),e.textBaseline="middle",o=Si(i.titleFont),a=i.titleSpacing,e.fillStyle=i.titleColor,e.font=o.string,r=0;r0!==t))?(t.beginPath(),t.fillStyle=n.multiKeyBackground,He(t,{x:e,y:g,w:h,h:l,radius:r}),t.fill(),t.stroke(),t.fillStyle=a.backgroundColor,t.beginPath(),He(t,{x:i,y:g+1,w:h-2,h:l-2,radius:r}),t.fill()):(t.fillStyle=n.multiKeyBackground,t.fillRect(e,g,h,l),t.strokeRect(e,g,h,l),t.fillStyle=a.backgroundColor,t.fillRect(i,g+1,h-2,l-2))}t.fillStyle=this.labelTextColors[i]}drawBody(t,e,i){const{body:s}=this,{bodySpacing:n,bodyAlign:o,displayColors:a,boxHeight:r,boxWidth:l,boxPadding:h}=i,c=Si(i.bodyFont);let d=c.lineHeight,f=0;const g=Oi(i.rtl,this.x,this.width),p=function(i){e.fillText(i,g.x(t.x+f),t.y+d/2),t.y+=d+n},m=g.textAlign(o);let x,b,_,y,v,M,w;for(e.textAlign=o,e.textBaseline="middle",e.font=c.string,t.x=Ia(this,m,i),e.fillStyle=i.bodyColor,u(this.beforeBody,p),f=a&&"right"!==m?"center"===o?l/2+h:l+2+h:0,y=0,M=s.length;y0&&e.stroke()}_updateAnimationTarget(t){const e=this.chart,i=this.$animations,s=i&&i.x,n=i&&i.y;if(s||n){const i=Da[t.position].call(this,this._active,this._eventPosition);if(!i)return;const o=this._size=Ta(this,t),a=Object.assign({},i,this._size),r=Ea(e,t,a),l=Ra(t,a,r,e);s._to===l.x&&n._to===l.y||(this.xAlign=r.xAlign,this.yAlign=r.yAlign,this.width=o.width,this.height=o.height,this.caretX=i.x,this.caretY=i.y,this._resolveAnimations().update(this,l))}}_willRender(){return!!this.opacity}draw(t){const e=this.options.setContext(this.getContext());let i=this.opacity;if(!i)return;this._updateAnimationTarget(e);const s={width:this.width,height:this.height},n={x:this.x,y:this.y};i=Math.abs(i)<.001?0:i;const o=ki(e.padding),a=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;e.enabled&&a&&(t.save(),t.globalAlpha=i,this.drawBackground(n,t,s,e),Ai(t,e.textDirection),n.y+=o.top,this.drawTitle(n,t,e),this.drawBody(n,t,e),this.drawFooter(n,t,e),Ti(t,e.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,e){const i=this._active,s=t.map((({datasetIndex:t,index:e})=>{const i=this.chart.getDatasetMeta(t);if(!i)throw new Error("Cannot find a dataset at index "+t);return{datasetIndex:t,element:i.data[e],index:e}})),n=!f(i,s),o=this._positionChanged(s,e);(n||o)&&(this._active=s,this._eventPosition=e,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(t,e,i=!0){if(e&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;const s=this.options,n=this._active||[],o=this._getActiveElements(t,n,e,i),a=this._positionChanged(o,t),r=e||!f(o,n)||a;return r&&(this._active=o,(s.enabled||s.external)&&(this._eventPosition={x:t.x,y:t.y},this.update(!0,e))),r}_getActiveElements(t,e,i,s){const n=this.options;if("mouseout"===t.type)return[];if(!s)return e.filter((t=>this.chart.data.datasets[t.datasetIndex]&&void 0!==this.chart.getDatasetMeta(t.datasetIndex).controller.getParsed(t.index)));const o=this.chart.getElementsAtEventForMode(t,n.mode,n,i);return n.reverse&&o.reverse(),o}_positionChanged(t,e){const{caretX:i,caretY:s,options:n}=this,o=Da[n.position].call(this,t,e);return!1!==o&&(i!==o.x||s!==o.y)}}var Na={id:"tooltip",_element:Wa,positioners:Da,afterInit(t,e,i){i&&(t.tooltip=new Wa({chart:t,options:i}))},beforeUpdate(t,e,i){t.tooltip&&t.tooltip.initialize(i)},reset(t,e,i){t.tooltip&&t.tooltip.initialize(i)},afterDraw(t){const e=t.tooltip;if(e&&e._willRender()){const i={tooltip:e};if(!1===t.notifyPlugins("beforeTooltipDraw",{...i,cancelable:!0}))return;e.draw(t.ctx),t.notifyPlugins("afterTooltipDraw",i)}},afterEvent(t,e){if(t.tooltip){const i=e.replay;t.tooltip.handleEvent(e.event,i,e.inChartArea)&&(e.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(t,e)=>e.bodyFont.size,boxWidth:(t,e)=>e.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:Va},defaultRoutes:{bodyFont:"font",footerFont:"font",titleFont:"font"},descriptors:{_scriptable:t=>"filter"!==t&&"itemSort"!==t&&"external"!==t,_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]};return Tn.register(Un,$o,go,t),Tn.helpers={...Hi},Tn._adapters=In,Tn.Animation=As,Tn.Animations=Ts,Tn.animator=bt,Tn.controllers=nn.controllers.items,Tn.DatasetController=js,Tn.Element=$s,Tn.elements=go,Tn.Interaction=Ki,Tn.layouts=ls,Tn.platforms=Ds,Tn.Scale=tn,Tn.Ticks=ae,Object.assign(Tn,Un,$o,go,t,Ds),Tn.Chart=Tn,"undefined"!=typeof window&&(window.Chart=Tn),Tn})); +//# sourceMappingURL=chart.umd.min.js.map diff --git a/dap-gateway/src/main/resources/static/lib/tailwindcss.js b/dap-gateway/src/main/resources/static/lib/tailwindcss.js new file mode 100644 index 00000000..573c1659 --- /dev/null +++ b/dap-gateway/src/main/resources/static/lib/tailwindcss.js @@ -0,0 +1,83 @@ +(()=>{var qv=Object.create;var Hi=Object.defineProperty;var $v=Object.getOwnPropertyDescriptor;var Lv=Object.getOwnPropertyNames;var Mv=Object.getPrototypeOf,Nv=Object.prototype.hasOwnProperty;var df=r=>Hi(r,"__esModule",{value:!0});var hf=r=>{if(typeof require!="undefined")return require(r);throw new Error('Dynamic require of "'+r+'" is not supported')};var P=(r,e)=>()=>(r&&(e=r(r=0)),e);var x=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports),Ge=(r,e)=>{df(r);for(var t in e)Hi(r,t,{get:e[t],enumerable:!0})},Bv=(r,e,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Lv(e))!Nv.call(r,i)&&i!=="default"&&Hi(r,i,{get:()=>e[i],enumerable:!(t=$v(e,i))||t.enumerable});return r},pe=r=>Bv(df(Hi(r!=null?qv(Mv(r)):{},"default",r&&r.__esModule&&"default"in r?{get:()=>r.default,enumerable:!0}:{value:r,enumerable:!0})),r);var m,u=P(()=>{m={platform:"",env:{},versions:{node:"14.17.6"}}});var Fv,be,ft=P(()=>{u();Fv=0,be={readFileSync:r=>self[r]||"",statSync:()=>({mtimeMs:Fv++}),promises:{readFile:r=>Promise.resolve(self[r]||"")}}});var Fs=x((oP,gf)=>{u();"use strict";var mf=class{constructor(e={}){if(!(e.maxSize&&e.maxSize>0))throw new TypeError("`maxSize` must be a number greater than 0");if(typeof e.maxAge=="number"&&e.maxAge===0)throw new TypeError("`maxAge` must be a number greater than 0");this.maxSize=e.maxSize,this.maxAge=e.maxAge||1/0,this.onEviction=e.onEviction,this.cache=new Map,this.oldCache=new Map,this._size=0}_emitEvictions(e){if(typeof this.onEviction=="function")for(let[t,i]of e)this.onEviction(t,i.value)}_deleteIfExpired(e,t){return typeof t.expiry=="number"&&t.expiry<=Date.now()?(typeof this.onEviction=="function"&&this.onEviction(e,t.value),this.delete(e)):!1}_getOrDeleteIfExpired(e,t){if(this._deleteIfExpired(e,t)===!1)return t.value}_getItemValue(e,t){return t.expiry?this._getOrDeleteIfExpired(e,t):t.value}_peek(e,t){let i=t.get(e);return this._getItemValue(e,i)}_set(e,t){this.cache.set(e,t),this._size++,this._size>=this.maxSize&&(this._size=0,this._emitEvictions(this.oldCache),this.oldCache=this.cache,this.cache=new Map)}_moveToRecent(e,t){this.oldCache.delete(e),this._set(e,t)}*_entriesAscending(){for(let e of this.oldCache){let[t,i]=e;this.cache.has(t)||this._deleteIfExpired(t,i)===!1&&(yield e)}for(let e of this.cache){let[t,i]=e;this._deleteIfExpired(t,i)===!1&&(yield e)}}get(e){if(this.cache.has(e)){let t=this.cache.get(e);return this._getItemValue(e,t)}if(this.oldCache.has(e)){let t=this.oldCache.get(e);if(this._deleteIfExpired(e,t)===!1)return this._moveToRecent(e,t),t.value}}set(e,t,{maxAge:i=this.maxAge===1/0?void 0:Date.now()+this.maxAge}={}){this.cache.has(e)?this.cache.set(e,{value:t,maxAge:i}):this._set(e,{value:t,expiry:i})}has(e){return this.cache.has(e)?!this._deleteIfExpired(e,this.cache.get(e)):this.oldCache.has(e)?!this._deleteIfExpired(e,this.oldCache.get(e)):!1}peek(e){if(this.cache.has(e))return this._peek(e,this.cache);if(this.oldCache.has(e))return this._peek(e,this.oldCache)}delete(e){let t=this.cache.delete(e);return t&&this._size--,this.oldCache.delete(e)||t}clear(){this.cache.clear(),this.oldCache.clear(),this._size=0}resize(e){if(!(e&&e>0))throw new TypeError("`maxSize` must be a number greater than 0");let t=[...this._entriesAscending()],i=t.length-e;i<0?(this.cache=new Map(t),this.oldCache=new Map,this._size=t.length):(i>0&&this._emitEvictions(t.slice(0,i)),this.oldCache=new Map(t.slice(i)),this.cache=new Map,this._size=0),this.maxSize=e}*keys(){for(let[e]of this)yield e}*values(){for(let[,e]of this)yield e}*[Symbol.iterator](){for(let e of this.cache){let[t,i]=e;this._deleteIfExpired(t,i)===!1&&(yield[t,i.value])}for(let e of this.oldCache){let[t,i]=e;this.cache.has(t)||this._deleteIfExpired(t,i)===!1&&(yield[t,i.value])}}*entriesDescending(){let e=[...this.cache];for(let t=e.length-1;t>=0;--t){let i=e[t],[n,s]=i;this._deleteIfExpired(n,s)===!1&&(yield[n,s.value])}e=[...this.oldCache];for(let t=e.length-1;t>=0;--t){let i=e[t],[n,s]=i;this.cache.has(n)||this._deleteIfExpired(n,s)===!1&&(yield[n,s.value])}}*entriesAscending(){for(let[e,t]of this._entriesAscending())yield[e,t.value]}get size(){if(!this._size)return this.oldCache.size;let e=0;for(let t of this.oldCache.keys())this.cache.has(t)||e++;return Math.min(this._size+e,this.maxSize)}};gf.exports=mf});var yf,bf=P(()=>{u();yf=r=>r&&r._hash});function Wi(r){return yf(r,{ignoreUnknown:!0})}var wf=P(()=>{u();bf()});function xt(r){if(r=`${r}`,r==="0")return"0";if(/^[+-]?(\d+|\d*\.\d+)(e[+-]?\d+)?(%|\w+)?$/.test(r))return r.replace(/^[+-]?/,t=>t==="-"?"":"-");let e=["var","calc","min","max","clamp"];for(let t of e)if(r.includes(`${t}(`))return`calc(${r} * -1)`}var Gi=P(()=>{u()});var vf,xf=P(()=>{u();vf=["preflight","container","accessibility","pointerEvents","visibility","position","inset","isolation","zIndex","order","gridColumn","gridColumnStart","gridColumnEnd","gridRow","gridRowStart","gridRowEnd","float","clear","margin","boxSizing","lineClamp","display","aspectRatio","size","height","maxHeight","minHeight","width","minWidth","maxWidth","flex","flexShrink","flexGrow","flexBasis","tableLayout","captionSide","borderCollapse","borderSpacing","transformOrigin","translate","rotate","skew","scale","transform","animation","cursor","touchAction","userSelect","resize","scrollSnapType","scrollSnapAlign","scrollSnapStop","scrollMargin","scrollPadding","listStylePosition","listStyleType","listStyleImage","appearance","columns","breakBefore","breakInside","breakAfter","gridAutoColumns","gridAutoFlow","gridAutoRows","gridTemplateColumns","gridTemplateRows","flexDirection","flexWrap","placeContent","placeItems","alignContent","alignItems","justifyContent","justifyItems","gap","space","divideWidth","divideStyle","divideColor","divideOpacity","placeSelf","alignSelf","justifySelf","overflow","overscrollBehavior","scrollBehavior","textOverflow","hyphens","whitespace","textWrap","wordBreak","borderRadius","borderWidth","borderStyle","borderColor","borderOpacity","backgroundColor","backgroundOpacity","backgroundImage","gradientColorStops","boxDecorationBreak","backgroundSize","backgroundAttachment","backgroundClip","backgroundPosition","backgroundRepeat","backgroundOrigin","fill","stroke","strokeWidth","objectFit","objectPosition","padding","textAlign","textIndent","verticalAlign","fontFamily","fontSize","fontWeight","textTransform","fontStyle","fontVariantNumeric","lineHeight","letterSpacing","textColor","textOpacity","textDecoration","textDecorationColor","textDecorationStyle","textDecorationThickness","textUnderlineOffset","fontSmoothing","placeholderColor","placeholderOpacity","caretColor","accentColor","opacity","backgroundBlendMode","mixBlendMode","boxShadow","boxShadowColor","outlineStyle","outlineWidth","outlineOffset","outlineColor","ringWidth","ringColor","ringOpacity","ringOffsetWidth","ringOffsetColor","blur","brightness","contrast","dropShadow","grayscale","hueRotate","invert","saturate","sepia","filter","backdropBlur","backdropBrightness","backdropContrast","backdropGrayscale","backdropHueRotate","backdropInvert","backdropOpacity","backdropSaturate","backdropSepia","backdropFilter","transitionProperty","transitionDelay","transitionDuration","transitionTimingFunction","willChange","contain","content","forcedColorAdjust"]});function kf(r,e){return r===void 0?e:Array.isArray(r)?r:[...new Set(e.filter(i=>r!==!1&&r[i]!==!1).concat(Object.keys(r).filter(i=>r[i]!==!1)))]}var Sf=P(()=>{u()});var Af={};Ge(Af,{default:()=>Qe});var Qe,Qi=P(()=>{u();Qe=new Proxy({},{get:()=>String})});function js(r,e,t){typeof m!="undefined"&&m.env.JEST_WORKER_ID||t&&Cf.has(t)||(t&&Cf.add(t),console.warn(""),e.forEach(i=>console.warn(r,"-",i)))}function zs(r){return Qe.dim(r)}var Cf,G,Be=P(()=>{u();Qi();Cf=new Set;G={info(r,e){js(Qe.bold(Qe.cyan("info")),...Array.isArray(r)?[r]:[e,r])},warn(r,e){["content-problems"].includes(r)||js(Qe.bold(Qe.yellow("warn")),...Array.isArray(r)?[r]:[e,r])},risk(r,e){js(Qe.bold(Qe.magenta("risk")),...Array.isArray(r)?[r]:[e,r])}}});var _f={};Ge(_f,{default:()=>Us});function qr({version:r,from:e,to:t}){G.warn(`${e}-color-renamed`,[`As of Tailwind CSS ${r}, \`${e}\` has been renamed to \`${t}\`.`,"Update your configuration file to silence this warning."])}var Us,Vs=P(()=>{u();Be();Us={inherit:"inherit",current:"currentColor",transparent:"transparent",black:"#000",white:"#fff",slate:{50:"#f8fafc",100:"#f1f5f9",200:"#e2e8f0",300:"#cbd5e1",400:"#94a3b8",500:"#64748b",600:"#475569",700:"#334155",800:"#1e293b",900:"#0f172a",950:"#020617"},gray:{50:"#f9fafb",100:"#f3f4f6",200:"#e5e7eb",300:"#d1d5db",400:"#9ca3af",500:"#6b7280",600:"#4b5563",700:"#374151",800:"#1f2937",900:"#111827",950:"#030712"},zinc:{50:"#fafafa",100:"#f4f4f5",200:"#e4e4e7",300:"#d4d4d8",400:"#a1a1aa",500:"#71717a",600:"#52525b",700:"#3f3f46",800:"#27272a",900:"#18181b",950:"#09090b"},neutral:{50:"#fafafa",100:"#f5f5f5",200:"#e5e5e5",300:"#d4d4d4",400:"#a3a3a3",500:"#737373",600:"#525252",700:"#404040",800:"#262626",900:"#171717",950:"#0a0a0a"},stone:{50:"#fafaf9",100:"#f5f5f4",200:"#e7e5e4",300:"#d6d3d1",400:"#a8a29e",500:"#78716c",600:"#57534e",700:"#44403c",800:"#292524",900:"#1c1917",950:"#0c0a09"},red:{50:"#fef2f2",100:"#fee2e2",200:"#fecaca",300:"#fca5a5",400:"#f87171",500:"#ef4444",600:"#dc2626",700:"#b91c1c",800:"#991b1b",900:"#7f1d1d",950:"#450a0a"},orange:{50:"#fff7ed",100:"#ffedd5",200:"#fed7aa",300:"#fdba74",400:"#fb923c",500:"#f97316",600:"#ea580c",700:"#c2410c",800:"#9a3412",900:"#7c2d12",950:"#431407"},amber:{50:"#fffbeb",100:"#fef3c7",200:"#fde68a",300:"#fcd34d",400:"#fbbf24",500:"#f59e0b",600:"#d97706",700:"#b45309",800:"#92400e",900:"#78350f",950:"#451a03"},yellow:{50:"#fefce8",100:"#fef9c3",200:"#fef08a",300:"#fde047",400:"#facc15",500:"#eab308",600:"#ca8a04",700:"#a16207",800:"#854d0e",900:"#713f12",950:"#422006"},lime:{50:"#f7fee7",100:"#ecfccb",200:"#d9f99d",300:"#bef264",400:"#a3e635",500:"#84cc16",600:"#65a30d",700:"#4d7c0f",800:"#3f6212",900:"#365314",950:"#1a2e05"},green:{50:"#f0fdf4",100:"#dcfce7",200:"#bbf7d0",300:"#86efac",400:"#4ade80",500:"#22c55e",600:"#16a34a",700:"#15803d",800:"#166534",900:"#14532d",950:"#052e16"},emerald:{50:"#ecfdf5",100:"#d1fae5",200:"#a7f3d0",300:"#6ee7b7",400:"#34d399",500:"#10b981",600:"#059669",700:"#047857",800:"#065f46",900:"#064e3b",950:"#022c22"},teal:{50:"#f0fdfa",100:"#ccfbf1",200:"#99f6e4",300:"#5eead4",400:"#2dd4bf",500:"#14b8a6",600:"#0d9488",700:"#0f766e",800:"#115e59",900:"#134e4a",950:"#042f2e"},cyan:{50:"#ecfeff",100:"#cffafe",200:"#a5f3fc",300:"#67e8f9",400:"#22d3ee",500:"#06b6d4",600:"#0891b2",700:"#0e7490",800:"#155e75",900:"#164e63",950:"#083344"},sky:{50:"#f0f9ff",100:"#e0f2fe",200:"#bae6fd",300:"#7dd3fc",400:"#38bdf8",500:"#0ea5e9",600:"#0284c7",700:"#0369a1",800:"#075985",900:"#0c4a6e",950:"#082f49"},blue:{50:"#eff6ff",100:"#dbeafe",200:"#bfdbfe",300:"#93c5fd",400:"#60a5fa",500:"#3b82f6",600:"#2563eb",700:"#1d4ed8",800:"#1e40af",900:"#1e3a8a",950:"#172554"},indigo:{50:"#eef2ff",100:"#e0e7ff",200:"#c7d2fe",300:"#a5b4fc",400:"#818cf8",500:"#6366f1",600:"#4f46e5",700:"#4338ca",800:"#3730a3",900:"#312e81",950:"#1e1b4b"},violet:{50:"#f5f3ff",100:"#ede9fe",200:"#ddd6fe",300:"#c4b5fd",400:"#a78bfa",500:"#8b5cf6",600:"#7c3aed",700:"#6d28d9",800:"#5b21b6",900:"#4c1d95",950:"#2e1065"},purple:{50:"#faf5ff",100:"#f3e8ff",200:"#e9d5ff",300:"#d8b4fe",400:"#c084fc",500:"#a855f7",600:"#9333ea",700:"#7e22ce",800:"#6b21a8",900:"#581c87",950:"#3b0764"},fuchsia:{50:"#fdf4ff",100:"#fae8ff",200:"#f5d0fe",300:"#f0abfc",400:"#e879f9",500:"#d946ef",600:"#c026d3",700:"#a21caf",800:"#86198f",900:"#701a75",950:"#4a044e"},pink:{50:"#fdf2f8",100:"#fce7f3",200:"#fbcfe8",300:"#f9a8d4",400:"#f472b6",500:"#ec4899",600:"#db2777",700:"#be185d",800:"#9d174d",900:"#831843",950:"#500724"},rose:{50:"#fff1f2",100:"#ffe4e6",200:"#fecdd3",300:"#fda4af",400:"#fb7185",500:"#f43f5e",600:"#e11d48",700:"#be123c",800:"#9f1239",900:"#881337",950:"#4c0519"},get lightBlue(){return qr({version:"v2.2",from:"lightBlue",to:"sky"}),this.sky},get warmGray(){return qr({version:"v3.0",from:"warmGray",to:"stone"}),this.stone},get trueGray(){return qr({version:"v3.0",from:"trueGray",to:"neutral"}),this.neutral},get coolGray(){return qr({version:"v3.0",from:"coolGray",to:"gray"}),this.gray},get blueGray(){return qr({version:"v3.0",from:"blueGray",to:"slate"}),this.slate}}});function Hs(r,...e){for(let t of e){for(let i in t)r?.hasOwnProperty?.(i)||(r[i]=t[i]);for(let i of Object.getOwnPropertySymbols(t))r?.hasOwnProperty?.(i)||(r[i]=t[i])}return r}var Ef=P(()=>{u()});function kt(r){if(Array.isArray(r))return r;let e=r.split("[").length-1,t=r.split("]").length-1;if(e!==t)throw new Error(`Path is invalid. Has unbalanced brackets: ${r}`);return r.split(/\.(?![^\[]*\])|[\[\]]/g).filter(Boolean)}var Yi=P(()=>{u()});function we(r,e){return Ki.future.includes(e)?r.future==="all"||(r?.future?.[e]??Of[e]??!1):Ki.experimental.includes(e)?r.experimental==="all"||(r?.experimental?.[e]??Of[e]??!1):!1}function Tf(r){return r.experimental==="all"?Ki.experimental:Object.keys(r?.experimental??{}).filter(e=>Ki.experimental.includes(e)&&r.experimental[e])}function Rf(r){if(m.env.JEST_WORKER_ID===void 0&&Tf(r).length>0){let e=Tf(r).map(t=>Qe.yellow(t)).join(", ");G.warn("experimental-flags-enabled",[`You have enabled experimental features: ${e}`,"Experimental features in Tailwind CSS are not covered by semver, may introduce breaking changes, and can change at any time."])}}var Of,Ki,ct=P(()=>{u();Qi();Be();Of={optimizeUniversalDefaults:!1,generalizedModifiers:!0,disableColorOpacityUtilitiesByDefault:!1,relativeContentPathsByDefault:!1},Ki={future:["hoverOnlyWhenSupported","respectDefaultRingColorOpacity","disableColorOpacityUtilitiesByDefault","relativeContentPathsByDefault"],experimental:["optimizeUniversalDefaults","generalizedModifiers"]}});function Pf(r){(()=>{if(r.purge||!r.content||!Array.isArray(r.content)&&!(typeof r.content=="object"&&r.content!==null))return!1;if(Array.isArray(r.content))return r.content.every(t=>typeof t=="string"?!0:!(typeof t?.raw!="string"||t?.extension&&typeof t?.extension!="string"));if(typeof r.content=="object"&&r.content!==null){if(Object.keys(r.content).some(t=>!["files","relative","extract","transform"].includes(t)))return!1;if(Array.isArray(r.content.files)){if(!r.content.files.every(t=>typeof t=="string"?!0:!(typeof t?.raw!="string"||t?.extension&&typeof t?.extension!="string")))return!1;if(typeof r.content.extract=="object"){for(let t of Object.values(r.content.extract))if(typeof t!="function")return!1}else if(!(r.content.extract===void 0||typeof r.content.extract=="function"))return!1;if(typeof r.content.transform=="object"){for(let t of Object.values(r.content.transform))if(typeof t!="function")return!1}else if(!(r.content.transform===void 0||typeof r.content.transform=="function"))return!1;if(typeof r.content.relative!="boolean"&&typeof r.content.relative!="undefined")return!1}return!0}return!1})()||G.warn("purge-deprecation",["The `purge`/`content` options have changed in Tailwind CSS v3.0.","Update your configuration file to eliminate this warning.","https://tailwindcss.com/docs/upgrade-guide#configure-content-sources"]),r.safelist=(()=>{let{content:t,purge:i,safelist:n}=r;return Array.isArray(n)?n:Array.isArray(t?.safelist)?t.safelist:Array.isArray(i?.safelist)?i.safelist:Array.isArray(i?.options?.safelist)?i.options.safelist:[]})(),r.blocklist=(()=>{let{blocklist:t}=r;if(Array.isArray(t)){if(t.every(i=>typeof i=="string"))return t;G.warn("blocklist-invalid",["The `blocklist` option must be an array of strings.","https://tailwindcss.com/docs/content-configuration#discarding-classes"])}return[]})(),typeof r.prefix=="function"?(G.warn("prefix-function",["As of Tailwind CSS v3.0, `prefix` cannot be a function.","Update `prefix` in your configuration to be a string to eliminate this warning.","https://tailwindcss.com/docs/upgrade-guide#prefix-cannot-be-a-function"]),r.prefix=""):r.prefix=r.prefix??"",r.content={relative:(()=>{let{content:t}=r;return t?.relative?t.relative:we(r,"relativeContentPathsByDefault")})(),files:(()=>{let{content:t,purge:i}=r;return Array.isArray(i)?i:Array.isArray(i?.content)?i.content:Array.isArray(t)?t:Array.isArray(t?.content)?t.content:Array.isArray(t?.files)?t.files:[]})(),extract:(()=>{let t=(()=>r.purge?.extract?r.purge.extract:r.content?.extract?r.content.extract:r.purge?.extract?.DEFAULT?r.purge.extract.DEFAULT:r.content?.extract?.DEFAULT?r.content.extract.DEFAULT:r.purge?.options?.extractors?r.purge.options.extractors:r.content?.options?.extractors?r.content.options.extractors:{})(),i={},n=(()=>{if(r.purge?.options?.defaultExtractor)return r.purge.options.defaultExtractor;if(r.content?.options?.defaultExtractor)return r.content.options.defaultExtractor})();if(n!==void 0&&(i.DEFAULT=n),typeof t=="function")i.DEFAULT=t;else if(Array.isArray(t))for(let{extensions:s,extractor:a}of t??[])for(let o of s)i[o]=a;else typeof t=="object"&&t!==null&&Object.assign(i,t);return i})(),transform:(()=>{let t=(()=>r.purge?.transform?r.purge.transform:r.content?.transform?r.content.transform:r.purge?.transform?.DEFAULT?r.purge.transform.DEFAULT:r.content?.transform?.DEFAULT?r.content.transform.DEFAULT:{})(),i={};return typeof t=="function"?i.DEFAULT=t:typeof t=="object"&&t!==null&&Object.assign(i,t),i})()};for(let t of r.content.files)if(typeof t=="string"&&/{([^,]*?)}/g.test(t)){G.warn("invalid-glob-braces",[`The glob pattern ${zs(t)} in your Tailwind CSS configuration is invalid.`,`Update it to ${zs(t.replace(/{([^,]*?)}/g,"$1"))} to silence this warning.`]);break}return r}var If=P(()=>{u();ct();Be()});function ke(r){if(Object.prototype.toString.call(r)!=="[object Object]")return!1;let e=Object.getPrototypeOf(r);return e===null||Object.getPrototypeOf(e)===null}var Kt=P(()=>{u()});function St(r){return Array.isArray(r)?r.map(e=>St(e)):typeof r=="object"&&r!==null?Object.fromEntries(Object.entries(r).map(([e,t])=>[e,St(t)])):r}var Xi=P(()=>{u()});function jt(r){return r.replace(/\\,/g,"\\2c ")}var Zi=P(()=>{u()});var Ws,Df=P(()=>{u();Ws={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}});function $r(r,{loose:e=!1}={}){if(typeof r!="string")return null;if(r=r.trim(),r==="transparent")return{mode:"rgb",color:["0","0","0"],alpha:"0"};if(r in Ws)return{mode:"rgb",color:Ws[r].map(s=>s.toString())};let t=r.replace(zv,(s,a,o,l,c)=>["#",a,a,o,o,l,l,c?c+c:""].join("")).match(jv);if(t!==null)return{mode:"rgb",color:[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)].map(s=>s.toString()),alpha:t[4]?(parseInt(t[4],16)/255).toString():void 0};let i=r.match(Uv)??r.match(Vv);if(i===null)return null;let n=[i[2],i[3],i[4]].filter(Boolean).map(s=>s.toString());return n.length===2&&n[0].startsWith("var(")?{mode:i[1],color:[n[0]],alpha:n[1]}:!e&&n.length!==3||n.length<3&&!n.some(s=>/^var\(.*?\)$/.test(s))?null:{mode:i[1],color:n,alpha:i[5]?.toString?.()}}function Gs({mode:r,color:e,alpha:t}){let i=t!==void 0;return r==="rgba"||r==="hsla"?`${r}(${e.join(", ")}${i?`, ${t}`:""})`:`${r}(${e.join(" ")}${i?` / ${t}`:""})`}var jv,zv,At,Ji,qf,Ct,Uv,Vv,Qs=P(()=>{u();Df();jv=/^#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i,zv=/^#([a-f\d])([a-f\d])([a-f\d])([a-f\d])?$/i,At=/(?:\d+|\d*\.\d+)%?/,Ji=/(?:\s*,\s*|\s+)/,qf=/\s*[,/]\s*/,Ct=/var\(--(?:[^ )]*?)(?:,(?:[^ )]*?|var\(--[^ )]*?\)))?\)/,Uv=new RegExp(`^(rgba?)\\(\\s*(${At.source}|${Ct.source})(?:${Ji.source}(${At.source}|${Ct.source}))?(?:${Ji.source}(${At.source}|${Ct.source}))?(?:${qf.source}(${At.source}|${Ct.source}))?\\s*\\)$`),Vv=new RegExp(`^(hsla?)\\(\\s*((?:${At.source})(?:deg|rad|grad|turn)?|${Ct.source})(?:${Ji.source}(${At.source}|${Ct.source}))?(?:${Ji.source}(${At.source}|${Ct.source}))?(?:${qf.source}(${At.source}|${Ct.source}))?\\s*\\)$`)});function Je(r,e,t){if(typeof r=="function")return r({opacityValue:e});let i=$r(r,{loose:!0});return i===null?t:Gs({...i,alpha:e})}function Ae({color:r,property:e,variable:t}){let i=[].concat(e);if(typeof r=="function")return{[t]:"1",...Object.fromEntries(i.map(s=>[s,r({opacityVariable:t,opacityValue:`var(${t}, 1)`})]))};let n=$r(r);return n===null?Object.fromEntries(i.map(s=>[s,r])):n.alpha!==void 0?Object.fromEntries(i.map(s=>[s,r])):{[t]:"1",...Object.fromEntries(i.map(s=>[s,Gs({...n,alpha:`var(${t}, 1)`})]))}}var Lr=P(()=>{u();Qs()});function ve(r,e){let t=[],i=[],n=0,s=!1;for(let a=0;a{u()});function en(r){return ve(r,",").map(t=>{let i=t.trim(),n={raw:i},s=i.split(Wv),a=new Set;for(let o of s)$f.lastIndex=0,!a.has("KEYWORD")&&Hv.has(o)?(n.keyword=o,a.add("KEYWORD")):$f.test(o)?a.has("X")?a.has("Y")?a.has("BLUR")?a.has("SPREAD")||(n.spread=o,a.add("SPREAD")):(n.blur=o,a.add("BLUR")):(n.y=o,a.add("Y")):(n.x=o,a.add("X")):n.color?(n.unknown||(n.unknown=[]),n.unknown.push(o)):n.color=o;return n.valid=n.x!==void 0&&n.y!==void 0,n})}function Lf(r){return r.map(e=>e.valid?[e.keyword,e.x,e.y,e.blur,e.spread,e.color].filter(Boolean).join(" "):e.raw).join(", ")}var Hv,Wv,$f,Ys=P(()=>{u();zt();Hv=new Set(["inset","inherit","initial","revert","unset"]),Wv=/\ +(?![^(]*\))/g,$f=/^-?(\d+|\.\d+)(.*?)$/g});function Ks(r){return Gv.some(e=>new RegExp(`^${e}\\(.*\\)`).test(r))}function K(r,e=null,t=!0){let i=e&&Qv.has(e.property);return r.startsWith("--")&&!i?`var(${r})`:r.includes("url(")?r.split(/(url\(.*?\))/g).filter(Boolean).map(n=>/^url\(.*?\)$/.test(n)?n:K(n,e,!1)).join(""):(r=r.replace(/([^\\])_+/g,(n,s)=>s+" ".repeat(n.length-1)).replace(/^_/g," ").replace(/\\_/g,"_"),t&&(r=r.trim()),r=Yv(r),r)}function Ye(r){return r.includes("=")&&(r=r.replace(/(=.*)/g,(e,t)=>{if(t[1]==="'"||t[1]==='"')return t;if(t.length>2){let i=t[t.length-1];if(t[t.length-2]===" "&&(i==="i"||i==="I"||i==="s"||i==="S"))return`="${t.slice(1,-2)}" ${t[t.length-1]}`}return`="${t.slice(1)}"`})),r}function Yv(r){let e=["theme"],t=["min-content","max-content","fit-content","safe-area-inset-top","safe-area-inset-right","safe-area-inset-bottom","safe-area-inset-left","titlebar-area-x","titlebar-area-y","titlebar-area-width","titlebar-area-height","keyboard-inset-top","keyboard-inset-right","keyboard-inset-bottom","keyboard-inset-left","keyboard-inset-width","keyboard-inset-height","radial-gradient","linear-gradient","conic-gradient","repeating-radial-gradient","repeating-linear-gradient","repeating-conic-gradient","anchor-size"];return r.replace(/(calc|min|max|clamp)\(.+\)/g,i=>{let n="";function s(){let a=n.trimEnd();return a[a.length-1]}for(let a=0;ai[a+p]===d)},l=function(f){let d=1/0;for(let h of f){let b=i.indexOf(h,a);b!==-1&&bo(f))){let f=t.find(d=>o(d));n+=f,a+=f.length-1}else e.some(f=>o(f))?n+=l([")"]):o("[")?n+=l(["]"]):["+","-","*","/"].includes(c)&&!["(","+","-","*","/",","].includes(s())?n+=` ${c} `:n+=c}return n.replace(/\s+/g," ")})}function Xs(r){return r.startsWith("url(")}function Zs(r){return!isNaN(Number(r))||Ks(r)}function Mr(r){return r.endsWith("%")&&Zs(r.slice(0,-1))||Ks(r)}function Nr(r){return r==="0"||new RegExp(`^[+-]?[0-9]*.?[0-9]+(?:[eE][+-]?[0-9]+)?${Xv}$`).test(r)||Ks(r)}function Mf(r){return Zv.has(r)}function Nf(r){let e=en(K(r));for(let t of e)if(!t.valid)return!1;return!0}function Bf(r){let e=0;return ve(r,"_").every(i=>(i=K(i),i.startsWith("var(")?!0:$r(i,{loose:!0})!==null?(e++,!0):!1))?e>0:!1}function Ff(r){let e=0;return ve(r,",").every(i=>(i=K(i),i.startsWith("var(")?!0:Xs(i)||ex(i)||["element(","image(","cross-fade(","image-set("].some(n=>i.startsWith(n))?(e++,!0):!1))?e>0:!1}function ex(r){r=K(r);for(let e of Jv)if(r.startsWith(`${e}(`))return!0;return!1}function jf(r){let e=0;return ve(r,"_").every(i=>(i=K(i),i.startsWith("var(")?!0:tx.has(i)||Nr(i)||Mr(i)?(e++,!0):!1))?e>0:!1}function zf(r){let e=0;return ve(r,",").every(i=>(i=K(i),i.startsWith("var(")?!0:i.includes(" ")&&!/(['"])([^"']+)\1/g.test(i)||/^\d/g.test(i)?!1:(e++,!0)))?e>0:!1}function Uf(r){return rx.has(r)}function Vf(r){return ix.has(r)}function Hf(r){return nx.has(r)}var Gv,Qv,Kv,Xv,Zv,Jv,tx,rx,ix,nx,Br=P(()=>{u();Qs();Ys();zt();Gv=["min","max","clamp","calc"];Qv=new Set(["scroll-timeline-name","timeline-scope","view-timeline-name","font-palette","anchor-name","anchor-scope","position-anchor","position-try-options","scroll-timeline","animation-timeline","view-timeline","position-try"]);Kv=["cm","mm","Q","in","pc","pt","px","em","ex","ch","rem","lh","rlh","vw","vh","vmin","vmax","vb","vi","svw","svh","lvw","lvh","dvw","dvh","cqw","cqh","cqi","cqb","cqmin","cqmax"],Xv=`(?:${Kv.join("|")})`;Zv=new Set(["thin","medium","thick"]);Jv=new Set(["conic-gradient","linear-gradient","radial-gradient","repeating-conic-gradient","repeating-linear-gradient","repeating-radial-gradient"]);tx=new Set(["center","top","right","bottom","left"]);rx=new Set(["serif","sans-serif","monospace","cursive","fantasy","system-ui","ui-serif","ui-sans-serif","ui-monospace","ui-rounded","math","emoji","fangsong"]);ix=new Set(["xx-small","x-small","small","medium","large","x-large","xx-large","xxx-large"]);nx=new Set(["larger","smaller"])});function Wf(r){let e=["cover","contain"];return ve(r,",").every(t=>{let i=ve(t,"_").filter(Boolean);return i.length===1&&e.includes(i[0])?!0:i.length!==1&&i.length!==2?!1:i.every(n=>Nr(n)||Mr(n)||n==="auto")})}var Gf=P(()=>{u();Br();zt()});function Qf(r,e){r.walkClasses(t=>{t.value=e(t.value),t.raws&&t.raws.value&&(t.raws.value=jt(t.raws.value))})}function Yf(r,e){if(!_t(r))return;let t=r.slice(1,-1);if(!!e(t))return K(t)}function sx(r,e={},t){let i=e[r];if(i!==void 0)return xt(i);if(_t(r)){let n=Yf(r,t);return n===void 0?void 0:xt(n)}}function tn(r,e={},{validate:t=()=>!0}={}){let i=e.values?.[r];return i!==void 0?i:e.supportsNegativeValues&&r.startsWith("-")?sx(r.slice(1),e.values,t):Yf(r,t)}function _t(r){return r.startsWith("[")&&r.endsWith("]")}function Kf(r){let e=r.lastIndexOf("/"),t=r.lastIndexOf("[",e),i=r.indexOf("]",e);return r[e-1]==="]"||r[e+1]==="["||t!==-1&&i!==-1&&t")){let e=r;return({opacityValue:t=1})=>e.replace(//g,t)}return r}function Xf(r){return K(r.slice(1,-1))}function ax(r,e={},{tailwindConfig:t={}}={}){if(e.values?.[r]!==void 0)return Xt(e.values?.[r]);let[i,n]=Kf(r);if(n!==void 0){let s=e.values?.[i]??(_t(i)?i.slice(1,-1):void 0);return s===void 0?void 0:(s=Xt(s),_t(n)?Je(s,Xf(n)):t.theme?.opacity?.[n]===void 0?void 0:Je(s,t.theme.opacity[n]))}return tn(r,e,{validate:Bf})}function ox(r,e={}){return e.values?.[r]}function qe(r){return(e,t)=>tn(e,t,{validate:r})}function lx(r,e){let t=r.indexOf(e);return t===-1?[void 0,r]:[r.slice(0,t),r.slice(t+1)]}function ea(r,e,t,i){if(t.values&&e in t.values)for(let{type:s}of r??[]){let a=Js[s](e,t,{tailwindConfig:i});if(a!==void 0)return[a,s,null]}if(_t(e)){let s=e.slice(1,-1),[a,o]=lx(s,":");if(!/^[\w-_]+$/g.test(a))o=s;else if(a!==void 0&&!Zf.includes(a))return[];if(o.length>0&&Zf.includes(a))return[tn(`[${o}]`,t),a,null]}let n=ta(r,e,t,i);for(let s of n)return s;return[]}function*ta(r,e,t,i){let n=we(i,"generalizedModifiers"),[s,a]=Kf(e);if(n&&t.modifiers!=null&&(t.modifiers==="any"||typeof t.modifiers=="object"&&(a&&_t(a)||a in t.modifiers))||(s=e,a=void 0),a!==void 0&&s===""&&(s="DEFAULT"),a!==void 0&&typeof t.modifiers=="object"){let l=t.modifiers?.[a]??null;l!==null?a=l:_t(a)&&(a=Xf(a))}for(let{type:l}of r??[]){let c=Js[l](s,t,{tailwindConfig:i});c!==void 0&&(yield[c,l,a??null])}}var Js,Zf,Fr=P(()=>{u();Zi();Lr();Br();Gi();Gf();ct();Js={any:tn,color:ax,url:qe(Xs),image:qe(Ff),length:qe(Nr),percentage:qe(Mr),position:qe(jf),lookup:ox,"generic-name":qe(Uf),"family-name":qe(zf),number:qe(Zs),"line-width":qe(Mf),"absolute-size":qe(Vf),"relative-size":qe(Hf),shadow:qe(Nf),size:qe(Wf)},Zf=Object.keys(Js)});function X(r){return typeof r=="function"?r({}):r}var ra=P(()=>{u()});function Zt(r){return typeof r=="function"}function jr(r,...e){let t=e.pop();for(let i of e)for(let n in i){let s=t(r[n],i[n]);s===void 0?ke(r[n])&&ke(i[n])?r[n]=jr({},r[n],i[n],t):r[n]=i[n]:r[n]=s}return r}function ux(r,...e){return Zt(r)?r(...e):r}function fx(r){return r.reduce((e,{extend:t})=>jr(e,t,(i,n)=>i===void 0?[n]:Array.isArray(i)?[n,...i]:[n,i]),{})}function cx(r){return{...r.reduce((e,t)=>Hs(e,t),{}),extend:fx(r)}}function Jf(r,e){if(Array.isArray(r)&&ke(r[0]))return r.concat(e);if(Array.isArray(e)&&ke(e[0])&&ke(r))return[r,...e];if(Array.isArray(e))return e}function px({extend:r,...e}){return jr(e,r,(t,i)=>!Zt(t)&&!i.some(Zt)?jr({},t,...i,Jf):(n,s)=>jr({},...[t,...i].map(a=>ux(a,n,s)),Jf))}function*dx(r){let e=kt(r);if(e.length===0||(yield e,Array.isArray(r)))return;let t=/^(.*?)\s*\/\s*([^/]+)$/,i=r.match(t);if(i!==null){let[,n,s]=i,a=kt(n);a.alpha=s,yield a}}function hx(r){let e=(t,i)=>{for(let n of dx(t)){let s=0,a=r;for(;a!=null&&s(t[i]=Zt(r[i])?r[i](e,ia):r[i],t),{})}function ec(r){let e=[];return r.forEach(t=>{e=[...e,t];let i=t?.plugins??[];i.length!==0&&i.forEach(n=>{n.__isOptionsFunction&&(n=n()),e=[...e,...ec([n?.config??{}])]})}),e}function mx(r){return[...r].reduceRight((t,i)=>Zt(i)?i({corePlugins:t}):kf(i,t),vf)}function gx(r){return[...r].reduceRight((t,i)=>[...t,...i],[])}function na(r){let e=[...ec(r),{prefix:"",important:!1,separator:":"}];return Pf(Hs({theme:hx(px(cx(e.map(t=>t?.theme??{})))),corePlugins:mx(e.map(t=>t.corePlugins)),plugins:gx(r.map(t=>t?.plugins??[]))},...e))}var ia,tc=P(()=>{u();Gi();xf();Sf();Vs();Ef();Yi();If();Kt();Xi();Fr();Lr();ra();ia={colors:Us,negative(r){return Object.keys(r).filter(e=>r[e]!=="0").reduce((e,t)=>{let i=xt(r[t]);return i!==void 0&&(e[`-${t}`]=i),e},{})},breakpoints(r){return Object.keys(r).filter(e=>typeof r[e]=="string").reduce((e,t)=>({...e,[`screen-${t}`]:r[t]}),{})}}});var rn=x((f3,rc)=>{u();rc.exports={content:[],presets:[],darkMode:"media",theme:{accentColor:({theme:r})=>({...r("colors"),auto:"auto"}),animation:{none:"none",spin:"spin 1s linear infinite",ping:"ping 1s cubic-bezier(0, 0, 0.2, 1) infinite",pulse:"pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite",bounce:"bounce 1s infinite"},aria:{busy:'busy="true"',checked:'checked="true"',disabled:'disabled="true"',expanded:'expanded="true"',hidden:'hidden="true"',pressed:'pressed="true"',readonly:'readonly="true"',required:'required="true"',selected:'selected="true"'},aspectRatio:{auto:"auto",square:"1 / 1",video:"16 / 9"},backdropBlur:({theme:r})=>r("blur"),backdropBrightness:({theme:r})=>r("brightness"),backdropContrast:({theme:r})=>r("contrast"),backdropGrayscale:({theme:r})=>r("grayscale"),backdropHueRotate:({theme:r})=>r("hueRotate"),backdropInvert:({theme:r})=>r("invert"),backdropOpacity:({theme:r})=>r("opacity"),backdropSaturate:({theme:r})=>r("saturate"),backdropSepia:({theme:r})=>r("sepia"),backgroundColor:({theme:r})=>r("colors"),backgroundImage:{none:"none","gradient-to-t":"linear-gradient(to top, var(--tw-gradient-stops))","gradient-to-tr":"linear-gradient(to top right, var(--tw-gradient-stops))","gradient-to-r":"linear-gradient(to right, var(--tw-gradient-stops))","gradient-to-br":"linear-gradient(to bottom right, var(--tw-gradient-stops))","gradient-to-b":"linear-gradient(to bottom, var(--tw-gradient-stops))","gradient-to-bl":"linear-gradient(to bottom left, var(--tw-gradient-stops))","gradient-to-l":"linear-gradient(to left, var(--tw-gradient-stops))","gradient-to-tl":"linear-gradient(to top left, var(--tw-gradient-stops))"},backgroundOpacity:({theme:r})=>r("opacity"),backgroundPosition:{bottom:"bottom",center:"center",left:"left","left-bottom":"left bottom","left-top":"left top",right:"right","right-bottom":"right bottom","right-top":"right top",top:"top"},backgroundSize:{auto:"auto",cover:"cover",contain:"contain"},blur:{0:"0",none:"",sm:"4px",DEFAULT:"8px",md:"12px",lg:"16px",xl:"24px","2xl":"40px","3xl":"64px"},borderColor:({theme:r})=>({...r("colors"),DEFAULT:r("colors.gray.200","currentColor")}),borderOpacity:({theme:r})=>r("opacity"),borderRadius:{none:"0px",sm:"0.125rem",DEFAULT:"0.25rem",md:"0.375rem",lg:"0.5rem",xl:"0.75rem","2xl":"1rem","3xl":"1.5rem",full:"9999px"},borderSpacing:({theme:r})=>({...r("spacing")}),borderWidth:{DEFAULT:"1px",0:"0px",2:"2px",4:"4px",8:"8px"},boxShadow:{sm:"0 1px 2px 0 rgb(0 0 0 / 0.05)",DEFAULT:"0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",md:"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",lg:"0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",xl:"0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)","2xl":"0 25px 50px -12px rgb(0 0 0 / 0.25)",inner:"inset 0 2px 4px 0 rgb(0 0 0 / 0.05)",none:"none"},boxShadowColor:({theme:r})=>r("colors"),brightness:{0:"0",50:".5",75:".75",90:".9",95:".95",100:"1",105:"1.05",110:"1.1",125:"1.25",150:"1.5",200:"2"},caretColor:({theme:r})=>r("colors"),colors:({colors:r})=>({inherit:r.inherit,current:r.current,transparent:r.transparent,black:r.black,white:r.white,slate:r.slate,gray:r.gray,zinc:r.zinc,neutral:r.neutral,stone:r.stone,red:r.red,orange:r.orange,amber:r.amber,yellow:r.yellow,lime:r.lime,green:r.green,emerald:r.emerald,teal:r.teal,cyan:r.cyan,sky:r.sky,blue:r.blue,indigo:r.indigo,violet:r.violet,purple:r.purple,fuchsia:r.fuchsia,pink:r.pink,rose:r.rose}),columns:{auto:"auto",1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"10",11:"11",12:"12","3xs":"16rem","2xs":"18rem",xs:"20rem",sm:"24rem",md:"28rem",lg:"32rem",xl:"36rem","2xl":"42rem","3xl":"48rem","4xl":"56rem","5xl":"64rem","6xl":"72rem","7xl":"80rem"},container:{},content:{none:"none"},contrast:{0:"0",50:".5",75:".75",100:"1",125:"1.25",150:"1.5",200:"2"},cursor:{auto:"auto",default:"default",pointer:"pointer",wait:"wait",text:"text",move:"move",help:"help","not-allowed":"not-allowed",none:"none","context-menu":"context-menu",progress:"progress",cell:"cell",crosshair:"crosshair","vertical-text":"vertical-text",alias:"alias",copy:"copy","no-drop":"no-drop",grab:"grab",grabbing:"grabbing","all-scroll":"all-scroll","col-resize":"col-resize","row-resize":"row-resize","n-resize":"n-resize","e-resize":"e-resize","s-resize":"s-resize","w-resize":"w-resize","ne-resize":"ne-resize","nw-resize":"nw-resize","se-resize":"se-resize","sw-resize":"sw-resize","ew-resize":"ew-resize","ns-resize":"ns-resize","nesw-resize":"nesw-resize","nwse-resize":"nwse-resize","zoom-in":"zoom-in","zoom-out":"zoom-out"},divideColor:({theme:r})=>r("borderColor"),divideOpacity:({theme:r})=>r("borderOpacity"),divideWidth:({theme:r})=>r("borderWidth"),dropShadow:{sm:"0 1px 1px rgb(0 0 0 / 0.05)",DEFAULT:["0 1px 2px rgb(0 0 0 / 0.1)","0 1px 1px rgb(0 0 0 / 0.06)"],md:["0 4px 3px rgb(0 0 0 / 0.07)","0 2px 2px rgb(0 0 0 / 0.06)"],lg:["0 10px 8px rgb(0 0 0 / 0.04)","0 4px 3px rgb(0 0 0 / 0.1)"],xl:["0 20px 13px rgb(0 0 0 / 0.03)","0 8px 5px rgb(0 0 0 / 0.08)"],"2xl":"0 25px 25px rgb(0 0 0 / 0.15)",none:"0 0 #0000"},fill:({theme:r})=>({none:"none",...r("colors")}),flex:{1:"1 1 0%",auto:"1 1 auto",initial:"0 1 auto",none:"none"},flexBasis:({theme:r})=>({auto:"auto",...r("spacing"),"1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%","1/5":"20%","2/5":"40%","3/5":"60%","4/5":"80%","1/6":"16.666667%","2/6":"33.333333%","3/6":"50%","4/6":"66.666667%","5/6":"83.333333%","1/12":"8.333333%","2/12":"16.666667%","3/12":"25%","4/12":"33.333333%","5/12":"41.666667%","6/12":"50%","7/12":"58.333333%","8/12":"66.666667%","9/12":"75%","10/12":"83.333333%","11/12":"91.666667%",full:"100%"}),flexGrow:{0:"0",DEFAULT:"1"},flexShrink:{0:"0",DEFAULT:"1"},fontFamily:{sans:["ui-sans-serif","system-ui","sans-serif",'"Apple Color Emoji"','"Segoe UI Emoji"','"Segoe UI Symbol"','"Noto Color Emoji"'],serif:["ui-serif","Georgia","Cambria",'"Times New Roman"',"Times","serif"],mono:["ui-monospace","SFMono-Regular","Menlo","Monaco","Consolas",'"Liberation Mono"','"Courier New"',"monospace"]},fontSize:{xs:["0.75rem",{lineHeight:"1rem"}],sm:["0.875rem",{lineHeight:"1.25rem"}],base:["1rem",{lineHeight:"1.5rem"}],lg:["1.125rem",{lineHeight:"1.75rem"}],xl:["1.25rem",{lineHeight:"1.75rem"}],"2xl":["1.5rem",{lineHeight:"2rem"}],"3xl":["1.875rem",{lineHeight:"2.25rem"}],"4xl":["2.25rem",{lineHeight:"2.5rem"}],"5xl":["3rem",{lineHeight:"1"}],"6xl":["3.75rem",{lineHeight:"1"}],"7xl":["4.5rem",{lineHeight:"1"}],"8xl":["6rem",{lineHeight:"1"}],"9xl":["8rem",{lineHeight:"1"}]},fontWeight:{thin:"100",extralight:"200",light:"300",normal:"400",medium:"500",semibold:"600",bold:"700",extrabold:"800",black:"900"},gap:({theme:r})=>r("spacing"),gradientColorStops:({theme:r})=>r("colors"),gradientColorStopPositions:{"0%":"0%","5%":"5%","10%":"10%","15%":"15%","20%":"20%","25%":"25%","30%":"30%","35%":"35%","40%":"40%","45%":"45%","50%":"50%","55%":"55%","60%":"60%","65%":"65%","70%":"70%","75%":"75%","80%":"80%","85%":"85%","90%":"90%","95%":"95%","100%":"100%"},grayscale:{0:"0",DEFAULT:"100%"},gridAutoColumns:{auto:"auto",min:"min-content",max:"max-content",fr:"minmax(0, 1fr)"},gridAutoRows:{auto:"auto",min:"min-content",max:"max-content",fr:"minmax(0, 1fr)"},gridColumn:{auto:"auto","span-1":"span 1 / span 1","span-2":"span 2 / span 2","span-3":"span 3 / span 3","span-4":"span 4 / span 4","span-5":"span 5 / span 5","span-6":"span 6 / span 6","span-7":"span 7 / span 7","span-8":"span 8 / span 8","span-9":"span 9 / span 9","span-10":"span 10 / span 10","span-11":"span 11 / span 11","span-12":"span 12 / span 12","span-full":"1 / -1"},gridColumnEnd:{auto:"auto",1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"10",11:"11",12:"12",13:"13"},gridColumnStart:{auto:"auto",1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"10",11:"11",12:"12",13:"13"},gridRow:{auto:"auto","span-1":"span 1 / span 1","span-2":"span 2 / span 2","span-3":"span 3 / span 3","span-4":"span 4 / span 4","span-5":"span 5 / span 5","span-6":"span 6 / span 6","span-7":"span 7 / span 7","span-8":"span 8 / span 8","span-9":"span 9 / span 9","span-10":"span 10 / span 10","span-11":"span 11 / span 11","span-12":"span 12 / span 12","span-full":"1 / -1"},gridRowEnd:{auto:"auto",1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"10",11:"11",12:"12",13:"13"},gridRowStart:{auto:"auto",1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"10",11:"11",12:"12",13:"13"},gridTemplateColumns:{none:"none",subgrid:"subgrid",1:"repeat(1, minmax(0, 1fr))",2:"repeat(2, minmax(0, 1fr))",3:"repeat(3, minmax(0, 1fr))",4:"repeat(4, minmax(0, 1fr))",5:"repeat(5, minmax(0, 1fr))",6:"repeat(6, minmax(0, 1fr))",7:"repeat(7, minmax(0, 1fr))",8:"repeat(8, minmax(0, 1fr))",9:"repeat(9, minmax(0, 1fr))",10:"repeat(10, minmax(0, 1fr))",11:"repeat(11, minmax(0, 1fr))",12:"repeat(12, minmax(0, 1fr))"},gridTemplateRows:{none:"none",subgrid:"subgrid",1:"repeat(1, minmax(0, 1fr))",2:"repeat(2, minmax(0, 1fr))",3:"repeat(3, minmax(0, 1fr))",4:"repeat(4, minmax(0, 1fr))",5:"repeat(5, minmax(0, 1fr))",6:"repeat(6, minmax(0, 1fr))",7:"repeat(7, minmax(0, 1fr))",8:"repeat(8, minmax(0, 1fr))",9:"repeat(9, minmax(0, 1fr))",10:"repeat(10, minmax(0, 1fr))",11:"repeat(11, minmax(0, 1fr))",12:"repeat(12, minmax(0, 1fr))"},height:({theme:r})=>({auto:"auto",...r("spacing"),"1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%","1/5":"20%","2/5":"40%","3/5":"60%","4/5":"80%","1/6":"16.666667%","2/6":"33.333333%","3/6":"50%","4/6":"66.666667%","5/6":"83.333333%",full:"100%",screen:"100vh",svh:"100svh",lvh:"100lvh",dvh:"100dvh",min:"min-content",max:"max-content",fit:"fit-content"}),hueRotate:{0:"0deg",15:"15deg",30:"30deg",60:"60deg",90:"90deg",180:"180deg"},inset:({theme:r})=>({auto:"auto",...r("spacing"),"1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%",full:"100%"}),invert:{0:"0",DEFAULT:"100%"},keyframes:{spin:{to:{transform:"rotate(360deg)"}},ping:{"75%, 100%":{transform:"scale(2)",opacity:"0"}},pulse:{"50%":{opacity:".5"}},bounce:{"0%, 100%":{transform:"translateY(-25%)",animationTimingFunction:"cubic-bezier(0.8,0,1,1)"},"50%":{transform:"none",animationTimingFunction:"cubic-bezier(0,0,0.2,1)"}}},letterSpacing:{tighter:"-0.05em",tight:"-0.025em",normal:"0em",wide:"0.025em",wider:"0.05em",widest:"0.1em"},lineHeight:{none:"1",tight:"1.25",snug:"1.375",normal:"1.5",relaxed:"1.625",loose:"2",3:".75rem",4:"1rem",5:"1.25rem",6:"1.5rem",7:"1.75rem",8:"2rem",9:"2.25rem",10:"2.5rem"},listStyleType:{none:"none",disc:"disc",decimal:"decimal"},listStyleImage:{none:"none"},margin:({theme:r})=>({auto:"auto",...r("spacing")}),lineClamp:{1:"1",2:"2",3:"3",4:"4",5:"5",6:"6"},maxHeight:({theme:r})=>({...r("spacing"),none:"none",full:"100%",screen:"100vh",svh:"100svh",lvh:"100lvh",dvh:"100dvh",min:"min-content",max:"max-content",fit:"fit-content"}),maxWidth:({theme:r,breakpoints:e})=>({...r("spacing"),none:"none",xs:"20rem",sm:"24rem",md:"28rem",lg:"32rem",xl:"36rem","2xl":"42rem","3xl":"48rem","4xl":"56rem","5xl":"64rem","6xl":"72rem","7xl":"80rem",full:"100%",min:"min-content",max:"max-content",fit:"fit-content",prose:"65ch",...e(r("screens"))}),minHeight:({theme:r})=>({...r("spacing"),full:"100%",screen:"100vh",svh:"100svh",lvh:"100lvh",dvh:"100dvh",min:"min-content",max:"max-content",fit:"fit-content"}),minWidth:({theme:r})=>({...r("spacing"),full:"100%",min:"min-content",max:"max-content",fit:"fit-content"}),objectPosition:{bottom:"bottom",center:"center",left:"left","left-bottom":"left bottom","left-top":"left top",right:"right","right-bottom":"right bottom","right-top":"right top",top:"top"},opacity:{0:"0",5:"0.05",10:"0.1",15:"0.15",20:"0.2",25:"0.25",30:"0.3",35:"0.35",40:"0.4",45:"0.45",50:"0.5",55:"0.55",60:"0.6",65:"0.65",70:"0.7",75:"0.75",80:"0.8",85:"0.85",90:"0.9",95:"0.95",100:"1"},order:{first:"-9999",last:"9999",none:"0",1:"1",2:"2",3:"3",4:"4",5:"5",6:"6",7:"7",8:"8",9:"9",10:"10",11:"11",12:"12"},outlineColor:({theme:r})=>r("colors"),outlineOffset:{0:"0px",1:"1px",2:"2px",4:"4px",8:"8px"},outlineWidth:{0:"0px",1:"1px",2:"2px",4:"4px",8:"8px"},padding:({theme:r})=>r("spacing"),placeholderColor:({theme:r})=>r("colors"),placeholderOpacity:({theme:r})=>r("opacity"),ringColor:({theme:r})=>({DEFAULT:r("colors.blue.500","#3b82f6"),...r("colors")}),ringOffsetColor:({theme:r})=>r("colors"),ringOffsetWidth:{0:"0px",1:"1px",2:"2px",4:"4px",8:"8px"},ringOpacity:({theme:r})=>({DEFAULT:"0.5",...r("opacity")}),ringWidth:{DEFAULT:"3px",0:"0px",1:"1px",2:"2px",4:"4px",8:"8px"},rotate:{0:"0deg",1:"1deg",2:"2deg",3:"3deg",6:"6deg",12:"12deg",45:"45deg",90:"90deg",180:"180deg"},saturate:{0:"0",50:".5",100:"1",150:"1.5",200:"2"},scale:{0:"0",50:".5",75:".75",90:".9",95:".95",100:"1",105:"1.05",110:"1.1",125:"1.25",150:"1.5"},screens:{sm:"640px",md:"768px",lg:"1024px",xl:"1280px","2xl":"1536px"},scrollMargin:({theme:r})=>({...r("spacing")}),scrollPadding:({theme:r})=>r("spacing"),sepia:{0:"0",DEFAULT:"100%"},skew:{0:"0deg",1:"1deg",2:"2deg",3:"3deg",6:"6deg",12:"12deg"},space:({theme:r})=>({...r("spacing")}),spacing:{px:"1px",0:"0px",.5:"0.125rem",1:"0.25rem",1.5:"0.375rem",2:"0.5rem",2.5:"0.625rem",3:"0.75rem",3.5:"0.875rem",4:"1rem",5:"1.25rem",6:"1.5rem",7:"1.75rem",8:"2rem",9:"2.25rem",10:"2.5rem",11:"2.75rem",12:"3rem",14:"3.5rem",16:"4rem",20:"5rem",24:"6rem",28:"7rem",32:"8rem",36:"9rem",40:"10rem",44:"11rem",48:"12rem",52:"13rem",56:"14rem",60:"15rem",64:"16rem",72:"18rem",80:"20rem",96:"24rem"},stroke:({theme:r})=>({none:"none",...r("colors")}),strokeWidth:{0:"0",1:"1",2:"2"},supports:{},data:{},textColor:({theme:r})=>r("colors"),textDecorationColor:({theme:r})=>r("colors"),textDecorationThickness:{auto:"auto","from-font":"from-font",0:"0px",1:"1px",2:"2px",4:"4px",8:"8px"},textIndent:({theme:r})=>({...r("spacing")}),textOpacity:({theme:r})=>r("opacity"),textUnderlineOffset:{auto:"auto",0:"0px",1:"1px",2:"2px",4:"4px",8:"8px"},transformOrigin:{center:"center",top:"top","top-right":"top right",right:"right","bottom-right":"bottom right",bottom:"bottom","bottom-left":"bottom left",left:"left","top-left":"top left"},transitionDelay:{0:"0s",75:"75ms",100:"100ms",150:"150ms",200:"200ms",300:"300ms",500:"500ms",700:"700ms",1e3:"1000ms"},transitionDuration:{DEFAULT:"150ms",0:"0s",75:"75ms",100:"100ms",150:"150ms",200:"200ms",300:"300ms",500:"500ms",700:"700ms",1e3:"1000ms"},transitionProperty:{none:"none",all:"all",DEFAULT:"color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter",colors:"color, background-color, border-color, text-decoration-color, fill, stroke",opacity:"opacity",shadow:"box-shadow",transform:"transform"},transitionTimingFunction:{DEFAULT:"cubic-bezier(0.4, 0, 0.2, 1)",linear:"linear",in:"cubic-bezier(0.4, 0, 1, 1)",out:"cubic-bezier(0, 0, 0.2, 1)","in-out":"cubic-bezier(0.4, 0, 0.2, 1)"},translate:({theme:r})=>({...r("spacing"),"1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%",full:"100%"}),size:({theme:r})=>({auto:"auto",...r("spacing"),"1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%","1/5":"20%","2/5":"40%","3/5":"60%","4/5":"80%","1/6":"16.666667%","2/6":"33.333333%","3/6":"50%","4/6":"66.666667%","5/6":"83.333333%","1/12":"8.333333%","2/12":"16.666667%","3/12":"25%","4/12":"33.333333%","5/12":"41.666667%","6/12":"50%","7/12":"58.333333%","8/12":"66.666667%","9/12":"75%","10/12":"83.333333%","11/12":"91.666667%",full:"100%",min:"min-content",max:"max-content",fit:"fit-content"}),width:({theme:r})=>({auto:"auto",...r("spacing"),"1/2":"50%","1/3":"33.333333%","2/3":"66.666667%","1/4":"25%","2/4":"50%","3/4":"75%","1/5":"20%","2/5":"40%","3/5":"60%","4/5":"80%","1/6":"16.666667%","2/6":"33.333333%","3/6":"50%","4/6":"66.666667%","5/6":"83.333333%","1/12":"8.333333%","2/12":"16.666667%","3/12":"25%","4/12":"33.333333%","5/12":"41.666667%","6/12":"50%","7/12":"58.333333%","8/12":"66.666667%","9/12":"75%","10/12":"83.333333%","11/12":"91.666667%",full:"100%",screen:"100vw",svw:"100svw",lvw:"100lvw",dvw:"100dvw",min:"min-content",max:"max-content",fit:"fit-content"}),willChange:{auto:"auto",scroll:"scroll-position",contents:"contents",transform:"transform"},zIndex:{auto:"auto",0:"0",10:"10",20:"20",30:"30",40:"40",50:"50"}},plugins:[]}});function nn(r){let e=(r?.presets??[ic.default]).slice().reverse().flatMap(n=>nn(n instanceof Function?n():n)),t={respectDefaultRingColorOpacity:{theme:{ringColor:({theme:n})=>({DEFAULT:"#3b82f67f",...n("colors")})}},disableColorOpacityUtilitiesByDefault:{corePlugins:{backgroundOpacity:!1,borderOpacity:!1,divideOpacity:!1,placeholderOpacity:!1,ringOpacity:!1,textOpacity:!1}}},i=Object.keys(t).filter(n=>we(r,n)).map(n=>t[n]);return[r,...i,...e]}var ic,nc=P(()=>{u();ic=pe(rn());ct()});var sc={};Ge(sc,{default:()=>zr});function zr(...r){let[,...e]=nn(r[0]);return na([...r,...e])}var sa=P(()=>{u();tc();nc()});var Ur={};Ge(Ur,{default:()=>me});var me,et=P(()=>{u();me={resolve:r=>r,extname:r=>"."+r.split(".").pop()}});function sn(r){return typeof r=="object"&&r!==null}function bx(r){return Object.keys(r).length===0}function ac(r){return typeof r=="string"||r instanceof String}function aa(r){return sn(r)&&r.config===void 0&&!bx(r)?null:sn(r)&&r.config!==void 0&&ac(r.config)?me.resolve(r.config):sn(r)&&r.config!==void 0&&sn(r.config)?null:ac(r)?me.resolve(r):wx()}function wx(){for(let r of yx)try{let e=me.resolve(r);return be.accessSync(e),e}catch(e){}return null}var yx,oc=P(()=>{u();ft();et();yx=["./tailwind.config.js","./tailwind.config.cjs","./tailwind.config.mjs","./tailwind.config.ts","./tailwind.config.cts","./tailwind.config.mts"]});var lc={};Ge(lc,{default:()=>oa});var oa,la=P(()=>{u();oa={parse:r=>({href:r})}});var ua=x(()=>{u()});var an=x((v3,cc)=>{u();"use strict";var uc=(Qi(),Af),fc=ua(),Jt=class extends Error{constructor(e,t,i,n,s,a){super(e);this.name="CssSyntaxError",this.reason=e,s&&(this.file=s),n&&(this.source=n),a&&(this.plugin=a),typeof t!="undefined"&&typeof i!="undefined"&&(typeof t=="number"?(this.line=t,this.column=i):(this.line=t.line,this.column=t.column,this.endLine=i.line,this.endColumn=i.column)),this.setMessage(),Error.captureStackTrace&&Error.captureStackTrace(this,Jt)}setMessage(){this.message=this.plugin?this.plugin+": ":"",this.message+=this.file?this.file:"",typeof this.line!="undefined"&&(this.message+=":"+this.line+":"+this.column),this.message+=": "+this.reason}showSourceCode(e){if(!this.source)return"";let t=this.source;e==null&&(e=uc.isColorSupported);let i=f=>f,n=f=>f,s=f=>f;if(e){let{bold:f,gray:d,red:p}=uc.createColors(!0);n=h=>f(p(h)),i=h=>d(h),fc&&(s=h=>fc(h))}let a=t.split(/\r?\n/),o=Math.max(this.line-3,0),l=Math.min(this.line+2,a.length),c=String(l).length;return a.slice(o,l).map((f,d)=>{let p=o+1+d,h=" "+(" "+p).slice(-c)+" | ";if(p===this.line){if(f.length>160){let v=20,y=Math.max(0,this.column-v),w=Math.max(this.column+v,this.endColumn+v),k=f.slice(y,w),S=i(h.replace(/\d/g," "))+f.slice(0,Math.min(this.column-1,v-1)).replace(/[^\t]/g," ");return n(">")+i(h)+s(k)+` + `+S+n("^")}let b=i(h.replace(/\d/g," "))+f.slice(0,this.column-1).replace(/[^\t]/g," ");return n(">")+i(h)+s(f)+` + `+b+n("^")}return" "+i(h)+s(f)}).join(` +`)}toString(){let e=this.showSourceCode();return e&&(e=` + +`+e+` +`),this.name+": "+this.message+e}};cc.exports=Jt;Jt.default=Jt});var fa=x((x3,dc)=>{u();"use strict";var pc={after:` +`,beforeClose:` +`,beforeComment:` +`,beforeDecl:` +`,beforeOpen:" ",beforeRule:` +`,colon:": ",commentLeft:" ",commentRight:" ",emptyBody:"",indent:" ",semicolon:!1};function vx(r){return r[0].toUpperCase()+r.slice(1)}var on=class{constructor(e){this.builder=e}atrule(e,t){let i="@"+e.name,n=e.params?this.rawValue(e,"params"):"";if(typeof e.raws.afterName!="undefined"?i+=e.raws.afterName:n&&(i+=" "),e.nodes)this.block(e,i+n);else{let s=(e.raws.between||"")+(t?";":"");this.builder(i+n+s,e)}}beforeAfter(e,t){let i;e.type==="decl"?i=this.raw(e,null,"beforeDecl"):e.type==="comment"?i=this.raw(e,null,"beforeComment"):t==="before"?i=this.raw(e,null,"beforeRule"):i=this.raw(e,null,"beforeClose");let n=e.parent,s=0;for(;n&&n.type!=="root";)s+=1,n=n.parent;if(i.includes(` +`)){let a=this.raw(e,null,"indent");if(a.length)for(let o=0;o0&&e.nodes[t].type==="comment";)t-=1;let i=this.raw(e,"semicolon");for(let n=0;n{if(n=l.raws[t],typeof n!="undefined")return!1})}return typeof n=="undefined"&&(n=pc[i]),a.rawCache[i]=n,n}rawBeforeClose(e){let t;return e.walk(i=>{if(i.nodes&&i.nodes.length>0&&typeof i.raws.after!="undefined")return t=i.raws.after,t.includes(` +`)&&(t=t.replace(/[^\n]+$/,"")),!1}),t&&(t=t.replace(/\S/g,"")),t}rawBeforeComment(e,t){let i;return e.walkComments(n=>{if(typeof n.raws.before!="undefined")return i=n.raws.before,i.includes(` +`)&&(i=i.replace(/[^\n]+$/,"")),!1}),typeof i=="undefined"?i=this.raw(t,null,"beforeDecl"):i&&(i=i.replace(/\S/g,"")),i}rawBeforeDecl(e,t){let i;return e.walkDecls(n=>{if(typeof n.raws.before!="undefined")return i=n.raws.before,i.includes(` +`)&&(i=i.replace(/[^\n]+$/,"")),!1}),typeof i=="undefined"?i=this.raw(t,null,"beforeRule"):i&&(i=i.replace(/\S/g,"")),i}rawBeforeOpen(e){let t;return e.walk(i=>{if(i.type!=="decl"&&(t=i.raws.between,typeof t!="undefined"))return!1}),t}rawBeforeRule(e){let t;return e.walk(i=>{if(i.nodes&&(i.parent!==e||e.first!==i)&&typeof i.raws.before!="undefined")return t=i.raws.before,t.includes(` +`)&&(t=t.replace(/[^\n]+$/,"")),!1}),t&&(t=t.replace(/\S/g,"")),t}rawColon(e){let t;return e.walkDecls(i=>{if(typeof i.raws.between!="undefined")return t=i.raws.between.replace(/[^\s:]/g,""),!1}),t}rawEmptyBody(e){let t;return e.walk(i=>{if(i.nodes&&i.nodes.length===0&&(t=i.raws.after,typeof t!="undefined"))return!1}),t}rawIndent(e){if(e.raws.indent)return e.raws.indent;let t;return e.walk(i=>{let n=i.parent;if(n&&n!==e&&n.parent&&n.parent===e&&typeof i.raws.before!="undefined"){let s=i.raws.before.split(` +`);return t=s[s.length-1],t=t.replace(/\S/g,""),!1}}),t}rawSemicolon(e){let t;return e.walk(i=>{if(i.nodes&&i.nodes.length&&i.last.type==="decl"&&(t=i.raws.semicolon,typeof t!="undefined"))return!1}),t}rawValue(e,t){let i=e[t],n=e.raws[t];return n&&n.value===i?n.raw:i}root(e){this.body(e),e.raws.after&&this.builder(e.raws.after)}rule(e){this.block(e,this.rawValue(e,"selector")),e.raws.ownSemicolon&&this.builder(e.raws.ownSemicolon,e,"end")}stringify(e,t){if(!this[e.type])throw new Error("Unknown AST node type "+e.type+". Maybe you need to change PostCSS stringifier.");this[e.type](e,t)}};dc.exports=on;on.default=on});var Vr=x((k3,hc)=>{u();"use strict";var xx=fa();function ca(r,e){new xx(e).stringify(r)}hc.exports=ca;ca.default=ca});var ln=x((S3,pa)=>{u();"use strict";pa.exports.isClean=Symbol("isClean");pa.exports.my=Symbol("my")});var Gr=x((A3,mc)=>{u();"use strict";var kx=an(),Sx=fa(),Ax=Vr(),{isClean:Hr,my:Cx}=ln();function da(r,e){let t=new r.constructor;for(let i in r){if(!Object.prototype.hasOwnProperty.call(r,i)||i==="proxyCache")continue;let n=r[i],s=typeof n;i==="parent"&&s==="object"?e&&(t[i]=e):i==="source"?t[i]=n:Array.isArray(n)?t[i]=n.map(a=>da(a,t)):(s==="object"&&n!==null&&(n=da(n)),t[i]=n)}return t}function Wr(r,e){if(e&&typeof e.offset!="undefined")return e.offset;let t=1,i=1,n=0;for(let s=0;se.root().toProxy():e[t]},set(e,t,i){return e[t]===i||(e[t]=i,(t==="prop"||t==="value"||t==="name"||t==="params"||t==="important"||t==="text")&&e.markDirty()),!0}}}markClean(){this[Hr]=!0}markDirty(){if(this[Hr]){this[Hr]=!1;let e=this;for(;e=e.parent;)e[Hr]=!1}}next(){if(!this.parent)return;let e=this.parent.index(this);return this.parent.nodes[e+1]}positionBy(e){let t=this.source.start;if(e.index)t=this.positionInside(e.index);else if(e.word){let n=this.source.input.css.slice(Wr(this.source.input.css,this.source.start),Wr(this.source.input.css,this.source.end)).indexOf(e.word);n!==-1&&(t=this.positionInside(n))}return t}positionInside(e){let t=this.source.start.column,i=this.source.start.line,n=Wr(this.source.input.css,this.source.start),s=n+e;for(let a=n;atypeof l=="object"&&l.toJSON?l.toJSON(null,t):l);else if(typeof o=="object"&&o.toJSON)i[a]=o.toJSON(null,t);else if(a==="source"){let l=t.get(o.input);l==null&&(l=s,t.set(o.input,s),s++),i[a]={end:o.end,inputId:l,start:o.start}}else i[a]=o}return n&&(i.inputs=[...t.keys()].map(a=>a.toJSON())),i}toProxy(){return this.proxyCache||(this.proxyCache=new Proxy(this,this.getProxyProcessor())),this.proxyCache}toString(e=Ax){e.stringify&&(e=e.stringify);let t="";return e(this,i=>{t+=i}),t}warn(e,t,i){let n={node:this};for(let s in i)n[s]=i[s];return e.warn(t,n)}get proxyOf(){return this}};mc.exports=un;un.default=un});var Qr=x((C3,gc)=>{u();"use strict";var _x=Gr(),fn=class extends _x{constructor(e){super(e);this.type="comment"}};gc.exports=fn;fn.default=fn});var Yr=x((_3,yc)=>{u();"use strict";var Ex=Gr(),cn=class extends Ex{constructor(e){e&&typeof e.value!="undefined"&&typeof e.value!="string"&&(e={...e,value:String(e.value)});super(e);this.type="decl"}get variable(){return this.prop.startsWith("--")||this.prop[0]==="$"}};yc.exports=cn;cn.default=cn});var Et=x((E3,_c)=>{u();"use strict";var bc=Qr(),wc=Yr(),Ox=Gr(),{isClean:vc,my:xc}=ln(),ha,kc,Sc,ma;function Ac(r){return r.map(e=>(e.nodes&&(e.nodes=Ac(e.nodes)),delete e.source,e))}function Cc(r){if(r[vc]=!1,r.proxyOf.nodes)for(let e of r.proxyOf.nodes)Cc(e)}var Fe=class extends Ox{append(...e){for(let t of e){let i=this.normalize(t,this.last);for(let n of i)this.proxyOf.nodes.push(n)}return this.markDirty(),this}cleanRaws(e){if(super.cleanRaws(e),this.nodes)for(let t of this.nodes)t.cleanRaws(e)}each(e){if(!this.proxyOf.nodes)return;let t=this.getIterator(),i,n;for(;this.indexes[t]e[t](...i.map(n=>typeof n=="function"?(s,a)=>n(s.toProxy(),a):n)):t==="every"||t==="some"?i=>e[t]((n,...s)=>i(n.toProxy(),...s)):t==="root"?()=>e.root().toProxy():t==="nodes"?e.nodes.map(i=>i.toProxy()):t==="first"||t==="last"?e[t].toProxy():e[t]:e[t]},set(e,t,i){return e[t]===i||(e[t]=i,(t==="name"||t==="params"||t==="selector")&&e.markDirty()),!0}}}index(e){return typeof e=="number"?e:(e.proxyOf&&(e=e.proxyOf),this.proxyOf.nodes.indexOf(e))}insertAfter(e,t){let i=this.index(e),n=this.normalize(t,this.proxyOf.nodes[i]).reverse();i=this.index(e);for(let a of n)this.proxyOf.nodes.splice(i+1,0,a);let s;for(let a in this.indexes)s=this.indexes[a],i(n[xc]||Fe.rebuild(n),n=n.proxyOf,n.parent&&n.parent.removeChild(n),n[vc]&&Cc(n),n.raws||(n.raws={}),typeof n.raws.before=="undefined"&&t&&typeof t.raws.before!="undefined"&&(n.raws.before=t.raws.before.replace(/\S/g,"")),n.parent=this.proxyOf,n))}prepend(...e){e=e.reverse();for(let t of e){let i=this.normalize(t,this.first,"prepend").reverse();for(let n of i)this.proxyOf.nodes.unshift(n);for(let n in this.indexes)this.indexes[n]=this.indexes[n]+i.length}return this.markDirty(),this}push(e){return e.parent=this,this.proxyOf.nodes.push(e),this}removeAll(){for(let e of this.proxyOf.nodes)e.parent=void 0;return this.proxyOf.nodes=[],this.markDirty(),this}removeChild(e){e=this.index(e),this.proxyOf.nodes[e].parent=void 0,this.proxyOf.nodes.splice(e,1);let t;for(let i in this.indexes)t=this.indexes[i],t>=e&&(this.indexes[i]=t-1);return this.markDirty(),this}replaceValues(e,t,i){return i||(i=t,t={}),this.walkDecls(n=>{t.props&&!t.props.includes(n.prop)||t.fast&&!n.value.includes(t.fast)||(n.value=n.value.replace(e,i))}),this.markDirty(),this}some(e){return this.nodes.some(e)}walk(e){return this.each((t,i)=>{let n;try{n=e(t,i)}catch(s){throw t.addToError(s)}return n!==!1&&t.walk&&(n=t.walk(e)),n})}walkAtRules(e,t){return t?e instanceof RegExp?this.walk((i,n)=>{if(i.type==="atrule"&&e.test(i.name))return t(i,n)}):this.walk((i,n)=>{if(i.type==="atrule"&&i.name===e)return t(i,n)}):(t=e,this.walk((i,n)=>{if(i.type==="atrule")return t(i,n)}))}walkComments(e){return this.walk((t,i)=>{if(t.type==="comment")return e(t,i)})}walkDecls(e,t){return t?e instanceof RegExp?this.walk((i,n)=>{if(i.type==="decl"&&e.test(i.prop))return t(i,n)}):this.walk((i,n)=>{if(i.type==="decl"&&i.prop===e)return t(i,n)}):(t=e,this.walk((i,n)=>{if(i.type==="decl")return t(i,n)}))}walkRules(e,t){return t?e instanceof RegExp?this.walk((i,n)=>{if(i.type==="rule"&&e.test(i.selector))return t(i,n)}):this.walk((i,n)=>{if(i.type==="rule"&&i.selector===e)return t(i,n)}):(t=e,this.walk((i,n)=>{if(i.type==="rule")return t(i,n)}))}get first(){if(!!this.proxyOf.nodes)return this.proxyOf.nodes[0]}get last(){if(!!this.proxyOf.nodes)return this.proxyOf.nodes[this.proxyOf.nodes.length-1]}};Fe.registerParse=r=>{kc=r};Fe.registerRule=r=>{ma=r};Fe.registerAtRule=r=>{ha=r};Fe.registerRoot=r=>{Sc=r};_c.exports=Fe;Fe.default=Fe;Fe.rebuild=r=>{r.type==="atrule"?Object.setPrototypeOf(r,ha.prototype):r.type==="rule"?Object.setPrototypeOf(r,ma.prototype):r.type==="decl"?Object.setPrototypeOf(r,wc.prototype):r.type==="comment"?Object.setPrototypeOf(r,bc.prototype):r.type==="root"&&Object.setPrototypeOf(r,Sc.prototype),r[xc]=!0,r.nodes&&r.nodes.forEach(e=>{Fe.rebuild(e)})}});var pn=x((O3,Oc)=>{u();"use strict";var Ec=Et(),Kr=class extends Ec{constructor(e){super(e);this.type="atrule"}append(...e){return this.proxyOf.nodes||(this.nodes=[]),super.append(...e)}prepend(...e){return this.proxyOf.nodes||(this.nodes=[]),super.prepend(...e)}};Oc.exports=Kr;Kr.default=Kr;Ec.registerAtRule(Kr)});var dn=x((T3,Pc)=>{u();"use strict";var Tx=Et(),Tc,Rc,er=class extends Tx{constructor(e){super({type:"document",...e});this.nodes||(this.nodes=[])}toResult(e={}){return new Tc(new Rc,this,e).stringify()}};er.registerLazyResult=r=>{Tc=r};er.registerProcessor=r=>{Rc=r};Pc.exports=er;er.default=er});var Dc=x((R3,Ic)=>{u();var Rx="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict",Px=(r,e=21)=>(t=e)=>{let i="",n=t;for(;n--;)i+=r[Math.random()*r.length|0];return i},Ix=(r=21)=>{let e="",t=r;for(;t--;)e+=Rx[Math.random()*64|0];return e};Ic.exports={nanoid:Ix,customAlphabet:Px}});var qc=x(()=>{u()});var ga=x((D3,$c)=>{u();$c.exports={}});var mn=x((q3,Bc)=>{u();"use strict";var{nanoid:Dx}=Dc(),{isAbsolute:ya,resolve:ba}=(et(),Ur),{SourceMapConsumer:qx,SourceMapGenerator:$x}=qc(),{fileURLToPath:Lc,pathToFileURL:hn}=(la(),lc),Mc=an(),Lx=ga(),wa=ua(),va=Symbol("fromOffsetCache"),Mx=Boolean(qx&&$x),Nc=Boolean(ba&&ya),Xr=class{constructor(e,t={}){if(e===null||typeof e=="undefined"||typeof e=="object"&&!e.toString)throw new Error(`PostCSS received ${e} instead of CSS string`);if(this.css=e.toString(),this.css[0]==="\uFEFF"||this.css[0]==="\uFFFE"?(this.hasBOM=!0,this.css=this.css.slice(1)):this.hasBOM=!1,t.from&&(!Nc||/^\w+:\/\//.test(t.from)||ya(t.from)?this.file=t.from:this.file=ba(t.from)),Nc&&Mx){let i=new Lx(this.css,t);if(i.text){this.map=i;let n=i.consumer().file;!this.file&&n&&(this.file=this.mapResolve(n))}}this.file||(this.id=""),this.map&&(this.map.file=this.from)}error(e,t,i,n={}){let s,a,o;if(t&&typeof t=="object"){let c=t,f=i;if(typeof c.offset=="number"){let d=this.fromOffset(c.offset);t=d.line,i=d.col}else t=c.line,i=c.column;if(typeof f.offset=="number"){let d=this.fromOffset(f.offset);a=d.line,s=d.col}else a=f.line,s=f.column}else if(!i){let c=this.fromOffset(t);t=c.line,i=c.col}let l=this.origin(t,i,a,s);return l?o=new Mc(e,l.endLine===void 0?l.line:{column:l.column,line:l.line},l.endLine===void 0?l.column:{column:l.endColumn,line:l.endLine},l.source,l.file,n.plugin):o=new Mc(e,a===void 0?t:{column:i,line:t},a===void 0?i:{column:s,line:a},this.css,this.file,n.plugin),o.input={column:i,endColumn:s,endLine:a,line:t,source:this.css},this.file&&(hn&&(o.input.url=hn(this.file).toString()),o.input.file=this.file),o}fromOffset(e){let t,i;if(this[va])i=this[va];else{let s=this.css.split(` +`);i=new Array(s.length);let a=0;for(let o=0,l=s.length;o=t)n=i.length-1;else{let s=i.length-2,a;for(;n>1),e=i[a+1])n=a+1;else{n=a;break}}return{col:e-i[n]+1,line:n+1}}mapResolve(e){return/^\w+:\/\//.test(e)?e:ba(this.map.consumer().sourceRoot||this.map.root||".",e)}origin(e,t,i,n){if(!this.map)return!1;let s=this.map.consumer(),a=s.originalPositionFor({column:t,line:e});if(!a.source)return!1;let o;typeof i=="number"&&(o=s.originalPositionFor({column:n,line:i}));let l;ya(a.source)?l=hn(a.source):l=new URL(a.source,this.map.consumer().sourceRoot||hn(this.map.mapFile));let c={column:a.column,endColumn:o&&o.column,endLine:o&&o.line,line:a.line,url:l.toString()};if(l.protocol==="file:")if(Lc)c.file=Lc(l);else throw new Error("file: protocol is not available in this PostCSS build");let f=s.sourceContentFor(a.source);return f&&(c.source=f),c}toJSON(){let e={};for(let t of["hasBOM","css","file","id"])this[t]!=null&&(e[t]=this[t]);return this.map&&(e.map={...this.map},e.map.consumerCache&&(e.map.consumerCache=void 0)),e}get from(){return this.file||this.id}};Bc.exports=Xr;Xr.default=Xr;wa&&wa.registerInput&&wa.registerInput(Xr)});var tr=x(($3,Uc)=>{u();"use strict";var Fc=Et(),jc,zc,Ut=class extends Fc{constructor(e){super(e);this.type="root",this.nodes||(this.nodes=[])}normalize(e,t,i){let n=super.normalize(e);if(t){if(i==="prepend")this.nodes.length>1?t.raws.before=this.nodes[1].raws.before:delete t.raws.before;else if(this.first!==t)for(let s of n)s.raws.before=t.raws.before}return n}removeChild(e,t){let i=this.index(e);return!t&&i===0&&this.nodes.length>1&&(this.nodes[1].raws.before=this.nodes[i].raws.before),super.removeChild(e)}toResult(e={}){return new jc(new zc,this,e).stringify()}};Ut.registerLazyResult=r=>{jc=r};Ut.registerProcessor=r=>{zc=r};Uc.exports=Ut;Ut.default=Ut;Fc.registerRoot(Ut)});var xa=x((L3,Vc)=>{u();"use strict";var Zr={comma(r){return Zr.split(r,[","],!0)},space(r){let e=[" ",` +`," "];return Zr.split(r,e)},split(r,e,t){let i=[],n="",s=!1,a=0,o=!1,l="",c=!1;for(let f of r)c?c=!1:f==="\\"?c=!0:o?f===l&&(o=!1):f==='"'||f==="'"?(o=!0,l=f):f==="("?a+=1:f===")"?a>0&&(a-=1):a===0&&e.includes(f)&&(s=!0),s?(n!==""&&i.push(n.trim()),n="",s=!1):n+=f;return(t||n!=="")&&i.push(n.trim()),i}};Vc.exports=Zr;Zr.default=Zr});var gn=x((M3,Wc)=>{u();"use strict";var Hc=Et(),Nx=xa(),Jr=class extends Hc{constructor(e){super(e);this.type="rule",this.nodes||(this.nodes=[])}get selectors(){return Nx.comma(this.selector)}set selectors(e){let t=this.selector?this.selector.match(/,\s*/):null,i=t?t[0]:","+this.raw("between","beforeOpen");this.selector=e.join(i)}};Wc.exports=Jr;Jr.default=Jr;Hc.registerRule(Jr)});var Qc=x((N3,Gc)=>{u();"use strict";var Bx=pn(),Fx=Qr(),jx=Yr(),zx=mn(),Ux=ga(),Vx=tr(),Hx=gn();function ei(r,e){if(Array.isArray(r))return r.map(n=>ei(n));let{inputs:t,...i}=r;if(t){e=[];for(let n of t){let s={...n,__proto__:zx.prototype};s.map&&(s.map={...s.map,__proto__:Ux.prototype}),e.push(s)}}if(i.nodes&&(i.nodes=r.nodes.map(n=>ei(n,e))),i.source){let{inputId:n,...s}=i.source;i.source=s,n!=null&&(i.source.input=e[n])}if(i.type==="root")return new Vx(i);if(i.type==="decl")return new jx(i);if(i.type==="rule")return new Hx(i);if(i.type==="comment")return new Fx(i);if(i.type==="atrule")return new Bx(i);throw new Error("Unknown node type: "+r.type)}Gc.exports=ei;ei.default=ei});var ka=x((B3,Yc)=>{u();Yc.exports=function(r,e){return{generate:()=>{let t="";return r(e,i=>{t+=i}),[t]}}}});var ep=x((F3,Jc)=>{u();"use strict";var Sa="'".charCodeAt(0),Kc='"'.charCodeAt(0),yn="\\".charCodeAt(0),Xc="/".charCodeAt(0),bn=` +`.charCodeAt(0),ti=" ".charCodeAt(0),wn="\f".charCodeAt(0),vn=" ".charCodeAt(0),xn="\r".charCodeAt(0),Wx="[".charCodeAt(0),Gx="]".charCodeAt(0),Qx="(".charCodeAt(0),Yx=")".charCodeAt(0),Kx="{".charCodeAt(0),Xx="}".charCodeAt(0),Zx=";".charCodeAt(0),Jx="*".charCodeAt(0),e1=":".charCodeAt(0),t1="@".charCodeAt(0),kn=/[\t\n\f\r "#'()/;[\\\]{}]/g,Sn=/[\t\n\f\r !"#'():;@[\\\]{}]|\/(?=\*)/g,r1=/.[\r\n"'(/\\]/,Zc=/[\da-f]/i;Jc.exports=function(e,t={}){let i=e.css.valueOf(),n=t.ignoreErrors,s,a,o,l,c,f,d,p,h,b,v=i.length,y=0,w=[],k=[];function S(){return y}function E(R){throw e.error("Unclosed "+R,y)}function T(){return k.length===0&&y>=v}function B(R){if(k.length)return k.pop();if(y>=v)return;let F=R?R.ignoreUnclosed:!1;switch(s=i.charCodeAt(y),s){case bn:case ti:case vn:case xn:case wn:{l=y;do l+=1,s=i.charCodeAt(l);while(s===ti||s===bn||s===vn||s===xn||s===wn);f=["space",i.slice(y,l)],y=l-1;break}case Wx:case Gx:case Kx:case Xx:case e1:case Zx:case Yx:{let Y=String.fromCharCode(s);f=[Y,Y,y];break}case Qx:{if(b=w.length?w.pop()[1]:"",h=i.charCodeAt(y+1),b==="url"&&h!==Sa&&h!==Kc&&h!==ti&&h!==bn&&h!==vn&&h!==wn&&h!==xn){l=y;do{if(d=!1,l=i.indexOf(")",l+1),l===-1)if(n||F){l=y;break}else E("bracket");for(p=l;i.charCodeAt(p-1)===yn;)p-=1,d=!d}while(d);f=["brackets",i.slice(y,l+1),y,l],y=l}else l=i.indexOf(")",y+1),a=i.slice(y,l+1),l===-1||r1.test(a)?f=["(","(",y]:(f=["brackets",a,y,l],y=l);break}case Sa:case Kc:{c=s===Sa?"'":'"',l=y;do{if(d=!1,l=i.indexOf(c,l+1),l===-1)if(n||F){l=y+1;break}else E("string");for(p=l;i.charCodeAt(p-1)===yn;)p-=1,d=!d}while(d);f=["string",i.slice(y,l+1),y,l],y=l;break}case t1:{kn.lastIndex=y+1,kn.test(i),kn.lastIndex===0?l=i.length-1:l=kn.lastIndex-2,f=["at-word",i.slice(y,l+1),y,l],y=l;break}case yn:{for(l=y,o=!0;i.charCodeAt(l+1)===yn;)l+=1,o=!o;if(s=i.charCodeAt(l+1),o&&s!==Xc&&s!==ti&&s!==bn&&s!==vn&&s!==xn&&s!==wn&&(l+=1,Zc.test(i.charAt(l)))){for(;Zc.test(i.charAt(l+1));)l+=1;i.charCodeAt(l+1)===ti&&(l+=1)}f=["word",i.slice(y,l+1),y,l],y=l;break}default:{s===Xc&&i.charCodeAt(y+1)===Jx?(l=i.indexOf("*/",y+2)+1,l===0&&(n||F?l=i.length:E("comment")),f=["comment",i.slice(y,l+1),y,l],y=l):(Sn.lastIndex=y+1,Sn.test(i),Sn.lastIndex===0?l=i.length-1:l=Sn.lastIndex-2,f=["word",i.slice(y,l+1),y,l],w.push(f),y=l);break}}return y++,f}function N(R){k.push(R)}return{back:N,endOfFile:T,nextToken:B,position:S}}});var sp=x((j3,np)=>{u();"use strict";var i1=pn(),n1=Qr(),s1=Yr(),a1=tr(),tp=gn(),o1=ep(),rp={empty:!0,space:!0};function l1(r){for(let e=r.length-1;e>=0;e--){let t=r[e],i=t[3]||t[2];if(i)return i}}var ip=class{constructor(e){this.input=e,this.root=new a1,this.current=this.root,this.spaces="",this.semicolon=!1,this.createTokenizer(),this.root.source={input:e,start:{column:1,line:1,offset:0}}}atrule(e){let t=new i1;t.name=e[1].slice(1),t.name===""&&this.unnamedAtrule(t,e),this.init(t,e[2]);let i,n,s,a=!1,o=!1,l=[],c=[];for(;!this.tokenizer.endOfFile();){if(e=this.tokenizer.nextToken(),i=e[0],i==="("||i==="["?c.push(i==="("?")":"]"):i==="{"&&c.length>0?c.push("}"):i===c[c.length-1]&&c.pop(),c.length===0)if(i===";"){t.source.end=this.getPosition(e[2]),t.source.end.offset++,this.semicolon=!0;break}else if(i==="{"){o=!0;break}else if(i==="}"){if(l.length>0){for(s=l.length-1,n=l[s];n&&n[0]==="space";)n=l[--s];n&&(t.source.end=this.getPosition(n[3]||n[2]),t.source.end.offset++)}this.end(e);break}else l.push(e);else l.push(e);if(this.tokenizer.endOfFile()){a=!0;break}}t.raws.between=this.spacesAndCommentsFromEnd(l),l.length?(t.raws.afterName=this.spacesAndCommentsFromStart(l),this.raw(t,"params",l),a&&(e=l[l.length-1],t.source.end=this.getPosition(e[3]||e[2]),t.source.end.offset++,this.spaces=t.raws.between,t.raws.between="")):(t.raws.afterName="",t.params=""),o&&(t.nodes=[],this.current=t)}checkMissedSemicolon(e){let t=this.colon(e);if(t===!1)return;let i=0,n;for(let s=t-1;s>=0&&(n=e[s],!(n[0]!=="space"&&(i+=1,i===2)));s--);throw this.input.error("Missed semicolon",n[0]==="word"?n[3]+1:n[2])}colon(e){let t=0,i,n,s;for(let[a,o]of e.entries()){if(n=o,s=n[0],s==="("&&(t+=1),s===")"&&(t-=1),t===0&&s===":")if(!i)this.doubleColon(n);else{if(i[0]==="word"&&i[1]==="progid")continue;return a}i=n}return!1}comment(e){let t=new n1;this.init(t,e[2]),t.source.end=this.getPosition(e[3]||e[2]),t.source.end.offset++;let i=e[1].slice(2,-2);if(/^\s*$/.test(i))t.text="",t.raws.left=i,t.raws.right="";else{let n=i.match(/^(\s*)([^]*\S)(\s*)$/);t.text=n[2],t.raws.left=n[1],t.raws.right=n[3]}}createTokenizer(){this.tokenizer=o1(this.input)}decl(e,t){let i=new s1;this.init(i,e[0][2]);let n=e[e.length-1];for(n[0]===";"&&(this.semicolon=!0,e.pop()),i.source.end=this.getPosition(n[3]||n[2]||l1(e)),i.source.end.offset++;e[0][0]!=="word";)e.length===1&&this.unknownWord(e),i.raws.before+=e.shift()[1];for(i.source.start=this.getPosition(e[0][2]),i.prop="";e.length;){let c=e[0][0];if(c===":"||c==="space"||c==="comment")break;i.prop+=e.shift()[1]}i.raws.between="";let s;for(;e.length;)if(s=e.shift(),s[0]===":"){i.raws.between+=s[1];break}else s[0]==="word"&&/\w/.test(s[1])&&this.unknownWord([s]),i.raws.between+=s[1];(i.prop[0]==="_"||i.prop[0]==="*")&&(i.raws.before+=i.prop[0],i.prop=i.prop.slice(1));let a=[],o;for(;e.length&&(o=e[0][0],!(o!=="space"&&o!=="comment"));)a.push(e.shift());this.precheckMissedSemicolon(e);for(let c=e.length-1;c>=0;c--){if(s=e[c],s[1].toLowerCase()==="!important"){i.important=!0;let f=this.stringFrom(e,c);f=this.spacesFromEnd(e)+f,f!==" !important"&&(i.raws.important=f);break}else if(s[1].toLowerCase()==="important"){let f=e.slice(0),d="";for(let p=c;p>0;p--){let h=f[p][0];if(d.trim().startsWith("!")&&h!=="space")break;d=f.pop()[1]+d}d.trim().startsWith("!")&&(i.important=!0,i.raws.important=d,e=f)}if(s[0]!=="space"&&s[0]!=="comment")break}e.some(c=>c[0]!=="space"&&c[0]!=="comment")&&(i.raws.between+=a.map(c=>c[1]).join(""),a=[]),this.raw(i,"value",a.concat(e),t),i.value.includes(":")&&!t&&this.checkMissedSemicolon(e)}doubleColon(e){throw this.input.error("Double colon",{offset:e[2]},{offset:e[2]+e[1].length})}emptyRule(e){let t=new tp;this.init(t,e[2]),t.selector="",t.raws.between="",this.current=t}end(e){this.current.nodes&&this.current.nodes.length&&(this.current.raws.semicolon=this.semicolon),this.semicolon=!1,this.current.raws.after=(this.current.raws.after||"")+this.spaces,this.spaces="",this.current.parent?(this.current.source.end=this.getPosition(e[2]),this.current.source.end.offset++,this.current=this.current.parent):this.unexpectedClose(e)}endFile(){this.current.parent&&this.unclosedBlock(),this.current.nodes&&this.current.nodes.length&&(this.current.raws.semicolon=this.semicolon),this.current.raws.after=(this.current.raws.after||"")+this.spaces,this.root.source.end=this.getPosition(this.tokenizer.position())}freeSemicolon(e){if(this.spaces+=e[1],this.current.nodes){let t=this.current.nodes[this.current.nodes.length-1];t&&t.type==="rule"&&!t.raws.ownSemicolon&&(t.raws.ownSemicolon=this.spaces,this.spaces="")}}getPosition(e){let t=this.input.fromOffset(e);return{column:t.col,line:t.line,offset:e}}init(e,t){this.current.push(e),e.source={input:this.input,start:this.getPosition(t)},e.raws.before=this.spaces,this.spaces="",e.type!=="comment"&&(this.semicolon=!1)}other(e){let t=!1,i=null,n=!1,s=null,a=[],o=e[1].startsWith("--"),l=[],c=e;for(;c;){if(i=c[0],l.push(c),i==="("||i==="[")s||(s=c),a.push(i==="("?")":"]");else if(o&&n&&i==="{")s||(s=c),a.push("}");else if(a.length===0)if(i===";")if(n){this.decl(l,o);return}else break;else if(i==="{"){this.rule(l);return}else if(i==="}"){this.tokenizer.back(l.pop()),t=!0;break}else i===":"&&(n=!0);else i===a[a.length-1]&&(a.pop(),a.length===0&&(s=null));c=this.tokenizer.nextToken()}if(this.tokenizer.endOfFile()&&(t=!0),a.length>0&&this.unclosedBracket(s),t&&n){if(!o)for(;l.length&&(c=l[l.length-1][0],!(c!=="space"&&c!=="comment"));)this.tokenizer.back(l.pop());this.decl(l,o)}else this.unknownWord(l)}parse(){let e;for(;!this.tokenizer.endOfFile();)switch(e=this.tokenizer.nextToken(),e[0]){case"space":this.spaces+=e[1];break;case";":this.freeSemicolon(e);break;case"}":this.end(e);break;case"comment":this.comment(e);break;case"at-word":this.atrule(e);break;case"{":this.emptyRule(e);break;default:this.other(e);break}this.endFile()}precheckMissedSemicolon(){}raw(e,t,i,n){let s,a,o=i.length,l="",c=!0,f,d;for(let p=0;ph+b[1],"");e.raws[t]={raw:p,value:l}}e[t]=l}rule(e){e.pop();let t=new tp;this.init(t,e[0][2]),t.raws.between=this.spacesAndCommentsFromEnd(e),this.raw(t,"selector",e),this.current=t}spacesAndCommentsFromEnd(e){let t,i="";for(;e.length&&(t=e[e.length-1][0],!(t!=="space"&&t!=="comment"));)i=e.pop()[1]+i;return i}spacesAndCommentsFromStart(e){let t,i="";for(;e.length&&(t=e[0][0],!(t!=="space"&&t!=="comment"));)i+=e.shift()[1];return i}spacesFromEnd(e){let t,i="";for(;e.length&&(t=e[e.length-1][0],t==="space");)i=e.pop()[1]+i;return i}stringFrom(e,t){let i="";for(let n=t;n{u();"use strict";var u1=Et(),f1=mn(),c1=sp();function An(r,e){let t=new f1(r,e),i=new c1(t);try{i.parse()}catch(n){throw n}return i.root}ap.exports=An;An.default=An;u1.registerParse(An)});var Aa=x((U3,op)=>{u();"use strict";var _n=class{constructor(e,t={}){if(this.type="warning",this.text=e,t.node&&t.node.source){let i=t.node.rangeBy(t);this.line=i.start.line,this.column=i.start.column,this.endLine=i.end.line,this.endColumn=i.end.column}for(let i in t)this[i]=t[i]}toString(){return this.node?this.node.error(this.text,{index:this.index,plugin:this.plugin,word:this.word}).message:this.plugin?this.plugin+": "+this.text:this.text}};op.exports=_n;_n.default=_n});var On=x((V3,lp)=>{u();"use strict";var p1=Aa(),En=class{constructor(e,t,i){this.processor=e,this.messages=[],this.root=t,this.opts=i,this.css=void 0,this.map=void 0}toString(){return this.css}warn(e,t={}){t.plugin||this.lastPlugin&&this.lastPlugin.postcssPlugin&&(t.plugin=this.lastPlugin.postcssPlugin);let i=new p1(e,t);return this.messages.push(i),i}warnings(){return this.messages.filter(e=>e.type==="warning")}get content(){return this.css}};lp.exports=En;En.default=En});var Ca=x((H3,fp)=>{u();"use strict";var up={};fp.exports=function(e){up[e]||(up[e]=!0,typeof console!="undefined"&&console.warn&&console.warn(e))}});var Oa=x((G3,hp)=>{u();"use strict";var d1=Et(),h1=dn(),m1=ka(),g1=Cn(),cp=On(),y1=tr(),b1=Vr(),{isClean:tt,my:w1}=ln(),W3=Ca(),v1={atrule:"AtRule",comment:"Comment",decl:"Declaration",document:"Document",root:"Root",rule:"Rule"},x1={AtRule:!0,AtRuleExit:!0,Comment:!0,CommentExit:!0,Declaration:!0,DeclarationExit:!0,Document:!0,DocumentExit:!0,Once:!0,OnceExit:!0,postcssPlugin:!0,prepare:!0,Root:!0,RootExit:!0,Rule:!0,RuleExit:!0},k1={Once:!0,postcssPlugin:!0,prepare:!0},rr=0;function ri(r){return typeof r=="object"&&typeof r.then=="function"}function pp(r){let e=!1,t=v1[r.type];return r.type==="decl"?e=r.prop.toLowerCase():r.type==="atrule"&&(e=r.name.toLowerCase()),e&&r.append?[t,t+"-"+e,rr,t+"Exit",t+"Exit-"+e]:e?[t,t+"-"+e,t+"Exit",t+"Exit-"+e]:r.append?[t,rr,t+"Exit"]:[t,t+"Exit"]}function dp(r){let e;return r.type==="document"?e=["Document",rr,"DocumentExit"]:r.type==="root"?e=["Root",rr,"RootExit"]:e=pp(r),{eventIndex:0,events:e,iterator:0,node:r,visitorIndex:0,visitors:[]}}function _a(r){return r[tt]=!1,r.nodes&&r.nodes.forEach(e=>_a(e)),r}var Ea={},pt=class{constructor(e,t,i){this.stringified=!1,this.processed=!1;let n;if(typeof t=="object"&&t!==null&&(t.type==="root"||t.type==="document"))n=_a(t);else if(t instanceof pt||t instanceof cp)n=_a(t.root),t.map&&(typeof i.map=="undefined"&&(i.map={}),i.map.inline||(i.map.inline=!1),i.map.prev=t.map);else{let s=g1;i.syntax&&(s=i.syntax.parse),i.parser&&(s=i.parser),s.parse&&(s=s.parse);try{n=s(t,i)}catch(a){this.processed=!0,this.error=a}n&&!n[w1]&&d1.rebuild(n)}this.result=new cp(e,n,i),this.helpers={...Ea,postcss:Ea,result:this.result},this.plugins=this.processor.plugins.map(s=>typeof s=="object"&&s.prepare?{...s,...s.prepare(this.result)}:s)}async(){return this.error?Promise.reject(this.error):this.processed?Promise.resolve(this.result):(this.processing||(this.processing=this.runAsync()),this.processing)}catch(e){return this.async().catch(e)}finally(e){return this.async().then(e,e)}getAsyncError(){throw new Error("Use process(css).then(cb) to work with async plugins")}handleError(e,t){let i=this.result.lastPlugin;try{t&&t.addToError(e),this.error=e,e.name==="CssSyntaxError"&&!e.plugin?(e.plugin=i.postcssPlugin,e.setMessage()):i.postcssVersion}catch(n){console&&console.error&&console.error(n)}return e}prepareVisitors(){this.listeners={};let e=(t,i,n)=>{this.listeners[i]||(this.listeners[i]=[]),this.listeners[i].push([t,n])};for(let t of this.plugins)if(typeof t=="object")for(let i in t){if(!x1[i]&&/^[A-Z]/.test(i))throw new Error(`Unknown event ${i} in ${t.postcssPlugin}. Try to update PostCSS (${this.processor.version} now).`);if(!k1[i])if(typeof t[i]=="object")for(let n in t[i])n==="*"?e(t,i,t[i][n]):e(t,i+"-"+n.toLowerCase(),t[i][n]);else typeof t[i]=="function"&&e(t,i,t[i])}this.hasListener=Object.keys(this.listeners).length>0}async runAsync(){this.plugin=0;for(let e=0;e0;){let i=this.visitTick(t);if(ri(i))try{await i}catch(n){let s=t[t.length-1].node;throw this.handleError(n,s)}}}if(this.listeners.OnceExit)for(let[t,i]of this.listeners.OnceExit){this.result.lastPlugin=t;try{if(e.type==="document"){let n=e.nodes.map(s=>i(s,this.helpers));await Promise.all(n)}else await i(e,this.helpers)}catch(n){throw this.handleError(n)}}}return this.processed=!0,this.stringify()}runOnRoot(e){this.result.lastPlugin=e;try{if(typeof e=="object"&&e.Once){if(this.result.root.type==="document"){let t=this.result.root.nodes.map(i=>e.Once(i,this.helpers));return ri(t[0])?Promise.all(t):t}return e.Once(this.result.root,this.helpers)}else if(typeof e=="function")return e(this.result.root,this.result)}catch(t){throw this.handleError(t)}}stringify(){if(this.error)throw this.error;if(this.stringified)return this.result;this.stringified=!0,this.sync();let e=this.result.opts,t=b1;e.syntax&&(t=e.syntax.stringify),e.stringifier&&(t=e.stringifier),t.stringify&&(t=t.stringify);let n=new m1(t,this.result.root,this.result.opts).generate();return this.result.css=n[0],this.result.map=n[1],this.result}sync(){if(this.error)throw this.error;if(this.processed)return this.result;if(this.processed=!0,this.processing)throw this.getAsyncError();for(let e of this.plugins){let t=this.runOnRoot(e);if(ri(t))throw this.getAsyncError()}if(this.prepareVisitors(),this.hasListener){let e=this.result.root;for(;!e[tt];)e[tt]=!0,this.walkSync(e);if(this.listeners.OnceExit)if(e.type==="document")for(let t of e.nodes)this.visitSync(this.listeners.OnceExit,t);else this.visitSync(this.listeners.OnceExit,e)}return this.result}then(e,t){return this.async().then(e,t)}toString(){return this.css}visitSync(e,t){for(let[i,n]of e){this.result.lastPlugin=i;let s;try{s=n(t,this.helpers)}catch(a){throw this.handleError(a,t.proxyOf)}if(t.type!=="root"&&t.type!=="document"&&!t.parent)return!0;if(ri(s))throw this.getAsyncError()}}visitTick(e){let t=e[e.length-1],{node:i,visitors:n}=t;if(i.type!=="root"&&i.type!=="document"&&!i.parent){e.pop();return}if(n.length>0&&t.visitorIndex{n[tt]||this.walkSync(n)});else{let n=this.listeners[i];if(n&&this.visitSync(n,e.toProxy()))return}}warnings(){return this.sync().warnings()}get content(){return this.stringify().content}get css(){return this.stringify().css}get map(){return this.stringify().map}get messages(){return this.sync().messages}get opts(){return this.result.opts}get processor(){return this.result.processor}get root(){return this.sync().root}get[Symbol.toStringTag](){return"LazyResult"}};pt.registerPostcss=r=>{Ea=r};hp.exports=pt;pt.default=pt;y1.registerLazyResult(pt);h1.registerLazyResult(pt)});var gp=x((Y3,mp)=>{u();"use strict";var S1=ka(),A1=Cn(),C1=On(),_1=Vr(),Q3=Ca(),Tn=class{constructor(e,t,i){t=t.toString(),this.stringified=!1,this._processor=e,this._css=t,this._opts=i,this._map=void 0;let n,s=_1;this.result=new C1(this._processor,n,this._opts),this.result.css=t;let a=this;Object.defineProperty(this.result,"root",{get(){return a.root}});let o=new S1(s,n,this._opts,t);if(o.isMap()){let[l,c]=o.generate();l&&(this.result.css=l),c&&(this.result.map=c)}else o.clearAnnotation(),this.result.css=o.css}async(){return this.error?Promise.reject(this.error):Promise.resolve(this.result)}catch(e){return this.async().catch(e)}finally(e){return this.async().then(e,e)}sync(){if(this.error)throw this.error;return this.result}then(e,t){return this.async().then(e,t)}toString(){return this._css}warnings(){return[]}get content(){return this.result.css}get css(){return this.result.css}get map(){return this.result.map}get messages(){return[]}get opts(){return this.result.opts}get processor(){return this.result.processor}get root(){if(this._root)return this._root;let e,t=A1;try{e=t(this._css,this._opts)}catch(i){this.error=i}if(this.error)throw this.error;return this._root=e,e}get[Symbol.toStringTag](){return"NoWorkResult"}};mp.exports=Tn;Tn.default=Tn});var bp=x((K3,yp)=>{u();"use strict";var E1=dn(),O1=Oa(),T1=gp(),R1=tr(),ir=class{constructor(e=[]){this.version="8.4.49",this.plugins=this.normalize(e)}normalize(e){let t=[];for(let i of e)if(i.postcss===!0?i=i():i.postcss&&(i=i.postcss),typeof i=="object"&&Array.isArray(i.plugins))t=t.concat(i.plugins);else if(typeof i=="object"&&i.postcssPlugin)t.push(i);else if(typeof i=="function")t.push(i);else if(!(typeof i=="object"&&(i.parse||i.stringify)))throw new Error(i+" is not a PostCSS plugin");return t}process(e,t={}){return!this.plugins.length&&!t.parser&&!t.stringifier&&!t.syntax?new T1(this,e,t):new O1(this,e,t)}use(e){return this.plugins=this.plugins.concat(this.normalize([e])),this}};yp.exports=ir;ir.default=ir;R1.registerProcessor(ir);E1.registerProcessor(ir)});var $e=x((X3,Cp)=>{u();"use strict";var wp=pn(),vp=Qr(),P1=Et(),I1=an(),xp=Yr(),kp=dn(),D1=Qc(),q1=mn(),$1=Oa(),L1=xa(),M1=Gr(),N1=Cn(),Ta=bp(),B1=On(),Sp=tr(),Ap=gn(),F1=Vr(),j1=Aa();function J(...r){return r.length===1&&Array.isArray(r[0])&&(r=r[0]),new Ta(r)}J.plugin=function(e,t){let i=!1;function n(...a){console&&console.warn&&!i&&(i=!0,console.warn(e+`: postcss.plugin was deprecated. Migration guide: +https://evilmartians.com/chronicles/postcss-8-plugin-migration`),m.env.LANG&&m.env.LANG.startsWith("cn")&&console.warn(e+`: \u91CC\u9762 postcss.plugin \u88AB\u5F03\u7528. \u8FC1\u79FB\u6307\u5357: +https://www.w3ctech.com/topic/2226`));let o=t(...a);return o.postcssPlugin=e,o.postcssVersion=new Ta().version,o}let s;return Object.defineProperty(n,"postcss",{get(){return s||(s=n()),s}}),n.process=function(a,o,l){return J([n(l)]).process(a,o)},n};J.stringify=F1;J.parse=N1;J.fromJSON=D1;J.list=L1;J.comment=r=>new vp(r);J.atRule=r=>new wp(r);J.decl=r=>new xp(r);J.rule=r=>new Ap(r);J.root=r=>new Sp(r);J.document=r=>new kp(r);J.CssSyntaxError=I1;J.Declaration=xp;J.Container=P1;J.Processor=Ta;J.Document=kp;J.Comment=vp;J.Warning=j1;J.AtRule=wp;J.Result=B1;J.Input=q1;J.Rule=Ap;J.Root=Sp;J.Node=M1;$1.registerPostcss(J);Cp.exports=J;J.default=J});var re,ee,Z3,J3,eI,tI,rI,iI,nI,sI,aI,oI,lI,uI,fI,cI,pI,dI,hI,mI,gI,yI,bI,wI,vI,xI,Ot=P(()=>{u();re=pe($e()),ee=re.default,Z3=re.default.stringify,J3=re.default.fromJSON,eI=re.default.plugin,tI=re.default.parse,rI=re.default.list,iI=re.default.document,nI=re.default.comment,sI=re.default.atRule,aI=re.default.rule,oI=re.default.decl,lI=re.default.root,uI=re.default.CssSyntaxError,fI=re.default.Declaration,cI=re.default.Container,pI=re.default.Processor,dI=re.default.Document,hI=re.default.Comment,mI=re.default.Warning,gI=re.default.AtRule,yI=re.default.Result,bI=re.default.Input,wI=re.default.Rule,vI=re.default.Root,xI=re.default.Node});var Ra=x((SI,_p)=>{u();_p.exports=function(r,e,t,i,n){for(e=e.split?e.split("."):e,i=0;i{u();"use strict";Rn.__esModule=!0;Rn.default=V1;function z1(r){for(var e=r.toLowerCase(),t="",i=!1,n=0;n<6&&e[n]!==void 0;n++){var s=e.charCodeAt(n),a=s>=97&&s<=102||s>=48&&s<=57;if(i=s===32,!a)break;t+=e[n]}if(t.length!==0){var o=parseInt(t,16),l=o>=55296&&o<=57343;return l||o===0||o>1114111?["\uFFFD",t.length+(i?1:0)]:[String.fromCodePoint(o),t.length+(i?1:0)]}}var U1=/\\/;function V1(r){var e=U1.test(r);if(!e)return r;for(var t="",i=0;i{u();"use strict";In.__esModule=!0;In.default=H1;function H1(r){for(var e=arguments.length,t=new Array(e>1?e-1:0),i=1;i0;){var n=t.shift();if(!r[n])return;r=r[n]}return r}Op.exports=In.default});var Pp=x((Dn,Rp)=>{u();"use strict";Dn.__esModule=!0;Dn.default=W1;function W1(r){for(var e=arguments.length,t=new Array(e>1?e-1:0),i=1;i0;){var n=t.shift();r[n]||(r[n]={}),r=r[n]}}Rp.exports=Dn.default});var Dp=x((qn,Ip)=>{u();"use strict";qn.__esModule=!0;qn.default=G1;function G1(r){for(var e="",t=r.indexOf("/*"),i=0;t>=0;){e=e+r.slice(i,t);var n=r.indexOf("*/",t+2);if(n<0)return e;i=n+2,t=r.indexOf("/*",i)}return e=e+r.slice(i),e}Ip.exports=qn.default});var ii=x(rt=>{u();"use strict";rt.__esModule=!0;rt.unesc=rt.stripComments=rt.getProp=rt.ensureObject=void 0;var Q1=$n(Pn());rt.unesc=Q1.default;var Y1=$n(Tp());rt.getProp=Y1.default;var K1=$n(Pp());rt.ensureObject=K1.default;var X1=$n(Dp());rt.stripComments=X1.default;function $n(r){return r&&r.__esModule?r:{default:r}}});var dt=x((ni,Lp)=>{u();"use strict";ni.__esModule=!0;ni.default=void 0;var qp=ii();function $p(r,e){for(var t=0;ti||this.source.end.linen||this.source.end.line===i&&this.source.end.column{u();"use strict";ie.__esModule=!0;ie.UNIVERSAL=ie.TAG=ie.STRING=ie.SELECTOR=ie.ROOT=ie.PSEUDO=ie.NESTING=ie.ID=ie.COMMENT=ie.COMBINATOR=ie.CLASS=ie.ATTRIBUTE=void 0;var tk="tag";ie.TAG=tk;var rk="string";ie.STRING=rk;var ik="selector";ie.SELECTOR=ik;var nk="root";ie.ROOT=nk;var sk="pseudo";ie.PSEUDO=sk;var ak="nesting";ie.NESTING=ak;var ok="id";ie.ID=ok;var lk="comment";ie.COMMENT=lk;var uk="combinator";ie.COMBINATOR=uk;var fk="class";ie.CLASS=fk;var ck="attribute";ie.ATTRIBUTE=ck;var pk="universal";ie.UNIVERSAL=pk});var Ln=x((si,Fp)=>{u();"use strict";si.__esModule=!0;si.default=void 0;var dk=mk(dt()),ht=hk(Se());function Mp(r){if(typeof WeakMap!="function")return null;var e=new WeakMap,t=new WeakMap;return(Mp=function(n){return n?t:e})(r)}function hk(r,e){if(!e&&r&&r.__esModule)return r;if(r===null||typeof r!="object"&&typeof r!="function")return{default:r};var t=Mp(e);if(t&&t.has(r))return t.get(r);var i={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in r)if(s!=="default"&&Object.prototype.hasOwnProperty.call(r,s)){var a=n?Object.getOwnPropertyDescriptor(r,s):null;a&&(a.get||a.set)?Object.defineProperty(i,s,a):i[s]=r[s]}return i.default=r,t&&t.set(r,i),i}function mk(r){return r&&r.__esModule?r:{default:r}}function gk(r,e){var t=typeof Symbol!="undefined"&&r[Symbol.iterator]||r["@@iterator"];if(t)return(t=t.call(r)).next.bind(t);if(Array.isArray(r)||(t=yk(r))||e&&r&&typeof r.length=="number"){t&&(r=t);var i=0;return function(){return i>=r.length?{done:!0}:{done:!1,value:r[i++]}}}throw new TypeError(`Invalid attempt to iterate non-iterable instance. +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function yk(r,e){if(!!r){if(typeof r=="string")return Np(r,e);var t=Object.prototype.toString.call(r).slice(8,-1);if(t==="Object"&&r.constructor&&(t=r.constructor.name),t==="Map"||t==="Set")return Array.from(r);if(t==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t))return Np(r,e)}}function Np(r,e){(e==null||e>r.length)&&(e=r.length);for(var t=0,i=new Array(e);t=n&&(this.indexes[a]=s-1);return this},t.removeAll=function(){for(var n=gk(this.nodes),s;!(s=n()).done;){var a=s.value;a.parent=void 0}return this.nodes=[],this},t.empty=function(){return this.removeAll()},t.insertAfter=function(n,s){s.parent=this;var a=this.index(n);this.nodes.splice(a+1,0,s),s.parent=this;var o;for(var l in this.indexes)o=this.indexes[l],a<=o&&(this.indexes[l]=o+1);return this},t.insertBefore=function(n,s){s.parent=this;var a=this.index(n);this.nodes.splice(a,0,s),s.parent=this;var o;for(var l in this.indexes)o=this.indexes[l],o<=a&&(this.indexes[l]=o+1);return this},t._findChildAtPosition=function(n,s){var a=void 0;return this.each(function(o){if(o.atPosition){var l=o.atPosition(n,s);if(l)return a=l,!1}else if(o.isAtPosition(n,s))return a=o,!1}),a},t.atPosition=function(n,s){if(this.isAtPosition(n,s))return this._findChildAtPosition(n,s)||this},t._inferEndPosition=function(){this.last&&this.last.source&&this.last.source.end&&(this.source=this.source||{},this.source.end=this.source.end||{},Object.assign(this.source.end,this.last.source.end))},t.each=function(n){this.lastEach||(this.lastEach=0),this.indexes||(this.indexes={}),this.lastEach++;var s=this.lastEach;if(this.indexes[s]=0,!!this.length){for(var a,o;this.indexes[s]{u();"use strict";ai.__esModule=!0;ai.default=void 0;var xk=Sk(Ln()),kk=Se();function Sk(r){return r&&r.__esModule?r:{default:r}}function jp(r,e){for(var t=0;t{u();"use strict";oi.__esModule=!0;oi.default=void 0;var Ek=Tk(Ln()),Ok=Se();function Tk(r){return r&&r.__esModule?r:{default:r}}function Rk(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,qa(r,e)}function qa(r,e){return qa=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},qa(r,e)}var Pk=function(r){Rk(e,r);function e(t){var i;return i=r.call(this,t)||this,i.type=Ok.SELECTOR,i}return e}(Ek.default);oi.default=Pk;Up.exports=oi.default});var Mn=x((_I,Vp)=>{u();"use strict";var Ik={},Dk=Ik.hasOwnProperty,qk=function(e,t){if(!e)return t;var i={};for(var n in t)i[n]=Dk.call(e,n)?e[n]:t[n];return i},$k=/[ -,\.\/:-@\[-\^`\{-~]/,Lk=/[ -,\.\/:-@\[\]\^`\{-~]/,Mk=/(^|\\+)?(\\[A-F0-9]{1,6})\x20(?![a-fA-F0-9\x20])/g,La=function r(e,t){t=qk(t,r.options),t.quotes!="single"&&t.quotes!="double"&&(t.quotes="single");for(var i=t.quotes=="double"?'"':"'",n=t.isIdentifier,s=e.charAt(0),a="",o=0,l=e.length;o126){if(f>=55296&&f<=56319&&o{u();"use strict";li.__esModule=!0;li.default=void 0;var Nk=Hp(Mn()),Bk=ii(),Fk=Hp(dt()),jk=Se();function Hp(r){return r&&r.__esModule?r:{default:r}}function Wp(r,e){for(var t=0;t{u();"use strict";ui.__esModule=!0;ui.default=void 0;var Hk=Gk(dt()),Wk=Se();function Gk(r){return r&&r.__esModule?r:{default:r}}function Qk(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,Ba(r,e)}function Ba(r,e){return Ba=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},Ba(r,e)}var Yk=function(r){Qk(e,r);function e(t){var i;return i=r.call(this,t)||this,i.type=Wk.COMMENT,i}return e}(Hk.default);ui.default=Yk;Qp.exports=ui.default});var za=x((fi,Yp)=>{u();"use strict";fi.__esModule=!0;fi.default=void 0;var Kk=Zk(dt()),Xk=Se();function Zk(r){return r&&r.__esModule?r:{default:r}}function Jk(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,ja(r,e)}function ja(r,e){return ja=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},ja(r,e)}var eS=function(r){Jk(e,r);function e(i){var n;return n=r.call(this,i)||this,n.type=Xk.ID,n}var t=e.prototype;return t.valueToString=function(){return"#"+r.prototype.valueToString.call(this)},e}(Kk.default);fi.default=eS;Yp.exports=fi.default});var Nn=x((ci,Zp)=>{u();"use strict";ci.__esModule=!0;ci.default=void 0;var tS=Kp(Mn()),rS=ii(),iS=Kp(dt());function Kp(r){return r&&r.__esModule?r:{default:r}}function Xp(r,e){for(var t=0;t{u();"use strict";pi.__esModule=!0;pi.default=void 0;var oS=uS(Nn()),lS=Se();function uS(r){return r&&r.__esModule?r:{default:r}}function fS(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,Va(r,e)}function Va(r,e){return Va=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},Va(r,e)}var cS=function(r){fS(e,r);function e(t){var i;return i=r.call(this,t)||this,i.type=lS.TAG,i}return e}(oS.default);pi.default=cS;Jp.exports=pi.default});var Ga=x((di,ed)=>{u();"use strict";di.__esModule=!0;di.default=void 0;var pS=hS(dt()),dS=Se();function hS(r){return r&&r.__esModule?r:{default:r}}function mS(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,Wa(r,e)}function Wa(r,e){return Wa=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},Wa(r,e)}var gS=function(r){mS(e,r);function e(t){var i;return i=r.call(this,t)||this,i.type=dS.STRING,i}return e}(pS.default);di.default=gS;ed.exports=di.default});var Ya=x((hi,td)=>{u();"use strict";hi.__esModule=!0;hi.default=void 0;var yS=wS(Ln()),bS=Se();function wS(r){return r&&r.__esModule?r:{default:r}}function vS(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,Qa(r,e)}function Qa(r,e){return Qa=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},Qa(r,e)}var xS=function(r){vS(e,r);function e(i){var n;return n=r.call(this,i)||this,n.type=bS.PSEUDO,n}var t=e.prototype;return t.toString=function(){var n=this.length?"("+this.map(String).join(",")+")":"";return[this.rawSpaceBefore,this.stringifyProperty("value"),n,this.rawSpaceAfter].join("")},e}(yS.default);hi.default=xS;td.exports=hi.default});var Bn={};Ge(Bn,{deprecate:()=>kS});function kS(r){return r}var Fn=P(()=>{u()});var id=x((EI,rd)=>{u();rd.exports=(Fn(),Bn).deprecate});var to=x(yi=>{u();"use strict";yi.__esModule=!0;yi.default=void 0;yi.unescapeValue=Ja;var mi=Xa(Mn()),SS=Xa(Pn()),AS=Xa(Nn()),CS=Se(),Ka;function Xa(r){return r&&r.__esModule?r:{default:r}}function nd(r,e){for(var t=0;t0&&!n.quoted&&o.before.length===0&&!(n.spaces.value&&n.spaces.value.after)&&(o.before=" "),sd(a,o)}))),s.push("]"),s.push(this.rawSpaceAfter),s.join("")},_S(e,[{key:"quoted",get:function(){var n=this.quoteMark;return n==="'"||n==='"'},set:function(n){RS()}},{key:"quoteMark",get:function(){return this._quoteMark},set:function(n){if(!this._constructed){this._quoteMark=n;return}this._quoteMark!==n&&(this._quoteMark=n,this._syncRawValue())}},{key:"qualifiedAttribute",get:function(){return this.qualifiedName(this.raws.attribute||this.attribute)}},{key:"insensitiveFlag",get:function(){return this.insensitive?"i":""}},{key:"value",get:function(){return this._value},set:function(n){if(this._constructed){var s=Ja(n),a=s.deprecatedUsage,o=s.unescaped,l=s.quoteMark;if(a&&TS(),o===this._value&&l===this._quoteMark)return;this._value=o,this._quoteMark=l,this._syncRawValue()}else this._value=n}},{key:"insensitive",get:function(){return this._insensitive},set:function(n){n||(this._insensitive=!1,this.raws&&(this.raws.insensitiveFlag==="I"||this.raws.insensitiveFlag==="i")&&(this.raws.insensitiveFlag=void 0)),this._insensitive=n}},{key:"attribute",get:function(){return this._attribute},set:function(n){this._handleEscapes("attribute",n),this._attribute=n}}]),e}(AS.default);yi.default=jn;jn.NO_QUOTE=null;jn.SINGLE_QUOTE="'";jn.DOUBLE_QUOTE='"';var eo=(Ka={"'":{quotes:"single",wrap:!0},'"':{quotes:"double",wrap:!0}},Ka[null]={isIdentifier:!0},Ka);function sd(r,e){return""+e.before+r+e.after}});var io=x((bi,ad)=>{u();"use strict";bi.__esModule=!0;bi.default=void 0;var DS=$S(Nn()),qS=Se();function $S(r){return r&&r.__esModule?r:{default:r}}function LS(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,ro(r,e)}function ro(r,e){return ro=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},ro(r,e)}var MS=function(r){LS(e,r);function e(t){var i;return i=r.call(this,t)||this,i.type=qS.UNIVERSAL,i.value="*",i}return e}(DS.default);bi.default=MS;ad.exports=bi.default});var so=x((wi,od)=>{u();"use strict";wi.__esModule=!0;wi.default=void 0;var NS=FS(dt()),BS=Se();function FS(r){return r&&r.__esModule?r:{default:r}}function jS(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,no(r,e)}function no(r,e){return no=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},no(r,e)}var zS=function(r){jS(e,r);function e(t){var i;return i=r.call(this,t)||this,i.type=BS.COMBINATOR,i}return e}(NS.default);wi.default=zS;od.exports=wi.default});var oo=x((vi,ld)=>{u();"use strict";vi.__esModule=!0;vi.default=void 0;var US=HS(dt()),VS=Se();function HS(r){return r&&r.__esModule?r:{default:r}}function WS(r,e){r.prototype=Object.create(e.prototype),r.prototype.constructor=r,ao(r,e)}function ao(r,e){return ao=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,n){return i.__proto__=n,i},ao(r,e)}var GS=function(r){WS(e,r);function e(t){var i;return i=r.call(this,t)||this,i.type=VS.NESTING,i.value="&",i}return e}(US.default);vi.default=GS;ld.exports=vi.default});var fd=x((zn,ud)=>{u();"use strict";zn.__esModule=!0;zn.default=QS;function QS(r){return r.sort(function(e,t){return e-t})}ud.exports=zn.default});var lo=x(M=>{u();"use strict";M.__esModule=!0;M.word=M.tilde=M.tab=M.str=M.space=M.slash=M.singleQuote=M.semicolon=M.plus=M.pipe=M.openSquare=M.openParenthesis=M.newline=M.greaterThan=M.feed=M.equals=M.doubleQuote=M.dollar=M.cr=M.comment=M.comma=M.combinator=M.colon=M.closeSquare=M.closeParenthesis=M.caret=M.bang=M.backslash=M.at=M.asterisk=M.ampersand=void 0;var YS=38;M.ampersand=YS;var KS=42;M.asterisk=KS;var XS=64;M.at=XS;var ZS=44;M.comma=ZS;var JS=58;M.colon=JS;var eA=59;M.semicolon=eA;var tA=40;M.openParenthesis=tA;var rA=41;M.closeParenthesis=rA;var iA=91;M.openSquare=iA;var nA=93;M.closeSquare=nA;var sA=36;M.dollar=sA;var aA=126;M.tilde=aA;var oA=94;M.caret=oA;var lA=43;M.plus=lA;var uA=61;M.equals=uA;var fA=124;M.pipe=fA;var cA=62;M.greaterThan=cA;var pA=32;M.space=pA;var cd=39;M.singleQuote=cd;var dA=34;M.doubleQuote=dA;var hA=47;M.slash=hA;var mA=33;M.bang=mA;var gA=92;M.backslash=gA;var yA=13;M.cr=yA;var bA=12;M.feed=bA;var wA=10;M.newline=wA;var vA=9;M.tab=vA;var xA=cd;M.str=xA;var kA=-1;M.comment=kA;var SA=-2;M.word=SA;var AA=-3;M.combinator=AA});var hd=x(xi=>{u();"use strict";xi.__esModule=!0;xi.FIELDS=void 0;xi.default=PA;var D=CA(lo()),nr,te;function pd(r){if(typeof WeakMap!="function")return null;var e=new WeakMap,t=new WeakMap;return(pd=function(n){return n?t:e})(r)}function CA(r,e){if(!e&&r&&r.__esModule)return r;if(r===null||typeof r!="object"&&typeof r!="function")return{default:r};var t=pd(e);if(t&&t.has(r))return t.get(r);var i={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in r)if(s!=="default"&&Object.prototype.hasOwnProperty.call(r,s)){var a=n?Object.getOwnPropertyDescriptor(r,s):null;a&&(a.get||a.set)?Object.defineProperty(i,s,a):i[s]=r[s]}return i.default=r,t&&t.set(r,i),i}var _A=(nr={},nr[D.tab]=!0,nr[D.newline]=!0,nr[D.cr]=!0,nr[D.feed]=!0,nr),EA=(te={},te[D.space]=!0,te[D.tab]=!0,te[D.newline]=!0,te[D.cr]=!0,te[D.feed]=!0,te[D.ampersand]=!0,te[D.asterisk]=!0,te[D.bang]=!0,te[D.comma]=!0,te[D.colon]=!0,te[D.semicolon]=!0,te[D.openParenthesis]=!0,te[D.closeParenthesis]=!0,te[D.openSquare]=!0,te[D.closeSquare]=!0,te[D.singleQuote]=!0,te[D.doubleQuote]=!0,te[D.plus]=!0,te[D.pipe]=!0,te[D.tilde]=!0,te[D.greaterThan]=!0,te[D.equals]=!0,te[D.dollar]=!0,te[D.caret]=!0,te[D.slash]=!0,te),uo={},dd="0123456789abcdefABCDEF";for(Un=0;Un0?(k=a+v,S=w-y[v].length):(k=a,S=s),T=D.comment,a=k,p=k,d=w-S):c===D.slash?(w=o,T=c,p=a,d=o-s,l=w+1):(w=OA(t,o),T=D.word,p=a,d=w-s),l=w+1;break}e.push([T,a,o-s,p,d,o,l]),S&&(s=S,S=null),o=l}return e}});var kd=x((ki,xd)=>{u();"use strict";ki.__esModule=!0;ki.default=void 0;var IA=je(Da()),fo=je($a()),DA=je(Na()),md=je(Fa()),qA=je(za()),$A=je(Ha()),co=je(Ga()),LA=je(Ya()),gd=Vn(to()),MA=je(io()),po=je(so()),NA=je(oo()),BA=je(fd()),O=Vn(hd()),q=Vn(lo()),FA=Vn(Se()),ue=ii(),Vt,ho;function yd(r){if(typeof WeakMap!="function")return null;var e=new WeakMap,t=new WeakMap;return(yd=function(n){return n?t:e})(r)}function Vn(r,e){if(!e&&r&&r.__esModule)return r;if(r===null||typeof r!="object"&&typeof r!="function")return{default:r};var t=yd(e);if(t&&t.has(r))return t.get(r);var i={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in r)if(s!=="default"&&Object.prototype.hasOwnProperty.call(r,s)){var a=n?Object.getOwnPropertyDescriptor(r,s):null;a&&(a.get||a.set)?Object.defineProperty(i,s,a):i[s]=r[s]}return i.default=r,t&&t.set(r,i),i}function je(r){return r&&r.__esModule?r:{default:r}}function bd(r,e){for(var t=0;t0){var a=this.current.last;if(a){var o=this.convertWhitespaceNodesToSpace(s),l=o.space,c=o.rawSpace;c!==void 0&&(a.rawSpaceAfter+=c),a.spaces.after+=l}else s.forEach(function(T){return i.newNode(T)})}return}var f=this.currToken,d=void 0;n>this.position&&(d=this.parseWhitespaceEquivalentTokens(n));var p;if(this.isNamedCombinator()?p=this.namedCombinator():this.currToken[O.FIELDS.TYPE]===q.combinator?(p=new po.default({value:this.content(),source:sr(this.currToken),sourceIndex:this.currToken[O.FIELDS.START_POS]}),this.position++):mo[this.currToken[O.FIELDS.TYPE]]||d||this.unexpected(),p){if(d){var h=this.convertWhitespaceNodesToSpace(d),b=h.space,v=h.rawSpace;p.spaces.before=b,p.rawSpaceBefore=v}}else{var y=this.convertWhitespaceNodesToSpace(d,!0),w=y.space,k=y.rawSpace;k||(k=w);var S={},E={spaces:{}};w.endsWith(" ")&&k.endsWith(" ")?(S.before=w.slice(0,w.length-1),E.spaces.before=k.slice(0,k.length-1)):w.startsWith(" ")&&k.startsWith(" ")?(S.after=w.slice(1),E.spaces.after=k.slice(1)):E.value=k,p=new po.default({value:" ",source:go(f,this.tokens[this.position-1]),sourceIndex:f[O.FIELDS.START_POS],spaces:S,raws:E})}return this.currToken&&this.currToken[O.FIELDS.TYPE]===q.space&&(p.spaces.after=this.optionalSpace(this.content()),this.position++),this.newNode(p)},e.comma=function(){if(this.position===this.tokens.length-1){this.root.trailingComma=!0,this.position++;return}this.current._inferEndPosition();var i=new fo.default({source:{start:wd(this.tokens[this.position+1])},sourceIndex:this.tokens[this.position+1][O.FIELDS.START_POS]});this.current.parent.append(i),this.current=i,this.position++},e.comment=function(){var i=this.currToken;this.newNode(new md.default({value:this.content(),source:sr(i),sourceIndex:i[O.FIELDS.START_POS]})),this.position++},e.error=function(i,n){throw this.root.error(i,n)},e.missingBackslash=function(){return this.error("Expected a backslash preceding the semicolon.",{index:this.currToken[O.FIELDS.START_POS]})},e.missingParenthesis=function(){return this.expected("opening parenthesis",this.currToken[O.FIELDS.START_POS])},e.missingSquareBracket=function(){return this.expected("opening square bracket",this.currToken[O.FIELDS.START_POS])},e.unexpected=function(){return this.error("Unexpected '"+this.content()+"'. Escaping special characters with \\ may help.",this.currToken[O.FIELDS.START_POS])},e.unexpectedPipe=function(){return this.error("Unexpected '|'.",this.currToken[O.FIELDS.START_POS])},e.namespace=function(){var i=this.prevToken&&this.content(this.prevToken)||!0;if(this.nextToken[O.FIELDS.TYPE]===q.word)return this.position++,this.word(i);if(this.nextToken[O.FIELDS.TYPE]===q.asterisk)return this.position++,this.universal(i);this.unexpectedPipe()},e.nesting=function(){if(this.nextToken){var i=this.content(this.nextToken);if(i==="|"){this.position++;return}}var n=this.currToken;this.newNode(new NA.default({value:this.content(),source:sr(n),sourceIndex:n[O.FIELDS.START_POS]})),this.position++},e.parentheses=function(){var i=this.current.last,n=1;if(this.position++,i&&i.type===FA.PSEUDO){var s=new fo.default({source:{start:wd(this.tokens[this.position])},sourceIndex:this.tokens[this.position][O.FIELDS.START_POS]}),a=this.current;for(i.append(s),this.current=s;this.position1&&i.nextToken&&i.nextToken[O.FIELDS.TYPE]===q.openParenthesis&&i.error("Misplaced parenthesis.",{index:i.nextToken[O.FIELDS.START_POS]})});else return this.expected(["pseudo-class","pseudo-element"],this.currToken[O.FIELDS.START_POS])},e.space=function(){var i=this.content();this.position===0||this.prevToken[O.FIELDS.TYPE]===q.comma||this.prevToken[O.FIELDS.TYPE]===q.openParenthesis||this.current.nodes.every(function(n){return n.type==="comment"})?(this.spaces=this.optionalSpace(i),this.position++):this.position===this.tokens.length-1||this.nextToken[O.FIELDS.TYPE]===q.comma||this.nextToken[O.FIELDS.TYPE]===q.closeParenthesis?(this.current.last.spaces.after=this.optionalSpace(i),this.position++):this.combinator()},e.string=function(){var i=this.currToken;this.newNode(new co.default({value:this.content(),source:sr(i),sourceIndex:i[O.FIELDS.START_POS]})),this.position++},e.universal=function(i){var n=this.nextToken;if(n&&this.content(n)==="|")return this.position++,this.namespace();var s=this.currToken;this.newNode(new MA.default({value:this.content(),source:sr(s),sourceIndex:s[O.FIELDS.START_POS]}),i),this.position++},e.splitWord=function(i,n){for(var s=this,a=this.nextToken,o=this.content();a&&~[q.dollar,q.caret,q.equals,q.word].indexOf(a[O.FIELDS.TYPE]);){this.position++;var l=this.content();if(o+=l,l.lastIndexOf("\\")===l.length-1){var c=this.nextToken;c&&c[O.FIELDS.TYPE]===q.space&&(o+=this.requiredSpace(this.content(c)),this.position++)}a=this.nextToken}var f=yo(o,".").filter(function(b){var v=o[b-1]==="\\",y=/^\d+\.\d+%$/.test(o);return!v&&!y}),d=yo(o,"#").filter(function(b){return o[b-1]!=="\\"}),p=yo(o,"#{");p.length&&(d=d.filter(function(b){return!~p.indexOf(b)}));var h=(0,BA.default)(UA([0].concat(f,d)));h.forEach(function(b,v){var y=h[v+1]||o.length,w=o.slice(b,y);if(v===0&&n)return n.call(s,w,h.length);var k,S=s.currToken,E=S[O.FIELDS.START_POS]+h[v],T=Ht(S[1],S[2]+b,S[3],S[2]+(y-1));if(~f.indexOf(b)){var B={value:w.slice(1),source:T,sourceIndex:E};k=new DA.default(ar(B,"value"))}else if(~d.indexOf(b)){var N={value:w.slice(1),source:T,sourceIndex:E};k=new qA.default(ar(N,"value"))}else{var R={value:w,source:T,sourceIndex:E};ar(R,"value"),k=new $A.default(R)}s.newNode(k,i),i=null}),this.position++},e.word=function(i){var n=this.nextToken;return n&&this.content(n)==="|"?(this.position++,this.namespace()):this.splitWord(i)},e.loop=function(){for(;this.position{u();"use strict";Si.__esModule=!0;Si.default=void 0;var HA=WA(kd());function WA(r){return r&&r.__esModule?r:{default:r}}var GA=function(){function r(t,i){this.func=t||function(){},this.funcRes=null,this.options=i}var e=r.prototype;return e._shouldUpdateSelector=function(i,n){n===void 0&&(n={});var s=Object.assign({},this.options,n);return s.updateSelector===!1?!1:typeof i!="string"},e._isLossy=function(i){i===void 0&&(i={});var n=Object.assign({},this.options,i);return n.lossless===!1},e._root=function(i,n){n===void 0&&(n={});var s=new HA.default(i,this._parseOptions(n));return s.root},e._parseOptions=function(i){return{lossy:this._isLossy(i)}},e._run=function(i,n){var s=this;return n===void 0&&(n={}),new Promise(function(a,o){try{var l=s._root(i,n);Promise.resolve(s.func(l)).then(function(c){var f=void 0;return s._shouldUpdateSelector(i,n)&&(f=l.toString(),i.selector=f),{transform:c,root:l,string:f}}).then(a,o)}catch(c){o(c);return}})},e._runSync=function(i,n){n===void 0&&(n={});var s=this._root(i,n),a=this.func(s);if(a&&typeof a.then=="function")throw new Error("Selector processor returned a promise to a synchronous call.");var o=void 0;return n.updateSelector&&typeof i!="string"&&(o=s.toString(),i.selector=o),{transform:a,root:s,string:o}},e.ast=function(i,n){return this._run(i,n).then(function(s){return s.root})},e.astSync=function(i,n){return this._runSync(i,n).root},e.transform=function(i,n){return this._run(i,n).then(function(s){return s.transform})},e.transformSync=function(i,n){return this._runSync(i,n).transform},e.process=function(i,n){return this._run(i,n).then(function(s){return s.string||s.root.toString()})},e.processSync=function(i,n){var s=this._runSync(i,n);return s.string||s.root.toString()},r}();Si.default=GA;Sd.exports=Si.default});var Cd=x(ne=>{u();"use strict";ne.__esModule=!0;ne.universal=ne.tag=ne.string=ne.selector=ne.root=ne.pseudo=ne.nesting=ne.id=ne.comment=ne.combinator=ne.className=ne.attribute=void 0;var QA=ze(to()),YA=ze(Na()),KA=ze(so()),XA=ze(Fa()),ZA=ze(za()),JA=ze(oo()),eC=ze(Ya()),tC=ze(Da()),rC=ze($a()),iC=ze(Ga()),nC=ze(Ha()),sC=ze(io());function ze(r){return r&&r.__esModule?r:{default:r}}var aC=function(e){return new QA.default(e)};ne.attribute=aC;var oC=function(e){return new YA.default(e)};ne.className=oC;var lC=function(e){return new KA.default(e)};ne.combinator=lC;var uC=function(e){return new XA.default(e)};ne.comment=uC;var fC=function(e){return new ZA.default(e)};ne.id=fC;var cC=function(e){return new JA.default(e)};ne.nesting=cC;var pC=function(e){return new eC.default(e)};ne.pseudo=pC;var dC=function(e){return new tC.default(e)};ne.root=dC;var hC=function(e){return new rC.default(e)};ne.selector=hC;var mC=function(e){return new iC.default(e)};ne.string=mC;var gC=function(e){return new nC.default(e)};ne.tag=gC;var yC=function(e){return new sC.default(e)};ne.universal=yC});var Td=x(Z=>{u();"use strict";Z.__esModule=!0;Z.isComment=Z.isCombinator=Z.isClassName=Z.isAttribute=void 0;Z.isContainer=TC;Z.isIdentifier=void 0;Z.isNamespace=RC;Z.isNesting=void 0;Z.isNode=bo;Z.isPseudo=void 0;Z.isPseudoClass=OC;Z.isPseudoElement=Od;Z.isUniversal=Z.isTag=Z.isString=Z.isSelector=Z.isRoot=void 0;var fe=Se(),Oe,bC=(Oe={},Oe[fe.ATTRIBUTE]=!0,Oe[fe.CLASS]=!0,Oe[fe.COMBINATOR]=!0,Oe[fe.COMMENT]=!0,Oe[fe.ID]=!0,Oe[fe.NESTING]=!0,Oe[fe.PSEUDO]=!0,Oe[fe.ROOT]=!0,Oe[fe.SELECTOR]=!0,Oe[fe.STRING]=!0,Oe[fe.TAG]=!0,Oe[fe.UNIVERSAL]=!0,Oe);function bo(r){return typeof r=="object"&&bC[r.type]}function Ue(r,e){return bo(e)&&e.type===r}var _d=Ue.bind(null,fe.ATTRIBUTE);Z.isAttribute=_d;var wC=Ue.bind(null,fe.CLASS);Z.isClassName=wC;var vC=Ue.bind(null,fe.COMBINATOR);Z.isCombinator=vC;var xC=Ue.bind(null,fe.COMMENT);Z.isComment=xC;var kC=Ue.bind(null,fe.ID);Z.isIdentifier=kC;var SC=Ue.bind(null,fe.NESTING);Z.isNesting=SC;var wo=Ue.bind(null,fe.PSEUDO);Z.isPseudo=wo;var AC=Ue.bind(null,fe.ROOT);Z.isRoot=AC;var CC=Ue.bind(null,fe.SELECTOR);Z.isSelector=CC;var _C=Ue.bind(null,fe.STRING);Z.isString=_C;var Ed=Ue.bind(null,fe.TAG);Z.isTag=Ed;var EC=Ue.bind(null,fe.UNIVERSAL);Z.isUniversal=EC;function Od(r){return wo(r)&&r.value&&(r.value.startsWith("::")||r.value.toLowerCase()===":before"||r.value.toLowerCase()===":after"||r.value.toLowerCase()===":first-letter"||r.value.toLowerCase()===":first-line")}function OC(r){return wo(r)&&!Od(r)}function TC(r){return!!(bo(r)&&r.walk)}function RC(r){return _d(r)||Ed(r)}});var Rd=x(Ke=>{u();"use strict";Ke.__esModule=!0;var vo=Se();Object.keys(vo).forEach(function(r){r==="default"||r==="__esModule"||r in Ke&&Ke[r]===vo[r]||(Ke[r]=vo[r])});var xo=Cd();Object.keys(xo).forEach(function(r){r==="default"||r==="__esModule"||r in Ke&&Ke[r]===xo[r]||(Ke[r]=xo[r])});var ko=Td();Object.keys(ko).forEach(function(r){r==="default"||r==="__esModule"||r in Ke&&Ke[r]===ko[r]||(Ke[r]=ko[r])})});var it=x((Ai,Id)=>{u();"use strict";Ai.__esModule=!0;Ai.default=void 0;var PC=qC(Ad()),IC=DC(Rd());function Pd(r){if(typeof WeakMap!="function")return null;var e=new WeakMap,t=new WeakMap;return(Pd=function(n){return n?t:e})(r)}function DC(r,e){if(!e&&r&&r.__esModule)return r;if(r===null||typeof r!="object"&&typeof r!="function")return{default:r};var t=Pd(e);if(t&&t.has(r))return t.get(r);var i={},n=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var s in r)if(s!=="default"&&Object.prototype.hasOwnProperty.call(r,s)){var a=n?Object.getOwnPropertyDescriptor(r,s):null;a&&(a.get||a.set)?Object.defineProperty(i,s,a):i[s]=r[s]}return i.default=r,t&&t.set(r,i),i}function qC(r){return r&&r.__esModule?r:{default:r}}var So=function(e){return new PC.default(e)};Object.assign(So,IC);delete So.__esModule;var $C=So;Ai.default=$C;Id.exports=Ai.default});function mt(r){return["fontSize","outline"].includes(r)?e=>(typeof e=="function"&&(e=e({})),Array.isArray(e)&&(e=e[0]),e):r==="fontFamily"?e=>{typeof e=="function"&&(e=e({}));let t=Array.isArray(e)&&ke(e[1])?e[0]:e;return Array.isArray(t)?t.join(", "):t}:["boxShadow","transitionProperty","transitionDuration","transitionDelay","transitionTimingFunction","backgroundImage","backgroundSize","backgroundColor","cursor","animation"].includes(r)?e=>(typeof e=="function"&&(e=e({})),Array.isArray(e)&&(e=e.join(", ")),e):["gridTemplateColumns","gridTemplateRows","objectPosition"].includes(r)?e=>(typeof e=="function"&&(e=e({})),typeof e=="string"&&(e=ee.list.comma(e).join(" ")),e):(e,t={})=>(typeof e=="function"&&(e=e(t)),e)}var Ci=P(()=>{u();Ot();Kt()});var Bd=x((MI,Oo)=>{u();var{AtRule:LC,Rule:Dd}=$e(),qd=it();function Ao(r,e){let t;try{qd(i=>{t=i}).processSync(r)}catch(i){throw r.includes(":")?e?e.error("Missed semicolon"):i:e?e.error(i.message):i}return t.at(0)}function $d(r,e){let t=!1;return r.each(i=>{if(i.type==="nesting"){let n=e.clone({});i.value!=="&"?i.replaceWith(Ao(i.value.replace("&",n.toString()))):i.replaceWith(n),t=!0}else"nodes"in i&&i.nodes&&$d(i,e)&&(t=!0)}),t}function Ld(r,e){let t=[];return r.selectors.forEach(i=>{let n=Ao(i,r);e.selectors.forEach(s=>{if(!s)return;let a=Ao(s,e);$d(a,n)||(a.prepend(qd.combinator({value:" "})),a.prepend(n.clone({}))),t.push(a.toString())})}),t}function Hn(r,e){let t=r.prev();for(e.after(r);t&&t.type==="comment";){let i=t.prev();e.after(t),t=i}return r}function MC(r){return function e(t,i,n,s=n){let a=[];if(i.each(o=>{o.type==="rule"&&n?s&&(o.selectors=Ld(t,o)):o.type==="atrule"&&o.nodes?r[o.name]?e(t,o,s):i[_o]!==!1&&a.push(o):a.push(o)}),n&&a.length){let o=t.clone({nodes:[]});for(let l of a)o.append(l);i.prepend(o)}}}function Co(r,e,t){let i=new Dd({nodes:[],selector:r});return i.append(e),t.after(i),i}function Md(r,e){let t={};for(let i of r)t[i]=!0;if(e)for(let i of e)t[i.replace(/^@/,"")]=!0;return t}function NC(r){r=r.trim();let e=r.match(/^\((.*)\)$/);if(!e)return{selector:r,type:"basic"};let t=e[1].match(/^(with(?:out)?):(.+)$/);if(t){let i=t[1]==="with",n=Object.fromEntries(t[2].trim().split(/\s+/).map(a=>[a,!0]));if(i&&n.all)return{type:"noop"};let s=a=>!!n[a];return n.all?s=()=>!0:i&&(s=a=>a==="all"?!1:!n[a]),{escapes:s,type:"withrules"}}return{type:"unknown"}}function BC(r){let e=[],t=r.parent;for(;t&&t instanceof LC;)e.push(t),t=t.parent;return e}function FC(r){let e=r[Nd];if(!e)r.after(r.nodes);else{let t=r.nodes,i,n=-1,s,a,o,l=BC(r);if(l.forEach((c,f)=>{if(e(c.name))i=c,n=f,a=o;else{let d=o;o=c.clone({nodes:[]}),d&&o.append(d),s=s||o}}),i?a?(s.append(t),i.after(a)):i.after(t):r.after(t),r.next()&&i){let c;l.slice(0,n+1).forEach((f,d,p)=>{let h=c;c=f.clone({nodes:[]}),h&&c.append(h);let b=[],y=(p[d-1]||r).next();for(;y;)b.push(y),y=y.next();c.append(b)}),c&&(a||t[t.length-1]).after(c)}}r.remove()}var _o=Symbol("rootRuleMergeSel"),Nd=Symbol("rootRuleEscapes");function jC(r){let{params:e}=r,{escapes:t,selector:i,type:n}=NC(e);if(n==="unknown")throw r.error(`Unknown @${r.name} parameter ${JSON.stringify(e)}`);if(n==="basic"&&i){let s=new Dd({nodes:r.nodes,selector:i});r.removeAll(),r.append(s)}r[Nd]=t,r[_o]=t?!t("all"):n==="noop"}var Eo=Symbol("hasRootRule");Oo.exports=(r={})=>{let e=Md(["media","supports","layer","container","starting-style"],r.bubble),t=MC(e),i=Md(["document","font-face","keyframes","-webkit-keyframes","-moz-keyframes"],r.unwrap),n=(r.rootRuleName||"at-root").replace(/^@/,""),s=r.preserveEmpty;return{Once(a){a.walkAtRules(n,o=>{jC(o),a[Eo]=!0})},postcssPlugin:"postcss-nested",RootExit(a){a[Eo]&&(a.walkAtRules(n,FC),a[Eo]=!1)},Rule(a){let o=!1,l=a,c=!1,f=[];a.each(d=>{d.type==="rule"?(f.length&&(l=Co(a.selector,f,l),f=[]),c=!0,o=!0,d.selectors=Ld(a,d),l=Hn(d,l)):d.type==="atrule"?(f.length&&(l=Co(a.selector,f,l),f=[]),d.name===n?(o=!0,t(a,d,!0,d[_o]),l=Hn(d,l)):e[d.name]?(c=!0,o=!0,t(a,d,!0),l=Hn(d,l)):i[d.name]?(c=!0,o=!0,t(a,d,!1),l=Hn(d,l)):c&&f.push(d)):d.type==="decl"&&c&&f.push(d)}),f.length&&(l=Co(a.selector,f,l)),o&&s!==!0&&(a.raws.semicolon=!0,a.nodes.length===0&&a.remove())}}};Oo.exports.postcss=!0});var Ud=x((NI,zd)=>{u();"use strict";var Fd=/-(\w|$)/g,jd=(r,e)=>e.toUpperCase(),zC=r=>(r=r.toLowerCase(),r==="float"?"cssFloat":r.startsWith("-ms-")?r.substr(1).replace(Fd,jd):r.replace(Fd,jd));zd.exports=zC});var Po=x((BI,Vd)=>{u();var UC=Ud(),VC={boxFlex:!0,boxFlexGroup:!0,columnCount:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,strokeDashoffset:!0,strokeOpacity:!0,strokeWidth:!0};function To(r){return typeof r.nodes=="undefined"?!0:Ro(r)}function Ro(r){let e,t={};return r.each(i=>{if(i.type==="atrule")e="@"+i.name,i.params&&(e+=" "+i.params),typeof t[e]=="undefined"?t[e]=To(i):Array.isArray(t[e])?t[e].push(To(i)):t[e]=[t[e],To(i)];else if(i.type==="rule"){let n=Ro(i);if(t[i.selector])for(let s in n)t[i.selector][s]=n[s];else t[i.selector]=n}else if(i.type==="decl"){i.prop[0]==="-"&&i.prop[1]==="-"||i.parent&&i.parent.selector===":export"?e=i.prop:e=UC(i.prop);let n=i.value;!isNaN(i.value)&&VC[e]&&(n=parseFloat(i.value)),i.important&&(n+=" !important"),typeof t[e]=="undefined"?t[e]=n:Array.isArray(t[e])?t[e].push(n):t[e]=[t[e],n]}}),t}Vd.exports=Ro});var Wn=x((FI,Qd)=>{u();var _i=$e(),Hd=/\s*!important\s*$/i,HC={"box-flex":!0,"box-flex-group":!0,"column-count":!0,flex:!0,"flex-grow":!0,"flex-positive":!0,"flex-shrink":!0,"flex-negative":!0,"font-weight":!0,"line-clamp":!0,"line-height":!0,opacity:!0,order:!0,orphans:!0,"tab-size":!0,widows:!0,"z-index":!0,zoom:!0,"fill-opacity":!0,"stroke-dashoffset":!0,"stroke-opacity":!0,"stroke-width":!0};function WC(r){return r.replace(/([A-Z])/g,"-$1").replace(/^ms-/,"-ms-").toLowerCase()}function Wd(r,e,t){t===!1||t===null||(e.startsWith("--")||(e=WC(e)),typeof t=="number"&&(t===0||HC[e]?t=t.toString():t+="px"),e==="css-float"&&(e="float"),Hd.test(t)?(t=t.replace(Hd,""),r.push(_i.decl({prop:e,value:t,important:!0}))):r.push(_i.decl({prop:e,value:t})))}function Gd(r,e,t){let i=_i.atRule({name:e[1],params:e[3]||""});typeof t=="object"&&(i.nodes=[],Io(t,i)),r.push(i)}function Io(r,e){let t,i,n;for(t in r)if(i=r[t],!(i===null||typeof i=="undefined"))if(t[0]==="@"){let s=t.match(/@(\S+)(\s+([\W\w]*)\s*)?/);if(Array.isArray(i))for(let a of i)Gd(e,s,a);else Gd(e,s,i)}else if(Array.isArray(i))for(let s of i)Wd(e,t,s);else typeof i=="object"?(n=_i.rule({selector:t}),Io(i,n),e.push(n)):Wd(e,t,i)}Qd.exports=function(r){let e=_i.root();return Io(r,e),e}});var Do=x((jI,Yd)=>{u();var GC=Po();Yd.exports=function(e){return console&&console.warn&&e.warnings().forEach(t=>{let i=t.plugin||"PostCSS";console.warn(i+": "+t.text)}),GC(e.root)}});var Xd=x((zI,Kd)=>{u();var QC=$e(),YC=Do(),KC=Wn();Kd.exports=function(e){let t=QC(e);return async i=>{let n=await t.process(i,{parser:KC,from:void 0});return YC(n)}}});var Jd=x((UI,Zd)=>{u();var XC=$e(),ZC=Do(),JC=Wn();Zd.exports=function(r){let e=XC(r);return t=>{let i=e.process(t,{parser:JC,from:void 0});return ZC(i)}}});var th=x((VI,eh)=>{u();var e_=Po(),t_=Wn(),r_=Xd(),i_=Jd();eh.exports={objectify:e_,parse:t_,async:r_,sync:i_}});var or,rh,HI,WI,GI,QI,ih=P(()=>{u();or=pe(th()),rh=or.default,HI=or.default.objectify,WI=or.default.parse,GI=or.default.async,QI=or.default.sync});function lr(r){return Array.isArray(r)?r.flatMap(e=>ee([(0,nh.default)({bubble:["screen"]})]).process(e,{parser:rh}).root.nodes):lr([r])}var nh,qo=P(()=>{u();Ot();nh=pe(Bd());ih()});function ur(r,e,t=!1){if(r==="")return e;let i=typeof e=="string"?(0,sh.default)().astSync(e):e;return i.walkClasses(n=>{let s=n.value,a=t&&s.startsWith("-");n.value=a?`-${r}${s.slice(1)}`:`${r}${s}`}),typeof e=="string"?i.toString():i}var sh,Gn=P(()=>{u();sh=pe(it())});function Te(r){let e=ah.default.className();return e.value=r,jt(e?.raws?.value??e.value)}var ah,fr=P(()=>{u();ah=pe(it());Zi()});function $o(r){return jt(`.${Te(r)}`)}function Qn(r,e){return $o(Ei(r,e))}function Ei(r,e){return e==="DEFAULT"?r:e==="-"||e==="-DEFAULT"?`-${r}`:e.startsWith("-")?`-${r}${e}`:e.startsWith("/")?`${r}${e}`:`${r}-${e}`}var Lo=P(()=>{u();fr();Zi()});function L(r,e=[[r,[r]]],{filterDefault:t=!1,...i}={}){let n=mt(r);return function({matchUtilities:s,theme:a}){for(let o of e){let l=Array.isArray(o[0])?o:[o];s(l.reduce((c,[f,d])=>Object.assign(c,{[f]:p=>d.reduce((h,b)=>Array.isArray(b)?Object.assign(h,{[b[0]]:b[1]}):Object.assign(h,{[b]:n(p)}),{})}),{}),{...i,values:t?Object.fromEntries(Object.entries(a(r)??{}).filter(([c])=>c!=="DEFAULT")):a(r)})}}}var oh=P(()=>{u();Ci()});function Tt(r){return r=Array.isArray(r)?r:[r],r.map(e=>{let t=e.values.map(i=>i.raw!==void 0?i.raw:[i.min&&`(min-width: ${i.min})`,i.max&&`(max-width: ${i.max})`].filter(Boolean).join(" and "));return e.not?`not all and ${t}`:t}).join(", ")}var Yn=P(()=>{u()});function Mo(r){return r.split(f_).map(t=>{let i=t.trim(),n={value:i},s=i.split(c_),a=new Set;for(let o of s)!a.has("DIRECTIONS")&&n_.has(o)?(n.direction=o,a.add("DIRECTIONS")):!a.has("PLAY_STATES")&&s_.has(o)?(n.playState=o,a.add("PLAY_STATES")):!a.has("FILL_MODES")&&a_.has(o)?(n.fillMode=o,a.add("FILL_MODES")):!a.has("ITERATION_COUNTS")&&(o_.has(o)||p_.test(o))?(n.iterationCount=o,a.add("ITERATION_COUNTS")):!a.has("TIMING_FUNCTION")&&l_.has(o)||!a.has("TIMING_FUNCTION")&&u_.some(l=>o.startsWith(`${l}(`))?(n.timingFunction=o,a.add("TIMING_FUNCTION")):!a.has("DURATION")&&lh.test(o)?(n.duration=o,a.add("DURATION")):!a.has("DELAY")&&lh.test(o)?(n.delay=o,a.add("DELAY")):a.has("NAME")?(n.unknown||(n.unknown=[]),n.unknown.push(o)):(n.name=o,a.add("NAME"));return n})}var n_,s_,a_,o_,l_,u_,f_,c_,lh,p_,uh=P(()=>{u();n_=new Set(["normal","reverse","alternate","alternate-reverse"]),s_=new Set(["running","paused"]),a_=new Set(["none","forwards","backwards","both"]),o_=new Set(["infinite"]),l_=new Set(["linear","ease","ease-in","ease-out","ease-in-out","step-start","step-end"]),u_=["cubic-bezier","steps"],f_=/\,(?![^(]*\))/g,c_=/\ +(?![^(]*\))/g,lh=/^(-?[\d.]+m?s)$/,p_=/^(\d+)$/});var fh,xe,ch=P(()=>{u();fh=r=>Object.assign({},...Object.entries(r??{}).flatMap(([e,t])=>typeof t=="object"?Object.entries(fh(t)).map(([i,n])=>({[e+(i==="DEFAULT"?"":`-${i}`)]:n})):[{[`${e}`]:t}])),xe=fh});var dh,ph=P(()=>{dh="3.4.17"});function Rt(r,e=!0){return Array.isArray(r)?r.map(t=>{if(e&&Array.isArray(t))throw new Error("The tuple syntax is not supported for `screens`.");if(typeof t=="string")return{name:t.toString(),not:!1,values:[{min:t,max:void 0}]};let[i,n]=t;return i=i.toString(),typeof n=="string"?{name:i,not:!1,values:[{min:n,max:void 0}]}:Array.isArray(n)?{name:i,not:!1,values:n.map(s=>mh(s))}:{name:i,not:!1,values:[mh(n)]}}):Rt(Object.entries(r??{}),!1)}function Kn(r){return r.values.length!==1?{result:!1,reason:"multiple-values"}:r.values[0].raw!==void 0?{result:!1,reason:"raw-values"}:r.values[0].min!==void 0&&r.values[0].max!==void 0?{result:!1,reason:"min-and-max"}:{result:!0,reason:null}}function hh(r,e,t){let i=Xn(e,r),n=Xn(t,r),s=Kn(i),a=Kn(n);if(s.reason==="multiple-values"||a.reason==="multiple-values")throw new Error("Attempted to sort a screen with multiple values. This should never happen. Please open a bug report.");if(s.reason==="raw-values"||a.reason==="raw-values")throw new Error("Attempted to sort a screen with raw values. This should never happen. Please open a bug report.");if(s.reason==="min-and-max"||a.reason==="min-and-max")throw new Error("Attempted to sort a screen with both min and max values. This should never happen. Please open a bug report.");let{min:o,max:l}=i.values[0],{min:c,max:f}=n.values[0];e.not&&([o,l]=[l,o]),t.not&&([c,f]=[f,c]),o=o===void 0?o:parseFloat(o),l=l===void 0?l:parseFloat(l),c=c===void 0?c:parseFloat(c),f=f===void 0?f:parseFloat(f);let[d,p]=r==="min"?[o,c]:[f,l];return d-p}function Xn(r,e){return typeof r=="object"?r:{name:"arbitrary-screen",values:[{[e]:r}]}}function mh({"min-width":r,min:e=r,max:t,raw:i}={}){return{min:e,max:t,raw:i}}var Zn=P(()=>{u()});function Jn(r,e){r.walkDecls(t=>{if(e.includes(t.prop)){t.remove();return}for(let i of e)t.value.includes(`/ var(${i})`)?t.value=t.value.replace(`/ var(${i})`,""):t.value.includes(`/ var(${i}, 1)`)&&(t.value=t.value.replace(`/ var(${i}, 1)`,""))})}var gh=P(()=>{u()});var se,Xe,nt,ge,yh,bh=P(()=>{u();ft();et();Ot();oh();Yn();fr();uh();ch();Lr();ra();Kt();Ci();ph();Be();Zn();Ys();gh();ct();Br();Oi();se={childVariant:({addVariant:r})=>{r("*","& > *")},pseudoElementVariants:({addVariant:r})=>{r("first-letter","&::first-letter"),r("first-line","&::first-line"),r("marker",[({container:e})=>(Jn(e,["--tw-text-opacity"]),"& *::marker"),({container:e})=>(Jn(e,["--tw-text-opacity"]),"&::marker")]),r("selection",["& *::selection","&::selection"]),r("file","&::file-selector-button"),r("placeholder","&::placeholder"),r("backdrop","&::backdrop"),r("before",({container:e})=>(e.walkRules(t=>{let i=!1;t.walkDecls("content",()=>{i=!0}),i||t.prepend(ee.decl({prop:"content",value:"var(--tw-content)"}))}),"&::before")),r("after",({container:e})=>(e.walkRules(t=>{let i=!1;t.walkDecls("content",()=>{i=!0}),i||t.prepend(ee.decl({prop:"content",value:"var(--tw-content)"}))}),"&::after"))},pseudoClassVariants:({addVariant:r,matchVariant:e,config:t,prefix:i})=>{let n=[["first","&:first-child"],["last","&:last-child"],["only","&:only-child"],["odd","&:nth-child(odd)"],["even","&:nth-child(even)"],"first-of-type","last-of-type","only-of-type",["visited",({container:a})=>(Jn(a,["--tw-text-opacity","--tw-border-opacity","--tw-bg-opacity"]),"&:visited")],"target",["open","&[open]"],"default","checked","indeterminate","placeholder-shown","autofill","optional","required","valid","invalid","in-range","out-of-range","read-only","empty","focus-within",["hover",we(t(),"hoverOnlyWhenSupported")?"@media (hover: hover) and (pointer: fine) { &:hover }":"&:hover"],"focus","focus-visible","active","enabled","disabled"].map(a=>Array.isArray(a)?a:[a,`&:${a}`]);for(let[a,o]of n)r(a,l=>typeof o=="function"?o(l):o);let s={group:(a,{modifier:o})=>o?[`:merge(${i(".group")}\\/${Te(o)})`," &"]:[`:merge(${i(".group")})`," &"],peer:(a,{modifier:o})=>o?[`:merge(${i(".peer")}\\/${Te(o)})`," ~ &"]:[`:merge(${i(".peer")})`," ~ &"]};for(let[a,o]of Object.entries(s))e(a,(l="",c)=>{let f=K(typeof l=="function"?l(c):l);f.includes("&")||(f="&"+f);let[d,p]=o("",c),h=null,b=null,v=0;for(let y=0;y{r("ltr",'&:where([dir="ltr"], [dir="ltr"] *)'),r("rtl",'&:where([dir="rtl"], [dir="rtl"] *)')},reducedMotionVariants:({addVariant:r})=>{r("motion-safe","@media (prefers-reduced-motion: no-preference)"),r("motion-reduce","@media (prefers-reduced-motion: reduce)")},darkVariants:({config:r,addVariant:e})=>{let[t,i=".dark"]=[].concat(r("darkMode","media"));if(t===!1&&(t="media",G.warn("darkmode-false",["The `darkMode` option in your Tailwind CSS configuration is set to `false`, which now behaves the same as `media`.","Change `darkMode` to `media` or remove it entirely.","https://tailwindcss.com/docs/upgrade-guide#remove-dark-mode-configuration"])),t==="variant"){let n;if(Array.isArray(i)||typeof i=="function"?n=i:typeof i=="string"&&(n=[i]),Array.isArray(n))for(let s of n)s===".dark"?(t=!1,G.warn("darkmode-variant-without-selector",["When using `variant` for `darkMode`, you must provide a selector.",'Example: `darkMode: ["variant", ".your-selector &"]`'])):s.includes("&")||(t=!1,G.warn("darkmode-variant-without-ampersand",["When using `variant` for `darkMode`, your selector must contain `&`.",'Example `darkMode: ["variant", ".your-selector &"]`']));i=n}t==="selector"?e("dark",`&:where(${i}, ${i} *)`):t==="media"?e("dark","@media (prefers-color-scheme: dark)"):t==="variant"?e("dark",i):t==="class"&&e("dark",`&:is(${i} *)`)},printVariant:({addVariant:r})=>{r("print","@media print")},screenVariants:({theme:r,addVariant:e,matchVariant:t})=>{let i=r("screens")??{},n=Object.values(i).every(w=>typeof w=="string"),s=Rt(r("screens")),a=new Set([]);function o(w){return w.match(/(\D+)$/)?.[1]??"(none)"}function l(w){w!==void 0&&a.add(o(w))}function c(w){return l(w),a.size===1}for(let w of s)for(let k of w.values)l(k.min),l(k.max);let f=a.size<=1;function d(w){return Object.fromEntries(s.filter(k=>Kn(k).result).map(k=>{let{min:S,max:E}=k.values[0];if(w==="min"&&S!==void 0)return k;if(w==="min"&&E!==void 0)return{...k,not:!k.not};if(w==="max"&&E!==void 0)return k;if(w==="max"&&S!==void 0)return{...k,not:!k.not}}).map(k=>[k.name,k]))}function p(w){return(k,S)=>hh(w,k.value,S.value)}let h=p("max"),b=p("min");function v(w){return k=>{if(n)if(f){if(typeof k=="string"&&!c(k))return G.warn("minmax-have-mixed-units",["The `min-*` and `max-*` variants are not supported with a `screens` configuration containing mixed units."]),[]}else return G.warn("mixed-screen-units",["The `min-*` and `max-*` variants are not supported with a `screens` configuration containing mixed units."]),[];else return G.warn("complex-screen-config",["The `min-*` and `max-*` variants are not supported with a `screens` configuration containing objects."]),[];return[`@media ${Tt(Xn(k,w))}`]}}t("max",v("max"),{sort:h,values:n?d("max"):{}});let y="min-screens";for(let w of s)e(w.name,`@media ${Tt(w)}`,{id:y,sort:n&&f?b:void 0,value:w});t("min",v("min"),{id:y,sort:b})},supportsVariants:({matchVariant:r,theme:e})=>{r("supports",(t="")=>{let i=K(t),n=/^\w*\s*\(/.test(i);return i=n?i.replace(/\b(and|or|not)\b/g," $1 "):i,n?`@supports ${i}`:(i.includes(":")||(i=`${i}: var(--tw)`),i.startsWith("(")&&i.endsWith(")")||(i=`(${i})`),`@supports ${i}`)},{values:e("supports")??{}})},hasVariants:({matchVariant:r,prefix:e})=>{r("has",t=>`&:has(${K(t)})`,{values:{},[Pt]:{respectPrefix:!1}}),r("group-has",(t,{modifier:i})=>i?`:merge(${e(".group")}\\/${i}):has(${K(t)}) &`:`:merge(${e(".group")}):has(${K(t)}) &`,{values:{},[Pt]:{respectPrefix:!1}}),r("peer-has",(t,{modifier:i})=>i?`:merge(${e(".peer")}\\/${i}):has(${K(t)}) ~ &`:`:merge(${e(".peer")}):has(${K(t)}) ~ &`,{values:{},[Pt]:{respectPrefix:!1}})},ariaVariants:({matchVariant:r,theme:e})=>{r("aria",t=>`&[aria-${Ye(K(t))}]`,{values:e("aria")??{}}),r("group-aria",(t,{modifier:i})=>i?`:merge(.group\\/${i})[aria-${Ye(K(t))}] &`:`:merge(.group)[aria-${Ye(K(t))}] &`,{values:e("aria")??{}}),r("peer-aria",(t,{modifier:i})=>i?`:merge(.peer\\/${i})[aria-${Ye(K(t))}] ~ &`:`:merge(.peer)[aria-${Ye(K(t))}] ~ &`,{values:e("aria")??{}})},dataVariants:({matchVariant:r,theme:e})=>{r("data",t=>`&[data-${Ye(K(t))}]`,{values:e("data")??{}}),r("group-data",(t,{modifier:i})=>i?`:merge(.group\\/${i})[data-${Ye(K(t))}] &`:`:merge(.group)[data-${Ye(K(t))}] &`,{values:e("data")??{}}),r("peer-data",(t,{modifier:i})=>i?`:merge(.peer\\/${i})[data-${Ye(K(t))}] ~ &`:`:merge(.peer)[data-${Ye(K(t))}] ~ &`,{values:e("data")??{}})},orientationVariants:({addVariant:r})=>{r("portrait","@media (orientation: portrait)"),r("landscape","@media (orientation: landscape)")},prefersContrastVariants:({addVariant:r})=>{r("contrast-more","@media (prefers-contrast: more)"),r("contrast-less","@media (prefers-contrast: less)")},forcedColorsVariants:({addVariant:r})=>{r("forced-colors","@media (forced-colors: active)")}},Xe=["translate(var(--tw-translate-x), var(--tw-translate-y))","rotate(var(--tw-rotate))","skewX(var(--tw-skew-x))","skewY(var(--tw-skew-y))","scaleX(var(--tw-scale-x))","scaleY(var(--tw-scale-y))"].join(" "),nt=["var(--tw-blur)","var(--tw-brightness)","var(--tw-contrast)","var(--tw-grayscale)","var(--tw-hue-rotate)","var(--tw-invert)","var(--tw-saturate)","var(--tw-sepia)","var(--tw-drop-shadow)"].join(" "),ge=["var(--tw-backdrop-blur)","var(--tw-backdrop-brightness)","var(--tw-backdrop-contrast)","var(--tw-backdrop-grayscale)","var(--tw-backdrop-hue-rotate)","var(--tw-backdrop-invert)","var(--tw-backdrop-opacity)","var(--tw-backdrop-saturate)","var(--tw-backdrop-sepia)"].join(" "),yh={preflight:({addBase:r})=>{let e=ee.parse(`*,::after,::before{box-sizing:border-box;border-width:0;border-style:solid;border-color:theme('borderColor.DEFAULT', currentColor)}::after,::before{--tw-content:''}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:theme('fontFamily.sans', ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:theme('fontFamily.sans[1].fontFeatureSettings', normal);font-variation-settings:theme('fontFamily.sans[1].fontVariationSettings', normal);-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:theme('fontFamily.mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:theme('fontFamily.mono[1].fontFeatureSettings', normal);font-variation-settings:theme('fontFamily.mono[1].fontVariationSettings', normal);font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:theme('colors.gray.4', #9ca3af)}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}`);r([ee.comment({text:`! tailwindcss v${dh} | MIT License | https://tailwindcss.com`}),...e.nodes])},container:(()=>{function r(t=[]){return t.flatMap(i=>i.values.map(n=>n.min)).filter(i=>i!==void 0)}function e(t,i,n){if(typeof n=="undefined")return[];if(!(typeof n=="object"&&n!==null))return[{screen:"DEFAULT",minWidth:0,padding:n}];let s=[];n.DEFAULT&&s.push({screen:"DEFAULT",minWidth:0,padding:n.DEFAULT});for(let a of t)for(let o of i)for(let{min:l}of o.values)l===a&&s.push({minWidth:a,padding:n[o.name]});return s}return function({addComponents:t,theme:i}){let n=Rt(i("container.screens",i("screens"))),s=r(n),a=e(s,n,i("container.padding")),o=c=>{let f=a.find(d=>d.minWidth===c);return f?{paddingRight:f.padding,paddingLeft:f.padding}:{}},l=Array.from(new Set(s.slice().sort((c,f)=>parseInt(c)-parseInt(f)))).map(c=>({[`@media (min-width: ${c})`]:{".container":{"max-width":c,...o(c)}}}));t([{".container":Object.assign({width:"100%"},i("container.center",!1)?{marginRight:"auto",marginLeft:"auto"}:{},o(0))},...l])}})(),accessibility:({addUtilities:r})=>{r({".sr-only":{position:"absolute",width:"1px",height:"1px",padding:"0",margin:"-1px",overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",borderWidth:"0"},".not-sr-only":{position:"static",width:"auto",height:"auto",padding:"0",margin:"0",overflow:"visible",clip:"auto",whiteSpace:"normal"}})},pointerEvents:({addUtilities:r})=>{r({".pointer-events-none":{"pointer-events":"none"},".pointer-events-auto":{"pointer-events":"auto"}})},visibility:({addUtilities:r})=>{r({".visible":{visibility:"visible"},".invisible":{visibility:"hidden"},".collapse":{visibility:"collapse"}})},position:({addUtilities:r})=>{r({".static":{position:"static"},".fixed":{position:"fixed"},".absolute":{position:"absolute"},".relative":{position:"relative"},".sticky":{position:"sticky"}})},inset:L("inset",[["inset",["inset"]],[["inset-x",["left","right"]],["inset-y",["top","bottom"]]],[["start",["inset-inline-start"]],["end",["inset-inline-end"]],["top",["top"]],["right",["right"]],["bottom",["bottom"]],["left",["left"]]]],{supportsNegativeValues:!0}),isolation:({addUtilities:r})=>{r({".isolate":{isolation:"isolate"},".isolation-auto":{isolation:"auto"}})},zIndex:L("zIndex",[["z",["zIndex"]]],{supportsNegativeValues:!0}),order:L("order",void 0,{supportsNegativeValues:!0}),gridColumn:L("gridColumn",[["col",["gridColumn"]]]),gridColumnStart:L("gridColumnStart",[["col-start",["gridColumnStart"]]],{supportsNegativeValues:!0}),gridColumnEnd:L("gridColumnEnd",[["col-end",["gridColumnEnd"]]],{supportsNegativeValues:!0}),gridRow:L("gridRow",[["row",["gridRow"]]]),gridRowStart:L("gridRowStart",[["row-start",["gridRowStart"]]],{supportsNegativeValues:!0}),gridRowEnd:L("gridRowEnd",[["row-end",["gridRowEnd"]]],{supportsNegativeValues:!0}),float:({addUtilities:r})=>{r({".float-start":{float:"inline-start"},".float-end":{float:"inline-end"},".float-right":{float:"right"},".float-left":{float:"left"},".float-none":{float:"none"}})},clear:({addUtilities:r})=>{r({".clear-start":{clear:"inline-start"},".clear-end":{clear:"inline-end"},".clear-left":{clear:"left"},".clear-right":{clear:"right"},".clear-both":{clear:"both"},".clear-none":{clear:"none"}})},margin:L("margin",[["m",["margin"]],[["mx",["margin-left","margin-right"]],["my",["margin-top","margin-bottom"]]],[["ms",["margin-inline-start"]],["me",["margin-inline-end"]],["mt",["margin-top"]],["mr",["margin-right"]],["mb",["margin-bottom"]],["ml",["margin-left"]]]],{supportsNegativeValues:!0}),boxSizing:({addUtilities:r})=>{r({".box-border":{"box-sizing":"border-box"},".box-content":{"box-sizing":"content-box"}})},lineClamp:({matchUtilities:r,addUtilities:e,theme:t})=>{r({"line-clamp":i=>({overflow:"hidden",display:"-webkit-box","-webkit-box-orient":"vertical","-webkit-line-clamp":`${i}`})},{values:t("lineClamp")}),e({".line-clamp-none":{overflow:"visible",display:"block","-webkit-box-orient":"horizontal","-webkit-line-clamp":"none"}})},display:({addUtilities:r})=>{r({".block":{display:"block"},".inline-block":{display:"inline-block"},".inline":{display:"inline"},".flex":{display:"flex"},".inline-flex":{display:"inline-flex"},".table":{display:"table"},".inline-table":{display:"inline-table"},".table-caption":{display:"table-caption"},".table-cell":{display:"table-cell"},".table-column":{display:"table-column"},".table-column-group":{display:"table-column-group"},".table-footer-group":{display:"table-footer-group"},".table-header-group":{display:"table-header-group"},".table-row-group":{display:"table-row-group"},".table-row":{display:"table-row"},".flow-root":{display:"flow-root"},".grid":{display:"grid"},".inline-grid":{display:"inline-grid"},".contents":{display:"contents"},".list-item":{display:"list-item"},".hidden":{display:"none"}})},aspectRatio:L("aspectRatio",[["aspect",["aspect-ratio"]]]),size:L("size",[["size",["width","height"]]]),height:L("height",[["h",["height"]]]),maxHeight:L("maxHeight",[["max-h",["maxHeight"]]]),minHeight:L("minHeight",[["min-h",["minHeight"]]]),width:L("width",[["w",["width"]]]),minWidth:L("minWidth",[["min-w",["minWidth"]]]),maxWidth:L("maxWidth",[["max-w",["maxWidth"]]]),flex:L("flex"),flexShrink:L("flexShrink",[["flex-shrink",["flex-shrink"]],["shrink",["flex-shrink"]]]),flexGrow:L("flexGrow",[["flex-grow",["flex-grow"]],["grow",["flex-grow"]]]),flexBasis:L("flexBasis",[["basis",["flex-basis"]]]),tableLayout:({addUtilities:r})=>{r({".table-auto":{"table-layout":"auto"},".table-fixed":{"table-layout":"fixed"}})},captionSide:({addUtilities:r})=>{r({".caption-top":{"caption-side":"top"},".caption-bottom":{"caption-side":"bottom"}})},borderCollapse:({addUtilities:r})=>{r({".border-collapse":{"border-collapse":"collapse"},".border-separate":{"border-collapse":"separate"}})},borderSpacing:({addDefaults:r,matchUtilities:e,theme:t})=>{r("border-spacing",{"--tw-border-spacing-x":0,"--tw-border-spacing-y":0}),e({"border-spacing":i=>({"--tw-border-spacing-x":i,"--tw-border-spacing-y":i,"@defaults border-spacing":{},"border-spacing":"var(--tw-border-spacing-x) var(--tw-border-spacing-y)"}),"border-spacing-x":i=>({"--tw-border-spacing-x":i,"@defaults border-spacing":{},"border-spacing":"var(--tw-border-spacing-x) var(--tw-border-spacing-y)"}),"border-spacing-y":i=>({"--tw-border-spacing-y":i,"@defaults border-spacing":{},"border-spacing":"var(--tw-border-spacing-x) var(--tw-border-spacing-y)"})},{values:t("borderSpacing")})},transformOrigin:L("transformOrigin",[["origin",["transformOrigin"]]]),translate:L("translate",[[["translate-x",[["@defaults transform",{}],"--tw-translate-x",["transform",Xe]]],["translate-y",[["@defaults transform",{}],"--tw-translate-y",["transform",Xe]]]]],{supportsNegativeValues:!0}),rotate:L("rotate",[["rotate",[["@defaults transform",{}],"--tw-rotate",["transform",Xe]]]],{supportsNegativeValues:!0}),skew:L("skew",[[["skew-x",[["@defaults transform",{}],"--tw-skew-x",["transform",Xe]]],["skew-y",[["@defaults transform",{}],"--tw-skew-y",["transform",Xe]]]]],{supportsNegativeValues:!0}),scale:L("scale",[["scale",[["@defaults transform",{}],"--tw-scale-x","--tw-scale-y",["transform",Xe]]],[["scale-x",[["@defaults transform",{}],"--tw-scale-x",["transform",Xe]]],["scale-y",[["@defaults transform",{}],"--tw-scale-y",["transform",Xe]]]]],{supportsNegativeValues:!0}),transform:({addDefaults:r,addUtilities:e})=>{r("transform",{"--tw-translate-x":"0","--tw-translate-y":"0","--tw-rotate":"0","--tw-skew-x":"0","--tw-skew-y":"0","--tw-scale-x":"1","--tw-scale-y":"1"}),e({".transform":{"@defaults transform":{},transform:Xe},".transform-cpu":{transform:Xe},".transform-gpu":{transform:Xe.replace("translate(var(--tw-translate-x), var(--tw-translate-y))","translate3d(var(--tw-translate-x), var(--tw-translate-y), 0)")},".transform-none":{transform:"none"}})},animation:({matchUtilities:r,theme:e,config:t})=>{let i=s=>Te(t("prefix")+s),n=Object.fromEntries(Object.entries(e("keyframes")??{}).map(([s,a])=>[s,{[`@keyframes ${i(s)}`]:a}]));r({animate:s=>{let a=Mo(s);return[...a.flatMap(o=>n[o.name]),{animation:a.map(({name:o,value:l})=>o===void 0||n[o]===void 0?l:l.replace(o,i(o))).join(", ")}]}},{values:e("animation")})},cursor:L("cursor"),touchAction:({addDefaults:r,addUtilities:e})=>{r("touch-action",{"--tw-pan-x":" ","--tw-pan-y":" ","--tw-pinch-zoom":" "});let t="var(--tw-pan-x) var(--tw-pan-y) var(--tw-pinch-zoom)";e({".touch-auto":{"touch-action":"auto"},".touch-none":{"touch-action":"none"},".touch-pan-x":{"@defaults touch-action":{},"--tw-pan-x":"pan-x","touch-action":t},".touch-pan-left":{"@defaults touch-action":{},"--tw-pan-x":"pan-left","touch-action":t},".touch-pan-right":{"@defaults touch-action":{},"--tw-pan-x":"pan-right","touch-action":t},".touch-pan-y":{"@defaults touch-action":{},"--tw-pan-y":"pan-y","touch-action":t},".touch-pan-up":{"@defaults touch-action":{},"--tw-pan-y":"pan-up","touch-action":t},".touch-pan-down":{"@defaults touch-action":{},"--tw-pan-y":"pan-down","touch-action":t},".touch-pinch-zoom":{"@defaults touch-action":{},"--tw-pinch-zoom":"pinch-zoom","touch-action":t},".touch-manipulation":{"touch-action":"manipulation"}})},userSelect:({addUtilities:r})=>{r({".select-none":{"user-select":"none"},".select-text":{"user-select":"text"},".select-all":{"user-select":"all"},".select-auto":{"user-select":"auto"}})},resize:({addUtilities:r})=>{r({".resize-none":{resize:"none"},".resize-y":{resize:"vertical"},".resize-x":{resize:"horizontal"},".resize":{resize:"both"}})},scrollSnapType:({addDefaults:r,addUtilities:e})=>{r("scroll-snap-type",{"--tw-scroll-snap-strictness":"proximity"}),e({".snap-none":{"scroll-snap-type":"none"},".snap-x":{"@defaults scroll-snap-type":{},"scroll-snap-type":"x var(--tw-scroll-snap-strictness)"},".snap-y":{"@defaults scroll-snap-type":{},"scroll-snap-type":"y var(--tw-scroll-snap-strictness)"},".snap-both":{"@defaults scroll-snap-type":{},"scroll-snap-type":"both var(--tw-scroll-snap-strictness)"},".snap-mandatory":{"--tw-scroll-snap-strictness":"mandatory"},".snap-proximity":{"--tw-scroll-snap-strictness":"proximity"}})},scrollSnapAlign:({addUtilities:r})=>{r({".snap-start":{"scroll-snap-align":"start"},".snap-end":{"scroll-snap-align":"end"},".snap-center":{"scroll-snap-align":"center"},".snap-align-none":{"scroll-snap-align":"none"}})},scrollSnapStop:({addUtilities:r})=>{r({".snap-normal":{"scroll-snap-stop":"normal"},".snap-always":{"scroll-snap-stop":"always"}})},scrollMargin:L("scrollMargin",[["scroll-m",["scroll-margin"]],[["scroll-mx",["scroll-margin-left","scroll-margin-right"]],["scroll-my",["scroll-margin-top","scroll-margin-bottom"]]],[["scroll-ms",["scroll-margin-inline-start"]],["scroll-me",["scroll-margin-inline-end"]],["scroll-mt",["scroll-margin-top"]],["scroll-mr",["scroll-margin-right"]],["scroll-mb",["scroll-margin-bottom"]],["scroll-ml",["scroll-margin-left"]]]],{supportsNegativeValues:!0}),scrollPadding:L("scrollPadding",[["scroll-p",["scroll-padding"]],[["scroll-px",["scroll-padding-left","scroll-padding-right"]],["scroll-py",["scroll-padding-top","scroll-padding-bottom"]]],[["scroll-ps",["scroll-padding-inline-start"]],["scroll-pe",["scroll-padding-inline-end"]],["scroll-pt",["scroll-padding-top"]],["scroll-pr",["scroll-padding-right"]],["scroll-pb",["scroll-padding-bottom"]],["scroll-pl",["scroll-padding-left"]]]]),listStylePosition:({addUtilities:r})=>{r({".list-inside":{"list-style-position":"inside"},".list-outside":{"list-style-position":"outside"}})},listStyleType:L("listStyleType",[["list",["listStyleType"]]]),listStyleImage:L("listStyleImage",[["list-image",["listStyleImage"]]]),appearance:({addUtilities:r})=>{r({".appearance-none":{appearance:"none"},".appearance-auto":{appearance:"auto"}})},columns:L("columns",[["columns",["columns"]]]),breakBefore:({addUtilities:r})=>{r({".break-before-auto":{"break-before":"auto"},".break-before-avoid":{"break-before":"avoid"},".break-before-all":{"break-before":"all"},".break-before-avoid-page":{"break-before":"avoid-page"},".break-before-page":{"break-before":"page"},".break-before-left":{"break-before":"left"},".break-before-right":{"break-before":"right"},".break-before-column":{"break-before":"column"}})},breakInside:({addUtilities:r})=>{r({".break-inside-auto":{"break-inside":"auto"},".break-inside-avoid":{"break-inside":"avoid"},".break-inside-avoid-page":{"break-inside":"avoid-page"},".break-inside-avoid-column":{"break-inside":"avoid-column"}})},breakAfter:({addUtilities:r})=>{r({".break-after-auto":{"break-after":"auto"},".break-after-avoid":{"break-after":"avoid"},".break-after-all":{"break-after":"all"},".break-after-avoid-page":{"break-after":"avoid-page"},".break-after-page":{"break-after":"page"},".break-after-left":{"break-after":"left"},".break-after-right":{"break-after":"right"},".break-after-column":{"break-after":"column"}})},gridAutoColumns:L("gridAutoColumns",[["auto-cols",["gridAutoColumns"]]]),gridAutoFlow:({addUtilities:r})=>{r({".grid-flow-row":{gridAutoFlow:"row"},".grid-flow-col":{gridAutoFlow:"column"},".grid-flow-dense":{gridAutoFlow:"dense"},".grid-flow-row-dense":{gridAutoFlow:"row dense"},".grid-flow-col-dense":{gridAutoFlow:"column dense"}})},gridAutoRows:L("gridAutoRows",[["auto-rows",["gridAutoRows"]]]),gridTemplateColumns:L("gridTemplateColumns",[["grid-cols",["gridTemplateColumns"]]]),gridTemplateRows:L("gridTemplateRows",[["grid-rows",["gridTemplateRows"]]]),flexDirection:({addUtilities:r})=>{r({".flex-row":{"flex-direction":"row"},".flex-row-reverse":{"flex-direction":"row-reverse"},".flex-col":{"flex-direction":"column"},".flex-col-reverse":{"flex-direction":"column-reverse"}})},flexWrap:({addUtilities:r})=>{r({".flex-wrap":{"flex-wrap":"wrap"},".flex-wrap-reverse":{"flex-wrap":"wrap-reverse"},".flex-nowrap":{"flex-wrap":"nowrap"}})},placeContent:({addUtilities:r})=>{r({".place-content-center":{"place-content":"center"},".place-content-start":{"place-content":"start"},".place-content-end":{"place-content":"end"},".place-content-between":{"place-content":"space-between"},".place-content-around":{"place-content":"space-around"},".place-content-evenly":{"place-content":"space-evenly"},".place-content-baseline":{"place-content":"baseline"},".place-content-stretch":{"place-content":"stretch"}})},placeItems:({addUtilities:r})=>{r({".place-items-start":{"place-items":"start"},".place-items-end":{"place-items":"end"},".place-items-center":{"place-items":"center"},".place-items-baseline":{"place-items":"baseline"},".place-items-stretch":{"place-items":"stretch"}})},alignContent:({addUtilities:r})=>{r({".content-normal":{"align-content":"normal"},".content-center":{"align-content":"center"},".content-start":{"align-content":"flex-start"},".content-end":{"align-content":"flex-end"},".content-between":{"align-content":"space-between"},".content-around":{"align-content":"space-around"},".content-evenly":{"align-content":"space-evenly"},".content-baseline":{"align-content":"baseline"},".content-stretch":{"align-content":"stretch"}})},alignItems:({addUtilities:r})=>{r({".items-start":{"align-items":"flex-start"},".items-end":{"align-items":"flex-end"},".items-center":{"align-items":"center"},".items-baseline":{"align-items":"baseline"},".items-stretch":{"align-items":"stretch"}})},justifyContent:({addUtilities:r})=>{r({".justify-normal":{"justify-content":"normal"},".justify-start":{"justify-content":"flex-start"},".justify-end":{"justify-content":"flex-end"},".justify-center":{"justify-content":"center"},".justify-between":{"justify-content":"space-between"},".justify-around":{"justify-content":"space-around"},".justify-evenly":{"justify-content":"space-evenly"},".justify-stretch":{"justify-content":"stretch"}})},justifyItems:({addUtilities:r})=>{r({".justify-items-start":{"justify-items":"start"},".justify-items-end":{"justify-items":"end"},".justify-items-center":{"justify-items":"center"},".justify-items-stretch":{"justify-items":"stretch"}})},gap:L("gap",[["gap",["gap"]],[["gap-x",["columnGap"]],["gap-y",["rowGap"]]]]),space:({matchUtilities:r,addUtilities:e,theme:t})=>{r({"space-x":i=>(i=i==="0"?"0px":i,{"& > :not([hidden]) ~ :not([hidden])":{"--tw-space-x-reverse":"0","margin-right":`calc(${i} * var(--tw-space-x-reverse))`,"margin-left":`calc(${i} * calc(1 - var(--tw-space-x-reverse)))`}}),"space-y":i=>(i=i==="0"?"0px":i,{"& > :not([hidden]) ~ :not([hidden])":{"--tw-space-y-reverse":"0","margin-top":`calc(${i} * calc(1 - var(--tw-space-y-reverse)))`,"margin-bottom":`calc(${i} * var(--tw-space-y-reverse))`}})},{values:t("space"),supportsNegativeValues:!0}),e({".space-y-reverse > :not([hidden]) ~ :not([hidden])":{"--tw-space-y-reverse":"1"},".space-x-reverse > :not([hidden]) ~ :not([hidden])":{"--tw-space-x-reverse":"1"}})},divideWidth:({matchUtilities:r,addUtilities:e,theme:t})=>{r({"divide-x":i=>(i=i==="0"?"0px":i,{"& > :not([hidden]) ~ :not([hidden])":{"@defaults border-width":{},"--tw-divide-x-reverse":"0","border-right-width":`calc(${i} * var(--tw-divide-x-reverse))`,"border-left-width":`calc(${i} * calc(1 - var(--tw-divide-x-reverse)))`}}),"divide-y":i=>(i=i==="0"?"0px":i,{"& > :not([hidden]) ~ :not([hidden])":{"@defaults border-width":{},"--tw-divide-y-reverse":"0","border-top-width":`calc(${i} * calc(1 - var(--tw-divide-y-reverse)))`,"border-bottom-width":`calc(${i} * var(--tw-divide-y-reverse))`}})},{values:t("divideWidth"),type:["line-width","length","any"]}),e({".divide-y-reverse > :not([hidden]) ~ :not([hidden])":{"@defaults border-width":{},"--tw-divide-y-reverse":"1"},".divide-x-reverse > :not([hidden]) ~ :not([hidden])":{"@defaults border-width":{},"--tw-divide-x-reverse":"1"}})},divideStyle:({addUtilities:r})=>{r({".divide-solid > :not([hidden]) ~ :not([hidden])":{"border-style":"solid"},".divide-dashed > :not([hidden]) ~ :not([hidden])":{"border-style":"dashed"},".divide-dotted > :not([hidden]) ~ :not([hidden])":{"border-style":"dotted"},".divide-double > :not([hidden]) ~ :not([hidden])":{"border-style":"double"},".divide-none > :not([hidden]) ~ :not([hidden])":{"border-style":"none"}})},divideColor:({matchUtilities:r,theme:e,corePlugins:t})=>{r({divide:i=>t("divideOpacity")?{["& > :not([hidden]) ~ :not([hidden])"]:Ae({color:i,property:"border-color",variable:"--tw-divide-opacity"})}:{["& > :not([hidden]) ~ :not([hidden])"]:{"border-color":X(i)}}},{values:(({DEFAULT:i,...n})=>n)(xe(e("divideColor"))),type:["color","any"]})},divideOpacity:({matchUtilities:r,theme:e})=>{r({"divide-opacity":t=>({["& > :not([hidden]) ~ :not([hidden])"]:{"--tw-divide-opacity":t}})},{values:e("divideOpacity")})},placeSelf:({addUtilities:r})=>{r({".place-self-auto":{"place-self":"auto"},".place-self-start":{"place-self":"start"},".place-self-end":{"place-self":"end"},".place-self-center":{"place-self":"center"},".place-self-stretch":{"place-self":"stretch"}})},alignSelf:({addUtilities:r})=>{r({".self-auto":{"align-self":"auto"},".self-start":{"align-self":"flex-start"},".self-end":{"align-self":"flex-end"},".self-center":{"align-self":"center"},".self-stretch":{"align-self":"stretch"},".self-baseline":{"align-self":"baseline"}})},justifySelf:({addUtilities:r})=>{r({".justify-self-auto":{"justify-self":"auto"},".justify-self-start":{"justify-self":"start"},".justify-self-end":{"justify-self":"end"},".justify-self-center":{"justify-self":"center"},".justify-self-stretch":{"justify-self":"stretch"}})},overflow:({addUtilities:r})=>{r({".overflow-auto":{overflow:"auto"},".overflow-hidden":{overflow:"hidden"},".overflow-clip":{overflow:"clip"},".overflow-visible":{overflow:"visible"},".overflow-scroll":{overflow:"scroll"},".overflow-x-auto":{"overflow-x":"auto"},".overflow-y-auto":{"overflow-y":"auto"},".overflow-x-hidden":{"overflow-x":"hidden"},".overflow-y-hidden":{"overflow-y":"hidden"},".overflow-x-clip":{"overflow-x":"clip"},".overflow-y-clip":{"overflow-y":"clip"},".overflow-x-visible":{"overflow-x":"visible"},".overflow-y-visible":{"overflow-y":"visible"},".overflow-x-scroll":{"overflow-x":"scroll"},".overflow-y-scroll":{"overflow-y":"scroll"}})},overscrollBehavior:({addUtilities:r})=>{r({".overscroll-auto":{"overscroll-behavior":"auto"},".overscroll-contain":{"overscroll-behavior":"contain"},".overscroll-none":{"overscroll-behavior":"none"},".overscroll-y-auto":{"overscroll-behavior-y":"auto"},".overscroll-y-contain":{"overscroll-behavior-y":"contain"},".overscroll-y-none":{"overscroll-behavior-y":"none"},".overscroll-x-auto":{"overscroll-behavior-x":"auto"},".overscroll-x-contain":{"overscroll-behavior-x":"contain"},".overscroll-x-none":{"overscroll-behavior-x":"none"}})},scrollBehavior:({addUtilities:r})=>{r({".scroll-auto":{"scroll-behavior":"auto"},".scroll-smooth":{"scroll-behavior":"smooth"}})},textOverflow:({addUtilities:r})=>{r({".truncate":{overflow:"hidden","text-overflow":"ellipsis","white-space":"nowrap"},".overflow-ellipsis":{"text-overflow":"ellipsis"},".text-ellipsis":{"text-overflow":"ellipsis"},".text-clip":{"text-overflow":"clip"}})},hyphens:({addUtilities:r})=>{r({".hyphens-none":{hyphens:"none"},".hyphens-manual":{hyphens:"manual"},".hyphens-auto":{hyphens:"auto"}})},whitespace:({addUtilities:r})=>{r({".whitespace-normal":{"white-space":"normal"},".whitespace-nowrap":{"white-space":"nowrap"},".whitespace-pre":{"white-space":"pre"},".whitespace-pre-line":{"white-space":"pre-line"},".whitespace-pre-wrap":{"white-space":"pre-wrap"},".whitespace-break-spaces":{"white-space":"break-spaces"}})},textWrap:({addUtilities:r})=>{r({".text-wrap":{"text-wrap":"wrap"},".text-nowrap":{"text-wrap":"nowrap"},".text-balance":{"text-wrap":"balance"},".text-pretty":{"text-wrap":"pretty"}})},wordBreak:({addUtilities:r})=>{r({".break-normal":{"overflow-wrap":"normal","word-break":"normal"},".break-words":{"overflow-wrap":"break-word"},".break-all":{"word-break":"break-all"},".break-keep":{"word-break":"keep-all"}})},borderRadius:L("borderRadius",[["rounded",["border-radius"]],[["rounded-s",["border-start-start-radius","border-end-start-radius"]],["rounded-e",["border-start-end-radius","border-end-end-radius"]],["rounded-t",["border-top-left-radius","border-top-right-radius"]],["rounded-r",["border-top-right-radius","border-bottom-right-radius"]],["rounded-b",["border-bottom-right-radius","border-bottom-left-radius"]],["rounded-l",["border-top-left-radius","border-bottom-left-radius"]]],[["rounded-ss",["border-start-start-radius"]],["rounded-se",["border-start-end-radius"]],["rounded-ee",["border-end-end-radius"]],["rounded-es",["border-end-start-radius"]],["rounded-tl",["border-top-left-radius"]],["rounded-tr",["border-top-right-radius"]],["rounded-br",["border-bottom-right-radius"]],["rounded-bl",["border-bottom-left-radius"]]]]),borderWidth:L("borderWidth",[["border",[["@defaults border-width",{}],"border-width"]],[["border-x",[["@defaults border-width",{}],"border-left-width","border-right-width"]],["border-y",[["@defaults border-width",{}],"border-top-width","border-bottom-width"]]],[["border-s",[["@defaults border-width",{}],"border-inline-start-width"]],["border-e",[["@defaults border-width",{}],"border-inline-end-width"]],["border-t",[["@defaults border-width",{}],"border-top-width"]],["border-r",[["@defaults border-width",{}],"border-right-width"]],["border-b",[["@defaults border-width",{}],"border-bottom-width"]],["border-l",[["@defaults border-width",{}],"border-left-width"]]]],{type:["line-width","length"]}),borderStyle:({addUtilities:r})=>{r({".border-solid":{"border-style":"solid"},".border-dashed":{"border-style":"dashed"},".border-dotted":{"border-style":"dotted"},".border-double":{"border-style":"double"},".border-hidden":{"border-style":"hidden"},".border-none":{"border-style":"none"}})},borderColor:({matchUtilities:r,theme:e,corePlugins:t})=>{r({border:i=>t("borderOpacity")?Ae({color:i,property:"border-color",variable:"--tw-border-opacity"}):{"border-color":X(i)}},{values:(({DEFAULT:i,...n})=>n)(xe(e("borderColor"))),type:["color","any"]}),r({"border-x":i=>t("borderOpacity")?Ae({color:i,property:["border-left-color","border-right-color"],variable:"--tw-border-opacity"}):{"border-left-color":X(i),"border-right-color":X(i)},"border-y":i=>t("borderOpacity")?Ae({color:i,property:["border-top-color","border-bottom-color"],variable:"--tw-border-opacity"}):{"border-top-color":X(i),"border-bottom-color":X(i)}},{values:(({DEFAULT:i,...n})=>n)(xe(e("borderColor"))),type:["color","any"]}),r({"border-s":i=>t("borderOpacity")?Ae({color:i,property:"border-inline-start-color",variable:"--tw-border-opacity"}):{"border-inline-start-color":X(i)},"border-e":i=>t("borderOpacity")?Ae({color:i,property:"border-inline-end-color",variable:"--tw-border-opacity"}):{"border-inline-end-color":X(i)},"border-t":i=>t("borderOpacity")?Ae({color:i,property:"border-top-color",variable:"--tw-border-opacity"}):{"border-top-color":X(i)},"border-r":i=>t("borderOpacity")?Ae({color:i,property:"border-right-color",variable:"--tw-border-opacity"}):{"border-right-color":X(i)},"border-b":i=>t("borderOpacity")?Ae({color:i,property:"border-bottom-color",variable:"--tw-border-opacity"}):{"border-bottom-color":X(i)},"border-l":i=>t("borderOpacity")?Ae({color:i,property:"border-left-color",variable:"--tw-border-opacity"}):{"border-left-color":X(i)}},{values:(({DEFAULT:i,...n})=>n)(xe(e("borderColor"))),type:["color","any"]})},borderOpacity:L("borderOpacity",[["border-opacity",["--tw-border-opacity"]]]),backgroundColor:({matchUtilities:r,theme:e,corePlugins:t})=>{r({bg:i=>t("backgroundOpacity")?Ae({color:i,property:"background-color",variable:"--tw-bg-opacity"}):{"background-color":X(i)}},{values:xe(e("backgroundColor")),type:["color","any"]})},backgroundOpacity:L("backgroundOpacity",[["bg-opacity",["--tw-bg-opacity"]]]),backgroundImage:L("backgroundImage",[["bg",["background-image"]]],{type:["lookup","image","url"]}),gradientColorStops:(()=>{function r(e){return Je(e,0,"rgb(255 255 255 / 0)")}return function({matchUtilities:e,theme:t,addDefaults:i}){i("gradient-color-stops",{"--tw-gradient-from-position":" ","--tw-gradient-via-position":" ","--tw-gradient-to-position":" "});let n={values:xe(t("gradientColorStops")),type:["color","any"]},s={values:t("gradientColorStopPositions"),type:["length","percentage"]};e({from:a=>{let o=r(a);return{"@defaults gradient-color-stops":{},"--tw-gradient-from":`${X(a)} var(--tw-gradient-from-position)`,"--tw-gradient-to":`${o} var(--tw-gradient-to-position)`,"--tw-gradient-stops":"var(--tw-gradient-from), var(--tw-gradient-to)"}}},n),e({from:a=>({"--tw-gradient-from-position":a})},s),e({via:a=>{let o=r(a);return{"@defaults gradient-color-stops":{},"--tw-gradient-to":`${o} var(--tw-gradient-to-position)`,"--tw-gradient-stops":`var(--tw-gradient-from), ${X(a)} var(--tw-gradient-via-position), var(--tw-gradient-to)`}}},n),e({via:a=>({"--tw-gradient-via-position":a})},s),e({to:a=>({"@defaults gradient-color-stops":{},"--tw-gradient-to":`${X(a)} var(--tw-gradient-to-position)`})},n),e({to:a=>({"--tw-gradient-to-position":a})},s)}})(),boxDecorationBreak:({addUtilities:r})=>{r({".decoration-slice":{"box-decoration-break":"slice"},".decoration-clone":{"box-decoration-break":"clone"},".box-decoration-slice":{"box-decoration-break":"slice"},".box-decoration-clone":{"box-decoration-break":"clone"}})},backgroundSize:L("backgroundSize",[["bg",["background-size"]]],{type:["lookup","length","percentage","size"]}),backgroundAttachment:({addUtilities:r})=>{r({".bg-fixed":{"background-attachment":"fixed"},".bg-local":{"background-attachment":"local"},".bg-scroll":{"background-attachment":"scroll"}})},backgroundClip:({addUtilities:r})=>{r({".bg-clip-border":{"background-clip":"border-box"},".bg-clip-padding":{"background-clip":"padding-box"},".bg-clip-content":{"background-clip":"content-box"},".bg-clip-text":{"background-clip":"text"}})},backgroundPosition:L("backgroundPosition",[["bg",["background-position"]]],{type:["lookup",["position",{preferOnConflict:!0}]]}),backgroundRepeat:({addUtilities:r})=>{r({".bg-repeat":{"background-repeat":"repeat"},".bg-no-repeat":{"background-repeat":"no-repeat"},".bg-repeat-x":{"background-repeat":"repeat-x"},".bg-repeat-y":{"background-repeat":"repeat-y"},".bg-repeat-round":{"background-repeat":"round"},".bg-repeat-space":{"background-repeat":"space"}})},backgroundOrigin:({addUtilities:r})=>{r({".bg-origin-border":{"background-origin":"border-box"},".bg-origin-padding":{"background-origin":"padding-box"},".bg-origin-content":{"background-origin":"content-box"}})},fill:({matchUtilities:r,theme:e})=>{r({fill:t=>({fill:X(t)})},{values:xe(e("fill")),type:["color","any"]})},stroke:({matchUtilities:r,theme:e})=>{r({stroke:t=>({stroke:X(t)})},{values:xe(e("stroke")),type:["color","url","any"]})},strokeWidth:L("strokeWidth",[["stroke",["stroke-width"]]],{type:["length","number","percentage"]}),objectFit:({addUtilities:r})=>{r({".object-contain":{"object-fit":"contain"},".object-cover":{"object-fit":"cover"},".object-fill":{"object-fit":"fill"},".object-none":{"object-fit":"none"},".object-scale-down":{"object-fit":"scale-down"}})},objectPosition:L("objectPosition",[["object",["object-position"]]]),padding:L("padding",[["p",["padding"]],[["px",["padding-left","padding-right"]],["py",["padding-top","padding-bottom"]]],[["ps",["padding-inline-start"]],["pe",["padding-inline-end"]],["pt",["padding-top"]],["pr",["padding-right"]],["pb",["padding-bottom"]],["pl",["padding-left"]]]]),textAlign:({addUtilities:r})=>{r({".text-left":{"text-align":"left"},".text-center":{"text-align":"center"},".text-right":{"text-align":"right"},".text-justify":{"text-align":"justify"},".text-start":{"text-align":"start"},".text-end":{"text-align":"end"}})},textIndent:L("textIndent",[["indent",["text-indent"]]],{supportsNegativeValues:!0}),verticalAlign:({addUtilities:r,matchUtilities:e})=>{r({".align-baseline":{"vertical-align":"baseline"},".align-top":{"vertical-align":"top"},".align-middle":{"vertical-align":"middle"},".align-bottom":{"vertical-align":"bottom"},".align-text-top":{"vertical-align":"text-top"},".align-text-bottom":{"vertical-align":"text-bottom"},".align-sub":{"vertical-align":"sub"},".align-super":{"vertical-align":"super"}}),e({align:t=>({"vertical-align":t})})},fontFamily:({matchUtilities:r,theme:e})=>{r({font:t=>{let[i,n={}]=Array.isArray(t)&&ke(t[1])?t:[t],{fontFeatureSettings:s,fontVariationSettings:a}=n;return{"font-family":Array.isArray(i)?i.join(", "):i,...s===void 0?{}:{"font-feature-settings":s},...a===void 0?{}:{"font-variation-settings":a}}}},{values:e("fontFamily"),type:["lookup","generic-name","family-name"]})},fontSize:({matchUtilities:r,theme:e})=>{r({text:(t,{modifier:i})=>{let[n,s]=Array.isArray(t)?t:[t];if(i)return{"font-size":n,"line-height":i};let{lineHeight:a,letterSpacing:o,fontWeight:l}=ke(s)?s:{lineHeight:s};return{"font-size":n,...a===void 0?{}:{"line-height":a},...o===void 0?{}:{"letter-spacing":o},...l===void 0?{}:{"font-weight":l}}}},{values:e("fontSize"),modifiers:e("lineHeight"),type:["absolute-size","relative-size","length","percentage"]})},fontWeight:L("fontWeight",[["font",["fontWeight"]]],{type:["lookup","number","any"]}),textTransform:({addUtilities:r})=>{r({".uppercase":{"text-transform":"uppercase"},".lowercase":{"text-transform":"lowercase"},".capitalize":{"text-transform":"capitalize"},".normal-case":{"text-transform":"none"}})},fontStyle:({addUtilities:r})=>{r({".italic":{"font-style":"italic"},".not-italic":{"font-style":"normal"}})},fontVariantNumeric:({addDefaults:r,addUtilities:e})=>{let t="var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)";r("font-variant-numeric",{"--tw-ordinal":" ","--tw-slashed-zero":" ","--tw-numeric-figure":" ","--tw-numeric-spacing":" ","--tw-numeric-fraction":" "}),e({".normal-nums":{"font-variant-numeric":"normal"},".ordinal":{"@defaults font-variant-numeric":{},"--tw-ordinal":"ordinal","font-variant-numeric":t},".slashed-zero":{"@defaults font-variant-numeric":{},"--tw-slashed-zero":"slashed-zero","font-variant-numeric":t},".lining-nums":{"@defaults font-variant-numeric":{},"--tw-numeric-figure":"lining-nums","font-variant-numeric":t},".oldstyle-nums":{"@defaults font-variant-numeric":{},"--tw-numeric-figure":"oldstyle-nums","font-variant-numeric":t},".proportional-nums":{"@defaults font-variant-numeric":{},"--tw-numeric-spacing":"proportional-nums","font-variant-numeric":t},".tabular-nums":{"@defaults font-variant-numeric":{},"--tw-numeric-spacing":"tabular-nums","font-variant-numeric":t},".diagonal-fractions":{"@defaults font-variant-numeric":{},"--tw-numeric-fraction":"diagonal-fractions","font-variant-numeric":t},".stacked-fractions":{"@defaults font-variant-numeric":{},"--tw-numeric-fraction":"stacked-fractions","font-variant-numeric":t}})},lineHeight:L("lineHeight",[["leading",["lineHeight"]]]),letterSpacing:L("letterSpacing",[["tracking",["letterSpacing"]]],{supportsNegativeValues:!0}),textColor:({matchUtilities:r,theme:e,corePlugins:t})=>{r({text:i=>t("textOpacity")?Ae({color:i,property:"color",variable:"--tw-text-opacity"}):{color:X(i)}},{values:xe(e("textColor")),type:["color","any"]})},textOpacity:L("textOpacity",[["text-opacity",["--tw-text-opacity"]]]),textDecoration:({addUtilities:r})=>{r({".underline":{"text-decoration-line":"underline"},".overline":{"text-decoration-line":"overline"},".line-through":{"text-decoration-line":"line-through"},".no-underline":{"text-decoration-line":"none"}})},textDecorationColor:({matchUtilities:r,theme:e})=>{r({decoration:t=>({"text-decoration-color":X(t)})},{values:xe(e("textDecorationColor")),type:["color","any"]})},textDecorationStyle:({addUtilities:r})=>{r({".decoration-solid":{"text-decoration-style":"solid"},".decoration-double":{"text-decoration-style":"double"},".decoration-dotted":{"text-decoration-style":"dotted"},".decoration-dashed":{"text-decoration-style":"dashed"},".decoration-wavy":{"text-decoration-style":"wavy"}})},textDecorationThickness:L("textDecorationThickness",[["decoration",["text-decoration-thickness"]]],{type:["length","percentage"]}),textUnderlineOffset:L("textUnderlineOffset",[["underline-offset",["text-underline-offset"]]],{type:["length","percentage","any"]}),fontSmoothing:({addUtilities:r})=>{r({".antialiased":{"-webkit-font-smoothing":"antialiased","-moz-osx-font-smoothing":"grayscale"},".subpixel-antialiased":{"-webkit-font-smoothing":"auto","-moz-osx-font-smoothing":"auto"}})},placeholderColor:({matchUtilities:r,theme:e,corePlugins:t})=>{r({placeholder:i=>t("placeholderOpacity")?{"&::placeholder":Ae({color:i,property:"color",variable:"--tw-placeholder-opacity"})}:{"&::placeholder":{color:X(i)}}},{values:xe(e("placeholderColor")),type:["color","any"]})},placeholderOpacity:({matchUtilities:r,theme:e})=>{r({"placeholder-opacity":t=>({["&::placeholder"]:{"--tw-placeholder-opacity":t}})},{values:e("placeholderOpacity")})},caretColor:({matchUtilities:r,theme:e})=>{r({caret:t=>({"caret-color":X(t)})},{values:xe(e("caretColor")),type:["color","any"]})},accentColor:({matchUtilities:r,theme:e})=>{r({accent:t=>({"accent-color":X(t)})},{values:xe(e("accentColor")),type:["color","any"]})},opacity:L("opacity",[["opacity",["opacity"]]]),backgroundBlendMode:({addUtilities:r})=>{r({".bg-blend-normal":{"background-blend-mode":"normal"},".bg-blend-multiply":{"background-blend-mode":"multiply"},".bg-blend-screen":{"background-blend-mode":"screen"},".bg-blend-overlay":{"background-blend-mode":"overlay"},".bg-blend-darken":{"background-blend-mode":"darken"},".bg-blend-lighten":{"background-blend-mode":"lighten"},".bg-blend-color-dodge":{"background-blend-mode":"color-dodge"},".bg-blend-color-burn":{"background-blend-mode":"color-burn"},".bg-blend-hard-light":{"background-blend-mode":"hard-light"},".bg-blend-soft-light":{"background-blend-mode":"soft-light"},".bg-blend-difference":{"background-blend-mode":"difference"},".bg-blend-exclusion":{"background-blend-mode":"exclusion"},".bg-blend-hue":{"background-blend-mode":"hue"},".bg-blend-saturation":{"background-blend-mode":"saturation"},".bg-blend-color":{"background-blend-mode":"color"},".bg-blend-luminosity":{"background-blend-mode":"luminosity"}})},mixBlendMode:({addUtilities:r})=>{r({".mix-blend-normal":{"mix-blend-mode":"normal"},".mix-blend-multiply":{"mix-blend-mode":"multiply"},".mix-blend-screen":{"mix-blend-mode":"screen"},".mix-blend-overlay":{"mix-blend-mode":"overlay"},".mix-blend-darken":{"mix-blend-mode":"darken"},".mix-blend-lighten":{"mix-blend-mode":"lighten"},".mix-blend-color-dodge":{"mix-blend-mode":"color-dodge"},".mix-blend-color-burn":{"mix-blend-mode":"color-burn"},".mix-blend-hard-light":{"mix-blend-mode":"hard-light"},".mix-blend-soft-light":{"mix-blend-mode":"soft-light"},".mix-blend-difference":{"mix-blend-mode":"difference"},".mix-blend-exclusion":{"mix-blend-mode":"exclusion"},".mix-blend-hue":{"mix-blend-mode":"hue"},".mix-blend-saturation":{"mix-blend-mode":"saturation"},".mix-blend-color":{"mix-blend-mode":"color"},".mix-blend-luminosity":{"mix-blend-mode":"luminosity"},".mix-blend-plus-darker":{"mix-blend-mode":"plus-darker"},".mix-blend-plus-lighter":{"mix-blend-mode":"plus-lighter"}})},boxShadow:(()=>{let r=mt("boxShadow"),e=["var(--tw-ring-offset-shadow, 0 0 #0000)","var(--tw-ring-shadow, 0 0 #0000)","var(--tw-shadow)"].join(", ");return function({matchUtilities:t,addDefaults:i,theme:n}){i("box-shadow",{"--tw-ring-offset-shadow":"0 0 #0000","--tw-ring-shadow":"0 0 #0000","--tw-shadow":"0 0 #0000","--tw-shadow-colored":"0 0 #0000"}),t({shadow:s=>{s=r(s);let a=en(s);for(let o of a)!o.valid||(o.color="var(--tw-shadow-color)");return{"@defaults box-shadow":{},"--tw-shadow":s==="none"?"0 0 #0000":s,"--tw-shadow-colored":s==="none"?"0 0 #0000":Lf(a),"box-shadow":e}}},{values:n("boxShadow"),type:["shadow"]})}})(),boxShadowColor:({matchUtilities:r,theme:e})=>{r({shadow:t=>({"--tw-shadow-color":X(t),"--tw-shadow":"var(--tw-shadow-colored)"})},{values:xe(e("boxShadowColor")),type:["color","any"]})},outlineStyle:({addUtilities:r})=>{r({".outline-none":{outline:"2px solid transparent","outline-offset":"2px"},".outline":{"outline-style":"solid"},".outline-dashed":{"outline-style":"dashed"},".outline-dotted":{"outline-style":"dotted"},".outline-double":{"outline-style":"double"}})},outlineWidth:L("outlineWidth",[["outline",["outline-width"]]],{type:["length","number","percentage"]}),outlineOffset:L("outlineOffset",[["outline-offset",["outline-offset"]]],{type:["length","number","percentage","any"],supportsNegativeValues:!0}),outlineColor:({matchUtilities:r,theme:e})=>{r({outline:t=>({"outline-color":X(t)})},{values:xe(e("outlineColor")),type:["color","any"]})},ringWidth:({matchUtilities:r,addDefaults:e,addUtilities:t,theme:i,config:n})=>{let s=(()=>{if(we(n(),"respectDefaultRingColorOpacity"))return i("ringColor.DEFAULT");let a=i("ringOpacity.DEFAULT","0.5");return i("ringColor")?.DEFAULT?Je(i("ringColor")?.DEFAULT,a,`rgb(147 197 253 / ${a})`):`rgb(147 197 253 / ${a})`})();e("ring-width",{"--tw-ring-inset":" ","--tw-ring-offset-width":i("ringOffsetWidth.DEFAULT","0px"),"--tw-ring-offset-color":i("ringOffsetColor.DEFAULT","#fff"),"--tw-ring-color":s,"--tw-ring-offset-shadow":"0 0 #0000","--tw-ring-shadow":"0 0 #0000","--tw-shadow":"0 0 #0000","--tw-shadow-colored":"0 0 #0000"}),r({ring:a=>({"@defaults ring-width":{},"--tw-ring-offset-shadow":"var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)","--tw-ring-shadow":`var(--tw-ring-inset) 0 0 0 calc(${a} + var(--tw-ring-offset-width)) var(--tw-ring-color)`,"box-shadow":["var(--tw-ring-offset-shadow)","var(--tw-ring-shadow)","var(--tw-shadow, 0 0 #0000)"].join(", ")})},{values:i("ringWidth"),type:"length"}),t({".ring-inset":{"@defaults ring-width":{},"--tw-ring-inset":"inset"}})},ringColor:({matchUtilities:r,theme:e,corePlugins:t})=>{r({ring:i=>t("ringOpacity")?Ae({color:i,property:"--tw-ring-color",variable:"--tw-ring-opacity"}):{"--tw-ring-color":X(i)}},{values:Object.fromEntries(Object.entries(xe(e("ringColor"))).filter(([i])=>i!=="DEFAULT")),type:["color","any"]})},ringOpacity:r=>{let{config:e}=r;return L("ringOpacity",[["ring-opacity",["--tw-ring-opacity"]]],{filterDefault:!we(e(),"respectDefaultRingColorOpacity")})(r)},ringOffsetWidth:L("ringOffsetWidth",[["ring-offset",["--tw-ring-offset-width"]]],{type:"length"}),ringOffsetColor:({matchUtilities:r,theme:e})=>{r({"ring-offset":t=>({"--tw-ring-offset-color":X(t)})},{values:xe(e("ringOffsetColor")),type:["color","any"]})},blur:({matchUtilities:r,theme:e})=>{r({blur:t=>({"--tw-blur":t.trim()===""?" ":`blur(${t})`,"@defaults filter":{},filter:nt})},{values:e("blur")})},brightness:({matchUtilities:r,theme:e})=>{r({brightness:t=>({"--tw-brightness":`brightness(${t})`,"@defaults filter":{},filter:nt})},{values:e("brightness")})},contrast:({matchUtilities:r,theme:e})=>{r({contrast:t=>({"--tw-contrast":`contrast(${t})`,"@defaults filter":{},filter:nt})},{values:e("contrast")})},dropShadow:({matchUtilities:r,theme:e})=>{r({"drop-shadow":t=>({"--tw-drop-shadow":Array.isArray(t)?t.map(i=>`drop-shadow(${i})`).join(" "):`drop-shadow(${t})`,"@defaults filter":{},filter:nt})},{values:e("dropShadow")})},grayscale:({matchUtilities:r,theme:e})=>{r({grayscale:t=>({"--tw-grayscale":`grayscale(${t})`,"@defaults filter":{},filter:nt})},{values:e("grayscale")})},hueRotate:({matchUtilities:r,theme:e})=>{r({"hue-rotate":t=>({"--tw-hue-rotate":`hue-rotate(${t})`,"@defaults filter":{},filter:nt})},{values:e("hueRotate"),supportsNegativeValues:!0})},invert:({matchUtilities:r,theme:e})=>{r({invert:t=>({"--tw-invert":`invert(${t})`,"@defaults filter":{},filter:nt})},{values:e("invert")})},saturate:({matchUtilities:r,theme:e})=>{r({saturate:t=>({"--tw-saturate":`saturate(${t})`,"@defaults filter":{},filter:nt})},{values:e("saturate")})},sepia:({matchUtilities:r,theme:e})=>{r({sepia:t=>({"--tw-sepia":`sepia(${t})`,"@defaults filter":{},filter:nt})},{values:e("sepia")})},filter:({addDefaults:r,addUtilities:e})=>{r("filter",{"--tw-blur":" ","--tw-brightness":" ","--tw-contrast":" ","--tw-grayscale":" ","--tw-hue-rotate":" ","--tw-invert":" ","--tw-saturate":" ","--tw-sepia":" ","--tw-drop-shadow":" "}),e({".filter":{"@defaults filter":{},filter:nt},".filter-none":{filter:"none"}})},backdropBlur:({matchUtilities:r,theme:e})=>{r({"backdrop-blur":t=>({"--tw-backdrop-blur":t.trim()===""?" ":`blur(${t})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ge,"backdrop-filter":ge})},{values:e("backdropBlur")})},backdropBrightness:({matchUtilities:r,theme:e})=>{r({"backdrop-brightness":t=>({"--tw-backdrop-brightness":`brightness(${t})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ge,"backdrop-filter":ge})},{values:e("backdropBrightness")})},backdropContrast:({matchUtilities:r,theme:e})=>{r({"backdrop-contrast":t=>({"--tw-backdrop-contrast":`contrast(${t})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ge,"backdrop-filter":ge})},{values:e("backdropContrast")})},backdropGrayscale:({matchUtilities:r,theme:e})=>{r({"backdrop-grayscale":t=>({"--tw-backdrop-grayscale":`grayscale(${t})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ge,"backdrop-filter":ge})},{values:e("backdropGrayscale")})},backdropHueRotate:({matchUtilities:r,theme:e})=>{r({"backdrop-hue-rotate":t=>({"--tw-backdrop-hue-rotate":`hue-rotate(${t})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ge,"backdrop-filter":ge})},{values:e("backdropHueRotate"),supportsNegativeValues:!0})},backdropInvert:({matchUtilities:r,theme:e})=>{r({"backdrop-invert":t=>({"--tw-backdrop-invert":`invert(${t})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ge,"backdrop-filter":ge})},{values:e("backdropInvert")})},backdropOpacity:({matchUtilities:r,theme:e})=>{r({"backdrop-opacity":t=>({"--tw-backdrop-opacity":`opacity(${t})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ge,"backdrop-filter":ge})},{values:e("backdropOpacity")})},backdropSaturate:({matchUtilities:r,theme:e})=>{r({"backdrop-saturate":t=>({"--tw-backdrop-saturate":`saturate(${t})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ge,"backdrop-filter":ge})},{values:e("backdropSaturate")})},backdropSepia:({matchUtilities:r,theme:e})=>{r({"backdrop-sepia":t=>({"--tw-backdrop-sepia":`sepia(${t})`,"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ge,"backdrop-filter":ge})},{values:e("backdropSepia")})},backdropFilter:({addDefaults:r,addUtilities:e})=>{r("backdrop-filter",{"--tw-backdrop-blur":" ","--tw-backdrop-brightness":" ","--tw-backdrop-contrast":" ","--tw-backdrop-grayscale":" ","--tw-backdrop-hue-rotate":" ","--tw-backdrop-invert":" ","--tw-backdrop-opacity":" ","--tw-backdrop-saturate":" ","--tw-backdrop-sepia":" "}),e({".backdrop-filter":{"@defaults backdrop-filter":{},"-webkit-backdrop-filter":ge,"backdrop-filter":ge},".backdrop-filter-none":{"-webkit-backdrop-filter":"none","backdrop-filter":"none"}})},transitionProperty:({matchUtilities:r,theme:e})=>{let t=e("transitionTimingFunction.DEFAULT"),i=e("transitionDuration.DEFAULT");r({transition:n=>({"transition-property":n,...n==="none"?{}:{"transition-timing-function":t,"transition-duration":i}})},{values:e("transitionProperty")})},transitionDelay:L("transitionDelay",[["delay",["transitionDelay"]]]),transitionDuration:L("transitionDuration",[["duration",["transitionDuration"]]],{filterDefault:!0}),transitionTimingFunction:L("transitionTimingFunction",[["ease",["transitionTimingFunction"]]],{filterDefault:!0}),willChange:L("willChange",[["will-change",["will-change"]]]),contain:({addDefaults:r,addUtilities:e})=>{let t="var(--tw-contain-size) var(--tw-contain-layout) var(--tw-contain-paint) var(--tw-contain-style)";r("contain",{"--tw-contain-size":" ","--tw-contain-layout":" ","--tw-contain-paint":" ","--tw-contain-style":" "}),e({".contain-none":{contain:"none"},".contain-content":{contain:"content"},".contain-strict":{contain:"strict"},".contain-size":{"@defaults contain":{},"--tw-contain-size":"size",contain:t},".contain-inline-size":{"@defaults contain":{},"--tw-contain-size":"inline-size",contain:t},".contain-layout":{"@defaults contain":{},"--tw-contain-layout":"layout",contain:t},".contain-paint":{"@defaults contain":{},"--tw-contain-paint":"paint",contain:t},".contain-style":{"@defaults contain":{},"--tw-contain-style":"style",contain:t}})},content:L("content",[["content",["--tw-content",["content","var(--tw-content)"]]]]),forcedColorAdjust:({addUtilities:r})=>{r({".forced-color-adjust-auto":{"forced-color-adjust":"auto"},".forced-color-adjust-none":{"forced-color-adjust":"none"}})}}});function h_(r){if(r===void 0)return!1;if(r==="true"||r==="1")return!0;if(r==="false"||r==="0")return!1;if(r==="*")return!0;let e=r.split(",").map(t=>t.split(":")[0]);return e.includes("-tailwindcss")?!1:!!e.includes("tailwindcss")}var Ze,wh,vh,es,No,gt,Ti,It=P(()=>{u();Ze=typeof m!="undefined"?{NODE_ENV:"production",DEBUG:h_(m.env.DEBUG)}:{NODE_ENV:"production",DEBUG:!1},wh=new Map,vh=new Map,es=new Map,No=new Map,gt=new String("*"),Ti=Symbol("__NONE__")});function cr(r){let e=[],t=!1;for(let i=0;i0)}var xh,kh,m_,Bo=P(()=>{u();xh=new Map([["{","}"],["[","]"],["(",")"]]),kh=new Map(Array.from(xh.entries()).map(([r,e])=>[e,r])),m_=new Set(['"',"'","`"])});function pr(r){let[e]=Sh(r);return e.forEach(([t,i])=>t.removeChild(i)),r.nodes.push(...e.map(([,t])=>t)),r}function Sh(r){let e=[],t=null;for(let i of r.nodes)if(i.type==="combinator")e=e.filter(([,n])=>jo(n).includes("jumpable")),t=null;else if(i.type==="pseudo"){g_(i)?(t=i,e.push([r,i,null])):t&&y_(i,t)?e.push([r,i,t]):t=null;for(let n of i.nodes??[]){let[s,a]=Sh(n);t=a||t,e.push(...s)}}return[e,t]}function Ah(r){return r.value.startsWith("::")||Fo[r.value]!==void 0}function g_(r){return Ah(r)&&jo(r).includes("terminal")}function y_(r,e){return r.type!=="pseudo"||Ah(r)?!1:jo(e).includes("actionable")}function jo(r){return Fo[r.value]??Fo.__default__}var Fo,ts=P(()=>{u();Fo={"::after":["terminal","jumpable"],"::backdrop":["terminal","jumpable"],"::before":["terminal","jumpable"],"::cue":["terminal"],"::cue-region":["terminal"],"::first-letter":["terminal","jumpable"],"::first-line":["terminal","jumpable"],"::grammar-error":["terminal"],"::marker":["terminal","jumpable"],"::part":["terminal","actionable"],"::placeholder":["terminal","jumpable"],"::selection":["terminal","jumpable"],"::slotted":["terminal"],"::spelling-error":["terminal"],"::target-text":["terminal"],"::file-selector-button":["terminal","actionable"],"::deep":["actionable"],"::v-deep":["actionable"],"::ng-deep":["actionable"],":after":["terminal","jumpable"],":before":["terminal","jumpable"],":first-letter":["terminal","jumpable"],":first-line":["terminal","jumpable"],":where":[],":is":[],":has":[],__default__:["terminal","actionable"]}});function dr(r,{context:e,candidate:t}){let i=e?.tailwindConfig.prefix??"",n=r.map(a=>{let o=(0,st.default)().astSync(a.format);return{...a,ast:a.respectPrefix?ur(i,o):o}}),s=st.default.root({nodes:[st.default.selector({nodes:[st.default.className({value:Te(t)})]})]});for(let{ast:a}of n)[s,a]=w_(s,a),a.walkNesting(o=>o.replaceWith(...s.nodes[0].nodes)),s=a;return s}function _h(r){let e=[];for(;r.prev()&&r.prev().type!=="combinator";)r=r.prev();for(;r&&r.type!=="combinator";)e.push(r),r=r.next();return e}function b_(r){return r.sort((e,t)=>e.type==="tag"&&t.type==="class"?-1:e.type==="class"&&t.type==="tag"?1:e.type==="class"&&t.type==="pseudo"&&t.value.startsWith("::")?-1:e.type==="pseudo"&&e.value.startsWith("::")&&t.type==="class"?1:r.index(e)-r.index(t)),r}function Uo(r,e){let t=!1;r.walk(i=>{if(i.type==="class"&&i.value===e)return t=!0,!1}),t||r.remove()}function rs(r,e,{context:t,candidate:i,base:n}){let s=t?.tailwindConfig?.separator??":";n=n??ve(i,s).pop();let a=(0,st.default)().astSync(r);if(a.walkClasses(f=>{f.raws&&f.value.includes(n)&&(f.raws.value=Te((0,Ch.default)(f.raws.value)))}),a.each(f=>Uo(f,n)),a.length===0)return null;let o=Array.isArray(e)?dr(e,{context:t,candidate:i}):e;if(o===null)return a.toString();let l=st.default.comment({value:"/*__simple__*/"}),c=st.default.comment({value:"/*__simple__*/"});return a.walkClasses(f=>{if(f.value!==n)return;let d=f.parent,p=o.nodes[0].nodes;if(d.nodes.length===1){f.replaceWith(...p);return}let h=_h(f);d.insertBefore(h[0],l),d.insertAfter(h[h.length-1],c);for(let v of p)d.insertBefore(h[0],v.clone());f.remove(),h=_h(l);let b=d.index(l);d.nodes.splice(b,h.length,...b_(st.default.selector({nodes:h})).nodes),l.remove(),c.remove()}),a.walkPseudos(f=>{f.value===zo&&f.replaceWith(f.nodes)}),a.each(f=>pr(f)),a.toString()}function w_(r,e){let t=[];return r.walkPseudos(i=>{i.value===zo&&t.push({pseudo:i,value:i.nodes[0].toString()})}),e.walkPseudos(i=>{if(i.value!==zo)return;let n=i.nodes[0].toString(),s=t.find(c=>c.value===n);if(!s)return;let a=[],o=i.next();for(;o&&o.type!=="combinator";)a.push(o),o=o.next();let l=o;s.pseudo.parent.insertAfter(s.pseudo,st.default.selector({nodes:a.map(c=>c.clone())})),i.remove(),a.forEach(c=>c.remove()),l&&l.type==="combinator"&&l.remove()}),[r,e]}var st,Ch,zo,Vo=P(()=>{u();st=pe(it()),Ch=pe(Pn());fr();Gn();ts();zt();zo=":merge"});function is(r,e){let t=(0,Ho.default)().astSync(r);return t.each(i=>{i.nodes.some(s=>s.type==="combinator")&&(i.nodes=[Ho.default.pseudo({value:":is",nodes:[i.clone()]})]),pr(i)}),`${e} ${t.toString()}`}var Ho,Wo=P(()=>{u();Ho=pe(it());ts()});function Go(r){return v_.transformSync(r)}function*x_(r){let e=1/0;for(;e>=0;){let t,i=!1;if(e===1/0&&r.endsWith("]")){let a=r.indexOf("[");r[a-1]==="-"?t=a-1:r[a-1]==="/"?(t=a-1,i=!0):t=-1}else e===1/0&&r.includes("/")?(t=r.lastIndexOf("/"),i=!0):t=r.lastIndexOf("-",e);if(t<0)break;let n=r.slice(0,t),s=r.slice(i?t:t+1);e=t-1,!(n===""||s==="/")&&(yield[n,s])}}function k_(r,e){if(r.length===0||e.tailwindConfig.prefix==="")return r;for(let t of r){let[i]=t;if(i.options.respectPrefix){let n=ee.root({nodes:[t[1].clone()]}),s=t[1].raws.tailwind.classCandidate;n.walkRules(a=>{let o=s.startsWith("-");a.selector=ur(e.tailwindConfig.prefix,a.selector,o)}),t[1]=n.nodes[0]}}return r}function S_(r,e){if(r.length===0)return r;let t=[];function i(n){return n.parent&&n.parent.type==="atrule"&&n.parent.name==="keyframes"}for(let[n,s]of r){let a=ee.root({nodes:[s.clone()]});a.walkRules(o=>{if(i(o))return;let l=(0,ns.default)().astSync(o.selector);l.each(c=>Uo(c,e)),Qf(l,c=>c===e?`!${c}`:c),o.selector=l.toString(),o.walkDecls(c=>c.important=!0)}),t.push([{...n,important:!0},a.nodes[0]])}return t}function A_(r,e,t){if(e.length===0)return e;let i={modifier:null,value:Ti};{let[n,...s]=ve(r,"/");if(s.length>1&&(n=n+"/"+s.slice(0,-1).join("/"),s=s.slice(-1)),s.length&&!t.variantMap.has(r)&&(r=n,i.modifier=s[0],!we(t.tailwindConfig,"generalizedModifiers")))return[]}if(r.endsWith("]")&&!r.startsWith("[")){let n=/(.)(-?)\[(.*)\]/g.exec(r);if(n){let[,s,a,o]=n;if(s==="@"&&a==="-")return[];if(s!=="@"&&a==="")return[];r=r.replace(`${a}[${o}]`,""),i.value=o}}if(Ko(r)&&!t.variantMap.has(r)){let n=t.offsets.recordVariant(r),s=K(r.slice(1,-1)),a=ve(s,",");if(a.length>1)return[];if(!a.every(ls))return[];let o=a.map((l,c)=>[t.offsets.applyParallelOffset(n,c),Ri(l.trim())]);t.variantMap.set(r,o)}if(t.variantMap.has(r)){let n=Ko(r),s=t.variantOptions.get(r)?.[Pt]??{},a=t.variantMap.get(r).slice(),o=[],l=(()=>!(n||s.respectPrefix===!1))();for(let[c,f]of e){if(c.layer==="user")continue;let d=ee.root({nodes:[f.clone()]});for(let[p,h,b]of a){let w=function(){v.raws.neededBackup||(v.raws.neededBackup=!0,v.walkRules(T=>T.raws.originalSelector=T.selector))},k=function(T){return w(),v.each(B=>{B.type==="rule"&&(B.selectors=B.selectors.map(N=>T({get className(){return Go(N)},selector:N})))}),v},v=(b??d).clone(),y=[],S=h({get container(){return w(),v},separator:t.tailwindConfig.separator,modifySelectors:k,wrap(T){let B=v.nodes;v.removeAll(),T.append(B),v.append(T)},format(T){y.push({format:T,respectPrefix:l})},args:i});if(Array.isArray(S)){for(let[T,B]of S.entries())a.push([t.offsets.applyParallelOffset(p,T),B,v.clone()]);continue}if(typeof S=="string"&&y.push({format:S,respectPrefix:l}),S===null)continue;v.raws.neededBackup&&(delete v.raws.neededBackup,v.walkRules(T=>{let B=T.raws.originalSelector;if(!B||(delete T.raws.originalSelector,B===T.selector))return;let N=T.selector,R=(0,ns.default)(F=>{F.walkClasses(Y=>{Y.value=`${r}${t.tailwindConfig.separator}${Y.value}`})}).processSync(B);y.push({format:N.replace(R,"&"),respectPrefix:l}),T.selector=B})),v.nodes[0].raws.tailwind={...v.nodes[0].raws.tailwind,parentLayer:c.layer};let E=[{...c,sort:t.offsets.applyVariantOffset(c.sort,p,Object.assign(i,t.variantOptions.get(r))),collectedFormats:(c.collectedFormats??[]).concat(y)},v.nodes[0]];o.push(E)}}return o}return[]}function Qo(r,e,t={}){return!ke(r)&&!Array.isArray(r)?[[r],t]:Array.isArray(r)?Qo(r[0],e,r[1]):(e.has(r)||e.set(r,lr(r)),[e.get(r),t])}function __(r){return C_.test(r)}function E_(r){if(!r.includes("://"))return!1;try{let e=new URL(r);return e.scheme!==""&&e.host!==""}catch(e){return!1}}function Eh(r){let e=!0;return r.walkDecls(t=>{if(!Oh(t.prop,t.value))return e=!1,!1}),e}function Oh(r,e){if(E_(`${r}:${e}`))return!1;try{return ee.parse(`a{${r}:${e}}`).toResult(),!0}catch(t){return!1}}function O_(r,e){let[,t,i]=r.match(/^\[([a-zA-Z0-9-_]+):(\S+)\]$/)??[];if(i===void 0||!__(t)||!cr(i))return null;let n=K(i,{property:t});return Oh(t,n)?[[{sort:e.offsets.arbitraryProperty(r),layer:"utilities",options:{respectImportant:!0}},()=>({[$o(r)]:{[t]:n}})]]:null}function*T_(r,e){e.candidateRuleMap.has(r)&&(yield[e.candidateRuleMap.get(r),"DEFAULT"]),yield*function*(o){o!==null&&(yield[o,"DEFAULT"])}(O_(r,e));let t=r,i=!1,n=e.tailwindConfig.prefix,s=n.length,a=t.startsWith(n)||t.startsWith(`-${n}`);t[s]==="-"&&a&&(i=!0,t=n+t.slice(s+1)),i&&e.candidateRuleMap.has(t)&&(yield[e.candidateRuleMap.get(t),"-DEFAULT"]);for(let[o,l]of x_(t))e.candidateRuleMap.has(o)&&(yield[e.candidateRuleMap.get(o),i?`-${l}`:l])}function R_(r,e){return r===gt?[gt]:ve(r,e)}function*P_(r,e){for(let t of r)t[1].raws.tailwind={...t[1].raws.tailwind,classCandidate:e,preserveSource:t[0].options?.preserveSource??!1},yield t}function*Yo(r,e){let t=e.tailwindConfig.separator,[i,...n]=R_(r,t).reverse(),s=!1;i.startsWith("!")&&(s=!0,i=i.slice(1));for(let a of T_(i,e)){let o=[],l=new Map,[c,f]=a,d=c.length===1;for(let[p,h]of c){let b=[];if(typeof h=="function")for(let v of[].concat(h(f,{isOnlyPlugin:d}))){let[y,w]=Qo(v,e.postCssNodeCache);for(let k of y)b.push([{...p,options:{...p.options,...w}},k])}else if(f==="DEFAULT"||f==="-DEFAULT"){let v=h,[y,w]=Qo(v,e.postCssNodeCache);for(let k of y)b.push([{...p,options:{...p.options,...w}},k])}if(b.length>0){let v=Array.from(ta(p.options?.types??[],f,p.options??{},e.tailwindConfig)).map(([y,w])=>w);v.length>0&&l.set(b,v),o.push(b)}}if(Ko(f)){if(o.length>1){let b=function(y){return y.length===1?y[0]:y.find(w=>{let k=l.get(w);return w.some(([{options:S},E])=>Eh(E)?S.types.some(({type:T,preferOnConflict:B})=>k.includes(T)&&B):!1)})},[p,h]=o.reduce((y,w)=>(w.some(([{options:S}])=>S.types.some(({type:E})=>E==="any"))?y[0].push(w):y[1].push(w),y),[[],[]]),v=b(h)??b(p);if(v)o=[v];else{let y=o.map(k=>new Set([...l.get(k)??[]]));for(let k of y)for(let S of k){let E=!1;for(let T of y)k!==T&&T.has(S)&&(T.delete(S),E=!0);E&&k.delete(S)}let w=[];for(let[k,S]of y.entries())for(let E of S){let T=o[k].map(([,B])=>B).flat().map(B=>B.toString().split(` +`).slice(1,-1).map(N=>N.trim()).map(N=>` ${N}`).join(` +`)).join(` + +`);w.push(` Use \`${r.replace("[",`[${E}:`)}\` for \`${T.trim()}\``);break}G.warn([`The class \`${r}\` is ambiguous and matches multiple utilities.`,...w,`If this is content and not a class, replace it with \`${r.replace("[","[").replace("]","]")}\` to silence this warning.`]);continue}}o=o.map(p=>p.filter(h=>Eh(h[1])))}o=o.flat(),o=Array.from(P_(o,i)),o=k_(o,e),s&&(o=S_(o,i));for(let p of n)o=A_(p,o,e);for(let p of o)p[1].raws.tailwind={...p[1].raws.tailwind,candidate:r},p=I_(p,{context:e,candidate:r}),p!==null&&(yield p)}}function I_(r,{context:e,candidate:t}){if(!r[0].collectedFormats)return r;let i=!0,n;try{n=dr(r[0].collectedFormats,{context:e,candidate:t})}catch{return null}let s=ee.root({nodes:[r[1].clone()]});return s.walkRules(a=>{if(!ss(a))try{let o=rs(a.selector,n,{candidate:t,context:e});if(o===null){a.remove();return}a.selector=o}catch{return i=!1,!1}}),!i||s.nodes.length===0?null:(r[1]=s.nodes[0],r)}function ss(r){return r.parent&&r.parent.type==="atrule"&&r.parent.name==="keyframes"}function D_(r){if(r===!0)return e=>{ss(e)||e.walkDecls(t=>{t.parent.type==="rule"&&!ss(t.parent)&&(t.important=!0)})};if(typeof r=="string")return e=>{ss(e)||(e.selectors=e.selectors.map(t=>is(t,r)))}}function as(r,e,t=!1){let i=[],n=D_(e.tailwindConfig.important);for(let s of r){if(e.notClassCache.has(s))continue;if(e.candidateRuleCache.has(s)){i=i.concat(Array.from(e.candidateRuleCache.get(s)));continue}let a=Array.from(Yo(s,e));if(a.length===0){e.notClassCache.add(s);continue}e.classCache.set(s,a);let o=e.candidateRuleCache.get(s)??new Set;e.candidateRuleCache.set(s,o);for(let l of a){let[{sort:c,options:f},d]=l;if(f.respectImportant&&n){let h=ee.root({nodes:[d.clone()]});h.walkRules(n),d=h.nodes[0]}let p=[c,t?d.clone():d];o.add(p),e.ruleCache.add(p),i.push(p)}}return i}function Ko(r){return r.startsWith("[")&&r.endsWith("]")}var ns,v_,C_,os=P(()=>{u();Ot();ns=pe(it());qo();Kt();Gn();Fr();Be();It();Vo();Lo();Br();Oi();Bo();zt();ct();Wo();v_=(0,ns.default)(r=>r.first.filter(({type:e})=>e==="class").pop().value);C_=/^[a-z_-]/});var Th,Rh=P(()=>{u();Th={}});function q_(r){try{return Th.createHash("md5").update(r,"utf-8").digest("binary")}catch(e){return""}}function Ph(r,e){let t=e.toString();if(!t.includes("@tailwind"))return!1;let i=No.get(r),n=q_(t),s=i!==n;return No.set(r,n),s}var Ih=P(()=>{u();Rh();It()});function us(r){return(r>0n)-(r<0n)}var Dh=P(()=>{u()});function qh(r,e){let t=0n,i=0n;for(let[n,s]of e)r&n&&(t=t|n,i=i|s);return r&~t|i}var $h=P(()=>{u()});function Lh(r){let e=null;for(let t of r)e=e??t,e=e>t?e:t;return e}function $_(r,e){let t=r.length,i=e.length,n=t{u();Dh();$h();Xo=class{constructor(){this.offsets={defaults:0n,base:0n,components:0n,utilities:0n,variants:0n,user:0n},this.layerPositions={defaults:0n,base:1n,components:2n,utilities:3n,user:4n,variants:5n},this.reservedVariantBits=0n,this.variantOffsets=new Map}create(e){return{layer:e,parentLayer:e,arbitrary:0n,variants:0n,parallelIndex:0n,index:this.offsets[e]++,propertyOffset:0n,property:"",options:[]}}arbitraryProperty(e){return{...this.create("utilities"),arbitrary:1n,property:e}}forVariant(e,t=0){let i=this.variantOffsets.get(e);if(i===void 0)throw new Error(`Cannot find offset for unknown variant ${e}`);return{...this.create("variants"),variants:i<n.startsWith("[")).sort(([n],[s])=>$_(n,s)),t=e.map(([,n])=>n).sort((n,s)=>us(n-s));return e.map(([,n],s)=>[n,t[s]]).filter(([n,s])=>n!==s)}remapArbitraryVariantOffsets(e){let t=this.recalculateVariantOffsets();return t.length===0?e:e.map(i=>{let[n,s]=i;return n={...n,variants:qh(n.variants,t)},[n,s]})}sortArbitraryProperties(e){let t=new Set;for(let[a]of e)a.arbitrary===1n&&t.add(a.property);if(t.size===0)return e;let i=Array.from(t).sort(),n=new Map,s=1n;for(let a of i)n.set(a,s++);return e.map(a=>{let[o,l]=a;return o={...o,propertyOffset:n.get(o.property)??0n},[o,l]})}sort(e){return e=this.remapArbitraryVariantOffsets(e),e=this.sortArbitraryProperties(e),e.sort(([t],[i])=>us(this.compare(t,i)))}}});function tl(r,e){let t=r.tailwindConfig.prefix;return typeof t=="function"?t(e):t+e}function Bh({type:r="any",...e}){let t=[].concat(r);return{...e,types:t.map(i=>Array.isArray(i)?{type:i[0],...i[1]}:{type:i,preferOnConflict:!1})}}function L_(r){let e=[],t="",i=0;for(let n=0;n0&&e.push(t.trim()),e=e.filter(n=>n!==""),e}function M_(r,e,{before:t=[]}={}){if(t=[].concat(t),t.length<=0){r.push(e);return}let i=r.length-1;for(let n of t){let s=r.indexOf(n);s!==-1&&(i=Math.min(i,s))}r.splice(i,0,e)}function Fh(r){return Array.isArray(r)?r.flatMap(e=>!Array.isArray(e)&&!ke(e)?e:lr(e)):Fh([r])}function N_(r,e){return(0,Zo.default)(i=>{let n=[];return e&&e(i),i.walkClasses(s=>{n.push(s.value)}),n}).transformSync(r)}function B_(r){r.walkPseudos(e=>{e.value===":not"&&e.remove()})}function F_(r,e={containsNonOnDemandable:!1},t=0){let i=[],n=[];r.type==="rule"?n.push(...r.selectors):r.type==="atrule"&&r.walkRules(s=>n.push(...s.selectors));for(let s of n){let a=N_(s,B_);a.length===0&&(e.containsNonOnDemandable=!0);for(let o of a)i.push(o)}return t===0?[e.containsNonOnDemandable||i.length===0,i]:i}function fs(r){return Fh(r).flatMap(e=>{let t=new Map,[i,n]=F_(e);return i&&n.unshift(gt),n.map(s=>(t.has(e)||t.set(e,e),[s,t.get(e)]))})}function ls(r){return r.startsWith("@")||r.includes("&")}function Ri(r){r=r.replace(/\n+/g,"").replace(/\s{1,}/g," ").trim();let e=L_(r).map(t=>{if(!t.startsWith("@"))return({format:s})=>s(t);let[,i,n]=/@(\S*)( .+|[({].*)?/g.exec(t);return({wrap:s})=>s(ee.atRule({name:i,params:n?.trim()??""}))}).reverse();return t=>{for(let i of e)i(t)}}function j_(r,e,{variantList:t,variantMap:i,offsets:n,classList:s}){function a(p,h){return p?(0,Nh.default)(r,p,h):r}function o(p){return ur(r.prefix,p)}function l(p,h){return p===gt?gt:h.respectPrefix?e.tailwindConfig.prefix+p:p}function c(p,h,b={}){let v=kt(p),y=a(["theme",...v],h);return mt(v[0])(y,b)}let f=0,d={postcss:ee,prefix:o,e:Te,config:a,theme:c,corePlugins:p=>Array.isArray(r.corePlugins)?r.corePlugins.includes(p):a(["corePlugins",p],!0),variants:()=>[],addBase(p){for(let[h,b]of fs(p)){let v=l(h,{}),y=n.create("base");e.candidateRuleMap.has(v)||e.candidateRuleMap.set(v,[]),e.candidateRuleMap.get(v).push([{sort:y,layer:"base"},b])}},addDefaults(p,h){let b={[`@defaults ${p}`]:h};for(let[v,y]of fs(b)){let w=l(v,{});e.candidateRuleMap.has(w)||e.candidateRuleMap.set(w,[]),e.candidateRuleMap.get(w).push([{sort:n.create("defaults"),layer:"defaults"},y])}},addComponents(p,h){h=Object.assign({},{preserveSource:!1,respectPrefix:!0,respectImportant:!1},Array.isArray(h)?{}:h);for(let[v,y]of fs(p)){let w=l(v,h);s.add(w),e.candidateRuleMap.has(w)||e.candidateRuleMap.set(w,[]),e.candidateRuleMap.get(w).push([{sort:n.create("components"),layer:"components",options:h},y])}},addUtilities(p,h){h=Object.assign({},{preserveSource:!1,respectPrefix:!0,respectImportant:!0},Array.isArray(h)?{}:h);for(let[v,y]of fs(p)){let w=l(v,h);s.add(w),e.candidateRuleMap.has(w)||e.candidateRuleMap.set(w,[]),e.candidateRuleMap.get(w).push([{sort:n.create("utilities"),layer:"utilities",options:h},y])}},matchUtilities:function(p,h){h=Bh({...{respectPrefix:!0,respectImportant:!0,modifiers:!1},...h});let v=n.create("utilities");for(let y in p){let S=function(T,{isOnlyPlugin:B}){let[N,R,F]=ea(h.types,T,h,r);if(N===void 0)return[];if(!h.types.some(({type:U})=>U===R))if(B)G.warn([`Unnecessary typehint \`${R}\` in \`${y}-${T}\`.`,`You can safely update it to \`${y}-${T.replace(R+":","")}\`.`]);else return[];if(!cr(N))return[];let Y={get modifier(){return h.modifiers||G.warn(`modifier-used-without-options-for-${y}`,["Your plugin must set `modifiers: true` in its options to support modifiers."]),F}},_=we(r,"generalizedModifiers");return[].concat(_?k(N,Y):k(N)).filter(Boolean).map(U=>({[Qn(y,T)]:U}))},w=l(y,h),k=p[y];s.add([w,h]);let E=[{sort:v,layer:"utilities",options:h},S];e.candidateRuleMap.has(w)||e.candidateRuleMap.set(w,[]),e.candidateRuleMap.get(w).push(E)}},matchComponents:function(p,h){h=Bh({...{respectPrefix:!0,respectImportant:!1,modifiers:!1},...h});let v=n.create("components");for(let y in p){let S=function(T,{isOnlyPlugin:B}){let[N,R,F]=ea(h.types,T,h,r);if(N===void 0)return[];if(!h.types.some(({type:U})=>U===R))if(B)G.warn([`Unnecessary typehint \`${R}\` in \`${y}-${T}\`.`,`You can safely update it to \`${y}-${T.replace(R+":","")}\`.`]);else return[];if(!cr(N))return[];let Y={get modifier(){return h.modifiers||G.warn(`modifier-used-without-options-for-${y}`,["Your plugin must set `modifiers: true` in its options to support modifiers."]),F}},_=we(r,"generalizedModifiers");return[].concat(_?k(N,Y):k(N)).filter(Boolean).map(U=>({[Qn(y,T)]:U}))},w=l(y,h),k=p[y];s.add([w,h]);let E=[{sort:v,layer:"components",options:h},S];e.candidateRuleMap.has(w)||e.candidateRuleMap.set(w,[]),e.candidateRuleMap.get(w).push(E)}},addVariant(p,h,b={}){h=[].concat(h).map(v=>{if(typeof v!="string")return(y={})=>{let{args:w,modifySelectors:k,container:S,separator:E,wrap:T,format:B}=y,N=v(Object.assign({modifySelectors:k,container:S,separator:E},b.type===Jo.MatchVariant&&{args:w,wrap:T,format:B}));if(typeof N=="string"&&!ls(N))throw new Error(`Your custom variant \`${p}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`);return Array.isArray(N)?N.filter(R=>typeof R=="string").map(R=>Ri(R)):N&&typeof N=="string"&&Ri(N)(y)};if(!ls(v))throw new Error(`Your custom variant \`${p}\` has an invalid format string. Make sure it's an at-rule or contains a \`&\` placeholder.`);return Ri(v)}),M_(t,p,b),i.set(p,h),e.variantOptions.set(p,b)},matchVariant(p,h,b){let v=b?.id??++f,y=p==="@",w=we(r,"generalizedModifiers");for(let[S,E]of Object.entries(b?.values??{}))S!=="DEFAULT"&&d.addVariant(y?`${p}${S}`:`${p}-${S}`,({args:T,container:B})=>h(E,w?{modifier:T?.modifier,container:B}:{container:B}),{...b,value:E,id:v,type:Jo.MatchVariant,variantInfo:el.Base});let k="DEFAULT"in(b?.values??{});d.addVariant(p,({args:S,container:E})=>S?.value===Ti&&!k?null:h(S?.value===Ti?b.values.DEFAULT:S?.value??(typeof S=="string"?S:""),w?{modifier:S?.modifier,container:E}:{container:E}),{...b,id:v,type:Jo.MatchVariant,variantInfo:el.Dynamic})}};return d}function cs(r){return rl.has(r)||rl.set(r,new Map),rl.get(r)}function jh(r,e){let t=!1,i=new Map;for(let n of r){if(!n)continue;let s=oa.parse(n),a=s.hash?s.href.replace(s.hash,""):s.href;a=s.search?a.replace(s.search,""):a;let o=be.statSync(decodeURIComponent(a),{throwIfNoEntry:!1})?.mtimeMs;!o||((!e.has(n)||o>e.get(n))&&(t=!0),i.set(n,o))}return[t,i]}function zh(r){r.walkAtRules(e=>{["responsive","variants"].includes(e.name)&&(zh(e),e.before(e.nodes),e.remove())})}function z_(r){let e=[];return r.each(t=>{t.type==="atrule"&&["responsive","variants"].includes(t.name)&&(t.name="layer",t.params="utilities")}),r.walkAtRules("layer",t=>{if(zh(t),t.params==="base"){for(let i of t.nodes)e.push(function({addBase:n}){n(i,{respectPrefix:!1})});t.remove()}else if(t.params==="components"){for(let i of t.nodes)e.push(function({addComponents:n}){n(i,{respectPrefix:!1,preserveSource:!0})});t.remove()}else if(t.params==="utilities"){for(let i of t.nodes)e.push(function({addUtilities:n}){n(i,{respectPrefix:!1,preserveSource:!0})});t.remove()}}),e}function U_(r,e){let t=Object.entries({...se,...yh}).map(([l,c])=>r.tailwindConfig.corePlugins.includes(l)?c:null).filter(Boolean),i=r.tailwindConfig.plugins.map(l=>(l.__isOptionsFunction&&(l=l()),typeof l=="function"?l:l.handler)),n=z_(e),s=[se.childVariant,se.pseudoElementVariants,se.pseudoClassVariants,se.hasVariants,se.ariaVariants,se.dataVariants],a=[se.supportsVariants,se.reducedMotionVariants,se.prefersContrastVariants,se.screenVariants,se.orientationVariants,se.directionVariants,se.darkVariants,se.forcedColorsVariants,se.printVariant];return(r.tailwindConfig.darkMode==="class"||Array.isArray(r.tailwindConfig.darkMode)&&r.tailwindConfig.darkMode[0]==="class")&&(a=[se.supportsVariants,se.reducedMotionVariants,se.prefersContrastVariants,se.darkVariants,se.screenVariants,se.orientationVariants,se.directionVariants,se.forcedColorsVariants,se.printVariant]),[...t,...s,...i,...a,...n]}function V_(r,e){let t=[],i=new Map;e.variantMap=i;let n=new Xo;e.offsets=n;let s=new Set,a=j_(e.tailwindConfig,e,{variantList:t,variantMap:i,offsets:n,classList:s});for(let f of r)if(Array.isArray(f))for(let d of f)d(a);else f?.(a);n.recordVariants(t,f=>i.get(f).length);for(let[f,d]of i.entries())e.variantMap.set(f,d.map((p,h)=>[n.forVariant(f,h),p]));let o=(e.tailwindConfig.safelist??[]).filter(Boolean);if(o.length>0){let f=[];for(let d of o){if(typeof d=="string"){e.changedContent.push({content:d,extension:"html"});continue}if(d instanceof RegExp){G.warn("root-regex",["Regular expressions in `safelist` work differently in Tailwind CSS v3.0.","Update your `safelist` configuration to eliminate this warning.","https://tailwindcss.com/docs/content-configuration#safelisting-classes"]);continue}f.push(d)}if(f.length>0){let d=new Map,p=e.tailwindConfig.prefix.length,h=f.some(b=>b.pattern.source.includes("!"));for(let b of s){let v=Array.isArray(b)?(()=>{let[y,w]=b,S=Object.keys(w?.values??{}).map(E=>Ei(y,E));return w?.supportsNegativeValues&&(S=[...S,...S.map(E=>"-"+E)],S=[...S,...S.map(E=>E.slice(0,p)+"-"+E.slice(p))]),w.types.some(({type:E})=>E==="color")&&(S=[...S,...S.flatMap(E=>Object.keys(e.tailwindConfig.theme.opacity).map(T=>`${E}/${T}`))]),h&&w?.respectImportant&&(S=[...S,...S.map(E=>"!"+E)]),S})():[b];for(let y of v)for(let{pattern:w,variants:k=[]}of f)if(w.lastIndex=0,d.has(w)||d.set(w,0),!!w.test(y)){d.set(w,d.get(w)+1),e.changedContent.push({content:y,extension:"html"});for(let S of k)e.changedContent.push({content:S+e.tailwindConfig.separator+y,extension:"html"})}}for(let[b,v]of d.entries())v===0&&G.warn([`The safelist pattern \`${b}\` doesn't match any Tailwind CSS classes.`,"Fix this pattern or remove it from your `safelist` configuration.","https://tailwindcss.com/docs/content-configuration#safelisting-classes"])}}let l=[].concat(e.tailwindConfig.darkMode??"media")[1]??"dark",c=[tl(e,l),tl(e,"group"),tl(e,"peer")];e.getClassOrder=function(d){let p=[...d].sort((y,w)=>y===w?0:y[y,null])),b=as(new Set(p),e,!0);b=e.offsets.sort(b);let v=BigInt(c.length);for(let[,y]of b){let w=y.raws.tailwind.candidate;h.set(w,h.get(w)??v++)}return d.map(y=>{let w=h.get(y)??null,k=c.indexOf(y);return w===null&&k!==-1&&(w=BigInt(k)),[y,w]})},e.getClassList=function(d={}){let p=[];for(let h of s)if(Array.isArray(h)){let[b,v]=h,y=[],w=Object.keys(v?.modifiers??{});v?.types?.some(({type:E})=>E==="color")&&w.push(...Object.keys(e.tailwindConfig.theme.opacity??{}));let k={modifiers:w},S=d.includeMetadata&&w.length>0;for(let[E,T]of Object.entries(v?.values??{})){if(T==null)continue;let B=Ei(b,E);if(p.push(S?[B,k]:B),v?.supportsNegativeValues&&xt(T)){let N=Ei(b,`-${E}`);y.push(S?[N,k]:N)}}p.push(...y)}else p.push(h);return p},e.getVariants=function(){let d=Math.random().toString(36).substring(7).toUpperCase(),p=[];for(let[h,b]of e.variantOptions.entries())b.variantInfo!==el.Base&&p.push({name:h,isArbitrary:b.type===Symbol.for("MATCH_VARIANT"),values:Object.keys(b.values??{}),hasDash:h!=="@",selectors({modifier:v,value:y}={}){let w=`TAILWINDPLACEHOLDER${d}`,k=ee.rule({selector:`.${w}`}),S=ee.root({nodes:[k.clone()]}),E=S.toString(),T=(e.variantMap.get(h)??[]).flatMap(([le,A])=>A),B=[];for(let le of T){let A=[],C={args:{modifier:v,value:b.values?.[y]??y},separator:e.tailwindConfig.separator,modifySelectors(V){return S.each(Ee=>{Ee.type==="rule"&&(Ee.selectors=Ee.selectors.map(Ie=>V({get className(){return Go(Ie)},selector:Ie})))}),S},format(V){A.push(V)},wrap(V){A.push(`@${V.name} ${V.params} { & }`)},container:S},he=le(C);if(A.length>0&&B.push(A),Array.isArray(he))for(let V of he)A=[],V(C),B.push(A)}let N=[],R=S.toString();E!==R&&(S.walkRules(le=>{let A=le.selector,C=(0,Zo.default)(he=>{he.walkClasses(V=>{V.value=`${h}${e.tailwindConfig.separator}${V.value}`})}).processSync(A);N.push(A.replace(C,"&").replace(w,"&"))}),S.walkAtRules(le=>{N.push(`@${le.name} (${le.params}) { & }`)}));let F=!(y in(b.values??{})),Y=b[Pt]??{},_=(()=>!(F||Y.respectPrefix===!1))();B=B.map(le=>le.map(A=>({format:A,respectPrefix:_}))),N=N.map(le=>({format:le,respectPrefix:_}));let Q={candidate:w,context:e},U=B.map(le=>rs(`.${w}`,dr(le,Q),Q).replace(`.${w}`,"&").replace("{ & }","").trim());return N.length>0&&U.push(dr(N,Q).toString().replace(`.${w}`,"&")),U}});return p}}function Uh(r,e){!r.classCache.has(e)||(r.notClassCache.add(e),r.classCache.delete(e),r.applyClassCache.delete(e),r.candidateRuleMap.delete(e),r.candidateRuleCache.delete(e),r.stylesheetCache=null)}function H_(r,e){let t=e.raws.tailwind.candidate;if(!!t){for(let i of r.ruleCache)i[1].raws.tailwind.candidate===t&&r.ruleCache.delete(i);Uh(r,t)}}function il(r,e=[],t=ee.root()){let i={disposables:[],ruleCache:new Set,candidateRuleCache:new Map,classCache:new Map,applyClassCache:new Map,notClassCache:new Set(r.blocklist??[]),postCssNodeCache:new Map,candidateRuleMap:new Map,tailwindConfig:r,changedContent:e,variantMap:new Map,stylesheetCache:null,variantOptions:new Map,markInvalidUtilityCandidate:s=>Uh(i,s),markInvalidUtilityNode:s=>H_(i,s)},n=U_(i,t);return V_(n,i),i}function Vh(r,e,t,i,n,s){let a=e.opts.from,o=i!==null;Ze.DEBUG&&console.log("Source path:",a);let l;if(o&&hr.has(a))l=hr.get(a);else if(Pi.has(n)){let p=Pi.get(n);Dt.get(p).add(a),hr.set(a,p),l=p}let c=Ph(a,r);if(l){let[p,h]=jh([...s],cs(l));if(!p&&!c)return[l,!1,h]}if(hr.has(a)){let p=hr.get(a);if(Dt.has(p)&&(Dt.get(p).delete(a),Dt.get(p).size===0)){Dt.delete(p);for(let[h,b]of Pi)b===p&&Pi.delete(h);for(let h of p.disposables.splice(0))h(p)}}Ze.DEBUG&&console.log("Setting up new context...");let f=il(t,[],r);Object.assign(f,{userConfigPath:i});let[,d]=jh([...s],cs(f));return Pi.set(n,f),hr.set(a,f),Dt.has(f)||Dt.set(f,new Set),Dt.get(f).add(a),[f,!0,d]}var Nh,Zo,Pt,Jo,el,rl,hr,Pi,Dt,Oi=P(()=>{u();ft();la();Ot();Nh=pe(Ra()),Zo=pe(it());Ci();qo();Gn();Kt();fr();Lo();Fr();bh();It();It();Yi();Be();Gi();Bo();os();Ih();Mh();ct();Vo();Pt=Symbol(),Jo={AddVariant:Symbol.for("ADD_VARIANT"),MatchVariant:Symbol.for("MATCH_VARIANT")},el={Base:1<<0,Dynamic:1<<1};rl=new WeakMap;hr=wh,Pi=vh,Dt=es});function nl(r){return r.ignore?[]:r.glob?m.env.ROLLUP_WATCH==="true"?[{type:"dependency",file:r.base}]:[{type:"dir-dependency",dir:r.base,glob:r.glob}]:[{type:"dependency",file:r.base}]}var Hh=P(()=>{u()});function Wh(r,e){return{handler:r,config:e}}var Gh,Qh=P(()=>{u();Wh.withOptions=function(r,e=()=>({})){let t=function(i){return{__options:i,handler:r(i),config:e(i)}};return t.__isOptionsFunction=!0,t.__pluginFunction=r,t.__configFunction=e,t};Gh=Wh});var sl={};Ge(sl,{default:()=>W_});var W_,al=P(()=>{u();Qh();W_=Gh});var Kh=x((z4,Yh)=>{u();var G_=(al(),sl).default,Q_={overflow:"hidden",display:"-webkit-box","-webkit-box-orient":"vertical"},Y_=G_(function({matchUtilities:r,addUtilities:e,theme:t,variants:i}){let n=t("lineClamp");r({"line-clamp":s=>({...Q_,"-webkit-line-clamp":`${s}`})},{values:n}),e([{".line-clamp-none":{"-webkit-line-clamp":"unset"}}],i("lineClamp"))},{theme:{lineClamp:{1:"1",2:"2",3:"3",4:"4",5:"5",6:"6"}},variants:{lineClamp:["responsive"]}});Yh.exports=Y_});function ol(r){r.content.files.length===0&&G.warn("content-problems",["The `content` option in your Tailwind CSS configuration is missing or empty.","Configure your content sources or your generated CSS will be missing styles.","https://tailwindcss.com/docs/content-configuration"]);try{let e=Kh();r.plugins.includes(e)&&(G.warn("line-clamp-in-core",["As of Tailwind CSS v3.3, the `@tailwindcss/line-clamp` plugin is now included by default.","Remove it from the `plugins` array in your configuration to eliminate this warning."]),r.plugins=r.plugins.filter(t=>t!==e))}catch{}return r}var Xh=P(()=>{u();Be()});var Zh,Jh=P(()=>{u();Zh=()=>!1});var ps,em=P(()=>{u();ps={sync:r=>[].concat(r),generateTasks:r=>[{dynamic:!1,base:".",negative:[],positive:[].concat(r),patterns:[].concat(r)}],escapePath:r=>r}});var ll,tm=P(()=>{u();ll=r=>r});var rm,im=P(()=>{u();rm=()=>""});function nm(r){let e=r,t=rm(r);return t!=="."&&(e=r.substr(t.length),e.charAt(0)==="/"&&(e=e.substr(1))),e.substr(0,2)==="./"?e=e.substr(2):e.charAt(0)==="/"&&(e=e.substr(1)),{base:t,glob:e}}var sm=P(()=>{u();im()});var ds=x(Ve=>{u();"use strict";Ve.isInteger=r=>typeof r=="number"?Number.isInteger(r):typeof r=="string"&&r.trim()!==""?Number.isInteger(Number(r)):!1;Ve.find=(r,e)=>r.nodes.find(t=>t.type===e);Ve.exceedsLimit=(r,e,t=1,i)=>i===!1||!Ve.isInteger(r)||!Ve.isInteger(e)?!1:(Number(e)-Number(r))/Number(t)>=i;Ve.escapeNode=(r,e=0,t)=>{let i=r.nodes[e];!i||(t&&i.type===t||i.type==="open"||i.type==="close")&&i.escaped!==!0&&(i.value="\\"+i.value,i.escaped=!0)};Ve.encloseBrace=r=>r.type!=="brace"?!1:r.commas>>0+r.ranges>>0==0?(r.invalid=!0,!0):!1;Ve.isInvalidBrace=r=>r.type!=="brace"?!1:r.invalid===!0||r.dollar?!0:r.commas>>0+r.ranges>>0==0||r.open!==!0||r.close!==!0?(r.invalid=!0,!0):!1;Ve.isOpenOrClose=r=>r.type==="open"||r.type==="close"?!0:r.open===!0||r.close===!0;Ve.reduce=r=>r.reduce((e,t)=>(t.type==="text"&&e.push(t.value),t.type==="range"&&(t.type="text"),e),[]);Ve.flatten=(...r)=>{let e=[],t=i=>{for(let n=0;n{u();"use strict";var am=ds();om.exports=(r,e={})=>{let t=(i,n={})=>{let s=e.escapeInvalid&&am.isInvalidBrace(n),a=i.invalid===!0&&e.escapeInvalid===!0,o="";if(i.value)return(s||a)&&am.isOpenOrClose(i)?"\\"+i.value:i.value;if(i.value)return i.value;if(i.nodes)for(let l of i.nodes)o+=t(l);return o};return t(r)}});var um=x((J4,lm)=>{u();"use strict";lm.exports=function(r){return typeof r=="number"?r-r==0:typeof r=="string"&&r.trim()!==""?Number.isFinite?Number.isFinite(+r):isFinite(+r):!1}});var bm=x((e6,ym)=>{u();"use strict";var fm=um(),Wt=(r,e,t)=>{if(fm(r)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(e===void 0||r===e)return String(r);if(fm(e)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let i={relaxZeros:!0,...t};typeof i.strictZeros=="boolean"&&(i.relaxZeros=i.strictZeros===!1);let n=String(i.relaxZeros),s=String(i.shorthand),a=String(i.capture),o=String(i.wrap),l=r+":"+e+"="+n+s+a+o;if(Wt.cache.hasOwnProperty(l))return Wt.cache[l].result;let c=Math.min(r,e),f=Math.max(r,e);if(Math.abs(c-f)===1){let v=r+"|"+e;return i.capture?`(${v})`:i.wrap===!1?v:`(?:${v})`}let d=gm(r)||gm(e),p={min:r,max:e,a:c,b:f},h=[],b=[];if(d&&(p.isPadded=d,p.maxLen=String(p.max).length),c<0){let v=f<0?Math.abs(f):1;b=cm(v,Math.abs(c),p,i),c=p.a=0}return f>=0&&(h=cm(c,f,p,i)),p.negatives=b,p.positives=h,p.result=K_(b,h,i),i.capture===!0?p.result=`(${p.result})`:i.wrap!==!1&&h.length+b.length>1&&(p.result=`(?:${p.result})`),Wt.cache[l]=p,p.result};function K_(r,e,t){let i=ul(r,e,"-",!1,t)||[],n=ul(e,r,"",!1,t)||[],s=ul(r,e,"-?",!0,t)||[];return i.concat(s).concat(n).join("|")}function X_(r,e){let t=1,i=1,n=dm(r,t),s=new Set([e]);for(;r<=n&&n<=e;)s.add(n),t+=1,n=dm(r,t);for(n=hm(e+1,i)-1;r1&&o.count.pop(),o.count.push(f.count[0]),o.string=o.pattern+mm(o.count),a=c+1;continue}t.isPadded&&(d=rE(c,t,i)),f.string=d+f.pattern+mm(f.count),s.push(f),a=c+1,o=f}return s}function ul(r,e,t,i,n){let s=[];for(let a of r){let{string:o}=a;!i&&!pm(e,"string",o)&&s.push(t+o),i&&pm(e,"string",o)&&s.push(t+o)}return s}function J_(r,e){let t=[];for(let i=0;ie?1:e>r?-1:0}function pm(r,e,t){return r.some(i=>i[e]===t)}function dm(r,e){return Number(String(r).slice(0,-e)+"9".repeat(e))}function hm(r,e){return r-r%Math.pow(10,e)}function mm(r){let[e=0,t=""]=r;return t||e>1?`{${e+(t?","+t:"")}}`:""}function tE(r,e,t){return`[${r}${e-r==1?"":"-"}${e}]`}function gm(r){return/^-?(0+)\d/.test(r)}function rE(r,e,t){if(!e.isPadded)return r;let i=Math.abs(e.maxLen-String(r).length),n=t.relaxZeros!==!1;switch(i){case 0:return"";case 1:return n?"0?":"0";case 2:return n?"0{0,2}":"00";default:return n?`0{0,${i}}`:`0{${i}}`}}Wt.cache={};Wt.clearCache=()=>Wt.cache={};ym.exports=Wt});var pl=x((t6,Cm)=>{u();"use strict";var iE=(Fn(),Bn),wm=bm(),vm=r=>r!==null&&typeof r=="object"&&!Array.isArray(r),nE=r=>e=>r===!0?Number(e):String(e),fl=r=>typeof r=="number"||typeof r=="string"&&r!=="",Ii=r=>Number.isInteger(+r),cl=r=>{let e=`${r}`,t=-1;if(e[0]==="-"&&(e=e.slice(1)),e==="0")return!1;for(;e[++t]==="0";);return t>0},sE=(r,e,t)=>typeof r=="string"||typeof e=="string"?!0:t.stringify===!0,aE=(r,e,t)=>{if(e>0){let i=r[0]==="-"?"-":"";i&&(r=r.slice(1)),r=i+r.padStart(i?e-1:e,"0")}return t===!1?String(r):r},ms=(r,e)=>{let t=r[0]==="-"?"-":"";for(t&&(r=r.slice(1),e--);r.length{r.negatives.sort((o,l)=>ol?1:0),r.positives.sort((o,l)=>ol?1:0);let i=e.capture?"":"?:",n="",s="",a;return r.positives.length&&(n=r.positives.map(o=>ms(String(o),t)).join("|")),r.negatives.length&&(s=`-(${i}${r.negatives.map(o=>ms(String(o),t)).join("|")})`),n&&s?a=`${n}|${s}`:a=n||s,e.wrap?`(${i}${a})`:a},xm=(r,e,t,i)=>{if(t)return wm(r,e,{wrap:!1,...i});let n=String.fromCharCode(r);if(r===e)return n;let s=String.fromCharCode(e);return`[${n}-${s}]`},km=(r,e,t)=>{if(Array.isArray(r)){let i=t.wrap===!0,n=t.capture?"":"?:";return i?`(${n}${r.join("|")})`:r.join("|")}return wm(r,e,t)},Sm=(...r)=>new RangeError("Invalid range arguments: "+iE.inspect(...r)),Am=(r,e,t)=>{if(t.strictRanges===!0)throw Sm([r,e]);return[]},lE=(r,e)=>{if(e.strictRanges===!0)throw new TypeError(`Expected step "${r}" to be a number`);return[]},uE=(r,e,t=1,i={})=>{let n=Number(r),s=Number(e);if(!Number.isInteger(n)||!Number.isInteger(s)){if(i.strictRanges===!0)throw Sm([r,e]);return[]}n===0&&(n=0),s===0&&(s=0);let a=n>s,o=String(r),l=String(e),c=String(t);t=Math.max(Math.abs(t),1);let f=cl(o)||cl(l)||cl(c),d=f?Math.max(o.length,l.length,c.length):0,p=f===!1&&sE(r,e,i)===!1,h=i.transform||nE(p);if(i.toRegex&&t===1)return xm(ms(r,d),ms(e,d),!0,i);let b={negatives:[],positives:[]},v=k=>b[k<0?"negatives":"positives"].push(Math.abs(k)),y=[],w=0;for(;a?n>=s:n<=s;)i.toRegex===!0&&t>1?v(n):y.push(aE(h(n,w),d,p)),n=a?n-t:n+t,w++;return i.toRegex===!0?t>1?oE(b,i,d):km(y,null,{wrap:!1,...i}):y},fE=(r,e,t=1,i={})=>{if(!Ii(r)&&r.length>1||!Ii(e)&&e.length>1)return Am(r,e,i);let n=i.transform||(p=>String.fromCharCode(p)),s=`${r}`.charCodeAt(0),a=`${e}`.charCodeAt(0),o=s>a,l=Math.min(s,a),c=Math.max(s,a);if(i.toRegex&&t===1)return xm(l,c,!1,i);let f=[],d=0;for(;o?s>=a:s<=a;)f.push(n(s,d)),s=o?s-t:s+t,d++;return i.toRegex===!0?km(f,null,{wrap:!1,options:i}):f},gs=(r,e,t,i={})=>{if(e==null&&fl(r))return[r];if(!fl(r)||!fl(e))return Am(r,e,i);if(typeof t=="function")return gs(r,e,1,{transform:t});if(vm(t))return gs(r,e,0,t);let n={...i};return n.capture===!0&&(n.wrap=!0),t=t||n.step||1,Ii(t)?Ii(r)&&Ii(e)?uE(r,e,t,n):fE(r,e,Math.max(Math.abs(t),1),n):t!=null&&!vm(t)?lE(t,n):gs(r,e,1,t)};Cm.exports=gs});var Om=x((r6,Em)=>{u();"use strict";var cE=pl(),_m=ds(),pE=(r,e={})=>{let t=(i,n={})=>{let s=_m.isInvalidBrace(n),a=i.invalid===!0&&e.escapeInvalid===!0,o=s===!0||a===!0,l=e.escapeInvalid===!0?"\\":"",c="";if(i.isOpen===!0)return l+i.value;if(i.isClose===!0)return console.log("node.isClose",l,i.value),l+i.value;if(i.type==="open")return o?l+i.value:"(";if(i.type==="close")return o?l+i.value:")";if(i.type==="comma")return i.prev.type==="comma"?"":o?i.value:"|";if(i.value)return i.value;if(i.nodes&&i.ranges>0){let f=_m.reduce(i.nodes),d=cE(...f,{...e,wrap:!1,toRegex:!0,strictZeros:!0});if(d.length!==0)return f.length>1&&d.length>1?`(${d})`:d}if(i.nodes)for(let f of i.nodes)c+=t(f,i);return c};return t(r)};Em.exports=pE});var Pm=x((i6,Rm)=>{u();"use strict";var dE=pl(),Tm=hs(),mr=ds(),Gt=(r="",e="",t=!1)=>{let i=[];if(r=[].concat(r),e=[].concat(e),!e.length)return r;if(!r.length)return t?mr.flatten(e).map(n=>`{${n}}`):e;for(let n of r)if(Array.isArray(n))for(let s of n)i.push(Gt(s,e,t));else for(let s of e)t===!0&&typeof s=="string"&&(s=`{${s}}`),i.push(Array.isArray(s)?Gt(n,s,t):n+s);return mr.flatten(i)},hE=(r,e={})=>{let t=e.rangeLimit===void 0?1e3:e.rangeLimit,i=(n,s={})=>{n.queue=[];let a=s,o=s.queue;for(;a.type!=="brace"&&a.type!=="root"&&a.parent;)a=a.parent,o=a.queue;if(n.invalid||n.dollar){o.push(Gt(o.pop(),Tm(n,e)));return}if(n.type==="brace"&&n.invalid!==!0&&n.nodes.length===2){o.push(Gt(o.pop(),["{}"]));return}if(n.nodes&&n.ranges>0){let d=mr.reduce(n.nodes);if(mr.exceedsLimit(...d,e.step,t))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let p=dE(...d,e);p.length===0&&(p=Tm(n,e)),o.push(Gt(o.pop(),p)),n.nodes=[];return}let l=mr.encloseBrace(n),c=n.queue,f=n;for(;f.type!=="brace"&&f.type!=="root"&&f.parent;)f=f.parent,c=f.queue;for(let d=0;d{u();"use strict";Im.exports={MAX_LENGTH:1e4,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:` +`,CHAR_NO_BREAK_SPACE:"\xA0",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:" ",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\uFEFF"}});var Nm=x((s6,Mm)=>{u();"use strict";var mE=hs(),{MAX_LENGTH:qm,CHAR_BACKSLASH:dl,CHAR_BACKTICK:gE,CHAR_COMMA:yE,CHAR_DOT:bE,CHAR_LEFT_PARENTHESES:wE,CHAR_RIGHT_PARENTHESES:vE,CHAR_LEFT_CURLY_BRACE:xE,CHAR_RIGHT_CURLY_BRACE:kE,CHAR_LEFT_SQUARE_BRACKET:$m,CHAR_RIGHT_SQUARE_BRACKET:Lm,CHAR_DOUBLE_QUOTE:SE,CHAR_SINGLE_QUOTE:AE,CHAR_NO_BREAK_SPACE:CE,CHAR_ZERO_WIDTH_NOBREAK_SPACE:_E}=Dm(),EE=(r,e={})=>{if(typeof r!="string")throw new TypeError("Expected a string");let t=e||{},i=typeof t.maxLength=="number"?Math.min(qm,t.maxLength):qm;if(r.length>i)throw new SyntaxError(`Input length (${r.length}), exceeds max characters (${i})`);let n={type:"root",input:r,nodes:[]},s=[n],a=n,o=n,l=0,c=r.length,f=0,d=0,p,h=()=>r[f++],b=v=>{if(v.type==="text"&&o.type==="dot"&&(o.type="text"),o&&o.type==="text"&&v.type==="text"){o.value+=v.value;return}return a.nodes.push(v),v.parent=a,v.prev=o,o=v,v};for(b({type:"bos"});f0){if(a.ranges>0){a.ranges=0;let v=a.nodes.shift();a.nodes=[v,{type:"text",value:mE(a)}]}b({type:"comma",value:p}),a.commas++;continue}if(p===bE&&d>0&&a.commas===0){let v=a.nodes;if(d===0||v.length===0){b({type:"text",value:p});continue}if(o.type==="dot"){if(a.range=[],o.value+=p,o.type="range",a.nodes.length!==3&&a.nodes.length!==5){a.invalid=!0,a.ranges=0,o.type="text";continue}a.ranges++,a.args=[];continue}if(o.type==="range"){v.pop();let y=v[v.length-1];y.value+=o.value+p,o=y,a.ranges--;continue}b({type:"dot",value:p});continue}b({type:"text",value:p})}do if(a=s.pop(),a.type!=="root"){a.nodes.forEach(w=>{w.nodes||(w.type==="open"&&(w.isOpen=!0),w.type==="close"&&(w.isClose=!0),w.nodes||(w.type="text"),w.invalid=!0)});let v=s[s.length-1],y=v.nodes.indexOf(a);v.nodes.splice(y,1,...a.nodes)}while(s.length>0);return b({type:"eos"}),n};Mm.exports=EE});var jm=x((a6,Fm)=>{u();"use strict";var Bm=hs(),OE=Om(),TE=Pm(),RE=Nm(),Le=(r,e={})=>{let t=[];if(Array.isArray(r))for(let i of r){let n=Le.create(i,e);Array.isArray(n)?t.push(...n):t.push(n)}else t=[].concat(Le.create(r,e));return e&&e.expand===!0&&e.nodupes===!0&&(t=[...new Set(t)]),t};Le.parse=(r,e={})=>RE(r,e);Le.stringify=(r,e={})=>typeof r=="string"?Bm(Le.parse(r,e),e):Bm(r,e);Le.compile=(r,e={})=>(typeof r=="string"&&(r=Le.parse(r,e)),OE(r,e));Le.expand=(r,e={})=>{typeof r=="string"&&(r=Le.parse(r,e));let t=TE(r,e);return e.noempty===!0&&(t=t.filter(Boolean)),e.nodupes===!0&&(t=[...new Set(t)]),t};Le.create=(r,e={})=>r===""||r.length<3?[r]:e.expand!==!0?Le.compile(r,e):Le.expand(r,e);Fm.exports=Le});var Di=x((o6,Wm)=>{u();"use strict";var PE=(et(),Ur),at="\\\\/",zm=`[^${at}]`,yt="\\.",IE="\\+",DE="\\?",ys="\\/",qE="(?=.)",Um="[^/]",hl=`(?:${ys}|$)`,Vm=`(?:^|${ys})`,ml=`${yt}{1,2}${hl}`,$E=`(?!${yt})`,LE=`(?!${Vm}${ml})`,ME=`(?!${yt}{0,1}${hl})`,NE=`(?!${ml})`,BE=`[^.${ys}]`,FE=`${Um}*?`,Hm={DOT_LITERAL:yt,PLUS_LITERAL:IE,QMARK_LITERAL:DE,SLASH_LITERAL:ys,ONE_CHAR:qE,QMARK:Um,END_ANCHOR:hl,DOTS_SLASH:ml,NO_DOT:$E,NO_DOTS:LE,NO_DOT_SLASH:ME,NO_DOTS_SLASH:NE,QMARK_NO_DOT:BE,STAR:FE,START_ANCHOR:Vm},jE={...Hm,SLASH_LITERAL:`[${at}]`,QMARK:zm,STAR:`${zm}*?`,DOTS_SLASH:`${yt}{1,2}(?:[${at}]|$)`,NO_DOT:`(?!${yt})`,NO_DOTS:`(?!(?:^|[${at}])${yt}{1,2}(?:[${at}]|$))`,NO_DOT_SLASH:`(?!${yt}{0,1}(?:[${at}]|$))`,NO_DOTS_SLASH:`(?!${yt}{1,2}(?:[${at}]|$))`,QMARK_NO_DOT:`[^.${at}]`,START_ANCHOR:`(?:^|[${at}])`,END_ANCHOR:`(?:[${at}]|$)`},zE={alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"};Wm.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:zE,REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:PE.sep,extglobChars(r){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${r.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}},globChars(r){return r===!0?jE:Hm}}});var qi=x(Re=>{u();"use strict";var UE=(et(),Ur),VE=m.platform==="win32",{REGEX_BACKSLASH:HE,REGEX_REMOVE_BACKSLASH:WE,REGEX_SPECIAL_CHARS:GE,REGEX_SPECIAL_CHARS_GLOBAL:QE}=Di();Re.isObject=r=>r!==null&&typeof r=="object"&&!Array.isArray(r);Re.hasRegexChars=r=>GE.test(r);Re.isRegexChar=r=>r.length===1&&Re.hasRegexChars(r);Re.escapeRegex=r=>r.replace(QE,"\\$1");Re.toPosixSlashes=r=>r.replace(HE,"/");Re.removeBackslashes=r=>r.replace(WE,e=>e==="\\"?"":e);Re.supportsLookbehinds=()=>{let r=m.version.slice(1).split(".").map(Number);return r.length===3&&r[0]>=9||r[0]===8&&r[1]>=10};Re.isWindows=r=>r&&typeof r.windows=="boolean"?r.windows:VE===!0||UE.sep==="\\";Re.escapeLast=(r,e,t)=>{let i=r.lastIndexOf(e,t);return i===-1?r:r[i-1]==="\\"?Re.escapeLast(r,e,i-1):`${r.slice(0,i)}\\${r.slice(i)}`};Re.removePrefix=(r,e={})=>{let t=r;return t.startsWith("./")&&(t=t.slice(2),e.prefix="./"),t};Re.wrapOutput=(r,e={},t={})=>{let i=t.contains?"":"^",n=t.contains?"":"$",s=`${i}(?:${r})${n}`;return e.negated===!0&&(s=`(?:^(?!${s}).*$)`),s}});var eg=x((u6,Jm)=>{u();"use strict";var Gm=qi(),{CHAR_ASTERISK:gl,CHAR_AT:YE,CHAR_BACKWARD_SLASH:$i,CHAR_COMMA:KE,CHAR_DOT:yl,CHAR_EXCLAMATION_MARK:bl,CHAR_FORWARD_SLASH:Qm,CHAR_LEFT_CURLY_BRACE:wl,CHAR_LEFT_PARENTHESES:vl,CHAR_LEFT_SQUARE_BRACKET:XE,CHAR_PLUS:ZE,CHAR_QUESTION_MARK:Ym,CHAR_RIGHT_CURLY_BRACE:JE,CHAR_RIGHT_PARENTHESES:Km,CHAR_RIGHT_SQUARE_BRACKET:e2}=Di(),Xm=r=>r===Qm||r===$i,Zm=r=>{r.isPrefix!==!0&&(r.depth=r.isGlobstar?1/0:1)},t2=(r,e)=>{let t=e||{},i=r.length-1,n=t.parts===!0||t.scanToEnd===!0,s=[],a=[],o=[],l=r,c=-1,f=0,d=0,p=!1,h=!1,b=!1,v=!1,y=!1,w=!1,k=!1,S=!1,E=!1,T=!1,B=0,N,R,F={value:"",depth:0,isGlob:!1},Y=()=>c>=i,_=()=>l.charCodeAt(c+1),Q=()=>(N=R,l.charCodeAt(++c));for(;c0&&(le=l.slice(0,f),l=l.slice(f),d-=f),U&&b===!0&&d>0?(U=l.slice(0,d),A=l.slice(d)):b===!0?(U="",A=l):U=l,U&&U!==""&&U!=="/"&&U!==l&&Xm(U.charCodeAt(U.length-1))&&(U=U.slice(0,-1)),t.unescape===!0&&(A&&(A=Gm.removeBackslashes(A)),U&&k===!0&&(U=Gm.removeBackslashes(U)));let C={prefix:le,input:r,start:f,base:U,glob:A,isBrace:p,isBracket:h,isGlob:b,isExtglob:v,isGlobstar:y,negated:S,negatedExtglob:E};if(t.tokens===!0&&(C.maxDepth=0,Xm(R)||a.push(F),C.tokens=a),t.parts===!0||t.tokens===!0){let he;for(let V=0;V{u();"use strict";var bs=Di(),Me=qi(),{MAX_LENGTH:ws,POSIX_REGEX_SOURCE:r2,REGEX_NON_SPECIAL_CHARS:i2,REGEX_SPECIAL_CHARS_BACKREF:n2,REPLACEMENTS:tg}=bs,s2=(r,e)=>{if(typeof e.expandRange=="function")return e.expandRange(...r,e);r.sort();let t=`[${r.join("-")}]`;try{new RegExp(t)}catch(i){return r.map(n=>Me.escapeRegex(n)).join("..")}return t},gr=(r,e)=>`Missing ${r}: "${e}" - use "\\\\${e}" to match literal characters`,xl=(r,e)=>{if(typeof r!="string")throw new TypeError("Expected a string");r=tg[r]||r;let t={...e},i=typeof t.maxLength=="number"?Math.min(ws,t.maxLength):ws,n=r.length;if(n>i)throw new SyntaxError(`Input length: ${n}, exceeds maximum allowed length: ${i}`);let s={type:"bos",value:"",output:t.prepend||""},a=[s],o=t.capture?"":"?:",l=Me.isWindows(e),c=bs.globChars(l),f=bs.extglobChars(c),{DOT_LITERAL:d,PLUS_LITERAL:p,SLASH_LITERAL:h,ONE_CHAR:b,DOTS_SLASH:v,NO_DOT:y,NO_DOT_SLASH:w,NO_DOTS_SLASH:k,QMARK:S,QMARK_NO_DOT:E,STAR:T,START_ANCHOR:B}=c,N=$=>`(${o}(?:(?!${B}${$.dot?v:d}).)*?)`,R=t.dot?"":y,F=t.dot?S:E,Y=t.bash===!0?N(t):T;t.capture&&(Y=`(${Y})`),typeof t.noext=="boolean"&&(t.noextglob=t.noext);let _={input:r,index:-1,start:0,dot:t.dot===!0,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:a};r=Me.removePrefix(r,_),n=r.length;let Q=[],U=[],le=[],A=s,C,he=()=>_.index===n-1,V=_.peek=($=1)=>r[_.index+$],Ee=_.advance=()=>r[++_.index]||"",Ie=()=>r.slice(_.index+1),De=($="",ae=0)=>{_.consumed+=$,_.index+=ae},ji=$=>{_.output+=$.output!=null?$.output:$.value,De($.value)},Iv=()=>{let $=1;for(;V()==="!"&&(V(2)!=="("||V(3)==="?");)Ee(),_.start++,$++;return $%2==0?!1:(_.negated=!0,_.start++,!0)},zi=$=>{_[$]++,le.push($)},Ft=$=>{_[$]--,le.pop()},W=$=>{if(A.type==="globstar"){let ae=_.braces>0&&($.type==="comma"||$.type==="brace"),I=$.extglob===!0||Q.length&&($.type==="pipe"||$.type==="paren");$.type!=="slash"&&$.type!=="paren"&&!ae&&!I&&(_.output=_.output.slice(0,-A.output.length),A.type="star",A.value="*",A.output=Y,_.output+=A.output)}if(Q.length&&$.type!=="paren"&&(Q[Q.length-1].inner+=$.value),($.value||$.output)&&ji($),A&&A.type==="text"&&$.type==="text"){A.value+=$.value,A.output=(A.output||"")+$.value;return}$.prev=A,a.push($),A=$},Ui=($,ae)=>{let I={...f[ae],conditions:1,inner:""};I.prev=A,I.parens=_.parens,I.output=_.output;let H=(t.capture?"(":"")+I.open;zi("parens"),W({type:$,value:ae,output:_.output?"":b}),W({type:"paren",extglob:!0,value:Ee(),output:H}),Q.push(I)},Dv=$=>{let ae=$.close+(t.capture?")":""),I;if($.type==="negate"){let H=Y;if($.inner&&$.inner.length>1&&$.inner.includes("/")&&(H=N(t)),(H!==Y||he()||/^\)+$/.test(Ie()))&&(ae=$.close=`)$))${H}`),$.inner.includes("*")&&(I=Ie())&&/^\.[^\\/.]+$/.test(I)){let ce=xl(I,{...e,fastpaths:!1}).output;ae=$.close=`)${ce})${H})`}$.prev.type==="bos"&&(_.negatedExtglob=!0)}W({type:"paren",extglob:!0,value:C,output:ae}),Ft("parens")};if(t.fastpaths!==!1&&!/(^[*!]|[/()[\]{}"])/.test(r)){let $=!1,ae=r.replace(n2,(I,H,ce,Ce,ye,Bs)=>Ce==="\\"?($=!0,I):Ce==="?"?H?H+Ce+(ye?S.repeat(ye.length):""):Bs===0?F+(ye?S.repeat(ye.length):""):S.repeat(ce.length):Ce==="."?d.repeat(ce.length):Ce==="*"?H?H+Ce+(ye?Y:""):Y:H?I:`\\${I}`);return $===!0&&(t.unescape===!0?ae=ae.replace(/\\/g,""):ae=ae.replace(/\\+/g,I=>I.length%2==0?"\\\\":I?"\\":"")),ae===r&&t.contains===!0?(_.output=r,_):(_.output=Me.wrapOutput(ae,_,e),_)}for(;!he();){if(C=Ee(),C==="\0")continue;if(C==="\\"){let I=V();if(I==="/"&&t.bash!==!0||I==="."||I===";")continue;if(!I){C+="\\",W({type:"text",value:C});continue}let H=/^\\+/.exec(Ie()),ce=0;if(H&&H[0].length>2&&(ce=H[0].length,_.index+=ce,ce%2!=0&&(C+="\\")),t.unescape===!0?C=Ee():C+=Ee(),_.brackets===0){W({type:"text",value:C});continue}}if(_.brackets>0&&(C!=="]"||A.value==="["||A.value==="[^")){if(t.posix!==!1&&C===":"){let I=A.value.slice(1);if(I.includes("[")&&(A.posix=!0,I.includes(":"))){let H=A.value.lastIndexOf("["),ce=A.value.slice(0,H),Ce=A.value.slice(H+2),ye=r2[Ce];if(ye){A.value=ce+ye,_.backtrack=!0,Ee(),!s.output&&a.indexOf(A)===1&&(s.output=b);continue}}}(C==="["&&V()!==":"||C==="-"&&V()==="]")&&(C=`\\${C}`),C==="]"&&(A.value==="["||A.value==="[^")&&(C=`\\${C}`),t.posix===!0&&C==="!"&&A.value==="["&&(C="^"),A.value+=C,ji({value:C});continue}if(_.quotes===1&&C!=='"'){C=Me.escapeRegex(C),A.value+=C,ji({value:C});continue}if(C==='"'){_.quotes=_.quotes===1?0:1,t.keepQuotes===!0&&W({type:"text",value:C});continue}if(C==="("){zi("parens"),W({type:"paren",value:C});continue}if(C===")"){if(_.parens===0&&t.strictBrackets===!0)throw new SyntaxError(gr("opening","("));let I=Q[Q.length-1];if(I&&_.parens===I.parens+1){Dv(Q.pop());continue}W({type:"paren",value:C,output:_.parens?")":"\\)"}),Ft("parens");continue}if(C==="["){if(t.nobracket===!0||!Ie().includes("]")){if(t.nobracket!==!0&&t.strictBrackets===!0)throw new SyntaxError(gr("closing","]"));C=`\\${C}`}else zi("brackets");W({type:"bracket",value:C});continue}if(C==="]"){if(t.nobracket===!0||A&&A.type==="bracket"&&A.value.length===1){W({type:"text",value:C,output:`\\${C}`});continue}if(_.brackets===0){if(t.strictBrackets===!0)throw new SyntaxError(gr("opening","["));W({type:"text",value:C,output:`\\${C}`});continue}Ft("brackets");let I=A.value.slice(1);if(A.posix!==!0&&I[0]==="^"&&!I.includes("/")&&(C=`/${C}`),A.value+=C,ji({value:C}),t.literalBrackets===!1||Me.hasRegexChars(I))continue;let H=Me.escapeRegex(A.value);if(_.output=_.output.slice(0,-A.value.length),t.literalBrackets===!0){_.output+=H,A.value=H;continue}A.value=`(${o}${H}|${A.value})`,_.output+=A.value;continue}if(C==="{"&&t.nobrace!==!0){zi("braces");let I={type:"brace",value:C,output:"(",outputIndex:_.output.length,tokensIndex:_.tokens.length};U.push(I),W(I);continue}if(C==="}"){let I=U[U.length-1];if(t.nobrace===!0||!I){W({type:"text",value:C,output:C});continue}let H=")";if(I.dots===!0){let ce=a.slice(),Ce=[];for(let ye=ce.length-1;ye>=0&&(a.pop(),ce[ye].type!=="brace");ye--)ce[ye].type!=="dots"&&Ce.unshift(ce[ye].value);H=s2(Ce,t),_.backtrack=!0}if(I.comma!==!0&&I.dots!==!0){let ce=_.output.slice(0,I.outputIndex),Ce=_.tokens.slice(I.tokensIndex);I.value=I.output="\\{",C=H="\\}",_.output=ce;for(let ye of Ce)_.output+=ye.output||ye.value}W({type:"brace",value:C,output:H}),Ft("braces"),U.pop();continue}if(C==="|"){Q.length>0&&Q[Q.length-1].conditions++,W({type:"text",value:C});continue}if(C===","){let I=C,H=U[U.length-1];H&&le[le.length-1]==="braces"&&(H.comma=!0,I="|"),W({type:"comma",value:C,output:I});continue}if(C==="/"){if(A.type==="dot"&&_.index===_.start+1){_.start=_.index+1,_.consumed="",_.output="",a.pop(),A=s;continue}W({type:"slash",value:C,output:h});continue}if(C==="."){if(_.braces>0&&A.type==="dot"){A.value==="."&&(A.output=d);let I=U[U.length-1];A.type="dots",A.output+=C,A.value+=C,I.dots=!0;continue}if(_.braces+_.parens===0&&A.type!=="bos"&&A.type!=="slash"){W({type:"text",value:C,output:d});continue}W({type:"dot",value:C,output:d});continue}if(C==="?"){if(!(A&&A.value==="(")&&t.noextglob!==!0&&V()==="("&&V(2)!=="?"){Ui("qmark",C);continue}if(A&&A.type==="paren"){let H=V(),ce=C;if(H==="<"&&!Me.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");(A.value==="("&&!/[!=<:]/.test(H)||H==="<"&&!/<([!=]|\w+>)/.test(Ie()))&&(ce=`\\${C}`),W({type:"text",value:C,output:ce});continue}if(t.dot!==!0&&(A.type==="slash"||A.type==="bos")){W({type:"qmark",value:C,output:E});continue}W({type:"qmark",value:C,output:S});continue}if(C==="!"){if(t.noextglob!==!0&&V()==="("&&(V(2)!=="?"||!/[!=<:]/.test(V(3)))){Ui("negate",C);continue}if(t.nonegate!==!0&&_.index===0){Iv();continue}}if(C==="+"){if(t.noextglob!==!0&&V()==="("&&V(2)!=="?"){Ui("plus",C);continue}if(A&&A.value==="("||t.regex===!1){W({type:"plus",value:C,output:p});continue}if(A&&(A.type==="bracket"||A.type==="paren"||A.type==="brace")||_.parens>0){W({type:"plus",value:C});continue}W({type:"plus",value:p});continue}if(C==="@"){if(t.noextglob!==!0&&V()==="("&&V(2)!=="?"){W({type:"at",extglob:!0,value:C,output:""});continue}W({type:"text",value:C});continue}if(C!=="*"){(C==="$"||C==="^")&&(C=`\\${C}`);let I=i2.exec(Ie());I&&(C+=I[0],_.index+=I[0].length),W({type:"text",value:C});continue}if(A&&(A.type==="globstar"||A.star===!0)){A.type="star",A.star=!0,A.value+=C,A.output=Y,_.backtrack=!0,_.globstar=!0,De(C);continue}let $=Ie();if(t.noextglob!==!0&&/^\([^?]/.test($)){Ui("star",C);continue}if(A.type==="star"){if(t.noglobstar===!0){De(C);continue}let I=A.prev,H=I.prev,ce=I.type==="slash"||I.type==="bos",Ce=H&&(H.type==="star"||H.type==="globstar");if(t.bash===!0&&(!ce||$[0]&&$[0]!=="/")){W({type:"star",value:C,output:""});continue}let ye=_.braces>0&&(I.type==="comma"||I.type==="brace"),Bs=Q.length&&(I.type==="pipe"||I.type==="paren");if(!ce&&I.type!=="paren"&&!ye&&!Bs){W({type:"star",value:C,output:""});continue}for(;$.slice(0,3)==="/**";){let Vi=r[_.index+4];if(Vi&&Vi!=="/")break;$=$.slice(3),De("/**",3)}if(I.type==="bos"&&he()){A.type="globstar",A.value+=C,A.output=N(t),_.output=A.output,_.globstar=!0,De(C);continue}if(I.type==="slash"&&I.prev.type!=="bos"&&!Ce&&he()){_.output=_.output.slice(0,-(I.output+A.output).length),I.output=`(?:${I.output}`,A.type="globstar",A.output=N(t)+(t.strictSlashes?")":"|$)"),A.value+=C,_.globstar=!0,_.output+=I.output+A.output,De(C);continue}if(I.type==="slash"&&I.prev.type!=="bos"&&$[0]==="/"){let Vi=$[1]!==void 0?"|$":"";_.output=_.output.slice(0,-(I.output+A.output).length),I.output=`(?:${I.output}`,A.type="globstar",A.output=`${N(t)}${h}|${h}${Vi})`,A.value+=C,_.output+=I.output+A.output,_.globstar=!0,De(C+Ee()),W({type:"slash",value:"/",output:""});continue}if(I.type==="bos"&&$[0]==="/"){A.type="globstar",A.value+=C,A.output=`(?:^|${h}|${N(t)}${h})`,_.output=A.output,_.globstar=!0,De(C+Ee()),W({type:"slash",value:"/",output:""});continue}_.output=_.output.slice(0,-A.output.length),A.type="globstar",A.output=N(t),A.value+=C,_.output+=A.output,_.globstar=!0,De(C);continue}let ae={type:"star",value:C,output:Y};if(t.bash===!0){ae.output=".*?",(A.type==="bos"||A.type==="slash")&&(ae.output=R+ae.output),W(ae);continue}if(A&&(A.type==="bracket"||A.type==="paren")&&t.regex===!0){ae.output=C,W(ae);continue}(_.index===_.start||A.type==="slash"||A.type==="dot")&&(A.type==="dot"?(_.output+=w,A.output+=w):t.dot===!0?(_.output+=k,A.output+=k):(_.output+=R,A.output+=R),V()!=="*"&&(_.output+=b,A.output+=b)),W(ae)}for(;_.brackets>0;){if(t.strictBrackets===!0)throw new SyntaxError(gr("closing","]"));_.output=Me.escapeLast(_.output,"["),Ft("brackets")}for(;_.parens>0;){if(t.strictBrackets===!0)throw new SyntaxError(gr("closing",")"));_.output=Me.escapeLast(_.output,"("),Ft("parens")}for(;_.braces>0;){if(t.strictBrackets===!0)throw new SyntaxError(gr("closing","}"));_.output=Me.escapeLast(_.output,"{"),Ft("braces")}if(t.strictSlashes!==!0&&(A.type==="star"||A.type==="bracket")&&W({type:"maybe_slash",value:"",output:`${h}?`}),_.backtrack===!0){_.output="";for(let $ of _.tokens)_.output+=$.output!=null?$.output:$.value,$.suffix&&(_.output+=$.suffix)}return _};xl.fastpaths=(r,e)=>{let t={...e},i=typeof t.maxLength=="number"?Math.min(ws,t.maxLength):ws,n=r.length;if(n>i)throw new SyntaxError(`Input length: ${n}, exceeds maximum allowed length: ${i}`);r=tg[r]||r;let s=Me.isWindows(e),{DOT_LITERAL:a,SLASH_LITERAL:o,ONE_CHAR:l,DOTS_SLASH:c,NO_DOT:f,NO_DOTS:d,NO_DOTS_SLASH:p,STAR:h,START_ANCHOR:b}=bs.globChars(s),v=t.dot?d:f,y=t.dot?p:f,w=t.capture?"":"?:",k={negated:!1,prefix:""},S=t.bash===!0?".*?":h;t.capture&&(S=`(${S})`);let E=R=>R.noglobstar===!0?S:`(${w}(?:(?!${b}${R.dot?c:a}).)*?)`,T=R=>{switch(R){case"*":return`${v}${l}${S}`;case".*":return`${a}${l}${S}`;case"*.*":return`${v}${S}${a}${l}${S}`;case"*/*":return`${v}${S}${o}${l}${y}${S}`;case"**":return v+E(t);case"**/*":return`(?:${v}${E(t)}${o})?${y}${l}${S}`;case"**/*.*":return`(?:${v}${E(t)}${o})?${y}${S}${a}${l}${S}`;case"**/.*":return`(?:${v}${E(t)}${o})?${a}${l}${S}`;default:{let F=/^(.*?)\.(\w+)$/.exec(R);if(!F)return;let Y=T(F[1]);return Y?Y+a+F[2]:void 0}}},B=Me.removePrefix(r,k),N=T(B);return N&&t.strictSlashes!==!0&&(N+=`${o}?`),N};rg.exports=xl});var sg=x((c6,ng)=>{u();"use strict";var a2=(et(),Ur),o2=eg(),kl=ig(),Sl=qi(),l2=Di(),u2=r=>r&&typeof r=="object"&&!Array.isArray(r),de=(r,e,t=!1)=>{if(Array.isArray(r)){let f=r.map(p=>de(p,e,t));return p=>{for(let h of f){let b=h(p);if(b)return b}return!1}}let i=u2(r)&&r.tokens&&r.input;if(r===""||typeof r!="string"&&!i)throw new TypeError("Expected pattern to be a non-empty string");let n=e||{},s=Sl.isWindows(e),a=i?de.compileRe(r,e):de.makeRe(r,e,!1,!0),o=a.state;delete a.state;let l=()=>!1;if(n.ignore){let f={...e,ignore:null,onMatch:null,onResult:null};l=de(n.ignore,f,t)}let c=(f,d=!1)=>{let{isMatch:p,match:h,output:b}=de.test(f,a,e,{glob:r,posix:s}),v={glob:r,state:o,regex:a,posix:s,input:f,output:b,match:h,isMatch:p};return typeof n.onResult=="function"&&n.onResult(v),p===!1?(v.isMatch=!1,d?v:!1):l(f)?(typeof n.onIgnore=="function"&&n.onIgnore(v),v.isMatch=!1,d?v:!1):(typeof n.onMatch=="function"&&n.onMatch(v),d?v:!0)};return t&&(c.state=o),c};de.test=(r,e,t,{glob:i,posix:n}={})=>{if(typeof r!="string")throw new TypeError("Expected input to be a string");if(r==="")return{isMatch:!1,output:""};let s=t||{},a=s.format||(n?Sl.toPosixSlashes:null),o=r===i,l=o&&a?a(r):r;return o===!1&&(l=a?a(r):r,o=l===i),(o===!1||s.capture===!0)&&(s.matchBase===!0||s.basename===!0?o=de.matchBase(r,e,t,n):o=e.exec(l)),{isMatch:Boolean(o),match:o,output:l}};de.matchBase=(r,e,t,i=Sl.isWindows(t))=>(e instanceof RegExp?e:de.makeRe(e,t)).test(a2.basename(r));de.isMatch=(r,e,t)=>de(e,t)(r);de.parse=(r,e)=>Array.isArray(r)?r.map(t=>de.parse(t,e)):kl(r,{...e,fastpaths:!1});de.scan=(r,e)=>o2(r,e);de.compileRe=(r,e,t=!1,i=!1)=>{if(t===!0)return r.output;let n=e||{},s=n.contains?"":"^",a=n.contains?"":"$",o=`${s}(?:${r.output})${a}`;r&&r.negated===!0&&(o=`^(?!${o}).*$`);let l=de.toRegex(o,e);return i===!0&&(l.state=r),l};de.makeRe=(r,e={},t=!1,i=!1)=>{if(!r||typeof r!="string")throw new TypeError("Expected a non-empty string");let n={negated:!1,fastpaths:!0};return e.fastpaths!==!1&&(r[0]==="."||r[0]==="*")&&(n.output=kl.fastpaths(r,e)),n.output||(n=kl(r,e)),de.compileRe(n,e,t,i)};de.toRegex=(r,e)=>{try{let t=e||{};return new RegExp(r,t.flags||(t.nocase?"i":""))}catch(t){if(e&&e.debug===!0)throw t;return/$^/}};de.constants=l2;ng.exports=de});var og=x((p6,ag)=>{u();"use strict";ag.exports=sg()});var dg=x((d6,pg)=>{u();"use strict";var lg=(Fn(),Bn),ug=jm(),ot=og(),Al=qi(),fg=r=>r===""||r==="./",cg=r=>{let e=r.indexOf("{");return e>-1&&r.indexOf("}",e)>-1},oe=(r,e,t)=>{e=[].concat(e),r=[].concat(r);let i=new Set,n=new Set,s=new Set,a=0,o=f=>{s.add(f.output),t&&t.onResult&&t.onResult(f)};for(let f=0;f!i.has(f));if(t&&c.length===0){if(t.failglob===!0)throw new Error(`No matches found for "${e.join(", ")}"`);if(t.nonull===!0||t.nullglob===!0)return t.unescape?e.map(f=>f.replace(/\\/g,"")):e}return c};oe.match=oe;oe.matcher=(r,e)=>ot(r,e);oe.isMatch=(r,e,t)=>ot(e,t)(r);oe.any=oe.isMatch;oe.not=(r,e,t={})=>{e=[].concat(e).map(String);let i=new Set,n=[],s=o=>{t.onResult&&t.onResult(o),n.push(o.output)},a=new Set(oe(r,e,{...t,onResult:s}));for(let o of n)a.has(o)||i.add(o);return[...i]};oe.contains=(r,e,t)=>{if(typeof r!="string")throw new TypeError(`Expected a string: "${lg.inspect(r)}"`);if(Array.isArray(e))return e.some(i=>oe.contains(r,i,t));if(typeof e=="string"){if(fg(r)||fg(e))return!1;if(r.includes(e)||r.startsWith("./")&&r.slice(2).includes(e))return!0}return oe.isMatch(r,e,{...t,contains:!0})};oe.matchKeys=(r,e,t)=>{if(!Al.isObject(r))throw new TypeError("Expected the first argument to be an object");let i=oe(Object.keys(r),e,t),n={};for(let s of i)n[s]=r[s];return n};oe.some=(r,e,t)=>{let i=[].concat(r);for(let n of[].concat(e)){let s=ot(String(n),t);if(i.some(a=>s(a)))return!0}return!1};oe.every=(r,e,t)=>{let i=[].concat(r);for(let n of[].concat(e)){let s=ot(String(n),t);if(!i.every(a=>s(a)))return!1}return!0};oe.all=(r,e,t)=>{if(typeof r!="string")throw new TypeError(`Expected a string: "${lg.inspect(r)}"`);return[].concat(e).every(i=>ot(i,t)(r))};oe.capture=(r,e,t)=>{let i=Al.isWindows(t),s=ot.makeRe(String(r),{...t,capture:!0}).exec(i?Al.toPosixSlashes(e):e);if(s)return s.slice(1).map(a=>a===void 0?"":a)};oe.makeRe=(...r)=>ot.makeRe(...r);oe.scan=(...r)=>ot.scan(...r);oe.parse=(r,e)=>{let t=[];for(let i of[].concat(r||[]))for(let n of ug(String(i),e))t.push(ot.parse(n,e));return t};oe.braces=(r,e)=>{if(typeof r!="string")throw new TypeError("Expected a string");return e&&e.nobrace===!0||!cg(r)?[r]:ug(r,e)};oe.braceExpand=(r,e)=>{if(typeof r!="string")throw new TypeError("Expected a string");return oe.braces(r,{...e,expand:!0})};oe.hasBraces=cg;pg.exports=oe});function mg(r,e){let t=e.content.files;t=t.filter(o=>typeof o=="string"),t=t.map(ll);let i=ps.generateTasks(t),n=[],s=[];for(let o of i)n.push(...o.positive.map(l=>gg(l,!1))),s.push(...o.negative.map(l=>gg(l,!0)));let a=[...n,...s];return a=c2(r,a),a=a.flatMap(p2),a=a.map(f2),a}function gg(r,e){let t={original:r,base:r,ignore:e,pattern:r,glob:null};return Zh(r)&&Object.assign(t,nm(r)),t}function f2(r){let e=ll(r.base);return e=ps.escapePath(e),r.pattern=r.glob?`${e}/${r.glob}`:e,r.pattern=r.ignore?`!${r.pattern}`:r.pattern,r}function c2(r,e){let t=[];return r.userConfigPath&&r.tailwindConfig.content.relative&&(t=[me.dirname(r.userConfigPath)]),e.map(i=>(i.base=me.resolve(...t,i.base),i))}function p2(r){let e=[r];try{let t=be.realpathSync(r.base);t!==r.base&&e.push({...r,base:t})}catch{}return e}function yg(r,e,t){let i=r.tailwindConfig.content.files.filter(a=>typeof a.raw=="string").map(({raw:a,extension:o="html"})=>({content:a,extension:o})),[n,s]=h2(e,t);for(let a of n){let o=me.extname(a).slice(1);i.push({file:a,extension:o})}return[i,s]}function d2(r){if(!r.some(s=>s.includes("**")&&!wg.test(s)))return()=>{};let t=[],i=[];for(let s of r){let a=hg.default.matcher(s);wg.test(s)&&i.push(a),t.push(a)}let n=!1;return s=>{if(n||i.some(f=>f(s)))return;let a=t.findIndex(f=>f(s));if(a===-1)return;let o=r[a],l=me.relative(m.cwd(),o);l[0]!=="."&&(l=`./${l}`);let c=bg.find(f=>s.includes(f));c&&(n=!0,G.warn("broad-content-glob-pattern",[`Your \`content\` configuration includes a pattern which looks like it's accidentally matching all of \`${c}\` and can cause serious performance issues.`,`Pattern: \`${l}\``,"See our documentation for recommendations:","https://tailwindcss.com/docs/content-configuration#pattern-recommendations"]))}}function h2(r,e){let t=r.map(o=>o.pattern),i=new Map,n=d2(t),s=new Set;Ze.DEBUG&&console.time("Finding changed files");let a=ps.sync(t,{absolute:!0});for(let o of a){n(o);let l=e.get(o)||-1/0,c=be.statSync(o).mtimeMs;c>l&&(s.add(o),i.set(o,c))}return Ze.DEBUG&&console.timeEnd("Finding changed files"),[s,i]}var hg,bg,wg,vg=P(()=>{u();ft();et();Jh();em();tm();sm();It();Be();hg=pe(dg());bg=["node_modules"],wg=new RegExp(`(${bg.map(r=>String.raw`\b${r}\b`).join("|")})`)});function xg(){}var kg=P(()=>{u()});function b2(r,e){for(let t of e){let i=`${r}${t}`;if(be.existsSync(i)&&be.statSync(i).isFile())return i}for(let t of e){let i=`${r}/index${t}`;if(be.existsSync(i))return i}return null}function*Sg(r,e,t,i=me.extname(r)){let n=b2(me.resolve(e,r),m2.includes(i)?g2:y2);if(n===null||t.has(n))return;t.add(n),yield n,e=me.dirname(n),i=me.extname(n);let s=be.readFileSync(n,"utf-8");for(let a of[...s.matchAll(/import[\s\S]*?['"](.{3,}?)['"]/gi),...s.matchAll(/import[\s\S]*from[\s\S]*?['"](.{3,}?)['"]/gi),...s.matchAll(/require\(['"`](.+)['"`]\)/gi)])!a[1].startsWith(".")||(yield*Sg(a[1],e,t,i))}function Cl(r){return r===null?new Set:new Set(Sg(r,me.dirname(r),new Set))}var m2,g2,y2,Ag=P(()=>{u();ft();et();m2=[".js",".cjs",".mjs"],g2=["",".js",".cjs",".mjs",".ts",".cts",".mts",".jsx",".tsx"],y2=["",".ts",".cts",".mts",".tsx",".js",".cjs",".mjs",".jsx"]});function w2(r,e){if(_l.has(r))return _l.get(r);let t=mg(r,e);return _l.set(r,t).get(r)}function v2(r){let e=aa(r);if(e!==null){let[i,n,s,a]=_g.get(e)||[],o=Cl(e),l=!1,c=new Map;for(let p of o){let h=be.statSync(p).mtimeMs;c.set(p,h),(!a||!a.has(p)||h>a.get(p))&&(l=!0)}if(!l)return[i,e,n,s];for(let p of o)delete hf.cache[p];let f=ol(zr(xg(e))),d=Wi(f);return _g.set(e,[f,d,o,c]),[f,e,d,o]}let t=zr(r?.config??r??{});return t=ol(t),[t,null,Wi(t),[]]}function El(r){return({tailwindDirectives:e,registerDependency:t})=>(i,n)=>{let[s,a,o,l]=v2(r),c=new Set(l);if(e.size>0){c.add(n.opts.from);for(let b of n.messages)b.type==="dependency"&&c.add(b.file)}let[f,,d]=Vh(i,n,s,a,o,c),p=cs(f),h=w2(f,s);if(e.size>0){for(let y of h)for(let w of nl(y))t(w);let[b,v]=yg(f,h,p);for(let y of b)f.changedContent.push(y);for(let[y,w]of v.entries())d.set(y,w)}for(let b of l)t({type:"dependency",file:b});for(let[b,v]of d.entries())p.set(b,v);return f}}var Cg,_g,_l,Eg=P(()=>{u();ft();Cg=pe(Fs());wf();sa();oc();Oi();Hh();Xh();vg();kg();Ag();_g=new Cg.default({maxSize:100}),_l=new WeakMap});function Ol(r){let e=new Set,t=new Set,i=new Set;if(r.walkAtRules(n=>{n.name==="apply"&&i.add(n),n.name==="import"&&(n.params==='"tailwindcss/base"'||n.params==="'tailwindcss/base'"?(n.name="tailwind",n.params="base"):n.params==='"tailwindcss/components"'||n.params==="'tailwindcss/components'"?(n.name="tailwind",n.params="components"):n.params==='"tailwindcss/utilities"'||n.params==="'tailwindcss/utilities'"?(n.name="tailwind",n.params="utilities"):(n.params==='"tailwindcss/screens"'||n.params==="'tailwindcss/screens'"||n.params==='"tailwindcss/variants"'||n.params==="'tailwindcss/variants'")&&(n.name="tailwind",n.params="variants")),n.name==="tailwind"&&(n.params==="screens"&&(n.params="variants"),e.add(n.params)),["layer","responsive","variants"].includes(n.name)&&(["responsive","variants"].includes(n.name)&&G.warn(`${n.name}-at-rule-deprecated`,[`The \`@${n.name}\` directive has been deprecated in Tailwind CSS v3.0.`,"Use `@layer utilities` or `@layer components` instead.","https://tailwindcss.com/docs/upgrade-guide#replace-variants-with-layer"]),t.add(n))}),!e.has("base")||!e.has("components")||!e.has("utilities")){for(let n of t)if(n.name==="layer"&&["base","components","utilities"].includes(n.params)){if(!e.has(n.params))throw n.error(`\`@layer ${n.params}\` is used but no matching \`@tailwind ${n.params}\` directive is present.`)}else if(n.name==="responsive"){if(!e.has("utilities"))throw n.error("`@responsive` is used but `@tailwind utilities` is missing.")}else if(n.name==="variants"&&!e.has("utilities"))throw n.error("`@variants` is used but `@tailwind utilities` is missing.")}return{tailwindDirectives:e,applyDirectives:i}}var Og=P(()=>{u();Be()});function Qt(r,e=void 0,t=void 0){return r.map(i=>{let n=i.clone();return t!==void 0&&(n.raws.tailwind={...n.raws.tailwind,...t}),e!==void 0&&Tg(n,s=>{if(s.raws.tailwind?.preserveSource===!0&&s.source)return!1;s.source=e}),n})}function Tg(r,e){e(r)!==!1&&r.each?.(t=>Tg(t,e))}var Rg=P(()=>{u()});function Tl(r){return r=Array.isArray(r)?r:[r],r=r.map(e=>e instanceof RegExp?e.source:e),r.join("")}function Ne(r){return new RegExp(Tl(r),"g")}function qt(r){return`(?:${r.map(Tl).join("|")})`}function Rl(r){return`(?:${Tl(r)})?`}function Ig(r){return r&&x2.test(r)?r.replace(Pg,"\\$&"):r||""}var Pg,x2,Dg=P(()=>{u();Pg=/[\\^$.*+?()[\]{}|]/g,x2=RegExp(Pg.source)});function qg(r){let e=Array.from(k2(r));return t=>{let i=[];for(let n of e)for(let s of t.match(n)??[])i.push(C2(s));for(let n of i.slice()){let s=ve(n,".");for(let a=0;a=s.length-1){i.push(o);continue}let l=Number(s[a+1]);isNaN(l)?i.push(o):a++}}return i}}function*k2(r){let e=r.tailwindConfig.separator,t=r.tailwindConfig.prefix!==""?Rl(Ne([/-?/,Ig(r.tailwindConfig.prefix)])):"",i=qt([/\[[^\s:'"`]+:[^\s\[\]]+\]/,/\[[^\s:'"`\]]+:[^\s]+?\[[^\s]+\][^\s]+?\]/,Ne([qt([/-?(?:\w+)/,/@(?:\w+)/]),Rl(qt([Ne([qt([/-(?:\w+-)*\['[^\s]+'\]/,/-(?:\w+-)*\["[^\s]+"\]/,/-(?:\w+-)*\[`[^\s]+`\]/,/-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s:\[\]]+\]/]),/(?![{([]])/,/(?:\/[^\s'"`\\><$]*)?/]),Ne([qt([/-(?:\w+-)*\['[^\s]+'\]/,/-(?:\w+-)*\["[^\s]+"\]/,/-(?:\w+-)*\[`[^\s]+`\]/,/-(?:\w+-)*\[(?:[^\s\[\]]+\[[^\s\[\]]+\])*[^\s\[\]]+\]/]),/(?![{([]])/,/(?:\/[^\s'"`\\$]*)?/]),/[-\/][^\s'"`\\$={><]*/]))])]),n=[qt([Ne([/@\[[^\s"'`]+\](\/[^\s"'`]+)?/,e]),Ne([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]\/[\w_-]+/,e]),Ne([/([^\s"'`\[\\]+-)?\[[^\s"'`]+\]/,e]),Ne([/[^\s"'`\[\\]+/,e])]),qt([Ne([/([^\s"'`\[\\]+-)?\[[^\s`]+\]\/[\w_-]+/,e]),Ne([/([^\s"'`\[\\]+-)?\[[^\s`]+\]/,e]),Ne([/[^\s`\[\\]+/,e])])];for(let s of n)yield Ne(["((?=((",s,")+))\\2)?",/!?/,t,i]);yield/[^<>"'`\s.(){}[\]#=%$][^<>"'`\s(){}[\]#=%$]*[^<>"'`\s.(){}[\]#=%:$]/g}function C2(r){if(!r.includes("-["))return r;let e=0,t=[],i=r.matchAll(S2);i=Array.from(i).flatMap(n=>{let[,...s]=n;return s.map((a,o)=>Object.assign([],n,{index:n.index+o,0:a}))});for(let n of i){let s=n[0],a=t[t.length-1];if(s===a?t.pop():(s==="'"||s==='"'||s==="`")&&t.push(s),!a){if(s==="["){e++;continue}else if(s==="]"){e--;continue}if(e<0)return r.substring(0,n.index-1);if(e===0&&!A2.test(s))return r.substring(0,n.index)}}return r}var S2,A2,$g=P(()=>{u();Dg();zt();S2=/([\[\]'"`])([^\[\]'"`])?/g,A2=/[^"'`\s<>\]]+/});function _2(r,e){let t=r.tailwindConfig.content.extract;return t[e]||t.DEFAULT||Mg[e]||Mg.DEFAULT(r)}function E2(r,e){let t=r.content.transform;return t[e]||t.DEFAULT||Ng[e]||Ng.DEFAULT}function O2(r,e,t,i){Li.has(e)||Li.set(e,new Lg.default({maxSize:25e3}));for(let n of r.split(` +`))if(n=n.trim(),!i.has(n))if(i.add(n),Li.get(e).has(n))for(let s of Li.get(e).get(n))t.add(s);else{let s=e(n).filter(o=>o!=="!*"),a=new Set(s);for(let o of a)t.add(o);Li.get(e).set(n,a)}}function T2(r,e){let t=e.offsets.sort(r),i={base:new Set,defaults:new Set,components:new Set,utilities:new Set,variants:new Set};for(let[n,s]of t)i[n.layer].add(s);return i}function Pl(r){return async e=>{let t={base:null,components:null,utilities:null,variants:null};if(e.walkAtRules(y=>{y.name==="tailwind"&&Object.keys(t).includes(y.params)&&(t[y.params]=y)}),Object.values(t).every(y=>y===null))return e;let i=new Set([...r.candidates??[],gt]),n=new Set;bt.DEBUG&&console.time("Reading changed files");let s=[];for(let y of r.changedContent){let w=E2(r.tailwindConfig,y.extension),k=_2(r,y.extension);s.push([y,{transformer:w,extractor:k}])}let a=500;for(let y=0;y{S=k?await be.promises.readFile(k,"utf8"):S,O2(E(S),T,i,n)}))}bt.DEBUG&&console.timeEnd("Reading changed files");let o=r.classCache.size;bt.DEBUG&&console.time("Generate rules"),bt.DEBUG&&console.time("Sorting candidates");let l=new Set([...i].sort((y,w)=>y===w?0:y{let w=y.raws.tailwind?.parentLayer;return w==="components"?t.components!==null:w==="utilities"?t.utilities!==null:!0});t.variants?(t.variants.before(Qt(b,t.variants.source,{layer:"variants"})),t.variants.remove()):b.length>0&&e.append(Qt(b,e.source,{layer:"variants"})),e.source.end=e.source.end??e.source.start;let v=b.some(y=>y.raws.tailwind?.parentLayer==="utilities");t.utilities&&p.size===0&&!v&&G.warn("content-problems",["No utility classes were detected in your source files. If this is unexpected, double-check the `content` option in your Tailwind CSS configuration.","https://tailwindcss.com/docs/content-configuration"]),bt.DEBUG&&(console.log("Potential classes: ",i.size),console.log("Active contexts: ",es.size)),r.changedContent=[],e.walkAtRules("layer",y=>{Object.keys(t).includes(y.params)&&y.remove()})}}var Lg,bt,Mg,Ng,Li,Bg=P(()=>{u();ft();Lg=pe(Fs());It();os();Be();Rg();$g();bt=Ze,Mg={DEFAULT:qg},Ng={DEFAULT:r=>r,svelte:r=>r.replace(/(?:^|\s)class:/g," ")};Li=new WeakMap});function xs(r){let e=new Map;ee.root({nodes:[r.clone()]}).walkRules(s=>{(0,vs.default)(a=>{a.walkClasses(o=>{let l=o.parent.toString(),c=e.get(l);c||e.set(l,c=new Set),c.add(o.value)})}).processSync(s.selector)});let i=Array.from(e.values(),s=>Array.from(s)),n=i.flat();return Object.assign(n,{groups:i})}function Il(r){return R2.astSync(r)}function Fg(r,e){let t=new Set;for(let i of r)t.add(i.split(e).pop());return Array.from(t)}function jg(r,e){let t=r.tailwindConfig.prefix;return typeof t=="function"?t(e):t+e}function*zg(r){for(yield r;r.parent;)yield r.parent,r=r.parent}function P2(r,e={}){let t=r.nodes;r.nodes=[];let i=r.clone(e);return r.nodes=t,i}function I2(r){for(let e of zg(r))if(r!==e){if(e.type==="root")break;r=P2(e,{nodes:[r]})}return r}function D2(r,e){let t=new Map;return r.walkRules(i=>{for(let a of zg(i))if(a.raws.tailwind?.layer!==void 0)return;let n=I2(i),s=e.offsets.create("user");for(let a of xs(i)){let o=t.get(a)||[];t.set(a,o),o.push([{layer:"user",sort:s,important:!1},n])}}),t}function q2(r,e){for(let t of r){if(e.notClassCache.has(t)||e.applyClassCache.has(t))continue;if(e.classCache.has(t)){e.applyClassCache.set(t,e.classCache.get(t).map(([n,s])=>[n,s.clone()]));continue}let i=Array.from(Yo(t,e));if(i.length===0){e.notClassCache.add(t);continue}e.applyClassCache.set(t,i)}return e.applyClassCache}function $2(r){let e=null;return{get:t=>(e=e||r(),e.get(t)),has:t=>(e=e||r(),e.has(t))}}function L2(r){return{get:e=>r.flatMap(t=>t.get(e)||[]),has:e=>r.some(t=>t.has(e))}}function Ug(r){let e=r.split(/[\s\t\n]+/g);return e[e.length-1]==="!important"?[e.slice(0,-1),!0]:[e,!1]}function Vg(r,e,t){let i=new Set,n=[];if(r.walkAtRules("apply",l=>{let[c]=Ug(l.params);for(let f of c)i.add(f);n.push(l)}),n.length===0)return;let s=L2([t,q2(i,e)]);function a(l,c,f){let d=Il(l),p=Il(c),b=Il(`.${Te(f)}`).nodes[0].nodes[0];return d.each(v=>{let y=new Set;p.each(w=>{let k=!1;w=w.clone(),w.walkClasses(S=>{S.value===b.value&&(k||(S.replaceWith(...v.nodes.map(E=>E.clone())),y.add(w),k=!0))})});for(let w of y){let k=[[]];for(let S of w.nodes)S.type==="combinator"?(k.push(S),k.push([])):k[k.length-1].push(S);w.nodes=[];for(let S of k)Array.isArray(S)&&S.sort((E,T)=>E.type==="tag"&&T.type==="class"?-1:E.type==="class"&&T.type==="tag"?1:E.type==="class"&&T.type==="pseudo"&&T.value.startsWith("::")?-1:E.type==="pseudo"&&E.value.startsWith("::")&&T.type==="class"?1:0),w.nodes=w.nodes.concat(S)}v.replaceWith(...y)}),d.toString()}let o=new Map;for(let l of n){let[c]=o.get(l.parent)||[[],l.source];o.set(l.parent,[c,l.source]);let[f,d]=Ug(l.params);if(l.parent.type==="atrule"){if(l.parent.name==="screen"){let p=l.parent.params;throw l.error(`@apply is not supported within nested at-rules like @screen. We suggest you write this as @apply ${f.map(h=>`${p}:${h}`).join(" ")} instead.`)}throw l.error(`@apply is not supported within nested at-rules like @${l.parent.name}. You can fix this by un-nesting @${l.parent.name}.`)}for(let p of f){if([jg(e,"group"),jg(e,"peer")].includes(p))throw l.error(`@apply should not be used with the '${p}' utility`);if(!s.has(p))throw l.error(`The \`${p}\` class does not exist. If \`${p}\` is a custom class, make sure it is defined within a \`@layer\` directive.`);let h=s.get(p);for(let[,b]of h)b.type!=="atrule"&&b.walkRules(()=>{throw l.error([`The \`${p}\` class cannot be used with \`@apply\` because \`@apply\` does not currently support nested CSS.`,"Rewrite the selector without nesting or configure the `tailwindcss/nesting` plugin:","https://tailwindcss.com/docs/using-with-preprocessors#nesting"].join(` +`))});c.push([p,d,h])}}for(let[l,[c,f]]of o){let d=[];for(let[h,b,v]of c){let y=[h,...Fg([h],e.tailwindConfig.separator)];for(let[w,k]of v){let S=xs(l),E=xs(k);if(E=E.groups.filter(R=>R.some(F=>y.includes(F))).flat(),E=E.concat(Fg(E,e.tailwindConfig.separator)),S.some(R=>E.includes(R)))throw k.error(`You cannot \`@apply\` the \`${h}\` utility here because it creates a circular dependency.`);let B=ee.root({nodes:[k.clone()]});B.walk(R=>{R.source=f}),(k.type!=="atrule"||k.type==="atrule"&&k.name!=="keyframes")&&B.walkRules(R=>{if(!xs(R).some(U=>U===h)){R.remove();return}let F=typeof e.tailwindConfig.important=="string"?e.tailwindConfig.important:null,_=l.raws.tailwind!==void 0&&F&&l.selector.indexOf(F)===0?l.selector.slice(F.length):l.selector;_===""&&(_=l.selector),R.selector=a(_,R.selector,h),F&&_!==l.selector&&(R.selector=is(R.selector,F)),R.walkDecls(U=>{U.important=w.important||b});let Q=(0,vs.default)().astSync(R.selector);Q.each(U=>pr(U)),R.selector=Q.toString()}),!!B.nodes[0]&&d.push([w.sort,B.nodes[0]])}}let p=e.offsets.sort(d).map(h=>h[1]);l.after(p)}for(let l of n)l.parent.nodes.length>1?l.remove():l.parent.remove();Vg(r,e,t)}function Dl(r){return e=>{let t=$2(()=>D2(e,r));Vg(e,r,t)}}var vs,R2,Hg=P(()=>{u();Ot();vs=pe(it());os();fr();Wo();ts();R2=(0,vs.default)()});var Wg=x((nq,ks)=>{u();(function(){"use strict";function r(i,n,s){if(!i)return null;r.caseSensitive||(i=i.toLowerCase());var a=r.threshold===null?null:r.threshold*i.length,o=r.thresholdAbsolute,l;a!==null&&o!==null?l=Math.min(a,o):a!==null?l=a:o!==null?l=o:l=null;var c,f,d,p,h,b=n.length;for(h=0;hs)return s+1;var l=[],c,f,d,p,h;for(c=0;c<=o;c++)l[c]=[c];for(f=0;f<=a;f++)l[0][f]=f;for(c=1;c<=o;c++){for(d=e,p=1,c>s&&(p=c-s),h=o+1,h>s+c&&(h=s+c),f=1;f<=a;f++)fh?l[c][f]=s+1:n.charAt(c-1)===i.charAt(f-1)?l[c][f]=l[c-1][f-1]:l[c][f]=Math.min(l[c-1][f-1]+1,Math.min(l[c][f-1]+1,l[c-1][f]+1)),l[c][f]s)return s+1}return l[o][a]}})()});var Qg=x((sq,Gg)=>{u();var ql="(".charCodeAt(0),$l=")".charCodeAt(0),Ss="'".charCodeAt(0),Ll='"'.charCodeAt(0),Ml="\\".charCodeAt(0),yr="/".charCodeAt(0),Nl=",".charCodeAt(0),Bl=":".charCodeAt(0),As="*".charCodeAt(0),M2="u".charCodeAt(0),N2="U".charCodeAt(0),B2="+".charCodeAt(0),F2=/^[a-f0-9?-]+$/i;Gg.exports=function(r){for(var e=[],t=r,i,n,s,a,o,l,c,f,d=0,p=t.charCodeAt(d),h=t.length,b=[{nodes:e}],v=0,y,w="",k="",S="";d{u();Yg.exports=function r(e,t,i){var n,s,a,o;for(n=0,s=e.length;n{u();function Xg(r,e){var t=r.type,i=r.value,n,s;return e&&(s=e(r))!==void 0?s:t==="word"||t==="space"?i:t==="string"?(n=r.quote||"",n+i+(r.unclosed?"":n)):t==="comment"?"/*"+i+(r.unclosed?"":"*/"):t==="div"?(r.before||"")+i+(r.after||""):Array.isArray(r.nodes)?(n=Zg(r.nodes,e),t!=="function"?n:i+"("+(r.before||"")+n+(r.after||"")+(r.unclosed?"":")")):i}function Zg(r,e){var t,i;if(Array.isArray(r)){for(t="",i=r.length-1;~i;i-=1)t=Xg(r[i],e)+t;return t}return Xg(r,e)}Jg.exports=Zg});var ry=x((lq,ty)=>{u();var Cs="-".charCodeAt(0),_s="+".charCodeAt(0),Fl=".".charCodeAt(0),j2="e".charCodeAt(0),z2="E".charCodeAt(0);function U2(r){var e=r.charCodeAt(0),t;if(e===_s||e===Cs){if(t=r.charCodeAt(1),t>=48&&t<=57)return!0;var i=r.charCodeAt(2);return t===Fl&&i>=48&&i<=57}return e===Fl?(t=r.charCodeAt(1),t>=48&&t<=57):e>=48&&e<=57}ty.exports=function(r){var e=0,t=r.length,i,n,s;if(t===0||!U2(r))return!1;for(i=r.charCodeAt(e),(i===_s||i===Cs)&&e++;e57));)e+=1;if(i=r.charCodeAt(e),n=r.charCodeAt(e+1),i===Fl&&n>=48&&n<=57)for(e+=2;e57));)e+=1;if(i=r.charCodeAt(e),n=r.charCodeAt(e+1),s=r.charCodeAt(e+2),(i===j2||i===z2)&&(n>=48&&n<=57||(n===_s||n===Cs)&&s>=48&&s<=57))for(e+=n===_s||n===Cs?3:2;e57));)e+=1;return{number:r.slice(0,e),unit:r.slice(e)}}});var ay=x((uq,sy)=>{u();var V2=Qg(),iy=Kg(),ny=ey();function $t(r){return this instanceof $t?(this.nodes=V2(r),this):new $t(r)}$t.prototype.toString=function(){return Array.isArray(this.nodes)?ny(this.nodes):""};$t.prototype.walk=function(r,e){return iy(this.nodes,r,e),this};$t.unit=ry();$t.walk=iy;$t.stringify=ny;sy.exports=$t});function zl(r){return typeof r=="object"&&r!==null}function H2(r,e){let t=kt(e);do if(t.pop(),(0,Mi.default)(r,t)!==void 0)break;while(t.length);return t.length?t:void 0}function br(r){return typeof r=="string"?r:r.reduce((e,t,i)=>t.includes(".")?`${e}[${t}]`:i===0?t:`${e}.${t}`,"")}function ly(r){return r.map(e=>`'${e}'`).join(", ")}function uy(r){return ly(Object.keys(r))}function Ul(r,e,t,i={}){let n=Array.isArray(e)?br(e):e.replace(/^['"]+|['"]+$/g,""),s=Array.isArray(e)?e:kt(n),a=(0,Mi.default)(r.theme,s,t);if(a===void 0){let l=`'${n}' does not exist in your theme config.`,c=s.slice(0,-1),f=(0,Mi.default)(r.theme,c);if(zl(f)){let d=Object.keys(f).filter(h=>Ul(r,[...c,h]).isValid),p=(0,oy.default)(s[s.length-1],d);p?l+=` Did you mean '${br([...c,p])}'?`:d.length>0&&(l+=` '${br(c)}' has the following valid keys: ${ly(d)}`)}else{let d=H2(r.theme,n);if(d){let p=(0,Mi.default)(r.theme,d);zl(p)?l+=` '${br(d)}' has the following keys: ${uy(p)}`:l+=` '${br(d)}' is not an object.`}else l+=` Your theme has the following top-level keys: ${uy(r.theme)}`}return{isValid:!1,error:l}}if(!(typeof a=="string"||typeof a=="number"||typeof a=="function"||a instanceof String||a instanceof Number||Array.isArray(a))){let l=`'${n}' was found but does not resolve to a string.`;if(zl(a)){let c=Object.keys(a).filter(f=>Ul(r,[...s,f]).isValid);c.length&&(l+=` Did you mean something like '${br([...s,c[0]])}'?`)}return{isValid:!1,error:l}}let[o]=s;return{isValid:!0,value:mt(o)(a,i)}}function W2(r,e,t){e=e.map(n=>fy(r,n,t));let i=[""];for(let n of e)n.type==="div"&&n.value===","?i.push(""):i[i.length-1]+=jl.default.stringify(n);return i}function fy(r,e,t){if(e.type==="function"&&t[e.value]!==void 0){let i=W2(r,e.nodes,t);e.type="word",e.value=t[e.value](r,...i)}return e}function G2(r,e,t){return Object.keys(t).some(n=>e.includes(`${n}(`))?(0,jl.default)(e).walk(n=>{fy(r,n,t)}).toString():e}function*Y2(r){r=r.replace(/^['"]+|['"]+$/g,"");let e=r.match(/^([^\s]+)(?![^\[]*\])(?:\s*\/\s*([^\/\s]+))$/),t;yield[r,void 0],e&&(r=e[1],t=e[2],yield[r,t])}function K2(r,e,t){let i=Array.from(Y2(e)).map(([n,s])=>Object.assign(Ul(r,n,t,{opacityValue:s}),{resolvedPath:n,alpha:s}));return i.find(n=>n.isValid)??i[0]}function cy(r){let e=r.tailwindConfig,t={theme:(i,n,...s)=>{let{isValid:a,value:o,error:l,alpha:c}=K2(e,n,s.length?s:void 0);if(!a){let p=i.parent,h=p?.raws.tailwind?.candidate;if(p&&h!==void 0){r.markInvalidUtilityNode(p),p.remove(),G.warn("invalid-theme-key-in-class",[`The utility \`${h}\` contains an invalid theme value and was not generated.`]);return}throw i.error(l)}let f=Xt(o),d=f!==void 0&&typeof f=="function";return(c!==void 0||d)&&(c===void 0&&(c=1),o=Je(f,c,f)),o},screen:(i,n)=>{n=n.replace(/^['"]+/g,"").replace(/['"]+$/g,"");let a=Rt(e.theme.screens).find(({name:o})=>o===n);if(!a)throw i.error(`The '${n}' screen does not exist in your theme.`);return Tt(a)}};return i=>{i.walk(n=>{let s=Q2[n.type];s!==void 0&&(n[s]=G2(n,n[s],t))})}}var Mi,oy,jl,Q2,py=P(()=>{u();Mi=pe(Ra()),oy=pe(Wg());Ci();jl=pe(ay());Zn();Yn();Yi();Lr();Fr();Be();Q2={atrule:"params",decl:"value"}});function dy({tailwindConfig:{theme:r}}){return function(e){e.walkAtRules("screen",t=>{let i=t.params,s=Rt(r.screens).find(({name:a})=>a===i);if(!s)throw t.error(`No \`${i}\` screen found.`);t.name="media",t.params=Tt(s)})}}var hy=P(()=>{u();Zn();Yn()});function X2(r){let e=r.filter(o=>o.type!=="pseudo"||o.nodes.length>0?!0:o.value.startsWith("::")||[":before",":after",":first-line",":first-letter"].includes(o.value)).reverse(),t=new Set(["tag","class","id","attribute"]),i=e.findIndex(o=>t.has(o.type));if(i===-1)return e.reverse().join("").trim();let n=e[i],s=my[n.type]?my[n.type](n):n;e=e.slice(0,i);let a=e.findIndex(o=>o.type==="combinator"&&o.value===">");return a!==-1&&(e.splice(0,a),e.unshift(Es.default.universal())),[s,...e.reverse()].join("").trim()}function J2(r){return Vl.has(r)||Vl.set(r,Z2.transformSync(r)),Vl.get(r)}function Hl({tailwindConfig:r}){return e=>{let t=new Map,i=new Set;if(e.walkAtRules("defaults",n=>{if(n.nodes&&n.nodes.length>0){i.add(n);return}let s=n.params;t.has(s)||t.set(s,new Set),t.get(s).add(n.parent),n.remove()}),we(r,"optimizeUniversalDefaults"))for(let n of i){let s=new Map,a=t.get(n.params)??[];for(let o of a)for(let l of J2(o.selector)){let c=l.includes(":-")||l.includes("::-")||l.includes(":has")?l:"__DEFAULT__",f=s.get(c)??new Set;s.set(c,f),f.add(l)}if(s.size===0){n.remove();continue}for(let[,o]of s){let l=ee.rule({source:n.source});l.selectors=[...o],l.append(n.nodes.map(c=>c.clone())),n.before(l)}n.remove()}else if(i.size){let n=ee.rule({selectors:["*","::before","::after"]});for(let a of i)n.append(a.nodes),n.parent||a.before(n),n.source||(n.source=a.source),a.remove();let s=n.clone({selectors:["::backdrop"]});n.after(s)}}}var Es,my,Z2,Vl,gy=P(()=>{u();Ot();Es=pe(it());ct();my={id(r){return Es.default.attribute({attribute:"id",operator:"=",value:r.value,quoteMark:'"'})}};Z2=(0,Es.default)(r=>r.map(e=>{let t=e.split(i=>i.type==="combinator"&&i.value===" ").pop();return X2(t)})),Vl=new Map});function Wl(){function r(e){let t=null;e.each(i=>{if(!eO.has(i.type)){t=null;return}if(t===null){t=i;return}let n=yy[i.type];i.type==="atrule"&&i.name==="font-face"?t=i:n.every(s=>(i[s]??"").replace(/\s+/g," ")===(t[s]??"").replace(/\s+/g," "))?(i.nodes&&t.append(i.nodes),i.remove()):t=i}),e.each(i=>{i.type==="atrule"&&r(i)})}return e=>{r(e)}}var yy,eO,by=P(()=>{u();yy={atrule:["name","params"],rule:["selector"]},eO=new Set(Object.keys(yy))});function Gl(){return r=>{r.walkRules(e=>{let t=new Map,i=new Set([]),n=new Map;e.walkDecls(s=>{if(s.parent===e){if(t.has(s.prop)){if(t.get(s.prop).value===s.value){i.add(t.get(s.prop)),t.set(s.prop,s);return}n.has(s.prop)||n.set(s.prop,new Set),n.get(s.prop).add(t.get(s.prop)),n.get(s.prop).add(s)}t.set(s.prop,s)}});for(let s of i)s.remove();for(let s of n.values()){let a=new Map;for(let o of s){let l=rO(o.value);l!==null&&(a.has(l)||a.set(l,new Set),a.get(l).add(o))}for(let o of a.values()){let l=Array.from(o).slice(0,-1);for(let c of l)c.remove()}}})}}function rO(r){let e=/^-?\d*.?\d+([\w%]+)?$/g.exec(r);return e?e[1]??tO:null}var tO,wy=P(()=>{u();tO=Symbol("unitless-number")});function iO(r){if(!r.walkAtRules)return;let e=new Set;if(r.walkAtRules("apply",t=>{e.add(t.parent)}),e.size!==0)for(let t of e){let i=[],n=[];for(let s of t.nodes)s.type==="atrule"&&s.name==="apply"?(n.length>0&&(i.push(n),n=[]),i.push([s])):n.push(s);if(n.length>0&&i.push(n),i.length!==1){for(let s of[...i].reverse()){let a=t.clone({nodes:[]});a.append(s),t.after(a)}t.remove()}}}function Os(){return r=>{iO(r)}}var vy=P(()=>{u()});function Ts(r){return async function(e,t){let{tailwindDirectives:i,applyDirectives:n}=Ol(e);Os()(e,t);let s=r({tailwindDirectives:i,applyDirectives:n,registerDependency(a){t.messages.push({plugin:"tailwindcss",parent:t.opts.from,...a})},createContext(a,o){return il(a,o,e)}})(e,t);if(s.tailwindConfig.separator==="-")throw new Error("The '-' character cannot be used as a custom separator in JIT mode due to parsing ambiguity. Please use another character like '_' instead.");Rf(s.tailwindConfig),await Pl(s)(e,t),Os()(e,t),Dl(s)(e,t),cy(s)(e,t),dy(s)(e,t),Hl(s)(e,t),Wl(s)(e,t),Gl(s)(e,t)}}var xy=P(()=>{u();Og();Bg();Hg();py();hy();gy();by();wy();vy();Oi();ct()});function ky(r,e){let t=null,i=null;return r.walkAtRules("config",n=>{if(i=n.source?.input.file??e.opts.from??null,i===null)throw n.error("The `@config` directive cannot be used without setting `from` in your PostCSS config.");if(t)throw n.error("Only one `@config` directive is allowed per file.");let s=n.params.match(/(['"])(.*?)\1/);if(!s)throw n.error("A path is required when using the `@config` directive.");let a=s[2];if(me.isAbsolute(a))throw n.error("The `@config` directive cannot be used with an absolute path.");if(t=me.resolve(me.dirname(i),a),!be.existsSync(t))throw n.error(`The config file at "${a}" does not exist. Make sure the path is correct and the file exists.`);n.remove()}),t||null}var Sy=P(()=>{u();ft();et()});var Ay=x((Wq,Ql)=>{u();Eg();xy();It();Sy();Ql.exports=function(e){return{postcssPlugin:"tailwindcss",plugins:[Ze.DEBUG&&function(t){return console.log(` +`),console.time("JIT TOTAL"),t},async function(t,i){e=ky(t,i)??e;let n=El(e);if(t.type==="document"){let s=t.nodes.filter(a=>a.type==="root");for(let a of s)a.type==="root"&&await Ts(n)(a,i);return}await Ts(n)(t,i)},Ze.DEBUG&&function(t){return console.timeEnd("JIT TOTAL"),console.log(` +`),t}].filter(Boolean)}};Ql.exports.postcss=!0});var _y=x((Gq,Cy)=>{u();Cy.exports=Ay()});var Yl=x((Qq,Ey)=>{u();Ey.exports=()=>["and_chr 114","and_uc 15.5","chrome 114","chrome 113","chrome 109","edge 114","firefox 114","ios_saf 16.5","ios_saf 16.4","ios_saf 16.3","ios_saf 16.1","opera 99","safari 16.5","samsung 21"]});var Rs={};Ge(Rs,{agents:()=>nO,feature:()=>sO});function sO(){return{status:"cr",title:"CSS Feature Queries",stats:{ie:{"6":"n","7":"n","8":"n","9":"n","10":"n","11":"n","5.5":"n"},edge:{"12":"y","13":"y","14":"y","15":"y","16":"y","17":"y","18":"y","79":"y","80":"y","81":"y","83":"y","84":"y","85":"y","86":"y","87":"y","88":"y","89":"y","90":"y","91":"y","92":"y","93":"y","94":"y","95":"y","96":"y","97":"y","98":"y","99":"y","100":"y","101":"y","102":"y","103":"y","104":"y","105":"y","106":"y","107":"y","108":"y","109":"y","110":"y","111":"y","112":"y","113":"y","114":"y"},firefox:{"2":"n","3":"n","4":"n","5":"n","6":"n","7":"n","8":"n","9":"n","10":"n","11":"n","12":"n","13":"n","14":"n","15":"n","16":"n","17":"n","18":"n","19":"n","20":"n","21":"n","22":"y","23":"y","24":"y","25":"y","26":"y","27":"y","28":"y","29":"y","30":"y","31":"y","32":"y","33":"y","34":"y","35":"y","36":"y","37":"y","38":"y","39":"y","40":"y","41":"y","42":"y","43":"y","44":"y","45":"y","46":"y","47":"y","48":"y","49":"y","50":"y","51":"y","52":"y","53":"y","54":"y","55":"y","56":"y","57":"y","58":"y","59":"y","60":"y","61":"y","62":"y","63":"y","64":"y","65":"y","66":"y","67":"y","68":"y","69":"y","70":"y","71":"y","72":"y","73":"y","74":"y","75":"y","76":"y","77":"y","78":"y","79":"y","80":"y","81":"y","82":"y","83":"y","84":"y","85":"y","86":"y","87":"y","88":"y","89":"y","90":"y","91":"y","92":"y","93":"y","94":"y","95":"y","96":"y","97":"y","98":"y","99":"y","100":"y","101":"y","102":"y","103":"y","104":"y","105":"y","106":"y","107":"y","108":"y","109":"y","110":"y","111":"y","112":"y","113":"y","114":"y","115":"y","116":"y","117":"y","3.5":"n","3.6":"n"},chrome:{"4":"n","5":"n","6":"n","7":"n","8":"n","9":"n","10":"n","11":"n","12":"n","13":"n","14":"n","15":"n","16":"n","17":"n","18":"n","19":"n","20":"n","21":"n","22":"n","23":"n","24":"n","25":"n","26":"n","27":"n","28":"y","29":"y","30":"y","31":"y","32":"y","33":"y","34":"y","35":"y","36":"y","37":"y","38":"y","39":"y","40":"y","41":"y","42":"y","43":"y","44":"y","45":"y","46":"y","47":"y","48":"y","49":"y","50":"y","51":"y","52":"y","53":"y","54":"y","55":"y","56":"y","57":"y","58":"y","59":"y","60":"y","61":"y","62":"y","63":"y","64":"y","65":"y","66":"y","67":"y","68":"y","69":"y","70":"y","71":"y","72":"y","73":"y","74":"y","75":"y","76":"y","77":"y","78":"y","79":"y","80":"y","81":"y","83":"y","84":"y","85":"y","86":"y","87":"y","88":"y","89":"y","90":"y","91":"y","92":"y","93":"y","94":"y","95":"y","96":"y","97":"y","98":"y","99":"y","100":"y","101":"y","102":"y","103":"y","104":"y","105":"y","106":"y","107":"y","108":"y","109":"y","110":"y","111":"y","112":"y","113":"y","114":"y","115":"y","116":"y","117":"y"},safari:{"4":"n","5":"n","6":"n","7":"n","8":"n","9":"y","10":"y","11":"y","12":"y","13":"y","14":"y","15":"y","17":"y","9.1":"y","10.1":"y","11.1":"y","12.1":"y","13.1":"y","14.1":"y","15.1":"y","15.2-15.3":"y","15.4":"y","15.5":"y","15.6":"y","16.0":"y","16.1":"y","16.2":"y","16.3":"y","16.4":"y","16.5":"y","16.6":"y",TP:"y","3.1":"n","3.2":"n","5.1":"n","6.1":"n","7.1":"n"},opera:{"9":"n","11":"n","12":"n","15":"y","16":"y","17":"y","18":"y","19":"y","20":"y","21":"y","22":"y","23":"y","24":"y","25":"y","26":"y","27":"y","28":"y","29":"y","30":"y","31":"y","32":"y","33":"y","34":"y","35":"y","36":"y","37":"y","38":"y","39":"y","40":"y","41":"y","42":"y","43":"y","44":"y","45":"y","46":"y","47":"y","48":"y","49":"y","50":"y","51":"y","52":"y","53":"y","54":"y","55":"y","56":"y","57":"y","58":"y","60":"y","62":"y","63":"y","64":"y","65":"y","66":"y","67":"y","68":"y","69":"y","70":"y","71":"y","72":"y","73":"y","74":"y","75":"y","76":"y","77":"y","78":"y","79":"y","80":"y","81":"y","82":"y","83":"y","84":"y","85":"y","86":"y","87":"y","88":"y","89":"y","90":"y","91":"y","92":"y","93":"y","94":"y","95":"y","96":"y","97":"y","98":"y","99":"y","100":"y","12.1":"y","9.5-9.6":"n","10.0-10.1":"n","10.5":"n","10.6":"n","11.1":"n","11.5":"n","11.6":"n"},ios_saf:{"8":"n","17":"y","9.0-9.2":"y","9.3":"y","10.0-10.2":"y","10.3":"y","11.0-11.2":"y","11.3-11.4":"y","12.0-12.1":"y","12.2-12.5":"y","13.0-13.1":"y","13.2":"y","13.3":"y","13.4-13.7":"y","14.0-14.4":"y","14.5-14.8":"y","15.0-15.1":"y","15.2-15.3":"y","15.4":"y","15.5":"y","15.6":"y","16.0":"y","16.1":"y","16.2":"y","16.3":"y","16.4":"y","16.5":"y","16.6":"y","3.2":"n","4.0-4.1":"n","4.2-4.3":"n","5.0-5.1":"n","6.0-6.1":"n","7.0-7.1":"n","8.1-8.4":"n"},op_mini:{all:"y"},android:{"3":"n","4":"n","114":"y","4.4":"y","4.4.3-4.4.4":"y","2.1":"n","2.2":"n","2.3":"n","4.1":"n","4.2-4.3":"n"},bb:{"7":"n","10":"n"},op_mob:{"10":"n","11":"n","12":"n","73":"y","11.1":"n","11.5":"n","12.1":"n"},and_chr:{"114":"y"},and_ff:{"115":"y"},ie_mob:{"10":"n","11":"n"},and_uc:{"15.5":"y"},samsung:{"4":"y","20":"y","21":"y","5.0-5.4":"y","6.2-6.4":"y","7.2-7.4":"y","8.2":"y","9.2":"y","10.1":"y","11.1-11.2":"y","12.0":"y","13.0":"y","14.0":"y","15.0":"y","16.0":"y","17.0":"y","18.0":"y","19.0":"y"},and_qq:{"13.1":"y"},baidu:{"13.18":"y"},kaios:{"2.5":"y","3.0-3.1":"y"}}}}var nO,Ps=P(()=>{u();nO={ie:{prefix:"ms"},edge:{prefix:"webkit",prefix_exceptions:{"12":"ms","13":"ms","14":"ms","15":"ms","16":"ms","17":"ms","18":"ms"}},firefox:{prefix:"moz"},chrome:{prefix:"webkit"},safari:{prefix:"webkit"},opera:{prefix:"webkit",prefix_exceptions:{"9":"o","11":"o","12":"o","9.5-9.6":"o","10.0-10.1":"o","10.5":"o","10.6":"o","11.1":"o","11.5":"o","11.6":"o","12.1":"o"}},ios_saf:{prefix:"webkit"},op_mini:{prefix:"o"},android:{prefix:"webkit"},bb:{prefix:"webkit"},op_mob:{prefix:"o",prefix_exceptions:{"73":"webkit"}},and_chr:{prefix:"webkit"},and_ff:{prefix:"moz"},ie_mob:{prefix:"ms"},and_uc:{prefix:"webkit",prefix_exceptions:{"15.5":"webkit"}},samsung:{prefix:"webkit"},and_qq:{prefix:"webkit"},baidu:{prefix:"webkit"},kaios:{prefix:"moz"}}});var Oy=x(()=>{u()});var _e=x((Xq,Lt)=>{u();var{list:Kl}=$e();Lt.exports.error=function(r){let e=new Error(r);throw e.autoprefixer=!0,e};Lt.exports.uniq=function(r){return[...new Set(r)]};Lt.exports.removeNote=function(r){return r.includes(" ")?r.split(" ")[0]:r};Lt.exports.escapeRegexp=function(r){return r.replace(/[$()*+-.?[\\\]^{|}]/g,"\\$&")};Lt.exports.regexp=function(r,e=!0){return e&&(r=this.escapeRegexp(r)),new RegExp(`(^|[\\s,(])(${r}($|[\\s(,]))`,"gi")};Lt.exports.editList=function(r,e){let t=Kl.comma(r),i=e(t,[]);if(t===i)return r;let n=r.match(/,\s*/);return n=n?n[0]:", ",i.join(n)};Lt.exports.splitSelector=function(r){return Kl.comma(r).map(e=>Kl.space(e).map(t=>t.split(/(?=\.|#)/g)))}});var Mt=x((Zq,Py)=>{u();var aO=Yl(),Ty=(Ps(),Rs).agents,oO=_e(),Ry=class{static prefixes(){if(this.prefixesCache)return this.prefixesCache;this.prefixesCache=[];for(let e in Ty)this.prefixesCache.push(`-${Ty[e].prefix}-`);return this.prefixesCache=oO.uniq(this.prefixesCache).sort((e,t)=>t.length-e.length),this.prefixesCache}static withPrefix(e){return this.prefixesRegexp||(this.prefixesRegexp=new RegExp(this.prefixes().join("|"))),this.prefixesRegexp.test(e)}constructor(e,t,i,n){this.data=e,this.options=i||{},this.browserslistOpts=n||{},this.selected=this.parse(t)}parse(e){let t={};for(let i in this.browserslistOpts)t[i]=this.browserslistOpts[i];return t.path=this.options.from,aO(e,t)}prefix(e){let[t,i]=e.split(" "),n=this.data[t],s=n.prefix_exceptions&&n.prefix_exceptions[i];return s||(s=n.prefix),`-${s}-`}isSelected(e){return this.selected.includes(e)}};Py.exports=Ry});var Ni=x((Jq,Iy)=>{u();Iy.exports={prefix(r){let e=r.match(/^(-\w+-)/);return e?e[0]:""},unprefixed(r){return r.replace(/^-\w+-/,"")}}});var wr=x((e$,qy)=>{u();var lO=Mt(),Dy=Ni(),uO=_e();function Xl(r,e){let t=new r.constructor;for(let i of Object.keys(r||{})){let n=r[i];i==="parent"&&typeof n=="object"?e&&(t[i]=e):i==="source"||i===null?t[i]=n:Array.isArray(n)?t[i]=n.map(s=>Xl(s,t)):i!=="_autoprefixerPrefix"&&i!=="_autoprefixerValues"&&i!=="proxyCache"&&(typeof n=="object"&&n!==null&&(n=Xl(n,t)),t[i]=n)}return t}var Is=class{static hack(e){return this.hacks||(this.hacks={}),e.names.map(t=>(this.hacks[t]=e,this.hacks[t]))}static load(e,t,i){let n=this.hacks&&this.hacks[e];return n?new n(e,t,i):new this(e,t,i)}static clone(e,t){let i=Xl(e);for(let n in t)i[n]=t[n];return i}constructor(e,t,i){this.prefixes=t,this.name=e,this.all=i}parentPrefix(e){let t;return typeof e._autoprefixerPrefix!="undefined"?t=e._autoprefixerPrefix:e.type==="decl"&&e.prop[0]==="-"?t=Dy.prefix(e.prop):e.type==="root"?t=!1:e.type==="rule"&&e.selector.includes(":-")&&/:(-\w+-)/.test(e.selector)?t=e.selector.match(/:(-\w+-)/)[1]:e.type==="atrule"&&e.name[0]==="-"?t=Dy.prefix(e.name):t=this.parentPrefix(e.parent),lO.prefixes().includes(t)||(t=!1),e._autoprefixerPrefix=t,e._autoprefixerPrefix}process(e,t){if(!this.check(e))return;let i=this.parentPrefix(e),n=this.prefixes.filter(a=>!i||i===uO.removeNote(a)),s=[];for(let a of n)this.add(e,a,s.concat([a]),t)&&s.push(a);return s}clone(e,t){return Is.clone(e,t)}};qy.exports=Is});var j=x((t$,My)=>{u();var fO=wr(),cO=Mt(),$y=_e(),Ly=class extends fO{check(){return!0}prefixed(e,t){return t+e}normalize(e){return e}otherPrefixes(e,t){for(let i of cO.prefixes())if(i!==t&&e.includes(i))return!0;return!1}set(e,t){return e.prop=this.prefixed(e.prop,t),e}needCascade(e){return e._autoprefixerCascade||(e._autoprefixerCascade=this.all.options.cascade!==!1&&e.raw("before").includes(` +`)),e._autoprefixerCascade}maxPrefixed(e,t){if(t._autoprefixerMax)return t._autoprefixerMax;let i=0;for(let n of e)n=$y.removeNote(n),n.length>i&&(i=n.length);return t._autoprefixerMax=i,t._autoprefixerMax}calcBefore(e,t,i=""){let s=this.maxPrefixed(e,t)-$y.removeNote(i).length,a=t.raw("before");return s>0&&(a+=Array(s).fill(" ").join("")),a}restoreBefore(e){let t=e.raw("before").split(` +`),i=t[t.length-1];this.all.group(e).up(n=>{let s=n.raw("before").split(` +`),a=s[s.length-1];a.lengtha.prop===n.prop&&a.value===n.value)))return this.needCascade(e)&&(n.raws.before=this.calcBefore(i,e,t)),e.parent.insertBefore(e,n)}isAlready(e,t){let i=this.all.group(e).up(n=>n.prop===t);return i||(i=this.all.group(e).down(n=>n.prop===t)),i}add(e,t,i,n){let s=this.prefixed(e.prop,t);if(!(this.isAlready(e,s)||this.otherPrefixes(e.value,t)))return this.insert(e,t,i,n)}process(e,t){if(!this.needCascade(e)){super.process(e,t);return}let i=super.process(e,t);!i||!i.length||(this.restoreBefore(e),e.raws.before=this.calcBefore(i,e))}old(e,t){return[this.prefixed(e,t)]}};My.exports=Ly});var By=x((r$,Ny)=>{u();Ny.exports=function r(e){return{mul:t=>new r(e*t),div:t=>new r(e/t),simplify:()=>new r(e),toString:()=>e.toString()}}});var zy=x((i$,jy)=>{u();var pO=By(),dO=wr(),Zl=_e(),hO=/(min|max)-resolution\s*:\s*\d*\.?\d+(dppx|dpcm|dpi|x)/gi,mO=/(min|max)-resolution(\s*:\s*)(\d*\.?\d+)(dppx|dpcm|dpi|x)/i,Fy=class extends dO{prefixName(e,t){return e==="-moz-"?t+"--moz-device-pixel-ratio":e+t+"-device-pixel-ratio"}prefixQuery(e,t,i,n,s){return n=new pO(n),s==="dpi"?n=n.div(96):s==="dpcm"&&(n=n.mul(2.54).div(96)),n=n.simplify(),e==="-o-"&&(n=n.n+"/"+n.d),this.prefixName(e,t)+i+n}clean(e){if(!this.bad){this.bad=[];for(let t of this.prefixes)this.bad.push(this.prefixName(t,"min")),this.bad.push(this.prefixName(t,"max"))}e.params=Zl.editList(e.params,t=>t.filter(i=>this.bad.every(n=>!i.includes(n))))}process(e){let t=this.parentPrefix(e),i=t?[t]:this.prefixes;e.params=Zl.editList(e.params,(n,s)=>{for(let a of n){if(!a.includes("min-resolution")&&!a.includes("max-resolution")){s.push(a);continue}for(let o of i){let l=a.replace(hO,c=>{let f=c.match(mO);return this.prefixQuery(o,f[1],f[2],f[3],f[4])});s.push(l)}s.push(a)}return Zl.uniq(s)})}};jy.exports=Fy});var Vy=x((n$,Uy)=>{u();var Jl="(".charCodeAt(0),eu=")".charCodeAt(0),Ds="'".charCodeAt(0),tu='"'.charCodeAt(0),ru="\\".charCodeAt(0),vr="/".charCodeAt(0),iu=",".charCodeAt(0),nu=":".charCodeAt(0),qs="*".charCodeAt(0),gO="u".charCodeAt(0),yO="U".charCodeAt(0),bO="+".charCodeAt(0),wO=/^[a-f0-9?-]+$/i;Uy.exports=function(r){for(var e=[],t=r,i,n,s,a,o,l,c,f,d=0,p=t.charCodeAt(d),h=t.length,b=[{nodes:e}],v=0,y,w="",k="",S="";d{u();Hy.exports=function r(e,t,i){var n,s,a,o;for(n=0,s=e.length;n{u();function Gy(r,e){var t=r.type,i=r.value,n,s;return e&&(s=e(r))!==void 0?s:t==="word"||t==="space"?i:t==="string"?(n=r.quote||"",n+i+(r.unclosed?"":n)):t==="comment"?"/*"+i+(r.unclosed?"":"*/"):t==="div"?(r.before||"")+i+(r.after||""):Array.isArray(r.nodes)?(n=Qy(r.nodes,e),t!=="function"?n:i+"("+(r.before||"")+n+(r.after||"")+(r.unclosed?"":")")):i}function Qy(r,e){var t,i;if(Array.isArray(r)){for(t="",i=r.length-1;~i;i-=1)t=Gy(r[i],e)+t;return t}return Gy(r,e)}Yy.exports=Qy});var Zy=x((o$,Xy)=>{u();var $s="-".charCodeAt(0),Ls="+".charCodeAt(0),su=".".charCodeAt(0),vO="e".charCodeAt(0),xO="E".charCodeAt(0);function kO(r){var e=r.charCodeAt(0),t;if(e===Ls||e===$s){if(t=r.charCodeAt(1),t>=48&&t<=57)return!0;var i=r.charCodeAt(2);return t===su&&i>=48&&i<=57}return e===su?(t=r.charCodeAt(1),t>=48&&t<=57):e>=48&&e<=57}Xy.exports=function(r){var e=0,t=r.length,i,n,s;if(t===0||!kO(r))return!1;for(i=r.charCodeAt(e),(i===Ls||i===$s)&&e++;e57));)e+=1;if(i=r.charCodeAt(e),n=r.charCodeAt(e+1),i===su&&n>=48&&n<=57)for(e+=2;e57));)e+=1;if(i=r.charCodeAt(e),n=r.charCodeAt(e+1),s=r.charCodeAt(e+2),(i===vO||i===xO)&&(n>=48&&n<=57||(n===Ls||n===$s)&&s>=48&&s<=57))for(e+=n===Ls||n===$s?3:2;e57));)e+=1;return{number:r.slice(0,e),unit:r.slice(e)}}});var Ms=x((l$,tb)=>{u();var SO=Vy(),Jy=Wy(),eb=Ky();function Nt(r){return this instanceof Nt?(this.nodes=SO(r),this):new Nt(r)}Nt.prototype.toString=function(){return Array.isArray(this.nodes)?eb(this.nodes):""};Nt.prototype.walk=function(r,e){return Jy(this.nodes,r,e),this};Nt.unit=Zy();Nt.walk=Jy;Nt.stringify=eb;tb.exports=Nt});var ab=x((u$,sb)=>{u();var{list:AO}=$e(),rb=Ms(),CO=Mt(),ib=Ni(),nb=class{constructor(e){this.props=["transition","transition-property"],this.prefixes=e}add(e,t){let i,n,s=this.prefixes.add[e.prop],a=this.ruleVendorPrefixes(e),o=a||s&&s.prefixes||[],l=this.parse(e.value),c=l.map(h=>this.findProp(h)),f=[];if(c.some(h=>h[0]==="-"))return;for(let h of l){if(n=this.findProp(h),n[0]==="-")continue;let b=this.prefixes.add[n];if(!(!b||!b.prefixes))for(i of b.prefixes){if(a&&!a.some(y=>i.includes(y)))continue;let v=this.prefixes.prefixed(n,i);v!=="-ms-transform"&&!c.includes(v)&&(this.disabled(n,i)||f.push(this.clone(n,v,h)))}}l=l.concat(f);let d=this.stringify(l),p=this.stringify(this.cleanFromUnprefixed(l,"-webkit-"));if(o.includes("-webkit-")&&this.cloneBefore(e,`-webkit-${e.prop}`,p),this.cloneBefore(e,e.prop,p),o.includes("-o-")){let h=this.stringify(this.cleanFromUnprefixed(l,"-o-"));this.cloneBefore(e,`-o-${e.prop}`,h)}for(i of o)if(i!=="-webkit-"&&i!=="-o-"){let h=this.stringify(this.cleanOtherPrefixes(l,i));this.cloneBefore(e,i+e.prop,h)}d!==e.value&&!this.already(e,e.prop,d)&&(this.checkForWarning(t,e),e.cloneBefore(),e.value=d)}findProp(e){let t=e[0].value;if(/^\d/.test(t)){for(let[i,n]of e.entries())if(i!==0&&n.type==="word")return n.value}return t}already(e,t,i){return e.parent.some(n=>n.prop===t&&n.value===i)}cloneBefore(e,t,i){this.already(e,t,i)||e.cloneBefore({prop:t,value:i})}checkForWarning(e,t){if(t.prop!=="transition-property")return;let i=!1,n=!1;t.parent.each(s=>{if(s.type!=="decl"||s.prop.indexOf("transition-")!==0)return;let a=AO.comma(s.value);if(s.prop==="transition-property"){a.forEach(o=>{let l=this.prefixes.add[o];l&&l.prefixes&&l.prefixes.length>0&&(i=!0)});return}return n=n||a.length>1,!1}),i&&n&&t.warn(e,"Replace transition-property to transition, because Autoprefixer could not support any cases of transition-property and other transition-*")}remove(e){let t=this.parse(e.value);t=t.filter(a=>{let o=this.prefixes.remove[this.findProp(a)];return!o||!o.remove});let i=this.stringify(t);if(e.value===i)return;if(t.length===0){e.remove();return}let n=e.parent.some(a=>a.prop===e.prop&&a.value===i),s=e.parent.some(a=>a!==e&&a.prop===e.prop&&a.value.length>i.length);if(n||s){e.remove();return}e.value=i}parse(e){let t=rb(e),i=[],n=[];for(let s of t.nodes)n.push(s),s.type==="div"&&s.value===","&&(i.push(n),n=[]);return i.push(n),i.filter(s=>s.length>0)}stringify(e){if(e.length===0)return"";let t=[];for(let i of e)i[i.length-1].type!=="div"&&i.push(this.div(e)),t=t.concat(i);return t[0].type==="div"&&(t=t.slice(1)),t[t.length-1].type==="div"&&(t=t.slice(0,-2+1||void 0)),rb.stringify({nodes:t})}clone(e,t,i){let n=[],s=!1;for(let a of i)!s&&a.type==="word"&&a.value===e?(n.push({type:"word",value:t}),s=!0):n.push(a);return n}div(e){for(let t of e)for(let i of t)if(i.type==="div"&&i.value===",")return i;return{type:"div",value:",",after:" "}}cleanOtherPrefixes(e,t){return e.filter(i=>{let n=ib.prefix(this.findProp(i));return n===""||n===t})}cleanFromUnprefixed(e,t){let i=e.map(s=>this.findProp(s)).filter(s=>s.slice(0,t.length)===t).map(s=>this.prefixes.unprefixed(s)),n=[];for(let s of e){let a=this.findProp(s),o=ib.prefix(a);!i.includes(a)&&(o===t||o==="")&&n.push(s)}return n}disabled(e,t){let i=["order","justify-content","align-self","align-content"];if(e.includes("flex")||i.includes(e)){if(this.prefixes.options.flexbox===!1)return!0;if(this.prefixes.options.flexbox==="no-2009")return t.includes("2009")}}ruleVendorPrefixes(e){let{parent:t}=e;if(t.type!=="rule")return!1;if(!t.selector.includes(":-"))return!1;let i=CO.prefixes().filter(n=>t.selector.includes(":"+n));return i.length>0?i:!1}};sb.exports=nb});var xr=x((f$,lb)=>{u();var _O=_e(),ob=class{constructor(e,t,i,n){this.unprefixed=e,this.prefixed=t,this.string=i||t,this.regexp=n||_O.regexp(t)}check(e){return e.includes(this.string)?!!e.match(this.regexp):!1}};lb.exports=ob});var He=x((c$,fb)=>{u();var EO=wr(),OO=xr(),TO=Ni(),RO=_e(),ub=class extends EO{static save(e,t){let i=t.prop,n=[];for(let s in t._autoprefixerValues){let a=t._autoprefixerValues[s];if(a===t.value)continue;let o,l=TO.prefix(i);if(l==="-pie-")continue;if(l===s){o=t.value=a,n.push(o);continue}let c=e.prefixed(i,s),f=t.parent;if(!f.every(b=>b.prop!==c)){n.push(o);continue}let d=a.replace(/\s+/," ");if(f.some(b=>b.prop===t.prop&&b.value.replace(/\s+/," ")===d)){n.push(o);continue}let h=this.clone(t,{value:a});o=t.parent.insertBefore(t,h),n.push(o)}return n}check(e){let t=e.value;return t.includes(this.name)?!!t.match(this.regexp()):!1}regexp(){return this.regexpCache||(this.regexpCache=RO.regexp(this.name))}replace(e,t){return e.replace(this.regexp(),`$1${t}$2`)}value(e){return e.raws.value&&e.raws.value.value===e.value?e.raws.value.raw:e.value}add(e,t){e._autoprefixerValues||(e._autoprefixerValues={});let i=e._autoprefixerValues[t]||this.value(e),n;do if(n=i,i=this.replace(i,t),i===!1)return;while(i!==n);e._autoprefixerValues[t]=i}old(e){return new OO(this.name,e+this.name)}};fb.exports=ub});var Bt=x((p$,cb)=>{u();cb.exports={}});var ou=x((d$,hb)=>{u();var pb=Ms(),PO=He(),IO=Bt().insertAreas,DO=/(^|[^-])linear-gradient\(\s*(top|left|right|bottom)/i,qO=/(^|[^-])radial-gradient\(\s*\d+(\w*|%)\s+\d+(\w*|%)\s*,/i,$O=/(!\s*)?autoprefixer:\s*ignore\s+next/i,LO=/(!\s*)?autoprefixer\s*grid:\s*(on|off|(no-)?autoplace)/i,MO=["width","height","min-width","max-width","min-height","max-height","inline-size","min-inline-size","max-inline-size","block-size","min-block-size","max-block-size"];function au(r){return r.parent.some(e=>e.prop==="grid-template"||e.prop==="grid-template-areas")}function NO(r){let e=r.parent.some(i=>i.prop==="grid-template-rows"),t=r.parent.some(i=>i.prop==="grid-template-columns");return e&&t}var db=class{constructor(e){this.prefixes=e}add(e,t){let i=this.prefixes.add["@resolution"],n=this.prefixes.add["@keyframes"],s=this.prefixes.add["@viewport"],a=this.prefixes.add["@supports"];e.walkAtRules(f=>{if(f.name==="keyframes"){if(!this.disabled(f,t))return n&&n.process(f)}else if(f.name==="viewport"){if(!this.disabled(f,t))return s&&s.process(f)}else if(f.name==="supports"){if(this.prefixes.options.supports!==!1&&!this.disabled(f,t))return a.process(f)}else if(f.name==="media"&&f.params.includes("-resolution")&&!this.disabled(f,t))return i&&i.process(f)}),e.walkRules(f=>{if(!this.disabled(f,t))return this.prefixes.add.selectors.map(d=>d.process(f,t))});function o(f){return f.parent.nodes.some(d=>{if(d.type!=="decl")return!1;let p=d.prop==="display"&&/(inline-)?grid/.test(d.value),h=d.prop.startsWith("grid-template"),b=/^grid-([A-z]+-)?gap/.test(d.prop);return p||h||b})}function l(f){return f.parent.some(d=>d.prop==="display"&&/(inline-)?flex/.test(d.value))}let c=this.gridStatus(e,t)&&this.prefixes.add["grid-area"]&&this.prefixes.add["grid-area"].prefixes;return e.walkDecls(f=>{if(this.disabledDecl(f,t))return;let d=f.parent,p=f.prop,h=f.value;if(p==="grid-row-span"){t.warn("grid-row-span is not part of final Grid Layout. Use grid-row.",{node:f});return}else if(p==="grid-column-span"){t.warn("grid-column-span is not part of final Grid Layout. Use grid-column.",{node:f});return}else if(p==="display"&&h==="box"){t.warn("You should write display: flex by final spec instead of display: box",{node:f});return}else if(p==="text-emphasis-position")(h==="under"||h==="over")&&t.warn("You should use 2 values for text-emphasis-position For example, `under left` instead of just `under`.",{node:f});else if(/^(align|justify|place)-(items|content)$/.test(p)&&l(f))(h==="start"||h==="end")&&t.warn(`${h} value has mixed support, consider using flex-${h} instead`,{node:f});else if(p==="text-decoration-skip"&&h==="ink")t.warn("Replace text-decoration-skip: ink to text-decoration-skip-ink: auto, because spec had been changed",{node:f});else{if(c&&this.gridStatus(f,t))if(f.value==="subgrid"&&t.warn("IE does not support subgrid",{node:f}),/^(align|justify|place)-items$/.test(p)&&o(f)){let v=p.replace("-items","-self");t.warn(`IE does not support ${p} on grid containers. Try using ${v} on child elements instead: ${f.parent.selector} > * { ${v}: ${f.value} }`,{node:f})}else if(/^(align|justify|place)-content$/.test(p)&&o(f))t.warn(`IE does not support ${f.prop} on grid containers`,{node:f});else if(p==="display"&&f.value==="contents"){t.warn("Please do not use display: contents; if you have grid setting enabled",{node:f});return}else if(f.prop==="grid-gap"){let v=this.gridStatus(f,t);v==="autoplace"&&!NO(f)&&!au(f)?t.warn("grid-gap only works if grid-template(-areas) is being used or both rows and columns have been declared and cells have not been manually placed inside the explicit grid",{node:f}):(v===!0||v==="no-autoplace")&&!au(f)&&t.warn("grid-gap only works if grid-template(-areas) is being used",{node:f})}else if(p==="grid-auto-columns"){t.warn("grid-auto-columns is not supported by IE",{node:f});return}else if(p==="grid-auto-rows"){t.warn("grid-auto-rows is not supported by IE",{node:f});return}else if(p==="grid-auto-flow"){let v=d.some(w=>w.prop==="grid-template-rows"),y=d.some(w=>w.prop==="grid-template-columns");au(f)?t.warn("grid-auto-flow is not supported by IE",{node:f}):h.includes("dense")?t.warn("grid-auto-flow: dense is not supported by IE",{node:f}):!v&&!y&&t.warn("grid-auto-flow works only if grid-template-rows and grid-template-columns are present in the same rule",{node:f});return}else if(h.includes("auto-fit")){t.warn("auto-fit value is not supported by IE",{node:f,word:"auto-fit"});return}else if(h.includes("auto-fill")){t.warn("auto-fill value is not supported by IE",{node:f,word:"auto-fill"});return}else p.startsWith("grid-template")&&h.includes("[")&&t.warn("Autoprefixer currently does not support line names. Try using grid-template-areas instead.",{node:f,word:"["});if(h.includes("radial-gradient"))if(qO.test(f.value))t.warn("Gradient has outdated direction syntax. New syntax is like `closest-side at 0 0` instead of `0 0, closest-side`.",{node:f});else{let v=pb(h);for(let y of v.nodes)if(y.type==="function"&&y.value==="radial-gradient")for(let w of y.nodes)w.type==="word"&&(w.value==="cover"?t.warn("Gradient has outdated direction syntax. Replace `cover` to `farthest-corner`.",{node:f}):w.value==="contain"&&t.warn("Gradient has outdated direction syntax. Replace `contain` to `closest-side`.",{node:f}))}h.includes("linear-gradient")&&DO.test(h)&&t.warn("Gradient has outdated direction syntax. New syntax is like `to left` instead of `right`.",{node:f})}MO.includes(f.prop)&&(f.value.includes("-fill-available")||(f.value.includes("fill-available")?t.warn("Replace fill-available to stretch, because spec had been changed",{node:f}):f.value.includes("fill")&&pb(h).nodes.some(y=>y.type==="word"&&y.value==="fill")&&t.warn("Replace fill to stretch, because spec had been changed",{node:f})));let b;if(f.prop==="transition"||f.prop==="transition-property")return this.prefixes.transition.add(f,t);if(f.prop==="align-self"){if(this.displayType(f)!=="grid"&&this.prefixes.options.flexbox!==!1&&(b=this.prefixes.add["align-self"],b&&b.prefixes&&b.process(f)),this.gridStatus(f,t)!==!1&&(b=this.prefixes.add["grid-row-align"],b&&b.prefixes))return b.process(f,t)}else if(f.prop==="justify-self"){if(this.gridStatus(f,t)!==!1&&(b=this.prefixes.add["grid-column-align"],b&&b.prefixes))return b.process(f,t)}else if(f.prop==="place-self"){if(b=this.prefixes.add["place-self"],b&&b.prefixes&&this.gridStatus(f,t)!==!1)return b.process(f,t)}else if(b=this.prefixes.add[f.prop],b&&b.prefixes)return b.process(f,t)}),this.gridStatus(e,t)&&IO(e,this.disabled),e.walkDecls(f=>{if(this.disabledValue(f,t))return;let d=this.prefixes.unprefixed(f.prop),p=this.prefixes.values("add",d);if(Array.isArray(p))for(let h of p)h.process&&h.process(f,t);PO.save(this.prefixes,f)})}remove(e,t){let i=this.prefixes.remove["@resolution"];e.walkAtRules((n,s)=>{this.prefixes.remove[`@${n.name}`]?this.disabled(n,t)||n.parent.removeChild(s):n.name==="media"&&n.params.includes("-resolution")&&i&&i.clean(n)});for(let n of this.prefixes.remove.selectors)e.walkRules((s,a)=>{n.check(s)&&(this.disabled(s,t)||s.parent.removeChild(a))});return e.walkDecls((n,s)=>{if(this.disabled(n,t))return;let a=n.parent,o=this.prefixes.unprefixed(n.prop);if((n.prop==="transition"||n.prop==="transition-property")&&this.prefixes.transition.remove(n),this.prefixes.remove[n.prop]&&this.prefixes.remove[n.prop].remove){let l=this.prefixes.group(n).down(c=>this.prefixes.normalize(c.prop)===o);if(o==="flex-flow"&&(l=!0),n.prop==="-webkit-box-orient"){let c={"flex-direction":!0,"flex-flow":!0};if(!n.parent.some(f=>c[f.prop]))return}if(l&&!this.withHackValue(n)){n.raw("before").includes(` +`)&&this.reduceSpaces(n),a.removeChild(s);return}}for(let l of this.prefixes.values("remove",o)){if(!l.check||!l.check(n.value))continue;if(o=l.unprefixed,this.prefixes.group(n).down(f=>f.value.includes(o))){a.removeChild(s);return}}})}withHackValue(e){return e.prop==="-webkit-background-clip"&&e.value==="text"}disabledValue(e,t){return this.gridStatus(e,t)===!1&&e.type==="decl"&&e.prop==="display"&&e.value.includes("grid")||this.prefixes.options.flexbox===!1&&e.type==="decl"&&e.prop==="display"&&e.value.includes("flex")||e.type==="decl"&&e.prop==="content"?!0:this.disabled(e,t)}disabledDecl(e,t){if(this.gridStatus(e,t)===!1&&e.type==="decl"&&(e.prop.includes("grid")||e.prop==="justify-items"))return!0;if(this.prefixes.options.flexbox===!1&&e.type==="decl"){let i=["order","justify-content","align-items","align-content"];if(e.prop.includes("flex")||i.includes(e.prop))return!0}return this.disabled(e,t)}disabled(e,t){if(!e)return!1;if(e._autoprefixerDisabled!==void 0)return e._autoprefixerDisabled;if(e.parent){let n=e.prev();if(n&&n.type==="comment"&&$O.test(n.text))return e._autoprefixerDisabled=!0,e._autoprefixerSelfDisabled=!0,!0}let i=null;if(e.nodes){let n;e.each(s=>{s.type==="comment"&&/(!\s*)?autoprefixer:\s*(off|on)/i.test(s.text)&&(typeof n!="undefined"?t.warn("Second Autoprefixer control comment was ignored. Autoprefixer applies control comment to whole block, not to next rules.",{node:s}):n=/on/i.test(s.text))}),n!==void 0&&(i=!n)}if(!e.nodes||i===null)if(e.parent){let n=this.disabled(e.parent,t);e.parent._autoprefixerSelfDisabled===!0?i=!1:i=n}else i=!1;return e._autoprefixerDisabled=i,i}reduceSpaces(e){let t=!1;if(this.prefixes.group(e).up(()=>(t=!0,!0)),t)return;let i=e.raw("before").split(` +`),n=i[i.length-1].length,s=!1;this.prefixes.group(e).down(a=>{i=a.raw("before").split(` +`);let o=i.length-1;i[o].length>n&&(s===!1&&(s=i[o].length-n),i[o]=i[o].slice(0,-s),a.raws.before=i.join(` +`))})}displayType(e){for(let t of e.parent.nodes)if(t.prop==="display"){if(t.value.includes("flex"))return"flex";if(t.value.includes("grid"))return"grid"}return!1}gridStatus(e,t){if(!e)return!1;if(e._autoprefixerGridStatus!==void 0)return e._autoprefixerGridStatus;let i=null;if(e.nodes){let n;e.each(s=>{if(s.type==="comment"&&LO.test(s.text)){let a=/:\s*autoplace/i.test(s.text),o=/no-autoplace/i.test(s.text);typeof n!="undefined"?t.warn("Second Autoprefixer grid control comment was ignored. Autoprefixer applies control comments to the whole block, not to the next rules.",{node:s}):a?n="autoplace":o?n=!0:n=/on/i.test(s.text)}}),n!==void 0&&(i=n)}if(e.type==="atrule"&&e.name==="supports"){let n=e.params;n.includes("grid")&&n.includes("auto")&&(i=!1)}if(!e.nodes||i===null)if(e.parent){let n=this.gridStatus(e.parent,t);e.parent._autoprefixerSelfDisabled===!0?i=!1:i=n}else typeof this.prefixes.options.grid!="undefined"?i=this.prefixes.options.grid:typeof m.env.AUTOPREFIXER_GRID!="undefined"?m.env.AUTOPREFIXER_GRID==="autoplace"?i="autoplace":i=!0:i=!1;return e._autoprefixerGridStatus=i,i}};hb.exports=db});var gb=x((h$,mb)=>{u();mb.exports={A:{A:{"2":"K E F G A B JC"},B:{"1":"C L M H N D O P Q R S T U V W X Y Z a b c d e f g h i j n o p q r s t u v w x y z I"},C:{"1":"2 3 4 5 6 7 8 9 AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB QB RB SB TB UB VB WB XB YB ZB aB bB cB 0B dB 1B eB fB gB hB iB jB kB lB mB nB oB m pB qB rB sB tB P Q R 2B S T U V W X Y Z a b c d e f g h i j n o p q r s t u v w x y z I uB 3B 4B","2":"0 1 KC zB J K E F G A B C L M H N D O k l LC MC"},D:{"1":"8 9 AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB QB RB SB TB UB VB WB XB YB ZB aB bB cB 0B dB 1B eB fB gB hB iB jB kB lB mB nB oB m pB qB rB sB tB P Q R S T U V W X Y Z a b c d e f g h i j n o p q r s t u v w x y z I uB 3B 4B","2":"0 1 2 3 4 5 6 7 J K E F G A B C L M H N D O k l"},E:{"1":"G A B C L M H D RC 6B vB wB 7B SC TC 8B 9B xB AC yB BC CC DC EC FC GC UC","2":"0 J K E F NC 5B OC PC QC"},F:{"1":"1 2 3 4 5 6 7 8 9 H N D O k l AB BB CB DB EB FB GB HB IB JB KB LB MB NB OB PB QB RB SB TB UB VB WB XB YB ZB aB bB cB dB eB fB gB hB iB jB kB lB mB nB oB m pB qB rB sB tB P Q R 2B S T U V W X Y Z a b c d e f g h i j wB","2":"G B C VC WC XC YC vB HC ZC"},G:{"1":"D fC gC hC iC jC kC lC mC nC oC pC qC rC sC tC 8B 9B xB AC yB BC CC DC EC FC GC","2":"F 5B aC IC bC cC dC eC"},H:{"1":"uC"},I:{"1":"I zC 0C","2":"zB J vC wC xC yC IC"},J:{"2":"E A"},K:{"1":"m","2":"A B C vB HC wB"},L:{"1":"I"},M:{"1":"uB"},N:{"2":"A B"},O:{"1":"xB"},P:{"1":"J k l 1C 2C 3C 4C 5C 6B 6C 7C 8C 9C AD yB BD CD DD"},Q:{"1":"7B"},R:{"1":"ED"},S:{"1":"FD GD"}},B:4,C:"CSS Feature Queries"}});var vb=x((m$,wb)=>{u();function yb(r){return r[r.length-1]}var bb={parse(r){let e=[""],t=[e];for(let i of r){if(i==="("){e=[""],yb(t).push(e),t.push(e);continue}if(i===")"){t.pop(),e=yb(t),e.push("");continue}e[e.length-1]+=i}return t[0]},stringify(r){let e="";for(let t of r){if(typeof t=="object"){e+=`(${bb.stringify(t)})`;continue}e+=t}return e}};wb.exports=bb});var Cb=x((g$,Ab)=>{u();var BO=gb(),{feature:FO}=(Ps(),Rs),{parse:jO}=$e(),zO=Mt(),lu=vb(),UO=He(),VO=_e(),xb=FO(BO),kb=[];for(let r in xb.stats){let e=xb.stats[r];for(let t in e){let i=e[t];/y/.test(i)&&kb.push(r+" "+t)}}var Sb=class{constructor(e,t){this.Prefixes=e,this.all=t}prefixer(){if(this.prefixerCache)return this.prefixerCache;let e=this.all.browsers.selected.filter(i=>kb.includes(i)),t=new zO(this.all.browsers.data,e,this.all.options);return this.prefixerCache=new this.Prefixes(this.all.data,t,this.all.options),this.prefixerCache}parse(e){let t=e.split(":"),i=t[0],n=t[1];return n||(n=""),[i.trim(),n.trim()]}virtual(e){let[t,i]=this.parse(e),n=jO("a{}").first;return n.append({prop:t,value:i,raws:{before:""}}),n}prefixed(e){let t=this.virtual(e);if(this.disabled(t.first))return t.nodes;let i={warn:()=>null},n=this.prefixer().add[t.first.prop];n&&n.process&&n.process(t.first,i);for(let s of t.nodes){for(let a of this.prefixer().values("add",t.first.prop))a.process(s);UO.save(this.all,s)}return t.nodes}isNot(e){return typeof e=="string"&&/not\s*/i.test(e)}isOr(e){return typeof e=="string"&&/\s*or\s*/i.test(e)}isProp(e){return typeof e=="object"&&e.length===1&&typeof e[0]=="string"}isHack(e,t){return!new RegExp(`(\\(|\\s)${VO.escapeRegexp(t)}:`).test(e)}toRemove(e,t){let[i,n]=this.parse(e),s=this.all.unprefixed(i),a=this.all.cleaner();if(a.remove[i]&&a.remove[i].remove&&!this.isHack(t,s))return!0;for(let o of a.values("remove",s))if(o.check(n))return!0;return!1}remove(e,t){let i=0;for(;itypeof t!="object"?t:t.length===1&&typeof t[0]=="object"?this.cleanBrackets(t[0]):this.cleanBrackets(t))}convert(e){let t=[""];for(let i of e)t.push([`${i.prop}: ${i.value}`]),t.push(" or ");return t[t.length-1]="",t}normalize(e){if(typeof e!="object")return e;if(e=e.filter(t=>t!==""),typeof e[0]=="string"){let t=e[0].trim();if(t.includes(":")||t==="selector"||t==="not selector")return[lu.stringify(e)]}return e.map(t=>this.normalize(t))}add(e,t){return e.map(i=>{if(this.isProp(i)){let n=this.prefixed(i[0]);return n.length>1?this.convert(n):i}return typeof i=="object"?this.add(i,t):i})}process(e){let t=lu.parse(e.params);t=this.normalize(t),t=this.remove(t,e.params),t=this.add(t,e.params),t=this.cleanBrackets(t),e.params=lu.stringify(t)}disabled(e){if(!this.all.options.grid&&(e.prop==="display"&&e.value.includes("grid")||e.prop.includes("grid")||e.prop==="justify-items"))return!0;if(this.all.options.flexbox===!1){if(e.prop==="display"&&e.value.includes("flex"))return!0;let t=["order","justify-content","align-items","align-content"];if(e.prop.includes("flex")||t.includes(e.prop))return!0}return!1}};Ab.exports=Sb});var Ob=x((y$,Eb)=>{u();var _b=class{constructor(e,t){this.prefix=t,this.prefixed=e.prefixed(this.prefix),this.regexp=e.regexp(this.prefix),this.prefixeds=e.possible().map(i=>[e.prefixed(i),e.regexp(i)]),this.unprefixed=e.name,this.nameRegexp=e.regexp()}isHack(e){let t=e.parent.index(e)+1,i=e.parent.nodes;for(;t{u();var{list:HO}=$e(),WO=Ob(),GO=wr(),QO=Mt(),YO=_e(),Tb=class extends GO{constructor(e,t,i){super(e,t,i);this.regexpCache=new Map}check(e){return e.selector.includes(this.name)?!!e.selector.match(this.regexp()):!1}prefixed(e){return this.name.replace(/^(\W*)/,`$1${e}`)}regexp(e){if(!this.regexpCache.has(e)){let t=e?this.prefixed(e):this.name;this.regexpCache.set(e,new RegExp(`(^|[^:"'=])${YO.escapeRegexp(t)}`,"gi"))}return this.regexpCache.get(e)}possible(){return QO.prefixes()}prefixeds(e){if(e._autoprefixerPrefixeds){if(e._autoprefixerPrefixeds[this.name])return e._autoprefixerPrefixeds}else e._autoprefixerPrefixeds={};let t={};if(e.selector.includes(",")){let n=HO.comma(e.selector).filter(s=>s.includes(this.name));for(let s of this.possible())t[s]=n.map(a=>this.replace(a,s)).join(", ")}else for(let i of this.possible())t[i]=this.replace(e.selector,i);return e._autoprefixerPrefixeds[this.name]=t,e._autoprefixerPrefixeds}already(e,t,i){let n=e.parent.index(e)-1;for(;n>=0;){let s=e.parent.nodes[n];if(s.type!=="rule")return!1;let a=!1;for(let o in t[this.name]){let l=t[this.name][o];if(s.selector===l){if(i===o)return!0;a=!0;break}}if(!a)return!1;n-=1}return!1}replace(e,t){return e.replace(this.regexp(),`$1${this.prefixed(t)}`)}add(e,t){let i=this.prefixeds(e);if(this.already(e,i,t))return;let n=this.clone(e,{selector:i[this.name][t]});e.parent.insertBefore(e,n)}old(e){return new WO(this,e)}};Rb.exports=Tb});var Db=x((w$,Ib)=>{u();var KO=wr(),Pb=class extends KO{add(e,t){let i=t+e.name;if(e.parent.some(a=>a.name===i&&a.params===e.params))return;let s=this.clone(e,{name:i});return e.parent.insertBefore(e,s)}process(e){let t=this.parentPrefix(e);for(let i of this.prefixes)(!t||t===i)&&this.add(e,i)}};Ib.exports=Pb});var $b=x((v$,qb)=>{u();var XO=kr(),uu=class extends XO{prefixed(e){return e==="-webkit-"?":-webkit-full-screen":e==="-moz-"?":-moz-full-screen":`:${e}fullscreen`}};uu.names=[":fullscreen"];qb.exports=uu});var Mb=x((x$,Lb)=>{u();var ZO=kr(),fu=class extends ZO{possible(){return super.possible().concat(["-moz- old","-ms- old"])}prefixed(e){return e==="-webkit-"?"::-webkit-input-placeholder":e==="-ms-"?"::-ms-input-placeholder":e==="-ms- old"?":-ms-input-placeholder":e==="-moz- old"?":-moz-placeholder":`::${e}placeholder`}};fu.names=["::placeholder"];Lb.exports=fu});var Bb=x((k$,Nb)=>{u();var JO=kr(),cu=class extends JO{prefixed(e){return e==="-ms-"?":-ms-input-placeholder":`:${e}placeholder-shown`}};cu.names=[":placeholder-shown"];Nb.exports=cu});var jb=x((S$,Fb)=>{u();var eT=kr(),tT=_e(),pu=class extends eT{constructor(e,t,i){super(e,t,i);this.prefixes&&(this.prefixes=tT.uniq(this.prefixes.map(n=>"-webkit-")))}prefixed(e){return e==="-webkit-"?"::-webkit-file-upload-button":`::${e}file-selector-button`}};pu.names=["::file-selector-button"];Fb.exports=pu});var Pe=x((A$,zb)=>{u();zb.exports=function(r){let e;return r==="-webkit- 2009"||r==="-moz-"?e=2009:r==="-ms-"?e=2012:r==="-webkit-"&&(e="final"),r==="-webkit- 2009"&&(r="-webkit-"),[e,r]}});var Wb=x((C$,Hb)=>{u();var Ub=$e().list,Vb=Pe(),rT=j(),Sr=class extends rT{prefixed(e,t){let i;return[i,t]=Vb(t),i===2009?t+"box-flex":super.prefixed(e,t)}normalize(){return"flex"}set(e,t){let i=Vb(t)[0];if(i===2009)return e.value=Ub.space(e.value)[0],e.value=Sr.oldValues[e.value]||e.value,super.set(e,t);if(i===2012){let n=Ub.space(e.value);n.length===3&&n[2]==="0"&&(e.value=n.slice(0,2).concat("0px").join(" "))}return super.set(e,t)}};Sr.names=["flex","box-flex"];Sr.oldValues={auto:"1",none:"0"};Hb.exports=Sr});var Yb=x((_$,Qb)=>{u();var Gb=Pe(),iT=j(),du=class extends iT{prefixed(e,t){let i;return[i,t]=Gb(t),i===2009?t+"box-ordinal-group":i===2012?t+"flex-order":super.prefixed(e,t)}normalize(){return"order"}set(e,t){return Gb(t)[0]===2009&&/\d/.test(e.value)?(e.value=(parseInt(e.value)+1).toString(),super.set(e,t)):super.set(e,t)}};du.names=["order","flex-order","box-ordinal-group"];Qb.exports=du});var Xb=x((E$,Kb)=>{u();var nT=j(),hu=class extends nT{check(e){let t=e.value;return!t.toLowerCase().includes("alpha(")&&!t.includes("DXImageTransform.Microsoft")&&!t.includes("data:image/svg+xml")}};hu.names=["filter"];Kb.exports=hu});var Jb=x((O$,Zb)=>{u();var sT=j(),mu=class extends sT{insert(e,t,i,n){if(t!=="-ms-")return super.insert(e,t,i);let s=this.clone(e),a=e.prop.replace(/end$/,"start"),o=t+e.prop.replace(/end$/,"span");if(!e.parent.some(l=>l.prop===o)){if(s.prop=o,e.value.includes("span"))s.value=e.value.replace(/span\s/i,"");else{let l;if(e.parent.walkDecls(a,c=>{l=c}),l){let c=Number(e.value)-Number(l.value)+"";s.value=c}else e.warn(n,`Can not prefix ${e.prop} (${a} is not found)`)}e.cloneBefore(s)}}};mu.names=["grid-row-end","grid-column-end"];Zb.exports=mu});var tw=x((T$,ew)=>{u();var aT=j(),gu=class extends aT{check(e){return!e.value.split(/\s+/).some(t=>{let i=t.toLowerCase();return i==="reverse"||i==="alternate-reverse"})}};gu.names=["animation","animation-direction"];ew.exports=gu});var iw=x((R$,rw)=>{u();var oT=Pe(),lT=j(),yu=class extends lT{insert(e,t,i){let n;if([n,t]=oT(t),n!==2009)return super.insert(e,t,i);let s=e.value.split(/\s+/).filter(d=>d!=="wrap"&&d!=="nowrap"&&"wrap-reverse");if(s.length===0||e.parent.some(d=>d.prop===t+"box-orient"||d.prop===t+"box-direction"))return;let o=s[0],l=o.includes("row")?"horizontal":"vertical",c=o.includes("reverse")?"reverse":"normal",f=this.clone(e);return f.prop=t+"box-orient",f.value=l,this.needCascade(e)&&(f.raws.before=this.calcBefore(i,e,t)),e.parent.insertBefore(e,f),f=this.clone(e),f.prop=t+"box-direction",f.value=c,this.needCascade(e)&&(f.raws.before=this.calcBefore(i,e,t)),e.parent.insertBefore(e,f)}};yu.names=["flex-flow","box-direction","box-orient"];rw.exports=yu});var sw=x((P$,nw)=>{u();var uT=Pe(),fT=j(),bu=class extends fT{normalize(){return"flex"}prefixed(e,t){let i;return[i,t]=uT(t),i===2009?t+"box-flex":i===2012?t+"flex-positive":super.prefixed(e,t)}};bu.names=["flex-grow","flex-positive"];nw.exports=bu});var ow=x((I$,aw)=>{u();var cT=Pe(),pT=j(),wu=class extends pT{set(e,t){if(cT(t)[0]!==2009)return super.set(e,t)}};wu.names=["flex-wrap"];aw.exports=wu});var uw=x((D$,lw)=>{u();var dT=j(),Ar=Bt(),vu=class extends dT{insert(e,t,i,n){if(t!=="-ms-")return super.insert(e,t,i);let s=Ar.parse(e),[a,o]=Ar.translate(s,0,2),[l,c]=Ar.translate(s,1,3);[["grid-row",a],["grid-row-span",o],["grid-column",l],["grid-column-span",c]].forEach(([f,d])=>{Ar.insertDecl(e,f,d)}),Ar.warnTemplateSelectorNotFound(e,n),Ar.warnIfGridRowColumnExists(e,n)}};vu.names=["grid-area"];lw.exports=vu});var cw=x((q$,fw)=>{u();var hT=j(),Bi=Bt(),xu=class extends hT{insert(e,t,i){if(t!=="-ms-")return super.insert(e,t,i);if(e.parent.some(a=>a.prop==="-ms-grid-row-align"))return;let[[n,s]]=Bi.parse(e);s?(Bi.insertDecl(e,"grid-row-align",n),Bi.insertDecl(e,"grid-column-align",s)):(Bi.insertDecl(e,"grid-row-align",n),Bi.insertDecl(e,"grid-column-align",n))}};xu.names=["place-self"];fw.exports=xu});var dw=x(($$,pw)=>{u();var mT=j(),ku=class extends mT{check(e){let t=e.value;return!t.includes("/")||t.includes("span")}normalize(e){return e.replace("-start","")}prefixed(e,t){let i=super.prefixed(e,t);return t==="-ms-"&&(i=i.replace("-start","")),i}};ku.names=["grid-row-start","grid-column-start"];pw.exports=ku});var gw=x((L$,mw)=>{u();var hw=Pe(),gT=j(),Cr=class extends gT{check(e){return e.parent&&!e.parent.some(t=>t.prop&&t.prop.startsWith("grid-"))}prefixed(e,t){let i;return[i,t]=hw(t),i===2012?t+"flex-item-align":super.prefixed(e,t)}normalize(){return"align-self"}set(e,t){let i=hw(t)[0];if(i===2012)return e.value=Cr.oldValues[e.value]||e.value,super.set(e,t);if(i==="final")return super.set(e,t)}};Cr.names=["align-self","flex-item-align"];Cr.oldValues={"flex-end":"end","flex-start":"start"};mw.exports=Cr});var bw=x((M$,yw)=>{u();var yT=j(),bT=_e(),Su=class extends yT{constructor(e,t,i){super(e,t,i);this.prefixes&&(this.prefixes=bT.uniq(this.prefixes.map(n=>n==="-ms-"?"-webkit-":n)))}};Su.names=["appearance"];yw.exports=Su});var xw=x((N$,vw)=>{u();var ww=Pe(),wT=j(),Au=class extends wT{normalize(){return"flex-basis"}prefixed(e,t){let i;return[i,t]=ww(t),i===2012?t+"flex-preferred-size":super.prefixed(e,t)}set(e,t){let i;if([i,t]=ww(t),i===2012||i==="final")return super.set(e,t)}};Au.names=["flex-basis","flex-preferred-size"];vw.exports=Au});var Sw=x((B$,kw)=>{u();var vT=j(),Cu=class extends vT{normalize(){return this.name.replace("box-image","border")}prefixed(e,t){let i=super.prefixed(e,t);return t==="-webkit-"&&(i=i.replace("border","box-image")),i}};Cu.names=["mask-border","mask-border-source","mask-border-slice","mask-border-width","mask-border-outset","mask-border-repeat","mask-box-image","mask-box-image-source","mask-box-image-slice","mask-box-image-width","mask-box-image-outset","mask-box-image-repeat"];kw.exports=Cu});var Cw=x((F$,Aw)=>{u();var xT=j(),lt=class extends xT{insert(e,t,i){let n=e.prop==="mask-composite",s;n?s=e.value.split(","):s=e.value.match(lt.regexp)||[],s=s.map(c=>c.trim()).filter(c=>c);let a=s.length,o;if(a&&(o=this.clone(e),o.value=s.map(c=>lt.oldValues[c]||c).join(", "),s.includes("intersect")&&(o.value+=", xor"),o.prop=t+"mask-composite"),n)return a?(this.needCascade(e)&&(o.raws.before=this.calcBefore(i,e,t)),e.parent.insertBefore(e,o)):void 0;let l=this.clone(e);return l.prop=t+l.prop,a&&(l.value=l.value.replace(lt.regexp,"")),this.needCascade(e)&&(l.raws.before=this.calcBefore(i,e,t)),e.parent.insertBefore(e,l),a?(this.needCascade(e)&&(o.raws.before=this.calcBefore(i,e,t)),e.parent.insertBefore(e,o)):e}};lt.names=["mask","mask-composite"];lt.oldValues={add:"source-over",subtract:"source-out",intersect:"source-in",exclude:"xor"};lt.regexp=new RegExp(`\\s+(${Object.keys(lt.oldValues).join("|")})\\b(?!\\))\\s*(?=[,])`,"ig");Aw.exports=lt});var Ow=x((j$,Ew)=>{u();var _w=Pe(),kT=j(),_r=class extends kT{prefixed(e,t){let i;return[i,t]=_w(t),i===2009?t+"box-align":i===2012?t+"flex-align":super.prefixed(e,t)}normalize(){return"align-items"}set(e,t){let i=_w(t)[0];return(i===2009||i===2012)&&(e.value=_r.oldValues[e.value]||e.value),super.set(e,t)}};_r.names=["align-items","flex-align","box-align"];_r.oldValues={"flex-end":"end","flex-start":"start"};Ew.exports=_r});var Rw=x((z$,Tw)=>{u();var ST=j(),_u=class extends ST{set(e,t){return t==="-ms-"&&e.value==="contain"&&(e.value="element"),super.set(e,t)}insert(e,t,i){if(!(e.value==="all"&&t==="-ms-"))return super.insert(e,t,i)}};_u.names=["user-select"];Tw.exports=_u});var Dw=x((U$,Iw)=>{u();var Pw=Pe(),AT=j(),Eu=class extends AT{normalize(){return"flex-shrink"}prefixed(e,t){let i;return[i,t]=Pw(t),i===2012?t+"flex-negative":super.prefixed(e,t)}set(e,t){let i;if([i,t]=Pw(t),i===2012||i==="final")return super.set(e,t)}};Eu.names=["flex-shrink","flex-negative"];Iw.exports=Eu});var $w=x((V$,qw)=>{u();var CT=j(),Ou=class extends CT{prefixed(e,t){return`${t}column-${e}`}normalize(e){return e.includes("inside")?"break-inside":e.includes("before")?"break-before":"break-after"}set(e,t){return(e.prop==="break-inside"&&e.value==="avoid-column"||e.value==="avoid-page")&&(e.value="avoid"),super.set(e,t)}insert(e,t,i){if(e.prop!=="break-inside")return super.insert(e,t,i);if(!(/region/i.test(e.value)||/page/i.test(e.value)))return super.insert(e,t,i)}};Ou.names=["break-inside","page-break-inside","column-break-inside","break-before","page-break-before","column-break-before","break-after","page-break-after","column-break-after"];qw.exports=Ou});var Mw=x((H$,Lw)=>{u();var _T=j(),Tu=class extends _T{prefixed(e,t){return t+"print-color-adjust"}normalize(){return"color-adjust"}};Tu.names=["color-adjust","print-color-adjust"];Lw.exports=Tu});var Bw=x((W$,Nw)=>{u();var ET=j(),Er=class extends ET{insert(e,t,i){if(t==="-ms-"){let n=this.set(this.clone(e),t);this.needCascade(e)&&(n.raws.before=this.calcBefore(i,e,t));let s="ltr";return e.parent.nodes.forEach(a=>{a.prop==="direction"&&(a.value==="rtl"||a.value==="ltr")&&(s=a.value)}),n.value=Er.msValues[s][e.value]||e.value,e.parent.insertBefore(e,n)}return super.insert(e,t,i)}};Er.names=["writing-mode"];Er.msValues={ltr:{"horizontal-tb":"lr-tb","vertical-rl":"tb-rl","vertical-lr":"tb-lr"},rtl:{"horizontal-tb":"rl-tb","vertical-rl":"bt-rl","vertical-lr":"bt-lr"}};Nw.exports=Er});var jw=x((G$,Fw)=>{u();var OT=j(),Ru=class extends OT{set(e,t){return e.value=e.value.replace(/\s+fill(\s)/,"$1"),super.set(e,t)}};Ru.names=["border-image"];Fw.exports=Ru});var Vw=x((Q$,Uw)=>{u();var zw=Pe(),TT=j(),Or=class extends TT{prefixed(e,t){let i;return[i,t]=zw(t),i===2012?t+"flex-line-pack":super.prefixed(e,t)}normalize(){return"align-content"}set(e,t){let i=zw(t)[0];if(i===2012)return e.value=Or.oldValues[e.value]||e.value,super.set(e,t);if(i==="final")return super.set(e,t)}};Or.names=["align-content","flex-line-pack"];Or.oldValues={"flex-end":"end","flex-start":"start","space-between":"justify","space-around":"distribute"};Uw.exports=Or});var Ww=x((Y$,Hw)=>{u();var RT=j(),We=class extends RT{prefixed(e,t){return t==="-moz-"?t+(We.toMozilla[e]||e):super.prefixed(e,t)}normalize(e){return We.toNormal[e]||e}};We.names=["border-radius"];We.toMozilla={};We.toNormal={};for(let r of["top","bottom"])for(let e of["left","right"]){let t=`border-${r}-${e}-radius`,i=`border-radius-${r}${e}`;We.names.push(t),We.names.push(i),We.toMozilla[t]=i,We.toNormal[i]=t}Hw.exports=We});var Qw=x((K$,Gw)=>{u();var PT=j(),Pu=class extends PT{prefixed(e,t){return e.includes("-start")?t+e.replace("-block-start","-before"):t+e.replace("-block-end","-after")}normalize(e){return e.includes("-before")?e.replace("-before","-block-start"):e.replace("-after","-block-end")}};Pu.names=["border-block-start","border-block-end","margin-block-start","margin-block-end","padding-block-start","padding-block-end","border-before","border-after","margin-before","margin-after","padding-before","padding-after"];Gw.exports=Pu});var Kw=x((X$,Yw)=>{u();var IT=j(),{parseTemplate:DT,warnMissedAreas:qT,getGridGap:$T,warnGridGap:LT,inheritGridGap:MT}=Bt(),Iu=class extends IT{insert(e,t,i,n){if(t!=="-ms-")return super.insert(e,t,i);if(e.parent.some(h=>h.prop==="-ms-grid-rows"))return;let s=$T(e),a=MT(e,s),{rows:o,columns:l,areas:c}=DT({decl:e,gap:a||s}),f=Object.keys(c).length>0,d=Boolean(o),p=Boolean(l);return LT({gap:s,hasColumns:p,decl:e,result:n}),qT(c,e,n),(d&&p||f)&&e.cloneBefore({prop:"-ms-grid-rows",value:o,raws:{}}),p&&e.cloneBefore({prop:"-ms-grid-columns",value:l,raws:{}}),e}};Iu.names=["grid-template"];Yw.exports=Iu});var Zw=x((Z$,Xw)=>{u();var NT=j(),Du=class extends NT{prefixed(e,t){return t+e.replace("-inline","")}normalize(e){return e.replace(/(margin|padding|border)-(start|end)/,"$1-inline-$2")}};Du.names=["border-inline-start","border-inline-end","margin-inline-start","margin-inline-end","padding-inline-start","padding-inline-end","border-start","border-end","margin-start","margin-end","padding-start","padding-end"];Xw.exports=Du});var e0=x((J$,Jw)=>{u();var BT=j(),qu=class extends BT{check(e){return!e.value.includes("flex-")&&e.value!=="baseline"}prefixed(e,t){return t+"grid-row-align"}normalize(){return"align-self"}};qu.names=["grid-row-align"];Jw.exports=qu});var r0=x((eL,t0)=>{u();var FT=j(),Tr=class extends FT{keyframeParents(e){let{parent:t}=e;for(;t;){if(t.type==="atrule"&&t.name==="keyframes")return!0;({parent:t}=t)}return!1}contain3d(e){if(e.prop==="transform-origin")return!1;for(let t of Tr.functions3d)if(e.value.includes(`${t}(`))return!0;return!1}set(e,t){return e=super.set(e,t),t==="-ms-"&&(e.value=e.value.replace(/rotatez/gi,"rotate")),e}insert(e,t,i){if(t==="-ms-"){if(!this.contain3d(e)&&!this.keyframeParents(e))return super.insert(e,t,i)}else if(t==="-o-"){if(!this.contain3d(e))return super.insert(e,t,i)}else return super.insert(e,t,i)}};Tr.names=["transform","transform-origin"];Tr.functions3d=["matrix3d","translate3d","translateZ","scale3d","scaleZ","rotate3d","rotateX","rotateY","perspective"];t0.exports=Tr});var s0=x((tL,n0)=>{u();var i0=Pe(),jT=j(),$u=class extends jT{normalize(){return"flex-direction"}insert(e,t,i){let n;if([n,t]=i0(t),n!==2009)return super.insert(e,t,i);if(e.parent.some(f=>f.prop===t+"box-orient"||f.prop===t+"box-direction"))return;let a=e.value,o,l;a==="inherit"||a==="initial"||a==="unset"?(o=a,l=a):(o=a.includes("row")?"horizontal":"vertical",l=a.includes("reverse")?"reverse":"normal");let c=this.clone(e);return c.prop=t+"box-orient",c.value=o,this.needCascade(e)&&(c.raws.before=this.calcBefore(i,e,t)),e.parent.insertBefore(e,c),c=this.clone(e),c.prop=t+"box-direction",c.value=l,this.needCascade(e)&&(c.raws.before=this.calcBefore(i,e,t)),e.parent.insertBefore(e,c)}old(e,t){let i;return[i,t]=i0(t),i===2009?[t+"box-orient",t+"box-direction"]:super.old(e,t)}};$u.names=["flex-direction","box-direction","box-orient"];n0.exports=$u});var o0=x((rL,a0)=>{u();var zT=j(),Lu=class extends zT{check(e){return e.value==="pixelated"}prefixed(e,t){return t==="-ms-"?"-ms-interpolation-mode":super.prefixed(e,t)}set(e,t){return t!=="-ms-"?super.set(e,t):(e.prop="-ms-interpolation-mode",e.value="nearest-neighbor",e)}normalize(){return"image-rendering"}process(e,t){return super.process(e,t)}};Lu.names=["image-rendering","interpolation-mode"];a0.exports=Lu});var u0=x((iL,l0)=>{u();var UT=j(),VT=_e(),Mu=class extends UT{constructor(e,t,i){super(e,t,i);this.prefixes&&(this.prefixes=VT.uniq(this.prefixes.map(n=>n==="-ms-"?"-webkit-":n)))}};Mu.names=["backdrop-filter"];l0.exports=Mu});var c0=x((nL,f0)=>{u();var HT=j(),WT=_e(),Nu=class extends HT{constructor(e,t,i){super(e,t,i);this.prefixes&&(this.prefixes=WT.uniq(this.prefixes.map(n=>n==="-ms-"?"-webkit-":n)))}check(e){return e.value.toLowerCase()==="text"}};Nu.names=["background-clip"];f0.exports=Nu});var d0=x((sL,p0)=>{u();var GT=j(),QT=["none","underline","overline","line-through","blink","inherit","initial","unset"],Bu=class extends GT{check(e){return e.value.split(/\s+/).some(t=>!QT.includes(t))}};Bu.names=["text-decoration"];p0.exports=Bu});var g0=x((aL,m0)=>{u();var h0=Pe(),YT=j(),Rr=class extends YT{prefixed(e,t){let i;return[i,t]=h0(t),i===2009?t+"box-pack":i===2012?t+"flex-pack":super.prefixed(e,t)}normalize(){return"justify-content"}set(e,t){let i=h0(t)[0];if(i===2009||i===2012){let n=Rr.oldValues[e.value]||e.value;if(e.value=n,i!==2009||n!=="distribute")return super.set(e,t)}else if(i==="final")return super.set(e,t)}};Rr.names=["justify-content","flex-pack","box-pack"];Rr.oldValues={"flex-end":"end","flex-start":"start","space-between":"justify","space-around":"distribute"};m0.exports=Rr});var b0=x((oL,y0)=>{u();var KT=j(),Fu=class extends KT{set(e,t){let i=e.value.toLowerCase();return t==="-webkit-"&&!i.includes(" ")&&i!=="contain"&&i!=="cover"&&(e.value=e.value+" "+e.value),super.set(e,t)}};Fu.names=["background-size"];y0.exports=Fu});var v0=x((lL,w0)=>{u();var XT=j(),ju=Bt(),zu=class extends XT{insert(e,t,i){if(t!=="-ms-")return super.insert(e,t,i);let n=ju.parse(e),[s,a]=ju.translate(n,0,1);n[0]&&n[0].includes("span")&&(a=n[0].join("").replace(/\D/g,"")),[[e.prop,s],[`${e.prop}-span`,a]].forEach(([l,c])=>{ju.insertDecl(e,l,c)})}};zu.names=["grid-row","grid-column"];w0.exports=zu});var S0=x((uL,k0)=>{u();var ZT=j(),{prefixTrackProp:x0,prefixTrackValue:JT,autoplaceGridItems:eR,getGridGap:tR,inheritGridGap:rR}=Bt(),iR=ou(),Uu=class extends ZT{prefixed(e,t){return t==="-ms-"?x0({prop:e,prefix:t}):super.prefixed(e,t)}normalize(e){return e.replace(/^grid-(rows|columns)/,"grid-template-$1")}insert(e,t,i,n){if(t!=="-ms-")return super.insert(e,t,i);let{parent:s,prop:a,value:o}=e,l=a.includes("rows"),c=a.includes("columns"),f=s.some(k=>k.prop==="grid-template"||k.prop==="grid-template-areas");if(f&&l)return!1;let d=new iR({options:{}}),p=d.gridStatus(s,n),h=tR(e);h=rR(e,h)||h;let b=l?h.row:h.column;(p==="no-autoplace"||p===!0)&&!f&&(b=null);let v=JT({value:o,gap:b});e.cloneBefore({prop:x0({prop:a,prefix:t}),value:v});let y=s.nodes.find(k=>k.prop==="grid-auto-flow"),w="row";if(y&&!d.disabled(y,n)&&(w=y.value.trim()),p==="autoplace"){let k=s.nodes.find(E=>E.prop==="grid-template-rows");if(!k&&f)return;if(!k&&!f){e.warn(n,"Autoplacement does not work without grid-template-rows property");return}!s.nodes.find(E=>E.prop==="grid-template-columns")&&!f&&e.warn(n,"Autoplacement does not work without grid-template-columns property"),c&&!f&&eR(e,n,h,w)}}};Uu.names=["grid-template-rows","grid-template-columns","grid-rows","grid-columns"];k0.exports=Uu});var C0=x((fL,A0)=>{u();var nR=j(),Vu=class extends nR{check(e){return!e.value.includes("flex-")&&e.value!=="baseline"}prefixed(e,t){return t+"grid-column-align"}normalize(){return"justify-self"}};Vu.names=["grid-column-align"];A0.exports=Vu});var E0=x((cL,_0)=>{u();var sR=j(),Hu=class extends sR{prefixed(e,t){return t+"scroll-chaining"}normalize(){return"overscroll-behavior"}set(e,t){return e.value==="auto"?e.value="chained":(e.value==="none"||e.value==="contain")&&(e.value="none"),super.set(e,t)}};Hu.names=["overscroll-behavior","scroll-chaining"];_0.exports=Hu});var R0=x((pL,T0)=>{u();var aR=j(),{parseGridAreas:oR,warnMissedAreas:lR,prefixTrackProp:uR,prefixTrackValue:O0,getGridGap:fR,warnGridGap:cR,inheritGridGap:pR}=Bt();function dR(r){return r.trim().slice(1,-1).split(/["']\s*["']?/g)}var Wu=class extends aR{insert(e,t,i,n){if(t!=="-ms-")return super.insert(e,t,i);let s=!1,a=!1,o=e.parent,l=fR(e);l=pR(e,l)||l,o.walkDecls(/-ms-grid-rows/,d=>d.remove()),o.walkDecls(/grid-template-(rows|columns)/,d=>{if(d.prop==="grid-template-rows"){a=!0;let{prop:p,value:h}=d;d.cloneBefore({prop:uR({prop:p,prefix:t}),value:O0({value:h,gap:l.row})})}else s=!0});let c=dR(e.value);s&&!a&&l.row&&c.length>1&&e.cloneBefore({prop:"-ms-grid-rows",value:O0({value:`repeat(${c.length}, auto)`,gap:l.row}),raws:{}}),cR({gap:l,hasColumns:s,decl:e,result:n});let f=oR({rows:c,gap:l});return lR(f,e,n),e}};Wu.names=["grid-template-areas"];T0.exports=Wu});var I0=x((dL,P0)=>{u();var hR=j(),Gu=class extends hR{set(e,t){return t==="-webkit-"&&(e.value=e.value.replace(/\s*(right|left)\s*/i,"")),super.set(e,t)}};Gu.names=["text-emphasis-position"];P0.exports=Gu});var q0=x((hL,D0)=>{u();var mR=j(),Qu=class extends mR{set(e,t){return e.prop==="text-decoration-skip-ink"&&e.value==="auto"?(e.prop=t+"text-decoration-skip",e.value="ink",e):super.set(e,t)}};Qu.names=["text-decoration-skip-ink","text-decoration-skip"];D0.exports=Qu});var F0=x((mL,B0)=>{u();"use strict";B0.exports={wrap:$0,limit:L0,validate:M0,test:Yu,curry:gR,name:N0};function $0(r,e,t){var i=e-r;return((t-r)%i+i)%i+r}function L0(r,e,t){return Math.max(r,Math.min(e,t))}function M0(r,e,t,i,n){if(!Yu(r,e,t,i,n))throw new Error(t+" is outside of range ["+r+","+e+")");return t}function Yu(r,e,t,i,n){return!(te||n&&t===e||i&&t===r)}function N0(r,e,t,i){return(t?"(":"[")+r+","+e+(i?")":"]")}function gR(r,e,t,i){var n=N0.bind(null,r,e,t,i);return{wrap:$0.bind(null,r,e),limit:L0.bind(null,r,e),validate:function(s){return M0(r,e,s,t,i)},test:function(s){return Yu(r,e,s,t,i)},toString:n,name:n}}});var U0=x((gL,z0)=>{u();var Ku=Ms(),yR=F0(),bR=xr(),wR=He(),vR=_e(),j0=/top|left|right|bottom/gi,wt=class extends wR{replace(e,t){let i=Ku(e);for(let n of i.nodes)if(n.type==="function"&&n.value===this.name)if(n.nodes=this.newDirection(n.nodes),n.nodes=this.normalize(n.nodes),t==="-webkit- old"){if(!this.oldWebkit(n))return!1}else n.nodes=this.convertDirection(n.nodes),n.value=t+n.value;return i.toString()}replaceFirst(e,...t){return t.map(n=>n===" "?{type:"space",value:n}:{type:"word",value:n}).concat(e.slice(1))}normalizeUnit(e,t){return`${parseFloat(e)/t*360}deg`}normalize(e){if(!e[0])return e;if(/-?\d+(.\d+)?grad/.test(e[0].value))e[0].value=this.normalizeUnit(e[0].value,400);else if(/-?\d+(.\d+)?rad/.test(e[0].value))e[0].value=this.normalizeUnit(e[0].value,2*Math.PI);else if(/-?\d+(.\d+)?turn/.test(e[0].value))e[0].value=this.normalizeUnit(e[0].value,1);else if(e[0].value.includes("deg")){let t=parseFloat(e[0].value);t=yR.wrap(0,360,t),e[0].value=`${t}deg`}return e[0].value==="0deg"?e=this.replaceFirst(e,"to"," ","top"):e[0].value==="90deg"?e=this.replaceFirst(e,"to"," ","right"):e[0].value==="180deg"?e=this.replaceFirst(e,"to"," ","bottom"):e[0].value==="270deg"&&(e=this.replaceFirst(e,"to"," ","left")),e}newDirection(e){if(e[0].value==="to"||(j0.lastIndex=0,!j0.test(e[0].value)))return e;e.unshift({type:"word",value:"to"},{type:"space",value:" "});for(let t=2;t0&&(e[0].value==="to"?this.fixDirection(e):e[0].value.includes("deg")?this.fixAngle(e):this.isRadial(e)&&this.fixRadial(e)),e}fixDirection(e){e.splice(0,2);for(let t of e){if(t.type==="div")break;t.type==="word"&&(t.value=this.revertDirection(t.value))}}fixAngle(e){let t=e[0].value;t=parseFloat(t),t=Math.abs(450-t)%360,t=this.roundFloat(t,3),e[0].value=`${t}deg`}fixRadial(e){let t=[],i=[],n,s,a,o,l;for(o=0;o{u();var xR=xr(),kR=He();function V0(r){return new RegExp(`(^|[\\s,(])(${r}($|[\\s),]))`,"gi")}var Xu=class extends kR{regexp(){return this.regexpCache||(this.regexpCache=V0(this.name)),this.regexpCache}isStretch(){return this.name==="stretch"||this.name==="fill"||this.name==="fill-available"}replace(e,t){return t==="-moz-"&&this.isStretch()?e.replace(this.regexp(),"$1-moz-available$3"):t==="-webkit-"&&this.isStretch()?e.replace(this.regexp(),"$1-webkit-fill-available$3"):super.replace(e,t)}old(e){let t=e+this.name;return this.isStretch()&&(e==="-moz-"?t="-moz-available":e==="-webkit-"&&(t="-webkit-fill-available")),new xR(this.name,t,t,V0(t))}add(e,t){if(!(e.prop.includes("grid")&&t!=="-webkit-"))return super.add(e,t)}};Xu.names=["max-content","min-content","fit-content","fill","fill-available","stretch"];H0.exports=Xu});var Y0=x((bL,Q0)=>{u();var G0=xr(),SR=He(),Zu=class extends SR{replace(e,t){return t==="-webkit-"?e.replace(this.regexp(),"$1-webkit-optimize-contrast"):t==="-moz-"?e.replace(this.regexp(),"$1-moz-crisp-edges"):super.replace(e,t)}old(e){return e==="-webkit-"?new G0(this.name,"-webkit-optimize-contrast"):e==="-moz-"?new G0(this.name,"-moz-crisp-edges"):super.old(e)}};Zu.names=["pixelated"];Q0.exports=Zu});var X0=x((wL,K0)=>{u();var AR=He(),Ju=class extends AR{replace(e,t){let i=super.replace(e,t);return t==="-webkit-"&&(i=i.replace(/("[^"]+"|'[^']+')(\s+\d+\w)/gi,"url($1)$2")),i}};Ju.names=["image-set"];K0.exports=Ju});var J0=x((vL,Z0)=>{u();var CR=$e().list,_R=He(),ef=class extends _R{replace(e,t){return CR.space(e).map(i=>{if(i.slice(0,+this.name.length+1)!==this.name+"(")return i;let n=i.lastIndexOf(")"),s=i.slice(n+1),a=i.slice(this.name.length+1,n);if(t==="-webkit-"){let o=a.match(/\d*.?\d+%?/);o?(a=a.slice(o[0].length).trim(),a+=`, ${o[0]}`):a+=", 0.5"}return t+this.name+"("+a+")"+s}).join(" ")}};ef.names=["cross-fade"];Z0.exports=ef});var tv=x((xL,ev)=>{u();var ER=Pe(),OR=xr(),TR=He(),tf=class extends TR{constructor(e,t){super(e,t);e==="display-flex"&&(this.name="flex")}check(e){return e.prop==="display"&&e.value===this.name}prefixed(e){let t,i;return[t,e]=ER(e),t===2009?this.name==="flex"?i="box":i="inline-box":t===2012?this.name==="flex"?i="flexbox":i="inline-flexbox":t==="final"&&(i=this.name),e+i}replace(e,t){return this.prefixed(t)}old(e){let t=this.prefixed(e);if(!!t)return new OR(this.name,t)}};tf.names=["display-flex","inline-flex"];ev.exports=tf});var iv=x((kL,rv)=>{u();var RR=He(),rf=class extends RR{constructor(e,t){super(e,t);e==="display-grid"&&(this.name="grid")}check(e){return e.prop==="display"&&e.value===this.name}};rf.names=["display-grid","inline-grid"];rv.exports=rf});var sv=x((SL,nv)=>{u();var PR=He(),nf=class extends PR{constructor(e,t){super(e,t);e==="filter-function"&&(this.name="filter")}};nf.names=["filter","filter-function"];nv.exports=nf});var uv=x((AL,lv)=>{u();var av=Ni(),z=j(),ov=zy(),IR=ab(),DR=ou(),qR=Cb(),sf=Mt(),Pr=kr(),$R=Db(),ut=He(),Ir=_e(),LR=$b(),MR=Mb(),NR=Bb(),BR=jb(),FR=Wb(),jR=Yb(),zR=Xb(),UR=Jb(),VR=tw(),HR=iw(),WR=sw(),GR=ow(),QR=uw(),YR=cw(),KR=dw(),XR=gw(),ZR=bw(),JR=xw(),e5=Sw(),t5=Cw(),r5=Ow(),i5=Rw(),n5=Dw(),s5=$w(),a5=Mw(),o5=Bw(),l5=jw(),u5=Vw(),f5=Ww(),c5=Qw(),p5=Kw(),d5=Zw(),h5=e0(),m5=r0(),g5=s0(),y5=o0(),b5=u0(),w5=c0(),v5=d0(),x5=g0(),k5=b0(),S5=v0(),A5=S0(),C5=C0(),_5=E0(),E5=R0(),O5=I0(),T5=q0(),R5=U0(),P5=W0(),I5=Y0(),D5=X0(),q5=J0(),$5=tv(),L5=iv(),M5=sv();Pr.hack(LR);Pr.hack(MR);Pr.hack(NR);Pr.hack(BR);z.hack(FR);z.hack(jR);z.hack(zR);z.hack(UR);z.hack(VR);z.hack(HR);z.hack(WR);z.hack(GR);z.hack(QR);z.hack(YR);z.hack(KR);z.hack(XR);z.hack(ZR);z.hack(JR);z.hack(e5);z.hack(t5);z.hack(r5);z.hack(i5);z.hack(n5);z.hack(s5);z.hack(a5);z.hack(o5);z.hack(l5);z.hack(u5);z.hack(f5);z.hack(c5);z.hack(p5);z.hack(d5);z.hack(h5);z.hack(m5);z.hack(g5);z.hack(y5);z.hack(b5);z.hack(w5);z.hack(v5);z.hack(x5);z.hack(k5);z.hack(S5);z.hack(A5);z.hack(C5);z.hack(_5);z.hack(E5);z.hack(O5);z.hack(T5);ut.hack(R5);ut.hack(P5);ut.hack(I5);ut.hack(D5);ut.hack(q5);ut.hack($5);ut.hack(L5);ut.hack(M5);var af=new Map,Fi=class{constructor(e,t,i={}){this.data=e,this.browsers=t,this.options=i,[this.add,this.remove]=this.preprocess(this.select(this.data)),this.transition=new IR(this),this.processor=new DR(this)}cleaner(){if(this.cleanerCache)return this.cleanerCache;if(this.browsers.selected.length){let e=new sf(this.browsers.data,[]);this.cleanerCache=new Fi(this.data,e,this.options)}else return this;return this.cleanerCache}select(e){let t={add:{},remove:{}};for(let i in e){let n=e[i],s=n.browsers.map(l=>{let c=l.split(" ");return{browser:`${c[0]} ${c[1]}`,note:c[2]}}),a=s.filter(l=>l.note).map(l=>`${this.browsers.prefix(l.browser)} ${l.note}`);a=Ir.uniq(a),s=s.filter(l=>this.browsers.isSelected(l.browser)).map(l=>{let c=this.browsers.prefix(l.browser);return l.note?`${c} ${l.note}`:c}),s=this.sort(Ir.uniq(s)),this.options.flexbox==="no-2009"&&(s=s.filter(l=>!l.includes("2009")));let o=n.browsers.map(l=>this.browsers.prefix(l));n.mistakes&&(o=o.concat(n.mistakes)),o=o.concat(a),o=Ir.uniq(o),s.length?(t.add[i]=s,s.length!s.includes(l)))):t.remove[i]=o}return t}sort(e){return e.sort((t,i)=>{let n=Ir.removeNote(t).length,s=Ir.removeNote(i).length;return n===s?i.length-t.length:s-n})}preprocess(e){let t={selectors:[],"@supports":new qR(Fi,this)};for(let n in e.add){let s=e.add[n];if(n==="@keyframes"||n==="@viewport")t[n]=new $R(n,s,this);else if(n==="@resolution")t[n]=new ov(n,s,this);else if(this.data[n].selector)t.selectors.push(Pr.load(n,s,this));else{let a=this.data[n].props;if(a){let o=ut.load(n,s,this);for(let l of a)t[l]||(t[l]={values:[]}),t[l].values.push(o)}else{let o=t[n]&&t[n].values||[];t[n]=z.load(n,s,this),t[n].values=o}}}let i={selectors:[]};for(let n in e.remove){let s=e.remove[n];if(this.data[n].selector){let a=Pr.load(n,s);for(let o of s)i.selectors.push(a.old(o))}else if(n==="@keyframes"||n==="@viewport")for(let a of s){let o=`@${a}${n.slice(1)}`;i[o]={remove:!0}}else if(n==="@resolution")i[n]=new ov(n,s,this);else{let a=this.data[n].props;if(a){let o=ut.load(n,[],this);for(let l of s){let c=o.old(l);if(c)for(let f of a)i[f]||(i[f]={}),i[f].values||(i[f].values=[]),i[f].values.push(c)}}else for(let o of s){let l=this.decl(n).old(n,o);if(n==="align-self"){let c=t[n]&&t[n].prefixes;if(c){if(o==="-webkit- 2009"&&c.includes("-webkit-"))continue;if(o==="-webkit-"&&c.includes("-webkit- 2009"))continue}}for(let c of l)i[c]||(i[c]={}),i[c].remove=!0}}}return[t,i]}decl(e){return af.has(e)||af.set(e,z.load(e)),af.get(e)}unprefixed(e){let t=this.normalize(av.unprefixed(e));return t==="flex-direction"&&(t="flex-flow"),t}normalize(e){return this.decl(e).normalize(e)}prefixed(e,t){return e=av.unprefixed(e),this.decl(e).prefixed(e,t)}values(e,t){let i=this[e],n=i["*"]&&i["*"].values,s=i[t]&&i[t].values;return n&&s?Ir.uniq(n.concat(s)):n||s||[]}group(e){let t=e.parent,i=t.index(e),{length:n}=t.nodes,s=this.unprefixed(e.prop),a=(o,l)=>{for(i+=o;i>=0&&i{u();fv.exports={"backdrop-filter":{feature:"css-backdrop-filter",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5","safari 16.5"]},element:{props:["background","background-image","border-image","mask","list-style","list-style-image","content","mask-image"],feature:"css-element-function",browsers:["firefox 114"]},"user-select":{mistakes:["-khtml-"],feature:"user-select-none",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5","safari 16.5"]},"background-clip":{feature:"background-clip-text",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},hyphens:{feature:"css-hyphens",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5","safari 16.5"]},fill:{props:["width","min-width","max-width","height","min-height","max-height","inline-size","min-inline-size","max-inline-size","block-size","min-block-size","max-block-size","grid","grid-template","grid-template-rows","grid-template-columns","grid-auto-columns","grid-auto-rows"],feature:"intrinsic-width",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"fill-available":{props:["width","min-width","max-width","height","min-height","max-height","inline-size","min-inline-size","max-inline-size","block-size","min-block-size","max-block-size","grid","grid-template","grid-template-rows","grid-template-columns","grid-auto-columns","grid-auto-rows"],feature:"intrinsic-width",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},stretch:{props:["width","min-width","max-width","height","min-height","max-height","inline-size","min-inline-size","max-inline-size","block-size","min-block-size","max-block-size","grid","grid-template","grid-template-rows","grid-template-columns","grid-auto-columns","grid-auto-rows"],feature:"intrinsic-width",browsers:["firefox 114"]},"fit-content":{props:["width","min-width","max-width","height","min-height","max-height","inline-size","min-inline-size","max-inline-size","block-size","min-block-size","max-block-size","grid","grid-template","grid-template-rows","grid-template-columns","grid-auto-columns","grid-auto-rows"],feature:"intrinsic-width",browsers:["firefox 114"]},"text-decoration-style":{feature:"text-decoration",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5"]},"text-decoration-color":{feature:"text-decoration",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5"]},"text-decoration-line":{feature:"text-decoration",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5"]},"text-decoration":{feature:"text-decoration",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5"]},"text-decoration-skip":{feature:"text-decoration",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5"]},"text-decoration-skip-ink":{feature:"text-decoration",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5"]},"text-size-adjust":{feature:"text-size-adjust",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5"]},"mask-clip":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-composite":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-image":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-origin":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-repeat":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-border-repeat":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-border-source":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},mask:{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-position":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-size":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-border":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-border-outset":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-border-width":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"mask-border-slice":{feature:"css-masks",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},"clip-path":{feature:"css-clip-path",browsers:["samsung 21"]},"box-decoration-break":{feature:"css-boxdecorationbreak",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5","opera 99","safari 16.5","samsung 21"]},appearance:{feature:"css-appearance",browsers:["samsung 21"]},"image-set":{props:["background","background-image","border-image","cursor","mask","mask-image","list-style","list-style-image","content"],feature:"css-image-set",browsers:["and_uc 15.5","chrome 109","samsung 21"]},"cross-fade":{props:["background","background-image","border-image","mask","list-style","list-style-image","content","mask-image"],feature:"css-cross-fade",browsers:["and_chr 114","and_uc 15.5","chrome 109","chrome 113","chrome 114","edge 114","opera 99","samsung 21"]},isolate:{props:["unicode-bidi"],feature:"css-unicode-bidi",browsers:["ios_saf 16.1","ios_saf 16.3","ios_saf 16.4","ios_saf 16.5","safari 16.5"]},"color-adjust":{feature:"css-color-adjust",browsers:["chrome 109","chrome 113","chrome 114","edge 114","opera 99"]}}});var dv=x((_L,pv)=>{u();pv.exports={}});var yv=x((EL,gv)=>{u();var N5=Yl(),{agents:B5}=(Ps(),Rs),of=Oy(),F5=Mt(),j5=uv(),z5=cv(),U5=dv(),hv={browsers:B5,prefixes:z5},mv=` + Replace Autoprefixer \`browsers\` option to Browserslist config. + Use \`browserslist\` key in \`package.json\` or \`.browserslistrc\` file. + + Using \`browsers\` option can cause errors. Browserslist config can + be used for Babel, Autoprefixer, postcss-normalize and other tools. + + If you really need to use option, rename it to \`overrideBrowserslist\`. + + Learn more at: + https://github.com/browserslist/browserslist#readme + https://twitter.com/browserslist + +`;function V5(r){return Object.prototype.toString.apply(r)==="[object Object]"}var lf=new Map;function H5(r,e){e.browsers.selected.length!==0&&(e.add.selectors.length>0||Object.keys(e.add).length>2||r.warn(`Autoprefixer target browsers do not need any prefixes.You do not need Autoprefixer anymore. +Check your Browserslist config to be sure that your targets are set up correctly. + + Learn more at: + https://github.com/postcss/autoprefixer#readme + https://github.com/browserslist/browserslist#readme + +`))}gv.exports=Dr;function Dr(...r){let e;if(r.length===1&&V5(r[0])?(e=r[0],r=void 0):r.length===0||r.length===1&&!r[0]?r=void 0:r.length<=2&&(Array.isArray(r[0])||!r[0])?(e=r[1],r=r[0]):typeof r[r.length-1]=="object"&&(e=r.pop()),e||(e={}),e.browser)throw new Error("Change `browser` option to `overrideBrowserslist` in Autoprefixer");if(e.browserslist)throw new Error("Change `browserslist` option to `overrideBrowserslist` in Autoprefixer");e.overrideBrowserslist?r=e.overrideBrowserslist:e.browsers&&(typeof console!="undefined"&&console.warn&&(of.red?console.warn(of.red(mv.replace(/`[^`]+`/g,n=>of.yellow(n.slice(1,-1))))):console.warn(mv)),r=e.browsers);let t={ignoreUnknownVersions:e.ignoreUnknownVersions,stats:e.stats,env:e.env};function i(n){let s=hv,a=new F5(s.browsers,r,n,t),o=a.selected.join(", ")+JSON.stringify(e);return lf.has(o)||lf.set(o,new j5(s.prefixes,a,e)),lf.get(o)}return{postcssPlugin:"autoprefixer",prepare(n){let s=i({from:n.opts.from,env:e.env});return{OnceExit(a){H5(n,s),e.remove!==!1&&s.processor.remove(a,n),e.add!==!1&&s.processor.add(a,n)}}},info(n){return n=n||{},n.from=n.from||m.cwd(),U5(i(n))},options:e,browsers:r}}Dr.postcss=!0;Dr.data=hv;Dr.defaults=N5.defaults;Dr.info=()=>Dr().info()});var bv={};Ge(bv,{default:()=>W5});var W5,wv=P(()=>{u();W5=[]});var xv={};Ge(xv,{default:()=>G5});var vv,G5,kv=P(()=>{u();Xi();vv=pe(rn()),G5=St(vv.default.theme)});var Av={};Ge(Av,{default:()=>Q5});var Sv,Q5,Cv=P(()=>{u();Xi();Sv=pe(rn()),Q5=St(Sv.default)});u();"use strict";var Y5=vt(_y()),K5=vt($e()),X5=vt(yv()),Z5=vt((wv(),bv)),J5=vt((kv(),xv)),eP=vt((Cv(),Av)),tP=vt((Vs(),_f)),rP=vt((al(),sl)),iP=vt((sa(),sc));function vt(r){return r&&r.__esModule?r:{default:r}}console.warn("cdn.tailwindcss.com should not be used in production. To use Tailwind CSS in production, install it as a PostCSS plugin or use the Tailwind CLI: https://tailwindcss.com/docs/installation");var Ns="tailwind",uf="text/tailwindcss",_v="/template.html",Yt,Ev=!0,Ov=0,ff=new Set,cf,Tv="",Rv=(r=!1)=>({get(e,t){return(!r||t==="config")&&typeof e[t]=="object"&&e[t]!==null?new Proxy(e[t],Rv()):e[t]},set(e,t,i){return e[t]=i,(!r||t==="config")&&pf(!0),!0}});window[Ns]=new Proxy({config:{},defaultTheme:J5.default,defaultConfig:eP.default,colors:tP.default,plugin:rP.default,resolveConfig:iP.default},Rv(!0));function Pv(r){cf.observe(r,{attributes:!0,attributeFilter:["type"],characterData:!0,subtree:!0,childList:!0})}new MutationObserver(async r=>{let e=!1;if(!cf){cf=new MutationObserver(async()=>await pf(!0));for(let t of document.querySelectorAll(`style[type="${uf}"]`))Pv(t)}for(let t of r)for(let i of t.addedNodes)i.nodeType===1&&i.tagName==="STYLE"&&i.getAttribute("type")===uf&&(Pv(i),e=!0);await pf(e)}).observe(document.documentElement,{attributes:!0,attributeFilter:["class"],childList:!0,subtree:!0});async function pf(r=!1){r&&(Ov++,ff.clear());let e="";for(let i of document.querySelectorAll(`style[type="${uf}"]`))e+=i.textContent;let t=new Set;for(let i of document.querySelectorAll("[class]"))for(let n of i.classList)ff.has(n)||t.add(n);if(document.body&&(Ev||t.size>0||e!==Tv||!Yt||!Yt.isConnected)){for(let n of t)ff.add(n);Ev=!1,Tv=e,self[_v]=Array.from(t).join(" ");let{css:i}=await(0,K5.default)([(0,Y5.default)({...window[Ns].config,_hash:Ov,content:{files:[_v],extract:{html:n=>n.split(" ")}},plugins:[...Z5.default,...Array.isArray(window[Ns].config.plugins)?window[Ns].config.plugins:[]]}),(0,X5.default)({remove:!1})]).process(`@tailwind base;@tailwind components;@tailwind utilities;${e}`);(!Yt||!Yt.isConnected)&&(Yt=document.createElement("style"),document.head.append(Yt)),Yt.textContent=i}}})(); +/*! + * fill-range + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Licensed under the MIT License. + */ +/*! + * is-number + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Released under the MIT License. + */ +/*! + * to-regex-range + * + * Copyright (c) 2015-present, Jon Schlinkert. + * Released under the MIT License. + */ +/*! https://mths.be/cssesc v3.0.0 by @mathias */ diff --git a/dap-gateway/src/main/resources/static/tester.html b/dap-gateway/src/main/resources/static/tester.html new file mode 100644 index 00000000..7565dcca --- /dev/null +++ b/dap-gateway/src/main/resources/static/tester.html @@ -0,0 +1,876 @@ + + + + + + Tool Auto Tester + + + + + + +
+
+ +
+ v0.0.1 +
+
+
+ +
+
+
+

Auto-Tester Dashboard

+

Batch execute registered tools, customize payloads, and view detailed results.

+
+
+ + + + +
+
+ +
+ +
+ + +
+ + + +
+
+

+ + Real-time Execution Console +

+ +
+
+
[System] Console initialized. Ready to execute tools.
+
+
+ +
+
+
Tool List
+
Total: 0 / Completed: 0
+
+
+ + + + + + + + + + + + + +
CategoryTool NamePayloadStatusAction
Loading tools...
+
+
+
+ + + + + + + + + + diff --git a/dap-gateway/src/main/resources/static/vendor/MARKED-LICENSE.md b/dap-gateway/src/main/resources/static/vendor/MARKED-LICENSE.md new file mode 100644 index 00000000..4bd2d4a0 --- /dev/null +++ b/dap-gateway/src/main/resources/static/vendor/MARKED-LICENSE.md @@ -0,0 +1,44 @@ +# License information + +## Contribution License Agreement + +If you contribute code to this project, you are implicitly allowing your code +to be distributed under the MIT license. You are also implicitly verifying that +all code is your original work. `` + +## Marked + +Copyright (c) 2018+, MarkedJS (https://github.com/markedjs/) +Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +## Markdown + +Copyright © 2004, John Gruber +http://daringfireball.net/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* Neither the name “Markdown” nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +This software is provided by the copyright holders and contributors “as is” and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright owner or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage. diff --git a/dap-gateway/src/main/resources/static/vendor/marked.umd.js b/dap-gateway/src/main/resources/static/vendor/marked.umd.js new file mode 100644 index 00000000..4d226e5e --- /dev/null +++ b/dap-gateway/src/main/resources/static/vendor/marked.umd.js @@ -0,0 +1,74 @@ +/** + * marked v17.0.5 - a markdown parser + * Copyright (c) 2018-2026, MarkedJS. (MIT License) + * Copyright (c) 2011-2018, Christopher Jeffrey. (MIT License) + * https://github.com/markedjs/marked + */ + +/** + * DO NOT EDIT THIS FILE + * The code in this file is generated from files in ./src/ + */ +(function(g,f){if(typeof exports=="object"&&typeof module<"u"){module.exports=f()}else if("function"==typeof define && define.amd){define("marked",f)}else {g["marked"]=f()}}(typeof globalThis < "u" ? globalThis : typeof self < "u" ? self : this,function(){var exports={};var __exports=exports;var module={exports}; +"use strict";var G=Object.defineProperty;var Re=Object.getOwnPropertyDescriptor;var Te=Object.getOwnPropertyNames;var Oe=Object.prototype.hasOwnProperty;var we=(l,e)=>{for(var t in e)G(l,t,{get:e[t],enumerable:!0})},ye=(l,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of Te(e))!Oe.call(l,r)&&r!==t&&G(l,r,{get:()=>e[r],enumerable:!(n=Re(e,r))||n.enumerable});return l};var Pe=l=>ye(G({},"__esModule",{value:!0}),l);var xt={};we(xt,{Hooks:()=>P,Lexer:()=>x,Marked:()=>A,Parser:()=>b,Renderer:()=>y,TextRenderer:()=>S,Tokenizer:()=>w,defaults:()=>R,getDefaults:()=>_,lexer:()=>mt,marked:()=>g,options:()=>pt,parse:()=>gt,parseInline:()=>dt,parser:()=>ft,setOptions:()=>ct,use:()=>ht,walkTokens:()=>kt});module.exports=Pe(xt);function _(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}var R=_();function N(l){R=l}var L={exec:()=>null};function k(l,e=""){let t=typeof l=="string"?l:l.source,n={replace:(r,i)=>{let s=typeof i=="string"?i:i.source;return s=s.replace(m.caret,"$1"),t=t.replace(r,s),n},getRegex:()=>new RegExp(t,e)};return n}var Se=(()=>{try{return!!new RegExp("(?<=1)(?/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] +\S/,listReplaceTask:/^\[[ xX]\] +/,listTaskCheckbox:/\[[ xX]\]/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:l=>new RegExp(`^( {0,3}${l})((?:[ ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`),hrRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}#`),htmlBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}<(?:[a-z].*>|!--)`,"i"),blockquoteBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}>`)},$e=/^(?:[ \t]*(?:\n|$))+/,_e=/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,Le=/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,B=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,Me=/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,j=/ {0,3}(?:[*+-]|\d{1,9}[.)])/,ie=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,oe=k(ie).replace(/bull/g,j).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),ze=k(ie).replace(/bull/g,j).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),F=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,Ee=/^[^\n]+/,U=/(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/,Ie=k(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",U).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),Ae=k(/^(bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,j).getRegex(),v="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",K=/|$))/,Ce=k("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))","i").replace("comment",K).replace("tag",v).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),ae=k(F).replace("hr",B).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",v).getRegex(),Be=k(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",ae).getRegex(),W={blockquote:Be,code:_e,def:Ie,fences:Le,heading:Me,hr:B,html:Ce,lheading:oe,list:Ae,newline:$e,paragraph:ae,table:L,text:Ee},re=k("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",B).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3} )[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",v).getRegex(),De={...W,lheading:ze,table:re,paragraph:k(F).replace("hr",B).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",re).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",v).getRegex()},qe={...W,html:k(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment",K).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:L,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:k(F).replace("hr",B).replace("heading",` *#{1,6} *[^ +]`).replace("lheading",oe).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},ve=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,He=/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,le=/^( {2,}|\\)\n(?!\s*$)/,Ze=/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\`+)[^`]+\k(?!`))*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)/).replace("precode-",Se?"(?`+)[^`]+\k(?!`)/).replace("html",/<(?! )[^<>]*?>/).getRegex(),pe=/^(?:\*+(?:((?!\*)punct)|([^\s*]))?)|^_+(?:((?!_)punct)|([^\s_]))?/,Fe=k(pe,"u").replace(/punct/g,z).getRegex(),Ue=k(pe,"u").replace(/punct/g,ue).getRegex(),ce="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",Ke=k(ce,"gu").replace(/notPunctSpace/g,X).replace(/punctSpace/g,H).replace(/punct/g,z).getRegex(),We=k(ce,"gu").replace(/notPunctSpace/g,Qe).replace(/punctSpace/g,Ne).replace(/punct/g,ue).getRegex(),Xe=k("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,X).replace(/punctSpace/g,H).replace(/punct/g,z).getRegex(),Je=k(/^~~?(?:((?!~)punct)|[^\s~])/,"u").replace(/punct/g,z).getRegex(),Ve="^[^~]+(?=[^~])|(?!~)punct(~~?)(?=[\\s]|$)|notPunctSpace(~~?)(?!~)(?=punctSpace|$)|(?!~)punctSpace(~~?)(?=notPunctSpace)|[\\s](~~?)(?!~)(?=punct)|(?!~)punct(~~?)(?!~)(?=punct)|notPunctSpace(~~?)(?=notPunctSpace)",Ye=k(Ve,"gu").replace(/notPunctSpace/g,X).replace(/punctSpace/g,H).replace(/punct/g,z).getRegex(),et=k(/\\(punct)/,"gu").replace(/punct/g,z).getRegex(),tt=k(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),nt=k(K).replace("(?:-->|$)","-->").getRegex(),rt=k("^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment",nt).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),q=/(?:\[(?:\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+(?!`)[^`]*?`+(?!`)|``+(?=\])|[^\[\]\\`])*?/,st=k(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]+(?:\n[ \t]*)?|\n[ \t]*)(title))?\s*\)/).replace("label",q).replace("href",/<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),he=k(/^!?\[(label)\]\[(ref)\]/).replace("label",q).replace("ref",U).getRegex(),ke=k(/^!?\[(ref)\](?:\[\])?/).replace("ref",U).getRegex(),it=k("reflink|nolink(?!\\()","g").replace("reflink",he).replace("nolink",ke).getRegex(),se=/[hH][tT][tT][pP][sS]?|[fF][tT][pP]/,J={_backpedal:L,anyPunctuation:et,autolink:tt,blockSkip:je,br:le,code:He,del:L,delLDelim:L,delRDelim:L,emStrongLDelim:Fe,emStrongRDelimAst:Ke,emStrongRDelimUnd:Xe,escape:ve,link:st,nolink:ke,punctuation:Ge,reflink:he,reflinkSearch:it,tag:rt,text:Ze,url:L},ot={...J,link:k(/^!?\[(label)\]\((.*?)\)/).replace("label",q).getRegex(),reflink:k(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",q).getRegex()},Q={...J,emStrongRDelimAst:We,emStrongLDelim:Ue,delLDelim:Je,delRDelim:Ye,url:k(/^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("protocol",se).replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/,text:k(/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\":">",'"':""","'":"'"},de=l=>lt[l];function O(l,e){if(e){if(m.escapeTest.test(l))return l.replace(m.escapeReplace,de)}else if(m.escapeTestNoEncode.test(l))return l.replace(m.escapeReplaceNoEncode,de);return l}function V(l){try{l=encodeURI(l).replace(m.percentDecode,"%")}catch{return null}return l}function Y(l,e){let t=l.replace(m.findPipe,(i,s,a)=>{let o=!1,u=s;for(;--u>=0&&a[u]==="\\";)o=!o;return o?"|":" |"}),n=t.split(m.splitPipe),r=0;if(n[0].trim()||n.shift(),n.length>0&&!n.at(-1)?.trim()&&n.pop(),e)if(n.length>e)n.splice(e);else for(;n.length0?-2:-1}function fe(l,e=0){let t=e,n="";for(let r of l)if(r===" "){let i=4-t%4;n+=" ".repeat(i),t+=i}else n+=r,t++;return n}function me(l,e,t,n,r){let i=e.href,s=e.title||null,a=l[1].replace(r.other.outputLinkReplace,"$1");n.state.inLink=!0;let o={type:l[0].charAt(0)==="!"?"image":"link",raw:t,href:i,title:s,text:a,tokens:n.inlineTokens(a)};return n.state.inLink=!1,o}function ut(l,e,t){let n=l.match(t.other.indentCodeCompensation);if(n===null)return e;let r=n[1];return e.split(` +`).map(i=>{let s=i.match(t.other.beginningSpace);if(s===null)return i;let[a]=s;return a.length>=r.length?i.slice(r.length):i}).join(` +`)}var w=class{options;rules;lexer;constructor(e){this.options=e||R}space(e){let t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){let t=this.rules.block.code.exec(e);if(t){let n=t[0].replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?n:I(n,` +`)}}}fences(e){let t=this.rules.block.fences.exec(e);if(t){let n=t[0],r=ut(n,t[3]||"",this.rules);return{type:"code",raw:n,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:r}}}heading(e){let t=this.rules.block.heading.exec(e);if(t){let n=t[2].trim();if(this.rules.other.endingHash.test(n)){let r=I(n,"#");(this.options.pedantic||!r||this.rules.other.endingSpaceChar.test(r))&&(n=r.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:n,tokens:this.lexer.inline(n)}}}hr(e){let t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:I(t[0],` +`)}}blockquote(e){let t=this.rules.block.blockquote.exec(e);if(t){let n=I(t[0],` +`).split(` +`),r="",i="",s=[];for(;n.length>0;){let a=!1,o=[],u;for(u=0;u1,i={type:"list",raw:"",ordered:r,start:r?+n.slice(0,-1):"",loose:!1,items:[]};n=r?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=r?n:"[*+-]");let s=this.rules.other.listItemRegex(n),a=!1;for(;e;){let u=!1,p="",c="";if(!(t=s.exec(e))||this.rules.block.hr.test(e))break;p=t[0],e=e.substring(p.length);let d=fe(t[2].split(` +`,1)[0],t[1].length),h=e.split(` +`,1)[0],T=!d.trim(),f=0;if(this.options.pedantic?(f=2,c=d.trimStart()):T?f=t[1].length+1:(f=d.search(this.rules.other.nonSpaceChar),f=f>4?1:f,c=d.slice(f),f+=t[1].length),T&&this.rules.other.blankLine.test(h)&&(p+=h+` +`,e=e.substring(h.length+1),u=!0),!u){let $=this.rules.other.nextBulletRegex(f),ee=this.rules.other.hrRegex(f),te=this.rules.other.fencesBeginRegex(f),ne=this.rules.other.headingBeginRegex(f),xe=this.rules.other.htmlBeginRegex(f),be=this.rules.other.blockquoteBeginRegex(f);for(;e;){let Z=e.split(` +`,1)[0],C;if(h=Z,this.options.pedantic?(h=h.replace(this.rules.other.listReplaceNesting," "),C=h):C=h.replace(this.rules.other.tabCharGlobal," "),te.test(h)||ne.test(h)||xe.test(h)||be.test(h)||$.test(h)||ee.test(h))break;if(C.search(this.rules.other.nonSpaceChar)>=f||!h.trim())c+=` +`+C.slice(f);else{if(T||d.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4||te.test(d)||ne.test(d)||ee.test(d))break;c+=` +`+h}T=!h.trim(),p+=Z+` +`,e=e.substring(Z.length+1),d=C.slice(f)}}i.loose||(a?i.loose=!0:this.rules.other.doubleBlankLine.test(p)&&(a=!0)),i.items.push({type:"list_item",raw:p,task:!!this.options.gfm&&this.rules.other.listIsTask.test(c),loose:!1,text:c,tokens:[]}),i.raw+=p}let o=i.items.at(-1);if(o)o.raw=o.raw.trimEnd(),o.text=o.text.trimEnd();else return;i.raw=i.raw.trimEnd();for(let u of i.items){if(this.lexer.state.top=!1,u.tokens=this.lexer.blockTokens(u.text,[]),u.task){if(u.text=u.text.replace(this.rules.other.listReplaceTask,""),u.tokens[0]?.type==="text"||u.tokens[0]?.type==="paragraph"){u.tokens[0].raw=u.tokens[0].raw.replace(this.rules.other.listReplaceTask,""),u.tokens[0].text=u.tokens[0].text.replace(this.rules.other.listReplaceTask,"");for(let c=this.lexer.inlineQueue.length-1;c>=0;c--)if(this.rules.other.listIsTask.test(this.lexer.inlineQueue[c].src)){this.lexer.inlineQueue[c].src=this.lexer.inlineQueue[c].src.replace(this.rules.other.listReplaceTask,"");break}}let p=this.rules.other.listTaskCheckbox.exec(u.raw);if(p){let c={type:"checkbox",raw:p[0]+" ",checked:p[0]!=="[ ]"};u.checked=c.checked,i.loose?u.tokens[0]&&["paragraph","text"].includes(u.tokens[0].type)&&"tokens"in u.tokens[0]&&u.tokens[0].tokens?(u.tokens[0].raw=c.raw+u.tokens[0].raw,u.tokens[0].text=c.raw+u.tokens[0].text,u.tokens[0].tokens.unshift(c)):u.tokens.unshift({type:"paragraph",raw:c.raw,text:c.raw,tokens:[c]}):u.tokens.unshift(c)}}if(!i.loose){let p=u.tokens.filter(d=>d.type==="space"),c=p.length>0&&p.some(d=>this.rules.other.anyLine.test(d.raw));i.loose=c}}if(i.loose)for(let u of i.items){u.loose=!0;for(let p of u.tokens)p.type==="text"&&(p.type="paragraph")}return i}}html(e){let t=this.rules.block.html.exec(e);if(t)return{type:"html",block:!0,raw:t[0],pre:t[1]==="pre"||t[1]==="script"||t[1]==="style",text:t[0]}}def(e){let t=this.rules.block.def.exec(e);if(t){let n=t[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal," "),r=t[2]?t[2].replace(this.rules.other.hrefBrackets,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",i=t[3]?t[3].substring(1,t[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):t[3];return{type:"def",tag:n,raw:t[0],href:r,title:i}}}table(e){let t=this.rules.block.table.exec(e);if(!t||!this.rules.other.tableDelimiter.test(t[2]))return;let n=Y(t[1]),r=t[2].replace(this.rules.other.tableAlignChars,"").split("|"),i=t[3]?.trim()?t[3].replace(this.rules.other.tableRowBlankLine,"").split(` +`):[],s={type:"table",raw:t[0],header:[],align:[],rows:[]};if(n.length===r.length){for(let a of r)this.rules.other.tableAlignRight.test(a)?s.align.push("right"):this.rules.other.tableAlignCenter.test(a)?s.align.push("center"):this.rules.other.tableAlignLeft.test(a)?s.align.push("left"):s.align.push(null);for(let a=0;a({text:o,tokens:this.lexer.inline(o),header:!1,align:s.align[u]})));return s}}lheading(e){let t=this.rules.block.lheading.exec(e);if(t){let n=t[1].trim();return{type:"heading",raw:t[0],depth:t[2].charAt(0)==="="?1:2,text:n,tokens:this.lexer.inline(n)}}}paragraph(e){let t=this.rules.block.paragraph.exec(e);if(t){let n=t[1].charAt(t[1].length-1)===` +`?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:n,tokens:this.lexer.inline(n)}}}text(e){let t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){let t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:t[1]}}tag(e){let t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&this.rules.other.startATag.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){let t=this.rules.inline.link.exec(e);if(t){let n=t[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(n)){if(!this.rules.other.endAngleBracket.test(n))return;let s=I(n.slice(0,-1),"\\");if((n.length-s.length)%2===0)return}else{let s=ge(t[2],"()");if(s===-2)return;if(s>-1){let o=(t[0].indexOf("!")===0?5:4)+t[1].length+s;t[2]=t[2].substring(0,s),t[0]=t[0].substring(0,o).trim(),t[3]=""}}let r=t[2],i="";if(this.options.pedantic){let s=this.rules.other.pedanticHrefTitle.exec(r);s&&(r=s[1],i=s[3])}else i=t[3]?t[3].slice(1,-1):"";return r=r.trim(),this.rules.other.startAngleBracket.test(r)&&(this.options.pedantic&&!this.rules.other.endAngleBracket.test(n)?r=r.slice(1):r=r.slice(1,-1)),me(t,{href:r&&r.replace(this.rules.inline.anyPunctuation,"$1"),title:i&&i.replace(this.rules.inline.anyPunctuation,"$1")},t[0],this.lexer,this.rules)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){let r=(n[2]||n[1]).replace(this.rules.other.multipleSpaceGlobal," "),i=t[r.toLowerCase()];if(!i){let s=n[0].charAt(0);return{type:"text",raw:s,text:s}}return me(n,i,n[0],this.lexer,this.rules)}}emStrong(e,t,n=""){let r=this.rules.inline.emStrongLDelim.exec(e);if(!r||!r[1]&&!r[2]&&!r[3]&&!r[4]||r[4]&&n.match(this.rules.other.unicodeAlphaNumeric))return;if(!(r[1]||r[3]||"")||!n||this.rules.inline.punctuation.exec(n)){let s=[...r[0]].length-1,a,o,u=s,p=0,c=r[0][0]==="*"?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(c.lastIndex=0,t=t.slice(-1*e.length+s);(r=c.exec(t))!=null;){if(a=r[1]||r[2]||r[3]||r[4]||r[5]||r[6],!a)continue;if(o=[...a].length,r[3]||r[4]){u+=o;continue}else if((r[5]||r[6])&&s%3&&!((s+o)%3)){p+=o;continue}if(u-=o,u>0)continue;o=Math.min(o,o+u+p);let d=[...r[0]][0].length,h=e.slice(0,s+r.index+d+o);if(Math.min(s,o)%2){let f=h.slice(1,-1);return{type:"em",raw:h,text:f,tokens:this.lexer.inlineTokens(f)}}let T=h.slice(2,-2);return{type:"strong",raw:h,text:T,tokens:this.lexer.inlineTokens(T)}}}}codespan(e){let t=this.rules.inline.code.exec(e);if(t){let n=t[2].replace(this.rules.other.newLineCharGlobal," "),r=this.rules.other.nonSpaceChar.test(n),i=this.rules.other.startingSpaceChar.test(n)&&this.rules.other.endingSpaceChar.test(n);return r&&i&&(n=n.substring(1,n.length-1)),{type:"codespan",raw:t[0],text:n}}}br(e){let t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e,t,n=""){let r=this.rules.inline.delLDelim.exec(e);if(!r)return;if(!(r[1]||"")||!n||this.rules.inline.punctuation.exec(n)){let s=[...r[0]].length-1,a,o,u=s,p=this.rules.inline.delRDelim;for(p.lastIndex=0,t=t.slice(-1*e.length+s);(r=p.exec(t))!=null;){if(a=r[1]||r[2]||r[3]||r[4]||r[5]||r[6],!a||(o=[...a].length,o!==s))continue;if(r[3]||r[4]){u+=o;continue}if(u-=o,u>0)continue;o=Math.min(o,o+u);let c=[...r[0]][0].length,d=e.slice(0,s+r.index+c+o),h=d.slice(s,-s);return{type:"del",raw:d,text:h,tokens:this.lexer.inlineTokens(h)}}}}autolink(e){let t=this.rules.inline.autolink.exec(e);if(t){let n,r;return t[2]==="@"?(n=t[1],r="mailto:"+n):(n=t[1],r=n),{type:"link",raw:t[0],text:n,href:r,tokens:[{type:"text",raw:n,text:n}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let n,r;if(t[2]==="@")n=t[0],r="mailto:"+n;else{let i;do i=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??"";while(i!==t[0]);n=t[0],t[1]==="www."?r="http://"+t[0]:r=t[0]}return{type:"link",raw:t[0],text:n,href:r,tokens:[{type:"text",raw:n,text:n}]}}}inlineText(e){let t=this.rules.inline.text.exec(e);if(t){let n=this.lexer.state.inRawBlock;return{type:"text",raw:t[0],text:t[0],escaped:n}}}};var x=class l{tokens;options;state;inlineQueue;tokenizer;constructor(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||R,this.options.tokenizer=this.options.tokenizer||new w,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};let t={other:m,block:D.normal,inline:E.normal};this.options.pedantic?(t.block=D.pedantic,t.inline=E.pedantic):this.options.gfm&&(t.block=D.gfm,this.options.breaks?t.inline=E.breaks:t.inline=E.gfm),this.tokenizer.rules=t}static get rules(){return{block:D,inline:E}}static lex(e,t){return new l(t).lex(e)}static lexInline(e,t){return new l(t).inlineTokens(e)}lex(e){e=e.replace(m.carriageReturn,` +`),this.blockTokens(e,this.tokens);for(let t=0;t(r=s.call({lexer:this},e,t))?(e=e.substring(r.raw.length),t.push(r),!0):!1))continue;if(r=this.tokenizer.space(e)){e=e.substring(r.raw.length);let s=t.at(-1);r.raw.length===1&&s!==void 0?s.raw+=` +`:t.push(r);continue}if(r=this.tokenizer.code(e)){e=e.substring(r.raw.length);let s=t.at(-1);s?.type==="paragraph"||s?.type==="text"?(s.raw+=(s.raw.endsWith(` +`)?"":` +`)+r.raw,s.text+=` +`+r.text,this.inlineQueue.at(-1).src=s.text):t.push(r);continue}if(r=this.tokenizer.fences(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.heading(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.hr(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.blockquote(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.list(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.html(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.def(e)){e=e.substring(r.raw.length);let s=t.at(-1);s?.type==="paragraph"||s?.type==="text"?(s.raw+=(s.raw.endsWith(` +`)?"":` +`)+r.raw,s.text+=` +`+r.raw,this.inlineQueue.at(-1).src=s.text):this.tokens.links[r.tag]||(this.tokens.links[r.tag]={href:r.href,title:r.title},t.push(r));continue}if(r=this.tokenizer.table(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.lheading(e)){e=e.substring(r.raw.length),t.push(r);continue}let i=e;if(this.options.extensions?.startBlock){let s=1/0,a=e.slice(1),o;this.options.extensions.startBlock.forEach(u=>{o=u.call({lexer:this},a),typeof o=="number"&&o>=0&&(s=Math.min(s,o))}),s<1/0&&s>=0&&(i=e.substring(0,s+1))}if(this.state.top&&(r=this.tokenizer.paragraph(i))){let s=t.at(-1);n&&s?.type==="paragraph"?(s.raw+=(s.raw.endsWith(` +`)?"":` +`)+r.raw,s.text+=` +`+r.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=s.text):t.push(r),n=i.length!==e.length,e=e.substring(r.raw.length);continue}if(r=this.tokenizer.text(e)){e=e.substring(r.raw.length);let s=t.at(-1);s?.type==="text"?(s.raw+=(s.raw.endsWith(` +`)?"":` +`)+r.raw,s.text+=` +`+r.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=s.text):t.push(r);continue}if(e){let s="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(s);break}else throw new Error(s)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){this.tokenizer.lexer=this;let n=e,r=null;if(this.tokens.links){let o=Object.keys(this.tokens.links);if(o.length>0)for(;(r=this.tokenizer.rules.inline.reflinkSearch.exec(n))!=null;)o.includes(r[0].slice(r[0].lastIndexOf("[")+1,-1))&&(n=n.slice(0,r.index)+"["+"a".repeat(r[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;(r=this.tokenizer.rules.inline.anyPunctuation.exec(n))!=null;)n=n.slice(0,r.index)+"++"+n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);let i;for(;(r=this.tokenizer.rules.inline.blockSkip.exec(n))!=null;)i=r[2]?r[2].length:0,n=n.slice(0,r.index+i)+"["+"a".repeat(r[0].length-i-2)+"]"+n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);n=this.options.hooks?.emStrongMask?.call({lexer:this},n)??n;let s=!1,a="";for(;e;){s||(a=""),s=!1;let o;if(this.options.extensions?.inline?.some(p=>(o=p.call({lexer:this},e,t))?(e=e.substring(o.raw.length),t.push(o),!0):!1))continue;if(o=this.tokenizer.escape(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.tag(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.link(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(o.raw.length);let p=t.at(-1);o.type==="text"&&p?.type==="text"?(p.raw+=o.raw,p.text+=o.text):t.push(o);continue}if(o=this.tokenizer.emStrong(e,n,a)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.codespan(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.br(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.del(e,n,a)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.autolink(e)){e=e.substring(o.raw.length),t.push(o);continue}if(!this.state.inLink&&(o=this.tokenizer.url(e))){e=e.substring(o.raw.length),t.push(o);continue}let u=e;if(this.options.extensions?.startInline){let p=1/0,c=e.slice(1),d;this.options.extensions.startInline.forEach(h=>{d=h.call({lexer:this},c),typeof d=="number"&&d>=0&&(p=Math.min(p,d))}),p<1/0&&p>=0&&(u=e.substring(0,p+1))}if(o=this.tokenizer.inlineText(u)){e=e.substring(o.raw.length),o.raw.slice(-1)!=="_"&&(a=o.raw.slice(-1)),s=!0;let p=t.at(-1);p?.type==="text"?(p.raw+=o.raw,p.text+=o.text):t.push(o);continue}if(e){let p="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(p);break}else throw new Error(p)}}return t}};var y=class{options;parser;constructor(e){this.options=e||R}space(e){return""}code({text:e,lang:t,escaped:n}){let r=(t||"").match(m.notSpaceStart)?.[0],i=e.replace(m.endingNewline,"")+` +`;return r?'
'+(n?i:O(i,!0))+`
+`:"
"+(n?i:O(i,!0))+`
+`}blockquote({tokens:e}){return`
+${this.parser.parse(e)}
+`}html({text:e}){return e}def(e){return""}heading({tokens:e,depth:t}){return`${this.parser.parseInline(e)} +`}hr(e){return`
+`}list(e){let t=e.ordered,n=e.start,r="";for(let a=0;a +`+r+" +`}listitem(e){return`
  • ${this.parser.parse(e.tokens)}
  • +`}checkbox({checked:e}){return" '}paragraph({tokens:e}){return`

    ${this.parser.parseInline(e)}

    +`}table(e){let t="",n="";for(let i=0;i${r}`),` + +`+t+` +`+r+`
    +`}tablerow({text:e}){return` +${e} +`}tablecell(e){let t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+` +`}strong({tokens:e}){return`${this.parser.parseInline(e)}`}em({tokens:e}){return`${this.parser.parseInline(e)}`}codespan({text:e}){return`${O(e,!0)}`}br(e){return"
    "}del({tokens:e}){return`${this.parser.parseInline(e)}`}link({href:e,title:t,tokens:n}){let r=this.parser.parseInline(n),i=V(e);if(i===null)return r;e=i;let s='
    ",s}image({href:e,title:t,text:n,tokens:r}){r&&(n=this.parser.parseInline(r,this.parser.textRenderer));let i=V(e);if(i===null)return O(n);e=i;let s=`${O(n)}{let a=i[s].flat(1/0);n=n.concat(this.walkTokens(a,t))}):i.tokens&&(n=n.concat(this.walkTokens(i.tokens,t)))}}return n}use(...e){let t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach(n=>{let r={...n};if(r.async=this.defaults.async||r.async||!1,n.extensions&&(n.extensions.forEach(i=>{if(!i.name)throw new Error("extension name required");if("renderer"in i){let s=t.renderers[i.name];s?t.renderers[i.name]=function(...a){let o=i.renderer.apply(this,a);return o===!1&&(o=s.apply(this,a)),o}:t.renderers[i.name]=i.renderer}if("tokenizer"in i){if(!i.level||i.level!=="block"&&i.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");let s=t[i.level];s?s.unshift(i.tokenizer):t[i.level]=[i.tokenizer],i.start&&(i.level==="block"?t.startBlock?t.startBlock.push(i.start):t.startBlock=[i.start]:i.level==="inline"&&(t.startInline?t.startInline.push(i.start):t.startInline=[i.start]))}"childTokens"in i&&i.childTokens&&(t.childTokens[i.name]=i.childTokens)}),r.extensions=t),n.renderer){let i=this.defaults.renderer||new y(this.defaults);for(let s in n.renderer){if(!(s in i))throw new Error(`renderer '${s}' does not exist`);if(["options","parser"].includes(s))continue;let a=s,o=n.renderer[a],u=i[a];i[a]=(...p)=>{let c=o.apply(i,p);return c===!1&&(c=u.apply(i,p)),c||""}}r.renderer=i}if(n.tokenizer){let i=this.defaults.tokenizer||new w(this.defaults);for(let s in n.tokenizer){if(!(s in i))throw new Error(`tokenizer '${s}' does not exist`);if(["options","rules","lexer"].includes(s))continue;let a=s,o=n.tokenizer[a],u=i[a];i[a]=(...p)=>{let c=o.apply(i,p);return c===!1&&(c=u.apply(i,p)),c}}r.tokenizer=i}if(n.hooks){let i=this.defaults.hooks||new P;for(let s in n.hooks){if(!(s in i))throw new Error(`hook '${s}' does not exist`);if(["options","block"].includes(s))continue;let a=s,o=n.hooks[a],u=i[a];P.passThroughHooks.has(s)?i[a]=p=>{if(this.defaults.async&&P.passThroughHooksRespectAsync.has(s))return(async()=>{let d=await o.call(i,p);return u.call(i,d)})();let c=o.call(i,p);return u.call(i,c)}:i[a]=(...p)=>{if(this.defaults.async)return(async()=>{let d=await o.apply(i,p);return d===!1&&(d=await u.apply(i,p)),d})();let c=o.apply(i,p);return c===!1&&(c=u.apply(i,p)),c}}r.hooks=i}if(n.walkTokens){let i=this.defaults.walkTokens,s=n.walkTokens;r.walkTokens=function(a){let o=[];return o.push(s.call(this,a)),i&&(o=o.concat(i.call(this,a))),o}}this.defaults={...this.defaults,...r}}),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return x.lex(e,t??this.defaults)}parser(e,t){return b.parse(e,t??this.defaults)}parseMarkdown(e){return(n,r)=>{let i={...r},s={...this.defaults,...i},a=this.onError(!!s.silent,!!s.async);if(this.defaults.async===!0&&i.async===!1)return a(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(typeof n>"u"||n===null)return a(new Error("marked(): input parameter is undefined or null"));if(typeof n!="string")return a(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(n)+", string expected"));if(s.hooks&&(s.hooks.options=s,s.hooks.block=e),s.async)return(async()=>{let o=s.hooks?await s.hooks.preprocess(n):n,p=await(s.hooks?await s.hooks.provideLexer():e?x.lex:x.lexInline)(o,s),c=s.hooks?await s.hooks.processAllTokens(p):p;s.walkTokens&&await Promise.all(this.walkTokens(c,s.walkTokens));let h=await(s.hooks?await s.hooks.provideParser():e?b.parse:b.parseInline)(c,s);return s.hooks?await s.hooks.postprocess(h):h})().catch(a);try{s.hooks&&(n=s.hooks.preprocess(n));let u=(s.hooks?s.hooks.provideLexer():e?x.lex:x.lexInline)(n,s);s.hooks&&(u=s.hooks.processAllTokens(u)),s.walkTokens&&this.walkTokens(u,s.walkTokens);let c=(s.hooks?s.hooks.provideParser():e?b.parse:b.parseInline)(u,s);return s.hooks&&(c=s.hooks.postprocess(c)),c}catch(o){return a(o)}}}onError(e,t){return n=>{if(n.message+=` +Please report this to https://github.com/markedjs/marked.`,e){let r="

    An error occurred:

    "+O(n.message+"",!0)+"
    ";return t?Promise.resolve(r):r}if(t)return Promise.reject(n);throw n}}};var M=new A;function g(l,e){return M.parse(l,e)}g.options=g.setOptions=function(l){return M.setOptions(l),g.defaults=M.defaults,N(g.defaults),g};g.getDefaults=_;g.defaults=R;g.use=function(...l){return M.use(...l),g.defaults=M.defaults,N(g.defaults),g};g.walkTokens=function(l,e){return M.walkTokens(l,e)};g.parseInline=M.parseInline;g.Parser=b;g.parser=b.parse;g.Renderer=y;g.TextRenderer=S;g.Lexer=x;g.lexer=x.lex;g.Tokenizer=w;g.Hooks=P;g.parse=g;var pt=g.options,ct=g.setOptions,ht=g.use,kt=g.walkTokens,dt=g.parseInline,gt=g,ft=b.parse,mt=x.lex; + +if(__exports != exports)module.exports = exports;return module.exports})); +//# sourceMappingURL=marked.umd.js.map diff --git a/dap-gateway/src/test/java/io/shinhanlife/dap/mcg/document/DocumentGeneratorControllerTest.java b/dap-gateway/src/test/java/io/shinhanlife/dap/mcg/document/DocumentGeneratorControllerTest.java new file mode 100644 index 00000000..4bea5cbe --- /dev/null +++ b/dap-gateway/src/test/java/io/shinhanlife/dap/mcg/document/DocumentGeneratorControllerTest.java @@ -0,0 +1,57 @@ +package io.shinhanlife.dap.mcg.document; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpHeaders; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +@ExtendWith(MockitoExtension.class) +class DocumentGeneratorControllerTest { + + @Mock + private DocumentGeneratorService service; + + private MockMvc mockMvc; + private ObjectMapper objectMapper; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + mockMvc = MockMvcBuilders.standaloneSetup(new DocumentGeneratorController(service)).build(); + } + + @Test + void downloadsGeneratedProgramWorkbook() throws Exception { + byte[] workbook = "xlsx-content".getBytes(StandardCharsets.UTF_8); + when(service.generateProgram(any())).thenReturn(new GeneratedDocument( + "고객정보 조회 Tool_프로그램정의서_v1.0_20260811.xlsx", workbook)); + DocumentGenerationRequest request = new DocumentGenerationRequest( + ToolMetadata.builder().uid("tool-uid").name("oth.cmm.customer.detail").build(), + "1.0", true, true, true); + + mockMvc.perform(post("/mcp/api/v1/admin/documents/program") + .contentType("application/json") + .content(objectMapper.writeValueAsBytes(request))) + .andExpect(status().isOk()) + .andExpect(content().contentType( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")) + .andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-store")) + .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, + org.hamcrest.Matchers.containsString("attachment"))) + .andExpect(content().bytes(workbook)); + } +} diff --git a/dap-gateway/src/test/java/io/shinhanlife/dap/mcg/document/DocumentGeneratorServiceTest.java b/dap-gateway/src/test/java/io/shinhanlife/dap/mcg/document/DocumentGeneratorServiceTest.java new file mode 100644 index 00000000..e6c60467 --- /dev/null +++ b/dap-gateway/src/test/java/io/shinhanlife/dap/mcg/document/DocumentGeneratorServiceTest.java @@ -0,0 +1,130 @@ +package io.shinhanlife.dap.mcg.document; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.dto.OperationType; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import java.io.ByteArrayInputStream; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class DocumentGeneratorServiceTest { + + private DocumentGeneratorService service; + private ToolMetadata tool; + + @BeforeEach + void setUp() { + Clock clock = Clock.fixed(Instant.parse("2026-08-11T03:00:00Z"), ZoneId.of("Asia/Seoul")); + service = new DocumentGeneratorService(new ObjectMapper(), clock); + + Map properties = new LinkedHashMap<>(); + properties.put("customerId", Map.of( + "type", "string", + "description", "고객 ID", + "pattern", "\\d{8}", + "examples", List.of("12345678"))); + properties.put("pageSize", Map.of( + "type", "integer", + "description", "페이지 크기", + "default", 20)); + + tool = ToolMetadata.builder() + .uid("ebf042d9-2203-3992-9237-634a58515223") + .semver("1.0.0") + .displayName("테스트 고객조회 Tool") + .name("oth.cmm.customer.detail") + .description("테스트 고객의 상세정보를 조회합니다.") + .categoryKey("cmm") + .endpoint("http://was-oth:8084/mcp/oth.cmm.customer.detail") + .podUrl("http://was-oth:8084") + .integrationType("REST") + .mciServiceId("CUST_001") + .operationType(OperationType.READ) + .timeoutMillis(5000L) + .visible(true) + .parametersSchema(Map.of( + "type", "object", + "properties", properties, + "required", List.of("customerId"))) + .build(); + } + + @Test + void createsSelectedProgramSheetsFromTemplate() throws Exception { + GeneratedDocument document = service.generateProgram( + new DocumentGenerationRequest(tool, "1.0", true, true, true)); + + assertThat(document.fileName()) + .isEqualTo("테스트 고객조회 Tool_프로그램정의서_v1.0_20260811.xlsx"); + try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(document.content()))) { + assertThat(workbook.getNumberOfSheets()).isEqualTo(3); + assertThat(workbook.getSheetName(0)).isEqualTo("프로그램정의서"); + assertThat(workbook.getSheetName(1)).isEqualTo("처리설계"); + assertThat(workbook.getSheetName(2)).isEqualTo("개정이력"); + assertThat(workbook.getSheet("프로그램정의서").getRow(4).getCell(1).getStringCellValue()) + .isEqualTo("테스트 고객조회 Tool"); + assertThat(workbook.getSheet("처리설계").getRow(0).getCell(0).getStringCellValue()) + .isEqualTo("처리설계"); + assertThat(workbook.getSheet("개정이력").getRow(3).getCell(0).getStringCellValue()) + .isEqualTo("1.0"); + assertThat(workbook.getNumCellStyles()).isGreaterThan(8); + } + } + + @Test + void removesUncheckedProgramSheets() throws Exception { + GeneratedDocument document = service.generateProgram( + new DocumentGenerationRequest(tool, "v2.0", false, false, true)); + + try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(document.content()))) { + assertThat(workbook.getNumberOfSheets()).isEqualTo(1); + assertThat(workbook.getSheetName(0)).isEqualTo("개정이력"); + assertThat(workbook.getSheetAt(0).getRow(3).getCell(0).getStringCellValue()).isEqualTo("2.0"); + } + } + + @Test + void createsRequestAndResponseInterfaceSheetsFromTemplate() throws Exception { + GeneratedDocument document = service.generateInterface( + new DocumentGenerationRequest(tool, "1.0", false, false, false)); + + assertThat(document.fileName()) + .isEqualTo("테스트 고객조회 Tool_인터페이스정의서_v1.0_20260811.xlsx"); + try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(document.content()))) { + assertThat(workbook.getNumberOfSheets()).isEqualTo(2); + assertThat(workbook.getSheetName(0)).isEqualTo("Request In"); + assertThat(workbook.getSheetName(1)).isEqualTo("Response Out"); + assertThat(workbook.getSheet("Request In").getRow(1).getCell(2).getStringCellValue()) + .isEqualTo("테스트 고객조회 Tool"); + assertThat(workbook.getSheet("Request In").getRow(3).getCell(3).getStringCellValue()) + .isEqualTo("http://was-oth:8084/mcp/oth.cmm.customer.detail"); + assertThat(workbook.getSheet("Request In").getRow(8).getCell(5).getStringCellValue()) + .isEqualTo("customerId"); + assertThat(workbook.getSheet("Request In").getRow(8).getCell(7).getStringCellValue()) + .isEqualTo("8"); + assertThat(workbook.getSheet("Request In").getRow(32).getCell(2).getStringCellValue()) + .contains("customerId", "12345678"); + assertThat(workbook.getSheet("Response Out").getRow(8).getCell(5).getStringCellValue()) + .isEqualTo("resultData"); + } + } + + @Test + void rejectsProgramRequestWithoutSelectedSheet() { + assertThatThrownBy(() -> service.generateProgram( + new DocumentGenerationRequest(tool, "1.0", false, false, false))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("한 개 이상의 시트"); + } +} diff --git a/dap-gateway/src/test/java/io/shinhanlife/dap/mcg/presentation/McpRouterControllerTest.java b/dap-gateway/src/test/java/io/shinhanlife/dap/mcg/presentation/McpRouterControllerTest.java new file mode 100644 index 00000000..d5a1a84f --- /dev/null +++ b/dap-gateway/src/test/java/io/shinhanlife/dap/mcg/presentation/McpRouterControllerTest.java @@ -0,0 +1,38 @@ +package io.shinhanlife.dap.mcg.presentation; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.mcp.security.SecurityProperties; +import io.shinhanlife.dap.lib.adapter.dto.JsonRpcResponse; +import io.shinhanlife.dap.mcg.config.GatewayFallbackProperties; +import io.shinhanlife.dap.mcg.registry.RedisRegistryService; +import io.shinhanlife.dap.mcg.service.ExecuteService; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.springframework.data.redis.RedisConnectionFailureException; +import org.springframework.http.ResponseEntity; + +class McpRouterControllerTest { + + @Test + void returnsEmptyToolListWhenRedisIsUnavailableAndNoFallbackIsConfigured() { + RedisRegistryService registryService = org.mockito.Mockito.mock(RedisRegistryService.class); + when(registryService.getAllTools()).thenThrow(new RedisConnectionFailureException("Redis unavailable")); + + McpRouterController controller = new McpRouterController( + registryService, + org.mockito.Mockito.mock(ExecuteService.class), + org.mockito.Mockito.mock(SecurityProperties.class), + new ObjectMapper(), + new GatewayFallbackProperties()); + + ResponseEntity response = assertDoesNotThrow(() -> controller.listTools(null)); + Map result = (Map) response.getBody().getResult(); + + assertEquals(List.of(), result.get("tools")); + } +} \ No newline at end of file diff --git a/dap-gateway/src/test/java/io/shinhanlife/dap/mcg/presentation/ScaffoldingControllerToolDraftTest.java b/dap-gateway/src/test/java/io/shinhanlife/dap/mcg/presentation/ScaffoldingControllerToolDraftTest.java new file mode 100644 index 00000000..375387ae --- /dev/null +++ b/dap-gateway/src/test/java/io/shinhanlife/dap/mcg/presentation/ScaffoldingControllerToolDraftTest.java @@ -0,0 +1,48 @@ +package io.shinhanlife.dap.mcg.presentation; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class ScaffoldingControllerToolDraftTest { + + @Test + void toolDraftEndpointIsAvailable() throws Exception { + ChatClient.Builder builder = mock(ChatClient.Builder.class); + ChatClient chatClient = mock(ChatClient.class); + ChatClient.ChatClientRequestSpec requestSpec = mock(ChatClient.ChatClientRequestSpec.class); + ChatClient.CallResponseSpec responseSpec = mock(ChatClient.CallResponseSpec.class); + when(builder.build()).thenReturn(chatClient); + when(chatClient.prompt()).thenReturn(requestSpec); + when(requestSpec.user(anyString())).thenReturn(requestSpec); + when(requestSpec.options(any(ChatOptions.class))).thenReturn(requestSpec); + when(requestSpec.call()).thenReturn(responseSpec); + when(responseSpec.content()).thenReturn(""" + {"baseName":"CustomerContractStatus","title":"계약 상태 조회","description":"고객번호로 계약 상태를 조회합니다.","categoryKey":"cmm","routingType":"HTTP","httpApiName":"contract-status","inputFields":[{"name":"customerId","type":"String","description":"고객번호","example":"C123","required":true}],"outputFields":[{"name":"resultCode","type":"String","description":"결과 코드","example":"SUCCESS","required":true}]} + """); + MockMvc mockMvc = MockMvcBuilders.standaloneSetup( + new ScaffoldingController(builder, new ObjectMapper())) + .setMessageConverters(new MappingJackson2HttpMessageConverter()) + .build(); + + mockMvc.perform(post("/api/v1/scaffold/tool-draft") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"description\":\"고객번호로 계약 상태를 조회\"}")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.baseName").value("CustomerContractStatus")) + .andExpect(jsonPath("$.inputFields[0].name").value("customerId")); + } +} diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/dto/ToolMetadata.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/dto/ToolMetadata.java index 1d0f0ac1..da6fdce5 100644 --- a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/dto/ToolMetadata.java +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/dto/ToolMetadata.java @@ -71,4 +71,6 @@ public class ToolMetadata { private Boolean openWorldHint = false; + private String operationType; + private Long timeoutMillis; } diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/usecase/ToolRegistryHeartbeatSender.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/usecase/ToolRegistryHeartbeatSender.java index b8d95a90..2710b681 100644 --- a/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/usecase/ToolRegistryHeartbeatSender.java +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/mcc/usecase/ToolRegistryHeartbeatSender.java @@ -94,7 +94,7 @@ public class ToolRegistryHeartbeatSender { ? mcpProperties.getNamespace() + "_" + rawSubToolName : rawSubToolName; - boolean isRegister = functionAnnotation.register(); + boolean isRegister = true; // Force register=true if (!isRegister) { log.info(" [HeartbeatSender] '{}' 툴은 어노테이션 설정에 의해 외부 등록(Redis) 대상에서 제외되었습니다. (최종 이름: {})", baseName, subToolName); } diff --git a/dap-was-lib/build.gradle b/dap-was-lib/build.gradle new file mode 100644 index 00000000..9a2774c0 --- /dev/null +++ b/dap-was-lib/build.gradle @@ -0,0 +1,55 @@ +plugins { + // Gateway와 모든 Tool Pod가 의존하는 공통 라이브러리 모듈입니다. + id 'java-library' +} + +dependencies { + // 공통 REST Controller, HTTP Client, 예외 처리 기반입니다. + api 'org.springframework.boot:spring-boot-starter-web' + + // Tool Request DTO의 Bean Validation을 지원합니다. + api 'org.springframework.boot:spring-boot-starter-validation' + + // Tool Manifest/Registry 캐시 및 Redis 기반 공통 기능을 제공합니다. + api 'org.springframework.boot:spring-boot-starter-data-redis' + + // Tool SLA, 로깅, 공통 Aspect를 적용합니다. + api 'org.springframework.boot:spring-boot-starter-aop' + + // MCI/DB 연동에 필요한 JDBC 공통 기능입니다. + api 'org.springframework.boot:spring-boot-starter-jdbc' + + // Tool 호출의 Circuit Breaker, Rate Limit, Retry 등 복원력 기능입니다. + api 'io.github.resilience4j:resilience4j-spring-boot3:2.2.0' + api 'io.github.resilience4j:resilience4j-core:2.2.0' + api 'io.github.resilience4j:resilience4j-circuitbreaker:2.2.0' + api 'io.github.resilience4j:resilience4j-ratelimiter' + api 'io.github.resilience4j:resilience4j-retry:2.2.0' + + // MCI/업무 데이터 접근을 위한 MyBatis 지원입니다. + api 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3' + + // 현재 로컬 테스트 DB 및 SQL 로그 처리에 사용합니다. + api 'com.h2database:h2' + api 'p6spy:p6spy:3.9.1' + + // Spring Data Redis가 사용하는 Redis Client를 공통 모듈에서 직접 참조합니다. + api 'io.lettuce:lettuce-core:6.6.0.RELEASE' + + // MCI XML 전문, JSON Tool Schema/Manifest 처리에 사용합니다. + api 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.17.1' + api 'com.fasterxml.jackson.core:jackson-databind:2.17.1' + api 'com.networknt:json-schema-validator:3.0.0' + + // Tool Pod의 /mcp Streamable HTTP Server와 MCP SDK를 제공합니다. + api 'org.springframework.ai:spring-ai-starter-mcp-server-webmvc' + + // Spring AI 1.1.x에서 @McpTool, @McpToolParam, metaProvider를 제공합니다. + api 'org.springaicommunity:mcp-annotations:0.9.0' + + // 이벤트 기반 확장이 필요한 Tool의 공통 Kafka 연동 기능입니다. + api 'org.springframework.kafka:spring-kafka:3.2.0' + + // Tool Pod REST API 문서 및 Swagger UI를 제공합니다. + api 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/ErrorDetail.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/ErrorDetail.java new file mode 100644 index 00000000..e85d3169 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/ErrorDetail.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.lib.adapter.dto; + +import lombok.Builder; +import lombok.NoArgsConstructor; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +/** + * @package io.shinhanlife.dap.lib.adapter.dto + * @className ErrorDetail + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Getter +@Setter +@AllArgsConstructor +@Builder +@NoArgsConstructor +public class ErrorDetail { + private int code; + private String message; +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/JsonRpcRequest.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/JsonRpcRequest.java new file mode 100644 index 00000000..3f4f105e --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/JsonRpcRequest.java @@ -0,0 +1,27 @@ +package io.shinhanlife.dap.lib.adapter.dto; + +import lombok.Getter; +import lombok.Setter; + +/** + * @package io.shinhanlife.dap.lib.adapter.dto + * @className JsonRpcRequest + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Getter +@Setter +public class JsonRpcRequest { + private String jsonrpc; + private String method; + private Params params; + private String id; +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/JsonRpcResponse.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/JsonRpcResponse.java new file mode 100644 index 00000000..cc79e10d --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/JsonRpcResponse.java @@ -0,0 +1,30 @@ +package io.shinhanlife.dap.lib.adapter.dto; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.Getter; +import lombok.Setter; + +// 2. 응답 DTO +/** + * @package io.shinhanlife.dap.lib.adapter.dto + * @className JsonRpcResponse + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Getter +@Setter +@JsonPropertyOrder({"jsonrpc", "result", "error", "id"}) +public class JsonRpcResponse { + public String jsonrpc = "2.0"; + public Object result; + public Object error; + public String id; +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/Params.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/Params.java new file mode 100644 index 00000000..e4931eb4 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/dto/Params.java @@ -0,0 +1,38 @@ +package io.shinhanlife.dap.lib.adapter.dto; + +import lombok.Builder; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import java.util.List; +import java.util.Map; + +import lombok.Getter; +import lombok.Setter; + +/** + * @package io.shinhanlife.dap.lib.adapter.dto + * @className Params + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Params { + private String routingType; + private String name; + private String interfaceId; + private Map data; + private List> spec; +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/test/MockEimsHttpServer.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/test/MockEimsHttpServer.java new file mode 100644 index 00000000..a5634cb8 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/test/MockEimsHttpServer.java @@ -0,0 +1,61 @@ +package io.shinhanlife.dap.lib.adapter.test; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Local HTTP mock server for scaffolded HTTP Tools. + * + *

    Each Tool Pod returns the JSON generated under + * {@code src/main/resources/mock-responses/{toolName}.json}. It is enabled only + * when {@code axhub.mock.http.enabled=true}, which the HTTP Scaffold adds to local configuration.

    + */ +@Slf4j +@RestController +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = "axhub.mock.http", name = "enabled", havingValue = "true") +@RequestMapping("/api") +public class MockEimsHttpServer { + + private final ObjectMapper objectMapper; + + @PostMapping("/mock/http/{toolName:[a-z0-9_-]+}") + public ResponseEntity mockToolHttpResponse( + @PathVariable String toolName, + @RequestBody(required = false) JsonNode request) { + ClassPathResource resource = new ClassPathResource("mock-responses/" + toolName + ".json"); + if (!resource.exists()) { + return ResponseEntity.notFound().build(); + } + try { + log.info("[MockEimsHttpServer] HTTP mock request. toolName={}, body={}", toolName, request); + return ResponseEntity.ok(objectMapper.readTree(resource.getInputStream())); + } catch (Exception e) { + log.warn("[MockEimsHttpServer] Unable to read mock response. toolName={}", toolName, e); + return ResponseEntity.internalServerError().build(); + } + } + + @PostMapping("/gateway") + public ResponseEntity mockEimsReceiver( + @RequestHeader(value = "X-Trace-Id", required = false) String traceId, + @RequestBody Map request) { + String interfaceId = String.valueOf(request.getOrDefault("interfaceId", "")); + log.info("[MockEimsHttpServer] Legacy gateway mock request. traceId={}, interfaceId={}", traceId, interfaceId); + return ResponseEntity.ok(Map.of( + "status", "404", + "message", "MOCK data is not defined for interfaceId: " + interfaceId)); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/util/PiiMaskingLogbackConverter.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/util/PiiMaskingLogbackConverter.java new file mode 100644 index 00000000..50bb7d7e --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/util/PiiMaskingLogbackConverter.java @@ -0,0 +1,35 @@ +package io.shinhanlife.dap.lib.adapter.util; + +import ch.qos.logback.classic.pattern.MessageConverter; +import ch.qos.logback.classic.spi.ILoggingEvent; + +/** + * Logback 커스텀 컨버터 + * 모든 로그 메시지(%msg)가 파일이나 콘솔에 찍히기 직전에 이 클래스를 거쳐가게 됩니다. + * 여기서 PiiMaskingUtils.mask()를 호출하여 PII(주민번호, 계좌번호 등)를 안전하게 별표(*) 처리합니다. + */ +/** + * @package io.shinhanlife.dap.lib.adapter.util + * @className PiiMaskingLogbackConverter + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +public class PiiMaskingLogbackConverter extends MessageConverter { + + @Override + public String convert(ILoggingEvent event) { + // 원본 로그 메시지를 가져옵니다. + String originalMessage = super.convert(event); + + // 정규식을 이용하여 개인정보가 포함되어 있으면 마스킹 처리하여 반환합니다. + return PiiMaskingUtils.mask(originalMessage); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/util/PiiMaskingUtils.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/util/PiiMaskingUtils.java new file mode 100644 index 00000000..d98c5704 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/adapter/util/PiiMaskingUtils.java @@ -0,0 +1,77 @@ +package io.shinhanlife.dap.lib.adapter.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @package io.shinhanlife.dap.lib.adapter.util + * @className PiiMaskingUtils + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +public class PiiMaskingUtils { + + // 1. 주민등록번호 패턴 (ex: 900101-1234567 또는 9001011234567) + private static final Pattern RRN_PATTERN = Pattern.compile("(\\d{6})[-]?([1-4]\\d{6})"); + + // 2. 휴대전화번호 패턴 (ex: 010-1234-5678) + private static final Pattern PHONE_PATTERN = Pattern.compile("(01[016789])[-]?(\\d{3,4})[-]?(\\d{4})"); + + // 3. 신한라이프 계좌/증권번호 패턴 (단순 예시용 계좌번호 11~14자리) + private static final Pattern ACCOUNT_PATTERN = Pattern.compile("(\\d{3})-?(\\d{3})-?(\\d{5,8})"); + + public static String mask(String input) { + if (input == null || input.isEmpty()) { + return input; + } + + String masked = input; + + // [1] 주민번호 뒷자리 마스킹 (첫자리 성별 식별자는 남기고 마스킹: 900101-1******) + Matcher rrnMatcher = RRN_PATTERN.matcher(masked); + StringBuffer rrnBuffer = new StringBuffer(); + while (rrnMatcher.find()) { + String firstPart = rrnMatcher.group(1); + String secondPart = rrnMatcher.group(2); + rrnMatcher.appendReplacement(rrnBuffer, firstPart + "-" + secondPart.charAt(0) + "******"); + } + rrnMatcher.appendTail(rrnBuffer); + masked = rrnBuffer.toString(); + + // [2] 전화번호 중간자리 마스킹 (010-****-5678) + Matcher phoneMatcher = PHONE_PATTERN.matcher(masked); + StringBuffer phoneBuffer = new StringBuffer(); + while (phoneMatcher.find()) { + String p1 = phoneMatcher.group(1); + String p2 = phoneMatcher.group(2); + String p3 = phoneMatcher.group(3); + String maskedP2 = p2.replaceAll(".", "*"); + phoneMatcher.appendReplacement(phoneBuffer, p1 + "-" + maskedP2 + "-" + p3); + } + phoneMatcher.appendTail(phoneBuffer); + masked = phoneBuffer.toString(); + + // [3] 계좌번호 뒷자리 마스킹 (110-123-********) + Matcher accMatcher = ACCOUNT_PATTERN.matcher(masked); + StringBuffer accBuffer = new StringBuffer(); + while (accMatcher.find()) { + String a1 = accMatcher.group(1); + String a2 = accMatcher.group(2); + String a3 = accMatcher.group(3); + String maskedA3 = a3.replaceAll(".", "*"); + accMatcher.appendReplacement(accBuffer, a1 + "-" + a2 + "-" + maskedA3); + } + accMatcher.appendTail(accBuffer); + masked = accBuffer.toString(); + + return masked; + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpOutputSchema.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpOutputSchema.java new file mode 100644 index 00000000..6d8f8370 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/McpOutputSchema.java @@ -0,0 +1,14 @@ +package io.shinhanlife.dap.lib.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a response DTO whose generated JSON Schema must be exposed and validated for a Tool response. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface McpOutputSchema { +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/ToolHint.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/ToolHint.java new file mode 100644 index 00000000..a0586fb1 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/annotation/ToolHint.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.lib.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Spring AI @Tool 어노테이션을 보완하여 MCP 시스템 메타데이터를 추가 제공하는 힌트 어노테이션 + * @package io.shinhanlife.dap.lib.annotation + * @className ToolHint + * @description 비즈니스 로직(Tool)과 시스템 제어 메타데이터 분리 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ToolHint { + boolean register() default false; + boolean requiresApproval() default false; + String categoryKey() default "com"; + String mappingId() default ""; + String inputSchemaResource() default ""; + String outputSchemaResource() default ""; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/aop/ToolSlaMonitoringAspect.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/aop/ToolSlaMonitoringAspect.java new file mode 100644 index 00000000..40be12b5 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/aop/ToolSlaMonitoringAspect.java @@ -0,0 +1,83 @@ +package io.shinhanlife.dap.lib.aop; + + +/** + * @package io.shinhanlife.dap.lib.aop + * @className ToolSlaMonitoringAspect + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +import io.shinhanlife.dap.lib.config.McpProperties; +import java.lang.reflect.Method; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springaicommunity.mcp.annotation.McpTool; +import org.springframework.stereotype.Component; +import org.springframework.util.StopWatch; + +@Slf4j +@Aspect +@Component +@RequiredArgsConstructor +public class ToolSlaMonitoringAspect { + + private final McpProperties mcpProperties; + + // @McpTool 어노테이션이 붙은 모든 비즈니스 툴 메서드 실행을 가로챕니다. + @Around("@annotation(org.springaicommunity.mcp.annotation.McpTool)") + public Object monitorToolSla(ProceedingJoinPoint joinPoint) throws Throwable { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + McpTool functionAnnotation = method.getAnnotation(McpTool.class); + + // 네임스페이스 자동 주입 로직을 반영하여 최종 툴 이름을 산출합니다. + String baseName = functionAnnotation.name(); + String finalName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty() + ? mcpProperties.getNamespace() + "_" + baseName + : baseName; + + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + + try { + // 실제 비즈니스 로직(툴) 실행 + Object result = joinPoint.proceed(); + + stopWatch.stop(); + long timeMillis = stopWatch.getTotalTimeMillis(); + + // SLA 기준을 초과하면 (예: 2초 이상) 경고 로깅 처리 가능 + if (timeMillis > 2000) { + log.warn(" [SLA 경고] Tool: {} | 소요시간: {}ms | 상태: SLOW_RESPONSE", finalName, timeMillis); + } else { + log.info(" [SLA 추적] Tool: {} | 소요시간: {}ms | 상태: SUCCESS", finalName, timeMillis); + } + + return result; + + } catch (Throwable e) { + if (stopWatch.isRunning()) { + stopWatch.stop(); + } + long timeMillis = stopWatch.getTotalTimeMillis(); + + // 에러 발생 시 명확하게 실패 로그 기록 + log.error(" [SLA 장애] Tool: {} | 소요시간: {}ms | 상태: FAILED | 사유: {}", finalName, timeMillis, e.getMessage()); + + // 원래 흐름대로 예외를 던져서 게이트웨이나 상위 로직이 에러를 처리하게 함 + throw e; + } + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/AxhubHttpConfiguration.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/AxhubHttpConfiguration.java new file mode 100644 index 00000000..2566ed5f --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/AxhubHttpConfiguration.java @@ -0,0 +1,29 @@ +package io.shinhanlife.dap.lib.config; + +import io.shinhanlife.glow.communication.module.http.component.GlowHttpComponent; +import java.net.http.HttpClient; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.JdkClientHttpRequestFactory; +import org.springframework.web.client.RestClient; + +/** + * Registers the temporary Glow HTTP compatibility component from the DAP library scan scope. + * The bean is only created when an official GlowHttpComponent has not already been supplied. + */ +@Configuration(proxyBeanMethods = false) +public class AxhubHttpConfiguration { + + @Bean + @ConditionalOnMissingBean(GlowHttpComponent.class) + public GlowHttpComponent glowHttpComponent(RestClient.Builder restClientBuilder) { + // WireMock and legacy internal endpoints can only support HTTP/1.1. + // Avoid JDK HTTP/2 negotiation that may cause RST_STREAM responses. + HttpClient http11Client = HttpClient.newBuilder() + .version(HttpClient.Version.HTTP_1_1) + .build(); + JdkClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(http11Client); + return new GlowHttpComponent(restClientBuilder.requestFactory(requestFactory)); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/CorsConfig.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/CorsConfig.java new file mode 100644 index 00000000..8fadfa7c --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/CorsConfig.java @@ -0,0 +1,34 @@ +package io.shinhanlife.dap.lib.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * @package io.shinhanlife.dap.lib.config + * @className CorsConfig + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Configuration +public class CorsConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") // 모든 엔드포인트에 대해 CORS 허용 + .allowedOriginPatterns("*") // 외부 Agent Builder 등 모든 오리진 허용 + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH") // 허용할 HTTP 메서드 + .allowedHeaders("*") // 모든 헤더 허용 + .exposedHeaders("Mcp-Session-Id") // MCP-HTTP 세션 아이디 노출 허용 + .allowCredentials(true) // 쿠키/인증 정보 허용 + .maxAge(3600); // preflight 요청 캐시 시간 (초 단위) + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/GlowCommunicationProperties.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/GlowCommunicationProperties.java new file mode 100644 index 00000000..eacf69e3 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/GlowCommunicationProperties.java @@ -0,0 +1,85 @@ +package io.shinhanlife.dap.lib.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * [Glow Framework 통신 환경 설정 클래스] + * application-glow-local.yml 의 'glow.communication' 하위 설정값들을 + * 자바 객체(Bean)로 매핑하여 제공합니다. + */ +/** + * @package io.shinhanlife.dap.lib.config + * @className GlowCommunicationProperties + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Component +@Getter +@Setter +@ConfigurationProperties(prefix = "glow.communication") +public class GlowCommunicationProperties { + + private Common common = new Common(); + private Http http = new Http(); + private Mci mci = new Mci(); + private ExtMci extmci = new ExtMci(); + private Eai eai = new Eai(); + private Websocket websocket = new Websocket(); + + @Getter @Setter + public static class Common { + private String envType; // 대내표준 헤더의 환경 타입정보 (D, T, P) + } + + @Getter @Setter + public static class Http { + private int connectionTimeout; // 연결 타임아웃 시간 (초 단위) + private int readTimeout; // 읽기 타임아웃 시간 (초 단위) + } + + @Getter @Setter + public static class Mci { + private String host; + private int port; + private String uri; + private String receiveUri; + private int connectionTimeout; + private int readTimeout; + private String encoding; + } + + @Getter @Setter + public static class ExtMci { + private String host; + private int port; + private String uri; + private String jsonUri; + private String receiveUri; + private int connectionTimeout; + private int readTimeout; + private String encoding; + } + + @Getter @Setter + public static class Eai { + private String host; + private int port; + } + + @Getter @Setter + public static class Websocket { + private String endpoint; + private String allowedOrigins; + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/McpProperties.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/McpProperties.java new file mode 100644 index 00000000..1ff72e5c --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/McpProperties.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.lib.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.Map; + +/** + * @package io.shinhanlife.dap.lib.config + * @className McpProperties + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Data +@Configuration +@ConfigurationProperties(prefix = "mcp") +public class McpProperties { + + private String namespace; + private Manifest manifest = new Manifest(); + + @Data + public static class Manifest { + private String bundleId; + private String namePrefix; + } + +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/MybatisConfig.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/MybatisConfig.java new file mode 100644 index 00000000..494f8a33 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/MybatisConfig.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.lib.config; + +import javax.sql.DataSource; + +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @package io.shinhanlife.dap.lib.config + * @className MybatisConfig + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +import org.mybatis.spring.annotation.MapperScan; +import io.shinhanlife.glow.GlowMybatisMapper; + +@Configuration +@MapperScan(basePackages = "io.shinhanlife.dap", annotationClass = GlowMybatisMapper.class) +public class MybatisConfig { + + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { + SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); + sessionFactory.setDataSource(dataSource); + return sessionFactory.getObject(); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/P6SpySqlFormatter.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/P6SpySqlFormatter.java new file mode 100644 index 00000000..06cc4c69 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/P6SpySqlFormatter.java @@ -0,0 +1,54 @@ +package io.shinhanlife.dap.lib.config; + +import com.p6spy.engine.logging.Category; +import com.p6spy.engine.spy.appender.MessageFormattingStrategy; + +/** + * @package io.shinhanlife.dap.lib.config + * @className P6SpySqlFormatter + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +public class P6SpySqlFormatter implements MessageFormattingStrategy { + + @Override + public String formatMessage(int connectionId, String now, long elapsed, + String category, String prepared, String sql, String url) { + + if (sql == null || sql.isBlank()) return ""; + if (Category.STATEMENT.getName().equals(category)) { + + String prettySQL = sql + .replaceAll("(?i)\\bSELECT\\b", "\nSELECT") + .replaceAll("(?i)\\bFROM\\b", "\n FROM") + .replaceAll("(?i)\\bWHERE\\b", "\n WHERE") + .replaceAll("(?i)\\bAND\\b", "\n AND") + .replaceAll("(?i)\\bOR\\b", "\n OR") + .replaceAll("(?i)\\bINNER JOIN\\b", "\n INNER JOIN") + .replaceAll("(?i)\\bLEFT JOIN\\b", "\n LEFT JOIN") + .replaceAll("(?i)\\bORDER BY\\b", "\n ORDER BY") + .replaceAll("(?i)\\bGROUP BY\\b", "\n GROUP BY") + .replaceAll("(?i)\\bINSERT INTO\\b", "\nINSERT INTO") + .replaceAll("(?i)\\bVALUES\\b", "\n VALUES") + .replaceAll("(?i)\\bUPDATE\\b", "\nUPDATE") + .replaceAll("(?i)\\bSET\\b", "\n SET") + .replaceAll("(?i)\\bDELETE FROM\\b", "\nDELETE FROM"); + + return String.format(""" + \n┌───────────────────────────────────────── + │ SQL [%dms] + │%s + └───────────────────────────────────────── + """, elapsed, prettySQL.indent(2).stripTrailing()); + } + return ""; + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/ToolSchemaConfiguration.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/ToolSchemaConfiguration.java new file mode 100644 index 00000000..d0f8e2ac --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/config/ToolSchemaConfiguration.java @@ -0,0 +1,24 @@ +package io.shinhanlife.dap.lib.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.util.ToolSchemaResolver; +import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Common MCP Tool Schema Bean configuration. + */ +@Configuration +public class ToolSchemaConfiguration { + + @Bean + public ToolSchemaResolver toolSchemaResolver(ObjectMapper objectMapper) { + return new ToolSchemaResolver(objectMapper); + } + + @Bean + public ToolArgumentSchemaValidator toolArgumentSchemaValidator(ObjectMapper objectMapper) { + return new ToolArgumentSchemaValidator(objectMapper); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/dto/OperationType.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/dto/OperationType.java new file mode 100644 index 00000000..6d868400 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/dto/OperationType.java @@ -0,0 +1,20 @@ +package io.shinhanlife.dap.lib.dto; + +/** + * @package io.shinhanlife.dap.mcg.dto + * @className OperationType + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +public enum OperationType { + READ, + WRITE +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/GlowIntegrationCall.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/GlowIntegrationCall.java new file mode 100644 index 00000000..ce38c785 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/GlowIntegrationCall.java @@ -0,0 +1,105 @@ +package io.shinhanlife.dap.lib.integration; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +// TODO: 실제 Glow Framework 의존성이 추가되면 아래 주석들을 풀고 사용하세요! +// import io.shinhanlife.glow.communication.dto.CommonHeader; +// import io.shinhanlife.glow.communication.dto.Transfer; +// import io.shinhanlife.glow.communication.module.eai.component.GlowEaiComponent; +// import io.shinhanlife.glow.communication.module.mci.component.GlowExtMciComponent; +// import io.shinhanlife.glow.communication.module.mci.component.GlowMciComponent; +// import io.shinhanlife.glow.communication.util.CommonHeaderFactory; + +/** + * [MCI / EAI 공통 연동 래퍼(Wrapper) 템플릿] + * 신한라이프 Glow Framework 개발표준정의서를 바탕으로 대내/대외망/EAI 통신을 + * MCP 툴에서 손쉽게 호출할 수 있도록 일원화한 컴포넌트입니다. + */ +/** + * @package io.shinhanlife.dap.lib.integration + * @className GlowIntegrationCall + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Component +@RequiredArgsConstructor +public class GlowIntegrationCall { + + // 1. 실제 의존성이 주입될 프레임워크 컴포넌트들 (임시 주석 처리) + /* + private final GlowMciComponent mci; + private final GlowEaiComponent eai; + private final GlowExtMciComponent extMci; + */ + + /** + * 1. 대외 MCI 호출 (타행, 금융결제원 등 외부 기관) + * 가이드 2.2.2에 명시된 필수 파라미터(기관코드, 종별코드, 업무코드, 거래코드)를 모두 포함합니다. + */ + /* + public Transfer callExtMci(String itrfId, String frbuCd, String cmouDutjCd, String cmouCssfCd, String cmouTraCd, S body, Class resBody) { + + // 1. 공통 헤더 생성 + CommonHeader header = CommonHeaderFactory.createRequestHeader(itrfId); + + // 2. 대외 전용 필수 코드 세팅 로직 (프레임워크 내부 스펙에 맞게 가공) + // (예: 헤더에 해당 속성들을 주입하거나 Transfer 객체에 싣는 과정 추가) + + // 3. Transfer 객체 빌드 + Transfer req = Transfer.builder() + .header(header) + .body(body) + .build(); + + // 4. 대외 MCI 컴포넌트를 통해 최종 전송 + return extMci.call(req, resBody); + } + */ + + /** + * 2. EAI 호출 (대내망 중계기) + * 가이드 2.3에 명시된 대로 수신서비스 ID 없이 인터페이스 ID(itrfId)만 필수로 받습니다. + */ + /* + public Transfer callEai(String itrfId, S body, Class resBody) { + + // 1. EAI는 인터페이스 ID만으로 심플하게 헤더 생성 + CommonHeader header = CommonHeaderFactory.createRequestHeader(itrfId); + + // 2. Transfer 객체 빌드 + Transfer req = Transfer.builder() + .header(header) + .body(body) + .build(); + + // 3. EAI 컴포넌트를 통해 최종 전송 + return eai.call(req, resBody); + } + */ + + /** + * 3. 대내 MCI 호출 (사내 시스템 간 통신) + */ + /* + public Transfer callMci(String itrfId, S body, Class resBody) { + + CommonHeader header = CommonHeaderFactory.createRequestHeader(itrfId); + + Transfer req = Transfer.builder() + .header(header) + .body(body) + .build(); + + return mci.call(req, resBody); + } + */ +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/dto/SampleGlowMessage.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/dto/SampleGlowMessage.java new file mode 100644 index 00000000..e9961c05 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/dto/SampleGlowMessage.java @@ -0,0 +1,84 @@ +package io.shinhanlife.dap.lib.integration.dto; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import java.util.List; + +// TODO: 실제 Glow Framework 의존성이 추가되면 아래 주석을 풀고 사용하세요! +// import io.shinhanlife.glow.communication.annotation.GlowMciFieldInfo; + +/** + * [대외 MCI 연동용 DTO 표준 템플릿] + * Glow Framework 개발표준정의서(2.2.1 IO 작성) 규칙을 100% 준수한 샘플입니다. + * 새로운 대외 통신 전문을 만들 때 이 파일을 복사해서 필드명과 길이만 수정하여 사용하세요. + */ +/** + * @package io.shinhanlife.dap.lib.integration.dto + * @className SampleGlowMessage + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PUBLIC) // [규칙 1] Reflection을 위한 기본 생성자 필수 (public 유지) +public class SampleGlowMessage { + + // [규칙 2] @GlowMciFieldInfo 선언 필수 (order: 순서) + // @GlowMciFieldInfo(order = 1) + private MessageHeader header; + + // [규칙 2] @GlowMciFieldInfo 선언 필수 (order: 순서) + // @GlowMciFieldInfo(order = 2) + private List msgDtdvValu; // 다건(List) 본문 데이터 + + + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor(access = AccessLevel.PUBLIC) + public static class MessageHeader { + + // [규칙 2] 단건 필드의 경우 length 필수 입력 (EIMS 길이와 일치해야 함) + // @GlowMciFieldInfo(order = 1, length = 1) + private String msgTnsmTypeCd; + + // @GlowMciFieldInfo(order = 2, length = 8) + private int msdvLen; + + // [규칙 3] 다건(List) 건수 필드의 경우, target 속성에 대상 변수명("msgDtdvValu") 필수 기입! + // @GlowMciFieldInfo(order = 3, length = 2, target = "msgDtdvValu") + private int msgRpttCc; + } + + + @Getter + @Builder + @AllArgsConstructor + @NoArgsConstructor(access = AccessLevel.PUBLIC) + public static class MessageBody { + + // @GlowMciFieldInfo(order = 1, length = 8) + private String msgCd; + + // @GlowMciFieldInfo(order = 2, length = 1) + private String msgPrnAttrCd; + + // @GlowMciFieldInfo(order = 3, length = 200) + private String msgCt; + + // @GlowMciFieldInfo(order = 4, length = 200) + private String anxMsgCt; + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/eai/component/AxhubEaiComponent.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/eai/component/AxhubEaiComponent.java new file mode 100644 index 00000000..8cf2b26e --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/eai/component/AxhubEaiComponent.java @@ -0,0 +1,99 @@ +package io.shinhanlife.dap.lib.integration.eai.component; + +import io.shinhanlife.dap.lib.config.GlowCommunicationProperties; +import io.shinhanlife.glow.communication.dto.CommonHeader; +import io.shinhanlife.glow.communication.dto.Transfer; +import io.shinhanlife.glow.communication.module.eai.component.GlowEaiComponent; +import io.shinhanlife.glow.communication.util.CommonHeaderFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * 신한라이프 내부 Glow 표준 EAI 컴포넌트 어댑터 (AXHUB) + */ +@Slf4j +@Component +@RequiredArgsConstructor + +/** + * @package io.shinhanlife.dap.lib.integration.eai.component + * @className AxhubEaiComponent + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +public class AxhubEaiComponent { + + @SuppressWarnings("rawtypes") + private final GlowEaiComponent eai; + private final GlowCommunicationProperties communicationProperties; + + @SuppressWarnings("unchecked") + private Transfer syncEai(Transfer request) { + // LOG 저장 (AXHUB 방식 로깅) + CommonHeader reqHeader = (CommonHeader) request.getHeader(); + log.info("[AxhubEaiComponent] {} EAI 호출시작 (수신서비스: {})", reqHeader.getItrfId(), reqHeader.getRcvSvcId()); + + Transfer response = (Transfer) eai.sync(request); + + log.info("[AxhubEaiComponent] {} EAI 호출종료 (수신서비스: {})", reqHeader.getItrfId(), reqHeader.getRcvSvcId()); + + return response; + } + + /** + * EAI 호출 + */ + public Transfer call(String itrfId, String rcvSvcId, I inputDto) throws Exception { + CommonHeader header = CommonHeaderFactory.createRequestHeader(itrfId, rcvSvcId); + + Transfer request = Transfer.builder() + .header(header) + .body(inputDto) + .build(); + + return syncEai(request); + } + + /** + * EAI 호출 (응답 타입 명시) + */ + @SuppressWarnings("unchecked") + public Transfer call(String itrfId, String rcvSvcId, I inputDto, Class resBodyClass) throws Exception { + CommonHeader header = CommonHeaderFactory.createRequestHeader(itrfId, rcvSvcId); + + Transfer request = Transfer.builder() + .header(header) + .body(inputDto) + .resBodyClass((Class) (Class) resBodyClass) + .build(); + + return syncEai(request); + } + + /** + * EAI 호출 (rcvSvcId 없는 경우) + */ + public Transfer call(String itrfId, I inputDTO, Class resBodyClass) throws Exception { + String className = inputDTO.getClass().getSimpleName(); + String rcvSvcId = className.replace("_I", ""); + return call(itrfId, rcvSvcId, inputDTO, resBodyClass); + } + + /** + * EAI 호출 (Response body class와 rcvSvcId 없는 경우) + */ + public Transfer call(String itrfId, I inputDTO) throws Exception { + String className = inputDTO.getClass().getSimpleName(); + String rcvSvcId = className.replace("_I", ""); + return call(itrfId, rcvSvcId, inputDTO); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpComponent.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpComponent.java new file mode 100644 index 00000000..dccf1fe5 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpComponent.java @@ -0,0 +1,140 @@ +package io.shinhanlife.dap.lib.integration.http.component; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.config.GlowCommunicationProperties; +import io.shinhanlife.dap.lib.mcp.McpRequestHeaderContext; +import io.shinhanlife.dap.lib.mcp.McpRequestHeaders; +import io.shinhanlife.glow.communication.module.http.component.GlowHttpComponent; +import io.shinhanlife.glow.communication.module.http.dto.HttpBody; +import io.shinhanlife.glow.communication.module.http.dto.HttpHeader; +import io.shinhanlife.glow.communication.module.http.dto.HttpTransfer; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; + +/** + * Tool Pod outbound HTTP component using the Glow HTTP client. + * Target URL, HTTP method, content type and Pod-to-Pod behaviour are resolved from + * {@code glow.communication.http.api-list}; business source code does not own endpoint values. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class AxhubHttpComponent { + + private static final String ANONYMOUS_REQUEST = "AXHUB-TOOL"; + + private final GlowHttpComponent http; + private final ObjectMapper json; + private final GlowCommunicationProperties communicationProperties; + private final AxhubHttpProperties properties; + + /** Calls the exact URL configured for the API name. */ + public R call(String apiName, T inputDto, Class responseBodyClass) { + return call(apiName, "", inputDto, responseBodyClass, 0); + } + + /** Calls the configured URL with an optional resource suffix. */ + public R call(String apiName, String uri, T inputDto, Class responseBodyClass) { + return call(apiName, uri, inputDto, responseBodyClass, 0); + } + + public R call(String apiName, String uri, T inputDto, Class responseBodyClass, int timeout) { + AxhubHttpProperties.ApiDefinition api = resolveApi(apiName); + return execute(api, uri, inputDto, responseBodyClass, timeout); + } + + /** Calls the configured URL only when the target is marked as a business Pod. */ + public R callBizPod(String apiName, T inputDto, Class responseBodyClass) { + AxhubHttpProperties.ApiDefinition api = resolveApi(apiName); + if (!api.bizPod()) { + throw new IllegalArgumentException("Configured API is not a business Pod: " + apiName); + } + return execute(api, "", inputDto, responseBodyClass, 0); + } + + private R execute(AxhubHttpProperties.ApiDefinition api, String uri, T inputDto, + Class responseBodyClass, int timeout) { + HttpHeader header = createHeader(api, timeout); + String requestUri = joinPath(api.url(), uri); + HttpTransfer request = HttpTransfer.http() + .header(header) + .domain(api.domain()) + .uri(requestUri) + .method(api.method()) + .contentType(contentType(api)) + .responseEntity(responseBodyClass) + .body(inputDto) + .build(); + + log.info("[AxhubHttpComponent] Glow HTTP call. apiName={}, method={}, uri={}", + api.name(), api.method(), requestUri); + ResponseEntity response = http.sync(request); + return convertResponse(response.getBody(), responseBodyClass); + } + + private AxhubHttpProperties.ApiDefinition resolveApi(String apiName) { + return properties.getApiList().stream() + .filter(api -> apiName != null && apiName.equals(api.name())) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No HTTP API configuration for name: " + apiName)); + } + + private HttpHeader createHeader(AxhubHttpProperties.ApiDefinition api, int timeout) { + HttpHeader header = new HttpHeader(); + if (api.bizPod()) { + header.set("X-POD-TO-POD", "true"); + } + McpRequestHeaders inbound = McpRequestHeaderContext.current(); + if (inbound == null) { + header.set("X-ANONYMOUS-REQ", ANONYMOUS_REQUEST); + } else { + putIfPresent(header, "trace-id", inbound.traceId()); + putIfPresent(header, "request-id", inbound.requestId()); + putIfPresent(header, "X-USER-ID", inbound.encryptedEmployeeId()); + } + header.setReadTimeout(timeout == 0 ? defaultReadTimeout() : timeout); + return header; + } + + private int defaultReadTimeout() { + return communicationProperties == null || communicationProperties.getHttp() == null + ? 0 : communicationProperties.getHttp().getReadTimeout(); + } + + private MediaType contentType(AxhubHttpProperties.ApiDefinition api) { + return api.contentType() == null || api.contentType().isBlank() + ? MediaType.APPLICATION_JSON + : MediaType.parseMediaType(api.contentType()); + } + + private R convertResponse(HttpBody responseBody, Class responseBodyClass) { + String content = responseBody == null ? null : responseBody.content(); + if (responseBodyClass == String.class) { + return responseBodyClass.cast(content); + } + try { + return json.readValue(content, responseBodyClass); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Failed to convert HTTP response to " + responseBodyClass.getSimpleName(), e); + } + } + + private void putIfPresent(HttpHeader header, String name, String value) { + if (value != null && !value.isBlank()) { + header.set(name, value); + } + } + + private String joinPath(String configuredUrl, String suffix) { + String left = configuredUrl == null ? "" : configuredUrl.replaceAll("/+$", ""); + if (suffix == null || suffix.isBlank()) { + return left.isEmpty() ? "/" : left; + } + String right = suffix.startsWith("/") ? suffix : "/" + suffix; + return left + right; + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpProperties.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpProperties.java new file mode 100644 index 00000000..b07835ed --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpProperties.java @@ -0,0 +1,34 @@ +package io.shinhanlife.dap.lib.integration.http.component; + +import java.util.ArrayList; +import java.util.List; +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.http.HttpMethod; +import org.springframework.stereotype.Component; + +/** + * Glow HTTP target catalog. + * + *

    Each target follows the ShinhanLife standard: name, domain, url, method, + * content-type, and biz-pod. Target-specific values belong in application-glow*.yml.

    + */ +@Getter +@Setter +@Component +@ConfigurationProperties(prefix = "glow.communication.http") +public class AxhubHttpProperties { + + private List apiList = new ArrayList<>(); + + public record ApiDefinition( + String name, + String domain, + String url, + HttpMethod method, + String contentType, + boolean bizPod + ) { + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/component/AxhubMciComponent.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/component/AxhubMciComponent.java new file mode 100644 index 00000000..746d70fb --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/component/AxhubMciComponent.java @@ -0,0 +1,168 @@ +package io.shinhanlife.dap.lib.integration.mci.component; + +import io.shinhanlife.dap.lib.session.dto.SessionDto; +import io.shinhanlife.dap.lib.util.SessionUtil; +import io.shinhanlife.dap.lib.config.GlowCommunicationProperties; +import io.shinhanlife.glow.communication.dto.CommonHeader; +import io.shinhanlife.glow.communication.dto.HeaderDefaults; +import io.shinhanlife.glow.communication.dto.Transfer; +import io.shinhanlife.glow.communication.module.mci.component.GlowMciComponent; +import io.shinhanlife.glow.communication.util.CommonHeaderFactory; +import io.shinhanlife.dap.lib.integration.mci.enums.IndvCtinRoleTyp; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * 신한라이프 내부 Glow 표준 컴포넌트 어댑터 (AXHUB) + */ +@Slf4j +@Component +@RequiredArgsConstructor + +/** + * @package io.shinhanlife.dap.lib.integration.mci.component + * @className AxhubMciComponent + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +public class AxhubMciComponent { + + @SuppressWarnings("rawtypes") + private final GlowMciComponent mci; + private final GlowCommunicationProperties communicationProperties; + + /** 전문생성채널유형코드 : 1 (채널계) */ + private final static String TGRM_CREA_CHNN_TYPE_CD_1 = "1"; + private static final String SUCO_UNBL_CODE = "NNB00147"; // 청약불가 + + /** + * 전문 Common Header 생성 + * @param itrfName 인터페이스Id + * @param rcvSvcId 수신서비스Id + * @return Map + */ + private Map createCommonHeaderMap(String itrfName, String rcvSvcId) { + SessionDto sessionDto = SessionUtil.getSession(); + Map commonHeaderMap = new HashMap<>(); + + commonHeaderMap.put(HeaderDefaults.ITRF_ID, itrfName); + commonHeaderMap.put(HeaderDefaults.RCV_SVC_ID, rcvSvcId); + + if (sessionDto != null) { + commonHeaderMap.put(HeaderDefaults.STR_YMD, sessionDto.getStrYmd()); + commonHeaderMap.put(HeaderDefaults.ACNT_OGNZ_NO, sessionDto.getBrafNo()); + commonHeaderMap.put(HeaderDefaults.PSMR_ASRT_CD, sessionDto.getPsmrAsrtCd()); + commonHeaderMap.put(HeaderDefaults.SBSN_RULP_ASRT_CD, sessionDto.getSbsnRulpAsrtCd()); + commonHeaderMap.put(HeaderDefaults.BSDU_CD, sessionDto.getBsduCd()); + commonHeaderMap.put(HeaderDefaults.BSQU_CD, sessionDto.getBsquCd()); + commonHeaderMap.put(HeaderDefaults.OGNZ_ASRT_CD, sessionDto.getOgnzAsrtCd()); + commonHeaderMap.put(HeaderDefaults.OGNZ_LEVE_CD, sessionDto.getOgnzLeveCd()); + commonHeaderMap.put(HeaderDefaults.SCRN_ID, sessionDto.getPrgrId()); + } + + commonHeaderMap.put(HeaderDefaults.INDV_CTIN_ROLE_CD, IndvCtinRoleTyp.CD_Z99.getCode()); + commonHeaderMap.put(HeaderDefaults.TGRM_CREA_CHNN_TYPE_CD, TGRM_CREA_CHNN_TYPE_CD_1); + + if (communicationProperties != null && communicationProperties.getCommon() != null) { + commonHeaderMap.put(HeaderDefaults.ENVR_TYPE_CD, communicationProperties.getCommon().getEnvType()); + } + + return commonHeaderMap; + } + + @SuppressWarnings("unchecked") + private Transfer syncMci(Transfer request) { + // LOG 저장 (AXHUB 방식 로깅) + CommonHeader reqHeader = (CommonHeader) request.getHeader(); + log.info("[AxhubMciComponent] {} MCI 호출시작 (수신서비스: {})", reqHeader.getItrfId(), reqHeader.getRcvSvcId()); + Transfer response = (Transfer) mci.sync(request); + log.info("[AxhubMciComponent] {} MCI 호출종료 (수신서비스: {})", reqHeader.getItrfId(), reqHeader.getRcvSvcId()); + + if (response != null && response.getHeader() != null) { + CommonHeader resHeader = (CommonHeader) response.getHeader(); + String tgrmDalRsltCd = resHeader.getTgrmDalRsltCd(); + // TODO 추가 메시지 처리 및 오류 코드 제어 로직 + } + + return response; + } + + public Transfer callTo(String itrfName, String rcvSvcId, I inputDto) throws Exception { + Map commonHeaderMap = createCommonHeaderMap(itrfName, rcvSvcId); + CommonHeader header = CommonHeaderFactory.createRequestHeader(commonHeaderMap); + + Transfer request = Transfer.builder() + .header(header) + .body(inputDto) + .build(); + + return syncMci(request); + } + + /** + * 대내 mci 호출 + * @param itrfName 인터페이스Id + * @param rcvSvcId 수신서비스Id + * @param inputDto inputDto + * @param resBodyClass resBodyClass + * @return Transfer + * @param resBodyClass 제너릭 + * @param inputDto 제너릭 + */ + @SuppressWarnings("unchecked") + public Transfer callTo(String itrfName, String rcvSvcId, I inputDto, Class resBodyClass) throws Exception { + Map commonHeaderMap = createCommonHeaderMap(itrfName, rcvSvcId); + CommonHeader header = CommonHeaderFactory.createRequestHeader(commonHeaderMap); + + Transfer request = Transfer.builder() + .header(header) + .body(inputDto) + .resBodyClass((Class) (Class) resBodyClass) + .build(); + + return syncMci(request); + } + + /** + * 대내 mci 호출 (rcvSvcId 없는 경우) + * @param itrfName 인터페이스Id + * @param inputDTO 수신서비스Id (클래스명 대체) + * @param resBodyClass resBodyClass + * @return Transfer + * @param resBodyClass 제너릭 + * @param inputDto 제너릭 + * @throws Exception Exception + */ + public Transfer callTo(String itrfName, I inputDTO, Class resBodyClass) throws Exception { + String className = inputDTO.getClass().getSimpleName(); + String rcvSvcId = className.replace("_I", ""); + return callTo(itrfName, rcvSvcId, inputDTO, resBodyClass); + } + + /** + * 대내 mci 호출 (Response body class와 rcvSvcId 없는 경우) + * @param itrfName 인터페이스Id + * @param inputDTO 수신서비스Id (클래스명 대체) + * @return Transfer + * @param resBodyClass 제너릭 + * @param inputDto 제너릭 + * @throws Exception Exception + */ + public Transfer callTo(String itrfName, I inputDTO) throws Exception { + String className = inputDTO.getClass().getSimpleName(); + String rcvSvcId = className.replace("_I", ""); + return callTo(itrfName, rcvSvcId, inputDTO); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/config/GlowMockConfig.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/config/GlowMockConfig.java new file mode 100644 index 00000000..a46fcd53 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/config/GlowMockConfig.java @@ -0,0 +1,40 @@ +package io.shinhanlife.dap.lib.integration.mci.config; + +import io.shinhanlife.glow.communication.module.mci.component.GlowMciComponent; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * TODO: 실제 Glow Framework 의존성이 추가되어 io.shinhanlife.glow 패키지가 + * ComponentScan에 잡히게 되면 이 설정 클래스는 삭제하세요. + */ +@Configuration + +/** + * @package io.shinhanlife.dap.lib.integration.mci.config + * @className GlowMockConfig + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +public class GlowMockConfig { + + @Bean + @SuppressWarnings("rawtypes") + public GlowMciComponent glowMciComponent() { + return new GlowMciComponent(); + } + + @Bean + @SuppressWarnings("rawtypes") + public io.shinhanlife.glow.communication.module.eai.component.GlowEaiComponent glowEaiComponent() { + return new io.shinhanlife.glow.communication.module.eai.component.GlowEaiComponent(); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/config/ShinhanIntegrationProperties.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/config/ShinhanIntegrationProperties.java new file mode 100644 index 00000000..d1302746 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/config/ShinhanIntegrationProperties.java @@ -0,0 +1,40 @@ +package io.shinhanlife.dap.lib.integration.mci.config; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @package io.shinhanlife.dap.lib.integration.mci.config + * @className ShinhanIntegrationProperties + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Getter +@Setter +@ConfigurationProperties(prefix = "shinhan.integration") +public class ShinhanIntegrationProperties { + + /** + * 환경유형코드: 운영(R), 테스트(T), 개발(D) + */ + private String envrTypeCd = "D"; + + private ServerInfo eai = new ServerInfo(); + private ServerInfo internalMci = new ServerInfo(); + + @Getter + @Setter + public static class ServerInfo { + private String url; + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/MciRequestWrapper.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/MciRequestWrapper.java new file mode 100644 index 00000000..23c3bc66 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/MciRequestWrapper.java @@ -0,0 +1,34 @@ +package io.shinhanlife.dap.lib.integration.mci.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; + +/** + * @package io.shinhanlife.dap.lib.integration.mci.dto + * @className MciRequestWrapper + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MciRequestWrapper { + private ShinhanCommonHeaderDto tgrmCmnnhddValu; + + @JsonUnwrapped + private T body; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/MciResponseWrapper.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/MciResponseWrapper.java new file mode 100644 index 00000000..4e2b9475 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/MciResponseWrapper.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.lib.integration.mci.dto; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @package io.shinhanlife.dap.lib.integration.mci.dto + * @className MciResponseWrapper + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MciResponseWrapper { + private ShinhanCommonHeaderDto tgrmCmnnhddValu; + + @JsonUnwrapped + private T body; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/OlCommonHeaderDto.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/OlCommonHeaderDto.java new file mode 100644 index 00000000..9eff40b6 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/OlCommonHeaderDto.java @@ -0,0 +1,90 @@ +package io.shinhanlife.dap.lib.integration.mci.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import java.util.List; + +/** + * @package io.shinhanlife.dap.lib.integration.mci.dto + * @className OlCommonHeaderDto + * @description AX HUB 시스템 처리 클래스 - OL(구 오렌지라이프) 공통 헤더 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class OlCommonHeaderDto { + private String custNm; // 고객명 + private String custRrn; // 고객 주민등록번호 + private String custNo; // 고객번호 + private String rcevNo; // 접수번호 + private String pono; // 증권번호 + private String scrNm; // 화면명 + private String scrId; // 화면ID + private String lginDttm; // 사용자가 로그인한 접속일시 + private String lginIpAddr; // 사용자가 접속한 IP 주소 + private String userNm; // 사용자 이름(한글) + private String userEngNm; // 사용자 영문이름 + private String userId; // 사용자 ID(AD ID) + private String userNo; // 사용자번호 + private String deptCd; // 사용자조직 코드 + private String salsDvCd; // 영업본부코드 + private String salsBoCd; // 영업지점코드 + private String uppDeptCd; // 상위조직코드 + private String prcsrUserId; // 처리자 ID(AD ID) + private String prcsrUserNo; // 처리지번호 + private String prcsrDeptCd; // 처리지조직 코드 + private String prcsrDvCd; // 처리지 영업본부코드 + private String prcsrBoCd; // 처리지 영업지점코드 + private String prcsrUppDeptCd; // 부서코드 + private String sysCd; // 요청이 들어온 시스템을 표시 + private String reqtSvcNm; // 요청하는 서비스 모듈명 + private String reqtMthdNm; // 요청하는 메소드명 + private String reqtVoNm; // 요청메소드에 전달할 값을 담는 VO명 + private String scrButnFuncClssCd; // 화면에서 버튼 별 이벤트 구분을 위한 구분코드 + private String scrGriCnt; // 화면 그리드 개수 + private List pageList; // 페이징 리스트 (L2 반복) + private String reqtDttm; // 요청일시 + private String crdtInfoIcluFlg; // 신용정보포함여부(Y,N) + private String crdtInfoDataChgTypCd; // 업무내역별 식별코드 부여 + private String crdtInfoIdfInEngAbbrNm; // 신용정보식별영문약어명 + private String crdtInfoIdfnSysCd; // 신용정보식별시스템코드 + private String scrButnNm; // 화면버튼명 + private String msgCnt; // 메시지 개수 + private List msgList; // 메시지 리스트 (L2 반복) + private String respDttm; // 응답일시 + private String svcRunNm; // 거래별로 유일한 ServiceExecutionID + private String stdate; // 기준일자 + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class OlPageDto { + private String pageSrno; // 페이지 인덱스값 (L3) + private String pageInqCnt; // 한페이지에 조회될 건수 (L3) + private String nxtButnNm; // 다음버튼ID (L3) + private String nxtButnEnbFlg; // 다음버튼 활성여부 (L3) + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class OlMsgDto { + private String msgNo; // 서버 측에서 세팅한 정상/에러 메시지코드 (L3) + private String msgTypCd; // 메시지유형코드 (L3) + private String msgNm; // 메시지코드의 내용 (L3) + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanCommonHeaderDto.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanCommonHeaderDto.java new file mode 100644 index 00000000..43e10757 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanCommonHeaderDto.java @@ -0,0 +1,72 @@ +package io.shinhanlife.dap.lib.integration.mci.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @package io.shinhanlife.dap.lib.integration.mci.dto + * @className ShinhanCommonHeaderDto + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ShinhanCommonHeaderDto { + + private String tgrmLencn; // 전문길이 + private String glbId; // 글로벌ID (전사공통키) + private String pgrsSriaNo; // 진행일련번호 + private String tgrmVrsnInfoValu; // 전문버전정보값 + private String tgrmEncrYn; // 전문암호화여부 + private String gpcpCd; // 그룹사코드 + private String appliDutjCd; // 어플리케이션업무코드 + private String appliDtptDutjCd; // 어플리케이션상세업무코드 + private String frbuCd; // 대외기관코드 + private String cmouDutjCd; // 대외업무코드 + private String cmouCssfCd; // 대외종별코드 + private String cmouTraCd; // 대외거래코드 + private String rcvSvcId; // 수신서비스ID + private String rsltRcvSvcId; // 결과수신서비스ID + private String tgrmCreaChnnTypeCd; // 전문생성채널유형코드 + + private String lnggDvsnCd; // 언어구분코드 + private String simulTraYn; // 시뮬레이션거래여부 + private String itrIfId; // 인터페이스ID + private String reqRspnScCd; // 요청응답구분코드 + private String tnsmTypeCd; // 전송유형코드 + private String envrTypeCd; // 환경유형코드 + private String inqrTraTypeCd; // 조회거래유형코드 + private String reqTgrmTnsmDtptDt; // 요청전문전송상세일시 + private String strYmd; // 기준일자 + private String scrnId; // 화면ID + private String scrnBtnId; // 화면버튼ID + + private String userIpAddr; // 사용자IP주소 + private String drtmCd; // 부서코드 + private String userId; // 사용자ID + private String indvCtinRoleCd; // 개인신용정보역할코드 + private String acntOgnzNo; // 경리조직번호 + private String rspnTgrmTnsmDtptDt; // 응답전문전송상세일시 + private String tgrmDalRsltCd; // 전문처리결과코드 + private String ognzAsrtCd; // 조직분류코드 + private String ognzLeveCd; // 조직레벨코드 + private String psmrAsrtCd; // 인사조직분류코드 + private String sbsnRulpAsrtCd; // 영업규정분류코드 + private String bsduCd; // 영업지국코드 + private String bsquCd; // 영업자격코드 + private String linkPrafDutyCd; // 연계인사직책코드 + private String indvInfoLogWritYn; // 개인정보로그작성여부 + private String prepImhdNm; // 예비항목명 +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanMessageDto.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanMessageDto.java new file mode 100644 index 00000000..5c02fd31 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanMessageDto.java @@ -0,0 +1,51 @@ +package io.shinhanlife.dap.lib.integration.mci.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @package io.shinhanlife.dap.lib.integration.mci.dto + * @className ShinhanMessageDto + * @description AX HUB 시스템 처리 클래스 - MCI 전문 메시지부 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ShinhanMessageDto { + + private MsgHddvValu msgHddvValu; // 메시지헤더부값 + private MsgDtdvValu msgDtdvValu; // 메시지데이터부값 + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class MsgHddvValu { + private String msgTnsmTypeCd; // 메시지전송유형코드 + private Integer msdvLencn; // 메시지부길이 + private Integer msgRpttCc; // 메시지반복건수 + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class MsgDtdvValu { + private String msgCd; // 메시지코드 + private String msgPrnAttrCd; // 메시지출력속성코드 + private String msgCt; // 메시지내용 + private String anxMsgCt; // 부가메시지내용 + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanTelegramWrapper.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanTelegramWrapper.java new file mode 100644 index 00000000..47133822 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/ShinhanTelegramWrapper.java @@ -0,0 +1,38 @@ +package io.shinhanlife.dap.lib.integration.mci.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import com.fasterxml.jackson.annotation.JsonUnwrapped; + +/** + * @package io.shinhanlife.dap.lib.integration.mci.dto + * @className ShinhanTelegramWrapper + * @description AX HUB 시스템 처리 클래스 - MCI 전문 전체 래퍼 (공통헤더부 + 메시지부 + 데이터부) + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ShinhanTelegramWrapper { + + // 1. 공통 헤더부 + private ShinhanCommonHeaderDto tgrmCmnnhddValu; + + // 2. 메시지부 + private ShinhanMessageDto tgrmMsdvValu; + + // 3. 데이터부 (비즈니스마다 다름, JsonUnwrapped로 평탄화하거나 객체 자체로 유지 가능. 여기서는 객체 유지) + private T tgrmDtdvValu; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/SlCommonHeaderDto.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/SlCommonHeaderDto.java new file mode 100644 index 00000000..73d3514c --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/dto/SlCommonHeaderDto.java @@ -0,0 +1,84 @@ +package io.shinhanlife.dap.lib.integration.mci.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @package io.shinhanlife.dap.lib.integration.mci.dto + * @className SlCommonHeaderDto + * @description AX HUB 시스템 처리 클래스 - SL(신한라이프) 표준 헤더 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SlCommonHeaderDto { + private String length; // 전문길이 + private SlGlobalId globalId; // 글로벌ID + private String headerVer; // 전문헤더버전 + private String encodeFlag; // 전문암호화여부 + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class SlGlobalId { + private String writeDate; // 전문작성일 (8) + private String sysCd; // 생성시스템명 (8) + private String typeCd; // 구분코드 (2) + private String detailCd; // 세부업무코드 (4) + private String seqNo; // 채번번호 (8) + private String step; // 진행상황 (2) + } + + private String groupCoCd; // 그룹사코드 + private String instCd; // 기관코드 + private String applCd; // 업무코드 + private String kindCd; // 종별코드 + private String txCd; // 거래코드 + private String pfmAppName; // 어플리케이션 명 + private String pfmSvcName; // 서비스 명 + private String pfmFnName; // 오퍼레이션 명 + private String systemCd; // 생성시스템구분 + private String trFlag; // 요청응답구분 + private String syncFlag; // 동기구분 + private String envrFlag; // 환경구분 + private String crudFlag; // 조회거래구분 + private String sendTime; // 전문전송일시 + private String screenId; // 화면ID + private String clntIp; // Client IP + private String orgCd; // 부서(지점)코드 + private String userId; // 사용자 사번(아이디) + private String indvCrdtInfo; // 개인신용정보역할코드 + private String acntOgnzNo; // 경리조직번호 + private String ttiFlag; // TimeOut사용 + private String ttiStartTm; // 최초시작시간 + private String ttiKeepTm; // 유지시간초수 + private String outMsgTm; // 응답전문작성일시 + private String resType; // 처리결과 + private String resCode; // 응답코드 + private String resBascMsg; // 응답기본내역 + private String msgType; // 메시지 유형 + private String rcvSvcCd; // 수신 서비스 Code + private String rsltRcvSvcCd; // 결과수신 서비스 Code + private String realSvcCd; // Real 서비스 Code + private String ognzAsrtCd; // 조직분류코드 + private String ognzLeveCd; // 조직레벨구분코드 + private String psmrAsrtCd; // 인사조직분류코드 + private String sbsnRulpAsrtCd; // 영업규정분류코드 + private String bsduCd; // 영업지국코드 + private String bsquCd; // 영업자격코드 + private String linkPrafDutyCd; // 직책코드 + private String temp; // 예비 필드 +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/enums/IndvCtinRoleTyp.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/enums/IndvCtinRoleTyp.java new file mode 100644 index 00000000..5ed17887 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/integration/mci/enums/IndvCtinRoleTyp.java @@ -0,0 +1,27 @@ +package io.shinhanlife.dap.lib.integration.mci.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor + +/** + * @package io.shinhanlife.dap.lib.integration.mci.enums + * @className IndvCtinRoleTyp + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +public enum IndvCtinRoleTyp { + CD_Z99("Z99"); + + private final String code; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestAnnotations.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestAnnotations.java new file mode 100644 index 00000000..f793904e --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestAnnotations.java @@ -0,0 +1,10 @@ +package io.shinhanlife.dap.lib.manifest; + +/** Behaviour hints exposed by the Tool Service manifest. */ +public record ToolManifestAnnotations( + String title, + boolean readOnlyHint, + boolean destructiveHint, + boolean idempotentHint, + boolean openWorldHint) { +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestItem.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestItem.java new file mode 100644 index 00000000..f03c9a96 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestItem.java @@ -0,0 +1,15 @@ +package io.shinhanlife.dap.lib.manifest; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; + +/** One MCP Tool declaration published by a Tool Service. */ +public record ToolManifestItem( + String name, + String endpoint, + String title, + String description, + Map inputSchema, + ToolManifestAnnotations annotations, + @JsonProperty("_meta") ToolManifestMeta meta) { +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestMeta.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestMeta.java new file mode 100644 index 00000000..192ba496 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestMeta.java @@ -0,0 +1,5 @@ +package io.shinhanlife.dap.lib.manifest; + +/** Operational metadata exposed by the Tool Service manifest. */ +public record ToolManifestMeta(String version, long timeoutMillis, boolean enabled) { +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestResponse.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestResponse.java new file mode 100644 index 00000000..293a9e01 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestResponse.java @@ -0,0 +1,7 @@ +package io.shinhanlife.dap.lib.manifest; + +import java.util.List; + +/** Top-level response for GET /tool-manifest. */ +public record ToolManifestResponse(String bundleId, String revision, List tools) { +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestService.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestService.java new file mode 100644 index 00000000..5050a442 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/manifest/ToolManifestService.java @@ -0,0 +1,131 @@ +package io.shinhanlife.dap.lib.manifest; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.config.McpProperties; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import io.shinhanlife.dap.lib.mcp.ToolRegistryHeartbeatSender; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** Builds the Tool Service owned manifest consumed by the MCP server. */ +@Service +public class ToolManifestService { + + private static final long DEFAULT_TIMEOUT_MILLIS = 300000L; + private static final AtomicLong LAST_ISSUED_REVISION = new AtomicLong(); + private final Supplier> toolSupplier; + private final ObjectMapper objectMapper; + private final McpProperties properties; + private String lastFingerprint; + private String lastRevision; + + @Autowired + public ToolManifestService(ToolRegistryHeartbeatSender heartbeatSender, ObjectMapper objectMapper, + McpProperties properties) { + this(heartbeatSender::getAllScannedTools, objectMapper, properties); + } + + ToolManifestService(Supplier> toolSupplier, ObjectMapper objectMapper, + McpProperties properties) { + this.toolSupplier = toolSupplier; + this.objectMapper = objectMapper; + this.properties = properties; + } + + public ToolManifestResponse currentManifest() { + String bundleId = properties.getManifest() == null ? null : properties.getManifest().getBundleId(); + if (bundleId == null || bundleId.isBlank()) { + throw new IllegalStateException("mcp.manifest.bundle-id must be configured"); + } + + List tools = toolSupplier.get().stream() + .map(this::toManifestItem) + .sorted(Comparator.comparing(ToolManifestItem::name)) + .toList(); + validate(tools); + return new ToolManifestResponse(bundleId, revision(bundleId, tools), tools); + } + + private ToolManifestItem toManifestItem(ToolMetadata tool) { + String title = tool.getDisplayName() == null || tool.getDisplayName().isBlank() + ? tool.getName() : tool.getDisplayName(); + Map schema = tool.getParametersSchema() == null ? emptySchema() : tool.getParametersSchema(); + return new ToolManifestItem( + tool.getName(), endpoint(tool), title, tool.getDescription(), schema, + new ToolManifestAnnotations(title, isTrue(tool.getReadOnlyHint()), isTrue(tool.getDestructiveHint()), + isTrue(tool.getIdempotentHint()), isTrue(tool.getOpenWorldHint())), + new ToolManifestMeta(defaultString(tool.getSemver(), "1.0.0"), + tool.getTimeoutMillis() == null ? DEFAULT_TIMEOUT_MILLIS : tool.getTimeoutMillis(), + tool.getEnabled() == null || tool.getEnabled())); + } + + private void validate(List tools) { + String namePrefix = properties.getManifest() == null ? null : properties.getManifest().getNamePrefix(); + Set names = new LinkedHashSet<>(); + for (ToolManifestItem tool : tools) { + if (tool.name() == null || tool.name().isBlank()) { + throw new IllegalStateException("Tool manifest contains a blank tool name"); + } + if (!names.add(tool.name())) { + throw new IllegalStateException("Tool manifest contains duplicate tool name: " + tool.name()); + } + if (namePrefix != null && !namePrefix.isBlank() && !tool.name().startsWith(namePrefix)) { + throw new IllegalStateException("Tool name does not match mcp.manifest.name-prefix: " + tool.name()); + } + if (!"object".equals(tool.inputSchema().get("type"))) { + throw new IllegalStateException("Tool inputSchema root type must be object: " + tool.name()); + } + } + } + + private synchronized String revision(String bundleId, List tools) { + String fingerprint = fingerprint(bundleId, tools); + if (!fingerprint.equals(lastFingerprint)) { + long localMinimum = lastRevision == null ? Long.MIN_VALUE : Long.parseLong(lastRevision) + 1; + long nextTimestamp = LAST_ISSUED_REVISION.updateAndGet(previous -> + Math.max(Math.max(System.currentTimeMillis(), localMinimum), previous + 1)); + lastFingerprint = fingerprint; + lastRevision = Long.toString(nextTimestamp); + } + return lastRevision; + } + + private String fingerprint(String bundleId, List tools) { + try { + return objectMapper.writeValueAsString(Map.of("bundleId", bundleId, "tools", tools)); + } catch (Exception exception) { + throw new IllegalStateException("Failed to build Tool manifest revision source", exception); + } + } + + private String endpoint(ToolMetadata tool) { + if (tool.getEndpoint() != null && !tool.getEndpoint().isBlank()) { + return tool.getEndpoint(); + } + if (tool.getPodUrl() == null || tool.getPodUrl().isBlank()) { + return "/mcp/" + tool.getName(); + } + return tool.getPodUrl().replaceAll("/+$", "") + "/mcp/" + tool.getName(); + } + private Map emptySchema() { + return Map.of("type", "object", "properties", Map.of(), "additionalProperties", false); + } + + private boolean isTrue(Boolean value) { + return Boolean.TRUE.equals(value); + } + + private String defaultString(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderContext.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderContext.java new file mode 100644 index 00000000..0104394d --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderContext.java @@ -0,0 +1,21 @@ +package io.shinhanlife.dap.lib.mcp; + +/** Holds optional MCP headers for the lifetime of one HTTP request thread. */ +public final class McpRequestHeaderContext { + private static final ThreadLocal CURRENT_HEADERS = new ThreadLocal<>(); + + private McpRequestHeaderContext() { + } + + public static McpRequestHeaders current() { + return CURRENT_HEADERS.get(); + } + + static void set(McpRequestHeaders headers) { + CURRENT_HEADERS.set(headers); + } + + static void clear() { + CURRENT_HEADERS.remove(); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderFilter.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderFilter.java new file mode 100644 index 00000000..aae2654f --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaderFilter.java @@ -0,0 +1,34 @@ +package io.shinhanlife.dap.lib.mcp; + +import java.io.IOException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +/** Captures optional correlation and employee headers for an MCP HTTP call. */ +@Component +public class McpRequestHeaderFilter extends OncePerRequestFilter { + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + return !request.getRequestURI().endsWith("/mcp"); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + McpRequestHeaderContext.set(new McpRequestHeaders( + request.getHeader("X-Request-Id"), + request.getHeader("trace-id"), + request.getHeader("request-id"), + request.getHeader("employee-id"))); + try { + filterChain.doFilter(request, response); + } finally { + McpRequestHeaderContext.clear(); + } + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaders.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaders.java new file mode 100644 index 00000000..3eb87c76 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpRequestHeaders.java @@ -0,0 +1,9 @@ +package io.shinhanlife.dap.lib.mcp; + +/** Optional request headers propagated from an MCP HTTP request to a Tool invocation. */ +public record McpRequestHeaders( + String headerRequestId, + String traceId, + String requestId, + String encryptedEmployeeId) { +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolExecutionService.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolExecutionService.java new file mode 100644 index 00000000..6b0c6cfb --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolExecutionService.java @@ -0,0 +1,101 @@ +package io.shinhanlife.dap.lib.mcp; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.Error; +import io.shinhanlife.dap.lib.util.ToolSchemaResolver; +import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** Executes a cached Tool independently from its HTTP or MCP transport. */ +@Slf4j +@Service +@RequiredArgsConstructor +public class McpToolExecutionService { + + private final McpToolMethodRegistry toolMethodRegistry; + private final ObjectMapper objectMapper; + private final ToolArgumentSchemaValidator toolArgumentSchemaValidator; + private final ToolSchemaResolver toolSchemaResolver; + + public ToolExecutionResult execute(String functionName, McpRequestHeaders requestHeaders, + Map arguments) { + String headerRequestId = requestHeaders == null ? null : requestHeaders.headerRequestId(); + String traceId = requestHeaders == null ? null : requestHeaders.traceId(); + String requestId = requestHeaders == null ? null : requestHeaders.requestId(); + log.info("[Tool] IN - trace-id: {}, request-id: {}, tool: {}", traceId, requestId, functionName); + + McpToolMethodRegistry.RegisteredTool resolvedTool = toolMethodRegistry.find(functionName); + if (resolvedTool == null) { + return error(404, "TOOL_NOT_FOUND", "Tool not found: " + functionName, headerRequestId); + } + ToolExecutionResult validationFailure = validateInput(resolvedTool, arguments, headerRequestId); + if (validationFailure != null) { + return validationFailure; + } + try { + Object methodResult = invoke(resolvedTool, convertArgument(resolvedTool.method(), arguments)); + ToolExecutionResult outputFailure = validateOutput(resolvedTool, methodResult, headerRequestId); + if (outputFailure != null) { + return outputFailure; + } + Map headers = new HashMap<>(); + if (traceId != null) headers.put("trace-id", traceId); + if (requestId != null) headers.put("request-id", requestId); + log.info("[Tool] OUT - trace-id: {}, request-id: {}, tool: {}", traceId, requestId, functionName); + return new ToolExecutionResult(200, methodResult, headers); + } catch (Exception error) { + log.error("[Tool] Tool execution failed. tool={}", functionName, error); + return error(502, "TOOL_ERROR", "Tool execution failed", headerRequestId); + } + } + + private ToolExecutionResult validateInput(McpToolMethodRegistry.RegisteredTool tool, + Map arguments, String requestId) { + if (tool.method().getParameterCount() == 0 || Map.class.isAssignableFrom(tool.method().getParameterTypes()[0])) return null; + try { + Map schema = toolSchemaResolver.resolve(tool.annotation(), tool.hint(), tool.method().getParameterTypes()[0]); + List errors = toolArgumentSchemaValidator.validate(schema, arguments); + return errors.isEmpty() ? null : error(422, "INVALID_PARAM", "Tool arguments do not match the input schema", requestId); + } catch (Exception error) { + log.error("[Tool] Input schema validation failed unexpectedly. tool={}", tool.annotation().name(), error); + return null; + } + } + + private Object convertArgument(Method method, Map arguments) { + if (method.getParameterCount() == 0 || arguments == null || Map.class.isAssignableFrom(method.getParameterTypes()[0])) return arguments; + return objectMapper.convertValue(arguments, method.getParameterTypes()[0]); + } + + private Object invoke(McpToolMethodRegistry.RegisteredTool tool, Object argument) throws Exception { + return tool.method().getParameterCount() == 0 ? tool.method().invoke(tool.bean()) : tool.method().invoke(tool.bean(), argument); + } + + private ToolExecutionResult validateOutput(McpToolMethodRegistry.RegisteredTool tool, + Object methodResult, String requestId) { + try { + Map outputSchema = toolSchemaResolver.resolveOutput(tool.annotation(), tool.method().getReturnType(), tool.hint()); + if (!outputSchema.isEmpty() && !toolArgumentSchemaValidator.validateValue(outputSchema, methodResult).isEmpty()) { + return error(500, "INVALID_TOOL_RESPONSE", "Tool response does not match its output schema", requestId); + } + } catch (Exception error) { + log.error("[Tool] Output schema validation failed unexpectedly. tool={}", tool.annotation().name(), error); + } + return null; + } + + private ToolExecutionResult error(int statusCode, String code, String message, String requestId) { + Map body = new HashMap<>(); + body.put("code", code); + body.put("message", message); + body.put("details", Map.of("status", Integer.toString(statusCode))); + if (requestId != null) body.put("request_id", requestId); + return new ToolExecutionResult(statusCode, body, Map.of()); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistry.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistry.java new file mode 100644 index 00000000..cd3dc4a1 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistry.java @@ -0,0 +1,87 @@ +package io.shinhanlife.dap.lib.mcp; + +import io.shinhanlife.dap.lib.annotation.ToolHint; +import io.shinhanlife.dap.lib.config.McpProperties; +import java.lang.reflect.Method; +import java.util.LinkedHashMap; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springaicommunity.mcp.annotation.McpTool; +import org.springframework.aop.support.AopUtils; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +/** + * Caches executable {@link McpTool} methods once when a Tool Pod starts. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class McpToolMethodRegistry { + + private final ApplicationContext applicationContext; + private final McpProperties mcpProperties; + + private volatile Map tools = Map.of(); + + @EventListener(ApplicationReadyEvent.class) + public void initialize() { + Map discovered = new LinkedHashMap<>(); + + for (Object bean : applicationContext.getBeansOfType(Object.class).values()) { + Class targetClass = AopUtils.getTargetClass(bean); + for (Method declaredMethod : targetClass.getDeclaredMethods()) { + McpTool annotation = AnnotationUtils.findAnnotation(declaredMethod, McpTool.class); + if (annotation == null) { + continue; + } + + RegisteredTool tool = new RegisteredTool( + bean, + findInvocableMethod(bean, declaredMethod), + annotation, + AnnotationUtils.findAnnotation(declaredMethod, ToolHint.class)); + register(discovered, annotation.name(), tool); + registerNamespaceAlias(discovered, annotation.name(), tool); + } + } + + tools = Map.copyOf(discovered); + log.info("[Tool Registry] {} executable tool names cached", tools.size()); + } + + public RegisteredTool find(String toolName) { + return tools.get(toolName); + } + + private void registerNamespaceAlias(Map discovered, String toolName, + RegisteredTool tool) { + String namespace = mcpProperties.getNamespace(); + if (StringUtils.hasText(namespace)) { + register(discovered, namespace + "_" + toolName, tool); + } + } + + private void register(Map discovered, String toolName, RegisteredTool tool) { + RegisteredTool existing = discovered.putIfAbsent(toolName, tool); + if (existing != null && existing != tool) { + throw new IllegalStateException("Duplicate @McpTool name: " + toolName); + } + } + + private Method findInvocableMethod(Object bean, Method declaredMethod) { + try { + return bean.getClass().getMethod(declaredMethod.getName(), declaredMethod.getParameterTypes()); + } catch (NoSuchMethodException ignored) { + return declaredMethod; + } + } + + public record RegisteredTool(Object bean, Method method, McpTool annotation, ToolHint hint) { + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolExecutionResult.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolExecutionResult.java new file mode 100644 index 00000000..461c3f42 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolExecutionResult.java @@ -0,0 +1,15 @@ +package io.shinhanlife.dap.lib.mcp; + +import java.util.Map; + +/** Protocol-neutral result of invoking one Tool Pod business tool. */ +public record ToolExecutionResult(int statusCode, Object body, Map headers) { + + public ToolExecutionResult { + headers = headers == null ? Map.of() : Map.copyOf(headers); + } + + public boolean isSuccess() { + return statusCode >= 200 && statusCode < 300; + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMcpServerConfiguration.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMcpServerConfiguration.java new file mode 100644 index 00000000..90afd71b --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolMcpServerConfiguration.java @@ -0,0 +1,38 @@ +package io.shinhanlife.dap.lib.mcp; + +import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; + +/** Exposes every Tool Pod through the MCP Streamable HTTP transport. */ +@Slf4j +@Configuration +public class ToolMcpServerConfiguration { + + @PostConstruct + public void init() { + log.warn("================================================="); + log.warn("ToolMcpServerConfiguration IS LOADED BY SPRING!!!"); + log.warn("================================================="); + } + + @Bean + @Primary + public HttpServletStreamableServerTransportProvider toolMcpTransportProvider() { + return HttpServletStreamableServerTransportProvider.builder() + .mcpEndpoint("/mcp") + .build(); + } + + @Bean + public ServletRegistrationBean toolMcpServlet( + HttpServletStreamableServerTransportProvider transportProvider) { + // "/mcp/*"로 매핑하면 BusinessToolController의 "/mcp/api/v1/tools/local" 까지 가로채게 되므로, + // 정확히 MCP 통신에 사용되는 "/mcp" 와 "/mcp/message" 두 개만 매핑합니다. + return new ServletRegistrationBean<>(transportProvider, "/mcp", "/mcp/message"); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolPodMcpToolSynchronizer.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolPodMcpToolSynchronizer.java new file mode 100644 index 00000000..9d327e5a --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolPodMcpToolSynchronizer.java @@ -0,0 +1,93 @@ +package io.shinhanlife.dap.lib.mcp; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.server.McpServerFeatures; +import io.modelcontextprotocol.server.McpSyncServer; +import io.modelcontextprotocol.spec.McpSchema; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; + +/** Registers the Tool Pod's existing annotated tools with its MCP SDK server. */ +@Component +@ConditionalOnBean(McpToolExecutionService.class) +public class ToolPodMcpToolSynchronizer { + private final McpSyncServer mcpServer; + private final ToolRegistryHeartbeatSender heartbeatSender; + private final McpToolExecutionService toolExecutionService; + private final ObjectMapper objectMapper; + + public ToolPodMcpToolSynchronizer(McpSyncServer mcpServer, ToolRegistryHeartbeatSender heartbeatSender, + McpToolExecutionService toolExecutionService, ObjectMapper objectMapper) { + this.mcpServer = mcpServer; + this.heartbeatSender = heartbeatSender; + this.toolExecutionService = toolExecutionService; + this.objectMapper = objectMapper; + } + + @EventListener(ApplicationReadyEvent.class) + public void registerLocalTools() { + heartbeatSender.getAllScannedTools().stream() + .filter(tool -> Boolean.TRUE.equals(tool.getVisible())) + .forEach(tool -> mcpServer.addTool(specification(tool))); + } + + private McpServerFeatures.SyncToolSpecification specification(ToolMetadata tool) { + McpSchema.Tool mcpTool = McpSchema.Tool.builder() + .name(tool.getName()) + .description(tool.getDescription() == null || tool.getDescription().isBlank() ? tool.getName() + " Tool" : tool.getDescription()) + .inputSchema(toJsonSchema(tool.getParametersSchema())) + .annotations(new McpSchema.ToolAnnotations( + tool.getDisplayName(), + tool.getReadOnlyHint(), + tool.getDestructiveHint(), + tool.getIdempotentHint(), + tool.getOpenWorldHint(), + null)) + .build(); + return McpServerFeatures.SyncToolSpecification.builder().tool(mcpTool) + .callHandler((context, request) -> invoke(tool.getName(), McpRequestHeaderContext.current(), request.arguments())).build(); + } + + private McpSchema.CallToolResult invoke(String toolName, McpRequestHeaders requestHeaders, + Map arguments) { + ToolExecutionResult result = toolExecutionService.execute(toolName, requestHeaders, arguments); + boolean failed = !result.isSuccess(); + Object body = result.body(); + try { + return McpSchema.CallToolResult.builder().addTextContent(objectMapper.writeValueAsString(body)) + .structuredContent(body).isError(failed).build(); + } catch (Exception error) { + return McpSchema.CallToolResult.builder().addTextContent(String.valueOf(body)).isError(failed).build(); + } + } + + private Map emptySchema() { + Map schema = new LinkedHashMap<>(); + schema.put("type", "object"); + schema.put("properties", Map.of()); + schema.put("additionalProperties", false); + return schema; + } + @SuppressWarnings("unchecked") + private McpSchema.JsonSchema toJsonSchema(Map source) { + Map schema = source == null ? emptySchema() : source; + return new McpSchema.JsonSchema( + String.valueOf(schema.getOrDefault("type", "object")), + schema.get("properties") instanceof Map properties + ? (Map) properties : Map.of(), + schema.get("required") instanceof List required + ? (List) required : List.of(), + schema.get("additionalProperties") instanceof Boolean additionalProperties + ? additionalProperties : Boolean.TRUE, + schema.get("$defs") instanceof Map defs ? (Map) defs : Map.of(), + schema.get("definitions") instanceof Map definitions + ? (Map) definitions : Map.of()); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java new file mode 100644 index 00000000..a2b6b58a --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSender.java @@ -0,0 +1,199 @@ +package io.shinhanlife.dap.lib.mcp; + + +/** + * @package io.shinhanlife.dap.mcc.service + * @className ToolRegistryHeartbeatSender + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; +import io.shinhanlife.dap.lib.config.McpProperties; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import io.shinhanlife.dap.lib.util.ToolSchemaResolver; +import jakarta.annotation.PostConstruct; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.http.ResponseEntity; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.util.ClassUtils; +import org.springframework.web.client.RestClient; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; + +@Slf4j +@Component +@Configuration +@EnableScheduling +@RequiredArgsConstructor +@ConditionalOnBean(McpToolExecutionService.class) +public class ToolRegistryHeartbeatSender { + + private final ApplicationContext applicationContext; + private final ObjectMapper objectMapper; + private final McpProperties mcpProperties; + private final RestClient restClient = RestClient.create(); + private final ToolSchemaResolver toolSchemaResolver; + + @Value("${axhub.gateway.url:http://localhost:8081}") + private String gatewayUrl; + + @Value("${axhub.tool.url:http://localhost:8080}") + private String podUrl; + + private List registeredTools = new ArrayList<>(); + + @Getter + private List allScannedTools = new ArrayList<>(); + + @PostConstruct + public void init() { + log.info(" [HeartbeatSender] 초기화 시작. Gateway URL: {}, Pod URL: {}", gatewayUrl, podUrl); + scanAndBuildMetadata(); + } + + private void scanAndBuildMetadata() { + Map allBeans = applicationContext.getBeansOfType(Object.class); + for (Object bean : allBeans.values()) { + Class targetClass = AopUtils.getTargetClass(bean); + + for (Method method : targetClass.getDeclaredMethods()) { + McpTool functionAnnotation = AnnotationUtils.findAnnotation(method, McpTool.class); + ToolHint hintAnnotation = AnnotationUtils.findAnnotation(method, ToolHint.class); + + if (functionAnnotation != null) { + String baseName = functionAnnotation.name(); + String rawSubToolName = functionAnnotation.name(); + String subToolName = mcpProperties.getNamespace() != null && !mcpProperties.getNamespace().isEmpty() + ? mcpProperties.getNamespace() + "_" + rawSubToolName + : rawSubToolName; + // @ToolHint(register = false)인 Tool은 메타데이터 조회에는 남기되, + // Gateway 등록 및 heartbeat 전송 대상에서는 제외합니다. + // ToolHint가 없는 기존 Tool은 이전 동작과 동일하게 등록합니다. + boolean isRegister = true; // Force register=true + if (!isRegister) { + log.info(" [HeartbeatSender] '{}' Tool is excluded from Gateway registration because register=false. (tool name: {})", + baseName, subToolName); + } + + ToolMetadata meta = new ToolMetadata(); + meta.setUid(UUID.nameUUIDFromBytes(subToolName.getBytes()).toString()); + String displayName = functionAnnotation.title().isEmpty() ? functionAnnotation.name() : functionAnnotation.title(); + meta.setDisplayName(displayName); + meta.setName(subToolName); + meta.setSemver("1.0.0"); + meta.setTimeoutMillis(5000L); + meta.setEnabled(true); + meta.setDescription(functionAnnotation.description()); + meta.setCategoryKey(hintAnnotation == null || hintAnnotation.categoryKey().isBlank() + ? "common" : hintAnnotation.categoryKey()); + meta.setIntegrationType("REST"); + meta.setMciServiceId(hintAnnotation != null ? hintAnnotation.mappingId() : ""); + meta.setPodUrl(podUrl); + meta.setEndpoint(podUrl.replaceAll("/+$", "") + "/mcp/" + subToolName); + + meta.setVisible(true); + meta.setIsRegistered(isRegister); + meta.setRequiresApproval(hintAnnotation != null && hintAnnotation.requiresApproval()); + + // extract standard hints from @McpTool.annotations() + McpTool.McpAnnotations ann = functionAnnotation.annotations(); + if (ann != null) { + meta.setReadOnlyHint(ann.readOnlyHint()); + meta.setDestructiveHint(ann.destructiveHint()); + meta.setIdempotentHint(ann.idempotentHint()); + meta.setOpenWorldHint(ann.openWorldHint()); + } else { + meta.setReadOnlyHint(false); + meta.setDestructiveHint(true); + meta.setIdempotentHint(false); + meta.setOpenWorldHint(true); + } + + Map prompts = new HashMap<>(); + meta.setActionPrompts(prompts); + + if (method.getParameterCount() > 0) { + try { + Class paramType = method.getParameterTypes()[0]; + // TODO: ToolSchemaResolver may need to be updated to take McpTool instead of McpFunction + Map finalSchema = toolSchemaResolver.resolve(functionAnnotation, hintAnnotation, paramType); + meta.setParametersSchema(finalSchema); + } catch (Exception e) { + log.error("Failed to generate schema for {}", subToolName, e); + } + } + + if (isRegister) { + registeredTools.add(meta); + } + allScannedTools.add(meta); + log.info(" [HeartbeatSender] 도구 메타데이터 생성: {} (isRegistered: {})", meta.getUid(), isRegister); + } + } + } + } + + @Scheduled(fixedRate = 30000) + public void sendHeartbeats() { + if (registeredTools.isEmpty()) return; + + for (ToolMetadata tool : registeredTools) { + try { + ResponseEntity response = restClient.post() + .uri(gatewayUrl + "/mcp/api/v1/registry/heartbeat") + .header("Content-Type", "application/json") + + .body(tool.getUid()) + .retrieve() + .toEntity(String.class); + + if (response.getStatusCode().is2xxSuccessful()) { + log.info(" [HeartbeatSender] 하트비트 전송 성공: {}", tool.getUid()); + } + } catch (Exception e) { + log.warn(" [HeartbeatSender] 하트비트 전송 실패 ({}): {}. 재등록을 시도합니다.", tool.getUid(), e.getMessage()); + registerTool(tool); + } + } + } + + private void registerTool(ToolMetadata tool) { + try { + restClient.post() + .uri(gatewayUrl + "/mcp/api/v1/registry/register") + .header("Content-Type", "application/json") + + .body(tool) + .retrieve() + .toBodilessEntity(); + log.info(" [HeartbeatSender] 툴 재등록 성공: {}", tool.getUid()); + } catch (Exception ex) { + log.error(" [HeartbeatSender] 툴 등록 실패: {}", ex.getMessage()); + } + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/CacheConfig.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/CacheConfig.java new file mode 100644 index 00000000..9a313bf7 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/CacheConfig.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.lib.mcp.config; + +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @package io.shinhanlife.dap.lib.mcp.config + * @className CacheConfig + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Configuration +@EnableCaching +public class CacheConfig { + + // 스프링이 캐시를 관리할 기본 저장소를 빈(Bean)으로 등록합니다. + @Bean + public CacheManager cacheManager() { + return new ConcurrentMapCacheManager("eimsData"); // 아까 설정한 캐시 이름 등록 + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/JacksonConfig.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/JacksonConfig.java new file mode 100644 index 00000000..1df9b21f --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/JacksonConfig.java @@ -0,0 +1,41 @@ +package io.shinhanlife.dap.lib.mcp.config; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; + +/** + * @package io.shinhanlife.dap.lib.mcp.config + * @className JacksonConfig + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Configuration +public class JacksonConfig { + + // 1. JSON 변환기(ObjectMapper)를 스프링 Bean으로 등록 + @Bean + @Primary + public ObjectMapper jsonMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return mapper; + } + + // 2. XML 변환기(XmlMapper)를 스프링 Bean으로 등록 + @Bean + public XmlMapper xmlMapper() { + return new XmlMapper(); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/KafkaLocalConfig.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/KafkaLocalConfig.java new file mode 100644 index 00000000..f9bda4e0 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/KafkaLocalConfig.java @@ -0,0 +1,58 @@ +package io.shinhanlife.dap.lib.mcp.config; + +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.core.ProducerFactory; + +import org.springframework.beans.factory.annotation.Value; +import java.util.HashMap; +import java.util.Map; + +/** + * @package io.shinhanlife.dap.lib.mcp.config + * @className KafkaLocalConfig + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Configuration +public class KafkaLocalConfig { + + @Value("${spring.kafka.bootstrap-servers:localhost:9092}") + private String bootstrapServers; + + // 1. 카프카 전송 공장(Factory) 세팅 + @Bean + public ProducerFactory producerFactory() { + Map configProps = new HashMap<>(); + // 가짜 로컬 주소 혹은 환경변수 세팅 + configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + // 데이터를 카프카로 보낼 때 문자열(String) 형태로 변환하겠다는 규칙 + configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + + // 3초 만에 빠른 실패 처리 (로컬 무한 대기 방지) + configProps.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 3000); + // 재접속 주기를 10초로 설정 (콘솔 로그 도배 방지) + configProps.put(ProducerConfig.RECONNECT_BACKOFF_MAX_MS_CONFIG, 10000); + + return new DefaultKafkaProducerFactory<>(configProps); + } + + // 2. EaiEimsSender가 애타게 찾던 KafkaTemplate을 스프링 Bean으로 등록! + @Bean + public KafkaTemplate kafkaTemplate() { + return new KafkaTemplate<>(producerFactory()); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/SwaggerConfig.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/SwaggerConfig.java new file mode 100644 index 00000000..5c2e22c9 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/SwaggerConfig.java @@ -0,0 +1,47 @@ +package io.shinhanlife.dap.lib.mcp.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @package io.shinhanlife.dap.lib.mcp.config + * @className SwaggerConfig + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI() + .info(new Info() + .title("Shinhan MCP Gateway API 명세서") + .version("v1.0") + .description("AI Agent와 신한라이프 내부망(EIMS/EAI)을 연결하는 Adapter Gateway API 문서입니다.")) + .addServersItem(new io.swagger.v3.oas.models.servers.Server().url("http://localhost:8080").description("Adapter Pod (8080)")) + .addServersItem(new io.swagger.v3.oas.models.servers.Server().url("http://localhost:8081").description("Gateway Pod (8081)")) + // 전역적으로 X-API-KEY 보안 설정을 Swagger UI에 추가합니다. + .addSecurityItem(new SecurityRequirement().addList("X-API-KEY")) + .components(new Components() + .addSecuritySchemes("X-API-KEY", + new SecurityScheme() + .name("X-API-KEY") + .type(SecurityScheme.Type.APIKEY) + .in(SecurityScheme.In.HEADER) + .description("헤더에 API Key를 입력해주세요. "))); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/WebConfig.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/WebConfig.java new file mode 100644 index 00000000..9e3cfc8b --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/config/WebConfig.java @@ -0,0 +1,54 @@ +package io.shinhanlife.dap.lib.mcp.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.config.annotation.CorsRegistry; + +import io.shinhanlife.dap.lib.mcp.security.ApiKeyInterceptor; + +/** + * @package io.shinhanlife.dap.lib.mcp.config + * @className WebConfig + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + // 1. 우리가 만든 인터셉터를 주입받습니다. + private final ApiKeyInterceptor apiKeyInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 2. 인터셉터 등록 및 검사할 URL 패턴 지정 + registry.addInterceptor(apiKeyInterceptor) + .addPathPatterns("/rpc/**", "/mcp/api/v1/**") // /rpc/, /mcp/api/v1/ 로 시작하는 모든 API는 API Key 검사 수행! + .excludePathPatterns( + "/test/**", "/health", "/error", "/mcp/api/v1/admin/**", + "/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**", // Swagger UI 경로는 인증 제외 + "/mcp/api/v1/tools/docs/markdown", "/favicon.ico", "/mcp/api/v1/tools/list" + ); + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + // Swagger UI(8080)에서 Gateway(8081)로 API 호출 시 발생하는 CORS 에러 해결 + registry.addMapping("/**") + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .exposedHeaders("Mcp-Session-Id") + .allowCredentials(true); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/exception/GlobalExceptionHandler.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..f5700193 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/exception/GlobalExceptionHandler.java @@ -0,0 +1,60 @@ +package io.shinhanlife.dap.lib.mcp.exception; + + +/** + * @package io.shinhanlife.dap.lib.mcp.exception + * @className GlobalExceptionHandler + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +import io.shinhanlife.dap.lib.adapter.dto.ErrorDetail; +import io.shinhanlife.dap.lib.adapter.dto.JsonRpcResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.resource.NoResourceFoundException; + +@Slf4j +@RestControllerAdvice // 이 어노테이션이 전역 적용의 핵심입니다! +public class GlobalExceptionHandler { + + @ExceptionHandler(NoResourceFoundException.class) + public ResponseEntity handleNoResourceFound(NoResourceFoundException e) { + log.warn(" [Gateway Not Found] 요청하신 리소스를 찾을 수 없습니다: {}", e.getResourcePath()); + return ResponseEntity.notFound().build(); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgument(IllegalArgumentException e) { + log.warn(" [Gateway Bad Request] 잘못된 요청: {}", e.getMessage()); + return buildErrorResponse(-32602, "Invalid params: " + e.getMessage()); + } + + @ExceptionHandler(RuntimeException.class) + public ResponseEntity handleRuntime(RuntimeException e) { + log.error(" [Gateway Internal Error] 시스템 장애: {}", e.getMessage(), e); + return buildErrorResponse(-32603, "Internal error: " + e.getMessage()); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleAllException(Exception e) { + log.error(" [Gateway Fatal Error] 치명적 오류 발생", e); + return buildErrorResponse(-32000, "Server error: 시스템 관리자에게 문의하세요."); + } + + private ResponseEntity buildErrorResponse(int code, String message) { + JsonRpcResponse response = new JsonRpcResponse(); + response.setError(new ErrorDetail(code, message)); + + return ResponseEntity.ok(response); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/filter/MdcLoggingFilter.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/filter/MdcLoggingFilter.java new file mode 100644 index 00000000..69a76ff1 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/filter/MdcLoggingFilter.java @@ -0,0 +1,56 @@ +package io.shinhanlife.dap.lib.mcp.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.UUID; + +/** + * @package io.shinhanlife.dap.lib.mcp.filter + * @className MdcLoggingFilter + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Component +public class MdcLoggingFilter extends OncePerRequestFilter { + + private static final String TRACE_ID_HEADER = "X-Trace-Id"; + private static final String MDC_KEY = "traceId"; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + // 클라이언트가 보낸 Trace ID가 있으면 쓰고, 없으면 새로 생성 + String traceId = request.getHeader(TRACE_ID_HEADER); + if (traceId == null || traceId.isEmpty()) { + // 간결하게 8자리 UUID만 사용 + traceId = UUID.randomUUID().toString().substring(0, 8); + } + + // 로깅 컨텍스트에 고유 ID 저장 + MDC.put(MDC_KEY, traceId); + + try { + // 이 요청이 처리되는 동안 찍히는 모든 log.info, log.error에 traceId가 자동으로 붙습니다. + filterChain.doFilter(request, response); + } finally { + // 메모리 누수 방지를 위해 요청이 끝나면 반드시 비워줍니다. + MDC.clear(); + } + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/ApiKeyInterceptor.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/ApiKeyInterceptor.java new file mode 100644 index 00000000..89fb3db5 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/ApiKeyInterceptor.java @@ -0,0 +1,80 @@ +package io.shinhanlife.dap.lib.mcp.security; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.util.Map; + +/** + * @package io.shinhanlife.dap.lib.mcp.security + * @className ApiKeyInterceptor + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class ApiKeyInterceptor implements HandlerInterceptor { + + // 1. 다중 테넌트 API Key 목록이 담긴 프로퍼티 객체를 주입받습니다. + private final SecurityProperties securityProperties; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + return true; + } + + String apiKey = request.getHeader("X-API-KEY"); + Map validApiKeys = securityProperties.getApiKeys(); + + // 2. 만약 프로퍼티에 API Key가 하나도 설정되어 있지 않다면 (개발/로컬 환경 등) 인증 없이 통과시킵니다. + if (validApiKeys == null || validApiKeys.isEmpty()) { + MDC.put("tenantId", "anonymous"); + request.setAttribute("tenantId", "anonymous"); + log.debug(" [보안 패스] 등록된 API Key 없음 - 익명 사용자(anonymous)로 통과"); + return true; + } + + // 3. 헤더로 들어온 API Key가 우리가 발급해준 목록(Map)에 존재하는지 확인합니다. + if (apiKey == null || !validApiKeys.containsKey(apiKey)) { + log.warn(" [보안 차단] 유효하지 않은 API Key 접근 시도 - IP: {}", request.getRemoteAddr()); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid API Key"); + return false; // 컨트롤러로 넘어가지 않음 + } + + // 4. 유효하다면 해당 키에 맵핑된 Tenant ID(식별자)를 가져옵니다. (ex. mcp-client-1) + String tenantId = validApiKeys.get(apiKey); + + // 4. 추출한 Tenant ID를 현재 스레드의 로깅 컨텍스트(MDC)에 저장합니다. + // 이렇게 하면 이 요청이 끝날 때까지 찍히는 모든 로그에 어떤 테넌트가 호출했는지 자동으로 기록됩니다. + MDC.put("tenantId", tenantId); + + // 5. 필요시 컨트롤러 로직에서 사용할 수 있도록 Request 속성에도 담아줍니다. + request.setAttribute("tenantId", tenantId); + + log.debug(" [보안 통과] API Key 인증 성공 - 접속 테넌트: {}", tenantId); + + return true; // 인증 통과! 컨트롤러로 진행 + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + // 6. 메모리 누수를 방지하기 위해 요청 처리가 완전히 끝나면 MDC에서 테넌트 정보를 지워줍니다. + MDC.remove("tenantId"); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/SecurityProperties.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/SecurityProperties.java new file mode 100644 index 00000000..381164e6 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/mcp/security/SecurityProperties.java @@ -0,0 +1,40 @@ +package io.shinhanlife.dap.lib.mcp.security; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * [다중 테넌트 설정 매핑 클래스] + * application-local.properties 파일에 정의된 mcp.security.api-keys.* 설정들을 + * Map 자료구조로 자동 바인딩(주입) 받기 위한 설정 클래스입니다. + */ +/** + * @package io.shinhanlife.dap.lib.mcp.security + * @className SecurityProperties + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Data +@Component +@ConfigurationProperties(prefix = "mcp.security") +public class SecurityProperties { + // API Key를 Key로, Tenant ID를 Value로 가지는 맵 + private Map apiKeys = new HashMap<>(); + + // Tenant ID를 Key로, 허용된 도메인 그룹 목록을 Value로 가지는 맵 (ex. mcp-client-1 -> [CUSTOMER, COMMON]) + // 만약 "ALL" 이 포함되어 있다면 모든 도메인에 접근 허용 + private Map> tenantDomains = new HashMap<>(); +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/session/dto/SessionDto.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/session/dto/SessionDto.java new file mode 100644 index 00000000..f6521174 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/session/dto/SessionDto.java @@ -0,0 +1,90 @@ +package io.shinhanlife.dap.lib.session.dto; + +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +/** + * @package io.shinhanlife.dap.lib.session.dto + * @className SessionDto + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class SessionDto { + + /* 인사번호 */ + private String prafNo; + /* 인사명 */ + private String prafNm; + /* 조직번호 */ + private String ognzNo; + /* 조직번호 */ + private String ognzNm; + /* 이메일주소 */ + private String addre; + /* 인사직무코드 */ + private String prafOfduCd; + /* 인사직무명 */ + private String prafOfduNm; + /* 인사직급코드 */ + private String prafOfleCd; + /* 인사직급명 */ + private String prafOfleNm; + /* 인사직책코드 */ + private String prafDutyCd; + /* 인사직책명 */ + private String prafDutyNm; + + private List roleNoList; + private List roleNmList; + private List tgtrPrafNoList; + private List tgtrOgnzNoList; + + // 추가된 LICO 연동 공통 헤더 필수 필드들 + private String strYmd; + private String brafNo; + private String psmrAsrtCd; + private String sbsnRulpAsrtCd; + private String bsduCd; + private String bsquCd; + private String ognzAsrtCd; + private String ognzLeveCd; + private String prgrId; + + + // 유틸성 + private String loginDtm; // 로그인일시 + private String isManager; // 관리자여부 + + public void setLoginDtm() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"); + this.loginDtm = LocalDateTime.now().format(formatter); + } + + public void setIsManager(String isManager) { + // TODO 역할 필터링 후 관리자 여부 체크 + this.isManager = "Y"; + } + + + +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/JsonSchemaGenerator.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/JsonSchemaGenerator.java new file mode 100644 index 00000000..626bbdf2 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/JsonSchemaGenerator.java @@ -0,0 +1,205 @@ +package io.shinhanlife.dap.lib.util; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import org.springaicommunity.mcp.annotation.McpToolParam; +import io.swagger.v3.oas.annotations.media.Schema; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +/** + * @package io.shinhanlife.dap.lib.util + * @className JsonSchemaGenerator + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +public class JsonSchemaGenerator { + + /** + * Java DTO 클래스를 분석하여 MCP 규격의 완전한 JSON Schema를 생성합니다. + */ + public static Map generateSchema(Class clazz) { + return generateSchema(clazz, new HashSet<>()); + } + + private static Map generateSchema(Class clazz, Set> visiting) { + Map schema = new HashMap<>(); + schema.put("type", "object"); + schema.put("additionalProperties", false); + if (!visiting.add(clazz)) { + return schema; + } + + Map properties = new HashMap<>(); + List requiredList = new ArrayList<>(); + + for (Field field : clazz.getDeclaredFields()) { + Map fieldSchema = createFieldSchema(field, visiting); + + // 1. 타입 매핑 + + // 2. 어노테이션 기반 설명 추출 + McpToolParam paramAnnotation = field.getAnnotation(McpToolParam.class); + JsonPropertyDescription descAnnotation = field.getAnnotation(JsonPropertyDescription.class); + if (paramAnnotation != null && !paramAnnotation.description().isEmpty()) { + fieldSchema.put("description", paramAnnotation.description()); + } else if (descAnnotation != null && !descAnnotation.value().isEmpty()) { + fieldSchema.put("description", descAnnotation.value()); + } else { + fieldSchema.put("description", field.getName()); // 기본값 + } + + // 3. 필수 여부 판단 + JsonProperty jsonProp = field.getAnnotation(JsonProperty.class); + if ((jsonProp != null && jsonProp.required()) || (paramAnnotation != null && paramAnnotation.required())) { + requiredList.add(field.getName()); + } + + Schema schemaAnnotation = field.getAnnotation(Schema.class); + if (schemaAnnotation != null) { + if (!schemaAnnotation.description().isEmpty() && !fieldSchema.containsKey("description")) { + fieldSchema.put("description", schemaAnnotation.description()); + } + if ((schemaAnnotation.required() || schemaAnnotation.requiredMode() == Schema.RequiredMode.REQUIRED) + && !requiredList.contains(field.getName())) { + requiredList.add(field.getName()); + } + if (!schemaAnnotation.pattern().isEmpty()) { + fieldSchema.put("pattern", schemaAnnotation.pattern()); + } + if (!schemaAnnotation.minimum().isEmpty()) { + try { + fieldSchema.put("minimum", Long.valueOf(schemaAnnotation.minimum())); + } catch (NumberFormatException ignored) {} + } + if (!schemaAnnotation.maximum().isEmpty()) { + try { + fieldSchema.put("maximum", Long.valueOf(schemaAnnotation.maximum())); + } catch (NumberFormatException ignored) {} + } + if (schemaAnnotation.minLength() > 0) { + fieldSchema.put("minLength", schemaAnnotation.minLength()); + } + if (schemaAnnotation.maxLength() > 0 && schemaAnnotation.maxLength() != Integer.MAX_VALUE) { + fieldSchema.put("maxLength", schemaAnnotation.maxLength()); + } + if (schemaAnnotation.allowableValues().length > 0 && !schemaAnnotation.allowableValues()[0].isEmpty()) { + fieldSchema.put("enum", List.of(schemaAnnotation.allowableValues())); + } + if (!schemaAnnotation.format().isEmpty()) { + fieldSchema.put("format", schemaAnnotation.format()); + } + if (!schemaAnnotation.defaultValue().isEmpty()) { + fieldSchema.put("default", coerceDefaultValue(schemaAnnotation.defaultValue(), field.getType())); + } + if (!schemaAnnotation.example().isEmpty()) { + fieldSchema.put("examples", List.of(schemaAnnotation.example())); + } + if (schemaAnnotation.nullable()) { + Map nonNullSchema = new HashMap<>(fieldSchema); + fieldSchema = new HashMap<>(); + fieldSchema.put("anyOf", List.of( + nonNullSchema, + Map.of("type", "null") + )); + } + } + properties.put(field.getName(), fieldSchema); + } + + schema.put("properties", properties); + if (!requiredList.isEmpty()) { + schema.put("required", requiredList); + } + + // anyOf removed + + visiting.remove(clazz); + return schema; + } + + + private static Object coerceDefaultValue(String value, Class fieldType) { + try { + if (fieldType == Integer.class || fieldType == int.class + || fieldType == Long.class || fieldType == long.class + || fieldType == Short.class || fieldType == short.class + || fieldType == Byte.class || fieldType == byte.class) { + return Long.valueOf(value); + } + if (fieldType == Double.class || fieldType == double.class + || fieldType == Float.class || fieldType == float.class) { + return Double.valueOf(value); + } + if (fieldType == Boolean.class || fieldType == boolean.class) { + return Boolean.valueOf(value); + } + return value; + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid MCP default value: " + value, e); + } + } + private static Map createFieldSchema(Field field, Set> visiting) { + Class fieldType = field.getType(); + if (isSimpleType(fieldType)) { + return new HashMap<>(Map.of("type", mapJavaTypeToJsonType(fieldType))); + } + if (List.class.isAssignableFrom(fieldType)) { + Map fieldSchema = new HashMap<>(); + fieldSchema.put("type", "array"); + fieldSchema.put("items", generateItemsSchema(field, visiting)); + return fieldSchema; + } + return generateSchema(fieldType, visiting); + } + + private static Map generateItemsSchema(Field field, Set> visiting) { + Type genericType = field.getGenericType(); + if (genericType instanceof ParameterizedType parameterizedType) { + Type itemType = parameterizedType.getActualTypeArguments()[0]; + if (itemType instanceof Class itemClass) { + if (isSimpleType(itemClass)) { + return new HashMap<>(Map.of("type", mapJavaTypeToJsonType(itemClass))); + } + return generateSchema(itemClass, visiting); + } + } + return new HashMap<>(Map.of("type", "object")); + } + + private static boolean isSimpleType(Class clazz) { + return clazz == String.class + || clazz == Integer.class || clazz == int.class + || clazz == Long.class || clazz == long.class + || clazz == Double.class || clazz == double.class + || clazz == Float.class || clazz == float.class + || clazz == Boolean.class || clazz == boolean.class; + } + + private static String mapJavaTypeToJsonType(Class clazz) { + if (clazz == String.class) return "string"; + if (clazz == Integer.class || clazz == int.class) return "integer"; + if (clazz == Long.class || clazz == long.class) return "integer"; + if (clazz == Double.class || clazz == double.class) return "number"; + if (clazz == Float.class || clazz == float.class) return "number"; + if (clazz == Boolean.class || clazz == boolean.class) return "boolean"; + if (List.class.isAssignableFrom(clazz)) return "array"; + return "object"; + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/PodScaffolder.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/PodScaffolder.java new file mode 100644 index 00000000..09bf3a8e --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/PodScaffolder.java @@ -0,0 +1,372 @@ +package io.shinhanlife.dap.lib.util; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Scanner; + +public class PodScaffolder { + + public static void main(String[] args) throws IOException { + Scanner scanner = new Scanner(System.in); + + System.out.println("========================================="); + System.out.println(" MCP Tool Pod Scaffolder (Java CLI) "); + System.out.println("=========================================\n"); + + String rawModuleName = getOrAsk(args, 0, scanner, "1. 생성할 모듈(Pod) 이름 (예: payment 또는 dap-was-payment): "); + String moduleName = rawModuleName.startsWith("dap-was-") ? rawModuleName : "dap-was-" + rawModuleName; + String portStr = getOrAsk(args, 1, scanner, "2. 사용할 포트 번호 (예: 8085): "); + String shortName = moduleName.replace("dap-was-", "").replace("-", ""); + + String defaultAuthor = System.getProperty("user.name"); + String defaultDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd")); + + String author = getOrAsk(args, 2, scanner, "3. 작성자 (엔터 입력 시 '" + defaultAuthor + "'): "); + if (author.trim().isEmpty()) author = defaultAuthor; + String createDate = getOrAsk(args, 3, scanner, "4. 작성일 (엔터 입력 시 '" + defaultDate + "'): "); + if (createDate.trim().isEmpty()) createDate = defaultDate; + + String result = scaffoldPod(moduleName, portStr, shortName, author, createDate); + System.out.println(result); + } + + private static String getOrAsk(String[] args, int index, Scanner scanner, String prompt) { + if (args.length > index) { + return args[index]; + } + System.out.print(prompt); + return scanner.nextLine().trim(); + } + + public static String scaffoldPod(String moduleName, String portStr, String shortName, String author, String createDate) throws IOException { + String envSourceDir = System.getenv("AXHUB_SOURCE_DIR"); + Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get("."); + return scaffoldPod(rootDir, moduleName, portStr, shortName, author, createDate); + } + + static String scaffoldPod(Path rootDir, String moduleName, String portStr, String shortName, + String author, String createDate) throws IOException { + Path modulePath = rootDir.resolve(Paths.get(moduleName)); + if (Files.exists(modulePath)) { + return "[오류] 이미 존재하는 모듈입니다: " + moduleName; + } + + StringBuilder log = new StringBuilder(); + log.append("[1/6] 모듈 디렉터리 생성 중...\n"); + Files.createDirectories(modulePath); + + log.append("[2/6] build.gradle 생성 중...\n"); + String buildGradle = """ + plugins { + // Spring Boot 3.5.11 version is managed by the root build.gradle. + id 'org.springframework.boot' + } + + dependencies { + // Shared MCP, Glow integration, validation, logging, Lombok and MapStruct configuration. + implementation project(':dap-was-lib') + } + """; + Files.writeString(modulePath.resolve("build.gradle"), buildGradle); + + log.append("[3/6] Dockerfile 생성 중...\n"); + String dockerfile = """ + FROM eclipse-temurin:21-jre-alpine + WORKDIR /app + RUN apk add --no-cache tzdata + ENV TZ=Asia/Seoul + COPY %s/build/libs/*-SNAPSHOT.jar app.jar + EXPOSE %s + ENTRYPOINT ["java", "-jar", "app.jar"] + """.formatted(moduleName, portStr); + Files.writeString(modulePath.resolve("Dockerfile"), dockerfile); + + log.append("[4/6] Application 클래스 및 설정 파일 생성 중...\n"); + Path srcPath = modulePath.resolve("src/main/java/io/shinhanlife/dap/mcc/" + shortName); + Files.createDirectories(srcPath); + + String appClass = """ + package io.shinhanlife.dap.mcc.%s; + + import org.springframework.boot.SpringApplication; + import org.springframework.boot.autoconfigure.SpringBootApplication; + import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + import org.springframework.cache.annotation.EnableCaching; + + /** + * @package io.shinhanlife.dap.mcc.%s + * @className DapWas%sApplication + * @description AX HUB 시스템 처리 클래스 + * @author %s + * @create %s + *
    +             * ---------- 개정이력 ----------
    +             * 수정일      수정자    수정내용
    +             * ---------- -------- ---------------------------
    +             * %s  %s    최초생성
    +             * 
    +             * 
    + */ + @SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) + @ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) + @EnableCaching + public class DapWas%sApplication { + public static void main(String[] args) { + SpringApplication.run(DapWas%sApplication.class, args); + } + } + """.formatted(shortName, shortName, capitalize(shortName), author, createDate, createDate, author, capitalize(shortName), capitalize(shortName)); + Files.writeString(srcPath.resolve("DapWas" + capitalize(shortName) + "Application.java"), appClass); + + Path resPath = modulePath.resolve("src/main/resources"); + Files.createDirectories(resPath); + String applicationYml = """ + server: + port: %s + spring: + application: + name: %s + profiles: + active: local + logging: + level: + org.apache.kafka: ERROR + mcp: + namespace: "" + manifest: + bundle-id: %s + name-prefix: "" + security: + tenant-domains: + TESTER-DEV: ALL + """.formatted(portStr, moduleName, moduleName.replace("dap-", "")); + Files.writeString(resPath.resolve("application.yml"), applicationYml); + + String applicationLocalYml = """ + # Local 환경 전용 설정 (H2 메모리 DB 등) + spring: + config: + activate: + on-profile: local + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-local.yml + datasource: + url: jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1; + driverClassName: com.p6spy.engine.spy.P6SpyDriver + username: sa + password: password + h2: + console: + enabled: true + + eims: + http: + url: http://localhost:${server.port}/api/gateway + tcp: + host: 127.0.0.1 + port: 8090 + timeout: 5000 + jsp: + form: + url: http://localhost:${server.port}/mock/jsp-form + json: + url: http://localhost:${server.port}/mock/jsp-json + mci: + url: http://localhost:${server.port}/api/mock/esb/api + mcistring: + url: http://localhost:${server.port}/api/mock/esb/string + + mcp: + security: + tenant-domains: + mcp-client-1: CUSTOMER,COMMON + mcp-client-2: ALL + + axhub: + gateway: + url: http://localhost:8081 + tool: + url: ${AXHUB_TOOL_URL:http://localhost:${server.port}} + """; + Files.writeString(resPath.resolve("application-local.yml"), applicationLocalYml); + + String applicationDevYml = """ + # OCI 클라우드 환경 전용 설정 + server: + port: ${PORT:%s} + + spring: + config: + activate: + on-profile: dev + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-dev.yml + + axhub: + gateway: + url: https://axhubmcp.devjun.net + tool: + url: http://144.24.70.100:%s + + eims: + http: + url: http://localhost:${server.port}/api/gateway + tcp: + host: 127.0.0.1 + port: 8090 + timeout: 5000 + jsp: + form: + url: http://localhost:${server.port}/mock/jsp-form + json: + url: http://localhost:${server.port}/mock/jsp-json + mci: + url: http://localhost:${server.port}/api/mock/esb/api + mcistring: + url: http://localhost:${server.port}/api/mock/esb/string + + shinhan: + integration: + envrTypeCd: D + eai: + url: http://10.176.32.181 + internalMci: + url: http://10.176.32.173 + bancaMci: + url: http://10.176.32.117 + externalMci: + url: http://10.176.32.176 + """.formatted(portStr, portStr); + Files.writeString(resPath.resolve("application-dev.yml"), applicationDevYml); + + String applicationTestYml = """ + server: + port: ${PORT:%s} + + spring: + config: + activate: + on-profile: test + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-test.yml + + axhub: + gateway: + url: ${AXHUB_GATEWAY_URL} + tool: + url: ${AXHUB_TOOL_URL} + """.formatted(portStr); + Files.writeString(resPath.resolve("application-test.yml"), applicationTestYml); + + String applicationProdYml = """ + server: + port: ${PORT:%s} + + spring: + config: + activate: + on-profile: prod + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-prod.yml + + axhub: + gateway: + url: ${AXHUB_GATEWAY_URL} + tool: + url: ${AXHUB_TOOL_URL} + """.formatted(portStr); + Files.writeString(resPath.resolve("application-prod.yml"), applicationProdYml); + + String logbackXml = """ + + + + + + ${LOG_PATTERN} + + + + logs/%s.log + + logs/%s-%%d{yyyy-MM-dd}.log + 30 + + + ${LOG_PATTERN} + + + + + + + + + """.formatted(moduleName, moduleName); + Files.writeString(resPath.resolve("logback-spring.xml"), logbackXml); + + log.append("[5/6] settings.gradle 에 모듈 등록 중...\n"); + Path settingsPath = rootDir.resolve(Paths.get("settings.gradle")); + if (Files.exists(settingsPath)) { + String settings = Files.readString(settingsPath); + if (!settings.contains("include '" + moduleName + "'")) { + Files.writeString(settingsPath, System.lineSeparator() + "include '" + moduleName + "'" + System.lineSeparator(), StandardOpenOption.APPEND); + } + } + + log.append("[6/6] docker-compose.yml 에 서비스 추가 중...\n"); + Path dockerComposePath = rootDir.resolve(Paths.get("docker-compose.yml")); + if (Files.exists(dockerComposePath)) { + String compose = Files.readString(dockerComposePath); + String serviceName = moduleName.replace("dap-", ""); // e.g. tool-payment + if (!compose.contains(" " + serviceName + ":")) { + String newService = """ + %s: + build: + context: . + dockerfile: %s/Dockerfile + ports: + - "%s:%s" + depends_on: + - redis + environment: + - TZ=Asia/Seoul + - SPRING_REDIS_HOST=redis + - SPRING_REDIS_PORT=6379 + - SPRING_DATA_REDIS_PORT=6379 + - AXHUB_GATEWAY_URL=http://gateway:8081 + - AXHUB_TOOL_URL=http://%s:%s + - GLOW_COMMUNICATION_MCI_HOST=http://mci-mock + - GLOW_COMMUNICATION_MCI_PORT=8080 + - GLOW_COMMUNICATION_EXTMCI_HOST=http://mci-mock + - GLOW_COMMUNICATION_EXTMCI_PORT=8080 + - GLOW_COMMUNICATION_EAI_HOST=http://mci-mock + - GLOW_COMMUNICATION_EAI_PORT=8080 + """.formatted(serviceName, moduleName, portStr, portStr, serviceName, portStr); + Files.writeString(dockerComposePath, System.lineSeparator() + newService, StandardOpenOption.APPEND); + } + } + + log.append("\n=========================================\n"); + log.append(" Pod Scaffolding Complete! \n"); + log.append("=========================================\n"); + log.append("1. [새로운 모듈] ").append(moduleName).append(" 폴더가 생성되었습니다.\n"); + log.append("2. [ToolScaffolder]를 사용해 이 모듈 안에 툴을 추가하세요.\n"); + log.append("3. 실행 전 Gradle 동기화(Sync)를 한 번 진행해 주세요.\n"); + return log.toString(); + } + + private static String capitalize(String str) { + if (str == null || str.isEmpty()) return str; + return str.substring(0, 1).toUpperCase() + str.substring(1); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/SessionUtil.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/SessionUtil.java new file mode 100644 index 00000000..e155b981 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/SessionUtil.java @@ -0,0 +1,45 @@ +package io.shinhanlife.dap.lib.util; + +import io.shinhanlife.dap.lib.session.dto.SessionDto; +import jakarta.servlet.http.HttpSession; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * @package io.shinhanlife.dap.lib.util + * @className SessionUtil + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +public class SessionUtil { + + private static final String SESSION_KEY = "userInfo"; + + private SessionUtil() {} + + public static SessionDto getSession() { + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if (attributes == null) return null; + HttpSession session = attributes.getRequest().getSession(false); + if (session == null) return null; + return (SessionDto) session.getAttribute(SESSION_KEY); + } + + public static String getPrafNo() { + SessionDto session = getSession(); + return session != null ? session.getPrafNo() : null; + } + + public static String getOgnzNo() { + SessionDto session = getSession(); + return session != null ? session.getOgnzNo() : null; + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java new file mode 100644 index 00000000..d1658a26 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolScaffolder.java @@ -0,0 +1,1276 @@ +package io.shinhanlife.dap.lib.util; + + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.Scanner; + +/** + * MCP Tool 肄붾뱶瑜??먮룞 ?앹꽦(Scaffolding)?섎뒗 ?좏떥由ы떚 ?대옒?? + * + * [?ㅽ뻾 諛⑸쾿] + * 諛⑸쾿 1. IDE(IntelliJ ???먯꽌 吏곸젒 ?ㅽ뻾 (?€?뷀삎 紐⑤뱶 異붿쿇 狩? + * - ???대옒??ToolScaffolder.java)瑜??닿퀬 main 硫붿꽌?쒕? 吏곸젒 ?ㅽ뻾(Run)?⑸땲?? + * - 肄섏넄 李쎌뿉 ?⑤뒗 吏덈Ц??李⑤??€濡?媛믪쓣 ?낅젰?섍린留??섎㈃ ?뚯씪???앹꽦?⑸땲?? + * + * 諛⑸쾿 2. 而ㅻ㎤?쒕씪???곕????먯꽌 ?ㅽ뻾 (紐낅졊??湲곕컲) + * - 而댄뙆?? javac -encoding UTF-8 dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/util/ToolScaffolder.java + * - ?ㅽ뻾: java -cp dap-was-lib/src/main/java io.shinhanlife.dap.lib.util.ToolScaffolder [?대쫫] [ID] "[?ㅻ챸]" "[洹몃9]" "[?듭떊諛⑹떇]" "[紐⑤뱢紐?" + */ +/** + * @package io.shinhanlife.dap.lib.util + * @className ToolScaffolder + * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 媛쒖젙?대젰 ----------
    + * ?섏젙??     ?섏젙??   ?섏젙?댁슜
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    理쒖큹?앹꽦
    + * 
    + * 
    + */ +public class ToolScaffolder { + + private static final String BASE_PACKAGE = "io.shinhanlife.dap.mcc"; + private static final String BASE_PACKAGE_PATH = "src/main/java/io/shinhanlife/dap/mcc"; + + public record FieldDefinition(String name, String type, String description, String example, boolean required) { + } + + public static void main(String[] args) throws IOException { + Scanner scanner = new Scanner(System.in); + + System.out.println("========================================="); + System.out.println(" MCP Tool Scaffolder (Java CLI) "); + System.out.println("=========================================\n"); + + String baseName = getOrAsk(args, 0, scanner, "1. ?앹꽦??Tool??湲곕낯 ?대쫫 (?? ExchangeRate) [?곷Ц PascalCase]: "); + String interfaceId = getOrAsk(args, 1, scanner, "2. ?덇굅??API ?명꽣?섏씠??ID (?? EXCH_001): "); + String title = getOrAsk(args, 2, scanner, "3. Tool title: "); + String description = getOrAsk(args, 3, scanner, "4. Tool description for LLM: "); + String group = getOrAsk(args, 4, scanner, "5. Tool category: "); + if (group.isEmpty()) group = "COMMON"; + String routingType = getOrAsk(args, 5, scanner, "6. Routing type (HTTP, TCP, MCI, EAI): "); + if (routingType.trim().isEmpty()) { + routingType = "HTTP"; + } + String moduleName = getOrAsk(args, 6, scanner, "7. Target module (default dap-was-oth): "); + if (moduleName.trim().isEmpty()) { + moduleName = "dap-was-oth"; + } + + String defaultAuthor = System.getProperty("user.name"); + String defaultDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy.MM.dd")); + + String author = getOrAsk(args, 7, scanner, "7. ?묒꽦??(?뷀꽣 ?낅젰 ??'" + defaultAuthor + "'): "); + if (author.trim().isEmpty()) author = defaultAuthor; + String createDate = getOrAsk(args, 8, scanner, "8. ?묒꽦??(?뷀꽣 ?낅젰 ??'" + defaultDate + "'): "); + if (createDate.trim().isEmpty()) createDate = defaultDate; + + String useSchemaResourceStr = getOrAsk(args, 9, scanner, "9. input/output JSON Schema ?뚯씪 ?먮룞 ?앹꽦 ?щ? (y/N): "); + boolean useSchemaResource = "y".equalsIgnoreCase(useSchemaResourceStr.trim()); + + String schemaResourceDirectory = "classpath:tool-schemas/" + group.toLowerCase() + "/"; + String inputSchemaResource = useSchemaResource ? schemaResourceDirectory + toKebabCase(baseName) + "-resource-input-schema.json" : null; + String outputSchemaResource = useSchemaResource ? schemaResourceDirectory + toKebabCase(baseName) + "-resource-output-schema.json" : null; + + String result = scaffold(baseName, interfaceId, title, description, group, routingType, moduleName, author, createDate, false, null, inputSchemaResource, outputSchemaResource, List.of(new FieldDefinition("query", "String", "Search query", "example", false)), List.of()); + System.out.println(result); + } + + private static String getOrAsk(String[] args, int index, Scanner scanner, String prompt) { + if (args.length > index) { + return args[index]; + } + System.out.print(prompt); + return scanner.nextLine().trim(); + } + + public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode) throws IOException { + return scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, register, clientSystemCode, null, null); + } + + public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode, String inputSchemaResource, String outputSchemaResource) throws IOException { + return scaffold(baseName, interfaceId, description, group, routingType, moduleName, author, createDate, + register, clientSystemCode, inputSchemaResource, outputSchemaResource, + List.of(new FieldDefinition("query", "String", "Search query", "example", false)), List.of()); + } + + public static String scaffold(String baseName, String interfaceId, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode, String inputSchemaResource, String outputSchemaResource, List inputFields, List outputFields) throws IOException { + return scaffold(baseName, interfaceId, description, description, group, routingType, moduleName, author, createDate, + register, clientSystemCode, inputSchemaResource, outputSchemaResource, inputFields, outputFields); + } + + /** + * Generates a Tool with a human-facing title and an LLM-facing description. + * Existing overloads keep their previous behavior by using the description as the title. + */ + public static String scaffold(String baseName, String interfaceId, String title, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode, String inputSchemaResource, String outputSchemaResource, List inputFields, List outputFields) throws IOException { + return scaffold(baseName, interfaceId, title, description, group, routingType, moduleName, author, createDate, + register, clientSystemCode, inputSchemaResource, outputSchemaResource, inputFields, outputFields, + toKebabCase(toPascalCase(baseName))); + } + + /** + * Generates a Tool using an HTTP API name that is resolved from glow.communication.http.api-list. + */ + public static String scaffold(String baseName, String interfaceId, String title, String description, String group, String routingType, String moduleName, String author, String createDate, boolean register, String clientSystemCode, String inputSchemaResource, String outputSchemaResource, List inputFields, List outputFields, String httpApiName) throws IOException { + baseName = toPascalCase(baseName); + title = title == null || title.isBlank() ? baseName : title.trim(); + description = description == null ? "" : description.trim(); + httpApiName = httpApiName == null || httpApiName.isBlank() ? toKebabCase(baseName) : httpApiName.trim(); + String envSourceDir = System.getenv("AXHUB_SOURCE_DIR"); + Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get("."); + + Path usecaseDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "usecase")); + Path usecaseImplDir = usecaseDir.resolve("impl"); + Path dtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "dto")); + + Path legacyDtoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "legacy")); + Path converterDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, "biz", group.toLowerCase(), "converter")); + + // schema resource ?뚯씪 寃쎈줈 (useSchemaResource=true ???뚮쭔 ?앹꽦) + boolean useSchemaResource = (inputSchemaResource != null && !inputSchemaResource.trim().isEmpty()) || (outputSchemaResource != null && !outputSchemaResource.trim().isEmpty()); + String schemaBaseName = toKebabCase(baseName); + String inputSchemaFileName = schemaBaseName + "-resource-input-schema.json"; + String outputSchemaFileName = schemaBaseName + "-resource-output-schema.json"; + Path schemaDir = rootDir.resolve(Paths.get(moduleName, "src/main/resources/tool-schemas", group.toLowerCase())); + String inputSchemaClasspath = "classpath:tool-schemas/" + group.toLowerCase() + "/" + inputSchemaFileName; + String outputSchemaClasspath = "classpath:tool-schemas/" + group.toLowerCase() + "/" + outputSchemaFileName; + + String bizPackage = BASE_PACKAGE + ".biz." + group.toLowerCase(); + boolean isMci = "MCI".equalsIgnoreCase(routingType); + boolean isHttp = "HTTP".equalsIgnoreCase(routingType); + if (!isMci && !isHttp) { + throw new IllegalArgumentException("Unsupported routing type: " + routingType + ". Only MCI and HTTP are supported."); + } + String mciGroupPath = "infra/itrf/mci/" + group.toLowerCase(); + String clientPrefixCap = ""; + Path mciClientDir = null; + + if (isMci && clientSystemCode != null && clientSystemCode.length() == 4) { + String clientPrefix = clientSystemCode.toLowerCase(); + clientPrefixCap = toPascalCase(clientSystemCode); + mciGroupPath = "infra/itrf/mci/" + clientPrefix; + mciClientDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, mciGroupPath)); + } + + Path mciIoDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, mciGroupPath, "io")); + String httpApiPackage = toPackageSegment(httpApiName); + String httpApiClass = toPascalCase(httpApiName); + String httpGroupPath = "infra/itrf/http/" + httpApiPackage; + Path httpClientDir = rootDir.resolve(Paths.get(moduleName, BASE_PACKAGE_PATH, httpGroupPath)); + Path httpIoDir = httpClientDir.resolve("io"); + + Files.createDirectories(usecaseDir); + Files.createDirectories(usecaseImplDir); + Files.createDirectories(dtoDir); + if (isMci) { + Files.createDirectories(mciIoDir); + if (mciClientDir != null) { + Files.createDirectories(mciClientDir); + } + } else if (isHttp) { + Files.createDirectories(httpIoDir); + } else { + Files.createDirectories(legacyDtoDir); + } + Files.createDirectories(converterDir); + + StringBuilder log = new StringBuilder(); + + // Generate Request DTO + String reqContent = """ + package %s.dto; + + import com.fasterxml.jackson.annotation.JsonInclude; + import lombok.Data; + + /** + * @package %s.dto + * @className %sRequest + * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @author %s + * @create %s + *
    +             * ---------- 媛쒖젙?대젰 ----------
    +             * ?섏젙??     ?섏젙??   ?섏젙?댁슜
    +             * ---------- -------- ---------------------------
    +             * %s  %s    理쒖큹?앹꽦
    +             * 
    +             * 
    + */ + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public class %sRequest { + @McpToolParam(description = "?섏떊???꾪솕踰덊샇", required = true) + -(?:\\\\d{3}|\\\\d{4})-\\\\d{4}$", examples = {"010-1234-5678"}) + private String phoneNumber; + + @McpToolParam(description = "?꾩넚??硫붿떆吏€ ?댁슜", required = true) + private String message; + } + """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); + reqContent = reqContent + .replace("import com.fasterxml.jackson.annotation.JsonInclude;", + "import com.fasterxml.jackson.annotation.JsonInclude;\nimport io.swagger.v3.oas.annotations.media.Schema;") + .replaceAll("(?m)^\\s*@McpToolParam\\([^\\r\\n]*\\)\\R", "") + .replaceAll("(?m)^\\s*-\\(\\?:[^\\r\\n]*\\R", "") + .replace("private String phoneNumber;", "@Schema(example = \"01012345678\")\n private String phoneNumber;") + .replace("private String message;", "@Schema(example = \"?뚯뒪??硫붿떆吏€?낅땲??\")\n private String message;"); + reqContent = dtoContent(bizPackage + ".dto", baseName + "Request", inputFields, author, createDate, true); + Files.writeString(dtoDir.resolve(baseName + "Request.java"), reqContent); + + // Generate Response DTO + String resContent = """ + package %s.dto; + + import com.fasterxml.jackson.annotation.JsonInclude; + import lombok.Data; + + /** + * @package %s.dto + * @className %sResponse + * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @author %s + * @create %s + *
    +             * ---------- 媛쒖젙?대젰 ----------
    +             * ?섏젙??     ?섏젙??   ?섏젙?댁슜
    +             * ---------- -------- ---------------------------
    +             * %s  %s    理쒖큹?앹꽦
    +             * 
    +             * 
    + */ + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public class %sResponse { + private String resultCode; + + private String resultMessage; + + // TODO: Add response fields here. Do not include PII in the Tool response. + } + """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); + resContent = dtoContent(bizPackage + ".dto", baseName + "Response", outputFields, author, createDate, false); + Files.writeString(dtoDir.resolve(baseName + "Response.java"), resContent); + + String toolName = toToolName(moduleName, group, baseName); + + String toolHintLine; + if (useSchemaResource) { + toolHintLine = (" @ToolHint(register = %s, categoryKey = \"%s\", mappingId = \"%s\",\n" + + " inputSchemaResource = \"%s\",\n" + + " outputSchemaResource = \"%s\")").formatted(register, group.toLowerCase(Locale.ROOT), interfaceId, inputSchemaClasspath, outputSchemaClasspath); + } else { + toolHintLine = " @ToolHint(register = %s, categoryKey = \"%s\", mappingId = \"%s\")".formatted(register, group.toLowerCase(Locale.ROOT), interfaceId); + } + + String serviceInterfaceContent = """ + package %s.usecase; + + import org.springaicommunity.mcp.annotation.McpTool; + import io.shinhanlife.dap.lib.annotation.ToolHint; + import %s.dto.%sRequest; + import %s.dto.%sResponse; + + /** + * @package %s.usecase + * @className %sUseCase + * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @author %s + * @create %s + *
    +             * ---------- 媛쒖젙?대젰 ----------
    +             * ?섏젙??     ?섏젙??   ?섏젙?댁슜
    +             * ---------- -------- ---------------------------
    +             * %s  %s    理쒖큹?앹꽦
    +             *
    +             * 
    + */ + public interface %sUseCase { + + @McpTool(name = "%s", title = "%s", description = "%s") + %s + %sResponse execute(%sRequest req); + } + """.formatted( + bizPackage, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, author, createDate, createDate, author, + baseName, + toolName, title, description, + toolHintLine, + baseName, baseName + ); + + Files.writeString(usecaseDir.resolve(baseName + "UseCase.java"), serviceInterfaceContent); + + String serviceImplContent; + + if (isMci) { + serviceImplContent = """ + package %s.usecase.impl; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.usecase.%sUseCase; + import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent; + import io.shinhanlife.glow.communication.dto.Transfer; + import org.springframework.stereotype.Service; + import lombok.RequiredArgsConstructor; + import lombok.extern.slf4j.Slf4j; + import %s.converter.%sConverter; + import %s.%s.io.%s_I; + %s + + /** + * @package %s.usecase.impl + * @className %sUseCaseImpl + * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @author %s + * @create %s + *
    +                 * ---------- 媛쒖젙?대젰 ----------
    +                 * ?섏젙??     ?섏젙??   ?섏젙?댁슜
    +                 * ---------- -------- ---------------------------
    +                 * %s  %s    理쒖큹?앹꽦
    +                 * 
    +                 * 
    + */ + @Slf4j + @Service + @RequiredArgsConstructor + public class %sUseCaseImpl implements %sUseCase { + + %s + private final %sConverter converter; + + @Override + public %sResponse execute(%sRequest req) { + log.info("[MCI Tool] {} ?붿껌 ?섏떊.", "%s"); + try { + // MapStruct瑜??댁슜???먮룞 留ㅽ븨 (AI DTO -> MCI DTO) + %s_I mciReq = converter.toLegacyRequest(req); + + Transfer resTransfer = mci.callTo( + "%s", + null, + mciReq, + Object.class + ); + %sResponse response = new %sResponse(); + response.setResultCode("SUCCESS"); + response.setResultMessage(resTransfer.getBody() != null + ? "MCI call completed." + : "MCI call completed without a response body."); + return response; + } catch (Exception e) { + log.error("[MCI Tool] ?곕룞 以??ㅻ쪟 諛쒖깮: {}", e.getMessage(), e); + %sResponse response = new %sResponse(); + response.setResultCode("ERROR"); + response.setResultMessage(e.getMessage() != null ? e.getMessage() : "Unknown error"); + return response; + } + } + } + """.formatted( + bizPackage, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, + BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, + (clientPrefixCap.isEmpty() ? "" : "import " + BASE_PACKAGE + "." + mciGroupPath.replace("/", ".") + ".Mci" + clientPrefixCap + "Client;\n"), + bizPackage, + baseName, + author, + createDate, + createDate, author, + baseName, + baseName, + (clientPrefixCap.isEmpty() ? "private final AxhubMciComponent mci;" : "private final Mci" + clientPrefixCap + "Client mci;"), + baseName, + baseName, + baseName, + toolName, + interfaceId, + interfaceId, + baseName, + baseName, + baseName, + baseName + ); + String mciIoPackage = BASE_PACKAGE + "." + mciGroupPath.replace("/", "."); + serviceImplContent = serviceImplContent + .replace("import " + mciIoPackage + ".io." + interfaceId + "_I;", + "import " + mciIoPackage + ".io." + interfaceId + "_I;\nimport " + mciIoPackage + ".io." + interfaceId + "_O;") + .replace("Transfer resTransfer", "Transfer<" + interfaceId + "_O> resTransfer") + .replace("Object.class", interfaceId + "_O.class") + .replace("response.setResultCode(\"SUCCESS\");", + "if (resTransfer.getBody() != null) {\n response = converter.toResponse(resTransfer.getBody());\n }\n response.setResultCode(\"SUCCESS\");"); + } else { + serviceImplContent = isHttp + ? httpUseCaseImplContent(bizPackage, baseName, httpGroupPath.replace("/", "."), httpApiClass, author, createDate) + : """ + package %s.usecase.impl; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.usecase.%sUseCase; + import %s.converter.%sConverter; + import org.springframework.stereotype.Service; + import lombok.RequiredArgsConstructor; + import lombok.extern.slf4j.Slf4j; + + /** + * @package %s.usecase.impl + * @className %sUseCaseImpl + * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @author %s + * @create %s + *
    +                 * ---------- 媛쒖젙?대젰 ----------
    +                 * ?섏젙??     ?섏젙??   ?섏젙?댁슜
    +                 * ---------- -------- ---------------------------
    +                 * %s  %s    理쒖큹?앹꽦
    +                 * 
    +                 * 
    + */ + @Slf4j + @Service + @RequiredArgsConstructor + public class %sUseCaseImpl implements %sUseCase { + + private final %sConverter converter; + + @Override + public %sResponse execute(%sRequest req) { + // %sLegacyRequest legacyRequest = converter.toLegacyRequest(req); + Object legacyResponse = null; + if (legacyResponse instanceof %sResponse response) { + return response; + } + %sResponse response = new %sResponse(); + response.setResultCode("SUCCESS"); + response.setResultMessage("Legacy call completed."); + return response; + } + } + """.formatted( + bizPackage, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, + author, + createDate, + createDate, author, + baseName, baseName, + baseName, + baseName, + baseName, + baseName, + baseName, + baseName, + baseName, + routingType, interfaceId + ); + } + + Files.writeString(usecaseImplDir.resolve(baseName + "UseCaseImpl.java"), serviceImplContent); + + if (isMci) { + String mciReqContent = """ + package %s.%s.io; + + import lombok.Data; + + /** + * @package %s.%s.io + * @className %s_I + * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @author %s + * @create %s + *
    +                 * ---------- 媛쒖젙?대젰 ----------
    +                 * ?섏젙??     ?섏젙??   ?섏젙?댁슜
    +                 * ---------- -------- ---------------------------
    +                 * %s  %s    理쒖큹?앹꽦
    +                 * 
    +                 * 
    + */ + @Data + public class %s_I { + /** + * EAI ?쒖뒪?쒖씠 ?붽뎄?섎뒗 ?섏떊??踰덊샇 ?뚮씪誘명꽣紐? + */ + private String phone; + + /** + * EAI ?쒖뒪?쒖씠 ?붽뎄?섎뒗 硫붿떆吏€ ?댁슜 ?뚮씪誘명꽣紐? + */ + private String content; + } + """.formatted(BASE_PACKAGE, mciGroupPath.replace("/", "."), BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, author, createDate, createDate, author, interfaceId); + mciReqContent = mciIoContent(mciGroupPath.replace("/", "."), interfaceId + "_I", inputFields, author, createDate); + Files.writeString(mciIoDir.resolve(interfaceId + "_I.java"), mciReqContent); + + String mciResContent = """ + package %s.%s.io; + + import lombok.Data; + + /** + * @package %s.%s.io + * @className %s_O + * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @author %s + * @create %s + *
    +                 * ---------- 媛쒖젙?대젰 ----------
    +                 * ?섏젙??     ?섏젙??   ?섏젙?댁슜
    +                 * ---------- -------- ---------------------------
    +                 * %s  %s    理쒖큹?앹꽦
    +                 * 
    +                 * 
    + */ + @Data + public class %s_O { + // TODO: Add response fields here + } + """.formatted(BASE_PACKAGE, mciGroupPath.replace("/", "."), BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, author, createDate, createDate, author, interfaceId); + mciResContent = mciIoContent(mciGroupPath.replace("/", "."), interfaceId + "_O", outputFields, author, createDate); + Files.writeString(mciIoDir.resolve(interfaceId + "_O.java"), mciResContent); + + String converterContent = """ + package %s.converter; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.%s.io.%s_I; + import %s.%s.io.%s_O; + import org.mapstruct.Mapper; + import org.mapstruct.Mapping; + import org.mapstruct.factory.Mappers; + + /** + * @package %s.converter + * @className %sConverter + * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @author %s + * @create %s + *
    +                 * ---------- 媛쒖젙?대젰 ----------
    +                 * ?섏젙??     ?섏젙??   ?섏젙?댁슜
    +                 * ---------- -------- ---------------------------
    +                 * %s  %s    理쒖큹?앹꽦
    +                 * 
    +                 * 
    + */ + @Mapper(componentModel = "spring") + public interface %sConverter { + + @Mapping(source = "phoneNumber", target = "phone") + @Mapping(source = "message", target = "content") + %s_I toLegacyRequest(%sRequest req); + + @Mapping(source = "phone", target = "phoneNumber") + @Mapping(source = "content", target = "message") + %sRequest toRequest(%s_I mciReq); + + // %sResponse toResponse(%s_O mciRes); + } + """.formatted( + bizPackage, + bizPackage, baseName, + bizPackage, baseName, + BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, + BASE_PACKAGE, mciGroupPath.replace("/", "."), interfaceId, + bizPackage, baseName, author, createDate, createDate, author, + baseName, interfaceId, baseName, + baseName, interfaceId, + baseName, interfaceId + ); + converterContent = mciConverterContent(bizPackage, baseName, mciGroupPath.replace("/", "."), interfaceId); + Files.writeString(converterDir.resolve(baseName + "Converter.java"), converterContent); + + log.append("\n=========================================\n"); + log.append(" Scaffolding Complete! (Routing: " + routingType + ")\n"); + log.append("=========================================\n"); + log.append("[Usecase Interface] ").append(usecaseDir.resolve(baseName + "UseCase.java")).append("\n"); + log.append("[Usecase Impl] ").append(usecaseImplDir.resolve(baseName + "UseCaseImpl.java")).append("\n"); + log.append("[Request DTO] ").append(dtoDir.resolve(baseName + "Request.java")).append("\n"); + log.append("[Response DTO] ").append(dtoDir.resolve(baseName + "Response.java")).append("\n"); + log.append("[MCI Request IO] ").append(mciIoDir.resolve(interfaceId + "_I.java")).append("\n"); + log.append("[MCI Response IO] ").append(mciIoDir.resolve(interfaceId + "_O.java")).append("\n"); + log.append("[MCI Converter] ").append(converterDir.resolve(baseName + "Converter.java")).append("\n"); + + if (!clientPrefixCap.isEmpty()) { + String mciClientContent = """ + package %s.%s; + + import org.springframework.stereotype.Component; + import lombok.RequiredArgsConstructor; + import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent; + import io.shinhanlife.glow.communication.dto.Transfer; + + /** + * @package %s.%s + * @className Mci%sClient + * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @author %s + * @create %s + *
    +                     * ---------- 媛쒖젙?대젰 ----------
    +                     * ?섏젙??     ?섏젙??   ?섏젙?댁슜
    +                     * ---------- -------- ---------------------------
    +                     * %s  %s    理쒖큹?앹꽦
    +                     * 
    +                     * 
    + */ + @Component + @RequiredArgsConstructor + public class Mci%sClient { + private final AxhubMciComponent mci; + + public Transfer callTo(String interfaceId, String dummy, Object mciReq, Class resType) throws Exception { + return mci.callTo(interfaceId, dummy, mciReq, resType); + } + } + """.formatted( + BASE_PACKAGE, mciGroupPath.replace("/", "."), + BASE_PACKAGE, mciGroupPath.replace("/", "."), clientPrefixCap, author, createDate, createDate, author, clientPrefixCap + ); + Files.writeString(mciClientDir.resolve("Mci" + clientPrefixCap + "Client.java"), mciClientContent); + log.append("[MCI Client] ").append(mciClientDir.resolve("Mci" + clientPrefixCap + "Client.java")).append("\n"); + } + + } else if (isHttp) { + String httpPackage = BASE_PACKAGE + "." + httpGroupPath.replace("/", "."); + String httpRequestClass = baseName + "HttpRequest"; + String httpResponseClass = baseName + "HttpResponse"; + String httpClientClass = httpApiClass + "Client"; + + Files.writeString(httpIoDir.resolve(httpRequestClass + ".java"), + dtoContent(httpPackage + ".io", httpRequestClass, inputFields, author, createDate, true)); + Files.writeString(httpIoDir.resolve(httpResponseClass + ".java"), + dtoContent(httpPackage + ".io", httpResponseClass, outputFields, author, createDate, false)); + Files.writeString(httpClientDir.resolve(httpClientClass + ".java"), + httpClientContent(httpPackage, httpClientClass, httpApiName)); + Files.writeString(converterDir.resolve(baseName + "Converter.java"), + httpConverterContent(bizPackage, baseName, httpPackage)); + + log.append("\n=========================================\n"); + log.append(" Scaffolding Complete! (Routing: HTTP)\n"); + log.append("=========================================\n"); + log.append("[Usecase Interface] ").append(usecaseDir.resolve(baseName + "UseCase.java")).append("\n"); + log.append("[Usecase Impl] ").append(usecaseImplDir.resolve(baseName + "UseCaseImpl.java")).append("\n"); + log.append("[Request DTO] ").append(dtoDir.resolve(baseName + "Request.java")).append("\n"); + log.append("[Response DTO] ").append(dtoDir.resolve(baseName + "Response.java")).append("\n"); + log.append("[HTTP Request IO] ").append(httpIoDir.resolve(httpRequestClass + ".java")).append("\n"); + log.append("[HTTP Response IO] ").append(httpIoDir.resolve(httpResponseClass + ".java")).append("\n"); + log.append("[HTTP Client] ").append(httpClientDir.resolve(httpClientClass + ".java")).append("\n"); + log.append("[HTTP Converter] ").append(converterDir.resolve(baseName + "Converter.java")).append("\n"); + } else { + String legacyReqContent = """ + package %s.legacy; + + import lombok.Data; + + /** + * @package %s.legacy + * @className %sLegacyRequest + * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @author %s + * @create %s + *
    +                 * ---------- 媛쒖젙?대젰 ----------
    +                 * ?섏젙??     ?섏젙??   ?섏젙?댁슜
    +                 * ---------- -------- ---------------------------
    +                 * %s  %s    理쒖큹?앹꽦
    +                 * 
    +                 * 
    + */ + @Data + public class %sLegacyRequest { + /** + * EAI ?쒖뒪?쒖씠 ?붽뎄?섎뒗 ?섏떊??踰덊샇 ?뚮씪誘명꽣紐? + */ + private String phone; + + /** + * EAI ?쒖뒪?쒖씠 ?붽뎄?섎뒗 硫붿떆吏€ ?댁슜 ?뚮씪誘명꽣紐? + */ + private String content; + } + """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); + legacyReqContent = dtoContent(bizPackage + ".legacy", baseName + "LegacyRequest", inputFields, author, createDate, true); + Files.writeString(legacyDtoDir.resolve(baseName + "LegacyRequest.java"), legacyReqContent); + + String legacyResContent = """ + package %s.legacy; + + import lombok.Data; + + /** + * @package %s.legacy + * @className %sLegacyResponse + * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @author %s + * @create %s + *
    +                 * ---------- 媛쒖젙?대젰 ----------
    +                 * ?섏젙??     ?섏젙??   ?섏젙?댁슜
    +                 * ---------- -------- ---------------------------
    +                 * %s  %s    理쒖큹?앹꽦
    +                 * 
    +                 * 
    + */ + @Data + public class %sLegacyResponse { + // TODO: Add legacy response fields here + } + """.formatted(bizPackage, bizPackage, baseName, author, createDate, createDate, author, baseName); + legacyResContent = dtoContent(bizPackage + ".legacy", baseName + "LegacyResponse", outputFields, author, createDate, true); + Files.writeString(legacyDtoDir.resolve(baseName + "LegacyResponse.java"), legacyResContent); + + String converterContent = """ + package %s.converter; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.legacy.%sLegacyRequest; + import %s.legacy.%sLegacyResponse; + import org.mapstruct.Mapper; + import org.mapstruct.Mapping; + import org.mapstruct.factory.Mappers; + + /** + * @package %s.converter + * @className %sConverter + * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @author %s + * @create %s + *
    +                 * ---------- 媛쒖젙?대젰 ----------
    +                 * ?섏젙??     ?섏젙??   ?섏젙?댁슜
    +                 * ---------- -------- ---------------------------
    +                 * %s  %s    理쒖큹?앹꽦
    +                 * 
    +                 * 
    + */ + @Mapper(componentModel = "spring") + public interface %sConverter { + + @Mapping(source = "phoneNumber", target = "phone") + @Mapping(source = "message", target = "content") + %sLegacyRequest toLegacyRequest(%sRequest req); + + @Mapping(source = "phone", target = "phoneNumber") + @Mapping(source = "content", target = "message") + %sRequest toRequest(%sLegacyRequest legacyRequest); + + // %sResponse toResponse(%sLegacyResponse legacyResponse); + } + """.formatted( + bizPackage, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, author, createDate, createDate, author, + baseName, baseName, baseName, baseName, baseName, baseName, baseName + ); + converterContent = legacyConverterContent(bizPackage, baseName); + Files.writeString(converterDir.resolve(baseName + "Converter.java"), converterContent); + + log.append("\n=========================================\n"); + log.append(" Scaffolding Complete! (Routing: " + routingType + ")\n"); + log.append("=========================================\n"); + log.append("[Usecase Interface] ").append(usecaseDir.resolve(baseName + "UseCase.java")).append("\n"); + log.append("[Usecase Impl] ").append(usecaseImplDir.resolve(baseName + "UseCaseImpl.java")).append("\n"); + log.append("[Request DTO] ").append(dtoDir.resolve(baseName + "Request.java")).append("\n"); + log.append("[Response DTO] ").append(dtoDir.resolve(baseName + "Response.java")).append("\n"); + log.append("[Legacy Request DTO] ").append(legacyDtoDir.resolve(baseName + "LegacyRequest.java")).append("\n"); + log.append("[Legacy Response DTO] ").append(legacyDtoDir.resolve(baseName + "LegacyResponse.java")).append("\n"); + log.append("[Legacy Converter] ").append(converterDir.resolve(baseName + "Converter.java")).append("\n"); + } + // schema resource ?뚯씪 ?앹꽦 (useSchemaResource=true ???? + if (useSchemaResource) { + Files.createDirectories(schemaDir); + String inputSchema = """ + { + "type": "object", + "additionalProperties": false, + "properties": { + "TODO_FIELD": { + "type": "string", + "description": "TODO: ?뚮씪誘명꽣 ?ㅻ챸???낅젰?섏꽭??" + } + }, + "required": [] + } + """; + String outputSchema = """ + { + "type": "object", + "additionalProperties": false, + "properties": { + "status": { + "type": "string", + "description": "泥섎━ 寃곌낵 ?곹깭 (SUCCESS / FAILURE)", + "enum": ["SUCCESS", "FAILURE"] + }, + "message": { + "type": "string", + "description": "泥섎━ 寃곌낵 硫붿떆吏€" + } + }, + "required": ["status"] + } + """; + Files.writeString(schemaDir.resolve(inputSchemaFileName), inputSchema); + Files.writeString(schemaDir.resolve(outputSchemaFileName), outputSchema); + log.append("[Input Schema] ").append(schemaDir.resolve(inputSchemaFileName)).append("\n"); + log.append("[Output Schema] ").append(schemaDir.resolve(outputSchemaFileName)).append("\n"); + } + + String mockResponse = mockResponseContent(outputFields); + if (isHttp) { + Path moduleRoot = rootDir.resolve(moduleName).toAbsolutePath().normalize(); + Path projectRoot = moduleRoot.getParent(); + Path wireMockBodyPath = projectRoot.resolve(Paths.get("mci-mock", "__files", toolName + ".json")); + Path wireMockMappingPath = projectRoot.resolve(Paths.get("mci-mock", "mappings", toolName + ".json")); + Path podMockResponsePath = moduleRoot.resolve(Paths.get("src/main/resources/mock-responses", toolName + ".json")); + Files.createDirectories(wireMockBodyPath.getParent()); + Files.createDirectories(wireMockMappingPath.getParent()); + Files.createDirectories(podMockResponsePath.getParent()); + Files.writeString(wireMockBodyPath, mockResponse); + Files.writeString(wireMockMappingPath, wireMockMappingContent(interfaceId, wireMockBodyPath.getFileName().toString())); + Files.writeString(podMockResponsePath, mockResponse); + ensureLocalHttpApiConfiguration(projectRoot, httpApiName, toolName); + log.append("[WireMock Response] ").append(wireMockBodyPath).append("\n"); + log.append("[WireMock Mapping] ").append(wireMockMappingPath).append("\n"); + log.append("[Pod Mock Response] ").append(podMockResponsePath).append("\n"); + } else { + Path mockResponsePath = rootDir.resolve(Paths.get(moduleName, "src/main/resources/mock-responses", toolName + ".json")); + Files.createDirectories(mockResponsePath.getParent()); + Files.writeString(mockResponsePath, mockResponse); + log.append("[Mock Response] ").append(mockResponsePath).append("\n"); + } + + Path generatedTestDir = rootDir.resolve(Paths.get(moduleName, "src/test/java/io/shinhanlife/dap/mcc/biz", group.toLowerCase(), "usecase")); + Files.createDirectories(generatedTestDir); + Path generatedTestPath = generatedTestDir.resolve(baseName + "UseCaseTest.java"); + Files.writeString(generatedTestPath, useCaseTestContent(bizPackage, baseName)); + log.append("[Unit Test] ").append(generatedTestPath).append("\\n"); + log.append("[Test Command] .\\gradlew.bat :").append(moduleName.substring(moduleName.lastIndexOf(java.io.File.separator) + 1)).append(":test --tests \"*").append(baseName).append("UseCaseTest\"\\n"); + log.append("\n Tip: HTTP Tool은 WireMock 실행 후 생성된 mapping URL로 호출을 확인하세요.\n"); + + return log.toString(); + } + + private static String toKebabCase(String pascalCase) { + if (pascalCase == null || pascalCase.isEmpty()) return pascalCase; + return pascalCase + .replaceAll("([a-z0-9])([A-Z])", "$1-$2") + .toLowerCase(Locale.ROOT); + } + + private static void ensureLocalHttpApiConfiguration(Path projectRoot, String httpApiName, String toolName) throws IOException { + Path localConfigPath = projectRoot.resolve("dap-was-lib/src/main/resources/glow/application-glow-local.yml"); + Files.createDirectories(localConfigPath.getParent()); + String existing = Files.exists(localConfigPath) ? Files.readString(localConfigPath) : ""; + if (java.util.regex.Pattern.compile("(?m)^\\s*-\\s+name:\\s*" + + java.util.regex.Pattern.quote(httpApiName) + "\\s*$").matcher(existing).find()) { + return; + } + String environmentKey = toPackageSegment(httpApiName).toUpperCase(Locale.ROOT).replace('-', '_'); + String apiEntry = """ + - name: %s + domain: ${AXHUB_%s_HTTP_DOMAIN:http://localhost:${server.port}} + url: ${AXHUB_%s_HTTP_URL:/api/mock/http/%s} + method: POST + content-type: application/json;charset=UTF-8 + biz-pod: false + """.formatted(httpApiName, environmentKey, environmentKey, toolName).stripTrailing() + "\n"; + if (existing.isBlank()) { + existing = """ + spring: + config: + activate: + on-profile: local + + glow: + communication: + http: + api-list: + """ + apiEntry; + } else if (existing.contains("\n mci:")) { + existing = existing.replace("\n mci:", "\n" + apiEntry + " mci:"); + } else if (existing.contains("\naxhub:")) { + existing = existing.replace("\naxhub:", apiEntry + "axhub:"); + } else if (existing.contains("api-list:")) { + existing += apiEntry; + } else { + throw new IllegalStateException("application-glow-local.yml must define glow.communication.http.api-list"); + } + if (!existing.contains("axhub:\n mock:\n http:\n enabled: true")) { + existing += """ + + axhub: + mock: + http: + enabled: true + """; + } + Files.writeString(localConfigPath, existing); + } + + private static String dtoContent(String packageName, String className, List fields, + String author, String createDate, boolean request) { + String body = fieldLines(fields, request ? Set.of() : Set.of("resultCode", "resultMessage")); + if (!request) { + body = " private String resultCode;\n\n private String resultMessage;\n" + body; + } + return """ + package %s; + + import com.fasterxml.jackson.annotation.JsonInclude; + import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; + + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public class %s { + %s} + """.formatted(packageName, className, body); + } + + private static String mciIoContent(String packageSuffix, String className, List fields, + String author, String createDate) { + return """ + package %s.%s.io; + + import io.swagger.v3.oas.annotations.media.Schema; + import lombok.Data; + + @Data + public class %s { + %s} + """.formatted(BASE_PACKAGE, packageSuffix, className, fieldLines(fields)); + } + + private static String httpUseCaseImplContent(String bizPackage, String baseName, String httpPackage, + String httpApiClass, String author, String createDate) { + String httpRequestClass = baseName + "HttpRequest"; + String httpResponseClass = baseName + "HttpResponse"; + String httpClientClass = httpApiClass + "Client"; + String clientVariable = Character.toLowerCase(httpClientClass.charAt(0)) + httpClientClass.substring(1); + return """ + package %s.usecase.impl; + + import %s.converter.%sConverter; + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.%s.%s; + import %s.%s.io.%s; + import %s.%s.io.%s; + import %s.usecase.%sUseCase; + import lombok.RequiredArgsConstructor; + import org.springframework.stereotype.Service; + + @Service + @RequiredArgsConstructor + public class %sUseCaseImpl implements %sUseCase { + + private final %sConverter converter; + private final %s %s; + + @Override + public %sResponse execute(%sRequest req) { + %s httpRequest = converter.toHttpRequest(req); + %s httpResponse = %s.call(httpRequest, %s.class); + + %sResponse response = converter.toResponse(httpResponse); + response.setResultCode("SUCCESS"); + response.setResultMessage("HTTP API call completed."); + return response; + } + } + """.formatted( + bizPackage, + bizPackage, baseName, + bizPackage, baseName, + bizPackage, baseName, + BASE_PACKAGE, httpPackage, httpClientClass, + BASE_PACKAGE, httpPackage, httpRequestClass, + BASE_PACKAGE, httpPackage, httpResponseClass, + bizPackage, baseName, + baseName, baseName, + baseName, httpClientClass, clientVariable, + baseName, baseName, + httpRequestClass, httpResponseClass, clientVariable, httpResponseClass, + baseName); + } + + private static String httpClientContent(String httpPackage, String clientClass, String apiName) { + return """ + package %s; + + import io.shinhanlife.dap.lib.integration.http.component.AxhubHttpComponent; + import lombok.RequiredArgsConstructor; + import org.springframework.stereotype.Component; + + @Component + @RequiredArgsConstructor + public class %s { + private static final String API_NAME = "%s"; + + private final AxhubHttpComponent http; + + public O call(I request, Class responseType) { + return http.call(API_NAME, request, responseType); + } + } + """.formatted(httpPackage, clientClass, apiName); + } + + private static String httpConverterContent(String bizPackage, String baseName, String httpPackage) { + return """ + package %s.converter; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.io.%sHttpRequest; + import %s.io.%sHttpResponse; + import org.mapstruct.Mapper; + import org.mapstruct.Mapping; + import org.mapstruct.ReportingPolicy; + + @Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) + public interface %sConverter { + // Field names differ? Add mappings like this before the method. + // @Mapping(source = "sourceField", target = "targetField") + %sHttpRequest toHttpRequest(%sRequest request); + %sResponse toResponse(%sHttpResponse httpResponse); + } + """.formatted(bizPackage, + bizPackage, baseName, + bizPackage, baseName, + httpPackage, baseName, + httpPackage, baseName, + baseName, baseName, baseName, baseName, baseName); + } + + private static String toPackageSegment(String value) { + String normalized = value == null ? "" : value.toLowerCase(Locale.ROOT) + .replaceAll("[^a-z0-9]+", "_") + .replaceAll("^_+|_+$", ""); + return normalized.isBlank() ? "http_api" : normalized; + } + private static String mciConverterContent(String bizPackage, String baseName, String mciPackage, String interfaceId) { + return """ + package %s.converter; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.%s.io.%s_I; + import %s.%s.io.%s_O; + import org.mapstruct.Mapper; + import org.mapstruct.Mapping; + import org.mapstruct.ReportingPolicy; + + @Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) + public interface %sConverter { + // Field names differ? Add mappings like this before the method. + // @Mapping(source = "sourceField", target = "targetField") + %s_I toLegacyRequest(%sRequest request); + %sRequest toRequest(%s_I mciRequest); + %sResponse toResponse(%s_O mciRes); + } + """.formatted(bizPackage, bizPackage, baseName, bizPackage, baseName, + BASE_PACKAGE, mciPackage, interfaceId, BASE_PACKAGE, mciPackage, interfaceId, + baseName, interfaceId, baseName, baseName, interfaceId, baseName, interfaceId); + } + + private static String legacyConverterContent(String bizPackage, String baseName) { + return """ + package %s.converter; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import %s.legacy.%sLegacyRequest; + import %s.legacy.%sLegacyResponse; + import org.mapstruct.Mapper; + import org.mapstruct.Mapping; + import org.mapstruct.ReportingPolicy; + + @Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) + public interface %sConverter { + // Field names differ? Add mappings like this before the method. + // @Mapping(source = "sourceField", target = "targetField") + %sLegacyRequest toLegacyRequest(%sRequest request); + %sRequest toRequest(%sLegacyRequest legacyRequest); + %sResponse toResponse(%sLegacyResponse legacyResponse); + } + """.formatted( + bizPackage, bizPackage, baseName, bizPackage, baseName, + bizPackage, baseName, bizPackage, baseName, + baseName, baseName, baseName, baseName, baseName, baseName, baseName); + } + private static String fieldLines(List fields) { + return fieldLines(fields, Set.of()); + } + + private static String fieldLines(List fields, Set excludedNames) { + StringBuilder source = new StringBuilder(); + Set generatedNames = new LinkedHashSet<>(); + for (FieldDefinition field : fields == null ? List.of() : fields) { + if (field == null || field.name() == null || field.name().isBlank()) { + continue; + } + String fieldName = field.name().trim(); + if (excludedNames.contains(fieldName) || !generatedNames.add(fieldName)) { + continue; + } + String type = supportedType(field.type()); + String description = field.description() == null ? "" : field.description().replace("\"", "\\\""); + String example = field.example() == null ? "" : field.example().replace("\"", "\\\""); + source.append(" @Schema(description = \"").append(description).append("\", example = \"") + .append(example).append("\""); + if (field.required()) { + source.append(", requiredMode = Schema.RequiredMode.REQUIRED"); + } + source.append(")\n private ").append(type).append(' ').append(fieldName).append(";\n\n"); + } + return source.toString(); + } + private static String supportedType(String type) { + return switch (type == null ? "String" : type) { + case "String", "Integer", "Long", "Double", "Boolean", "BigDecimal" -> type; + default -> throw new IllegalArgumentException("Unsupported field type: " + type); + }; + } + + + static String wireMockMappingContent(String interfaceId, String bodyFileName) { + return """ + { + "request" : { + "method" : "POST", + "urlPath" : "/%s" + }, + "response" : { + "status" : 200, + "headers" : { + "Content-Type" : "application/json;charset=UTF-8" + }, + "bodyFileName" : "%s" + } + } + """.formatted(jsonEscape(interfaceId), jsonEscape(bodyFileName)); + } + private static String mockResponseContent(List outputFields) { + StringBuilder json = new StringBuilder("{\n"); + List fields = outputFields == null ? List.of() : outputFields; + boolean first = true; + for (FieldDefinition field : fields) { + if (field.name() == null || field.name().isBlank()) { + continue; + } + if (!first) { + json.append(",\n"); + } + json.append(" \"").append(jsonEscape(field.name())).append("\" : ") + .append(mockValue(field)); + first = false; + } + return json.append("\n}\n").toString(); + } + + private static String mockValue(FieldDefinition field) { + if (field.example() == null || field.example().isBlank()) { + return "null"; + } + return switch (supportedType(field.type())) { + case "Integer", "Long", "Double", "BigDecimal" -> field.example(); + case "Boolean" -> Boolean.parseBoolean(field.example()) ? "true" : "false"; + default -> "\"" + jsonEscape(field.example()) + "\""; + }; + } + + private static String jsonEscape(String value) { + return value.replace("\\", "\\\\").replace("\"", "\\\""); + } + + private static String useCaseTestContent(String bizPackage, String baseName) { + return """ + package %s.usecase; + + import static org.junit.jupiter.api.Assertions.assertNotNull; + + import %s.dto.%sRequest; + import %s.dto.%sResponse; + import org.junit.jupiter.api.Test; + + class %sUseCaseTest { + + @Test + void createsToolRequestAndResponseDtos() { + assertNotNull(new %sRequest()); + assertNotNull(new %sResponse()); + } + } + """.formatted(bizPackage, bizPackage, baseName, bizPackage, baseName, + baseName, baseName, baseName); + } + private static String toToolName(String moduleName, String group, String baseName) { + String normalizedName = baseName.replaceAll("([a-z0-9])([A-Z])", "$1 $2") + .toLowerCase(Locale.ROOT) + .replaceAll("[^a-z0-9]+", " ") + .trim(); + String[] words = normalizedName.split("\\s+"); + String service = words[0]; + String action = words.length == 1 ? "execute" : words[words.length - 1]; + return "%s_%s_%s".formatted( + group.toLowerCase(Locale.ROOT), + service, + action); + } + + private static String toPascalCase(String str) { + if (str == null || str.isEmpty()) { + return str; + } + StringBuilder result = new StringBuilder(); + boolean capitalizeNext = true; + for (char c : str.toCharArray()) { + if (c == '_' || c == '-' || c == ' ') { + capitalizeNext = true; + } else if (capitalizeNext) { + result.append(Character.toUpperCase(c)); + capitalizeNext = false; + } else { + result.append(c); + } + } + if (result.length() > 0) { + result.setCharAt(0, Character.toUpperCase(result.charAt(0))); + } + return result.toString(); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSchemaResolver.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSchemaResolver.java new file mode 100644 index 00000000..3d7dc3fc --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSchemaResolver.java @@ -0,0 +1,78 @@ +package io.shinhanlife.dap.lib.util; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.annotation.McpOutputSchema; +import io.shinhanlife.dap.lib.annotation.ToolHint; +import java.io.InputStream; +import java.util.Map; +import org.springframework.core.io.ClassPathResource; + +/** Resolves MCP Tool schemas from resources or DTO metadata. */ +public class ToolSchemaResolver { + + private final ObjectMapper objectMapper; + + public ToolSchemaResolver(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public Map resolve(org.springaicommunity.mcp.annotation.McpTool function, + ToolHint hint, Class requestType) { + if (hint != null && !hint.inputSchemaResource().isBlank()) { + return loadResource(hint.inputSchemaResource()); + } + return JsonSchemaGenerator.generateSchema(requestType); + } + + /** + * Resolves a response schema only when it is explicitly declared. + * A JSON resource has precedence over a DTO marker annotation. + */ + public Map resolveOutput(org.springaicommunity.mcp.annotation.McpTool function, + Class responseType, ToolHint hint) { + if (hint != null && !hint.outputSchemaResource().isBlank()) { + return loadResource(hint.outputSchemaResource()); + } + return resolveOutput(function, responseType); + } + + /** + * Generates a response schema only for DTOs marked with {@link McpOutputSchema}. + */ + public Map resolveOutput(org.springaicommunity.mcp.annotation.McpTool function, + Class responseType) { + if (responseType == null + || responseType == Object.class + || Map.class.isAssignableFrom(responseType) + || responseType == Void.class + || responseType == void.class + || !responseType.isAnnotationPresent(McpOutputSchema.class)) { + return Map.of(); + } + return JsonSchemaGenerator.generateSchema(responseType); + } + + /** + * Retained for callers that use only explicit output schemas. + */ + public Map resolveOutput(org.springaicommunity.mcp.annotation.McpTool function) { + return Map.of(); + } + + private Map loadResource(String location) { + String path = location.startsWith("classpath:") + ? location.substring("classpath:".length()) + : location; + ClassPathResource resource = new ClassPathResource(path); + if (!resource.exists()) { + throw new IllegalStateException("MCP schema resource not found: " + location); + } + + try (InputStream inputStream = resource.getInputStream()) { + return objectMapper.readValue(inputStream, new TypeReference<>() { }); + } catch (Exception e) { + throw new IllegalStateException("Failed to load MCP schema resource: " + location, e); + } + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java new file mode 100644 index 00000000..f0384524 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/util/ToolSourceUpdater.java @@ -0,0 +1,140 @@ +package io.shinhanlife.dap.lib.util; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Comparator; + +/** Updates MCP SDK and project-owned metadata in a generated tool source file. */ +public final class ToolSourceUpdater { + + private ToolSourceUpdater() { + } + + public static void updateToolSource(String toolName, String categoryKey, String description, + boolean register, Boolean requiresApproval) throws IOException { + String configuredSourceDirectory = System.getenv("AXHUB_SOURCE_DIR"); + Path rootDirectory = configuredSourceDirectory == null || configuredSourceDirectory.isBlank() + ? Paths.get(".") : Paths.get(configuredSourceDirectory); + updateToolSource(rootDirectory, toolName, categoryKey, description, register, requiresApproval); + } + + static void updateToolSource(Path rootDirectory, String toolName, String categoryKey, String description, + boolean register, Boolean requiresApproval) throws IOException { + Path targetFile = findToolSource(rootDirectory, toolName); + if (targetFile == null) { + throw new IllegalArgumentException("Tool source not found: " + toolName); + } + + String content = Files.readString(targetFile); + content = updateMcpTool(content, toolName, description); + content = updateToolHint(content, categoryKey, register, requiresApproval); + Files.writeString(targetFile, content); + } + + private static Path findToolSource(Path rootDirectory, String toolName) throws IOException { + try (var paths = Files.walk(rootDirectory)) { + return paths.filter(Files::isRegularFile) + .filter(path -> path.getFileName().toString().endsWith("UseCase.java")) + .filter(path -> path.toString().contains("dap-was-") || path.toString().contains("axhub-tool-")) + .sorted(Comparator.naturalOrder()) + .filter(path -> containsMcpTool(path, toolName)) + .findFirst() + .orElse(null); + } + } + + private static boolean containsMcpTool(Path path, String toolName) { + try { + return annotationArguments(Files.readString(path), "McpTool", toolName) != null; + } catch (IOException exception) { + throw new IllegalStateException("Failed to read tool source: " + path, exception); + } + } + + private static String updateMcpTool(String content, String toolName, String description) { + AnnotationRange range = annotationArguments(content, "McpTool", toolName); + if (range == null) { + throw new IllegalArgumentException("McpTool declaration not found: " + toolName); + } + return description == null ? content : replaceAttribute(content, range, "description", quote(description)); + } + + private static String updateToolHint(String content, String categoryKey, boolean register, Boolean requiresApproval) { + AnnotationRange range = annotationArguments(content, "ToolHint", null); + if (range == null) { + throw new IllegalArgumentException("ToolHint declaration not found next to McpTool"); + } + String updated = replaceAttribute(content, range, "register", Boolean.toString(register)); + range = annotationArguments(updated, "ToolHint", null); + if (requiresApproval != null) { + updated = replaceAttribute(updated, range, "requiresApproval", Boolean.toString(requiresApproval)); + range = annotationArguments(updated, "ToolHint", null); + } + if (categoryKey != null && !categoryKey.isBlank()) { + updated = replaceAttribute(updated, range, "categoryKey", quote(categoryKey)); + } + return updated; + } + + private static String replaceAttribute(String content, AnnotationRange range, String attribute, String value) { + String arguments = content.substring(range.argumentsStart(), range.argumentsEnd()); + String pattern = "\\b" + attribute + "\\s*=\\s*(?:true|false|\\\"(?:\\\\.|[^\\\"\\\\])*\\\")"; + String replacement = arguments.replaceFirst(pattern, attribute + " = " + value); + if (replacement.equals(arguments)) { + replacement = arguments.isBlank() ? attribute + " = " + value : arguments + ", " + attribute + " = " + value; + } + return content.substring(0, range.argumentsStart()) + replacement + content.substring(range.argumentsEnd()); + } + + private static String quote(String value) { + return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\""; + } + + private static AnnotationRange annotationArguments(String content, String annotationName, String toolName) { + int offset = content.indexOf("@" + annotationName); + while (offset >= 0) { + int openingParenthesis = content.indexOf('(', offset); + int closingParenthesis = findAnnotationEnd(content, openingParenthesis); + if (openingParenthesis < 0 || closingParenthesis < 0) { + return null; + } + AnnotationRange range = new AnnotationRange(openingParenthesis + 1, closingParenthesis); + if (toolName == null || content.substring(range.argumentsStart(), range.argumentsEnd()) + .matches("(?s).*\\bname\\s*=\\s*\\\"" + java.util.regex.Pattern.quote(toolName) + "\\\".*")) { + return range; + } + offset = content.indexOf("@" + annotationName, closingParenthesis + 1); + } + return null; + } + + private static int findAnnotationEnd(String content, int openingParenthesis) { + int depth = 0; + boolean inString = false; + boolean escaped = false; + for (int index = openingParenthesis; index < content.length(); index++) { + char character = content.charAt(index); + if (inString) { + if (escaped) { + escaped = false; + } else if (character == '\\') { + escaped = true; + } else if (character == '\"') { + inString = false; + } + } else if (character == '\"') { + inString = true; + } else if (character == '(') { + depth++; + } else if (character == ')' && --depth == 0) { + return index; + } + } + return -1; + } + + private record AnnotationRange(int argumentsStart, int argumentsEnd) { + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidationRunner.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidationRunner.java new file mode 100644 index 00000000..414843e3 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidationRunner.java @@ -0,0 +1,21 @@ +package io.shinhanlife.dap.lib.validation; + +import java.nio.file.Path; + +/** Gradle entry point for validating unique MCP Tool names before packaging. */ +public final class McpToolNameValidationRunner { + + private McpToolNameValidationRunner() { + } + + public static void main(String[] args) { + if (args.length != 1) { + throw new IllegalArgumentException("Usage: McpToolNameValidationRunner "); + } + validate(Path.of(args[0])); + } + + static void validate(Path projectRoot) { + McpToolNameValidator.assertUnique(projectRoot); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidator.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidator.java new file mode 100644 index 00000000..d662c925 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/McpToolNameValidator.java @@ -0,0 +1,173 @@ +package io.shinhanlife.dap.lib.validation; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @package io.shinhanlife.dap.lib.validation + * @className McpToolNameValidator + * @description Validates unique MCP SDK tool names across tool modules + * @author 0986406 + * @create 2026.07.27 + *
    + * ---------- revision history ----------
    + * date       author    description
    + * ---------- --------- ---------------------------
    + * 2026.07.27 0986406    initial creation
    + * 
    + */ +public final class McpToolNameValidator { + + private static final Pattern TOOL_NAME_PATTERN = Pattern.compile("\\bname\\s*=\\s*\\\"([^\\\"]+)\\\""); + private static final Pattern TOOL_NAME_CONVENTION = Pattern.compile("^[a-zA-Z0-9_-]{1,128}$"); + + private McpToolNameValidator() { + } + + public static void assertUnique(Path projectRoot) { + Map> declarationsByName = new LinkedHashMap<>(); + + try (var modules = Files.list(projectRoot)) { + modules.filter(Files::isDirectory) + .filter(path -> path.getFileName().toString().startsWith("dap-was-")) + .filter(path -> !path.getFileName().toString().equals("dap-was-lib")) + .sorted() + .forEach(module -> collectDeclarations(module, declarationsByName)); + } catch (IOException exception) { + throw new UncheckedIOException("Failed to scan MCP tool modules", exception); + } + + List>> invalidNames = declarationsByName.entrySet().stream() + .filter(entry -> !TOOL_NAME_CONVENTION.matcher(entry.getKey()).matches()) + .sorted(Map.Entry.comparingByKey()) + .toList(); + + if (!invalidNames.isEmpty()) { + throw new IllegalStateException(buildInvalidNameMessage(invalidNames)); + } + List>> duplicates = declarationsByName.entrySet().stream() + .filter(entry -> entry.getValue().size() > 1) + .sorted(Map.Entry.comparingByKey()) + .toList(); + + if (!duplicates.isEmpty()) { + throw new IllegalStateException(buildDuplicateMessage(duplicates)); + } + } + + private static void collectDeclarations(Path module, Map> declarationsByName) { + Path sourceDirectory = module.resolve("src/main/java"); + if (!Files.isDirectory(sourceDirectory)) { + return; + } + + try (var sources = Files.walk(sourceDirectory)) { + sources.filter(path -> path.toString().endsWith(".java")) + .sorted() + .forEach(source -> collectDeclarations(module.getFileName().toString(), source, declarationsByName)); + } catch (IOException exception) { + throw new UncheckedIOException("Failed to scan module " + module.getFileName(), exception); + } + } + + private static void collectDeclarations(String moduleName, Path source, + Map> declarationsByName) { + String content; + try { + content = Files.readString(source); + } catch (IOException exception) { + throw new UncheckedIOException("Failed to read " + source, exception); + } + + int annotationOffset = content.indexOf("@McpTool"); + while (annotationOffset >= 0) { + int openingParenthesis = content.indexOf('(', annotationOffset); + int closingParenthesis = findAnnotationEnd(content, openingParenthesis); + if (openingParenthesis < 0 || closingParenthesis < 0) { + break; + } + + Matcher matcher = TOOL_NAME_PATTERN.matcher(content.substring(openingParenthesis + 1, closingParenthesis)); + if (matcher.find()) { + String toolName = matcher.group(1); + int line = 1 + (int) content.substring(0, annotationOffset).chars().filter(character -> character == '\n').count(); + declarationsByName.computeIfAbsent(toolName, ignored -> new ArrayList<>()) + .add(new ToolDeclaration(moduleName, source, line)); + } + annotationOffset = content.indexOf("@McpTool", closingParenthesis + 1); + } + } + + private static int findAnnotationEnd(String content, int openingParenthesis) { + if (openingParenthesis < 0) { + return -1; + } + + int depth = 0; + boolean inString = false; + boolean escaped = false; + for (int index = openingParenthesis; index < content.length(); index++) { + char character = content.charAt(index); + if (inString) { + if (escaped) { + escaped = false; + } else if (character == '\\') { + escaped = true; + } else if (character == '\"') { + inString = false; + } + continue; + } + if (character == '\"') { + inString = true; + } else if (character == '(') { + depth++; + } else if (character == ')' && --depth == 0) { + return index; + } + } + return -1; + } + + private static String buildInvalidNameMessage(List>> invalidNames) { + StringBuilder message = new StringBuilder("Invalid MCP tool name(s): expected 1-128 characters using letters, digits, underscores, or hyphens."); + for (Map.Entry> invalidName : invalidNames) { + message.append("\n\n").append(invalidName.getKey()); + invalidName.getValue().stream() + .sorted(Comparator.comparing(ToolDeclaration::moduleName).thenComparing(declaration -> declaration.source().toString())) + .forEach(declaration -> message.append("\n- ") + .append(declaration.moduleName()) + .append(": ") + .append(declaration.source()) + .append(':').append(declaration.line())); + } + return message.toString(); + } + + private static String buildDuplicateMessage(List>> duplicates) { + StringBuilder message = new StringBuilder("Duplicate MCP tool name(s):"); + for (Map.Entry> duplicate : duplicates) { + message.append("\n\n").append(duplicate.getKey()); + duplicate.getValue().stream() + .sorted(Comparator.comparing(ToolDeclaration::moduleName).thenComparing(declaration -> declaration.source().toString())) + .forEach(declaration -> message.append("\n- ") + .append(declaration.moduleName()) + .append(": ") + .append(declaration.source()) + .append(':').append(declaration.line())); + } + return message.toString(); + } + + private record ToolDeclaration(String moduleName, Path source, int line) { + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/ToolArgumentSchemaValidator.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/ToolArgumentSchemaValidator.java new file mode 100644 index 00000000..46e54ea6 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/lib/validation/ToolArgumentSchemaValidator.java @@ -0,0 +1,30 @@ +package io.shinhanlife.dap.lib.validation; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.Error; +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import java.util.List; +import java.util.Map; + +/** Validates tool arguments with the NetworkNT version selected by the MCP SDK. */ +public class ToolArgumentSchemaValidator { + + private final ObjectMapper objectMapper; + + public ToolArgumentSchemaValidator(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + public List validate(Map schemaDefinition, Map arguments) throws Exception { + return validateValue(schemaDefinition, arguments); + } + + public List validateValue(Map schemaDefinition, Object value) throws Exception { + SchemaRegistry schemaRegistry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + Schema schema = schemaRegistry.getSchema(objectMapper.writeValueAsString(schemaDefinition)); + return schema.validate(objectMapper.writeValueAsString(value), InputFormat.JSON); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/dto/ToolMetadata.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/dto/ToolMetadata.java new file mode 100644 index 00000000..44e9f7d9 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/dto/ToolMetadata.java @@ -0,0 +1,133 @@ +package io.shinhanlife.dap.mcc.dto; + +import lombok.Getter; +import lombok.Setter; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; +import java.util.Set; +import java.util.List; +import java.util.HashSet; +import io.shinhanlife.dap.lib.dto.OperationType; +/** + * Tool(Agent)의 명세 및 라우팅 정보를 담고 있는 메타데이터 클래스 + * Redis 레지스트리에 저장되며, Planner와 Router 간의 통신 객체(Plan)로 사용됩니다. + */ +/** + * @package io.shinhanlife.dap.mcg.dto + * @className ToolMetadata + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class ToolMetadata { + + // 1. Tool 기본 정보 + private String uid; // UUID 형식의 고유 식별자 + private String semver; // 버전 (예: 1.0.0) + private String displayName; // 사람이 읽는 라벨 (1-128자) + private String name; // MCP 서브툴 명칭 (64자 이하, 예: CustomerSearchTool) + private String description; // 툴의 목적 및 설명 (LLM 프롬프트에 활용 가능) + + // 2. 파라미터 스키마 (JSON Schema 형태의 Map) + private Map parametersSchema; + + // 2-0. 프론트엔드 UI용 함수별 프롬프트 매핑 (추가됨) + private Map actionPrompts; + + // 2-1. 도메인 부서 그룹명 (category_key, 슬러그 형식) + private String categoryKey; + + // 2-2. 툴 처리 엔드포인트 URI 경로 (예: /api/tool/customer-info) + private String endpoint; + + // 2-3. Pod 실행 URL (독립적인 Microservice 라우팅용, 예: http://localhost:8082) + private String podUrl; + + // 2-4. 가시성 여부 + @Builder.Default + private Boolean visible = true; + + // 활성화 여부 + @Builder.Default + private Boolean enabled = true; + + // 2-5. Redis 등록 여부 (UI 표출용) + @Builder.Default + private Boolean isRegistered = true; + + // 2-6. HITL 승인 필요 여부 + @Builder.Default + private Boolean requiresApproval = false; + + @Builder.Default + private Boolean readOnlyHint = false; + + @Builder.Default + private Boolean destructiveHint = false; + + @Builder.Default + private Boolean idempotentHint = false; + + @Builder.Default + private Boolean openWorldHint = false; + + + + // 3. 연동 아키텍처 구분 (DIRECT / MCI_EAI) + private String integrationType; // 연동 타입: "DIRECT" 또는 "MCI_EAI" + + // 4. 레거시(MCI/EAI) 연동 시 필수 정보 (integrationType이 "MCI_EAI"일 때 사용) + private String mciServiceId; // MCI/EAI 호출을 위한 서비스 ID (예: CRM_001, LICO_992) + + // 5. 인프라 상태 정보 (DIRECT 연동 시 사용) + private Long lastHeartbeat; // Redis TTL 갱신용 마지막 하트비트 타임스탬프 + + // 6. 동적 서킷 브레이커 & 속도 제어 설정 (Registry 기반) + private Integer failureRateThreshold; // 서킷 브레이커 동작 기준 실패율 (%) + private Integer slidingWindowSize; // 서킷 브레이커 에러율 계산 표본 요청 수 + private Integer rateLimitForPeriod; // 속도 제어: 1초당 허용 최대 요청 수 + + // 7. Gateway 코어 제어용 설정 필드 추가 (재시도, 타임아웃, 오퍼레이션 타입) + @Builder.Default + private OperationType operationType = OperationType.READ; + + @Builder.Default + private Boolean retryEnabled = true; + + @Builder.Default + private Integer circuitBreakerFailureThreshold = 0; + + @Builder.Default + private Long circuitBreakerOpenMillis = 0L; + + @Builder.Default + private Long timeoutMillis = 0L; + + // --- Guardrail 호환성을 위한 메서드 추가 --- + public Set allowedArguments() { + if (parametersSchema == null || !parametersSchema.containsKey("properties")) return Set.of(); + return ((Map) parametersSchema.get("properties")).keySet(); + } + + public Set requiredArguments() { + if (parametersSchema == null || !parametersSchema.containsKey("required")) return Set.of(); + return new HashSet<>((List) parametersSchema.get("required")); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java new file mode 100644 index 00000000..3926a879 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/BusinessToolController.java @@ -0,0 +1,50 @@ +package io.shinhanlife.dap.mcc.presentation; + +import io.shinhanlife.dap.lib.mcp.McpRequestHeaders; +import io.shinhanlife.dap.lib.mcp.ToolExecutionResult; +import io.shinhanlife.dap.lib.mcp.McpToolExecutionService; +import io.shinhanlife.dap.lib.mcp.ToolRegistryHeartbeatSender; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** Legacy REST adapter for Tool Pod execution. */ +@RestController +@RequestMapping("/") +@RequiredArgsConstructor +public class BusinessToolController { + + private final ToolRegistryHeartbeatSender toolRegistryHeartbeatSender; + private final McpToolExecutionService toolExecutionService; + + @GetMapping("/mcp/api/v1/tools/local") + public List getLocalTools() { + return toolRegistryHeartbeatSender.getAllScannedTools(); + } + + @PostMapping("/mcp/{name}") + public ResponseEntity executeDynamicTool( + @PathVariable("name") String functionName, + @RequestHeader(value = "X-Request-Id", required = false) String headerRequestId, + @RequestHeader(value = "trace-id", required = false) String traceId, + @RequestHeader(value = "request-id", required = false) String requestId, + @RequestHeader(value = "employee-id", required = false) String encryptedEmployeeId, + @RequestBody(required = false) Map arguments) { + ToolExecutionResult result = toolExecutionService.execute( + functionName, + new McpRequestHeaders(headerRequestId, traceId, requestId, encryptedEmployeeId), + arguments); + ResponseEntity.BodyBuilder response = ResponseEntity.status(result.statusCode()); + result.headers().forEach(response::header); + return response.body(result.body()); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolManifestController.java b/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolManifestController.java new file mode 100644 index 00000000..a8eeea7f --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/dap/mcc/presentation/ToolManifestController.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.presentation; + +import io.shinhanlife.dap.lib.manifest.ToolManifestResponse; +import io.shinhanlife.dap.lib.manifest.ToolManifestService; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; + +/** Read-only Tool Service manifest endpoint for MCP background discovery. */ +@RestController +public class ToolManifestController { + + private final ToolManifestService toolManifestService; + + public ToolManifestController(ToolManifestService toolManifestService) { + this.toolManifestService = toolManifestService; + } + + @GetMapping(value = "/tool-manifest", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity getManifest( + @RequestHeader(value = HttpHeaders.IF_NONE_MATCH, required = false) String ifNoneMatch) { + ToolManifestResponse manifest = toolManifestService.currentManifest(); + String eTag = '"' + manifest.revision() + '"'; + if (eTag.equals(ifNoneMatch)) { + return ResponseEntity.status(304).eTag(eTag).build(); + } + return ResponseEntity.ok().eTag(eTag).body(manifest); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/BaseException.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/BaseException.java new file mode 100644 index 00000000..a2d8cffc --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/BaseException.java @@ -0,0 +1,43 @@ +package io.shinhanlife.glow; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +//@Schema(description = "응답 에러 객체. 성공 케이스일 경우 null. 실제 에러가 발생할 경우에만 예외명, 예외 메시지 필드 세팅 예정.") +/** + * @package io.shinhanlife.glow + * @className BaseException + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +public class BaseException { + +// @Schema(description = "Error 코드 Meta 참조 운영. (예) 20001, 50001 등", shinhanlife = "20001") + String code; + +// @Schema(description = "메시지") + String message; + +// @Schema(description = "예외명.", shinhanlife = "NullPointerException") + @Builder.Default + String exceptionName = ""; + +// @Schema(description = "예외상세", shinhanlife = "StackTrace") + @Builder.Default + String exceptionDetail = ""; + +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/BaseResponse.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/BaseResponse.java new file mode 100644 index 00000000..7ddcfca7 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/BaseResponse.java @@ -0,0 +1,39 @@ +package io.shinhanlife.glow; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +/** + * @package io.shinhanlife.glow + * @className BaseResponse + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@ToString +@Getter +@Builder +@AllArgsConstructor +public class BaseResponse { + +// @Schema(description = "응답 코드 HTTP STATUS 오류 - 실제 Header 는 200 으로 내려감", shinhanlife = "200") + private int code; + +// @Schema(description = "응답 메시지. 응답 코드와 매핑된 메시지.", shinhanlife = "데이터 생성 성공") + private String message; + +// @Schema(description = "응답 본문 데이터. 실제 비즈니스 데이터.") + private T data; + + private BaseException error; + +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/BizException.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/BizException.java new file mode 100644 index 00000000..835c800d --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/BizException.java @@ -0,0 +1,20 @@ +package io.shinhanlife.glow; + +/** + * @package io.shinhanlife.glow + * @className BizException + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +public class BizException extends RuntimeException { + public BizException(String s) { + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowAppServiceId.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowAppServiceId.java new file mode 100644 index 00000000..fe4ec3ac --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowAppServiceId.java @@ -0,0 +1,28 @@ +package io.shinhanlife.glow; + + +/** + * @package io.shinhanlife.glow + * @className GlowAppServiceId + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +import java.lang.annotation.*; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface GlowAppServiceId { + + String value(); + + String description() default ""; +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowControllerId.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowControllerId.java new file mode 100644 index 00000000..d9e806c7 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowControllerId.java @@ -0,0 +1,20 @@ +package io.shinhanlife.glow; + + +/** + * @package io.shinhanlife.glow + * @className GlowControllerId + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +public @interface GlowControllerId { + String value(); +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowIndexPaging.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowIndexPaging.java new file mode 100644 index 00000000..c03265ff --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowIndexPaging.java @@ -0,0 +1,24 @@ +package io.shinhanlife.glow; + + +/** + * @package io.shinhanlife.glow + * @className GlowIndexPaging + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +import java.lang.annotation.*; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface GlowIndexPaging { +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowLogTarget.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowLogTarget.java new file mode 100644 index 00000000..a748a067 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowLogTarget.java @@ -0,0 +1,29 @@ +package io.shinhanlife.glow; + +import java.lang.annotation.*; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface GlowLogTarget { + + Target[] value() default {}; + +/** + * @package io.shinhanlife.glow + * @className Target + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ + enum Target { + FILE, CONSOLE + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowLogger.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowLogger.java new file mode 100644 index 00000000..0cf9e84e --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowLogger.java @@ -0,0 +1,33 @@ +package io.shinhanlife.glow; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * @package io.shinhanlife.glow + * @className GlowLogger + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Component +@Scope("prototype") +public class GlowLogger { + + private Logger log = LoggerFactory.getLogger(GlowLogger.class); + + public void debug(String message, Object... args) { log.debug(message, args); } + public void info(String message, Object... args) { log.info(message, args); } + public void warn(String message, Object... args) { log.warn(message, args); } + public void error(String message, Object... args) { log.error(message, args); } + public void error(String message, Throwable t) { log.error(message, t); } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowMciFieldInfo.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowMciFieldInfo.java new file mode 100644 index 00000000..bce1efef --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowMciFieldInfo.java @@ -0,0 +1,44 @@ +package io.shinhanlife.glow; + + +/** + * @package io.shinhanlife.glow + * @className GlowMciFieldInfo + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +import java.lang.annotation.*; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface GlowMciFieldInfo { + + /** + * 필드 순서 + */ + int order(); + + /** + * 필드 길이 (List인 경우 전체 길이로 활용될 수 있음) + */ + int length(); + + /** + * 필드 설명 + */ + String description() default ""; + + /** + * List 매핑 시 대상 DTO 클래스 + */ + Class target() default void.class; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowMybatisMapper.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowMybatisMapper.java new file mode 100644 index 00000000..9c6c09f7 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowMybatisMapper.java @@ -0,0 +1,33 @@ +package io.shinhanlife.glow; + + +/** + * @package io.shinhanlife.glow + * @className GlowMybatisMapper + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +import org.apache.ibatis.annotations.Mapper; +import org.springframework.stereotype.Component; + +import java.lang.annotation.*; + +/** + * MyBatis Mapper 인터페이스를 나타내는 어노테이션 + * MyBatis Mapper와 동일한 기능을 제공합니다. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Mapper +@Component +public @interface GlowMybatisMapper { +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowServiceGroupId.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowServiceGroupId.java new file mode 100644 index 00000000..f6c38166 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowServiceGroupId.java @@ -0,0 +1,22 @@ +package io.shinhanlife.glow; + + +/** + * @package io.shinhanlife.glow + * @className GlowServiceGroupId + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +public @interface GlowServiceGroupId { + String value(); + + String description(); +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowTrgmField.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowTrgmField.java new file mode 100644 index 00000000..6e44dfae --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/GlowTrgmField.java @@ -0,0 +1,41 @@ +package io.shinhanlife.glow; + + +/** + * @package io.shinhanlife.glow + * @className GlowTrgmField + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +import java.lang.annotation.*; + +@Deprecated(forRemoval = false) +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface GlowTrgmField { + + /** + * 필드 순서 + */ + int order(); + + /** + * 필드 길이 + */ + int length(); + + /** + * 필드 설명 + */ + String description() default ""; + +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/PageInfo.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/PageInfo.java new file mode 100644 index 00000000..985d76a4 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/PageInfo.java @@ -0,0 +1,83 @@ +package io.shinhanlife.glow; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import io.shinhanlife.glow.communication.annotation.GlowTrgmField; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.apache.ibatis.session.RowBounds; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @package io.shinhanlife.glow + * @className PageInfo + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Getter +@Setter +@JsonIgnoreProperties(ignoreUnknown = true) +@EqualsAndHashCode(callSuper = true) +public class PageInfo extends RowBounds implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 페이지번호 ( 입력값 ) + */ + @GlowTrgmField(order = 1, length = 5, description = "페이지번호") + private int pageNo; + + /** + * 페이지 데이터 건수 ( 열 건수, 입력값 ) + */ + @GlowTrgmField(order = 2, length = 5, description = "페이지데이터건수") + private int pageDataCc; + + /** + * 총페이지 수 ( 리턴값 ) + */ + @GlowTrgmField(order = 3, length = 10, description = "총페이지수") + private int totaPageCn; + + /** + * 총 페이지 데이터 건수 ( 리턴값 ) + */ + @GlowTrgmField(order = 4, length = 10, description = "총페이지데이터건수") + private int totaPageDataCc; + + public PageInfo(int pageNo, int pageDataCc) { + super(((pageNo <= 0 ? 1 : pageNo) - 1) * pageDataCc, pageDataCc); + this.pageNo = pageNo; + this.pageDataCc = pageDataCc; + } + + public PageInfo() { + + } + + @JsonIgnore + @Override + public int getOffset() { + return super.getOffset(); + } + + @JsonIgnore + @Override + public int getLimit() { + return super.getLimit(); + } + +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/ResponseCode.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/ResponseCode.java new file mode 100644 index 00000000..84d60e95 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/ResponseCode.java @@ -0,0 +1,45 @@ +package io.shinhanlife.glow; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +/** + * @package io.shinhanlife.glow + * @className ResponseCode + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Getter +@RequiredArgsConstructor +public enum ResponseCode { + + /* ===================== 성공 ===================== */ + SUCCESS(HttpStatus.OK, "정상 처리되었습니다."), + CREATED(HttpStatus.CREATED, "데이터 생성 성공"), + + /* ===================== 클라이언트 오류 (4xx) ===================== */ + BAD_REQUEST(HttpStatus.BAD_REQUEST, "잘못된 요청입니다."), + INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "유효하지 않은 파라미터입니다."), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "인증이 필요합니다."), + FORBIDDEN(HttpStatus.FORBIDDEN, "접근 권한이 없습니다."), + NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 리소스를 찾을 수 없습니다."), + METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "허용되지 않은 HTTP 메서드입니다."), + CONFLICT(HttpStatus.CONFLICT, "이미 존재하는 데이터입니다."), + + /* ===================== 서버 오류 (5xx) ===================== */ + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류가 발생했습니다."), + SERVICE_UNAVAILABLE(HttpStatus.SERVICE_UNAVAILABLE, "서비스를 사용할 수 없습니다."); + + private final HttpStatus status; + private final String message; + +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/ResponseUtil.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/ResponseUtil.java new file mode 100644 index 00000000..cda4738e --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/ResponseUtil.java @@ -0,0 +1,125 @@ +package io.shinhanlife.glow; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +/** + * @package io.shinhanlife.glow + * @className ResponseUtil + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +public final class ResponseUtil { + + private ResponseUtil() { + + } + + public static ResponseEntity> ok() { + return ResponseEntity.ok( + BaseResponse.builder() + .code(ResponseCode.SUCCESS.getStatus().value()) + .message(ResponseCode.SUCCESS.getMessage()) + .build() + ); + } + + public static ResponseEntity> ok(T data) { + return ResponseEntity.ok( + BaseResponse.builder() + .code(ResponseCode.SUCCESS.getStatus().value()) + .message(ResponseCode.SUCCESS.getMessage()) + .data(data) + .build() + ); + } + + public static ResponseEntity> ok(T data, ResponseCode responseCode) { + return ResponseEntity.ok( + BaseResponse.builder() + .code(responseCode.getStatus().value()) + .message(responseCode.getMessage()) + .data(data) + .build() + ); + } + + public static ResponseEntity> error( + HttpStatus status, String code, String message + ) { + return ResponseEntity.status(status) + .body( + BaseResponse.builder() + .code(status.value()) + .error( + BaseException.builder() + .code(code) + .message(message) + .build() + ) + .build() + ); + } + + public static ResponseEntity> error( + HttpStatus status, String code, String message, String exceptionName, String stackTrace + ) { + return ResponseEntity.status(status) + .body( + BaseResponse.builder() + .code(status.value()) + .error( + BaseException.builder() + .code(code) + .message(message) + .exceptionName(exceptionName) + .exceptionDetail(stackTrace) + .build() + ) + .build() + ); + } + + public static ResponseEntity> okError( + HttpStatus status, String code, String message + ) { + return ResponseEntity.ok( + BaseResponse.builder() + .code(status.value()) + .error( + BaseException.builder() + .code(code) + .message(message) + .build() + ) + .build() + ); + } + + public static ResponseEntity> okError( + HttpStatus status, String code, String message, String exceptionName, String stackTrace + ) { + return ResponseEntity.ok( + BaseResponse.builder() + .code(status.value()) + .error( + BaseException.builder() + .code(code) + .message(message) + .exceptionName(exceptionName) + .exceptionDetail(stackTrace) + .build() + ) + .build() + ); + } + +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/ICommunication.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/ICommunication.java new file mode 100644 index 00000000..f93b1ea2 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/ICommunication.java @@ -0,0 +1,6 @@ +package io.shinhanlife.glow.communication; + +/** Minimal Glow communication contract used by the temporary compatibility layer. */ +public interface ICommunication { + O sync(I request); +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/annotation/GlowTrgmField.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/annotation/GlowTrgmField.java new file mode 100644 index 00000000..9463acf2 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/annotation/GlowTrgmField.java @@ -0,0 +1,26 @@ +package io.shinhanlife.glow.communication.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** Declares fixed-width Glow transaction-message metadata for a DTO field. */ +@Target({ElementType.LOCAL_VARIABLE, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface GlowTrgmField { + + int order(); + + int length() default 0; + + int decimal() default 0; + + String description() default ""; + + String target() default ""; + + String type() default ""; +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/CommonHeader.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/CommonHeader.java new file mode 100644 index 00000000..7c991d52 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/CommonHeader.java @@ -0,0 +1,8 @@ +package io.shinhanlife.glow.communication.dto; +import lombok.Data; +@Data +public class CommonHeader { + private String itrfId; + private String rcvSvcId; + private String tgrmDalRsltCd; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/HeaderDefaults.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/HeaderDefaults.java new file mode 100644 index 00000000..7df3c0a7 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/HeaderDefaults.java @@ -0,0 +1,6 @@ +package io.shinhanlife.glow.communication.dto; + +public enum HeaderDefaults { + ITRF_ID, RCV_SVC_ID, STR_YMD, ACNT_OGNZ_NO, PSMR_ASRT_CD, SBSN_RULP_ASRT_CD, BSDU_CD, BSQU_CD, + INDV_CTIN_ROLE_CD, SCRN_ID, OGNZ_ASRT_CD, OGNZ_LEVE_CD, TGRM_CREA_CHNN_TYPE_CD, ENVR_TYPE_CD +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/Transfer.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/Transfer.java new file mode 100644 index 00000000..31383d77 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/dto/Transfer.java @@ -0,0 +1,20 @@ +package io.shinhanlife.glow.communication.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * TODO: 실제 Glow Framework 의존성(JAR)이 추가되면 이 Mock 클래스를 삭제하세요. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Transfer { + private Object header; + private T body; + private Class resBodyClass; + private Object message; +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/exception/ItrfException.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/exception/ItrfException.java new file mode 100644 index 00000000..464cc52e --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/exception/ItrfException.java @@ -0,0 +1,10 @@ +package io.shinhanlife.glow.communication.exception; + +public class ItrfException extends Exception { + public ItrfException(String msg) { + super(msg); + } + public ItrfException(String msgCd, String msgPrnAttrCd, String msgCt, String anxMsgCt) { + super(msgCd + ": " + msgCt); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/eai/component/GlowEaiComponent.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/eai/component/GlowEaiComponent.java new file mode 100644 index 00000000..99bc00be --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/eai/component/GlowEaiComponent.java @@ -0,0 +1,26 @@ +package io.shinhanlife.glow.communication.module.eai.component; + +import io.shinhanlife.glow.communication.dto.Transfer; + +import org.springframework.stereotype.Component; + +/** + * TODO: 실제 Glow Framework 의존성(JAR)이 추가되면 이 Mock 클래스를 삭제하세요. + */ +@Component +public class GlowEaiComponent { + public Transfer sync(Transfer request) { + // Mock 구현 + Transfer response = new Transfer<>(); + response.setHeader(request.getHeader()); + response.setResBodyClass((Class) request.getResBodyClass()); + return response; + } + + public Transfer call(Transfer request, Class resBody) { + Transfer response = new Transfer<>(); + response.setHeader(request.getHeader()); + response.setResBodyClass(resBody); + return response; + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/component/GlowHttpComponent.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/component/GlowHttpComponent.java new file mode 100644 index 00000000..34265031 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/component/GlowHttpComponent.java @@ -0,0 +1,98 @@ +package io.shinhanlife.glow.communication.module.http.component; + +import io.shinhanlife.glow.communication.ICommunication; +import io.shinhanlife.glow.communication.module.http.dto.HttpBody; +import io.shinhanlife.glow.communication.module.http.dto.HttpHeader; +import io.shinhanlife.glow.communication.module.http.dto.HttpTransfer; +import java.util.List; +import java.util.Map; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClient; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * Temporary compatibility implementation of the internal Glow HTTP component. + * Replace this class with the official Glow HTTP JAR when it is supplied. + */ +@Component +public class GlowHttpComponent implements ICommunication, ResponseEntity> { + + private final RestClient restClient; + + public GlowHttpComponent(RestClient.Builder restClientBuilder) { + this.restClient = restClientBuilder.build(); + } + + @Override + public ResponseEntity sync(HttpTransfer request) { + if (request == null || request.getHeader() == null || request.getMethod() == null + || !StringUtils.hasText(request.getDomain())) { + throw new IllegalArgumentException("Glow HTTP request header, domain, and method are required."); + } + if (HttpMethod.GET.equals(request.getMethod())) { + return get(request); + } + if (HttpMethod.POST.equals(request.getMethod())) { + return post(request); + } + if (HttpMethod.PUT.equals(request.getMethod())) { + return put(request); + } + if (HttpMethod.DELETE.equals(request.getMethod())) { + return delete(request); + } + throw new IllegalArgumentException("Unsupported HTTP method: " + request.getMethod()); + } + + private ResponseEntity get(HttpTransfer request) { + return toHttpBody(restClient.get().uri(buildUri(request, true)) + .headers(headers -> applyHeaders(headers, request.getHeader())) + .retrieve().toEntity(String.class)); + } + + private ResponseEntity post(HttpTransfer request) { + return toHttpBody(restClient.post().uri(buildUri(request, false)) + .contentType(contentType(request)).headers(headers -> applyHeaders(headers, request.getHeader())) + .body(request.getBody()).retrieve().toEntity(String.class)); + } + + private ResponseEntity put(HttpTransfer request) { + return toHttpBody(restClient.put().uri(buildUri(request, false)) + .contentType(contentType(request)).headers(headers -> applyHeaders(headers, request.getHeader())) + .body(request.getBody()).retrieve().toEntity(String.class)); + } + + private ResponseEntity delete(HttpTransfer request) { + return toHttpBody(restClient.delete().uri(buildUri(request, true)) + .headers(headers -> applyHeaders(headers, request.getHeader())) + .retrieve().toEntity(String.class)); + } + + private String buildUri(HttpTransfer request, boolean includeQueryParameters) { + String uri = request.getDomain() + (request.getUri() == null ? "" : request.getUri()); + if (!includeQueryParameters || !(request.getBody() instanceof Map parameters)) { + return uri; + } + UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(uri); + parameters.forEach((key, value) -> { if (key != null && value != null) builder.queryParam(String.valueOf(key), value); }); + return builder.build().encode().toUriString(); + } + + private MediaType contentType(HttpTransfer request) { + return request.getContentType() == null ? MediaType.APPLICATION_JSON : request.getContentType(); + } + + private void applyHeaders(HttpHeaders target, HttpHeader source) { + target.setAccept(List.of(MediaType.APPLICATION_JSON)); + source.getValues().forEach((name, value) -> { if (StringUtils.hasText(name) && StringUtils.hasText(value)) target.set(name, value); }); + } + + private ResponseEntity toHttpBody(ResponseEntity response) { + return new ResponseEntity<>(new HttpBody(response.getBody()), response.getHeaders(), response.getStatusCode()); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpBody.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpBody.java new file mode 100644 index 00000000..2406e7b5 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpBody.java @@ -0,0 +1,5 @@ +package io.shinhanlife.glow.communication.module.http.dto; + +/** Raw body returned by the Glow HTTP transport. */ +public record HttpBody(String content) { +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpHeader.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpHeader.java new file mode 100644 index 00000000..396f4a7d --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpHeader.java @@ -0,0 +1,18 @@ +package io.shinhanlife.glow.communication.module.http.dto; + +import java.util.LinkedHashMap; +import java.util.Map; +import lombok.Getter; +import lombok.Setter; + +/** Glow HTTP request headers and timeout metadata. */ +@Getter +@Setter +public class HttpHeader { + private Map values = new LinkedHashMap<>(); + private int readTimeout; + + public void set(String name, String value) { + values.put(name, value); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpTransfer.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpTransfer.java new file mode 100644 index 00000000..30fb28e9 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/http/dto/HttpTransfer.java @@ -0,0 +1,19 @@ +package io.shinhanlife.glow.communication.module.http.dto; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; + +/** Glow HTTP request envelope. Use HttpTransfer.http() to build a request. */ +@Getter +@Builder(builderMethodName = "http") +public class HttpTransfer { + private final HttpHeader header; + private final String domain; + private final String uri; + private final HttpMethod method; + private final MediaType contentType; + private final Class responseEntity; + private final T body; +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/mci/component/GlowMciComponent.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/mci/component/GlowMciComponent.java new file mode 100644 index 00000000..ecbe4a96 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/module/mci/component/GlowMciComponent.java @@ -0,0 +1,18 @@ +package io.shinhanlife.glow.communication.module.mci.component; + +import io.shinhanlife.glow.communication.dto.Transfer; +import org.springframework.stereotype.Component; + +/** + * TODO: 실제 Glow Framework 의존성(JAR)이 추가되면 이 Mock 클래스를 삭제하세요. + */ +@Component +public class GlowMciComponent { + + public Transfer sync(Transfer request) { + // 실제 Glow HTTP 통신 (GlowHttpHeaderUtil, HttpClient 등) 수행 시뮬레이션 + return (Transfer) Transfer.builder() + .body(new Object()) + .build(); + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/util/CommonHeaderFactory.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/util/CommonHeaderFactory.java new file mode 100644 index 00000000..fbb154cd --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/communication/util/CommonHeaderFactory.java @@ -0,0 +1,18 @@ +package io.shinhanlife.glow.communication.util; +import io.shinhanlife.glow.communication.dto.CommonHeader; +import io.shinhanlife.glow.communication.dto.HeaderDefaults; +import java.util.Map; +public class CommonHeaderFactory { + public static CommonHeader createRequestHeader(Map commonHeaderMap) { + CommonHeader header = new CommonHeader(); + header.setItrfId(commonHeaderMap.get(HeaderDefaults.ITRF_ID)); + header.setRcvSvcId(commonHeaderMap.get(HeaderDefaults.RCV_SVC_ID)); + return header; + } + public static CommonHeader createRequestHeader(String itrfId, String rcvSvcId) { + CommonHeader header = new CommonHeader(); + header.setItrfId(itrfId); + header.setRcvSvcId(rcvSvcId); + return header; + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/db/dto/AuditInfo.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/db/dto/AuditInfo.java new file mode 100644 index 00000000..b9717f8d --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/db/dto/AuditInfo.java @@ -0,0 +1,40 @@ +package io.shinhanlife.glow.db.dto; + +import lombok.Getter; +import lombok.Setter; +import lombok.Builder; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import java.util.Date; + +/** + * @package io.shinhanlife.glow.db.dto + * @className AuditInfo + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class AuditInfo { + private Date systRgiDt; // 시스템등록일시 + private String systRgiPrafNo; // 시스템등록인사번호 + private String systRgiOgnzNo; // 시스템등록조직번호 + private String systRgiSystCd; // 시스템등록시스템코드 + private String systRgiPrgrId; // 시스템등록프로그램ID + private Date systChgDt; // 시스템변경일시 + private String systChgPrafNo; // 시스템변경인사번호 + private String systChgOgnzNo; // 시스템변경조직번호 + private String systChgSystCd; // 시스템변경시스템코드 + private String systChgPrgrId; // 시스템변경프로그램ID +} \ No newline at end of file diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/util/GlowMciParser.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/util/GlowMciParser.java new file mode 100644 index 00000000..5216ae1e --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/util/GlowMciParser.java @@ -0,0 +1,105 @@ +package io.shinhanlife.glow.util; + +import io.shinhanlife.glow.GlowMciFieldInfo; +import lombok.extern.slf4j.Slf4j; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * @package io.shinhanlife.glow.util + * @className GlowMciParser + * @description MCI 고정 길이 전문 파싱 유틸리티 (GlowMciFieldInfo 기반) + * @author 0986406 + * @create 2026.09.01 + */ +@Slf4j +public class GlowMciParser { + + public static T parse(String mciString, Class clazz) { + if (mciString == null || mciString.isEmpty()) { + return null; + } + + try { + T instance = clazz.getDeclaredConstructor().newInstance(); + + List fields = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(GlowMciFieldInfo.class)) { + fields.add(field); + } + } + + fields.sort(Comparator.comparingInt(f -> f.getAnnotation(GlowMciFieldInfo.class).order())); + + int currentIndex = 0; + for (Field field : fields) { + GlowMciFieldInfo annotation = field.getAnnotation(GlowMciFieldInfo.class); + int length = annotation.length(); + + if (currentIndex >= mciString.length()) { + break; + } + + int endIndex = Math.min(currentIndex + length, mciString.length()); + String value = mciString.substring(currentIndex, endIndex); + + field.setAccessible(true); + setFieldValue(instance, field, value, annotation); + + currentIndex += length; + } + + return instance; + } catch (Exception e) { + log.error("GlowMciFieldInfo 파싱 중 오류 발생: {}", e.getMessage(), e); + throw new RuntimeException("MCI 전문 파싱 오류", e); + } + } + + private static void setFieldValue(Object instance, Field field, String value, GlowMciFieldInfo annotation) throws IllegalAccessException { + Class fieldType = field.getType(); + String trimmedValue = value.trim(); + + if (fieldType == String.class) { + field.set(instance, trimmedValue); + } else if (fieldType == int.class || fieldType == Integer.class) { + field.set(instance, trimmedValue.isEmpty() ? 0 : Integer.parseInt(trimmedValue)); + } else if (fieldType == long.class || fieldType == Long.class) { + field.set(instance, trimmedValue.isEmpty() ? 0L : Long.parseLong(trimmedValue)); + } else if (fieldType == boolean.class || fieldType == Boolean.class) { + field.set(instance, Boolean.parseBoolean(trimmedValue)); + } else if (fieldType == double.class || fieldType == Double.class) { + field.set(instance, trimmedValue.isEmpty() ? 0.0 : Double.parseDouble(trimmedValue)); + } else if (List.class.isAssignableFrom(fieldType)) { + Class targetClass = annotation.target(); + if (targetClass != void.class) { + List list = new ArrayList<>(); + int itemLength = calculateTotalLength(targetClass); + if (itemLength > 0) { + for (int i = 0; i < value.length(); i += itemLength) { + int end = Math.min(i + itemLength, value.length()); + String itemStr = value.substring(i, end); + if (itemStr.trim().isEmpty()) continue; + list.add(parse(itemStr, targetClass)); + } + } + field.set(instance, list); + } + } else { + field.set(instance, trimmedValue); + } + } + + private static int calculateTotalLength(Class clazz) { + int total = 0; + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(GlowMciFieldInfo.class)) { + total += field.getAnnotation(GlowMciFieldInfo.class).length(); + } + } + return total; + } +} diff --git a/dap-was-lib/src/main/java/io/shinhanlife/glow/util/GlowTrgmParser.java b/dap-was-lib/src/main/java/io/shinhanlife/glow/util/GlowTrgmParser.java new file mode 100644 index 00000000..1b89ede9 --- /dev/null +++ b/dap-was-lib/src/main/java/io/shinhanlife/glow/util/GlowTrgmParser.java @@ -0,0 +1,92 @@ +package io.shinhanlife.glow.util; + +import io.shinhanlife.glow.communication.annotation.GlowTrgmField; +import lombok.extern.slf4j.Slf4j; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * @package io.shinhanlife.glow.util + * @className GlowTrgmParser + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Slf4j +public class GlowTrgmParser { + + /** + * 고정 길이 문자열 전문을 @GlowTrgmField 어노테이션 정보에 따라 DTO 객체로 파싱합니다. + * @param trgmString 원본 전문 문자열 + * @param clazz 매핑할 DTO 클래스 + * @return 파싱된 DTO 인스턴스 + */ + public static T parse(String trgmString, Class clazz) { + if (trgmString == null || trgmString.isEmpty()) { + return null; + } + + try { + T instance = clazz.getDeclaredConstructor().newInstance(); + + List fields = new ArrayList<>(); + for (Field field : clazz.getDeclaredFields()) { + if (field.isAnnotationPresent(GlowTrgmField.class)) { + fields.add(field); + } + } + + fields.sort(Comparator.comparingInt(f -> f.getAnnotation(GlowTrgmField.class).order())); + + int currentIndex = 0; + for (Field field : fields) { + GlowTrgmField annotation = field.getAnnotation(GlowTrgmField.class); + int length = annotation.length(); + + if (currentIndex >= trgmString.length()) { + break; + } + + int endIndex = Math.min(currentIndex + length, trgmString.length()); + String value = trgmString.substring(currentIndex, endIndex).trim(); + + field.setAccessible(true); + setFieldValue(instance, field, value); + + currentIndex += length; + } + + return instance; + } catch (Exception e) { + log.error("GlowTrgmField 파싱 중 오류 발생: {}", e.getMessage(), e); + throw new RuntimeException("전문 파싱 오류", e); + } + } + + private static void setFieldValue(Object instance, Field field, String value) throws IllegalAccessException { + Class fieldType = field.getType(); + + if (fieldType == String.class) { + field.set(instance, value); + } else if (fieldType == int.class || fieldType == Integer.class) { + field.set(instance, value.isEmpty() ? 0 : Integer.parseInt(value)); + } else if (fieldType == long.class || fieldType == Long.class) { + field.set(instance, value.isEmpty() ? 0L : Long.parseLong(value)); + } else if (fieldType == boolean.class || fieldType == Boolean.class) { + field.set(instance, Boolean.parseBoolean(value)); + } else if (fieldType == double.class || fieldType == Double.class) { + field.set(instance, value.isEmpty() ? 0.0 : Double.parseDouble(value)); + } else { + field.set(instance, value); + } + } +} diff --git a/dap-was-lib/src/main/resources/glow/application-glow-dev.yml b/dap-was-lib/src/main/resources/glow/application-glow-dev.yml new file mode 100644 index 00000000..e8b864cf --- /dev/null +++ b/dap-was-lib/src/main/resources/glow/application-glow-dev.yml @@ -0,0 +1,19 @@ +# Glow 개발 환경 설정 +spring: + config: + activate: + on-profile: dev + +glow: + communication: + common: + env-type: D + mci: + host: ${GLOW_COMMUNICATION_MCI_HOST:https://dev-ichmci.shinhanlife.co.kr} + port: ${GLOW_COMMUNICATION_MCI_PORT:26160} + extmci: + host: ${GLOW_COMMUNICATION_EXTMCI_HOST:http://host.docker.internal} + port: ${GLOW_COMMUNICATION_EXTMCI_PORT:8080} + eai: + host: ${GLOW_COMMUNICATION_EAI_HOST:tcp://host.docker.internal} + port: ${GLOW_COMMUNICATION_EAI_PORT:9999} \ No newline at end of file diff --git a/dap-was-lib/src/main/resources/glow/application-glow-local.yml b/dap-was-lib/src/main/resources/glow/application-glow-local.yml new file mode 100644 index 00000000..0a9b014e --- /dev/null +++ b/dap-was-lib/src/main/resources/glow/application-glow-local.yml @@ -0,0 +1,41 @@ +# Glow 로컬 환경 설정 +spring: + config: + activate: + on-profile: local + +glow: + communication: + common: + env-type: D + # Direct local process uses WireMock host port mapped by docker-compose. + http: + connection-timeout: 5 + read-timeout: 5 + api-list: + - name: memo + domain: ${AXHUB_MEMO_HTTP_DOMAIN:http://localhost:${server.port}} + url: ${AXHUB_MEMO_HTTP_URL:/api/mock/http/cmm_memo_retriever} + method: POST + content-type: application/json;charset=UTF-8 + biz-pod: false + - name: insurance + domain: ${AXHUB_INSURANCE_HTTP_DOMAIN:http://localhost:${server.port}} + url: ${AXHUB_INSURANCE_HTTP_URL:/api/mock/http/ins_insurance_processor} + method: POST + content-type: application/json;charset=UTF-8 + biz-pod: false + mci: + host: ${GLOW_COMMUNICATION_MCI_HOST:http://localhost} + port: ${GLOW_COMMUNICATION_MCI_PORT:8080} + extmci: + host: ${GLOW_COMMUNICATION_EXTMCI_HOST:http://localhost} + port: ${GLOW_COMMUNICATION_EXTMCI_PORT:8080} + eai: + host: ${GLOW_COMMUNICATION_EAI_HOST:http://localhost} + port: ${GLOW_COMMUNICATION_EAI_PORT:8080} + +axhub: + mock: + http: + enabled: true diff --git a/dap-was-lib/src/main/resources/glow/application-glow-prod.yml b/dap-was-lib/src/main/resources/glow/application-glow-prod.yml new file mode 100644 index 00000000..cf51ecb2 --- /dev/null +++ b/dap-was-lib/src/main/resources/glow/application-glow-prod.yml @@ -0,0 +1,19 @@ +# Glow 운영 환경 설정 +spring: + config: + activate: + on-profile: prod + +glow: + communication: + common: + env-type: P + mci: + host: ${GLOW_COMMUNICATION_MCI_HOST} + port: ${GLOW_COMMUNICATION_MCI_PORT} + extmci: + host: ${GLOW_COMMUNICATION_EXTMCI_HOST} + port: ${GLOW_COMMUNICATION_EXTMCI_PORT} + eai: + host: ${GLOW_COMMUNICATION_EAI_HOST} + port: ${GLOW_COMMUNICATION_EAI_PORT} \ No newline at end of file diff --git a/dap-was-lib/src/main/resources/glow/application-glow-test.yml b/dap-was-lib/src/main/resources/glow/application-glow-test.yml new file mode 100644 index 00000000..60084090 --- /dev/null +++ b/dap-was-lib/src/main/resources/glow/application-glow-test.yml @@ -0,0 +1,19 @@ +# Glow 테스트 환경 설정 +spring: + config: + activate: + on-profile: test + +glow: + communication: + common: + env-type: T + mci: + host: ${GLOW_COMMUNICATION_MCI_HOST} + port: ${GLOW_COMMUNICATION_MCI_PORT} + extmci: + host: ${GLOW_COMMUNICATION_EXTMCI_HOST} + port: ${GLOW_COMMUNICATION_EXTMCI_PORT} + eai: + host: ${GLOW_COMMUNICATION_EAI_HOST} + port: ${GLOW_COMMUNICATION_EAI_PORT} \ No newline at end of file diff --git a/dap-was-lib/src/main/resources/glow/application-glow.yml b/dap-was-lib/src/main/resources/glow/application-glow.yml new file mode 100644 index 00000000..9beff3ed --- /dev/null +++ b/dap-was-lib/src/main/resources/glow/application-glow.yml @@ -0,0 +1,41 @@ +# AX HUB Tool Pod 공통 Glow Framework 설정입니다. +# Tool Pod의 application-{profile}.yml에서 이 파일과 환경별 Glow 설정을 함께 import합니다. +logging: + level: + root: INFO + io.shinhanlife.axhub: DEBUG + io.shinhanlife.glow: INFO + org.springframework.web: INFO + pattern: + console: "[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] %logger{36} - %msg%n" + +spring: + data: + redis: + host: ${SPRING_DATA_REDIS_HOST:127.0.0.1} + port: ${SPRING_DATA_REDIS_PORT:6379} + password: ${SPRING_DATA_REDIS_PASSWORD:} + +glow: + communication: + http: + connection-timeout: 5 + read-timeout: 5 + # HTTP Tool target catalog. Scaffold adds local mock entries after a Tool is created. + api-list: [] + mci: + uri: /ntl_mci/dap_rcv + receive-uri: /itrf/mciReceive + connection-timeout: 300 + read-timeout: 300 + encoding: UTF-8 + extmci: + uri: /extmci + json-uri: /extmciJson + receive-uri: /app/extMciReceive + connection-timeout: 5 + read-timeout: 30 + encoding: EUC-KR + websocket: + endpoint: /ws-glow + allowed-origins: "*" diff --git a/dap-was-lib/src/main/resources/mock-responses.json b/dap-was-lib/src/main/resources/mock-responses.json new file mode 100644 index 00000000..7d6abfed --- /dev/null +++ b/dap-was-lib/src/main/resources/mock-responses.json @@ -0,0 +1,106 @@ +{ + "BILL_001": { + "status": "SUCCESS", + "message": "청구 심사 상태 조회가 완료되었습니다.", + "data": { + "billingStatus": "PROCESSING", + "expectedCompletionDate": "2026-07-05" + } + }, + "BILL_002": { + "status": "SUCCESS", + "message": "청구 처리가 완료되었습니다.", + "data": { + "processId": "PRC-998811", + "result": "APPROVED" + } + }, + "BOND_001": { + "status": "SUCCESS", + "message": "디지털 증권 발행 가능 한도 조회가 완료되었습니다.", + "data": { + "availableLimit": 500000000, + "currency": "KRW" + } + }, + "BOND_002": { + "status": "SUCCESS", + "message": "디지털 증권 발행이 성공적으로 완료되었습니다.", + "data": { + "bondId": "BND-2026-07-04-1234", + "issuedAmount": 10000000 + } + }, + "HR_VAC_01": { + "status": "SUCCESS", + "message": "연차 휴가 등록이 완료되었습니다.", + "data": { + "vacationId": "VAC-8877", + "status": "APPROVED" + } + }, + "HR_VAC_02": { + "status": "SUCCESS", + "message": "잔여 연차 일수 조회가 완료되었습니다.", + "data": { + "totalDays": 15, + "usedDays": 3, + "remainingDays": 12 + } + }, + "COM_SMS_01": { + "status": "SUCCESS", + "message": "SMS 발송이 성공적으로 완료되었습니다.", + "data": { + "messageId": "SMS-11223344", + "sentTime": "2026-07-04 10:00:00" + } + }, + "COM_EML_01": { + "status": "SUCCESS", + "message": "이메일 발송이 성공적으로 완료되었습니다.", + "data": { + "emailId": "EML-998877", + "sentTime": "2026-07-04 10:00:00" + } + }, + "CNTR_001": { + "status": "SUCCESS", + "message": "계약 상태 조회가 완료되었습니다.", + "data": { + "contractStatus": "ACTIVE", + "startDate": "2024-01-01" + } + }, + "CNTR_002": { + "status": "SUCCESS", + "message": "계약 상세 내역 조회가 완료되었습니다.", + "data": { + "productName": "신한 라이프 스마트 연금보험", + "monthlyPremium": 500000 + } + }, + "CRM_001": { + "status": "SUCCESS", + "message": "고객 등급 조회가 완료되었습니다.", + "data": { + "customerGrade": "VIP", + "loyaltyPoints": 15000 + } + }, + "CRM_002": { + "status": "SUCCESS", + "message": "고객 상세 정보 조회가 완료되었습니다.", + "data": { + "address": "서울특별시 중구 세종대로 9", + "phoneNumber": "010-XXXX-XXXX" + } + }, + "PAY_001": { + "status": "SUCCESS", + "message": "결제가 성공적으로 승인되었습니다.", + "data": { + "processStatus": "COMPLETED" + } + } +} diff --git a/dap-was-lib/src/main/resources/static/tool-test-console.html b/dap-was-lib/src/main/resources/static/tool-test-console.html new file mode 100644 index 00000000..a6ad6a6d --- /dev/null +++ b/dap-was-lib/src/main/resources/static/tool-test-console.html @@ -0,0 +1,338 @@ + + + + + + AX HUB Tool Test Console + + + + + +
    +
    + +
    + + Manifest loading + v0.0.1 +
    +
    +
    +
    +

    Schema 기반 Tool 테스트

    Tool을 선택하고 요청 JSON을 확인한 뒤 실행하세요. 검증한 요청은 브라우저에 저장되며, 저장된 케이스 전체를 한 번에 다시 실행할 수 있습니다.

    +
    + +
    +

    2. 요청 JSON

    필수값과 형식은 Tool의 inputSchema 기준입니다. MCI·외부 연동 Tool은 업무에 맞는 테스트 데이터를 입력한 후 저장하세요.
    +

    3. 실행 결과

    대기-trace-id: -request-id: -
    Tool을 선택하고 실행하세요.
    +
    +
    + +
    + + + + diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/adapter/test/MockEimsHttpServerTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/adapter/test/MockEimsHttpServerTest.java new file mode 100644 index 00000000..773659d4 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/adapter/test/MockEimsHttpServerTest.java @@ -0,0 +1,19 @@ +package io.shinhanlife.dap.lib.adapter.test; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class MockEimsHttpServerTest { + + @Test + void returnsTheScaffoldGeneratedJsonResponse() { + MockEimsHttpServer server = new MockEimsHttpServer(new ObjectMapper()); + + var response = server.mockToolHttpResponse("cmm_memo_retriever", null); + + assertThat(response.getStatusCode().is2xxSuccessful()).isTrue(); + assertThat(response.getBody().path("resultCode").asText()).isEqualTo("SUCCESS"); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/common/adapter/sender/ShinhanMciSenderTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/common/adapter/sender/ShinhanMciSenderTest.java new file mode 100644 index 00000000..9ee767a2 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/common/adapter/sender/ShinhanMciSenderTest.java @@ -0,0 +1,60 @@ +package io.shinhanlife.dap.lib.common.adapter.sender; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.integration.mci.dto.MciRequestWrapper; +import io.shinhanlife.dap.lib.integration.mci.dto.ShinhanCommonHeaderDto; +import lombok.Data; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @package io.shinhanlife.dap.lib.adapter.sender + * @className ShinhanMciSenderTest + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +class ShinhanMciSenderTest { + + @Data + static class SampleBody { + private String msgCd; + private String anxMsgCt; + } + + @Test + void testJsonUnwrapped() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + + ShinhanCommonHeaderDto header = new ShinhanCommonHeaderDto(); + header.setGlbId("20211115150135679009808815NCS17007887"); + header.setPgrsSriaNo("002"); + header.setItrIfId("NCSBACO00001"); + + SampleBody body = new SampleBody(); + body.setMsgCd("12345"); + body.setAnxMsgCt("Test Message"); + + MciRequestWrapper wrapper = new MciRequestWrapper<>(); + wrapper.setTgrmCmnnhddValu(header); + wrapper.setBody(body); + + String json = mapper.writeValueAsString(wrapper); + + System.out.println(json); + + // 검증: body 필드가 json root 레벨에 평탄화되어 있는지 확인 + assertThat(json).contains("\"tgrmCmnnhddValu\":{"); + assertThat(json).contains("\"msgCd\":\"12345\""); + assertThat(json).contains("\"anxMsgCt\":\"Test Message\""); + assertThat(json).doesNotContain("\"body\":"); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/config/AxhubHttpConfigurationTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/config/AxhubHttpConfigurationTest.java new file mode 100644 index 00000000..4e7f10f5 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/config/AxhubHttpConfigurationTest.java @@ -0,0 +1,31 @@ +package io.shinhanlife.dap.lib.config; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.shinhanlife.glow.communication.module.http.component.GlowHttpComponent; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.web.client.RestClient; + +class AxhubHttpConfigurationTest { + + @Test + void registersGlowHttpComponentFromDapLibConfiguration() { + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class)) { + assertThat(context.getBean(GlowHttpComponent.class)).isNotNull(); + } + } + + @Configuration + @Import(AxhubHttpConfiguration.class) + static class TestConfiguration { + + @Bean + RestClient.Builder restClientBuilder() { + return RestClient.builder(); + } + } +} \ No newline at end of file diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/config/ToolSchemaConfigurationTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/config/ToolSchemaConfigurationTest.java new file mode 100644 index 00000000..c33789f2 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/config/ToolSchemaConfigurationTest.java @@ -0,0 +1,22 @@ +package io.shinhanlife.dap.lib.config; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +class ToolSchemaConfigurationTest { + + @Test + void providesToolArgumentSchemaValidatorWithoutComponentScanning() { + try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { + context.registerBean(ObjectMapper.class); + context.register(ToolSchemaConfiguration.class); + context.refresh(); + + assertNotNull(context.getBean(ToolArgumentSchemaValidator.class)); + } + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpComponentTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpComponentTest.java new file mode 100644 index 00000000..074a0b29 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/integration/http/component/AxhubHttpComponentTest.java @@ -0,0 +1,67 @@ +package io.shinhanlife.dap.lib.integration.http.component; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.header; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.config.GlowCommunicationProperties; +import io.shinhanlife.glow.communication.module.http.component.GlowHttpComponent; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpMethod; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestClient; + +class AxhubHttpComponentTest { + + @Test + void callByApiNameBuildsGlowTransferAndDeserializesJsonResponse() { + RestClient.Builder builder = RestClient.builder(); + MockRestServiceServer server = MockRestServiceServer.bindTo(builder).build(); + GlowHttpComponent glowHttpComponent = new GlowHttpComponent(builder); + AxhubHttpProperties properties = new AxhubHttpProperties(); + properties.setApiList(List.of(new AxhubHttpProperties.ApiDefinition( + "status", "https://api.example.test", "/v1", HttpMethod.GET, "application/json", false))); + AxhubHttpComponent component = new AxhubHttpComponent( + glowHttpComponent, new ObjectMapper(), new GlowCommunicationProperties(), properties); + + server.expect(requestTo("https://api.example.test/v1/status")) + .andExpect(header("X-ANONYMOUS-REQ", "AXHUB-TOOL")) + .andRespond(withSuccess("{\"status\":\"OK\"}", APPLICATION_JSON)); + + SampleResponse response = component.call("status", "/status", null, SampleResponse.class); + + assertThat(response.status()).isEqualTo("OK"); + server.verify(); + } + + @Test + void callByApiNameUsesConfiguredUrlMethodContentTypeAndBizPodHeader() { + RestClient.Builder builder = RestClient.builder(); + MockRestServiceServer server = MockRestServiceServer.bindTo(builder).build(); + GlowHttpComponent glowHttpComponent = new GlowHttpComponent(builder); + AxhubHttpProperties properties = new AxhubHttpProperties(); + properties.setApiList(List.of(new AxhubHttpProperties.ApiDefinition( + "employee", "https://employee.example.test", "/itrf/employee", HttpMethod.POST, + "application/json;charset=UTF-8", true))); + AxhubHttpComponent component = new AxhubHttpComponent( + glowHttpComponent, new ObjectMapper(), new GlowCommunicationProperties(), properties); + + server.expect(requestTo("https://employee.example.test/itrf/employee")) + .andExpect(method(HttpMethod.POST)) + .andExpect(header("Content-Type", "application/json;charset=UTF-8")) + .andExpect(header("X-POD-TO-POD", "true")) + .andRespond(withSuccess("{\"status\":\"OK\"}", APPLICATION_JSON)); + + SampleResponse response = component.call("employee", "{\"employeeId\":\"EMP10001\"}", SampleResponse.class); + + assertThat(response.status()).isEqualTo("OK"); + server.verify(); + } + record SampleResponse(String status) { + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistryTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistryTest.java new file mode 100644 index 00000000..647d4229 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/McpToolMethodRegistryTest.java @@ -0,0 +1,57 @@ +package io.shinhanlife.dap.lib.mcp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import io.shinhanlife.dap.lib.config.McpProperties; +import org.junit.jupiter.api.Test; +import org.springaicommunity.mcp.annotation.McpTool; +import org.springframework.context.support.StaticApplicationContext; + +class McpToolMethodRegistryTest { + + @Test + void indexesAnnotatedToolDuringInitialization() { + StaticApplicationContext context = contextWith(new EchoTool()); + McpToolMethodRegistry registry = new McpToolMethodRegistry(context, new McpProperties()); + + registry.initialize(); + + McpToolMethodRegistry.RegisteredTool tool = registry.find("cmm_echo_search"); + assertNotNull(tool); + assertEquals("execute", tool.method().getName()); + assertEquals("cmm_echo_search", tool.annotation().name()); + } + + @Test + void rejectsDuplicateToolNamesDuringInitialization() { + StaticApplicationContext context = contextWith(new EchoTool(), new DuplicateEchoTool()); + McpToolMethodRegistry registry = new McpToolMethodRegistry(context, new McpProperties()); + + assertThrows(IllegalStateException.class, registry::initialize); + } + + private StaticApplicationContext contextWith(Object... tools) { + StaticApplicationContext context = new StaticApplicationContext(); + for (int index = 0; index < tools.length; index++) { + context.getBeanFactory().registerSingleton("tool" + index, tools[index]); + } + context.refresh(); + return context; + } + + static class EchoTool { + @McpTool(name = "cmm_echo_search") + public String execute(String request) { + return request; + } + } + + static class DuplicateEchoTool { + @McpTool(name = "cmm_echo_search") + public String execute(String request) { + return request; + } + } +} \ No newline at end of file diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolExecutionServiceTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolExecutionServiceTest.java new file mode 100644 index 00000000..26ddc40a --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolExecutionServiceTest.java @@ -0,0 +1,62 @@ +package io.shinhanlife.dap.lib.mcp; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.annotation.ToolHint; +import io.shinhanlife.dap.lib.config.McpProperties; +import io.shinhanlife.dap.lib.util.ToolSchemaResolver; +import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.springaicommunity.mcp.annotation.McpTool; +import org.springaicommunity.mcp.annotation.McpToolParam; +import org.springframework.context.support.StaticApplicationContext; + +class McpToolExecutionServiceTest { + + @Test + void returnsNotFoundWhenNoToolMatchesTheRequestedName() { + McpToolExecutionService service = serviceWith(new Object()); + ToolExecutionResult result = service.execute("missing.cmm.tool.inquiry", null, Map.of()); + assertEquals(404, result.statusCode()); + assertEquals("TOOL_NOT_FOUND", ((Map) result.body()).get("code")); + } + + @Test + void convertsArgumentsToDtoAndExecutesTheMatchedTool() { + McpToolExecutionService service = serviceWith(new EchoTool()); + ToolExecutionResult result = service.execute("sample.cmm.value.echo", + new McpRequestHeaders("gateway-1", "trace-1", "request-1", "employee-1"), Map.of("value", "hello")); + assertEquals(200, result.statusCode()); + assertEquals("hello", ((Map) result.body()).get("value")); + assertEquals("trace-1", result.headers().get("trace-id")); + assertEquals("request-1", result.headers().get("request-id")); + } + + private McpToolExecutionService serviceWith(Object toolBean) { + ObjectMapper objectMapper = new ObjectMapper(); + StaticApplicationContext context = new StaticApplicationContext(); + context.getBeanFactory().registerSingleton("toolBean", toolBean); + context.refresh(); + McpToolMethodRegistry registry = new McpToolMethodRegistry(context, new McpProperties()); + registry.initialize(); + return new McpToolExecutionService(registry, objectMapper, + new ToolArgumentSchemaValidator(objectMapper), new ToolSchemaResolver(objectMapper)); + } + + static class EchoTool { + @McpTool(name = "sample.cmm.value.echo", description = "Echoes a value") + @ToolHint + public Map execute(EchoRequest request) { + return Map.of("value", request.value); + } + } + + static class EchoRequest { + @McpToolParam(description = "Value", required = true) + private String value; + public String getValue() { return value; } + public void setValue(String value) { this.value = value; } + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSenderTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSenderTest.java new file mode 100644 index 00000000..9fb55a49 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/mcp/ToolRegistryHeartbeatSenderTest.java @@ -0,0 +1,56 @@ +package io.shinhanlife.dap.lib.mcp; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.annotation.ToolHint; +import io.shinhanlife.dap.lib.config.McpProperties; +import io.shinhanlife.dap.lib.util.ToolSchemaResolver; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.springaicommunity.mcp.annotation.McpTool; +import org.springframework.context.ApplicationContext; + +class ToolRegistryHeartbeatSenderTest { + + @Test + void excludesRegisterFalseToolFromGatewayRegistrationTargets() throws Exception { + ApplicationContext applicationContext = mock(ApplicationContext.class); + when(applicationContext.getBeansOfType(Object.class)).thenReturn(Map.of("disabledTool", new DisabledTool())); + ToolRegistryHeartbeatSender sender = new ToolRegistryHeartbeatSender( + applicationContext, new ObjectMapper(), new McpProperties(), mock(ToolSchemaResolver.class)); + + setField(sender, "podUrl", "http://localhost:8084"); + + sender.init(); + + assertThat(sender.getAllScannedTools()).singleElement() + .extracting(tool -> tool.getIsRegistered()) + .isEqualTo(false); + assertThat(registeredTools(sender)).isEmpty(); + } + + private void setField(Object target, String name, Object value) throws Exception { + Field field = target.getClass().getDeclaredField(name); + field.setAccessible(true); + field.set(target, value); + } + @SuppressWarnings("unchecked") + private List registeredTools(ToolRegistryHeartbeatSender sender) throws Exception { + Field field = ToolRegistryHeartbeatSender.class.getDeclaredField("registeredTools"); + field.setAccessible(true); + return (List) field.get(sender); + } + + static class DisabledTool { + + @McpTool(name = "test_disabled_tool") + @ToolHint(register = false) + void execute() { + } + } +} \ No newline at end of file diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java new file mode 100644 index 00000000..27aceba1 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/JsonSchemaGeneratorTest.java @@ -0,0 +1,120 @@ +package io.shinhanlife.dap.lib.util; + + +import org.springaicommunity.mcp.annotation.McpToolParam; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.networknt.schema.Error; +import com.networknt.schema.InputFormat; +import com.networknt.schema.Schema; +import com.networknt.schema.SchemaRegistry; +import com.networknt.schema.SpecificationVersion; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class JsonSchemaGeneratorTest { + + @Test + void includesMcpParameterConstraintsInGeneratedSchema() { + Map schema = JsonSchemaGenerator.generateSchema(ValidatedRequest.class); + Map> properties = properties(schema); + + assertTrue(((List) schema.get("required")).contains("phoneNumber")); + assertTrue(((List) schema.get("required")).contains("approvalStatus")); + assertEquals("^01[0-9]{8,9}$", properties.get("phoneNumber").get("pattern")); + assertEquals(1L, properties.get("amount").get("minimum")); + assertEquals(List.of("APPROVE", "REJECT"), properties.get("approvalStatus").get("enum")); + } + + @Test + void includesExtendedMcpValidationConstraintsInGeneratedSchema() { + Map schema = JsonSchemaGenerator.generateSchema(ValidatedRequest.class); + + assertEquals(50L, property(schema, "pageSize").get("maximum")); + assertEquals(20L, property(schema, "pageSize").get("default")); + assertEquals(1, property(schema, "reference").get("minLength")); + assertEquals(30, property(schema, "reference").get("maxLength")); + } + + @Test + void includesNestedDtoConstraintsInGeneratedSchema() { + Map schema = JsonSchemaGenerator.generateSchema(NestedRequest.class); + Map childSchema = property(schema, "child"); + + assertEquals("object", childSchema.get("type")); + assertTrue(required(childSchema).contains("businessDate")); + assertEquals("^\\d{8}$", property(childSchema, "businessDate").get("pattern")); + } + + @Test + void includesNestedDtoSchemaForListItems() { + Map schema = JsonSchemaGenerator.generateSchema(ListRequest.class); + Map itemSchema = map(property(schema, "items").get("items")); + + assertEquals("object", itemSchema.get("type")); + assertTrue(required(itemSchema).contains("businessDate")); + } + + @Test + void validatorRejectsInvalidNestedValue() throws Exception { + ObjectMapper objectMapper = new ObjectMapper(); + Schema schema = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7) + .getSchema(objectMapper.writeValueAsString(JsonSchemaGenerator.generateSchema(NestedRequest.class))); + List errors = schema.validate(objectMapper.writeValueAsString(Map.of( + "child", Map.of("businessDate", "2026-07-28"))), InputFormat.JSON); + + assertFalse(errors.isEmpty()); + } + + @SuppressWarnings("unchecked") + private Map> properties(Map schema) { + return (Map>) schema.get("properties"); + } + + private Map property(Map schema, String name) { + return properties(schema).get(name); + } + + @SuppressWarnings("unchecked") + private Map map(Object value) { + return (Map) value; + } + + @SuppressWarnings("unchecked") + private List required(Map schema) { + return (List) schema.get("required"); + } + + private static class ValidatedRequest { + @McpToolParam(description = "recipient phone number", required = true) + private String phoneNumber; + + @McpToolParam(description = "issue amount", required = true) + private Long amount; + + @McpToolParam(description = "approval result") + private String approvalStatus; + + @McpToolParam(description = "page size") + private Integer pageSize; + + @McpToolParam(description = "reference") + private String reference; + } + + private static class NestedRequest { + private NestedChild child; + } + + private static class ListRequest { + private List items; + } + + private static class NestedChild { + private String businessDate; + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/PodScaffolderTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/PodScaffolderTest.java new file mode 100644 index 00000000..5d708d4b --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/PodScaffolderTest.java @@ -0,0 +1,53 @@ +package io.shinhanlife.dap.lib.util; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class PodScaffolderTest { + + @TempDir + Path root; + + @Test + void generatesBuildGradleUsingRepositoryStandard() throws Exception { + PodScaffolder.scaffoldPod(root, "dap-was-sample", "8099", "sample", "tester", "2026.08.07"); + + String buildGradle = Files.readString(root.resolve("dap-was-sample/build.gradle")); + assertTrue(buildGradle.contains("id 'org.springframework.boot'")); + assertTrue(buildGradle.contains("Spring Boot 3.5.11")); + assertTrue(buildGradle.contains("implementation project(':dap-was-lib')")); + assertFalse(buildGradle.contains("compileOnly 'org.projectlombok:lombok")); + } + + @Test + void generatesOthStandardRuntimeResources() throws Exception { + PodScaffolder.scaffoldPod(root, "dap-was-sample", "8099", "sample", "tester", "2026.08.07"); + + Path resources = root.resolve("dap-was-sample/src/main/resources"); + String dockerfile = Files.readString(root.resolve("dap-was-sample/Dockerfile")); + + assertTrue(dockerfile.contains("RUN apk add --no-cache tzdata")); + assertTrue(dockerfile.contains("ENV TZ=Asia/Seoul")); + assertTrue(dockerfile.contains("COPY dap-was-sample/build/libs/*-SNAPSHOT.jar app.jar")); + assertTrue(dockerfile.contains("EXPOSE 8099")); + assertTrue(Files.exists(resources.resolve("application-test.yml"))); + assertTrue(Files.exists(resources.resolve("application-prod.yml"))); + assertTrue(Files.readString(resources.resolve("application-test.yml")) + .contains("${AXHUB_GATEWAY_URL}")); + assertTrue(Files.readString(resources.resolve("application-prod.yml")) + .contains("${AXHUB_TOOL_URL}")); + assertTrue(Files.readString(resources.resolve("application-local.yml")) + .contains("classpath:glow/application-glow-local.yml")); + assertTrue(Files.readString(resources.resolve("application-dev.yml")) + .contains("classpath:glow/application-glow-dev.yml")); + assertTrue(Files.readString(resources.resolve("application-test.yml")) + .contains("classpath:glow/application-glow-test.yml")); + assertTrue(Files.readString(resources.resolve("application-prod.yml")) + .contains("classpath:glow/application-glow-prod.yml")); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java new file mode 100644 index 00000000..8f7073ad --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolScaffolderTest.java @@ -0,0 +1,258 @@ +package io.shinhanlife.dap.lib.util; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class ToolScaffolderTest { + + @TempDir + Path root; + + @Test + void generatesSpringAiToolAndResponseDto() throws Exception { + String moduleName = "build/scaffold-manifest-test"; + + ToolScaffolder.scaffold("claim search", "CLM0001", "청구 조회", "cmm", "HTTP", moduleName, + "tester", "2026.08.04", true, null); + + Path root = Path.of(moduleName, "src/main/java/io/shinhanlife/dap/mcc/biz/cmm"); + String useCase = Files.readString(root.resolve("usecase/ClaimSearchUseCase.java")); + String response = Files.readString(root.resolve("dto/ClaimSearchResponse.java")); + + assertTrue(useCase.contains("name = \"cmm_claim_search\"")); + assertTrue(useCase.contains("@ToolHint(register = true, categoryKey = \"cmm\", mappingId = \"CLM0001\")")); + assertTrue(response.contains("private String resultCode;")); + assertTrue(response.contains("private String resultMessage;")); + } + @Test + void usesWasModuleNameAsToolPodPrefix() throws Exception { + String moduleName = "build/dap-was-sms"; + + ToolScaffolder.scaffold("notification send", "SMS0001", "SMS 발송", "cmm", "HTTP", moduleName, + "tester", "2026.08.05", true, null); + + Path useCasePath = Path.of(moduleName, + "src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/NotificationSendUseCase.java"); + String useCase = Files.readString(useCasePath); + + assertTrue(useCase.contains("name = \"cmm_notification_send\"")); + } + + @Test + void createsSchemaResourcesInToolSchemaCategoryDirectory() throws Exception { + String moduleName = root.resolve("dap-was-sample").toString(); + + ToolScaffolder.scaffold("claim search", "CLM0001", "청구 조회", "cmm", "HTTP", moduleName, + "tester", "2026.08.09", true, null, + "classpath:tool-schemas/cmm/claim-search-resource-input-schema.json", + "classpath:tool-schemas/cmm/claim-search-resource-output-schema.json"); + + Path schemas = root.resolve("dap-was-sample/src/main/resources/tool-schemas/cmm"); + assertTrue(Files.exists(schemas.resolve("claim-search-resource-input-schema.json"))); + assertTrue(Files.exists(schemas.resolve("claim-search-resource-output-schema.json"))); String useCase = Files.readString(root.resolve("dap-was-sample/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCase.java")); + assertTrue(useCase.contains("@ToolHint(register = true, categoryKey = \"cmm\", mappingId = \"CLM0001\",")); + assertTrue(useCase.contains("inputSchemaResource = \"classpath:tool-schemas/cmm/claim-search-resource-input-schema.json\"")); + assertTrue(useCase.contains("outputSchemaResource = \"classpath:tool-schemas/cmm/claim-search-resource-output-schema.json\"")); + } + + @Test + void generatesRequestDtoFromDeclaredFields() throws Exception { + String moduleName = root.resolve("dap-was-pay").toString(); + + ToolScaffolder.scaffold("search hr", "SHEARCH_01", "HR 조회", "pay", "MCI", moduleName, + "tester", "2026.08.09", true, "DFAG"); + + Path requestPath = root.resolve("dap-was-pay/src/main/java/io/shinhanlife/dap/mcc/biz/pay/dto/SearchHrRequest.java"); + String request = Files.readString(requestPath); + + assertTrue(request.contains("import io.swagger.v3.oas.annotations.media.Schema;")); + assertTrue(request.contains("@Schema(description = \"Search query\", example = \"example\")")); + assertTrue(request.contains("private String query;")); + assertFalse(request.contains("McpToolParam")); + + Path implementationPath = root.resolve("dap-was-pay/src/main/java/io/shinhanlife/dap/mcc/biz/pay/usecase/impl/SearchHrUseCaseImpl.java"); + String implementation = Files.readString(implementationPath); + assertTrue(implementation.contains("public SearchHrResponse execute(SearchHrRequest req)")); + assertTrue(implementation.contains("response.setResultCode(\"SUCCESS\")")); + } + + @Test + void generatesMciToolFromDeclaredInputAndOutputFields() throws Exception { + String moduleName = root.resolve("dap-was-pay").toString(); + List inputFields = List.of( + new ToolScaffolder.FieldDefinition("employeeId", "String", "Employee identifier", "EMP10001", true), + new ToolScaffolder.FieldDefinition("page", "Integer", "Page number", "1", false)); + List outputFields = List.of( + new ToolScaffolder.FieldDefinition("employeeName", "String", "Employee name", "Hong Gildong", true)); + + ToolScaffolder.scaffold("search hr", "SHEARCH_01", "HR search", "pay", "MCI", moduleName, + "tester", "2026.08.09", true, "DFAG", null, null, inputFields, outputFields); + + Path sourceRoot = root.resolve("dap-was-pay/src/main/java/io/shinhanlife/dap/mcc"); + String request = Files.readString(sourceRoot.resolve("biz/pay/dto/SearchHrRequest.java")); + String response = Files.readString(sourceRoot.resolve("biz/pay/dto/SearchHrResponse.java")); + String mciRequest = Files.readString(sourceRoot.resolve("infra/itrf/mci/dfag/io/SHEARCH_01_I.java")); + String mciResponse = Files.readString(sourceRoot.resolve("infra/itrf/mci/dfag/io/SHEARCH_01_O.java")); + String converter = Files.readString(sourceRoot.resolve("biz/pay/converter/SearchHrConverter.java")); String useCase = Files.readString(sourceRoot.resolve("biz/pay/usecase/SearchHrUseCase.java")); + String implementation = Files.readString(sourceRoot.resolve("biz/pay/usecase/impl/SearchHrUseCaseImpl.java")); + assertTrue(mciRequest.contains("package io.shinhanlife.dap.mcc.infra.itrf.mci.dfag.io;"), mciRequest); assertTrue(useCase.contains("@ToolHint(register = true, categoryKey = \"pay\", mappingId = \"SHEARCH_01\")")); + assertTrue(implementation.contains("import io.shinhanlife.dap.mcc.infra.itrf.mci.dfag.io.SHEARCH_01_O;")); + + assertTrue(request.contains("private String employeeId;")); + assertTrue(request.contains("private Integer page;")); + assertFalse(request.contains("phoneNumber")); + assertTrue(response.contains("private String employeeName;")); + assertTrue(mciRequest.contains("private String employeeId;")); + assertTrue(mciResponse.contains("private String employeeName;")); + assertTrue(converter.contains("SearchHrResponse toResponse(SHEARCH_01_O mciRes);")); + } + @Test + void generatesMockResponseAndUnitTestSkeletonFromOutputFields() throws Exception { + String moduleName = root.resolve("dap-was-oth").toString(); + List outputFields = List.of( + new ToolScaffolder.FieldDefinition("status", "String", "Claim status", "RECEIVED", true)); + + ToolScaffolder.scaffold("claim search", "CLM0001", "Claim search", "cmm", "MCI", moduleName, + "tester", "2026.08.10", true, null, null, null, List.of(), outputFields); + + Path mockResponse = root.resolve("dap-was-oth/src/main/resources/mock-responses/cmm_claim_search.json"); + Path useCaseTest = root.resolve("dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCaseTest.java"); + + assertTrue(Files.exists(mockResponse)); + assertTrue(Files.readString(mockResponse).contains("\"status\" : \"RECEIVED\"")); + assertTrue(Files.exists(useCaseTest)); + assertTrue(Files.readString(useCaseTest).contains("class ClaimSearchUseCaseTest")); + } + @Test + void createsWireMockMappingForHttpTool() { + String mapping = ToolScaffolder.wireMockMappingContent("HR_EMPLOYEE_SEARCH", "smp_employee_search.json"); + + assertTrue(mapping.contains("\"method\" : \"POST\""), mapping); + assertTrue(mapping.contains("\"urlPath\" : \"/HR_EMPLOYEE_SEARCH\""), mapping); + assertTrue(mapping.contains("\"bodyFileName\" : \"smp_employee_search.json\""), mapping); + } + + + @Test + void generatesDtoPackageAndRemovesDuplicateResponseFields() throws Exception { + String moduleName = root.resolve("dap-was-http").toString(); + List outputFields = List.of( + new ToolScaffolder.FieldDefinition("resultCode", "String", "API result", "SUCCESS", true), + new ToolScaffolder.FieldDefinition("employeeName", "String", "Employee name", "Hong Gildong", false), + new ToolScaffolder.FieldDefinition("employeeName", "String", "Duplicate name", "Duplicate", false)); + + List inputFields = List.of( + new ToolScaffolder.FieldDefinition("employeeId", "String", "Employee identifier", "EMP10001", true)); + String resultLog = ToolScaffolder.scaffold("employee search", "HR_EMPLOYEE_SEARCH", "Employee search", "smp", "HTTP", moduleName, + "tester", "2026.08.11", false, null, null, null, inputFields, outputFields); + + Path dtoRoot = root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto"); + String request = Files.readString(dtoRoot.resolve("EmployeeSearchRequest.java")); + String response = Files.readString(dtoRoot.resolve("EmployeeSearchResponse.java")); + + assertTrue(request.contains("package io.shinhanlife.dap.mcc.biz.smp.dto;"), request); + assertTrue(response.contains("package io.shinhanlife.dap.mcc.biz.smp.dto;"), response); + assertTrue(response.indexOf("private String resultCode;") == response.lastIndexOf("private String resultCode;"), response); + assertTrue(response.indexOf("private String employeeName;") == response.lastIndexOf("private String employeeName;"), response); + String converter = Files.readString(root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/biz/smp/converter/EmployeeSearchConverter.java")); + String httpRequest = Files.readString(root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/employee_search/io/EmployeeSearchHttpRequest.java")); + String httpResponse = Files.readString(root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/employee_search/io/EmployeeSearchHttpResponse.java")); + String httpClient = Files.readString(root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/employee_search/EmployeeSearchClient.java")); + assertFalse(converter.contains("phoneNumber"), converter); + assertTrue(converter.contains("infra.itrf.http.employee_search.io.EmployeeSearchHttpRequest"), converter); + assertFalse(converter.contains("io.shinhanlife.dap.mcc.io.shinhanlife.dap.mcc"), converter); + assertTrue(converter.contains("// @Mapping(source = \"sourceField\", target = \"targetField\")"), converter); + assertTrue(httpRequest.contains("private String employeeId;"), httpRequest); + assertTrue(httpResponse.contains("private String employeeName;"), httpResponse); + assertTrue(httpClient.contains("http.call(API_NAME, request, responseType)"), httpClient); + assertFalse(Files.exists(root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/biz/smp/legacy"))); + String implementation = Files.readString(root.resolve("dap-was-http/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/EmployeeSearchUseCaseImpl.java")); + assertTrue(implementation.contains("public EmployeeSearchResponse execute(EmployeeSearchRequest req)"), implementation); + assertTrue(implementation.contains("import io.shinhanlife.dap.mcc.infra.itrf.http.employee_search.EmployeeSearchClient;"), implementation); + assertTrue(implementation.contains("private final EmployeeSearchClient employeeSearchClient;"), implementation); + assertTrue(implementation.contains("employeeSearchClient.call(httpRequest, EmployeeSearchHttpResponse.class)"), implementation); + assertFalse(implementation.contains("AxhubHttpComponent"), implementation); + assertFalse(implementation.contains("executeLegacy(\"HTTP\""), implementation); + Path wireMockResponse = root.resolve("mci-mock/__files/smp_employee_search.json"); + Path wireMockMapping = root.resolve("mci-mock/mappings/smp_employee_search.json"); + assertTrue(Files.exists(wireMockResponse), wireMockResponse.toString()); + assertTrue(Files.exists(wireMockMapping), wireMockMapping.toString()); + assertTrue(Files.readString(wireMockMapping).contains("\"urlPath\" : \"/HR_EMPLOYEE_SEARCH\"")); + Path localConfig = root.resolve("dap-was-lib/src/main/resources/glow/application-glow-local.yml"); + assertTrue(Files.exists(localConfig), localConfig.toString()); + assertTrue(Files.readString(localConfig).contains("name: employee-search")); + assertTrue(Files.readString(localConfig).contains("url: ${AXHUB_EMPLOYEE_SEARCH_HTTP_URL:/api/mock/http/smp_employee_search}")); + Path podMockResponse = root.resolve("dap-was-http/src/main/resources/mock-responses/smp_employee_search.json"); + assertTrue(Files.exists(podMockResponse), podMockResponse.toString()); + assertTrue(resultLog.contains("Tip: HTTP Tool은 WireMock 실행 후 생성된 mapping URL로 호출을 확인하세요."), resultLog); + + ToolScaffolder.scaffold("employee search", "HR_EMPLOYEE_SEARCH", "Employee search", "smp", "HTTP", moduleName, + "tester", "2026.08.11", false, null, null, null, inputFields, outputFields); + long apiNameCount = Files.readAllLines(localConfig).stream() + .filter(line -> line.trim().equals("- name: employee-search")) + .count(); + assertEquals(1, apiNameCount); + } + @Test + void generatesSeparateToolTitleAndDescription() throws Exception { + String moduleName = root.resolve("dap-was-title").toString(); + + ToolScaffolder.scaffold("employee search", "HR_EMPLOYEE_SEARCH", "직원 정보 조회", + "사번을 입력받아 재직 중인 직원의 기본 정보를 조회한다.", "smp", "HTTP", moduleName, + "tester", "2026.08.11", false, null, null, null, List.of(), List.of()); + + Path useCasePath = root.resolve("dap-was-title/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/EmployeeSearchUseCase.java"); + String useCase = Files.readString(useCasePath); + + assertTrue(useCase.contains("title = \"직원 정보 조회\""), useCase); + assertTrue(useCase.contains("description = \"사번을 입력받아 재직 중인 직원의 기본 정보를 조회한다.\""), useCase); + } + @Test + void generatesHttpToolUsingConfiguredApiNameAndConfiguredUrlOnly() throws Exception { + String moduleName = root.resolve("dap-was-http-api-name").toString(); + + ToolScaffolder.scaffold("employee search", "HR_EMPLOYEE_SEARCH", "직원 정보 조회", + "사번으로 직원을 조회한다.", "smp", "HTTP", moduleName, "tester", "2026.08.11", + false, null, null, null, List.of(), List.of(), "employee"); + + Path implementationPath = root.resolve("dap-was-http-api-name/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/EmployeeSearchUseCaseImpl.java"); + String implementation = Files.readString(implementationPath); + + assertTrue(implementation.contains("import io.shinhanlife.dap.mcc.infra.itrf.http.employee.EmployeeClient;"), implementation); + assertTrue(implementation.contains("employeeClient.call(httpRequest, EmployeeSearchHttpResponse.class)"), implementation); + assertFalse(implementation.contains("AxhubHttpDomain"), implementation); + assertFalse(implementation.contains("\"/HR_EMPLOYEE_SEARCH\""), implementation); + } + + @Test + void addsHttpApiEntryOnItsOwnYamlLineBeforeMciConfiguration() throws Exception { + String moduleName = root.resolve("dap-was-http-yaml").toString(); + Path localConfig = root.resolve("dap-was-lib/src/main/resources/glow/application-glow-local.yml"); + Files.createDirectories(localConfig.getParent()); + Files.writeString(localConfig, """ + glow: + communication: + http: + api-list: + - name: memo + biz-pod: false + mci: + host: localhost + """); + + ToolScaffolder.scaffold("insurance claim processor", "CLAIM0000001", "Insurance claim", "Insurance claim", "ins", "HTTP", moduleName, + "tester", "2026.08.11", false, null, null, null, List.of(), List.of(), "insurance"); + + String yaml = Files.readString(localConfig); + assertFalse(yaml.contains("biz-pod: false - name"), yaml); + assertTrue(yaml.contains(" biz-pod: false\n mci:"), yaml); + assertTrue(yaml.contains(" - name: insurance"), yaml); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSchemaResolverTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSchemaResolverTest.java new file mode 100644 index 00000000..9e0cab83 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSchemaResolverTest.java @@ -0,0 +1,83 @@ +package io.shinhanlife.dap.lib.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.annotation.McpOutputSchema; +import io.swagger.v3.oas.annotations.media.Schema; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.springaicommunity.mcp.annotation.McpTool; + +class ToolSchemaResolverTest { + + private final ToolSchemaResolver resolver = new ToolSchemaResolver(new ObjectMapper()); + + @Test + void generatesSchemaFromRequestDtoWhenNoExplicitSchemaIsConfigured() throws Exception { + Method method = AutomaticSchemaTool.class.getDeclaredMethod("search", AutomaticRequest.class); + + Map schema = resolver.resolve(method.getAnnotation(McpTool.class), null, AutomaticRequest.class); + + assertTrue(properties(schema).containsKey("differentField")); + } + + @Test + void generatesOutputSchemaFromMarkedResponseDto() throws Exception { + Method method = AutomaticOutputSchemaTool.class.getDeclaredMethod("search", AutomaticRequest.class); + + Map schema = resolver.resolveOutput( + method.getAnnotation(McpTool.class), SimpleResponse.class); + + assertEquals(List.of("resultCode"), schema.get("required")); + assertEquals(List.of("SUCCESS", "FAILURE"), property(schema, "resultCode").get("enum")); + } + + @Test + void doesNotEnableOutputValidationWhenOutputSchemaIsNotDeclared() throws Exception { + Method method = AutomaticSchemaTool.class.getDeclaredMethod("search", AutomaticRequest.class); + + Map schema = resolver.resolveOutput( + method.getAnnotation(McpTool.class), AutomaticRequest.class); + + assertTrue(schema.isEmpty()); + } + + @SuppressWarnings("unchecked") + private Map properties(Map schema) { + return (Map) schema.get("properties"); + } + + @SuppressWarnings("unchecked") + private Map property(Map schema, String name) { + return (Map) properties(schema).get(name); + } + + static class AutomaticSchemaTool { + @McpTool(name = "oth_test_automatic_search") + void search(AutomaticRequest request) { + } + } + + static class AutomaticOutputSchemaTool { + @McpTool(name = "oth_test_output_search") + SimpleResponse search(AutomaticRequest request) { + return null; + } + } + + @McpOutputSchema + static class SimpleResponse { + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, allowableValues = {"SUCCESS", "FAILURE"}) + private String resultCode; + + private String message; + } + + static class AutomaticRequest { + private String differentField; + } +} \ No newline at end of file diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSourceUpdaterTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSourceUpdaterTest.java new file mode 100644 index 00000000..ca4c9720 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/util/ToolSourceUpdaterTest.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.lib.util; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class ToolSourceUpdaterTest { + + @TempDir + Path temporaryRoot; + + @Test + void updatesSdkAndProjectOwnedMetadataOnTheSameToolMethod() throws Exception { + Path source = temporaryRoot.resolve("dap-was-oth/src/main/java/example/SampleUseCase.java"); + Files.createDirectories(source.getParent()); + Files.writeString(source, """ + package example; + import org.springaicommunity.mcp.annotation.McpTool; + import io.shinhanlife.dap.lib.annotation.ToolHint; + interface SampleUseCase { + @McpTool(name = "cmm_sample_search", description = "old") + @ToolHint(register = false, requiresApproval = false) + void search(); + } + """); + + ToolSourceUpdater.updateToolSource(temporaryRoot, "cmm_sample_search", "customer", "new", true, true); + + String updated = Files.readString(source); + assertTrue(updated.contains("@McpTool(name = \"cmm_sample_search\", description = \"new\")")); + assertTrue(updated.contains("@ToolHint(register = true, requiresApproval = true")); + assertTrue(updated.contains("categoryKey = \"customer\""), updated); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/validation/McpToolNameValidatorTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/validation/McpToolNameValidatorTest.java new file mode 100644 index 00000000..8435a81b --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/lib/validation/McpToolNameValidatorTest.java @@ -0,0 +1,112 @@ +package io.shinhanlife.dap.lib.validation; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +/** + * @package io.shinhanlife.dap.lib.validation + * @className McpToolNameValidatorTest + * @description MCP tool name uniqueness validation test + * @author 0986406 + * @create 2026.07.27 + *
    + * ---------- revision history ----------
    + * date       author    description
    + * ---------- --------- ---------------------------
    + * 2026.07.27 0986406    initial creation
    + * 
    + */ +class McpToolNameValidatorTest { + + @TempDir + Path temporaryRoot; + + @Test + void rejectsDuplicateMcpToolNamesAcrossToolModules() throws IOException { + writeToolSource("dap-was-first", "FirstTool.java", "first", "oth_sms_notification_send"); + writeToolSource("dap-was-second", "SecondTool.java", "second", "oth_sms_notification_send"); + + IllegalStateException exception = assertThrows(IllegalStateException.class, + () -> McpToolNameValidator.assertUnique(temporaryRoot)); + + assertTrue(exception.getMessage().contains("oth_sms_notification_send")); + assertTrue(exception.getMessage().contains("dap-was-first")); + assertTrue(exception.getMessage().contains("dap-was-second")); + } + + @Test + void validationRunnerRejectsDuplicateMcpToolNamesBeforePackaging() throws IOException { + writeToolSource("dap-was-first", "FirstTool.java", "first", "oth_sms_notification_send"); + writeToolSource("dap-was-second", "SecondTool.java", "second", "oth_sms_notification_send"); + + assertThrows(IllegalStateException.class, + () -> McpToolNameValidationRunner.validate(temporaryRoot)); + } + + @Test + void rejectsToolNameOutsideConfiguredPattern() throws IOException { + writeToolSource("dap-was-first", "FirstTool.java", "first", "bond.issue"); + + IllegalStateException exception = assertThrows(IllegalStateException.class, + () -> McpToolNameValidator.assertUnique(temporaryRoot)); + + assertTrue(exception.getMessage().contains("Invalid MCP tool name(s)")); + assertTrue(exception.getMessage().contains("bond.issue")); + } + @Test + void acceptsLettersDigitsUnderscoresAndDashesWithin128Characters() throws IOException { + String validName = "Tool_Name-" + "a".repeat(118); + writeToolSource("dap-was-first", "FirstTool.java", "first", validName); + + assertDoesNotThrow(() -> McpToolNameValidator.assertUnique(temporaryRoot)); + } + + @Test + void rejectsToolNameLongerThan128Characters() throws IOException { + String invalidName = "a".repeat(129); + writeToolSource("dap-was-first", "FirstTool.java", "first", invalidName); + + IllegalStateException exception = assertThrows(IllegalStateException.class, + () -> McpToolNameValidator.assertUnique(temporaryRoot)); + + assertTrue(exception.getMessage().contains(invalidName)); + } + @Test + void acceptsCurrentProjectToolNames() { + assertDoesNotThrow(() -> McpToolNameValidator.assertUnique(findProjectRoot())); + } + + private void writeToolSource(String moduleName, String fileName, String className, String toolName) throws IOException { + Path source = temporaryRoot.resolve(moduleName).resolve("src/main/java/example").resolve(fileName); + Files.createDirectories(source.getParent()); + Files.writeString(source, """ + package example; + + import org.springaicommunity.mcp.annotation.McpTool; + + class %s { + @McpTool(name = "%s") + void execute() { } + } + """.formatted(className, toolName)); + } + + private Path findProjectRoot() { + Path current = Path.of("").toAbsolutePath(); + while (current != null && !Files.exists(current.resolve("settings.gradle"))) { + current = current.getParent(); + } + if (current == null) { + throw new IllegalStateException("Could not locate Gradle project root"); + } + return current; + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/manifest/ToolManifestServiceTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/manifest/ToolManifestServiceTest.java new file mode 100644 index 00000000..cd29bc3a --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/manifest/ToolManifestServiceTest.java @@ -0,0 +1,100 @@ +package io.shinhanlife.dap.lib.manifest; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.config.McpProperties; +import io.shinhanlife.dap.mcc.dto.ToolMetadata; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ToolManifestServiceTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void buildsStandardManifestAndDerivesRevisionFromToolDefinition() { + McpProperties properties = manifestProperties("insurance-processing", "processing."); + ToolManifestService service = new ToolManifestService( + () -> List.of(tool("processing.contract.inquiry", "1.2.0", 3000)), objectMapper, properties); + + ToolManifestResponse manifest = service.currentManifest(); + + assertEquals("insurance-processing", manifest.bundleId()); + assertTrue(manifest.revision().matches("\\d+")); + assertEquals(1, manifest.tools().size()); + ToolManifestItem item = manifest.tools().getFirst(); + assertEquals("processing.contract.inquiry", item.name()); + assertEquals("http://tool-processing.ax-hub.svc.cluster.local:8080/mcp/processing.contract.inquiry", + item.endpoint()); + assertEquals("계약 조회", item.title()); + assertEquals("object", item.inputSchema().get("type")); + assertTrue(item.annotations().readOnlyHint()); + assertEquals("1.2.0", item.meta().version()); + assertEquals(3000, item.meta().timeoutMillis()); + } + + @Test + void changesRevisionWhenToolDefinitionChanges() { + McpProperties properties = manifestProperties("insurance-processing", "processing."); + ToolManifestService before = new ToolManifestService( + () -> List.of(tool("processing.contract.inquiry", "1.2.0", 3000)), objectMapper, properties); + ToolManifestService after = new ToolManifestService( + () -> List.of(tool("processing.contract.inquiry", "1.2.0", 5000)), objectMapper, properties); + + assertTrue(!before.currentManifest().revision().equals(after.currentManifest().revision())); + } + + @Test + void rejectsEntireManifestWhenToolNameDoesNotMatchConfiguredPrefix() { + McpProperties properties = manifestProperties("insurance-processing", "processing."); + ToolManifestService service = new ToolManifestService( + () -> List.of(tool("notification_sms_send", "1.0.0", 3000)), objectMapper, properties); + + IllegalStateException error = assertThrows(IllegalStateException.class, service::currentManifest); + + assertTrue(error.getMessage().contains("name-prefix")); + } + + @Test + void rejectsEntireManifestWhenToolNamesAreDuplicated() { + McpProperties properties = manifestProperties("insurance-processing", "processing."); + ToolManifestService service = new ToolManifestService( + () -> List.of(tool("processing.contract.inquiry", "1.0.0", 3000), + tool("processing.contract.inquiry", "1.0.1", 3000)), objectMapper, properties); + + IllegalStateException error = assertThrows(IllegalStateException.class, service::currentManifest); + + assertTrue(error.getMessage().contains("duplicate")); + } + + private McpProperties manifestProperties(String bundleId, String namePrefix) { + McpProperties properties = new McpProperties(); + McpProperties.Manifest manifest = new McpProperties.Manifest(); + manifest.setBundleId(bundleId); + manifest.setNamePrefix(namePrefix); + properties.setManifest(manifest); + return properties; + } + + private ToolMetadata tool(String name, String version, long timeoutMillis) { + return ToolMetadata.builder() + .name(name) + .podUrl("http://tool-processing.ax-hub.svc.cluster.local:8080") + .displayName("계약 조회") + .description("계약번호로 계약 정보를 조회합니다.") + .parametersSchema(Map.of("type", "object", "properties", Map.of("contractNo", Map.of("type", "string")), + "required", List.of("contractNo"), "additionalProperties", false)) + .semver(version) + .timeoutMillis(timeoutMillis) + .enabled(true) + .readOnlyHint(true) + .destructiveHint(false) + .idempotentHint(true) + .openWorldHint(false) + .build(); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/McpRequestHeaderFilterTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/McpRequestHeaderFilterTest.java new file mode 100644 index 00000000..159c27ff --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/McpRequestHeaderFilterTest.java @@ -0,0 +1,60 @@ +package io.shinhanlife.dap.mcc.mcp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.server.McpSyncServer; +import io.shinhanlife.dap.lib.mcp.*; +import java.lang.reflect.Method; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +class McpRequestHeaderFilterTest { + + @Test + void capturesOptionalMcpHeadersOnlyForTheCurrentRequest() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("POST", "/mcp"); + request.addHeader("X-Request-Id", "gateway-request-id"); + request.addHeader("trace-id", "trace-001"); + request.addHeader("request-id", "tool-request-001"); + request.addHeader("employee-id", "encrypted-employee-id"); + + new McpRequestHeaderFilter().doFilter(request, new MockHttpServletResponse(), (req, res) -> { + McpRequestHeaders headers = McpRequestHeaderContext.current(); + assertEquals("gateway-request-id", headers.headerRequestId()); + assertEquals("trace-001", headers.traceId()); + assertEquals("tool-request-001", headers.requestId()); + assertEquals("encrypted-employee-id", headers.encryptedEmployeeId()); + }); + + assertNull(McpRequestHeaderContext.current()); + } + + @Test + void forwardsCapturedHeadersToSharedToolExecutionService() throws Exception { + McpToolExecutionService service = mock(McpToolExecutionService.class); + McpRequestHeaders headers = new McpRequestHeaders( + "gateway-request-id", "trace-001", "tool-request-001", "encrypted-employee-id"); + doReturn(new ToolExecutionResult(200, Map.of("result", "ok"), Map.of())) + .when(service).execute(eq("sampleTool"), eq(headers), eq(Map.of("key", "value"))); + ToolPodMcpToolSynchronizer synchronizer = new ToolPodMcpToolSynchronizer( + mock(McpSyncServer.class), mock(ToolRegistryHeartbeatSender.class), service, new ObjectMapper()); + + Method invoke = ToolPodMcpToolSynchronizer.class.getDeclaredMethod( + "invoke", String.class, McpRequestHeaders.class, Map.class); + invoke.setAccessible(true); + invoke.invoke(synchronizer, "sampleTool", + headers, + Map.of("key", "value")); + + verify(service).execute("sampleTool", headers, Map.of("key", "value")); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java new file mode 100644 index 00000000..82fe9796 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/mcp/ToolMcpServerConfigurationTest.java @@ -0,0 +1,23 @@ +package io.shinhanlife.dap.mcc.mcp; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.assertj.core.api.Assertions.assertThat; + +import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider; +import io.shinhanlife.dap.lib.mcp.ToolMcpServerConfiguration; +import org.junit.jupiter.api.Test; +import org.springframework.boot.web.servlet.ServletRegistrationBean; + +class ToolMcpServerConfigurationTest { + + private final ToolMcpServerConfiguration configuration = new ToolMcpServerConfiguration(); + + @Test + void exposesOnlyExactMcpEndpointSoLegacyMcpApiPathsRemainAvailable() { + HttpServletStreamableServerTransportProvider transport = configuration.toolMcpTransportProvider(); + ServletRegistrationBean registration = configuration.toolMcpServlet(transport); + + assertThat(registration.getUrlMappings()).containsExactlyInAnyOrder("/mcp", "/mcp/message"); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/BusinessToolControllerHeaderContractTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/BusinessToolControllerHeaderContractTest.java new file mode 100644 index 00000000..165b6aeb --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/BusinessToolControllerHeaderContractTest.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.presentation; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.springframework.web.bind.annotation.RequestHeader; + +class BusinessToolControllerHeaderContractTest { + + @Test + void encryptedEmployeeIdHeaderIsOptional() throws Exception { + Method method = BusinessToolController.class.getDeclaredMethod( + "executeDynamicTool", + String.class, + String.class, + String.class, + String.class, + String.class, + Map.class); + + Parameter employeeIdParameter = method.getParameters()[4]; + RequestHeader requestHeader = employeeIdParameter.getAnnotation(RequestHeader.class); + + assertEquals("employee-id", requestHeader.value()); + assertFalse(requestHeader.required()); + } +} \ No newline at end of file diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java new file mode 100644 index 00000000..c62bf519 --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolArgumentSchemaValidatorTest.java @@ -0,0 +1,26 @@ +package io.shinhanlife.dap.mcc.presentation; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.lib.validation.ToolArgumentSchemaValidator; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ToolArgumentSchemaValidatorTest { + + private final ToolArgumentSchemaValidator validator = new ToolArgumentSchemaValidator(new ObjectMapper()); + + @Test + void validatesDraft7SchemaWithTheRuntimeNetworkntVersion() throws Exception { + Map schema = Map.of( + "type", "object", + "properties", Map.of("name", Map.of("type", "string")), + "required", List.of("name")); + + assertTrue(validator.validate(schema, Map.of("name", "Hong")).isEmpty()); + assertFalse(validator.validate(schema, Map.of()).isEmpty()); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolTestConsoleResourceTest.java b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolTestConsoleResourceTest.java new file mode 100644 index 00000000..6c21272c --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/dap/mcc/presentation/ToolTestConsoleResourceTest.java @@ -0,0 +1,24 @@ +package io.shinhanlife.dap.mcc.presentation; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import org.junit.jupiter.api.Test; + +class ToolTestConsoleResourceTest { + + @Test + void publishesManifestDrivenToolTestConsole() throws Exception { + try (InputStream resource = getClass().getResourceAsStream("/static/tool-test-console.html")) { + assertNotNull(resource); + String html = new String(resource.readAllBytes(), StandardCharsets.UTF_8); + + assertTrue(html.contains("/tool-manifest")); + assertTrue(html.contains("Run saved cases")); + assertTrue(html.contains("localStorage")); + assertTrue(html.contains("request-id")); + } + } +} \ No newline at end of file diff --git a/dap-was-lib/src/test/java/io/shinhanlife/glow/communication/annotation/GlowTrgmFieldContractTest.java b/dap-was-lib/src/test/java/io/shinhanlife/glow/communication/annotation/GlowTrgmFieldContractTest.java new file mode 100644 index 00000000..3740a24a --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/glow/communication/annotation/GlowTrgmFieldContractTest.java @@ -0,0 +1,27 @@ +package io.shinhanlife.glow.communication.annotation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.reflect.Field; +import org.junit.jupiter.api.Test; + +class GlowTrgmFieldContractTest { + + private static class StandardTrgmDto { + @GlowTrgmField(order = 1, length = 8, decimal = 2, description = "금액", target = "body", type = "gs") + private String amount; + } + + @Test + void exposesTheStandardGlowTrgmFieldMetadata() throws Exception { + Field field = StandardTrgmDto.class.getDeclaredField("amount"); + GlowTrgmField metadata = field.getAnnotation(GlowTrgmField.class); + + assertEquals(1, metadata.order()); + assertEquals(8, metadata.length()); + assertEquals(2, metadata.decimal()); + assertEquals("금액", metadata.description()); + assertEquals("body", metadata.target()); + assertEquals("gs", metadata.type()); + } +} diff --git a/dap-was-lib/src/test/java/io/shinhanlife/glow/util/GlowMciParserTest.java b/dap-was-lib/src/test/java/io/shinhanlife/glow/util/GlowMciParserTest.java new file mode 100644 index 00000000..283a129a --- /dev/null +++ b/dap-was-lib/src/test/java/io/shinhanlife/glow/util/GlowMciParserTest.java @@ -0,0 +1,65 @@ +package io.shinhanlife.glow.util; + + +/** + * @package io.shinhanlife.glow.util + * @className GlowMciParserTest + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +import io.shinhanlife.glow.GlowMciFieldInfo; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class GlowMciParserTest { + + public static class DummyMciDto { + @GlowMciFieldInfo(order = 1, length = 10) + private String customerId; + + @GlowMciFieldInfo(order = 2, length = 15) + private String name; + + @GlowMciFieldInfo(order = 3, length = 3) + private int age; + + public String getCustomerId() { return customerId; } + public String getName() { return name; } + public int getAge() { return age; } + } + + @Test + @DisplayName("고정 길이 MCI 문자열을 DTO로 파싱하는 테스트") + public void testParseFixedLengthString() { + // given: 고정 길이 텍스트 (총 28자리) + // ID(10) + Name(15) + Age(3) + String rawMciString = "CUST000001KIM SHINHAN 035"; + + // when + DummyMciDto result = GlowMciParser.parse(rawMciString, DummyMciDto.class); + + // then + System.out.println("=================================================="); + System.out.println(" [원본 MCI 전문] : [" + rawMciString + "]"); + System.out.println(" [파싱된 ID (10자리)] : [" + result.getCustomerId() + "]"); + System.out.println(" [파싱된 Name (15자리)] : [" + result.getName() + "]"); + System.out.println(" [파싱된 Age (3자리)] : [" + result.getAge() + "]"); + System.out.println("=================================================="); + + assertNotNull(result); + assertEquals("CUST000001", result.getCustomerId()); + assertEquals("KIM SHINHAN", result.getName()); + assertEquals(35, result.getAge()); + } +} diff --git a/dap-was-lib/src/test/resources/mock-responses/cmm_memo_retriever.json b/dap-was-lib/src/test/resources/mock-responses/cmm_memo_retriever.json new file mode 100644 index 00000000..a949502b --- /dev/null +++ b/dap-was-lib/src/test/resources/mock-responses/cmm_memo_retriever.json @@ -0,0 +1,3 @@ +{ + "resultCode": "SUCCESS" +} diff --git a/dap-was-oth/Dockerfile b/dap-was-oth/Dockerfile new file mode 100644 index 00000000..51b0b079 --- /dev/null +++ b/dap-was-oth/Dockerfile @@ -0,0 +1,8 @@ +FROM eclipse-temurin:21-jre-alpine +WORKDIR /app +RUN apk add --no-cache tzdata +ENV TZ=Asia/Seoul +COPY dap-was-oth/build/libs/*-SNAPSHOT.jar app.jar +EXPOSE 8084 +ENTRYPOINT ["java", "-jar", "app.jar"] + diff --git a/dap-was-oth/build.gradle b/dap-was-oth/build.gradle new file mode 100644 index 00000000..85b96419 --- /dev/null +++ b/dap-was-oth/build.gradle @@ -0,0 +1,10 @@ +plugins { + // OTH Tool Pod를 독립 실행 가능한 Spring Boot JAR로 생성합니다. + id 'org.springframework.boot' +} + +dependencies { + // MCP Server, Tool 공통 처리, MCI/EAI 연동 기반은 dap-was-lib에서 상속합니다. + implementation project(':dap-was-lib') + implementation 'org.apache.poi:poi-ooxml:5.3.0' +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java new file mode 100644 index 00000000..87f56ee9 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaCommonCodeConverter.java @@ -0,0 +1,31 @@ +package io.shinhanlife.dap.mcc.biz.cmm.converter; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeResponse; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_O; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.converter + * @className MetaCommonCodeConverter + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  09863409    최초생성
    + * 
    + * 
    + */ +@Mapper(componentModel = "spring") +public interface MetaCommonCodeConverter { + @Mapping(target = "csNo", source = "groupCode", defaultValue = "GRP_COMM_CD") + CLCNNB00001_I toLegacyRequest(MetaCommonCodeRequest req); + + @Mapping(target = "codeList", ignore = true) + MetaCommonCodeResponse toResponse(CLCNNB00001_O mciRes); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaTableConverter.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaTableConverter.java new file mode 100644 index 00000000..c59e3524 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MetaTableConverter.java @@ -0,0 +1,31 @@ +package io.shinhanlife.dap.mcc.biz.cmm.converter; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableResponse; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_O; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.converter + * @className MetaTableConverter + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  09863409    최초생성
    + * 
    + * 
    + */ +@Mapper(componentModel = "spring") +public interface MetaTableConverter { + @Mapping(target = "csNo", source = "tableName", defaultValue = "TB_META_BAS") + CLCNNB00001_I toLegacyRequest(MetaTableRequest req); + + @Mapping(target = "tableList", ignore = true) + MetaTableResponse toResponse(CLCNNB00001_O mciRes); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MciSampleStringResponse.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MciSampleStringResponse.java new file mode 100644 index 00000000..441b1092 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MciSampleStringResponse.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.shinhanlife.glow.GlowMciFieldInfo; +import lombok.Data; +import java.util.List; + +/** + * @package io.shinhanlife.dap.mcc.dto + * @className MciSampleStringResponse + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MciSampleStringResponse { + @GlowMciFieldInfo(order = 1, length = 10, description = "이름") + private String name; + + @GlowMciFieldInfo(order = 2, length = 3, description = "나이") + private int age; + + @GlowMciFieldInfo(order = 3, length = 8, description = "가입일자(YYYYMMDD)") + private String joinDate; + + @GlowMciFieldInfo(order = 4, length = 2, description = "상태코드") + private String statusCode; + + @GlowMciFieldInfo(order = 5, length = 30, description = "타겟 리스트", target = MciSampleTargetDto.class) + private List targetList; +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MciSampleTargetDto.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MciSampleTargetDto.java new file mode 100644 index 00000000..6c11d39c --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MciSampleTargetDto.java @@ -0,0 +1,20 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import io.shinhanlife.glow.GlowMciFieldInfo; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.dto + * @className MciSampleTargetDto + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + */ +@Data +public class MciSampleTargetDto { + @GlowMciFieldInfo(order = 1, length = 5, description = "항목 코드") + private String itemCode; + + @GlowMciFieldInfo(order = 2, length = 5, description = "항목 값") + private String itemValue; +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java new file mode 100644 index 00000000..78a26a2a --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeRequest.java @@ -0,0 +1,39 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springaicommunity.mcp.annotation.McpToolParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.dto + * @className MetaCommonCodeRequest + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  09863409    최초생성
    + * 
    + * 
    + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MetaCommonCodeRequest { + @McpToolParam(description = "통합코드 그룹 ID (예: GRP_SYS_01, GRP_COMM_CD)", required = false) + @Schema(example = "GRP_001") + private String groupCode; + + +@McpToolParam(description = "코드명 검색 키워드 (예: 사용, 상태)", required = false) + private String codeName; + + @McpToolParam(description = "사용여부 (예: Y, N)", required = false) + @Schema(example = "Y") + private String useYn; +} \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java new file mode 100644 index 00000000..653a32a2 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaCommonCodeResponse.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.util.List; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.dto + * @className MetaCommonCodeResponse + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  09863409    최초생성
    + * 
    + * 
    + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MetaCommonCodeResponse { + private List codeList; + + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class MetaCommonCodeItem { + private String groupCode; + private String code; + private String codeName; + private String codeDesc; + private Integer sortSeq; + private String useYn; + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableRequest.java new file mode 100644 index 00000000..cccef9d4 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableRequest.java @@ -0,0 +1,40 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springaicommunity.mcp.annotation.McpToolParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.dto + * @className MetaTableRequest + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  09863409    최초생성
    + * 
    + * 
    + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MetaTableRequest { + @McpToolParam(description = "테이블 물리명 키워드 (예: TB_CUST_BAS, TB_CONT)", required = false) + @Schema(example = "TB_USER") + private String tableName; + + +@McpToolParam(description = "테이블 논리명(한글) 키워드 (예: 고객기본, 계약)", required = false) + @Schema(example = "고객기본") + private String tableLogicalName; + + @McpToolParam(description = "스키마/소유자명 (예: DAPADM, SHLOWN)", required = false) + @Schema(example = "DAPADM") + private String owner; +} \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableResponse.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableResponse.java new file mode 100644 index 00000000..1b9eaf6b --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MetaTableResponse.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.util.List; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.dto + * @className MetaTableResponse + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  09863409    최초생성
    + * 
    + * 
    + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MetaTableResponse { + private List tableList; + + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class MetaTableItem { + private String owner; + private String tableName; + private String tableLogicalName; + private String tableDesc; + private Integer columnCount; + private Long rowCount; + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/SampleStringRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/SampleStringRequest.java new file mode 100644 index 00000000..8553125e --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/SampleStringRequest.java @@ -0,0 +1,27 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + + +import io.swagger.v3.oas.annotations.media.Schema; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.dto + * @className SampleStringRequest + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SampleStringRequest { + @Schema(example = "test query") + private String query; +} \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/SampleStringResponse.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/SampleStringResponse.java new file mode 100644 index 00000000..ab1810d7 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/SampleStringResponse.java @@ -0,0 +1,38 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.shinhanlife.glow.communication.annotation.GlowTrgmField; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.dto + * @className SampleStringResponse + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SampleStringResponse { + @GlowTrgmField(order = 1, length = 10, description = "이름") + private String name; + + @GlowTrgmField(order = 2, length = 3, description = "나이") + private int age; + + @GlowTrgmField(order = 3, length = 8, description = "가입일자(YYYYMMDD)") + private String joinDate; + + @GlowTrgmField(order = 4, length = 2, description = "상태코드") + private String statusCode; + + @GlowTrgmField(order = 5, length = 10, description = "타겟") + private String target; +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/TemplateDownloadRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/TemplateDownloadRequest.java new file mode 100644 index 00000000..96340c54 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/TemplateDownloadRequest.java @@ -0,0 +1,38 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * @package io.shinhanlife.dap.mcc.dto + * @className TemplateDownloadRequest + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Getter +@Builder +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class TemplateDownloadRequest { + + /** + * 다운로드할 템플릿의 종류 ID (예: CUSTOMER_EXCEL, PRODUCT_PDF 등) + */ + @Schema(example = "TPL_001") + private String templateId; +} \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java new file mode 100644 index 00000000..2dc43e5d --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaCommonCodeUseCase.java @@ -0,0 +1,26 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase; + +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase + * @className MetaCommonCodeUseCase + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  09863409    최초생성
    + * 
    + * 
    + */ +public interface MetaCommonCodeUseCase { + @McpTool(name = "cmm_commonCode_lookup", title = "메타 공통코드 조회 툴", description = "메타 통합코드 목록을 조회해줘", annotations = @McpTool.McpAnnotations(openWorldHint = true)) + @ToolHint(register = false, requiresApproval = false, categoryKey = "cmm", mappingId = "CLCNNB00001") + Object execute(MetaCommonCodeRequest req); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java new file mode 100644 index 00000000..6fe6c90f --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MetaTableUseCase.java @@ -0,0 +1,26 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase; + +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase + * @className MetaTableUseCase + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  09863409    최초생성
    + * 
    + * 
    + */ +public interface MetaTableUseCase { + @McpTool(name = "cmm_meta_table", title = "메타 테이블 조회 툴", description = "메타 테이블 정보 목록을 조회해줘", annotations = @McpTool.McpAnnotations(openWorldHint = true)) + @ToolHint(register = false, requiresApproval = false, categoryKey = "cmm", mappingId = "CLCNNB00001") + Object execute(MetaTableRequest req); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/TemplateUtilityUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/TemplateUtilityUseCase.java new file mode 100644 index 00000000..c596e765 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/TemplateUtilityUseCase.java @@ -0,0 +1,14 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase; + + +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; +import io.shinhanlife.dap.mcc.biz.cmm.dto.*; + +import java.util.Map; + +public interface TemplateUtilityUseCase { + @McpTool(name = "cmm_template_url", title = "템플릿 유틸리티 툴", description = "요청한 템플릿(엑셀, 워드 등) 파일(양식)을 다운로드 받을 수 있는 시스템 URL을 반환합니다. AI는 이 URL을 사용자에게 마크다운 링크 형태로 제공해야 합니다.") + @ToolHint(categoryKey = "cmm") + Map getTemplateFileUrl(TemplateDownloadRequest req); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java new file mode 100644 index 00000000..92ddf781 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaCommonCodeUseCaseImpl.java @@ -0,0 +1,89 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.cmm.converter.MetaCommonCodeConverter; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeResponse; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaCommonCodeResponse.MetaCommonCodeItem; +import io.shinhanlife.dap.mcc.biz.cmm.usecase.MetaCommonCodeUseCase; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.MciCfpaClient; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl + * @className MetaCommonCodeUseCaseImpl + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  09863409    최초생성
    + * 
    + * 
    + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class MetaCommonCodeUseCaseImpl implements MetaCommonCodeUseCase { + + private final MciCfpaClient mci; + private final MetaCommonCodeConverter converter; + + @Override + public Object execute(MetaCommonCodeRequest req) { + log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "metaCommonCode", req); + try { + CLCNNB00001_I mciRequest = converter.toLegacyRequest(req); + + Object mciResponse = mci.callCfpa0001(mciRequest); + log.info("[MCI Tool] CLCNNB00001 MCI call completed. Returning response status: {}", + mciResponse != null); + + // 현업 테스트용으로 MCI 통신을 바이패스하고 하드코딩된 메타 통합코드 샘플 결과를 반환합니다. + MetaCommonCodeResponse res = new MetaCommonCodeResponse(); + List list = new ArrayList<>(); + + MetaCommonCodeItem item1 = new MetaCommonCodeItem(); + item1.setGroupCode(req.getGroupCode() != null ? req.getGroupCode() : "GRP_COMM_CD"); + item1.setCode("CD001"); + item1.setCodeName("진행중"); + item1.setCodeDesc("SR 요청 처리 진행 중 상태"); + item1.setSortSeq(1); + item1.setUseYn("Y"); + list.add(item1); + + MetaCommonCodeItem item2 = new MetaCommonCodeItem(); + item2.setGroupCode(req.getGroupCode() != null ? req.getGroupCode() : "GRP_COMM_CD"); + item2.setCode("CD002"); + item2.setCodeName("완료"); + item2.setCodeDesc("SR 요청 처리 완료 상태"); + item2.setSortSeq(2); + item2.setUseYn("Y"); + list.add(item2); + + MetaCommonCodeItem item3 = new MetaCommonCodeItem(); + item3.setGroupCode(req.getGroupCode() != null ? req.getGroupCode() : "GRP_COMM_CD"); + item3.setCode("CD003"); + item3.setCodeName("보류"); + item3.setCodeDesc("SR 요청 처리 일시 보류 상태"); + item3.setSortSeq(3); + item3.setUseYn("N"); + list.add(item3); + + res.setCodeList(list); + + return res; + } catch (Exception e) { + log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e); + return Map.of("status", "ERROR", "message", e.getMessage() != null ? e.getMessage() : "Unknown error"); + } + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaTableUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaTableUseCaseImpl.java new file mode 100644 index 00000000..ef981898 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MetaTableUseCaseImpl.java @@ -0,0 +1,89 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.cmm.converter.MetaTableConverter; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableResponse; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MetaTableResponse.MetaTableItem; +import io.shinhanlife.dap.mcc.biz.cmm.usecase.MetaTableUseCase; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.MciCfpaClient; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl + * @className MetaTableUseCaseImpl + * @description AX HUB 시스템 처리 클래스 + * @author 09863409 + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  09863409    최초생성
    + * 
    + * 
    + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class MetaTableUseCaseImpl implements MetaTableUseCase { + + private final MciCfpaClient mci; + private final MetaTableConverter converter; + + @Override + public Object execute(MetaTableRequest req) { + log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "metaTable", req); + try { + CLCNNB00001_I mciRequest = converter.toLegacyRequest(req); + + Object mciResponse = mci.callCfpa0001(mciRequest); + log.info("[MCI Tool] CLCNNB00001 MCI call completed. Returning response status: {}", + mciResponse != null); + + // 현업 테스트용으로 MCI 통신을 바이패스하고 하드코딩된 메타 테이블 샘플 결과를 반환합니다. + MetaTableResponse res = new MetaTableResponse(); + List list = new ArrayList<>(); + + MetaTableItem item1 = new MetaTableItem(); + item1.setOwner(req.getOwner() != null ? req.getOwner() : "DAPADM"); + item1.setTableName(req.getTableName() != null ? req.getTableName() : "TB_CUST_BAS"); + item1.setTableLogicalName("고객기본정보"); + item1.setTableDesc("고객 기본 프로필 및 인적사항 관리 테이블"); + item1.setColumnCount(35); + item1.setRowCount(1250000L); + list.add(item1); + + MetaTableItem item2 = new MetaTableItem(); + item2.setOwner(req.getOwner() != null ? req.getOwner() : "DAPADM"); + item2.setTableName("TB_CONT_MCD"); + item2.setTableLogicalName("계약주계약정보"); + item2.setTableDesc("보험 계약 주계약 상세 원장 테이블"); + item2.setColumnCount(58); + item2.setRowCount(3400000L); + list.add(item2); + + MetaTableItem item3 = new MetaTableItem(); + item3.setOwner(req.getOwner() != null ? req.getOwner() : "DAPADM"); + item3.setTableName("TB_CLAIM_DTL"); + item3.setTableLogicalName("청구접수상세"); + item3.setTableDesc("보험금 청구 접수 건별 내역 테이블"); + item3.setColumnCount(42); + item3.setRowCount(890000L); + list.add(item3); + + res.setTableList(list); + + return res; + } catch (Exception e) { + log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e); + return Map.of("status", "ERROR", "message", e.getMessage() != null ? e.getMessage() : "Unknown error"); + } + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/TemplateUtilityUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/TemplateUtilityUseCaseImpl.java new file mode 100644 index 00000000..5cb28b3f --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/TemplateUtilityUseCaseImpl.java @@ -0,0 +1,57 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.cmm.usecase.TemplateUtilityUseCase; +import io.shinhanlife.dap.mcc.biz.cmm.dto.TemplateDownloadRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@Service + +/** + * @package io.shinhanlife.dap.mcc.service + * @className TemplateUtilityService + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +public class TemplateUtilityUseCaseImpl implements TemplateUtilityUseCase { + + @Override + public Map getTemplateFileUrl(TemplateDownloadRequest data) { + try { + String templateId = (data != null && data.getTemplateId() != null) ? data.getTemplateId().toLowerCase() : "default"; + log.info("MCP 툴 호출됨: get_template_file_url, 요청 템플릿 ID: {}", templateId); + + String fileName = "sample_" + templateId + ".xlsx"; + String downloadUrl = "https://axhub-file-server.shinhanlife.io/downloads/" + fileName; + + Map result = new HashMap<>(); + result.put("status", "success"); + + Map contract = new HashMap<>(); + contract.put("fileName", fileName); + contract.put("downloadUrl", downloadUrl); + contract.put("message", "다운로드 링크가 성공적으로 생성되었습니다. AI는 이 링크를 마크다운 형식으로 사용자에게 전달해야 합니다."); + contract.put("status", "success"); + + result.put("contracts", Collections.singletonList(contract)); + + return result; + } catch (Exception e) { + log.error("getTemplateFileUrl 내부 예외 발생", e); + throw new RuntimeException("템플릿 URL 생성 실패", e); + } + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/converter/InsuranceClaimProcessorConverter.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/converter/InsuranceClaimProcessorConverter.java new file mode 100644 index 00000000..e6d7d139 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/converter/InsuranceClaimProcessorConverter.java @@ -0,0 +1,17 @@ +package io.shinhanlife.dap.mcc.biz.ins.converter; + +import io.shinhanlife.dap.mcc.biz.ins.dto.InsuranceClaimProcessorRequest; +import io.shinhanlife.dap.mcc.biz.ins.dto.InsuranceClaimProcessorResponse; +import io.shinhanlife.dap.mcc.infra.itrf.http.insurance.io.InsuranceClaimProcessorHttpRequest; +import io.shinhanlife.dap.mcc.infra.itrf.http.insurance.io.InsuranceClaimProcessorHttpResponse; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface InsuranceClaimProcessorConverter { + // Field names differ? Add mappings like this before the method. + // @Mapping(source = "sourceField", target = "targetField") + InsuranceClaimProcessorHttpRequest toHttpRequest(InsuranceClaimProcessorRequest request); + InsuranceClaimProcessorResponse toResponse(InsuranceClaimProcessorHttpResponse httpResponse); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/dto/InsuranceClaimProcessorRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/dto/InsuranceClaimProcessorRequest.java new file mode 100644 index 00000000..ccc1338d --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/dto/InsuranceClaimProcessorRequest.java @@ -0,0 +1,19 @@ +package io.shinhanlife.dap.mcc.biz.ins.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class InsuranceClaimProcessorRequest { + @Schema(description = "보험 청구 번호", example = "CLM20230001", requiredMode = Schema.RequiredMode.REQUIRED) + private String claimNumber; + + @Schema(description = "청구 금액", example = "1500000.00", requiredMode = Schema.RequiredMode.REQUIRED) + private Double claimAmount; + + @Schema(description = "청구 일자 (YYYY-MM-DD)", example = "2023-09-15", requiredMode = Schema.RequiredMode.REQUIRED) + private String claimDate; + +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/dto/InsuranceClaimProcessorResponse.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/dto/InsuranceClaimProcessorResponse.java new file mode 100644 index 00000000..a4830ea5 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/dto/InsuranceClaimProcessorResponse.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.biz.ins.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class InsuranceClaimProcessorResponse { + private String resultCode; + + private String resultMessage; + @Schema(description = "청구 처리 고유 식별자", example = "CLM20230001", requiredMode = Schema.RequiredMode.REQUIRED) + private String claimId; + +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/InsuranceClaimProcessorUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/InsuranceClaimProcessorUseCase.java new file mode 100644 index 00000000..ec3faaa9 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/InsuranceClaimProcessorUseCase.java @@ -0,0 +1,27 @@ +package io.shinhanlife.dap.mcc.biz.ins.usecase; + +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; +import io.shinhanlife.dap.mcc.biz.ins.dto.InsuranceClaimProcessorRequest; +import io.shinhanlife.dap.mcc.biz.ins.dto.InsuranceClaimProcessorResponse; + +/** + * @package io.shinhanlife.dap.mcc.biz.ins.usecase + * @className InsuranceClaimProcessorUseCase + * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @author Admin + * @create 2026.08.11 + *
    + * ---------- 媛쒖젙?대젰 ----------
    + * ?섏젙??     ?섏젙??   ?섏젙?댁슜
    + * ---------- -------- ---------------------------
    + * 2026.08.11  Admin    理쒖큹?앹꽦
    + *
    + * 
    + */ +public interface InsuranceClaimProcessorUseCase { + + @McpTool(name = "ins_insurance_processor", title = "보험금 청구", description = "보험금 청구 요청을 처리하고 결과를 반환하는 LLM 도구 가이드") + @ToolHint(register = false, categoryKey = "ins", mappingId = "CLAIM0000001") + InsuranceClaimProcessorResponse execute(InsuranceClaimProcessorRequest req); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/impl/InsuranceClaimProcessorUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/impl/InsuranceClaimProcessorUseCaseImpl.java new file mode 100644 index 00000000..e18e2cc2 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/ins/usecase/impl/InsuranceClaimProcessorUseCaseImpl.java @@ -0,0 +1,30 @@ +package io.shinhanlife.dap.mcc.biz.ins.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.ins.converter.InsuranceClaimProcessorConverter; +import io.shinhanlife.dap.mcc.biz.ins.dto.InsuranceClaimProcessorRequest; +import io.shinhanlife.dap.mcc.biz.ins.dto.InsuranceClaimProcessorResponse; +import io.shinhanlife.dap.mcc.infra.itrf.http.insurance.InsuranceClient; +import io.shinhanlife.dap.mcc.infra.itrf.http.insurance.io.InsuranceClaimProcessorHttpRequest; +import io.shinhanlife.dap.mcc.infra.itrf.http.insurance.io.InsuranceClaimProcessorHttpResponse; +import io.shinhanlife.dap.mcc.biz.ins.usecase.InsuranceClaimProcessorUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class InsuranceClaimProcessorUseCaseImpl implements InsuranceClaimProcessorUseCase { + + private final InsuranceClaimProcessorConverter converter; + private final InsuranceClient insuranceClient; + + @Override + public InsuranceClaimProcessorResponse execute(InsuranceClaimProcessorRequest req) { + InsuranceClaimProcessorHttpRequest httpRequest = converter.toHttpRequest(req); + InsuranceClaimProcessorHttpResponse httpResponse = insuranceClient.call(httpRequest, InsuranceClaimProcessorHttpResponse.class); + + InsuranceClaimProcessorResponse response = converter.toResponse(httpResponse); + response.setResultCode("SUCCESS"); + response.setResultMessage("HTTP API call completed."); + return response; + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/converter/Onnba3011Converter.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/converter/Onnba3011Converter.java new file mode 100644 index 00000000..97c6820c --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/converter/Onnba3011Converter.java @@ -0,0 +1,29 @@ +package io.shinhanlife.dap.mcc.biz.oth.converter; + +import io.shinhanlife.dap.mcc.biz.oth.dto.Onnba3011Request; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import org.mapstruct.Mapper; + +/** + * @package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.converter + * @className Onnba3011Converter + * @description Converts ONNBA tool input into the CLCNNB00001 MCI payload + * @author 0986406 + * @create 2026.07.27 + *
    + * ---------- revision history ----------
    + * date       author    description
    + * ---------- --------- ---------------------------
    + * 2026.07.27 0986406    initial creation
    + * 
    + */ +@Mapper(componentModel = "spring") +public interface Onnba3011Converter { + + CLCNNB00001_I toMciRequest(Onnba3011Request source); + + CLCNNB00001_I.UnfcPrbuIrcoAdduDto toUnfcPrbuIrcoAddu( + Onnba3011Request.UnfcPrbuIrcoAdduDto source); + + CLCNNB00001_I.SucoIspaBasDto toSucoIspaBas(Onnba3011Request.SucoIspaBasDto source); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/dto/Onnba3011Request.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/dto/Onnba3011Request.java new file mode 100644 index 00000000..7eafeaae --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/dto/Onnba3011Request.java @@ -0,0 +1,119 @@ +package io.shinhanlife.dap.mcc.biz.oth.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.dto + * @className Onnba3011ReqDto + * @description 보종By가입설계한도계산조회 MCI 요청 전문 (ONNBA3011_I) + * @author 0986406 + * @create 2026.09.01 + */ +@Data +public class Onnba3011Request { + + @JsonPropertyDescription("통합기계약보험 (유형: gs, 길이: 72)") + @JsonProperty("unfcPrbuIrcoAddu") + private UnfcPrbuIrcoAdduDto unfcPrbuIrcoAddu; + + @JsonPropertyDescription("처리구분코드 (길이: 1)") + @JsonProperty("dalScCd") + private String dalScCd; + + @JsonPropertyDescription("고객청약관계 (길이: 2)") + @JsonProperty("cstSucoRltyCd") + private String cstSucoRltyCd; + + @JsonPropertyDescription("고객번호 (길이: 12)") + @JsonProperty("csNo") + private String csNo; + + @JsonPropertyDescription("주민등록번호 (길이: 50)") + @JsonProperty("rdreNo") + private String rdreNo; + + @JsonPropertyDescription("통합급부계산 (길이: 1)") + @JsonProperty("unfcPvsCalReqYn") + private String unfcPvsCalReqYn; + + @JsonPropertyDescription("한국신용정보 (길이: 1)") + @JsonProperty("kcisPymmTnnrRequest") + private String kcisPymmTnnrRequest; + + @JsonPropertyDescription("한도초과여부 (길이: 1)") + @JsonProperty("lmovYn") + private String lmovYn; + + @JsonPropertyDescription("일반경유승인 (길이: 1)") + @JsonProperty("genPsthApvTrgtYn") + private String genPsthApvTrgtYn; + + @JsonPropertyDescription("보험사한도초과 (길이: 1)") + @JsonProperty("ircoLmovEcpbTrgtYn") + private String ircoLmovEcpbTrgtYn; + + @JsonPropertyDescription("진단계산여부 (길이: 1)") + @JsonProperty("digCalYn") + private String digCalYn; + + @JsonPropertyDescription("기계약포함진단 (길이: 1)") + @JsonProperty("prbuIciDigCalYn") + private String prbuIciDigCalYn; + + @JsonPropertyDescription("청약심사기본Dto (유형: gs, 길이: 2532)") + @JsonProperty("sucoIspaBasDto") + private SucoIspaBasDto sucoIspaBasDto; + + // ----- Nested DTO Classes ----- + + @Data + public static class UnfcPrbuIrcoAdduDto { + // 실제 필요한 하위 필드들 추가 (사진 생략부분) + } + + @Data + public static class SucoIspaBasDto { + + @JsonPropertyDescription("계약처리유형 (길이: 2)") + @JsonProperty("ccnDalTypCd") + private String ccnDalTypCd; + + @JsonPropertyDescription("신계약입력경로 (길이: 2)") + @JsonProperty("nwcnptCursCd") + private String nwcnptCursCd; + + @JsonPropertyDescription("개인단체계약 (길이: 2)") + @JsonProperty("induAsctScCd") + private String induAsctScCd; + + @JsonPropertyDescription("모집조직번호 (길이: 7)") + @JsonProperty("cepeOgnzNo") + private String cepeOgnzNo; + + @JsonPropertyDescription("모집자사번번호 (길이: 8)") + @JsonProperty("cepePrafNo") + private String cepePrafNo; + + @JsonPropertyDescription("수금조직번호 (길이: 7)") + @JsonProperty("clmoOgnzNo") + private String clmoOgnzNo; + + @JsonPropertyDescription("수금자사번번호 (길이: 8)") + @JsonProperty("clmoPrafNo") + private String clmoPrafNo; + + @JsonPropertyDescription("청약일자 (길이: 20)") + @JsonProperty("sucoYmd") + private String sucoYmd; + + @JsonPropertyDescription("발행일자 (길이: 20)") + @JsonProperty("ispDt") + private String ispDt; + + @JsonPropertyDescription("계약일자 (길이: 20)") + @JsonProperty("contYmd") + private String contYmd; + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/Onnba3011UseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/Onnba3011UseCase.java new file mode 100644 index 00000000..1e88db13 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/Onnba3011UseCase.java @@ -0,0 +1,11 @@ +package io.shinhanlife.dap.mcc.biz.oth.usecase; + +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; +import io.shinhanlife.dap.mcc.biz.oth.dto.*; + +public interface Onnba3011UseCase { + @McpTool(name = "oth_onnba3011_call", description = "Onnba3011 호출 툴") + @ToolHint(categoryKey = "oth", register = false) + Object execute(Onnba3011Request req); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImpl.java new file mode 100644 index 00000000..bf8e2138 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImpl.java @@ -0,0 +1,58 @@ +package io.shinhanlife.dap.mcc.biz.oth.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.oth.converter.Onnba3011Converter; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.MciCfpaClient; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import io.shinhanlife.dap.mcc.biz.oth.usecase.Onnba3011UseCase; +import io.shinhanlife.dap.mcc.biz.oth.dto.Onnba3011Request; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * @package io.shinhanlife.dap.mcc.service + * @className OnnbaMciToolService + * @description 보종By가입설계한도계산조회 MCI 연동 툴 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class Onnba3011UseCaseImpl implements Onnba3011UseCase { + + private static final String INTERFACE_CODE_3011 = "CLCNNB00001"; + + // 공통 MCI Client 주입 + private final MciCfpaClient mciCfpaClient; + private final Onnba3011Converter onnba3011Converter; + /** + * AI Agent가 호출하게 될 메서드입니다. + */ + @Override + public Object execute(Onnba3011Request req) { + log.info("[MCI Tool] 보종By가입설계한도계산조회 요청 수신."); + + try { + // MciCfpaClient를 통한 호출 + CLCNNB00001_I mciRequest = onnba3011Converter.toMciRequest(req); + Object response = mciCfpaClient.callCfpa0001(mciRequest); + + log.info("[MCI Tool] Glow 기반 MCI 연동 성공."); + + // 결과 반환 + return response != null ? response : "{\"status\":\"SUCCESS\", \"message\":\"GlowMciComponent 통신 완료\"}"; + + } catch (Exception e) { + log.error("[MCI Tool] MCI 연동 중 오류 발생: {}", e.getMessage(), e); + return "{\"status\":\"ERROR\", \"message\":\"MCI 통신 실패: " + e.getMessage() + "\"}"; + } + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/DailyQuoteRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/DailyQuoteRequest.java new file mode 100644 index 00000000..848b2e84 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/DailyQuoteRequest.java @@ -0,0 +1,19 @@ +package io.shinhanlife.dap.mcc.biz.smp.dto; + + + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springaicommunity.mcp.annotation.McpToolParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class DailyQuoteRequest { + + @McpToolParam(description = "카테고리 (예: 속담 등)", required = false) + @Schema(example = "속담") + private String category; +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/DailyQuoteResponse.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/DailyQuoteResponse.java new file mode 100644 index 00000000..bb09b716 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/DailyQuoteResponse.java @@ -0,0 +1,3 @@ +package io.shinhanlife.dap.mcc.biz.smp.dto; + +public record DailyQuoteResponse(String quote, String author) {} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/ExchangeRateRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/ExchangeRateRequest.java new file mode 100644 index 00000000..8a2f2fe2 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/ExchangeRateRequest.java @@ -0,0 +1,18 @@ +package io.shinhanlife.dap.mcc.biz.smp.dto; + + + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springaicommunity.mcp.annotation.McpToolParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ExchangeRateRequest { + @McpToolParam(description = "환율 코드 (예: USD 등)", required = false) + @Schema(example = "USD") + private String currencyCode; +} \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/ExchangeRateResponse.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/ExchangeRateResponse.java new file mode 100644 index 00000000..4968d01a --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/ExchangeRateResponse.java @@ -0,0 +1,3 @@ +package io.shinhanlife.dap.mcc.biz.smp.dto; + +public record ExchangeRateResponse(String baseCurrency, String targetCurrency, double rate) {} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/TeamMemberRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/TeamMemberRequest.java new file mode 100644 index 00000000..229232e7 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/TeamMemberRequest.java @@ -0,0 +1,32 @@ +package io.shinhanlife.dap.mcc.biz.smp.dto; + + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springaicommunity.mcp.annotation.McpToolParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.biz.smp.dto + * @className TeamMemberRequest + * @description AX HUB 시스템 처리 클래스 + * @author jade + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  jade    최초생성
    + *
    + * 
    + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TeamMemberRequest { + @McpToolParam(description = "조회할 팀 이름 (예: AX, MCP, TOOL, 전체 등)", required = false) + @Schema(example = "TOOL") + private String teamName; + +} \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/TeamMemberResponse.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/TeamMemberResponse.java new file mode 100644 index 00000000..f10b8ace --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/TeamMemberResponse.java @@ -0,0 +1,24 @@ +package io.shinhanlife.dap.mcc.biz.smp.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.biz.smp.dto + * @className TeamMemberResponse + * @description AX HUB 시스템 처리 클래스 + * @author jade + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  jade    최초생성
    + *
    + * 
    + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class TeamMemberResponse { + private String result; +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/WeatherRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/WeatherRequest.java new file mode 100644 index 00000000..3d4e8a78 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/WeatherRequest.java @@ -0,0 +1,27 @@ +package io.shinhanlife.dap.mcc.biz.smp.dto; + + + +import org.springaicommunity.mcp.annotation.McpToolParam; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; + +/** + * @package io.shinhanlife.dap.mcc.dto + * @className WeatherRequest + * @description 기상 조회 요청 클래스 + * @author 0986406 + * @create 2026.07.14 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.14  0986406    최초생성
    + * 
    + * 
    + */ +public record WeatherRequest( + @McpToolParam(description = "도시를 입력하세여(예: 서울)", required = true) + String city +) { +} \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/WeatherResponse.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/WeatherResponse.java new file mode 100644 index 00000000..4e153681 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/dto/WeatherResponse.java @@ -0,0 +1,24 @@ +package io.shinhanlife.dap.mcc.biz.smp.dto; + +/** + * @package io.shinhanlife.dap.mcc.dto + * @className WeatherResponse + * @description 기상 조회 응답 클래스 + * @author 0986406 + * @create 2026.07.14 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.14  0986406    최초생성
    + * 
    + * 
    + */ +public record WeatherResponse( + String city, + double temperature, + double windSpeed, + String reportTime, + String summary +) { +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/DailyQuoteToolUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/DailyQuoteToolUseCase.java new file mode 100644 index 00000000..6c5fe77a --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/DailyQuoteToolUseCase.java @@ -0,0 +1,13 @@ +package io.shinhanlife.dap.mcc.biz.smp.usecase; + + +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; +import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteRequest; +import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteResponse; + +public interface DailyQuoteToolUseCase { + @McpTool(name = "smp_quote_daily", title = "오늘의 명언 툴", description = "무작위로 영감을 주는 명언을 하나 가져옵니다.") + @ToolHint(register = false, categoryKey = "smp", mappingId = "QUOTE_001") + DailyQuoteResponse execute(DailyQuoteRequest req); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/ExchangeRateToolUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/ExchangeRateToolUseCase.java new file mode 100644 index 00000000..aa4f3cb4 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/ExchangeRateToolUseCase.java @@ -0,0 +1,15 @@ +package io.shinhanlife.dap.mcc.biz.smp.usecase; + + +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; +import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateRequest; +import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateResponse; +import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateRequest; +import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateResponse; + +public interface ExchangeRateToolUseCase { + @McpTool(name = "smp_exchangeRate_inquiry", title = "실시간 환율 조회 툴", description = "원하는 통화의 실시간 환율을 조회합니다. (예: USD, EUR, JPY)") + @ToolHint(register = false, categoryKey = "smp", mappingId = "EXCHANGE_001") + ExchangeRateResponse execute(ExchangeRateRequest req); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/TeamMemberUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/TeamMemberUseCase.java new file mode 100644 index 00000000..b947f7e7 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/TeamMemberUseCase.java @@ -0,0 +1,12 @@ +package io.shinhanlife.dap.mcc.biz.smp.usecase; + +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; + +import io.shinhanlife.dap.mcc.biz.smp.dto.TeamMemberRequest; + +public interface TeamMemberUseCase { + @McpTool(name = "smp_team_list", title = "신한라이프 MCP, TOOL 파트 구성원 조회", description = "신한라이프 MCP, TOOL 파트 구성원을 조회합니다.", annotations = @McpTool.McpAnnotations(openWorldHint = true)) + @ToolHint(register = false, requiresApproval = false, categoryKey = "smp", mappingId = "DIRECT0001") + Object execute(TeamMemberRequest req); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/WeatherToolUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/WeatherToolUseCase.java new file mode 100644 index 00000000..b960ade9 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/WeatherToolUseCase.java @@ -0,0 +1,12 @@ +package io.shinhanlife.dap.mcc.biz.smp.usecase; + + +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; +import io.shinhanlife.dap.mcc.biz.smp.dto.*; + +public interface WeatherToolUseCase { + @McpTool(name = "smp_weather_inquiry", title = "날씨 조회 툴", description = "특정 도시의 현재 날씨, 온도, 풍속 정보를 조회합니다.") + @ToolHint(register = false, categoryKey = "smp", mappingId = "WEATHER_001") + WeatherResponse execute(WeatherRequest req); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/DailyQuoteToolUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/DailyQuoteToolUseCaseImpl.java new file mode 100644 index 00000000..ed9f04ff --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/DailyQuoteToolUseCaseImpl.java @@ -0,0 +1,45 @@ +package io.shinhanlife.dap.mcc.biz.smp.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteRequest; +import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteResponse; +import io.shinhanlife.dap.mcc.biz.smp.usecase.DailyQuoteToolUseCase; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Random; + +/** + * @package io.shinhanlife.dap.mcc.service + * @className DailyQuoteToolService + * @description 랜덤 명언 제공 툴 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Slf4j +@Service +public class DailyQuoteToolUseCaseImpl implements DailyQuoteToolUseCase { + + private final List quotes = List.of( + new DailyQuoteResponse("성공은 매일 반복한 작은 노력들의 합이다.", "로버트 콜리어"), + new DailyQuoteResponse("시작이 반이다.", "아리스토텔레스"), + new DailyQuoteResponse("포기하지 않는 한 실패는 없다.", "알베르트 아인슈타인"), + new DailyQuoteResponse("가장 큰 위험은 위험 없는 삶이다.", "스티븐 코비") + ); + + @Override + public DailyQuoteResponse execute(DailyQuoteRequest req) { + int index = new Random().nextInt(quotes.size()); + DailyQuoteResponse selected = quotes.get(index); + + log.info("[DailyQuoteTool] 명언 제공 완료: {}", selected.author()); + return selected; + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/ExchangeRateToolUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/ExchangeRateToolUseCaseImpl.java new file mode 100644 index 00000000..029d47e5 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/ExchangeRateToolUseCaseImpl.java @@ -0,0 +1,49 @@ +package io.shinhanlife.dap.mcc.biz.smp.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateRequest; +import io.shinhanlife.dap.mcc.biz.smp.dto.ExchangeRateResponse; +import io.shinhanlife.dap.mcc.biz.smp.usecase.ExchangeRateToolUseCase; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +/** + * @package io.shinhanlife.dap.mcc.service + * @className ExchangeRateToolService + * @description 실시간 환율 조회 툴 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Slf4j +@Service +public class ExchangeRateToolUseCaseImpl implements ExchangeRateToolUseCase { + + private final RestClient restClient; + + public ExchangeRateToolUseCaseImpl() { + this.restClient = RestClient.create(); + } + + @Override + public ExchangeRateResponse execute(ExchangeRateRequest req) { + String targetCurrency = req.getCurrencyCode() != null ? req.getCurrencyCode().toUpperCase().trim() : "USD"; + + // 간단한 모의 데이터로 반환 (실제 구현 시 외부 연동) + double dummyRate = 1350.50; + if (targetCurrency.contains("JPY")) { + dummyRate = 905.20; + } else if (targetCurrency.contains("EUR")) { + dummyRate = 1450.30; + } + + log.info("[ExchangeRateTool] 환율 조회 완료: {} -> {}", targetCurrency, dummyRate); + return new ExchangeRateResponse("KRW", targetCurrency, dummyRate); + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/TeamMemberUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/TeamMemberUseCaseImpl.java new file mode 100644 index 00000000..c7927e2b --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/TeamMemberUseCaseImpl.java @@ -0,0 +1,53 @@ +package io.shinhanlife.dap.mcc.biz.smp.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.smp.dto.TeamMemberRequest; +import io.shinhanlife.dap.mcc.biz.smp.dto.TeamMemberResponse; +import io.shinhanlife.dap.mcc.biz.smp.usecase.TeamMemberUseCase; +import org.springframework.stereotype.Service; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * @package io.shinhanlife.dap.mcc.biz.smp.usecase.impl + * @className TeamMemberUseCaseImpl + * @description AX HUB 시스템 처리 클래스 + * @author jade + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  jade    최초생성
    + *
    + * 
    + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class TeamMemberUseCaseImpl implements TeamMemberUseCase { + + @Override + public Object execute(TeamMemberRequest req) { + log.info("[A01] 신한라이프 MCP, TOOL 파트 구성원 조회 요청: {}", req); + + String filter = req != null && req.getTeamName() != null ? req.getTeamName().toUpperCase() : "전체"; + + String resultString = ""; + if (filter.contains("AX")) { + resultString += "ax 추진팀 박세진 프로\n"; + } else if (filter.contains("MCP") && !filter.contains("TOOL")) { + resultString += "MCP 팀은 고석민 수석 , 장효원 책임\n"; + } else if (filter.contains("TOOL") && !filter.contains("MCP")) { + resultString += "TOOL 팀은 김형식 수석 ,김영진 책임 , 김도겸 대리 , 이주희 선임 , 문주현 선임 , 이보람 대리 , 박수빈 대리\n"; + } else { + resultString += "ax 추진팀 박세진 프로\n" + + "MCP & TOOL 팀 담당자는 윤희준 이사\n" + + "MCP 팀은 고석민 수석 , 장효원 책임\n" + + "TOOL 팀은 김형식 수석 ,김영진 책임 , 김도겸 대리 , 이주희 선임 , 문주현 선임 , 이보람 대리 , 박수빈 대리"; + } + + TeamMemberResponse res = new TeamMemberResponse(); + res.setResult(resultString.trim()); + return res; + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/WeatherToolUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/WeatherToolUseCaseImpl.java new file mode 100644 index 00000000..9cd244fc --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/WeatherToolUseCaseImpl.java @@ -0,0 +1,104 @@ +package io.shinhanlife.dap.mcc.biz.smp.usecase.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.shinhanlife.dap.mcc.biz.smp.usecase.WeatherToolUseCase; +import io.shinhanlife.dap.mcc.biz.smp.dto.WeatherRequest; +import io.shinhanlife.dap.mcc.biz.smp.dto.WeatherResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClient; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * @package io.shinhanlife.dap.mcc.service + * @className WeatherToolService + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.07.14 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.14  0986406    최초생성
    + * 
    + * 
    + */ +@Slf4j +@Service +public class WeatherToolUseCaseImpl implements WeatherToolUseCase { + + private final RestClient restClient; + + public WeatherToolUseCaseImpl() { + this.restClient = RestClient.create(); + } + public WeatherResponse execute(WeatherRequest req) { + String city = req.city() != null ? req.city().trim() : "서울"; + + // 지역별 위경도 매핑 (간단한 예시) + double lat = 37.566; + double lon = 126.978; + + if (city.contains("부산")) { + lat = 35.179; + lon = 129.075; + } else if (city.contains("제주")) { + lat = 33.499; + lon = 126.531; + } else if (city.contains("인천")) { + lat = 37.456; + lon = 126.705; + } + + try { + String newRequestId = java.util.UUID.randomUUID().toString(); + String url = String.format("https://api.open-meteo.com/v1/forecast?latitude=%f&longitude=%f¤t_weather=true", lat, lon); + + log.info("[WeatherTool] OUTBOUND HTTP IN - request-id: {}", newRequestId); + log.info("[WeatherTool] 날씨 조회 요청 URL: {}", url); + + String responseStr = restClient.get() + .uri(url) + .header("request-id", newRequestId) + .retrieve() + .body(String.class); + + log.info("[WeatherTool] OUTBOUND HTTP OUT - request-id: {}", newRequestId); + + ObjectMapper mapper = new ObjectMapper(); + JsonNode response = mapper.readTree(responseStr); + + if (response != null && response.has("current_weather")) { + JsonNode current = response.get("current_weather"); + double temp = current.path("temperature").asDouble(); + double windSpeed = current.path("windspeed").asDouble(); + String time = current.path("time").asText(); + + int weatherCode = current.path("weathercode").asInt(); + String summary = parseWeatherCode(weatherCode); + + String formattedTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); + + return new WeatherResponse(city, temp, windSpeed, formattedTime, summary); + } + } catch (Exception e) { + log.error("[WeatherTool] 날씨 API 연동 실패: {}", e.getMessage()); + return new WeatherResponse(city, 0.0, 0.0, "", "날씨 정보를 불러오는데 실패했습니다."); + } + + return new WeatherResponse(city, 0.0, 0.0, "", "알 수 없는 응답입니다."); + } + + private String parseWeatherCode(int code) { + if (code == 0) return "맑음 (Clear)"; + if (code >= 1 && code <= 3) return "구름조금/흐림 (Cloudy)"; + if (code >= 45 && code <= 48) return "안개 (Fog)"; + if (code >= 51 && code <= 67) return "비/이슬비 (Rain)"; + if (code >= 71 && code <= 77) return "눈 (Snow)"; + if (code >= 95) return "뇌우/천둥번개 (Thunderstorm)"; + return "알 수 없음 (Unknown)"; + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/converter/SolReqDetailConverter.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/converter/SolReqDetailConverter.java new file mode 100644 index 00000000..6b3a7019 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/converter/SolReqDetailConverter.java @@ -0,0 +1,31 @@ +package io.shinhanlife.dap.mcc.biz.sol.converter; + +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailResponse; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000002_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000002_O; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.converter + * @className SolReqDetailConverter + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + *
    + * 
    + */ +@Mapper(componentModel = "spring") +public interface SolReqDetailConverter { + + @Mapping(source = "srId", target = "srId") + SOLG00000002_I toLegacyRequest(SolReqDetailRequest req); + + SolReqDetailResponse toResponse(SOLG00000002_O mciRes); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/converter/SolReqListConverter.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/converter/SolReqListConverter.java new file mode 100644 index 00000000..f71e31e2 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/converter/SolReqListConverter.java @@ -0,0 +1,33 @@ +package io.shinhanlife.dap.mcc.biz.sol.converter; + +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListResponse; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_O; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.converter + * @className SolReqListConverter + * @description AX HUB 시스템 처리 클래스 + * @author jade + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  jade    최초생성
    + *
    + * 
    + */ +@Mapper(componentModel = "spring") +public interface SolReqListConverter { + @Mapping(source = "status", target = "reqStatus", defaultValue = "진행중") + @Mapping(source = "period", target = "reqPeriod", defaultValue = "최근 3개월") + @Mapping(source = "target", target = "reqTarget", defaultValue = "나의 업무") + SOLG00000001_I toLegacyRequest(SolReqListRequest req); + + SolReqListResponse toResponse(SOLG00000001_O mciRes); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailRequest.java new file mode 100644 index 00000000..0d3e0976 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailRequest.java @@ -0,0 +1,33 @@ +package io.shinhanlife.dap.mcc.biz.sol.dto; + + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springaicommunity.mcp.annotation.McpToolParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.dto + * @className SolReqDetailRequest + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + *
    + * 
    + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SolReqDetailRequest { + + @McpToolParam(description = "상세 조회할 SOL 의뢰서 ID", required = true) + @Schema(example = "SR20260805") + private String srId; + +} \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailResponse.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailResponse.java new file mode 100644 index 00000000..b1c1c230 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqDetailResponse.java @@ -0,0 +1,33 @@ +package io.shinhanlife.dap.mcc.biz.sol.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.dto + * @className SolReqDetailResponse + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + *
    + * 
    + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SolReqDetailResponse { + + private String srId; + private String srName; + private String process; + private String devStage; + private String appName; + private String requester; + private String requestDate; + private String dueDate; + private String description; +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqListRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqListRequest.java new file mode 100644 index 00000000..e447e69f --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqListRequest.java @@ -0,0 +1,39 @@ +package io.shinhanlife.dap.mcc.biz.sol.dto; + + + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.springaicommunity.mcp.annotation.McpToolParam; +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.dto + * @className SolReqListRequest + * @description AX HUB 시스템 처리 클래스 + * @author jade + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  jade    최초생성
    + *
    + * 
    + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SolReqListRequest { + @McpToolParam(description = "진행상태 (예: 진행중, 완료 등)", required = false) + @Schema(example = "RECEIVED") + private String status; + + +@McpToolParam(description = "조회기간 (예: 1개월, 3개월 등)", required = false) + private String period; + + @McpToolParam(description = "조회대상 (예: 나의 업무, 전체 등)", required = false) + @Schema(example = "USER") + private String target; +} \ No newline at end of file diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqListResponse.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqListResponse.java new file mode 100644 index 00000000..842694bb --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/dto/SolReqListResponse.java @@ -0,0 +1,37 @@ +package io.shinhanlife.dap.mcc.biz.sol.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; + +import java.util.List; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.dto + * @className SolReqListResponse + * @description AX HUB 시스템 처리 클래스 + * @author jade + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  jade    최초생성
    + *
    + * 
    + */ +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class SolReqListResponse { + private List reqList; + + @Data + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class SolReqListItem { + private String srId; + private String srName; + private String process; + private String devStage; + private String appName; + private String requester; + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java new file mode 100644 index 00000000..ef8521ea --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqDetailUseCase.java @@ -0,0 +1,27 @@ +package io.shinhanlife.dap.mcc.biz.sol.usecase; + +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; + +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.usecase + * @className SolReqDetailUseCase + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + *
    + * 
    + */ +public interface SolReqDetailUseCase { + + @McpTool(name = "sol_request_detail", title = "SolReqDetail 툴", description = "SOL 의뢰서 상세 조회", annotations = @McpTool.McpAnnotations(openWorldHint = true, readOnlyHint = true)) + @ToolHint(register = false, requiresApproval = false, categoryKey = "sol", mappingId = "SOLG00000002") + Object execute(SolReqDetailRequest req); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqListUseCase.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqListUseCase.java new file mode 100644 index 00000000..f82ebe8d --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/SolReqListUseCase.java @@ -0,0 +1,12 @@ +package io.shinhanlife.dap.mcc.biz.sol.usecase; + +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; + +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest; + +public interface SolReqListUseCase { + @McpTool(name = "sol_request_list", title = "SolReqList 툴", description = "SOL 의뢰서 목록 조회해줘", annotations = @McpTool.McpAnnotations(openWorldHint = true)) + @ToolHint(register = false, requiresApproval = false, categoryKey = "sol", mappingId = "SOLG00000001") + Object execute(SolReqListRequest req); +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImpl.java new file mode 100644 index 00000000..be197500 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImpl.java @@ -0,0 +1,101 @@ +package io.shinhanlife.dap.mcc.biz.sol.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.sol.converter.SolReqDetailConverter; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailResponse; +import io.shinhanlife.dap.mcc.biz.sol.usecase.SolReqDetailUseCase; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.MciNclgClient; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000002_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000002_O; +import io.shinhanlife.glow.communication.dto.Transfer; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.usecase.impl + * @className SolReqDetailUseCaseImpl + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + *
    + * 
    + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SolReqDetailUseCaseImpl implements SolReqDetailUseCase { + + private final MciNclgClient mci; + private final SolReqDetailConverter converter; + + @Value("${sol.req-detail.mock-enabled:false}") + private boolean mockEnabled; + + @Override + public Object execute(SolReqDetailRequest req) { + log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "solReqDetail", req); + if (req == null || req.getSrId() == null || req.getSrId().isBlank()) { + return Map.of("status", "ERROR", "message", "srId는 필수입니다."); + } + + if (mockEnabled) { + return createLocalSampleResponse(req.getSrId()); + } + + try { + SOLG00000002_I mciRequest = converter.toLegacyRequest(req); + Transfer mciResponse = mci.callTo( + "SOLG00000002", "SOLG00000002", mciRequest, SOLG00000002_O.class); + + if (mciResponse == null || mciResponse.getBody() == null) { + return Map.of("status", "NOT_FOUND", "message", "의뢰서 상세 정보를 찾을 수 없습니다."); + } + return converter.toResponse(mciResponse.getBody()); + } catch (Exception e) { + log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e); + return Map.of( + "status", "ERROR", + "message", e.getMessage() != null ? e.getMessage() : "Unknown error"); + } + } + + private Object createLocalSampleResponse(String srId) { + SolReqDetailResponse response = new SolReqDetailResponse(); + if ("SR-2026-001".equalsIgnoreCase(srId)) { + response.setSrId("SR-2026-001"); + response.setSrName("AX HUB 메인 화면 UI 개편"); + response.setProcess("진행중"); + response.setDevStage("개발(단위테스트)"); + response.setAppName("AX HUB"); + response.setRequester("신한준"); + response.setRequestDate("2026-07-01"); + response.setDueDate("2026-08-31"); + response.setDescription("AX HUB 메인 화면의 사용성과 접근성을 개선하는 UI 개편 의뢰입니다."); + return response; + } + if ("SR-2026-002".equalsIgnoreCase(srId)) { + response.setSrId("SR-2026-002"); + response.setSrName("SOL 연동 모듈 추가 개발"); + response.setProcess("진행중"); + response.setDevStage("분석/설계"); + response.setAppName("MCP Gateway"); + response.setRequester("고석민"); + response.setRequestDate("2026-07-15"); + response.setDueDate("2026-09-30"); + response.setDescription("SOL 의뢰서 조회 기능을 MCP 도구로 제공하기 위한 연동 모듈 개발 의뢰입니다."); + return response; + } + return Map.of( + "status", "NOT_FOUND", + "message", "의뢰서를 찾을 수 없습니다.", + "srId", srId); + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqListUseCaseImpl.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqListUseCaseImpl.java new file mode 100644 index 00000000..35ea868e --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqListUseCaseImpl.java @@ -0,0 +1,80 @@ +package io.shinhanlife.dap.mcc.biz.sol.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListResponse; +import io.shinhanlife.dap.mcc.biz.sol.usecase.SolReqListUseCase; +import io.shinhanlife.glow.communication.dto.Transfer; +import org.springframework.stereotype.Service; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import java.util.Map; +import java.util.List; +import java.util.ArrayList; +import io.shinhanlife.dap.mcc.biz.sol.converter.SolReqListConverter; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_O; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.MciNclgClient; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListResponse.SolReqListItem; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.usecase.impl + * @className SolReqListUseCaseImpl + * @description AX HUB 시스템 처리 클래스 + * @author jade + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  jade    최초생성
    + *
    + * 
    + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class SolReqListUseCaseImpl implements SolReqListUseCase { + + private final MciNclgClient mci; + private final SolReqListConverter converter; + + @Override + public Object execute(SolReqListRequest req) { + log.info("[MCI Tool] {} 요청 수신. 파라미터: {}", "solReqList", req); + try { + SOLG00000001_I mciRequest = converter.toLegacyRequest(req); + Transfer mciResponse = mci.callTo( + "SOLG00000001", "SOLG00000001", mciRequest, SOLG00000001_O.class); + log.info("[MCI Tool] SOLG00000001 MCI call completed. Returning dummy response: {}", + mciResponse != null); + // 현업 테스트용으로 MCI 통신을 바이패스하고 하드코딩된 결과를 반환합니다. + SolReqListResponse res = new SolReqListResponse(); + List list = new ArrayList<>(); + + SolReqListItem item1 = new SolReqListItem(); + item1.setSrId("SR-2026-001"); + item1.setSrName("AX HUB 메인 화면 UI 개편"); + item1.setProcess("진행중"); + item1.setDevStage("개발(단위테스트)"); + item1.setAppName("AX HUB"); + item1.setRequester("윤희준"); + list.add(item1); + + SolReqListItem item2 = new SolReqListItem(); + item2.setSrId("SR-2026-002"); + item2.setSrName("툴 연동 모듈 추가 개발"); + item2.setProcess("진행중"); + item2.setDevStage("분석/설계"); + item2.setAppName("MCP Gateway"); + item2.setRequester("고석민"); + list.add(item2); + + res.setReqList(list); + + return res; + } catch (Exception e) { + log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e); + return Map.of("status", "ERROR", "message", e.getMessage() != null ? e.getMessage() : "Unknown error"); + } + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/insurance/InsuranceClient.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/insurance/InsuranceClient.java new file mode 100644 index 00000000..b67a95ed --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/insurance/InsuranceClient.java @@ -0,0 +1,17 @@ +package io.shinhanlife.dap.mcc.infra.itrf.http.insurance; + +import io.shinhanlife.dap.lib.integration.http.component.AxhubHttpComponent; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class InsuranceClient { + private static final String API_NAME = "insurance"; + + private final AxhubHttpComponent http; + + public O call(I request, Class responseType) { + return http.call(API_NAME, request, responseType); + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/insurance/io/InsuranceClaimProcessorHttpRequest.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/insurance/io/InsuranceClaimProcessorHttpRequest.java new file mode 100644 index 00000000..96ee6ef2 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/insurance/io/InsuranceClaimProcessorHttpRequest.java @@ -0,0 +1,19 @@ +package io.shinhanlife.dap.mcc.infra.itrf.http.insurance.io; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class InsuranceClaimProcessorHttpRequest { + @Schema(description = "보험 청구 번호", example = "CLM20230001", requiredMode = Schema.RequiredMode.REQUIRED) + private String claimNumber; + + @Schema(description = "청구 금액", example = "1500000.00", requiredMode = Schema.RequiredMode.REQUIRED) + private Double claimAmount; + + @Schema(description = "청구 일자 (YYYY-MM-DD)", example = "2023-09-15", requiredMode = Schema.RequiredMode.REQUIRED) + private String claimDate; + +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/insurance/io/InsuranceClaimProcessorHttpResponse.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/insurance/io/InsuranceClaimProcessorHttpResponse.java new file mode 100644 index 00000000..a2b05e99 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/insurance/io/InsuranceClaimProcessorHttpResponse.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.infra.itrf.http.insurance.io; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class InsuranceClaimProcessorHttpResponse { + private String resultCode; + + private String resultMessage; + @Schema(description = "청구 처리 고유 식별자", example = "CLM20230001", requiredMode = Schema.RequiredMode.REQUIRED) + private String claimId; + +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/cfp/a/MciCfpaClient.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/cfp/a/MciCfpaClient.java new file mode 100644 index 00000000..f952241d --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/cfp/a/MciCfpaClient.java @@ -0,0 +1,72 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a; + +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_O; +import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent; +import io.shinhanlife.glow.communication.dto.Transfer; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * @package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io + * @className MciCfpaClient + * @description 보장분석결과조회 MCI 호출 클라이언트 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class MciCfpaClient { + + private final AxhubMciComponent mci; + + // 인터페이스 코드 상수 + private static final String INTERFACE_CODE = "CLCNNB00001"; // CLCCFP00001 (Onnba용 코드 유지) + + // 정상 응답 코드 상수 + private static final String SUCCESS_CODE_COM = "COM00139"; + private static final String SUCCESS_CODE_CFP = "CFP00000"; + private static final String NO_DATA_CODE = "COM00150"; + + // 예외 코드 상수 + private static final String ERROR_CODE_PROCESS = "CLC00007"; + private static final String ERROR_CODE_MESSAGE = "CLC00043"; + + /** + * 보장분석결과조회 + * + * @param inDto 입력 DTO + * @return 출력 객체 + */ + public Object callCfpa0001(CLCNNB00001_I mciReq) throws Exception { + // 인터페이스 IO 객체 생성 및 매핑 + + // 인터페이스 호출 + Transfer resTransfer = mci.callTo(INTERFACE_CODE, mciReq, CLCNNB00001_O.class); + + // 응답 검증 + validateResponse(resTransfer); + + // 메시지 검증 + validateMessage(String.valueOf(resTransfer.getMessage())); + + return resTransfer.getBody(); + } + + private void validateResponse(Transfer resTransfer) { + log.info("응답 검증 로직 수행"); + } + + private void validateMessage(String message) { + log.info("메시지 검증 로직 수행: {}", message); + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/cfp/a/io/CLCNNB00001_I.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/cfp/a/io/CLCNNB00001_I.java new file mode 100644 index 00000000..7de87b33 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/cfp/a/io/CLCNNB00001_I.java @@ -0,0 +1,91 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io + * @className CLCNNB00001_I + * @description 보장분석결과조회 MCI 요청 전문 + * @author 0986406 + * @create 2026.09.01 + */ +@Data +public class CLCNNB00001_I { + + @JsonProperty("unfcPrbuIrcoAddu") + private UnfcPrbuIrcoAdduDto unfcPrbuIrcoAddu; + + @JsonProperty("dalScCd") + private String dalScCd; + + @JsonProperty("cstSucoRltyCd") + private String cstSucoRltyCd; + + @JsonProperty("csNo") + private String csNo; + + @JsonProperty("rdreNo") + private String rdreNo; + + @JsonProperty("unfcPvsCalReqYn") + private String unfcPvsCalReqYn; + + @JsonProperty("kcisPymmTnnrRequest") + private String kcisPymmTnnrRequest; + + @JsonProperty("lmovYn") + private String lmovYn; + + @JsonProperty("genPsthApvTrgtYn") + private String genPsthApvTrgtYn; + + @JsonProperty("ircoLmovEcpbTrgtYn") + private String ircoLmovEcpbTrgtYn; + + @JsonProperty("digCalYn") + private String digCalYn; + + @JsonProperty("prbuIciDigCalYn") + private String prbuIciDigCalYn; + + @JsonProperty("sucoIspaBasDto") + private SucoIspaBasDto sucoIspaBasDto; + + @Data + public static class UnfcPrbuIrcoAdduDto { + } + + @Data + public static class SucoIspaBasDto { + @JsonProperty("ccnDalTypCd") + private String ccnDalTypCd; + + @JsonProperty("nwcnptCursCd") + private String nwcnptCursCd; + + @JsonProperty("induAsctScCd") + private String induAsctScCd; + + @JsonProperty("cepeOgnzNo") + private String cepeOgnzNo; + + @JsonProperty("cepePrafNo") + private String cepePrafNo; + + @JsonProperty("clmoOgnzNo") + private String clmoOgnzNo; + + @JsonProperty("clmoPrafNo") + private String clmoPrafNo; + + @JsonProperty("sucoYmd") + private String sucoYmd; + + @JsonProperty("ispDt") + private String ispDt; + + @JsonProperty("contYmd") + private String contYmd; + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/cfp/a/io/CLCNNB00001_O.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/cfp/a/io/CLCNNB00001_O.java new file mode 100644 index 00000000..fcb0f3fc --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/cfp/a/io/CLCNNB00001_O.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io; + +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io + * @className CLCNNB00001_O + * @description 보장분석결과조회 MCI 응답 전문 + * @author 0986406 + * @create 2026.09.01 + */ +@Data +public class CLCNNB00001_O { + // 응답 전문 필드 정의 (필요에 따라 추가) + private Object resultData; +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/MciNclgClient.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/MciNclgClient.java new file mode 100644 index 00000000..6ca133b0 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/MciNclgClient.java @@ -0,0 +1,30 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g; + +import org.springframework.stereotype.Component; +import lombok.RequiredArgsConstructor; +import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent; +import io.shinhanlife.glow.communication.dto.Transfer; + +/** + * @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g + * @className MciNclgClient + * @description AX HUB 시스템 처리 클래스 + * @author jade + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  jade    최초생성
    + *
    + * 
    + */ +@Component +@RequiredArgsConstructor +public class MciNclgClient { + private final AxhubMciComponent mci; + + public Transfer callTo(String interfaceId, String dummy, Object mciReq, Class resType) throws Exception { + return mci.callTo(interfaceId, dummy, mciReq, resType); + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000001_I.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000001_I.java new file mode 100644 index 00000000..cc976137 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000001_I.java @@ -0,0 +1,24 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io; + +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io + * @className SOLG00000001_I + * @description AX HUB 시스템 처리 클래스 + * @author jade + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  jade    최초생성
    + *
    + * 
    + */ +@Data +public class SOLG00000001_I { + private String reqStatus; + private String reqPeriod; + private String reqTarget; +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000001_O.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000001_O.java new file mode 100644 index 00000000..7c1e4959 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000001_O.java @@ -0,0 +1,34 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io; + +import lombok.Data; + +import java.util.List; + +/** + * @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io + * @className SOLG00000001_O + * @description AX HUB 시스템 처리 클래스 + * @author jade + * @create 2026.07.29 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.07.29  jade    최초생성
    + *
    + * 
    + */ +@Data +public class SOLG00000001_O { + private List reqList; + + @Data + public static class SOLG00000001_O_Item { + private String srId; + private String srName; + private String process; + private String devStage; + private String appName; + private String requester; + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_I.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_I.java new file mode 100644 index 00000000..3b8b2cb1 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_I.java @@ -0,0 +1,23 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io; + +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io + * @className SOLG00000002_I + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + *
    + * 
    + */ +@Data +public class SOLG00000002_I { + + private String srId; +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_O.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_O.java new file mode 100644 index 00000000..1aa61472 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncl/g/io/SOLG00000002_O.java @@ -0,0 +1,31 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io; + +import lombok.Data; + +/** + * @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io + * @className SOLG00000002_O + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + *
    + * 
    + */ +@Data +public class SOLG00000002_O { + + private String srId; + private String srName; + private String process; + private String devStage; + private String appName; + private String requester; + private String requestDate; + private String dueDate; + private String description; +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncm/d/io/ONCMD0030_O.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncm/d/io/ONCMD0030_O.java new file mode 100644 index 00000000..82bf8f95 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncm/d/io/ONCMD0030_O.java @@ -0,0 +1,68 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.ncm.d.io; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import io.shinhanlife.glow.communication.annotation.GlowTrgmField; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * ONCMD0030_O 매핑 DTO + */ +@NoArgsConstructor +@Getter +@Setter +public class ONCMD0030_O { + + @GlowTrgmField(order = 1, description = "고객스마트정보조회OutDto", type = "gm") + private List cstSmartIfinOutDto; + + @GlowTrgmField(order = 1, description = "고객스마트정보조회OutDto2", type = "gm") + private List cstSmartIfinOutDto2; + + @JsonIgnoreProperties(ignoreUnknown = true) + @NoArgsConstructor + @Getter + @Setter + public static class CstSmartIfinOutDto { + + @GlowTrgmField(order = 1, length = 1, description = "동의여부") + private String agrYn; + + @GlowTrgmField(order = 2, length = 12, description = "고객번호") + private String csNo; + + @GlowTrgmField(order = 3, length = 50, description = "주민등록번호") + private String rdreNo; + + @GlowTrgmField(order = 4, length = 20, description = "등록일시") + private String rgiDt; + + } + + + @JsonIgnoreProperties(ignoreUnknown = true) + @NoArgsConstructor + @Getter + @Setter + public static class CstSmartIfinOutDto2 { + + @GlowTrgmField(order = 1, length = 1, description = "동의여부") + private String agrYnaa; + + @GlowTrgmField(order = 2, length = 12, description = "고객번호") + private String csNoaa; + + @GlowTrgmField(order = 3, length = 50, description = "주민등록번호") + private String rdreNoaa; + + @GlowTrgmField(order = 4, length = 20, description = "등록일시") + private String rgiDtaa; + + } + +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/oth/DapWasOthApplication.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/oth/DapWasOthApplication.java new file mode 100644 index 00000000..4b831fb1 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/oth/DapWasOthApplication.java @@ -0,0 +1,33 @@ +package io.shinhanlife.dap.mcc.oth; + + +/** + * @package io.shinhanlife.dap.mcc.oth + * @className DapWasOthApplication + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Import; +import io.shinhanlife.dap.lib.mcp.ToolMcpServerConfiguration; + +@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) +@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) +@EnableCaching +@Import(ToolMcpServerConfiguration.class) +public class DapWasOthApplication { + public static void main(String[] args) { + SpringApplication.run(DapWasOthApplication.class, args); + } +} diff --git a/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/presentation/DtoExcelDownloadController.java b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/presentation/DtoExcelDownloadController.java new file mode 100644 index 00000000..4ffe7657 --- /dev/null +++ b/dap-was-oth/src/main/java/io/shinhanlife/dap/mcc/presentation/DtoExcelDownloadController.java @@ -0,0 +1,424 @@ +package io.shinhanlife.dap.mcc.presentation; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.regex.Pattern; + +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.XSSFColor; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.type.filter.RegexPatternTypeFilter; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +/** + * MCI DTO 클래스를 조회하고 인터페이스 설계서 형식의 엑셀 파일을 생성한다. + * 외부 템플릿 파일에 의존하지 않고 Apache POI로 양식과 데이터를 모두 만든다. + */ +@RestController +public class DtoExcelDownloadController { + + private static final int FIRST_FIELD_ROW = 12; + private static final int TEMPLATE_LAST_ROW = 35; + private static final int COLUMN_COUNT = 20; + private static final MediaType XLSX_MEDIA_TYPE = MediaType.parseMediaType( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + private static final String MCI_BASE_PACKAGE = "io.shinhanlife.dap.mcc.infra.itrf.mci"; + private static final String INVALID_DTO_MESSAGE = + "dto의 내용이 엑셀파일 양식에 맞지않습니다 파일을 확인해주세요"; + + private final Map dtoClasses; + + public DtoExcelDownloadController() { + this.dtoClasses = scanDtoClasses(); + } + + @GetMapping("/dto-download/options") + public List options() { + // 클래스패스에서 자동 검색된 DTO 목록을 화면에 제공한다. + return List.copyOf(dtoClasses.keySet()); + } + + @GetMapping("/dto-download/{dtoName}") + public ResponseEntity download(@PathVariable String dtoName) throws IOException { + // 스캔되지 않은 이름을 받아 임의 클래스를 조회하지 못하도록 제한한다. + String className = dtoClasses.get(dtoName); + if (className == null) { + return ResponseEntity.notFound().build(); + } + byte[] workbook = createWorkbook(dtoName, className); + String fileName = dtoName + ".xlsx"; + return ResponseEntity.ok() + .contentType(XLSX_MEDIA_TYPE) + .contentLength(workbook.length) + .header(HttpHeaders.CONTENT_DISPOSITION, + ContentDisposition.attachment().filename(fileName).build().toString()) + .body(workbook); + } + + @ExceptionHandler(DtoFormatException.class) + public ResponseEntity handleInvalidDto() { + return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY) + .contentType(MediaType.parseMediaType("text/plain;charset=UTF-8")) + .body(INVALID_DTO_MESSAGE); + } + + private byte[] createWorkbook(String dtoName, String className) throws IOException { + // 요청마다 새 워크북을 생성하므로 여러 사용자의 다운로드가 서로 영향을 주지 않는다. + try (XSSFWorkbook workbook = createTemplateWorkbook(); + ByteArrayOutputStream output = new ByteArrayOutputStream()) { + Sheet sheet = workbook.getSheetAt(0); + setText(sheet, 2, 2, dtoName); + setText(sheet, 3, 2, dtoName); + + List fields; + try { + fields = describeFields(Class.forName(className)); + } catch (ReflectiveOperationException error) { + throw new IOException("DTO class could not be inspected: " + className, error); + } + writeFields(sheet, fields); + workbook.write(output); + return output.toByteArray(); + } + } + + private XSSFWorkbook createTemplateWorkbook() { + // 기준 문서의 시트명, 열 너비, 병합, 색상과 테두리를 코드로 재현한다. + XSSFWorkbook workbook = new XSSFWorkbook(); + Sheet sheet = workbook.createSheet("대내"); + sheet.setDisplayGridlines(false); + sheet.createFreezePane(0, 12); + sheet.getPrintSetup().setLandscape(true); + sheet.setRepeatingRows(new CellRangeAddress(11, 11, -1, -1)); + + double[] widths = {4.44, 8, 23.22, 23.22, 10, 14, 18, 11, 9, 7.44, + 8, 9.55, 9, 10.55, 11.44, 9, 13, 10.55, 14, 30}; + for (int column = 0; column < widths.length; column++) { + sheet.setColumnWidth(column, (int) (widths[column] * 256)); + } + + CellStyle titleStyle = style(workbook, "000000", "FFFFFF", true, (short) 14, + HorizontalAlignment.CENTER, false); + CellStyle sectionStyle = style(workbook, "F2F2F2", "000000", true, (short) 10, + HorizontalAlignment.CENTER, false); + CellStyle labelStyle = borderedStyle(workbook, "F2F2F2", true, HorizontalAlignment.CENTER); + CellStyle inputStyle = borderedStyle(workbook, "C6D9F1", false, HorizontalAlignment.LEFT); + CellStyle requiredStyle = borderedStyle(workbook, "C6D9F1", false, HorizontalAlignment.LEFT); + CellStyle autoStyle = borderedStyle(workbook, "F2DCDB", false, HorizontalAlignment.LEFT); + CellStyle userStyle = borderedStyle(workbook, "FFFFFF", false, HorizontalAlignment.LEFT); + CellStyle headerStyle = borderedStyle(workbook, "D9D9D9", true, HorizontalAlignment.CENTER); + headerStyle.setWrapText(true); + CellStyle whiteDataStyle = borderedStyle(workbook, "FFFFFF", false, HorizontalAlignment.CENTER); + CellStyle blueDataStyle = borderedStyle(workbook, "C6D9F1", false, HorizontalAlignment.CENTER); + CellStyle pinkDataStyle = borderedStyle(workbook, "F2DCDB", false, HorizontalAlignment.CENTER); + + createStyledRow(sheet, 0, 25.5f, titleStyle); + merge(sheet, "A1:T1"); + setText(sheet, 0, 0, "인터페이스 설계서(대내)"); + + createStyledRow(sheet, 1, 18f, sectionStyle); + merge(sheet, "A2:T2"); + setText(sheet, 1, 0, "기본정보"); + + String[] labels = {"코드", "한글명", "영문명", "암호화", "유형", "레코드구분자", "필드구분자"}; + for (int index = 0; index < labels.length; index++) { + int rowIndex = index + 2; + Row row = sheet.createRow(rowIndex); + cell(row, 1, labelStyle).setCellValue(labels[index]); + cell(row, 2, inputStyle); + cell(row, 3, inputStyle); + merge(sheet, "C" + (rowIndex + 1) + ":D" + (rowIndex + 1)); + } + setText(sheet, 6, 2, "json"); + sheet.getRow(7).setHeightInPoints(24); + sheet.getRow(8).setHeightInPoints(24); + + for (int rowIndex = 3; rowIndex <= 5; rowIndex++) { + Row row = sheet.getRow(rowIndex); + CellStyle legendStyle = rowIndex == 3 ? requiredStyle : rowIndex == 4 ? autoStyle : userStyle; + cell(row, 5, legendStyle); + cell(row, 6, legendStyle); + merge(sheet, "F" + (rowIndex + 1) + ":G" + (rowIndex + 1)); + } + setText(sheet, 3, 7, "필수입력"); + setText(sheet, 4, 7, "필드자동채우기(메타시스템 연동시)"); + setText(sheet, 5, 7, "사용자입력(필요시)"); + + createStyledRow(sheet, 10, 18f, sectionStyle); + merge(sheet, "A11:T11"); + setText(sheet, 10, 0, "필드정보"); + + String[] headers = {"NO", "Level", "한글명", "부모식별자(한글명)", "끝수여부", "영문명", + "부모식별자(영문명)", "데이터유형", "필드길이", "SCALE", "기본값", "정렬기준", + "채움문자", "암호화방식", "메타체크여부", "한글여부", "소수점포함여부", + "마스킹여부", "마스킹패턴코드", "비고"}; + Row header = sheet.createRow(11); + header.setHeightInPoints(30); + for (int column = 0; column < headers.length; column++) { + cell(header, column, headerStyle).setCellValue(headers[column]); + } + + for (int rowIndex = FIRST_FIELD_ROW; rowIndex <= TEMPLATE_LAST_ROW; rowIndex++) { + Row row = sheet.createRow(rowIndex); + row.setHeightInPoints(15.75f); + for (int column = 0; column < COLUMN_COUNT; column++) { + CellStyle dataStyle; + if (column == 0 || column == 4 || (column >= 14 && column <= 16) || column == 19) { + dataStyle = whiteDataStyle; + } else if (column >= 1 && column <= 3) { + dataStyle = blueDataStyle; + } else { + dataStyle = pinkDataStyle; + } + cell(row, column, dataStyle); + } + } + return workbook; + } + + private CellStyle style(XSSFWorkbook workbook, String fillColor, String fontColor, + boolean bold, short fontSize, HorizontalAlignment alignment, + boolean bordered) { + CellStyle style = workbook.createCellStyle(); + style.setAlignment(alignment); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setFillForegroundColor(new XSSFColor(java.awt.Color.decode("#" + fillColor), null)); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + Font font = workbook.createFont(); + font.setFontName("맑은 고딕"); + font.setFontHeightInPoints(fontSize); + font.setBold(bold); + font.setColor("FFFFFF".equals(fontColor) ? IndexedColors.WHITE.getIndex() : IndexedColors.BLACK.getIndex()); + style.setFont(font); + if (bordered) setBorders(style); + return style; + } + + private CellStyle borderedStyle(XSSFWorkbook workbook, String fillColor, + boolean bold, HorizontalAlignment alignment) { + return style(workbook, fillColor, "000000", bold, (short) 9, alignment, true); + } + + private void setBorders(CellStyle style) { + style.setBorderTop(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + } + + private void createStyledRow(Sheet sheet, int rowIndex, float height, CellStyle style) { + Row row = sheet.createRow(rowIndex); + row.setHeightInPoints(height); + for (int column = 0; column < COLUMN_COUNT; column++) cell(row, column, style); + } + + private Cell cell(Row row, int column, CellStyle style) { + Cell cell = row.createCell(column); + cell.setCellStyle(style); + return cell; + } + + private void merge(Sheet sheet, String range) { + sheet.addMergedRegion(CellRangeAddress.valueOf(range)); + } + + private void writeFields(Sheet sheet, List fields) { + // 기본 24행을 유지하고 필드가 더 많으면 마지막 행의 서식을 복제해 확장한다. + int requiredRows = Math.max(fields.size(), TEMPLATE_LAST_ROW - FIRST_FIELD_ROW + 1); + for (int offset = 0; offset < requiredRows; offset++) { + int rowIndex = FIRST_FIELD_ROW + offset; + Row row = sheet.getRow(rowIndex); + if (row == null) { + row = cloneTemplateRow(sheet, rowIndex); + } + clearRowValues(row); + setNumber(row, 0, offset + 1); + if (offset < fields.size()) { + FieldRow field = fields.get(offset); + setNumber(row, 1, field.level()); + setText(row, 2, field.description()); + setText(row, 3, field.parentDescription()); + setText(row, 5, field.name()); + setText(row, 6, field.parentName()); + setText(row, 7, field.dataType()); + if (field.length() > 0) { + setNumber(row, 8, field.length()); + } + } + } + } + + private Row cloneTemplateRow(Sheet sheet, int rowIndex) { + Row source = sheet.getRow(TEMPLATE_LAST_ROW); + Row target = sheet.createRow(rowIndex); + target.setHeight(source.getHeight()); + for (int column = 0; column < COLUMN_COUNT; column++) { + Cell sourceCell = source.getCell(column, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); + Cell targetCell = target.createCell(column); + CellStyle style = sourceCell.getCellStyle(); + targetCell.setCellStyle(style); + } + return target; + } + + private void clearRowValues(Row row) { + for (int column = 0; column < COLUMN_COUNT; column++) { + Cell cell = row.getCell(column, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); + cell.setBlank(); + } + } + + private List describeFields(Class rootClass) { + List output = new ArrayList<>(); + appendFields(rootClass, 1, "", "", output); + return output; + } + + private void appendFields(Class type, int level, String parentName, + String parentDescription, List output) { + // 중첩 DTO와 List 요소 타입을 재귀적으로 펼쳐 Level 및 부모 식별자를 계산한다. + List fields = new ArrayList<>(List.of(type.getDeclaredFields())); + fields.removeIf(field -> field.isSynthetic()); + fields.sort(Comparator.comparingInt(this::fieldOrder)); + for (Field field : fields) { + Annotation metadata = telegramMetadata(field); + // 한글명, 순서, 길이를 알 수 없는 DTO는 설계서 양식으로 변환할 수 없다. + if (metadata == null) { + throw new DtoFormatException(); + } + String description = annotationString(metadata, "description", field.getName()); + int length = annotationInt(metadata, "length", 0); + Class nestedType = nestedType(field); + String dataType = annotationString(metadata, "type", simpleDataType(field)); + output.add(new FieldRow(level, description, parentDescription, field.getName(), + parentName, dataType, length)); + if (nestedType != null && nestedType != type) { + appendFields(nestedType, level + 1, field.getName(), description, output); + } + } + } + + private int fieldOrder(Field field) { + return annotationInt(telegramMetadata(field), "order", Integer.MAX_VALUE); + } + + private Annotation telegramMetadata(Field field) { + for (Annotation annotation : field.getDeclaredAnnotations()) { + if (annotation.annotationType().getSimpleName().equals("GlowTrgmField")) { + return annotation; + } + } + return null; + } + + private String annotationString(Annotation annotation, String methodName, String fallback) { + Object value = annotationValue(annotation, methodName); + return value instanceof String text && !text.isBlank() ? text : fallback; + } + + private int annotationInt(Annotation annotation, String methodName, int fallback) { + Object value = annotationValue(annotation, methodName); + return value instanceof Number number ? number.intValue() : fallback; + } + + private Object annotationValue(Annotation annotation, String methodName) { + if (annotation == null) { + return null; + } + try { + Method method = annotation.annotationType().getMethod(methodName); + return method.invoke(annotation); + } catch (ReflectiveOperationException ignored) { + return null; + } + } + + private Class nestedType(Field field) { + Class type = field.getType(); + if (List.class.isAssignableFrom(type) && field.getGenericType() instanceof ParameterizedType generic) { + Type argument = generic.getActualTypeArguments()[0]; + if (argument instanceof Class itemType && isDtoType(itemType)) { + return itemType; + } + } + return isDtoType(type) ? type : null; + } + + private boolean isDtoType(Class type) { + return !type.isPrimitive() + && !type.getName().startsWith("java.") + && !type.isEnum(); + } + + private String simpleDataType(Field field) { + if (List.class.isAssignableFrom(field.getType())) { + return "List"; + } + return field.getType().getSimpleName(); + } + + private void setText(Sheet sheet, int row, int column, String value) { + setText(sheet.getRow(row), column, value); + } + + private void setText(Row row, int column, String value) { + row.getCell(column, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellValue(value == null ? "" : value); + } + + private void setNumber(Row row, int column, int value) { + row.getCell(column, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellValue(value); + } + + private Map scanDtoClasses() { + // itrf.mci 하위의 모든 io 패키지를 검색하므로 신규 DTO 추가 시 하드코딩이 필요 없다. + ClassPathScanningCandidateComponentProvider scanner = + new ClassPathScanningCandidateComponentProvider(false); + scanner.addIncludeFilter(new RegexPatternTypeFilter( + Pattern.compile(".*\\.itrf\\.mci\\..*\\.io\\.[^.]+$"))); + + Map classes = new TreeMap<>(); + scanner.findCandidateComponents(MCI_BASE_PACKAGE).forEach(candidate -> { + String className = candidate.getBeanClassName(); + if (className == null || className.contains("$")) { + return; + } + String simpleName = className.substring(className.lastIndexOf('.') + 1); + String previous = classes.putIfAbsent(simpleName, className); + if (previous != null) { + throw new IllegalStateException("Duplicate DTO class name: " + simpleName); + } + }); + return Map.copyOf(classes); + } + + private record FieldRow(int level, String description, String parentDescription, + String name, String parentName, String dataType, int length) { + } + + private static final class DtoFormatException extends RuntimeException { + } +} diff --git a/dap-was-oth/src/main/resources/application-dev.yml b/dap-was-oth/src/main/resources/application-dev.yml new file mode 100644 index 00000000..658693ab --- /dev/null +++ b/dap-was-oth/src/main/resources/application-dev.yml @@ -0,0 +1,15 @@ +# OCI ?대씪?곕뱶 ?섍꼍 ?꾩슜 ?ㅼ젙 +server: + port: ${PORT:8084} + +axhub: + gateway: + url: https://axhubmcp.devjun.net + +spring: + config: + activate: + on-profile: dev + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-dev.yml diff --git a/dap-was-oth/src/main/resources/application-local.yml b/dap-was-oth/src/main/resources/application-local.yml new file mode 100644 index 00000000..20256677 --- /dev/null +++ b/dap-was-oth/src/main/resources/application-local.yml @@ -0,0 +1,32 @@ +# Local 환경 전용 설정 (H2 메모리 DB 등) +spring: + config: + activate: + on-profile: local + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-local.yml + datasource: + url: jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1; + driverClassName: com.p6spy.engine.spy.P6SpyDriver + username: sa + password: password + h2: + console: + enabled: true + +mcp: + security: + tenant-domains: + mcp-client-1: CUSTOMER,COMMON + mcp-client-2: ALL + +axhub: + gateway: + url: http://localhost:8081 + tool: + url: ${AXHUB_TOOL_URL:http://localhost:${server.port}} + +sol: + req-detail: + mock-enabled: true diff --git a/dap-was-oth/src/main/resources/application-prod.yml b/dap-was-oth/src/main/resources/application-prod.yml new file mode 100644 index 00000000..b5f08f21 --- /dev/null +++ b/dap-was-oth/src/main/resources/application-prod.yml @@ -0,0 +1,16 @@ +server: + port: ${PORT:8084} + +spring: + config: + activate: + on-profile: prod + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-prod.yml + +axhub: + gateway: + url: ${AXHUB_GATEWAY_URL} + tool: + url: ${AXHUB_TOOL_URL} diff --git a/dap-was-oth/src/main/resources/application-test.yml b/dap-was-oth/src/main/resources/application-test.yml new file mode 100644 index 00000000..ab1c6908 --- /dev/null +++ b/dap-was-oth/src/main/resources/application-test.yml @@ -0,0 +1,16 @@ +server: + port: ${PORT:8084} + +spring: + config: + activate: + on-profile: test + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-test.yml + +axhub: + gateway: + url: ${AXHUB_GATEWAY_URL} + tool: + url: ${AXHUB_TOOL_URL} diff --git a/dap-was-oth/src/main/resources/application.yml b/dap-was-oth/src/main/resources/application.yml new file mode 100644 index 00000000..f81a5d6f --- /dev/null +++ b/dap-was-oth/src/main/resources/application.yml @@ -0,0 +1,19 @@ +server: + port: 8084 +spring: + application: + name: dap-was-oth + profiles: + active: local +logging: + level: + org.apache.kafka: ERROR +mcp: + namespace: "" + manifest: + bundle-id: was-oth + # Set the AA-assigned prefix before MCP pull activation (for example: oth.). + name-prefix: "" + security: + tenant-domains: + TESTER-DEV: ALL \ No newline at end of file diff --git a/dap-was-oth/src/main/resources/logback-spring.xml b/dap-was-oth/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..ee1a3e15 --- /dev/null +++ b/dap-was-oth/src/main/resources/logback-spring.xml @@ -0,0 +1,39 @@ + + + + + + + + + + ${LOG_PATTERN} + + + + + + + + + /swlog/dap-was-oth/A01/${HOSTNAME}_A01.log + + + /swlog/dap-was-oth/A01/${HOSTNAME}_A01_%d{yyyyMMdd}.log + + 30 + + + ${LOG_PATTERN} + + + + + + + + + + + + diff --git a/dap-was-oth/src/main/resources/mock-responses/ins_insurance_processor.json b/dap-was-oth/src/main/resources/mock-responses/ins_insurance_processor.json new file mode 100644 index 00000000..d856cc5b --- /dev/null +++ b/dap-was-oth/src/main/resources/mock-responses/ins_insurance_processor.json @@ -0,0 +1,4 @@ +{ + "resultCode" : "SUCCESS", + "claimId" : "CLM20230001" +} diff --git a/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/ins/usecase/InsuranceClaimProcessorUseCaseTest.java b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/ins/usecase/InsuranceClaimProcessorUseCaseTest.java new file mode 100644 index 00000000..7508af8d --- /dev/null +++ b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/ins/usecase/InsuranceClaimProcessorUseCaseTest.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.biz.ins.usecase; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.shinhanlife.dap.mcc.biz.ins.dto.InsuranceClaimProcessorRequest; +import io.shinhanlife.dap.mcc.biz.ins.dto.InsuranceClaimProcessorResponse; +import org.junit.jupiter.api.Test; + +class InsuranceClaimProcessorUseCaseTest { + + @Test + void createsToolRequestAndResponseDtos() { + assertNotNull(new InsuranceClaimProcessorRequest()); + assertNotNull(new InsuranceClaimProcessorResponse()); + } +} diff --git a/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImplTest.java b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImplTest.java new file mode 100644 index 00000000..5fbb51df --- /dev/null +++ b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/oth/usecase/impl/Onnba3011UseCaseImplTest.java @@ -0,0 +1,46 @@ +package io.shinhanlife.dap.mcc.biz.oth.usecase.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.shinhanlife.dap.mcc.biz.oth.converter.Onnba3011Converter; +import io.shinhanlife.dap.mcc.biz.oth.dto.Onnba3011Request; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.MciCfpaClient; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +/** + * @package io.shinhanlife.dap.mcc.biz.oth.usecase.impl + * @className OnnbaMciToolUseCaseImplTest + * @description ONNBA MCI tool use case test + * @author 0986406 + * @create 2026.07.27 + *
    + * ---------- revision history ----------
    + * date       author    description
    + * ---------- --------- ---------------------------
    + * 2026.07.27 0986406    initial creation
    + * 
    + */ +class Onnba3011UseCaseImplTest { + + @Test + void convertsToolRequestBeforeCallingMciClient() throws Exception { + MciCfpaClient mciCfpaClient = Mockito.mock(MciCfpaClient.class); + Onnba3011Converter converter = Mockito.mock(Onnba3011Converter.class); + Onnba3011UseCaseImpl useCase = new Onnba3011UseCaseImpl(mciCfpaClient, converter); + Onnba3011Request request = new Onnba3011Request(); + CLCNNB00001_I mciRequest = new CLCNNB00001_I(); + + when(converter.toMciRequest(request)).thenReturn(mciRequest); + when(mciCfpaClient.callCfpa0001(mciRequest)).thenReturn("success"); + + Object result = useCase.execute(request); + + assertEquals("success", result); + verify(converter).toMciRequest(request); + verify(mciCfpaClient).callCfpa0001(mciRequest); + } +} diff --git a/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImplTest.java b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImplTest.java new file mode 100644 index 00000000..e2b245f0 --- /dev/null +++ b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqDetailUseCaseImplTest.java @@ -0,0 +1,45 @@ +package io.shinhanlife.dap.mcc.biz.sol.usecase.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.shinhanlife.dap.mcc.biz.sol.converter.SolReqDetailConverter; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailRequest; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqDetailResponse; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.MciNclgClient; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.test.util.ReflectionTestUtils; + +/** + * @package io.shinhanlife.dap.mcc.biz.sol.usecase.impl + * @className SolReqDetailUseCaseImplTest + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + *
    + * 
    + */ +class SolReqDetailUseCaseImplTest { + + @Test + void returnsLocalSampleDetailBySrId() { + MciNclgClient mci = Mockito.mock(MciNclgClient.class); + SolReqDetailConverter converter = Mockito.mock(SolReqDetailConverter.class); + SolReqDetailUseCaseImpl useCase = new SolReqDetailUseCaseImpl(mci, converter); + ReflectionTestUtils.setField(useCase, "mockEnabled", true); + + SolReqDetailRequest request = new SolReqDetailRequest(); + request.setSrId("SR-2026-001"); + + SolReqDetailResponse response = (SolReqDetailResponse) useCase.execute(request); + + assertThat(response.getSrId()).isEqualTo("SR-2026-001"); + assertThat(response.getSrName()).isEqualTo("AX HUB 메인 화면 UI 개편"); + Mockito.verifyNoInteractions(mci); + } +} diff --git a/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqListUseCaseImplTest.java b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqListUseCaseImplTest.java new file mode 100644 index 00000000..26ef60f7 --- /dev/null +++ b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/biz/sol/usecase/impl/SolReqListUseCaseImplTest.java @@ -0,0 +1,39 @@ +package io.shinhanlife.dap.mcc.biz.sol.usecase.impl; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.shinhanlife.dap.mcc.biz.sol.converter.SolReqListConverter; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListRequest; +import io.shinhanlife.dap.mcc.biz.sol.dto.SolReqListResponse; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.MciNclgClient; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncl.g.io.SOLG00000001_O; +import io.shinhanlife.glow.communication.dto.Transfer; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class SolReqListUseCaseImplTest { + + @Test + void callsMciWithConvertedRequestAndReturnsDummyResponse() throws Exception { + MciNclgClient mci = Mockito.mock(MciNclgClient.class); + SolReqListConverter converter = Mockito.mock(SolReqListConverter.class); + SolReqListUseCaseImpl useCase = new SolReqListUseCaseImpl(mci, converter); + SolReqListRequest request = new SolReqListRequest(); + SOLG00000001_I mciRequest = new SOLG00000001_I(); + + when(converter.toLegacyRequest(request)).thenReturn(mciRequest); + when(mci.callTo(eq("SOLG00000001"), eq("SOLG00000001"), eq(mciRequest), eq(SOLG00000001_O.class))) + .thenReturn(new Transfer<>()); + + SolReqListResponse response = (SolReqListResponse) useCase.execute(request); + + verify(converter).toLegacyRequest(request); + verify(mci).callTo("SOLG00000001", "SOLG00000001", mciRequest, SOLG00000001_O.class); + assertThat(response.getReqList()).extracting(SolReqListResponse.SolReqListItem::getSrId) + .containsExactly("SR-2026-001", "SR-2026-002"); + } +} diff --git a/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/infra/itrf/mci/cfp/a/converter/Onnba3011MciRequestConverterTest.java b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/infra/itrf/mci/cfp/a/converter/Onnba3011MciRequestConverterTest.java new file mode 100644 index 00000000..d7e3a564 --- /dev/null +++ b/dap-was-oth/src/test/java/io/shinhanlife/dap/mcc/infra/itrf/mci/cfp/a/converter/Onnba3011MciRequestConverterTest.java @@ -0,0 +1,55 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.converter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.shinhanlife.dap.mcc.biz.oth.converter.Onnba3011Converter; +import io.shinhanlife.dap.mcc.biz.oth.dto.Onnba3011Request; +import io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.io.CLCNNB00001_I; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; + +/** + * @package io.shinhanlife.dap.mcc.infra.itrf.mci.cfp.a.converter + * @className Onnba3011MciRequestConverterTest + * @description ONNBA 3011 request converter test + * @author 0986406 + * @create 2026.07.27 + *
    + * ---------- revision history ----------
    + * date       author    description
    + * ---------- --------- ---------------------------
    + * 2026.07.27 0986406    initial creation
    + * 
    + */ +class Onnba3011MciRequestConverterTest { + + private final Onnba3011Converter converter = Mappers.getMapper(Onnba3011Converter.class); + + @Test + void mapsRootAndNestedRequestFieldsToMciPayload() { + Onnba3011Request source = request(); + + CLCNNB00001_I result = converter.toMciRequest(source); + + assertEquals("A", result.getDalScCd()); + assertEquals("123456", result.getCsNo()); + assertNotNull(result.getUnfcPrbuIrcoAddu()); + assertNotNull(result.getSucoIspaBasDto()); + assertEquals("01", result.getSucoIspaBasDto().getCcnDalTypCd()); + assertEquals("20260727", result.getSucoIspaBasDto().getSucoYmd()); + } + + private Onnba3011Request request() { + Onnba3011Request request = new Onnba3011Request(); + request.setDalScCd("A"); + request.setCsNo("123456"); + request.setUnfcPrbuIrcoAddu(new Onnba3011Request.UnfcPrbuIrcoAdduDto()); + + Onnba3011Request.SucoIspaBasDto suco = new Onnba3011Request.SucoIspaBasDto(); + suco.setCcnDalTypCd("01"); + suco.setSucoYmd("20260727"); + request.setSucoIspaBasDto(suco); + return request; + } +} diff --git a/dap-was-sms/Dockerfile b/dap-was-sms/Dockerfile new file mode 100644 index 00000000..44631b5c --- /dev/null +++ b/dap-was-sms/Dockerfile @@ -0,0 +1,8 @@ +FROM eclipse-temurin:21-jre-alpine +WORKDIR /app +RUN apk add --no-cache tzdata +ENV TZ=Asia/Seoul +COPY dap-was-sms/build/libs/*-SNAPSHOT.jar app.jar +EXPOSE 8082 +ENTRYPOINT ["java", "-jar", "app.jar"] + diff --git a/dap-was-sms/build.gradle b/dap-was-sms/build.gradle new file mode 100644 index 00000000..561bc8e3 --- /dev/null +++ b/dap-was-sms/build.gradle @@ -0,0 +1,9 @@ +plugins { + // SMS Tool Pod를 독립 실행 가능한 Spring Boot JAR로 생성합니다. + id 'org.springframework.boot' +} + +dependencies { + // MCP Server, Tool 공통 처리, MCI/EAI 연동 기반은 dap-was-lib에서 상속합니다. + implementation project(':dap-was-lib') +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/ClaimSearchConverter.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/ClaimSearchConverter.java new file mode 100644 index 00000000..ed5d79c8 --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/ClaimSearchConverter.java @@ -0,0 +1,14 @@ +package io.shinhanlife.dap.mcc.biz.cmm.converter; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchResponse; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io.CLCNNB00001_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io.CLCNNB00001_O; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public interface ClaimSearchConverter { + CLCNNB00001_I toLegacyRequest(ClaimSearchRequest request); + ClaimSearchRequest toRequest(CLCNNB00001_I mciRequest); + ClaimSearchResponse toResponse(CLCNNB00001_O mciRes); +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MemoListRetrieverConverter.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MemoListRetrieverConverter.java new file mode 100644 index 00000000..ba759323 --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/converter/MemoListRetrieverConverter.java @@ -0,0 +1,17 @@ +package io.shinhanlife.dap.mcc.biz.cmm.converter; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverResponse; +import io.shinhanlife.dap.mcc.infra.itrf.http.memo.io.MemoListRetrieverHttpRequest; +import io.shinhanlife.dap.mcc.infra.itrf.http.memo.io.MemoListRetrieverHttpResponse; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface MemoListRetrieverConverter { + // Field names differ? Add mappings like this before the method. + // @Mapping(source = "sourceField", target = "targetField") + MemoListRetrieverHttpRequest toHttpRequest(MemoListRetrieverRequest request); + MemoListRetrieverResponse toResponse(MemoListRetrieverHttpResponse httpResponse); +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequest.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequest.java new file mode 100644 index 00000000..644d3146 --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchRequest.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ClaimSearchRequest { + @Schema(description = "보험금 청구번호", example = "CLM202608100001", requiredMode = Schema.RequiredMode.REQUIRED) + private String claimNo; + + @Schema(description = "보험 계약번호", example = "10023456789") + private String contractNo; + +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchResponse.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchResponse.java new file mode 100644 index 00000000..301d3f8a --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/ClaimSearchResponse.java @@ -0,0 +1,22 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ClaimSearchResponse { + private String resultCode; + + private String resultMessage; + @Schema(description = "청구 처리 상태 코드", example = "RECEIVED", requiredMode = Schema.RequiredMode.REQUIRED) + private String status; + + @Schema(description = "청구 처리 상태명", example = "접수", requiredMode = Schema.RequiredMode.REQUIRED) + private String statusLabel; + + @Schema(description = "승인 금액", example = "150000") + private Long approvedAmount; + +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MemoListRetrieverRequest.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MemoListRetrieverRequest.java new file mode 100644 index 00000000..9cc7c4d3 --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MemoListRetrieverRequest.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MemoListRetrieverRequest { + @Schema(description = "조회할 의뢰서 상태", example = "OPEN", requiredMode = Schema.RequiredMode.REQUIRED) + private String memoStatus; + + @Schema(description = "검색 키워드", example = "프로젝트", requiredMode = Schema.RequiredMode.REQUIRED) + private String searchKeyword; + +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MemoListRetrieverResponse.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MemoListRetrieverResponse.java new file mode 100644 index 00000000..79f704d8 --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/dto/MemoListRetrieverResponse.java @@ -0,0 +1,13 @@ +package io.shinhanlife.dap.mcc.biz.cmm.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MemoListRetrieverResponse { + private String resultCode; + + private String resultMessage; +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCase.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCase.java new file mode 100644 index 00000000..e9e02557 --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCase.java @@ -0,0 +1,29 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase; + +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; +import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchResponse; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase + * @className ClaimSearchUseCase + * @description AX HUB 시스템 처리 클래스 + * @author jade + * @create 2026.08.10 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.08.10  jade    최초생성
    + *
    + * 
    + */ +public interface ClaimSearchUseCase { + + @McpTool(name = "cmm_claim_search", title = "청구번호 또는 계약번호로 보험금 청구 상태를 조회한다.", description = "청구번호 또는 계약번호로 보험금 청구 상태를 조회한다.") + @ToolHint(register = false, categoryKey = "cmm", mappingId = "CLCNNB00001", + inputSchemaResource = "classpath:tool-schemas/cmm/claim-search-resource-input-schema.json", + outputSchemaResource = "classpath:tool-schemas/cmm/claim-search-resource-output-schema.json") + ClaimSearchResponse execute(ClaimSearchRequest req); +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCase.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCase.java new file mode 100644 index 00000000..13cb3645 --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCase.java @@ -0,0 +1,27 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase; + +import org.springaicommunity.mcp.annotation.McpTool; +import io.shinhanlife.dap.lib.annotation.ToolHint; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverResponse; + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase + * @className MemoListRetrieverUseCase + * @description AX HUB ?쒖뒪??泥섎━ ?대옒?? + * @author Admin + * @create 2026.08.11 + *
    + * ---------- 媛쒖젙?대젰 ----------
    + * ?섏젙??     ?섏젙??   ?섏젙?댁슜
    + * ---------- -------- ---------------------------
    + * 2026.08.11  Admin    理쒖큹?앹꽦
    + *
    + * 
    + */ +public interface MemoListRetrieverUseCase { + + @McpTool(name = "cmm_memo_retriever", title = "의뢰서 목록 조회", description = "의뢰서 목록을 조회하여 의뢰 정보를 반환합니다.") + @ToolHint(register = false, categoryKey = "cmm", mappingId = "MEMO0000001") + MemoListRetrieverResponse execute(MemoListRetrieverRequest req); +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/ClaimSearchUseCaseImpl.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/ClaimSearchUseCaseImpl.java new file mode 100644 index 00000000..b6dfd97f --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/ClaimSearchUseCaseImpl.java @@ -0,0 +1,68 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchResponse; +import io.shinhanlife.dap.mcc.biz.cmm.usecase.ClaimSearchUseCase; +import io.shinhanlife.glow.communication.dto.Transfer; +import org.springframework.stereotype.Service; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import io.shinhanlife.dap.mcc.biz.cmm.converter.ClaimSearchConverter; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io.CLCNNB00001_I; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io.CLCNNB00001_O; +import io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.MciNclaClient; + + +/** + * @package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl + * @className ClaimSearchUseCaseImpl + * @description AX HUB 시스템 처리 클래스 + * @author jade + * @create 2026.08.10 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.08.10  jade    최초생성
    + *
    + * 
    + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ClaimSearchUseCaseImpl implements ClaimSearchUseCase { + + private final MciNclaClient mci; + private final ClaimSearchConverter converter; + + @Override + public ClaimSearchResponse execute(ClaimSearchRequest req) { + log.info("[MCI Tool] {} 요청 수신.", "cmm_claim_search"); + try { + // MapStruct를 이용한 자동 매핑 (AI DTO -> MCI DTO) + CLCNNB00001_I mciReq = converter.toLegacyRequest(req); + + Transfer resTransfer = mci.callTo( + "CLCNNB00001", + null, + mciReq, + CLCNNB00001_O.class + ); + ClaimSearchResponse response = new ClaimSearchResponse(); + if (resTransfer.getBody() != null) { + response = converter.toResponse(resTransfer.getBody()); + } + response.setResultCode("SUCCESS"); + response.setResultMessage(resTransfer.getBody() != null + ? "MCI call completed." + : "MCI call completed without a response body."); + return response; + } catch (Exception e) { + log.error("[MCI Tool] 연동 중 오류 발생: {}", e.getMessage(), e); + ClaimSearchResponse response = new ClaimSearchResponse(); + response.setResultCode("ERROR"); + response.setResultMessage(e.getMessage() != null ? e.getMessage() : "Unknown error"); + return response; + } + } +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MemoListRetrieverUseCaseImpl.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MemoListRetrieverUseCaseImpl.java new file mode 100644 index 00000000..474832e5 --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/impl/MemoListRetrieverUseCaseImpl.java @@ -0,0 +1,30 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase.impl; + +import io.shinhanlife.dap.mcc.biz.cmm.converter.MemoListRetrieverConverter; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverResponse; +import io.shinhanlife.dap.mcc.infra.itrf.http.memo.MemoClient; +import io.shinhanlife.dap.mcc.infra.itrf.http.memo.io.MemoListRetrieverHttpRequest; +import io.shinhanlife.dap.mcc.infra.itrf.http.memo.io.MemoListRetrieverHttpResponse; +import io.shinhanlife.dap.mcc.biz.cmm.usecase.MemoListRetrieverUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class MemoListRetrieverUseCaseImpl implements MemoListRetrieverUseCase { + + private final MemoListRetrieverConverter converter; + private final MemoClient memoClient; + + @Override + public MemoListRetrieverResponse execute(MemoListRetrieverRequest req) { + MemoListRetrieverHttpRequest httpRequest = converter.toHttpRequest(req); + MemoListRetrieverHttpResponse httpResponse = memoClient.call(httpRequest, MemoListRetrieverHttpResponse.class); + + MemoListRetrieverResponse response = converter.toResponse(httpResponse); + response.setResultCode("SUCCESS"); + response.setResultMessage("HTTP API call completed."); + return response; + } +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/MemoClient.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/MemoClient.java new file mode 100644 index 00000000..53026602 --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/MemoClient.java @@ -0,0 +1,17 @@ +package io.shinhanlife.dap.mcc.infra.itrf.http.memo; + +import io.shinhanlife.dap.lib.integration.http.component.AxhubHttpComponent; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class MemoClient { + private static final String API_NAME = "memo"; + + private final AxhubHttpComponent http; + + public O call(I request, Class responseType) { + return http.call(API_NAME, request, responseType); + } +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/io/MemoListRetrieverHttpRequest.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/io/MemoListRetrieverHttpRequest.java new file mode 100644 index 00000000..0a90902c --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/io/MemoListRetrieverHttpRequest.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.infra.itrf.http.memo.io; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MemoListRetrieverHttpRequest { + @Schema(description = "조회할 의뢰서 상태", example = "OPEN", requiredMode = Schema.RequiredMode.REQUIRED) + private String memoStatus; + + @Schema(description = "검색 키워드", example = "프로젝트", requiredMode = Schema.RequiredMode.REQUIRED) + private String searchKeyword; + +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/io/MemoListRetrieverHttpResponse.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/io/MemoListRetrieverHttpResponse.java new file mode 100644 index 00000000..191aeea2 --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/http/memo/io/MemoListRetrieverHttpResponse.java @@ -0,0 +1,13 @@ +package io.shinhanlife.dap.mcc.infra.itrf.http.memo.io; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@JsonInclude(JsonInclude.Include.NON_NULL) +public class MemoListRetrieverHttpResponse { + private String resultCode; + + private String resultMessage; +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/MciNclaClient.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/MciNclaClient.java new file mode 100644 index 00000000..5c81c4fa --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/MciNclaClient.java @@ -0,0 +1,30 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.ncla; + +import org.springframework.stereotype.Component; +import lombok.RequiredArgsConstructor; +import io.shinhanlife.dap.lib.integration.mci.component.AxhubMciComponent; +import io.shinhanlife.glow.communication.dto.Transfer; + +/** + * @package io.shinhanlife.dap.mcc.infra.itrf.mci.ncla + * @className MciNclaClient + * @description AX HUB 시스템 처리 클래스 + * @author jade + * @create 2026.08.10 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.08.10  jade    최초생성
    + *
    + * 
    + */ +@Component +@RequiredArgsConstructor +public class MciNclaClient { + private final AxhubMciComponent mci; + + public Transfer callTo(String interfaceId, String dummy, Object mciReq, Class resType) throws Exception { + return mci.callTo(interfaceId, dummy, mciReq, resType); + } +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/io/CLCNNB00001_I.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/io/CLCNNB00001_I.java new file mode 100644 index 00000000..1c5a292f --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/io/CLCNNB00001_I.java @@ -0,0 +1,14 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class CLCNNB00001_I { + @Schema(description = "보험금 청구번호", example = "CLM202608100001", requiredMode = Schema.RequiredMode.REQUIRED) + private String claimNo; + + @Schema(description = "보험 계약번호", example = "10023456789") + private String contractNo; + +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/io/CLCNNB00001_O.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/io/CLCNNB00001_O.java new file mode 100644 index 00000000..99760997 --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/infra/itrf/mci/ncla/io/CLCNNB00001_O.java @@ -0,0 +1,17 @@ +package io.shinhanlife.dap.mcc.infra.itrf.mci.ncla.io; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class CLCNNB00001_O { + @Schema(description = "청구 처리 상태 코드", example = "RECEIVED", requiredMode = Schema.RequiredMode.REQUIRED) + private String status; + + @Schema(description = "청구 처리 상태명", example = "접수", requiredMode = Schema.RequiredMode.REQUIRED) + private String statusLabel; + + @Schema(description = "승인 금액", example = "150000") + private Long approvedAmount; + +} diff --git a/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/sms/DapWasSmsApplication.java b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/sms/DapWasSmsApplication.java new file mode 100644 index 00000000..49eb8db4 --- /dev/null +++ b/dap-was-sms/src/main/java/io/shinhanlife/dap/mcc/sms/DapWasSmsApplication.java @@ -0,0 +1,30 @@ +package io.shinhanlife.dap.mcc.sms; + + +/** + * @package io.shinhanlife.dap.mcc.sms + * @className DapWasSmsApplication + * @description AX HUB 시스템 처리 클래스 + * @author 0986406 + * @create 2026.09.01 + *
    + * ---------- 개정이력 ----------
    + * 수정일      수정자    수정내용
    + * ---------- -------- ---------------------------
    + * 2026.09.01  0986406    최초생성
    + * 
    + * 
    + */ +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.cache.annotation.EnableCaching; + +@SpringBootApplication(scanBasePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) +@ConfigurationPropertiesScan(basePackages = {"io.shinhanlife.dap.mcc", "io.shinhanlife.dap.lib"}) +@EnableCaching +public class DapWasSmsApplication { + public static void main(String[] args) { + SpringApplication.run(DapWasSmsApplication.class, args); + } +} diff --git a/dap-was-sms/src/main/resources/application-dev.yml b/dap-was-sms/src/main/resources/application-dev.yml new file mode 100644 index 00000000..dfee070e --- /dev/null +++ b/dap-was-sms/src/main/resources/application-dev.yml @@ -0,0 +1,15 @@ +# OCI ?대씪?곕뱶 ?섍꼍 ?꾩슜 ?ㅼ젙 +server: + port: ${PORT:8082} + +axhub: + gateway: + url: https://axhubmcp.devjun.net + +spring: + config: + activate: + on-profile: dev + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-dev.yml diff --git a/dap-was-sms/src/main/resources/application-local.yml b/dap-was-sms/src/main/resources/application-local.yml new file mode 100644 index 00000000..d4add358 --- /dev/null +++ b/dap-was-sms/src/main/resources/application-local.yml @@ -0,0 +1,28 @@ +# Local 환경 전용 설정 (H2 메모리 DB 등) +spring: + config: + activate: + on-profile: local + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-local.yml + datasource: + url: jdbc:p6spy:h2:mem:testdb;DB_CLOSE_DELAY=-1; + driverClassName: com.p6spy.engine.spy.P6SpyDriver + username: sa + password: password + h2: + console: + enabled: true + +mcp: + security: + tenant-domains: + mcp-client-1: CUSTOMER,COMMON + mcp-client-2: ALL + +axhub: + gateway: + url: http://localhost:8081 + tool: + url: ${AXHUB_TOOL_URL:http://localhost:${server.port}} diff --git a/dap-was-sms/src/main/resources/application-prod.yml b/dap-was-sms/src/main/resources/application-prod.yml new file mode 100644 index 00000000..8d6f1ab0 --- /dev/null +++ b/dap-was-sms/src/main/resources/application-prod.yml @@ -0,0 +1,16 @@ +server: + port: ${PORT:8082} + +spring: + config: + activate: + on-profile: prod + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-prod.yml + +axhub: + gateway: + url: ${AXHUB_GATEWAY_URL} + tool: + url: ${AXHUB_TOOL_URL} \ No newline at end of file diff --git a/dap-was-sms/src/main/resources/application-test.yml b/dap-was-sms/src/main/resources/application-test.yml new file mode 100644 index 00000000..a394e88d --- /dev/null +++ b/dap-was-sms/src/main/resources/application-test.yml @@ -0,0 +1,16 @@ +server: + port: ${PORT:8082} + +spring: + config: + activate: + on-profile: test + import: + - classpath:glow/application-glow.yml + - classpath:glow/application-glow-test.yml + +axhub: + gateway: + url: ${AXHUB_GATEWAY_URL} + tool: + url: ${AXHUB_TOOL_URL} \ No newline at end of file diff --git a/dap-was-sms/src/main/resources/application.yml b/dap-was-sms/src/main/resources/application.yml new file mode 100644 index 00000000..38f04748 --- /dev/null +++ b/dap-was-sms/src/main/resources/application.yml @@ -0,0 +1,19 @@ +server: + port: 8082 +spring: + application: + name: dap-was-sms + profiles: + active: local +logging: + level: + org.apache.kafka: ERROR +mcp: + namespace: "" + manifest: + bundle-id: tool-sms + # Set the AA-assigned prefix before MCP pull activation (for example: sms.). + name-prefix: "" + security: + tenant-domains: + TESTER-DEV: ALL \ No newline at end of file diff --git a/dap-was-sms/src/main/resources/logback-spring.xml b/dap-was-sms/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..e3bf3693 --- /dev/null +++ b/dap-was-sms/src/main/resources/logback-spring.xml @@ -0,0 +1,39 @@ + + + + + + + + + + ${LOG_PATTERN} + + + + + + + + + /swlog/dap-was-sms/A01/${HOSTNAME}_A01.log + + + /swlog/dap-was-sms/A01/${HOSTNAME}_A01_%d{yyyyMMdd}.log + + 30 + + + ${LOG_PATTERN} + + + + + + + + + + + + diff --git a/dap-was-sms/src/main/resources/mock-responses/cmm_memo_retriever.json b/dap-was-sms/src/main/resources/mock-responses/cmm_memo_retriever.json new file mode 100644 index 00000000..a949502b --- /dev/null +++ b/dap-was-sms/src/main/resources/mock-responses/cmm_memo_retriever.json @@ -0,0 +1,3 @@ +{ + "resultCode": "SUCCESS" +} diff --git a/dap-was-sms/src/main/resources/mock-responses/sms_cmm_claim_search.json b/dap-was-sms/src/main/resources/mock-responses/sms_cmm_claim_search.json new file mode 100644 index 00000000..9c4f9c03 --- /dev/null +++ b/dap-was-sms/src/main/resources/mock-responses/sms_cmm_claim_search.json @@ -0,0 +1,5 @@ +{ + "status" : "RECEIVED", + "statusLabel" : "접수", + "approvedAmount" : 150000 +} diff --git a/dap-was-sms/src/main/resources/tool-schemas/cmm/claim-search-resource-input-schema.json b/dap-was-sms/src/main/resources/tool-schemas/cmm/claim-search-resource-input-schema.json new file mode 100644 index 00000000..e6f63087 --- /dev/null +++ b/dap-was-sms/src/main/resources/tool-schemas/cmm/claim-search-resource-input-schema.json @@ -0,0 +1,11 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "TODO_FIELD": { + "type": "string", + "description": "TODO: 파라미터 설명을 입력하세요." + } + }, + "required": [] +} diff --git a/dap-was-sms/src/main/resources/tool-schemas/cmm/claim-search-resource-output-schema.json b/dap-was-sms/src/main/resources/tool-schemas/cmm/claim-search-resource-output-schema.json new file mode 100644 index 00000000..9c280db8 --- /dev/null +++ b/dap-was-sms/src/main/resources/tool-schemas/cmm/claim-search-resource-output-schema.json @@ -0,0 +1,16 @@ +{ + "type": "object", + "additionalProperties": false, + "properties": { + "status": { + "type": "string", + "description": "처리 결과 상태 (SUCCESS / FAILURE)", + "enum": ["SUCCESS", "FAILURE"] + }, + "message": { + "type": "string", + "description": "처리 결과 메시지" + } + }, + "required": ["status"] +} diff --git a/dap-was-sms/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCaseTest.java b/dap-was-sms/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCaseTest.java new file mode 100644 index 00000000..37356b3e --- /dev/null +++ b/dap-was-sms/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/ClaimSearchUseCaseTest.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.ClaimSearchResponse; +import org.junit.jupiter.api.Test; + +class ClaimSearchUseCaseTest { + + @Test + void createsToolRequestAndResponseDtos() { + assertNotNull(new ClaimSearchRequest()); + assertNotNull(new ClaimSearchResponse()); + } +} diff --git a/dap-was-sms/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCaseTest.java b/dap-was-sms/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCaseTest.java new file mode 100644 index 00000000..61d6f7f4 --- /dev/null +++ b/dap-was-sms/src/test/java/io/shinhanlife/dap/mcc/biz/cmm/usecase/MemoListRetrieverUseCaseTest.java @@ -0,0 +1,16 @@ +package io.shinhanlife.dap.mcc.biz.cmm.usecase; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverRequest; +import io.shinhanlife.dap.mcc.biz.cmm.dto.MemoListRetrieverResponse; +import org.junit.jupiter.api.Test; + +class MemoListRetrieverUseCaseTest { + + @Test + void createsToolRequestAndResponseDtos() { + assertNotNull(new MemoListRetrieverRequest()); + assertNotNull(new MemoListRetrieverResponse()); + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 6ad78860..35ab900c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,6 +43,7 @@ services: - ./mci-mock:/home/wiremock tool-sms: + hostname: tool-sms build: context: . dockerfile: dap-tool-sms/Dockerfile @@ -67,6 +68,7 @@ services: - SPRING_PROFILES_ACTIVE=${ACTIVE_PROFILE:-local} tool-oth: + hostname: tool-oth build: context: . dockerfile: dap-tool-oth/Dockerfile diff --git a/docs/shinhanlife-internal-network-migration-checklist.md b/docs/shinhanlife-internal-network-migration-checklist.md new file mode 100644 index 00000000..0d09894a --- /dev/null +++ b/docs/shinhanlife-internal-network-migration-checklist.md @@ -0,0 +1,96 @@ +# 신한라이프 내부망 Tool Pod 이관 준비 체크리스트 + +> 범위: `dap-was-lib`, `dap-was-oth`, `dap-was-sms` Tool Pod 이관 +> + +## 1. 소스 및 형상관리 + +- [ ] 내부 Git 저장소 생성 +- [ ] 대상 모듈 이관: `dap-was-lib`, `dap-was-oth`, `dap-was-sms` +- [ ] `main` 브랜치 및 필요한 커밋 이력 이관 +- [ ] API Key, 비밀번호, 인증서, 개인 설정 파일은 Git에서 제외 +- [ ] 내부 Git URL 기준으로 README와 CI/CD 설정 변경 + +## 2. 개발 및 빌드 환경 + +- [ ] JDK 21 설치 및 `JAVA_HOME` 설정 +- [ ] Gradle Wrapper 실행 가능 여부 확인 +- [ ] Docker 또는 Kubernetes 배포 환경 확인 +- [ ] 내부 Git 접근 권한 확인 +- [ ] 사내 Nexus 접근 권한 확인 + +## 3. 사내 Nexus 라이브러리 준비 + +인터넷 없이 빌드하려면 사내 Nexus에 다음 라이브러리 또는 Proxy Repository가 준비되어야 합니다. + +- [ ] Spring Boot 3.5.11 +- [ ] Spring AI 1.1.8 +- [ ] MapStruct, Lombok, H2, P6Spy +- [ ] Glow Framework 관련 라이브러리 +- [ ] 신한라이프 MCI/EAI 및 보안 관련 사내 라이브러리 +- [ ] Docker Base Image: `eclipse-temurin:21-jre-alpine` + +## 4. 환경별 설정 + +사용 프로필: + +```text +local / dev / test / prod +``` + +배포 환경변수: + +```text +SPRING_PROFILES_ACTIVE +AXHUB_TOOL_URL + +GLOW_COMMUNICATION_MCI_HOST +GLOW_COMMUNICATION_MCI_PORT +``` + +- [ ] 개발계(`dev`) MCI 주소 및 포트 등록 +- [ ] 테스트계(`test`) MCI 주소 및 포트 등록 +- [ ] 운영계(`prod`) MCI 주소 및 포트 등록 +- [ ] 운영 비밀값은 Git이 아닌 배포 환경변수 또는 Secret으로 관리 + +## 5. MCI 연계 협의 + +MCI 담당자에게 아래 정보를 요청합니다. + +- [ ] 개발·테스트·운영 MCI URL과 포트 +- [ ] Interface ID와 URI +- [ ] 요청·응답 전문 규격 +- [ ] 필수 Header 및 인증 방식 +- [ ] Connection/Read Timeout 기준 +- [ ] Tool Pod → MCI 통신 ACL 허용 + +## 6. Tool Pod 배포 정보 + +| Tool Pod | 내부 Endpoint | +|---|---| +| OTH Tool Pod | `http://tool-oth:8084/mcp` | +| SMS Tool Pod | `http://tool-sms:8082/mcp` | + +Portal 담당자에게 아래 정보를 전달합니다. + +- [ ] Pod명 및 서비스명 +- [ ] 포트와 MCP Endpoint +- [ ] Tool 목록 및 Tool 명칭 +- [ ] 담당자 및 장애 연락처 +- [ ] Health Check URL + +## 7. Redis 정책 + +- [ ] Tool Pod에서 Redis 사용 여부 결정 +- [ ] Redis를 사용할 경우 Host, Port, Password, ACL 확인 +- [ ] Redis를 사용하지 않을 경우 Tool Pod 기능에 영향이 없는지 개별 Tool 기준 확인 + +## 8. 이관 후 검증 + +- [ ] 내부 Nexus만으로 `./gradlew clean build` 성공 +- [ ] OTH, SMS Tool Pod 기동 성공 +- [ ] 각 Tool Pod의 MCP Endpoint 연결 성공 +- [ ] Tool Pod → MCI 호출 성공 +- [ ] `trace-id`, `request-id` 전달 확인 +- [ ] Tool 이름 중복 검증 확인 +- [ ] 민감정보와 비밀값이 Git에 포함되지 않았는지 확인 \ No newline at end of file diff --git a/manifest_output.json b/manifest_output.json new file mode 100644 index 00000000..b8125795 --- /dev/null +++ b/manifest_output.json @@ -0,0 +1,606 @@ +{ + "bundleId": "tool-oth", + "revision": "1785826298946", + "tools": [ + { + "name": "balance", + "endpoint": "http://localhost:8084/mcp/balance", + "title": "balance 툴", + "description": "고객의 계좌 잔액을 조회합니다.", + "inputSchema": { + "additionalProperties": false, + "type": "object", + "properties": { + "accountNumber": { + "type": "string", + "pattern": "\\S", + "description": "고객의 계좌번호 (- 제외) " + } + }, + "required": [ + "accountNumber" + ] + }, + "annotations": { + "title": "balance 툴", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": false + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + }, + { + "name": "billing_process", + "endpoint": "http://localhost:8084/mcp/billing_process", + "title": "process 툴", + "description": "청구 처리", + "inputSchema": { + "additionalProperties": false, + "type": "object", + "properties": { + "approvalStatus": { + "type": "string", + "description": "심사 승인 여부 (예: APPROVE, REJECT)", + "enum": [ + "APPROVE", + "REJECT" + ] + }, + "billingId": { + "type": "string", + "pattern": "\\S", + "description": "처리할 청구 접수 번호" + } + }, + "required": [ + "billingId", + "approvalStatus" + ] + }, + "annotations": { + "title": "process 툴", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": false + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + }, + { + "name": "bond_issue", + "endpoint": "http://localhost:8084/mcp/bond_issue", + "title": "issue 툴", + "description": "증권 발행 í\u0085ŒìŠ¤íŠ¸1", + "inputSchema": { + "additionalProperties": false, + "type": "object", + "properties": { + "amount": { + "type": "integer", + "description": "발행할 디지털 증권 금액", + "minimum": 1 + }, + "targetAccount": { + "type": "string", + "pattern": "\\S", + "description": "발행 대상 계좌 번호" + } + }, + "required": [ + "amount", + "targetAccount" + ] + }, + "annotations": { + "title": "issue 툴", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": false + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + }, + { + "name": "contract_detail", + "endpoint": "http://localhost:8084/mcp/contract_detail", + "title": "contract_detail 툴", + "description": "계약상세 조회", + "inputSchema": { + "additionalProperties": false, + "type": "object", + "properties": { + "contractId": { + "type": "string", + "description": "조회할 계약 번호" + }, + "customerName": { + "type": "string", + "description": "고객ëª\u0085" + } + }, + "required": [ + "customerName", + "contractId" + ] + }, + "annotations": { + "title": "contract_detail 툴", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": false + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + }, + { + "name": "customer_detail", + "endpoint": "http://localhost:8084/mcp/customer_detail", + "title": "detail 툴", + "description": "고객상세 정보 조회", + "inputSchema": { + "additionalProperties": false, + "type": "object", + "properties": { + "customerId": { + "type": "string", + "description": "고객 식별 번호 (CID)" + }, + "customerName": { + "type": "string", + "description": "고객ëª\u0085" + } + }, + "required": [ + "customerName" + ] + }, + "annotations": { + "title": "detail 툴", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": false + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + }, + { + "name": "daily_quote", + "endpoint": "http://localhost:8084/mcp/daily_quote", + "title": "랜덤 ëª\u0085언 툴", + "description": "무작위로 영감을 주는 ëª\u0085언을 하나 가져옵니다.", + "inputSchema": { + "additionalProperties": false, + "type": "object", + "properties": { + "category": { + "type": "string", + "description": "category" + } + } + }, + "annotations": { + "title": "랜덤 ëª\u0085언 툴", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": false + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + }, + { + "name": "exchange_rate", + "endpoint": "http://localhost:8084/mcp/exchange_rate", + "title": "실시간 환율 조회 툴", + "description": "원하는 통화의 실시간 환율을 조회합니다. (예: USD, EUR, JPY)", + "inputSchema": { + "additionalProperties": false, + "type": "object", + "properties": { + "currencyCode": { + "type": "string", + "description": "currencyCode" + } + } + }, + "annotations": { + "title": "실시간 환율 조회 툴", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": false + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + }, + { + "name": "get_leave_count", + "endpoint": "http://localhost:8084/mcp/get_leave_count", + "title": "get_leave_count 툴", + "description": "연차 갯수 조회", + "inputSchema": { + "additionalProperties": false, + "type": "object", + "properties": { + "employeeId": { + "type": "string", + "description": "연차 내역을 조회할 사원 번호" + } + }, + "required": [ + "employeeId" + ] + }, + "annotations": { + "title": "get_leave_count 툴", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": false + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + }, + { + "name": "get_smp_members", + "endpoint": "http://localhost:8084/mcp/get_smp_members", + "title": "신한라이프 MCP, TOOL 파트 구성원 조회", + "description": "신한라이프 MCP, TOOL 파트 구성원을 조회합니다.", + "inputSchema": { + "additionalProperties": false, + "type": "object", + "properties": { + "teamName": { + "type": "string", + "description": "조회할 팀 이름 (예: AX, MCP, TOOL, 전체 등)" + } + } + }, + "annotations": { + "title": "신한라이프 MCP, TOOL 파트 구성원 조회", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + }, + { + "name": "get_template_file_url", + "endpoint": "http://localhost:8084/mcp/get_template_file_url", + "title": "í\u0085œí”Œë¦¿ 유틸리티", + "description": "요청한 í\u0085œí”Œë¦¿(엑ì\u0085€, 워드 등) 파일(양식)을 다운로드 받을 수 있는 시스í\u0085œ URL을 반환합니다. AI는 이 URL을 사용자에게 마크다운 링크 형태로 제공해야 합니다.", + "inputSchema": { + "additionalProperties": false, + "type": "object", + "properties": { + "templateId": { + "type": "string", + "description": "templateId" + } + } + }, + "annotations": { + "title": "í\u0085œí”Œë¦¿ 유틸리티", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": false + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + }, + { + "name": "metaCommonCode", + "endpoint": "http://localhost:8084/mcp/metaCommonCode", + "title": "메타 통합코드 조회 툴", + "description": "메타 통합코드 목록을 조회해줘", + "inputSchema": { + "additionalProperties": false, + "type": "object", + "properties": { + "codeName": { + "type": "string", + "description": "코드ëª\u0085 검색 키워드 (예: 사용, 상태)" + }, + "useYn": { + "type": "string", + "description": "사용여부 (예: Y, N)" + }, + "groupCode": { + "type": "string", + "description": "통합코드 그룹 ID (예: GRP_SYS_01, GRP_COMM_CD)" + } + } + }, + "annotations": { + "title": "메타 통합코드 조회 툴", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + }, + { + "name": "metaTable", + "endpoint": "http://localhost:8084/mcp/metaTable", + "title": "메타 í\u0085Œì´ë¸” 조회 툴", + "description": "메타 í\u0085Œì´ë¸” 정보 목록을 조회해줘", + "inputSchema": { + "additionalProperties": false, + "type": "object", + "properties": { + "tableLogicalName": { + "type": "string", + "description": "í\u0085Œì´ë¸” ë\u0085¼ë¦¬ëª\u0085(한글) 키워드 (예: 고객기본, 계약)" + }, + "owner": { + "type": "string", + "description": "스키마/소유자ëª\u0085 (예: DAPADM, SHLOWN)" + }, + "tableName": { + "type": "string", + "description": "í\u0085Œì´ë¸” 물리ëª\u0085 키워드 (예: TB_CUST_BAS, TB_CONT)" + } + } + }, + "annotations": { + "title": "메타 í\u0085Œì´ë¸” 조회 툴", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + }, + { + "name": "sample.claim.search.resource", + "endpoint": "http://localhost:8084/mcp/sample.claim.search.resource", + "title": "Claim search JSON Schema sample", + "description": "Claim search Tool sample using input and output JSON Schema resources.", + "inputSchema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "claimNo": { + "type": "string", + "description": "청구번호. CLM 다음 숫자 13자리 형식이다.", + "pattern": "^CLM[0-9]{13}$", + "examples": [ + "CLM2026070100123" + ] + }, + "contractNo": { + "type": "string", + "description": "계약번호. 숫자 11자리 형식이다.", + "pattern": "^[0-9]{11}$", + "examples": [ + "10023456789" + ] + }, + "status": { + "type": "string", + "enum": [ + "RECEIVED", + "REVIEWING", + "ADDITIONAL_DOC_REQUIRED", + "APPROVED", + "PAID", + "REJECTED", + "WITHDRAWN" + ] + }, + "size": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 20 + } + }, + "required": [ + + ], + "additionalProperties": false, + "anyOf": [ + { + "required": [ + "claimNo" + ] + }, + { + "required": [ + "contractNo" + ] + } + ] + }, + "annotations": { + "title": "Claim search JSON Schema sample", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": true, + "openWorldHint": false + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + }, + { + "name": "secret_tool", + "endpoint": "http://localhost:8084/mcp/secret_tool", + "title": "secret_tool 툴", + "description": "비공개 툴 í\u0085ŒìŠ¤íŠ¸", + "inputSchema": { + "additionalProperties": false, + "type": "object", + "properties": { + "employeeId": { + "type": "string", + "description": "연차 내역을 조회할 사원 번호" + } + }, + "required": [ + "employeeId" + ] + }, + "annotations": { + "title": "secret_tool 툴", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": false + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + }, + { + "name": "solReqDetail", + "endpoint": "http://localhost:8084/mcp/solReqDetail", + "title": "SolReqDetail 툴", + "description": "SOL 의뢰서 상세 조회", + "inputSchema": { + "additionalProperties": false, + "type": "object", + "properties": { + "srId": { + "type": "string", + "description": "상세 조회할 SOL 의뢰서 ID" + } + }, + "required": [ + "srId" + ] + }, + "annotations": { + "title": "SolReqDetail 툴", + "readOnlyHint": true, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + }, + { + "name": "solReqList", + "endpoint": "http://localhost:8084/mcp/solReqList", + "title": "SolReqList 툴", + "description": "SOL 의뢰서 목록 조회해줘", + "inputSchema": { + "additionalProperties": false, + "type": "object", + "properties": { + "period": { + "type": "string", + "description": "조회기간 (예: 1개월, 3개월 등)" + }, + "status": { + "type": "string", + "description": "진행상태 (예: 진행중, 완료 등)" + }, + "target": { + "type": "string", + "description": "조회대상 (예: 나의 ì—\u0085무, 전체 등)" + } + } + }, + "annotations": { + "title": "SolReqList 툴", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": true + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + }, + { + "name": "weather", + "endpoint": "http://localhost:8084/mcp/weather", + "title": "날씨 조회 툴", + "description": "특정 도시의 현재 날씨, 온도, 풍속 정보를 조회합니다.", + "inputSchema": { + "additionalProperties": false, + "type": "object", + "properties": { + "city": { + "type": "string", + "description": "날씨를 조회할 도시 이름 (예: 서울, 부산, 제주)" + } + }, + "required": [ + "city" + ] + }, + "annotations": { + "title": "날씨 조회 툴", + "readOnlyHint": false, + "destructiveHint": false, + "idempotentHint": false, + "openWorldHint": false + }, + "_meta": { + "version": "1.0.0", + "timeoutMillis": 300000, + "enabled": true + } + } + ] +} \ No newline at end of file diff --git a/mci-mock/__files/cmm_memo_retriever.json b/mci-mock/__files/cmm_memo_retriever.json new file mode 100644 index 00000000..9f9a02a5 --- /dev/null +++ b/mci-mock/__files/cmm_memo_retriever.json @@ -0,0 +1,3 @@ +{ + "resultCode" : "SUCCESS" +} diff --git a/mci-mock/__files/ins_insurance_processor.json b/mci-mock/__files/ins_insurance_processor.json new file mode 100644 index 00000000..d856cc5b --- /dev/null +++ b/mci-mock/__files/ins_insurance_processor.json @@ -0,0 +1,4 @@ +{ + "resultCode" : "SUCCESS", + "claimId" : "CLM20230001" +} diff --git a/mci-mock/mappings/cmm_memo_retriever.json b/mci-mock/mappings/cmm_memo_retriever.json new file mode 100644 index 00000000..d592009c --- /dev/null +++ b/mci-mock/mappings/cmm_memo_retriever.json @@ -0,0 +1,13 @@ +{ + "request" : { + "method" : "POST", + "urlPath" : "/MEMO0000001" + }, + "response" : { + "status" : 200, + "headers" : { + "Content-Type" : "application/json;charset=UTF-8" + }, + "bodyFileName" : "cmm_memo_retriever.json" + } +} diff --git a/mci-mock/mappings/ins_insurance_processor.json b/mci-mock/mappings/ins_insurance_processor.json new file mode 100644 index 00000000..7234f808 --- /dev/null +++ b/mci-mock/mappings/ins_insurance_processor.json @@ -0,0 +1,13 @@ +{ + "request" : { + "method" : "POST", + "urlPath" : "/CLAIM0000001" + }, + "response" : { + "status" : 200, + "headers" : { + "Content-Type" : "application/json;charset=UTF-8" + }, + "bodyFileName" : "ins_insurance_processor.json" + } +}