From 9cc87337b89ae33fd29d117d0470011e9631eb42 Mon Sep 17 00:00:00 2001 From: Zeeshaun Date: Tue, 24 Mar 2026 15:26:03 -0500 Subject: [PATCH] Adding --- LCU.API.Internal.Dispatcher/.gitignore | 330 ++++++ LCU.API.Internal.Dispatcher/Dockerfile | 42 + .../LCU.Api.Internal.Dispatcher.sln | 66 ++ .../Configurations/ApiConfiguration.cs | 26 + .../ConnectionsConfiguration.cs | 22 + .../Configurations/FeatureFlags.cs | 13 + .../ReceiveEndpointConfigurations.cs | 11 + .../Consumers/CompletionConsumer.cs | 47 + .../Consumers/DetermineOperationConsumer.cs | 52 + .../Consumers/ExecutionConsumer.cs | 47 + .../Consumers/JobFailureConsumer.cs | 50 + .../Consumers/SendErroredJobAlertConsumer.cs | 57 + .../CleanUpJobProcessController.cs | 42 + .../CleanUpJobSettingController.cs | 161 +++ .../Controllers/CleanUpJobTagController.cs | 192 ++++ .../Controllers/ExecutionMessageController.cs | 43 + .../Controllers/HostedServicesController.cs | 163 +++ .../Controllers/IngestionController.cs | 51 + .../Controllers/JobController.cs | 304 ++++++ .../Controllers/JobTagController.cs | 112 ++ .../Controllers/QueueCompletionController.cs | 45 + .../Controllers/QueueExecutionController.cs | 45 + .../Controllers/StatusController.cs | 31 + .../Controllers/TagController.cs | 137 +++ .../Controllers/WorkItemController.cs | 39 + .../DataAccess/INotificationsApiDao.cs | 8 + .../DataAccess/NotificationsApiDao.cs | 21 + .../Extensions/ModelStateMessages.cs | 14 + .../BatchErroredJobsHostedService.cs | 68 ++ .../CleanUpJobsHostedService.cs | 69 ++ .../IBatchErroredJobsHostedService.cs | 8 + .../ICleanUpJobsHostedService.cs | 8 + .../IJobSchedulerHostedService.cs | 8 + .../JobSchedulerHostedService.cs | 58 + .../LCU.Api.Internal.Dispatcher.csproj | 96 ++ .../Models/EnvironmentView.cs | 30 + .../Models/IEnvironmentView.cs | 11 + .../LCU.Api.Internal.Dispatcher/Program.cs | 396 +++++++ .../Services/CleanUpJobSettingService.cs | 114 ++ .../Services/CleanUpJobTagService.cs | 135 +++ .../BatchErroredJobsService.cs | 72 ++ .../CleanUpJobsService.cs | 172 +++ .../CompletionService.cs | 84 ++ .../DetermineOperationService.cs | 82 ++ .../ExecutionService.cs | 96 ++ .../IBatchErroredJobsService.cs | 7 + .../ICleanUpJobsService.cs | 8 + .../ICompletionService.cs | 7 + .../IDetermineOperationService.cs | 9 + .../IExecutionService.cs | 6 + .../IIngestionService.cs | 7 + .../IJobFailureService.cs | 9 + .../IJobScheduleService.cs | 7 + .../ISendErroredJobAlertService.cs | 10 + .../IngestionService.cs | 159 +++ .../JobFailureService.cs | 91 ++ .../JobScheduleService.cs | 67 ++ .../SendErroredJobAlertService.cs | 146 +++ .../Services/ExecutionMessageService.cs | 90 ++ .../Services/ICleanUpJobSettingService.cs | 13 + .../Services/ICleanUpJobTagService.cs | 13 + .../Services/IExecutionMessageService.cs | 9 + .../Services/IJobErrorRecipientService.cs | 9 + .../Services/IJobService.cs | 35 + .../Services/IJobTagService.cs | 10 + .../Services/INotificationService.cs | 7 + .../Services/ITagService.cs | 11 + .../Services/IWorkItemExecutionService.cs | 17 + .../Services/IWorkItemService.cs | 18 + .../Services/JobErrorRecipientService.cs | 55 + .../Services/JobService.cs | 583 +++++++++++ .../Services/JobTagService.cs | 81 ++ .../Services/NotificationService.cs | 40 + .../QueueServices/IQueueCompletionService.cs | 8 + .../IQueueDetermineOperationService.cs | 10 + .../QueueServices/IQueueExecutionService.cs | 7 + .../QueueServices/IQueueJobFailureService.cs | 10 + .../IQueueSendErroredJobAlertService.cs | 10 + .../QueueServices/QueueCompletionService.cs | 45 + .../QueueDetermineOperationService.cs | 48 + .../QueueServices/QueueExecutionService.cs | 55 + .../QueueServices/QueueJobFailureService.cs | 49 + .../QueueSendErroredJobAlertService.cs | 63 ++ .../Services/TagService.cs | 135 +++ .../Services/WorkItemExecutionService.cs | 150 +++ .../Services/WorkItemService.cs | 258 +++++ .../Templates/BatchErroredJobsTemplate.html | 58 + .../Templates/ErrorRowTemplate.html | 6 + .../LCU.Api.Internal.Dispatcher/Using.cs | 7 + .../appsettings.json | 216 ++++ .../LCU.Api.Internal.Dispatcher/libman.json | 5 + .../DispatcherRequestBuilderTest.cs | 126 +++ ...Internal.DispatcherIntegrationTests.csproj | 34 + .../appsettings.integration.json | 78 ++ .../Extensions/Enumerables/StatusTypeMatch.cs | 25 + .../Queryables/ContainsTagIdQueryable.cs | 18 + .../Queryables/IncludeAllQueryable.cs | 70 ++ .../Queryables/JobDescriptionQueryable.cs | 18 + .../Extensions/Queryables/JobNameQueryable.cs | 18 + .../NextRunTimeGreaterThanDateQueryable.cs | 18 + .../Queryables/SortableFieldsQueryable.cs | 27 + .../LCU.Api.Internal.DispatcherLib.csproj | 47 + .../Persistence/AppDbContext.cs | 32 + .../Repositories/BaseRepository.cs | 12 + .../CleanUpJobSettingRepository.cs | 45 + .../Repositories/CleanUpJobTagRepository.cs | 51 + .../ExecutionMessageRepository.cs | 23 + .../ICleanUpJobSettingRepository.cs | 17 + .../Repositories/ICleanUpJobTagRepository.cs | 16 + .../IExecutionMessageRepository.cs | 9 + .../IJobErrorRecipientRepository.cs | 9 + .../Repositories/IJobRepository.cs | 35 + .../Repositories/IJobTagRepository.cs | 13 + .../Repositories/ITagRepository.cs | 13 + .../Repositories/IUnitOfWork.cs | 8 + .../IWorkItemExecutionRepository.cs | 14 + .../Repositories/IWorkItemRepository.cs | 20 + .../JobErrorRecipientRepository.cs | 19 + .../Repositories/JobRepository.cs | 159 +++ .../Repositories/JobTagRepository.cs | 46 + .../Repositories/TagRepository.cs | 40 + .../Repositories/UnitOfWork.cs | 23 + .../WorkItemExecutionRepository.cs | 54 + .../Repositories/WorkItemRepository.cs | 93 ++ .../BuilderServices/ActionBuilderService.cs | 351 +++++++ .../BuilderServices/ActionService.cs | 102 ++ .../BuilderServices/IActionBuilderService.cs | 8 + .../BuilderServices/IActionService.cs | 13 + .../BuilderServices/IRequestBuilderService.cs | 16 + .../BuilderServices/RequestBuilderService.cs | 152 +++ .../ArchiverApiConfiguration.cs | 11 + .../Configurations/BlendApiConfiguration.cs | 12 + .../DataCleanUpApiConfiguration.cs | 22 + .../Configurations/DialerApiConfiguration.cs | 23 + .../DispatcherRequestBuilderConfiguration.cs | 6 + .../DocumentIngestorApiConfiguration.cs | 26 + .../DocumentTypeCleanupApiConfiguration.cs | 12 + .../FileOperationsApiConfiguration.cs | 75 ++ .../FileUpdaterApiConfiguration.cs | 12 + .../Configurations/FtpApiConfiguration.cs | 23 + ...toricalDocumentIngestorApiConfiguration.cs | 17 + .../IncentiveManagerConfiguration.cs | 42 + .../Configurations/MomBotApiConfiguration.cs | 15 + .../NoticeProcessorApiConfiguration.cs | 14 + .../NotificationApiConfiguration.cs | 11 + .../RealTimeAlertsApiConfiguration.cs | 16 + .../RemoteDepositCaptureApiConfiguration.cs | 11 + .../Configurations/RepoApiConfiguration.cs | 11 + .../TemenosNotificationApiConfiguration.cs | 12 + .../DependencyInjection.cs | 38 + ...CU.Api.Internal.DispatcherSharedLib.csproj | 297 ++++++ .../Utilities/CronHelpers.cs | 38 + .../Utilities/HolidayHelpers.cs | 92 ++ .../CleanUpJobSetting.sql | 5 + .../CleanUpJobTag.sql | 8 + .../ExecutionMessage.sql | 16 + .../ExecutionMessage_History.sql | 16 + .../LCU.Api.Internal.DispatcherSql/Job.sql | 36 + .../JobErrorRecipient.sql | 14 + .../LCU.Api.Internal.DispatcherSql/JobTag.sql | 21 + ...LCU.Api.Internal.DispatcherSql.refactorlog | 185 ++++ .../LCU.Api.Internal.DispatcherSql.sqlproj | 81 ++ .../LCU.Api.Internal.DispatcherSql/Tag.sql | 5 + .../WorkItem.sql | 23 + .../WorkItemExecution.sql | 16 + .../WorkItemExecution_History.sql | 17 + .../ActionBuilderServiceTest.cs | 529 ++++++++++ .../IngestionControllerTest.cs | 72 ++ .../ControllerTests/JobControllerTest.cs | 132 +++ .../HelperTests/CronHelpersTest.cs | 125 +++ .../HelperTests/HolidayHelpersTest.cs | 218 ++++ .../LCU.Api.Internal.DispatcherTests.csproj | 33 + .../RepositoryTests/DbContextFromJson.cs | 71 ++ .../RepositoryTests/JobRepositoryTest.cs | 395 +++++++ .../RepositoryTests/WorkItemRepositoryTest.cs | 102 ++ .../RepositoryTests/job_repo_basic.json | 440 ++++++++ .../CleanUpJobSettingServiceTest.cs | 129 +++ .../ServiceTests/CleanUpJobTagServiceTest.cs | 145 +++ .../BatchErroredJobsServiceTest.cs | 101 ++ .../CleanUpJobsServiceTest.cs | 382 +++++++ .../CompletionServiceTest.cs | 210 ++++ .../ExecutionServiceTest.cs | 127 +++ .../IngestionServiceTest.cs | 643 ++++++++++++ .../ServiceTests/JobServiceTest.cs | 990 ++++++++++++++++++ .../ServiceTests/JobTagServiceTest.cs | 70 ++ .../SendErroredJobAlertServiceTests.cs | 389 +++++++ .../ServiceTests/TagServiceTest.cs | 116 ++ .../WorkItemExecutionServiceTest.cs | 102 ++ .../ServiceTests/WorkItemServiceTest.cs | 226 ++++ .../Test_BatchErroredJobsTemplate.html | 3 + .../TemplatesTest/Test_ErrorRowTemplate.html | 6 + .../LCU.Api.Internal.DispatcherTests/Using.cs | 8 + .../additional_test_args.runsettings | 27 + .../Constants/ActionMessages.cs | 23 + .../Constants/JobSortableFields.cs | 16 + .../Entities/CleanUpJobSetting.cs | 17 + .../Entities/CleanUpJobTag.cs | 15 + .../Entities/ExecutionMessage.cs | 26 + .../Entities/Job.cs | 98 ++ .../Entities/JobErrorRecipient.cs | 8 + .../Entities/JobTag.cs | 11 + .../Entities/Tag.cs | 10 + .../Entities/WorkItem.cs | 48 + .../Entities/WorkItemExecution.cs | 39 + .../Enums/ActionType.cs | 69 ++ .../Enums/DetermineOperationType.cs | 8 + .../Enums/HttpMethodType.cs | 7 + .../Enums/JobStatus.cs | 12 + .../Enums/StatusType.cs | 11 + .../Helpers/Clone.cs | 16 + ...U.Library.Internal.DispatcherModels.csproj | 146 +++ .../Models/DispatcherReturnResponse.cs | 7 + .../Models/Errorable.cs | 6 + .../Models/Filter.cs | 44 + .../Models/JobDetails.cs | 41 + .../Models/JobErrorDetail.cs | 9 + .../Models/PagedResponse.cs | 31 + .../Requests/BatchedErroredJobAlertRequest.cs | 9 + .../Models/Requests/CopyJobRequest.cs | 7 + .../Requests/DetermineOperationRequest.cs | 12 + .../Models/Requests/ErroredJobAlertRequest.cs | 10 + .../Models/Requests/JobFailureRequest.cs | 11 + .../Models/Results/CleanUpJobBatchResult.cs | 36 + .../Models/Results/PagedResult.cs | 8 + .../Models/SortableField.cs | 8 + .../Models/StatusFilter.cs | 8 + .../Models/WorkItemDetails.cs | 20 + LCU.API.Internal.Dispatcher/README.md | 89 ++ .../azdo_pipelines/azure-k8s-pipeline.yaml | 72 ++ .../azure-pipelines-feature-branches.yaml | 10 + .../azdo_pipelines/azure-pipelines-prd.yaml | 9 + .../azdo_pipelines/azure-pipelines-qa.yaml | 9 + .../azdo_pipelines/azure-pipelines-uat.yaml | 9 + .../config-files/prd-pvars.yaml | 12 + .../azdo_pipelines/config-files/qa-pvars.yaml | 12 + .../config-files/uat-pvars.yaml | 12 + .../azure-pipelines.yml | 87 ++ .../kustomize/base/Deployment.yaml | 44 + .../kustomize/base/Service.yaml | 11 + .../kustomize/base/kustomization.yaml | 9 + .../base/secrets-provider-class.yaml | 12 + .../kustomize/base/virtual-service.yaml | 19 + .../keyvaults/normal/kustomization.yaml | 10 + .../normal/set_secrets_deployment.yaml | 49 + .../normal/set_secrets_provider.yaml | 24 + .../completion/appsettings/appsettings.json | 14 + .../workloads/completion/kustomization.yaml | 40 + .../set_appsettings_deployment.yaml | 12 + .../appsettings/appsettings.json | 14 + .../determine-operation/kustomization.yaml | 40 + .../set_appsettings_deployment.yaml | 12 + .../execution/appsettings/appsettings.json | 14 + .../workloads/execution/kustomization.yaml | 40 + .../execution/set_appsettings_deployment.yaml | 12 + .../job-failure/appsettings/appsettings.json | 14 + .../workloads/job-failure/kustomization.yaml | 40 + .../set_appsettings_deployment.yaml | 12 + .../appsettings/appsettings.json | 15 + .../job-scheduler/kustomization.yaml | 40 + .../set_appsettings_deployment.yaml | 12 + .../normal/appsettings/appsettings.json | 22 + .../workloads/normal/kustomization.yaml | 40 + .../normal/set_appsettings_deployment.yaml | 12 + .../appsettings/appsettings.json | 14 + .../send-errored-job-alert/kustomization.yaml | 40 + .../set_appsettings_deployment.yaml | 12 + .../overlays/local/common/kustomization.yaml | 8 + .../common/set_appsettings_deployment.yaml | 12 + .../common/set_secrets_provider_class.yaml | 9 + .../overlays/local/kustomization.yaml | 2 + .../overlays/local/normal/configmap.yaml | 89 ++ .../overlays/local/normal/kustomization.yaml | 5 + .../local/normal/set_virtual_service.yaml | 8 + .../overlays/prd/appsettings/appsettings.json | 72 ++ .../prd/completion/kustomization.yaml | 12 + .../prd/completion/set_virtual_service.yaml | 3 + .../overlays/prd/component/kustomization.yaml | 10 + .../component/set_appsettings_deployment.yaml | 5 + .../component/set_secrets_provider_class.yaml | 7 + .../determine-operation/kustomization.yaml | 12 + .../set_virtual_service.yaml | 3 + .../overlays/prd/execution/kustomization.yaml | 12 + .../prd/execution/set_virtual_service.yaml | 3 + .../prd/job-failure/kustomization.yaml | 12 + .../prd/job-failure/set_virtual_service.yaml | 3 + .../prd/job-scheduler/kustomization.yaml | 12 + .../job-scheduler/set_virtual_service.yaml | 3 + .../kustomize/overlays/prd/kustomization.yaml | 20 + .../overlays/prd/normal/kustomization.yaml | 12 + .../prd/normal/set_virtual_service.yaml | 3 + .../send-errored-job-alert/kustomization.yaml | 12 + .../set_virtual_service.yaml | 3 + .../prd/set_configmap_deployment.yaml | 12 + .../overlays/qa/appsettings/appsettings.json | 74 ++ .../overlays/qa/completion/kustomization.yaml | 12 + .../qa/completion/set_virtual_service.yaml | 3 + .../overlays/qa/component/kustomization.yaml | 10 + .../component/set_appsettings_deployment.yaml | 5 + .../component/set_secrets_provider_class.yaml | 7 + .../qa/determine-operation/kustomization.yaml | 12 + .../set_virtual_service.yaml | 3 + .../overlays/qa/execution/kustomization.yaml | 12 + .../qa/execution/set_virtual_service.yaml | 3 + .../qa/job-failure/kustomization.yaml | 12 + .../qa/job-failure/set_virtual_service.yaml | 3 + .../qa/job-scheduler/kustomization.yaml | 12 + .../qa/job-scheduler/set_virtual_service.yaml | 3 + .../kustomize/overlays/qa/kustomization.yaml | 20 + .../overlays/qa/normal/kustomization.yaml | 12 + .../qa/normal/set_virtual_service.yaml | 3 + .../send-errored-job-alert/kustomization.yaml | 12 + .../set_virtual_service.yaml | 3 + .../overlays/qa/set_configmap_deployment.yaml | 12 + .../overlays/uat/appsettings/appsettings.json | 74 ++ .../uat/completion/kustomization.yaml | 12 + .../uat/completion/set_virtual_service.yaml | 3 + .../overlays/uat/component/kustomization.yaml | 10 + .../component/set_appsettings_deployment.yaml | 5 + .../component/set_secrets_provider_class.yaml | 7 + .../determine-operation/kustomization.yaml | 12 + .../set_virtual_service.yaml | 3 + .../overlays/uat/execution/kustomization.yaml | 12 + .../uat/execution/set_virtual_service.yaml | 3 + .../uat/job-failure/kustomization.yaml | 12 + .../uat/job-failure/set_virtual_service.yaml | 3 + .../uat/job-scheduler/kustomization.yaml | 12 + .../job-scheduler/set_virtual_service.yaml | 3 + .../kustomize/overlays/uat/kustomization.yaml | 20 + .../overlays/uat/normal/kustomization.yaml | 12 + .../uat/normal/set_virtual_service.yaml | 3 + .../send-errored-job-alert/kustomization.yaml | 12 + .../set_virtual_service.yaml | 3 + .../uat/set_configmap_deployment.yaml | 12 + 333 files changed, 17931 insertions(+) create mode 100644 LCU.API.Internal.Dispatcher/.gitignore create mode 100644 LCU.API.Internal.Dispatcher/Dockerfile create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher.sln create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Configurations/ApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Configurations/ConnectionsConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Configurations/FeatureFlags.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Configurations/ReceiveEndpointConfigurations.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/CompletionConsumer.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/DetermineOperationConsumer.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/ExecutionConsumer.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/JobFailureConsumer.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/SendErroredJobAlertConsumer.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/CleanUpJobProcessController.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/CleanUpJobSettingController.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/CleanUpJobTagController.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/ExecutionMessageController.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/HostedServicesController.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/IngestionController.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/JobController.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/JobTagController.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/QueueCompletionController.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/QueueExecutionController.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/StatusController.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/TagController.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/WorkItemController.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/DataAccess/INotificationsApiDao.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/DataAccess/NotificationsApiDao.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Extensions/ModelStateMessages.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/BatchErroredJobsHostedService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/CleanUpJobsHostedService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/IBatchErroredJobsHostedService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/ICleanUpJobsHostedService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/IJobSchedulerHostedService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/JobSchedulerHostedService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/LCU.Api.Internal.Dispatcher.csproj create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Models/EnvironmentView.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Models/IEnvironmentView.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Program.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/CleanUpJobSettingService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/CleanUpJobTagService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/BatchErroredJobsService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/CleanUpJobsService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/CompletionService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/DetermineOperationService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/ExecutionService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IBatchErroredJobsService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/ICleanUpJobsService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/ICompletionService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IDetermineOperationService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IExecutionService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IIngestionService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IJobFailureService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IJobScheduleService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/ISendErroredJobAlertService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IngestionService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/JobFailureService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/JobScheduleService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/SendErroredJobAlertService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/ExecutionMessageService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/ICleanUpJobSettingService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/ICleanUpJobTagService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IExecutionMessageService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IJobErrorRecipientService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IJobService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IJobTagService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/INotificationService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/ITagService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IWorkItemExecutionService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IWorkItemService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/JobErrorRecipientService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/JobService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/JobTagService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/NotificationService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueCompletionService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueDetermineOperationService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueExecutionService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueJobFailureService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueSendErroredJobAlertService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueCompletionService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueDetermineOperationService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueExecutionService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueJobFailureService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueSendErroredJobAlertService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/TagService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/WorkItemExecutionService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/WorkItemService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Templates/BatchErroredJobsTemplate.html create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Templates/ErrorRowTemplate.html create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Using.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/appsettings.json create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/libman.json create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherIntegrationTests/DispatcherRequestBuilderTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherIntegrationTests/LCU.Api.Internal.DispatcherIntegrationTests.csproj create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherIntegrationTests/appsettings.integration.json create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Enumerables/StatusTypeMatch.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/ContainsTagIdQueryable.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/IncludeAllQueryable.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/JobDescriptionQueryable.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/JobNameQueryable.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/NextRunTimeGreaterThanDateQueryable.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/SortableFieldsQueryable.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/LCU.Api.Internal.DispatcherLib.csproj create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Persistence/AppDbContext.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/BaseRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/CleanUpJobSettingRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/CleanUpJobTagRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/ExecutionMessageRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/ICleanUpJobSettingRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/ICleanUpJobTagRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IExecutionMessageRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IJobErrorRecipientRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IJobRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IJobTagRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/ITagRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IUnitOfWork.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IWorkItemExecutionRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IWorkItemRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/JobErrorRecipientRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/JobRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/JobTagRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/TagRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/UnitOfWork.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/WorkItemExecutionRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/WorkItemRepository.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/ActionBuilderService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/ActionService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/IActionBuilderService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/IActionService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/IRequestBuilderService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/RequestBuilderService.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/ArchiverApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/BlendApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DataCleanUpApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DialerApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DispatcherRequestBuilderConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DocumentIngestorApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DocumentTypeCleanupApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/FileOperationsApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/FileUpdaterApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/FtpApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/HistoricalDocumentIngestorApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/IncentiveManagerConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/MomBotApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/NoticeProcessorApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/NotificationApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/RealTimeAlertsApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/RemoteDepositCaptureApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/RepoApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/TemenosNotificationApiConfiguration.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/DependencyInjection.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/LCU.Api.Internal.DispatcherSharedLib.csproj create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Utilities/CronHelpers.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Utilities/HolidayHelpers.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/CleanUpJobSetting.sql create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/CleanUpJobTag.sql create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/ExecutionMessage.sql create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/ExecutionMessage_History.sql create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/Job.sql create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/JobErrorRecipient.sql create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/JobTag.sql create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/LCU.Api.Internal.DispatcherSql.refactorlog create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/LCU.Api.Internal.DispatcherSql.sqlproj create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/Tag.sql create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/WorkItem.sql create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/WorkItemExecution.sql create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/WorkItemExecution_History.sql create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/BuilderServiceTests/ActionBuilderServiceTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ControllerTests/IngestionControllerTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ControllerTests/JobControllerTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/HelperTests/CronHelpersTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/HelperTests/HolidayHelpersTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/LCU.Api.Internal.DispatcherTests.csproj create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/RepositoryTests/DbContextFromJson.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/RepositoryTests/JobRepositoryTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/RepositoryTests/WorkItemRepositoryTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/RepositoryTests/job_repo_basic.json create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/CleanUpJobSettingServiceTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/CleanUpJobTagServiceTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/BatchErroredJobsServiceTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/CleanUpJobsServiceTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/CompletionServiceTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/ExecutionServiceTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/IngestionServiceTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/JobServiceTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/JobTagServiceTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/SendErroredJobAlertServiceTests.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/TagServiceTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/WorkItemExecutionServiceTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/WorkItemServiceTest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/TemplatesTest/Test_BatchErroredJobsTemplate.html create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/TemplatesTest/Test_ErrorRowTemplate.html create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/Using.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/additional_test_args.runsettings create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Constants/ActionMessages.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Constants/JobSortableFields.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/CleanUpJobSetting.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/CleanUpJobTag.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/ExecutionMessage.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/Job.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/JobErrorRecipient.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/JobTag.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/Tag.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/WorkItem.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/WorkItemExecution.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/ActionType.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/DetermineOperationType.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/HttpMethodType.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/JobStatus.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/StatusType.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Helpers/Clone.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/LCU.Library.Internal.DispatcherModels.csproj create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/DispatcherReturnResponse.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Errorable.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Filter.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/JobDetails.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/JobErrorDetail.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/PagedResponse.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/BatchedErroredJobAlertRequest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/CopyJobRequest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/DetermineOperationRequest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/ErroredJobAlertRequest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/JobFailureRequest.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Results/CleanUpJobBatchResult.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Results/PagedResult.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/SortableField.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/StatusFilter.cs create mode 100644 LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/WorkItemDetails.cs create mode 100644 LCU.API.Internal.Dispatcher/README.md create mode 100644 LCU.API.Internal.Dispatcher/azdo_pipelines/azure-k8s-pipeline.yaml create mode 100644 LCU.API.Internal.Dispatcher/azdo_pipelines/azure-pipelines-feature-branches.yaml create mode 100644 LCU.API.Internal.Dispatcher/azdo_pipelines/azure-pipelines-prd.yaml create mode 100644 LCU.API.Internal.Dispatcher/azdo_pipelines/azure-pipelines-qa.yaml create mode 100644 LCU.API.Internal.Dispatcher/azdo_pipelines/azure-pipelines-uat.yaml create mode 100644 LCU.API.Internal.Dispatcher/azdo_pipelines/config-files/prd-pvars.yaml create mode 100644 LCU.API.Internal.Dispatcher/azdo_pipelines/config-files/qa-pvars.yaml create mode 100644 LCU.API.Internal.Dispatcher/azdo_pipelines/config-files/uat-pvars.yaml create mode 100644 LCU.API.Internal.Dispatcher/azure-pipelines.yml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/base/Deployment.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/base/Service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/base/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/base/secrets-provider-class.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/base/virtual-service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/keyvaults/normal/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/keyvaults/normal/set_secrets_deployment.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/keyvaults/normal/set_secrets_provider.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/completion/appsettings/appsettings.json create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/completion/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/completion/set_appsettings_deployment.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/determine-operation/appsettings/appsettings.json create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/determine-operation/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/determine-operation/set_appsettings_deployment.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/execution/appsettings/appsettings.json create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/execution/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/execution/set_appsettings_deployment.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-failure/appsettings/appsettings.json create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-failure/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-failure/set_appsettings_deployment.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-scheduler/appsettings/appsettings.json create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-scheduler/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-scheduler/set_appsettings_deployment.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/normal/appsettings/appsettings.json create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/normal/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/normal/set_appsettings_deployment.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/send-errored-job-alert/appsettings/appsettings.json create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/send-errored-job-alert/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/components/workloads/send-errored-job-alert/set_appsettings_deployment.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/local/common/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/local/common/set_appsettings_deployment.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/local/common/set_secrets_provider_class.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/local/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/local/normal/configmap.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/local/normal/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/local/normal/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/appsettings/appsettings.json create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/completion/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/completion/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/component/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/component/set_appsettings_deployment.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/component/set_secrets_provider_class.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/determine-operation/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/determine-operation/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/execution/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/execution/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/job-failure/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/job-failure/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/job-scheduler/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/job-scheduler/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/normal/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/normal/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/send-errored-job-alert/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/send-errored-job-alert/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/prd/set_configmap_deployment.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/appsettings/appsettings.json create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/completion/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/completion/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/component/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/component/set_appsettings_deployment.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/component/set_secrets_provider_class.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/determine-operation/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/determine-operation/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/execution/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/execution/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/job-failure/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/job-failure/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/job-scheduler/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/job-scheduler/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/normal/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/normal/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/send-errored-job-alert/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/send-errored-job-alert/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/qa/set_configmap_deployment.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/appsettings/appsettings.json create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/completion/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/completion/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/component/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/component/set_appsettings_deployment.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/component/set_secrets_provider_class.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/determine-operation/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/determine-operation/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/execution/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/execution/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/job-failure/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/job-failure/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/job-scheduler/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/job-scheduler/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/normal/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/normal/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/send-errored-job-alert/kustomization.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/send-errored-job-alert/set_virtual_service.yaml create mode 100644 LCU.API.Internal.Dispatcher/kustomize/overlays/uat/set_configmap_deployment.yaml diff --git a/LCU.API.Internal.Dispatcher/.gitignore b/LCU.API.Internal.Dispatcher/.gitignore new file mode 100644 index 0000000..3e759b7 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/.gitignore @@ -0,0 +1,330 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ diff --git a/LCU.API.Internal.Dispatcher/Dockerfile b/LCU.API.Internal.Dispatcher/Dockerfile new file mode 100644 index 0000000..0b99a87 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/Dockerfile @@ -0,0 +1,42 @@ +# .NET Core SDK +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS aspnetcore-build + +# Copy Projects +COPY LCU.Api.Internal.Dispatcher/LCU.Api.Internal.Dispatcher.csproj ./source/LCU.Api.Internal.Dispatcher/ +COPY LCU.Api.Internal.DispatcherLib/LCU.Api.Internal.DispatcherLib.csproj ./source/LCU.Api.Internal.DispatcherLib/ +COPY LCU.Api.Internal.DispatcherSharedLib/LCU.Api.Internal.DispatcherSharedLib.csproj ./source/LCU.Api.Internal.DispatcherSharedLib/ +COPY LCU.Library.Internal.DispatcherModels/LCU.Library.Internal.DispatcherModels.csproj ./source/LCU.Library.Internal.DispatcherModels/ + +# Install Credential Provider and set env variables to enable Nuget restore with auth +ARG PAT=bh2lq3jibktw67b2wdbqyfln7cd3je6lk52kijntpj43v6kimpgq +RUN curl -L https://raw.githubusercontent.com/Microsoft/artifacts-credprovider/master/helpers/installcredprovider.sh | sh +#RUN wget -qO- https://raw.githubusercontent.com/Microsoft/artifacts-credprovider/master/helpers/installcredprovider.sh | bash +ENV NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED true +ENV VSS_NUGET_EXTERNAL_FEED_ENDPOINTS "{\"endpointCredentials\": [{\"endpoint\":\"https://landmarkcu.pkgs.visualstudio.com/_packaging/LCU-Internal/nuget/v3/index.json\", \"password\":\"${PAT}\"}]}" + +# .NET Core Restore, use auth'd feed +RUN dotnet restore -s "https://landmarkcu.pkgs.visualstudio.com/_packaging/LCU-Internal/nuget/v3/index.json" -s "https://api.nuget.org/v3/index.json" ./source/LCU.Api.Internal.Dispatcher/LCU.Api.Internal.Dispatcher.csproj + +# Copy All Files +COPY . ./source/ + +FROM aspnetcore-build AS test +LABEL test=true +COPY LCU.Api.Internal.DispatcherTests/LCU.Api.Internal.DispatcherTests.csproj ./source/LCU.Api.Internal.DispatcherTests/ +RUN dotnet restore ./source/LCU.Api.Internal.DispatcherTests/LCU.Api.Internal.DispatcherTests.csproj +RUN dotnet test -c Release --results-directory /testresults --logger "trx;LogFileName=test_results.trx" ./source/LCU.Api.Internal.DispatcherTests/LCU.Api.Internal.DispatcherTests.csproj + +# .NET Core Publish +FROM aspnetcore-build as publish +RUN dotnet publish ./source/LCU.Api.Internal.Dispatcher/LCU.Api.Internal.Dispatcher.csproj -c Release -o /dist + +# ASP.NET Core +FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.20 +RUN apk add --no-cache icu-libs && apk add --no-cache tzdata; +ENV TZ America/Chicago +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV ASPNETCORE_ENVIRONMENT=Development + +WORKDIR /app +COPY --from=publish /dist . +ENTRYPOINT ["dotnet", "LCU.Api.Internal.Dispatcher.dll"] diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher.sln b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher.sln new file mode 100644 index 0000000..5558f80 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher.sln @@ -0,0 +1,66 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.Api.Internal.Dispatcher", "LCU.Api.Internal.Dispatcher\LCU.Api.Internal.Dispatcher.csproj", "{1C92A326-91A3-4904-B3EF-B766E10E7191}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.Api.Internal.DispatcherTests", "LCU.Api.Internal.DispatcherTests\LCU.Api.Internal.DispatcherTests.csproj", "{98503299-44F3-410A-B61F-36BC1706F132}" + ProjectSection(ProjectDependencies) = postProject + {1C92A326-91A3-4904-B3EF-B766E10E7191} = {1C92A326-91A3-4904-B3EF-B766E10E7191} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.Api.Internal.DispatcherLib", "LCU.Api.Internal.DispatcherLib\LCU.Api.Internal.DispatcherLib.csproj", "{0D3255ED-449B-4F11-861B-5C9479520ED5}" +EndProject +Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "LCU.Api.Internal.DispatcherSql", "LCU.Api.Internal.DispatcherSql\LCU.Api.Internal.DispatcherSql.sqlproj", "{B82ADFBD-F6BD-431A-88CE-5540190D225E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.Api.Internal.DispatcherSharedLib", "LCU.Api.Internal.DispatcherSharedLib\LCU.Api.Internal.DispatcherSharedLib.csproj", "{1A38E17A-65AD-4CB9-84AB-A733C80DCA11}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.Api.Internal.DispatcherIntegrationTests", "LCU.Api.Internal.DispatcherIntegrationTests\LCU.Api.Internal.DispatcherIntegrationTests.csproj", "{02AC7B60-3118-4D90-BDC6-654736BC82E8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.Library.Internal.DispatcherModels", "LCU.Library.Internal.DispatcherModels\LCU.Library.Internal.DispatcherModels.csproj", "{1B92770F-4729-4634-AD40-0E66B7F65432}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1C92A326-91A3-4904-B3EF-B766E10E7191}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C92A326-91A3-4904-B3EF-B766E10E7191}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C92A326-91A3-4904-B3EF-B766E10E7191}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C92A326-91A3-4904-B3EF-B766E10E7191}.Release|Any CPU.Build.0 = Release|Any CPU + {98503299-44F3-410A-B61F-36BC1706F132}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98503299-44F3-410A-B61F-36BC1706F132}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98503299-44F3-410A-B61F-36BC1706F132}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98503299-44F3-410A-B61F-36BC1706F132}.Release|Any CPU.Build.0 = Release|Any CPU + {0D3255ED-449B-4F11-861B-5C9479520ED5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D3255ED-449B-4F11-861B-5C9479520ED5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D3255ED-449B-4F11-861B-5C9479520ED5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D3255ED-449B-4F11-861B-5C9479520ED5}.Release|Any CPU.Build.0 = Release|Any CPU + {B82ADFBD-F6BD-431A-88CE-5540190D225E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B82ADFBD-F6BD-431A-88CE-5540190D225E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B82ADFBD-F6BD-431A-88CE-5540190D225E}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {B82ADFBD-F6BD-431A-88CE-5540190D225E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B82ADFBD-F6BD-431A-88CE-5540190D225E}.Release|Any CPU.Build.0 = Release|Any CPU + {B82ADFBD-F6BD-431A-88CE-5540190D225E}.Release|Any CPU.Deploy.0 = Release|Any CPU + {1A38E17A-65AD-4CB9-84AB-A733C80DCA11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A38E17A-65AD-4CB9-84AB-A733C80DCA11}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A38E17A-65AD-4CB9-84AB-A733C80DCA11}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A38E17A-65AD-4CB9-84AB-A733C80DCA11}.Release|Any CPU.Build.0 = Release|Any CPU + {02AC7B60-3118-4D90-BDC6-654736BC82E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {02AC7B60-3118-4D90-BDC6-654736BC82E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {02AC7B60-3118-4D90-BDC6-654736BC82E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {02AC7B60-3118-4D90-BDC6-654736BC82E8}.Release|Any CPU.Build.0 = Release|Any CPU + {1B92770F-4729-4634-AD40-0E66B7F65432}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B92770F-4729-4634-AD40-0E66B7F65432}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B92770F-4729-4634-AD40-0E66B7F65432}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B92770F-4729-4634-AD40-0E66B7F65432}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {56A51B7E-A90A-4BFF-85D1-19ECB769AF6A} + EndGlobalSection +EndGlobal diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Configurations/ApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Configurations/ApiConfiguration.cs new file mode 100644 index 0000000..0ce0044 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Configurations/ApiConfiguration.cs @@ -0,0 +1,26 @@ +using LCU.Library.Internal.DispatcherModels.Enums; +using System.Collections.Generic; + +namespace LCU.Api.Internal.Dispatcher.Configurations; + +public class ApiConfiguration +{ + public string AppName { get; set; } + public string SeqUrl { get; set; } + public string SeqApiKey { get; set; } + public string[] CorsAllowedOrigins { get; set; } + public string SchedulersCronSchedule { get; set; } + public string BatchErroredJobsCronSchedule { get; set; } + public int ProcessingLimitInMinutes { get; set; } + public int NumberOfWorkItemExecutions { get; set; } + public int NumberOfExecutionMessages { get; set; } + public string BatchErroredJobsTemplate { get; set; } + public string ErrorRowTemplate { get; set; } + public string BatchErroredJobsSubject { get; set; } + public string DefaultErrorRecipient { get; set; } + public string CleanUpJobsCronSchedule { get; set; } + public int CleanUpJobsDaysToDelete { get; set; } + public int DefaultDelayMilliseconds { get; set; } + public FeatureFlags FeatureFlags { get; set; } + public List JobStatusesToMonitorByTimeLimit { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Configurations/ConnectionsConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Configurations/ConnectionsConfiguration.cs new file mode 100644 index 0000000..f9962aa --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Configurations/ConnectionsConfiguration.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +namespace LCU.Api.Internal.Dispatcher.Configurations; + +public class ConnectionsConfiguration +{ + public Sql Sql { get; set; } +} + +public class Sql +{ + public string DispatcherDb { get; set; } + public SQLOptions SQLOptions { get; set; } +} + +public class SQLOptions +{ + public int MaxRetryCount { get; set; } + public TimeSpan MaxRetryDelay { get; set; } + public ICollection ErrorNumbersToAdd { get; set; } + public bool EnableDetailedErrors { get; set; } + public int CommandTimeout { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Configurations/FeatureFlags.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Configurations/FeatureFlags.cs new file mode 100644 index 0000000..afd471c --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Configurations/FeatureFlags.cs @@ -0,0 +1,13 @@ +namespace LCU.Api.Internal.Dispatcher.Configurations; + +public class FeatureFlags +{ + public bool RunCompletionConsumer { get; set; } + public bool RunExecutionConsumer { get; set; } + public bool RunJobSchedulerHostedService { get; set; } + public bool RunBatchErroredJobsHostedService { get; set; } + public bool RunCleanUpJobsHostedService { get; set; } + public bool RunDetermineOperationConsumer { get; set; } + public bool RunJobFailureConsumer { get; set; } + public bool RunSendErroredJobAlertConsumer { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Configurations/ReceiveEndpointConfigurations.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Configurations/ReceiveEndpointConfigurations.cs new file mode 100644 index 0000000..2930f67 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Configurations/ReceiveEndpointConfigurations.cs @@ -0,0 +1,11 @@ +using LCU.Library.Internal.LcuMassTransitModels; +namespace LCU.Api.Internal.Dispatcher.Configurations; + +public class ReceiveEndpointConfigurations +{ + public ReceiveEndpointConfiguration ExecutionReceiveEndpoint { get; set; } + public ReceiveEndpointConfiguration CompletionReceiveEndpoint { get; set; } + public ReceiveEndpointConfiguration DetermineOperationReceiveEndpoint { get; set; } + public ReceiveEndpointConfiguration JobFailureReceiveEndpoint { get; set; } + public ReceiveEndpointConfiguration SendErroredJobAlertReceiveEndpoint { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/CompletionConsumer.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/CompletionConsumer.cs new file mode 100644 index 0000000..01ee974 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/CompletionConsumer.cs @@ -0,0 +1,47 @@ +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.Logging.Performance; +using MassTransit; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using System.Collections.Generic; +using ApiResponse = LCU.Library.Internal.ApiResponse.Models.Response; +namespace LCU.Api.Internal.Dispatcher.Consumers; + +public class CompletionConsumer : IConsumer +{ + private readonly IServiceScopeFactory _scopeFactory; + + public CompletionConsumer(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task Consume(ConsumeContext context) + { + try + { + using var scope = _scopeFactory.CreateAsyncScope(); + var dispatcherReturnResponse = JsonConvert.DeserializeObject(context.Message.ResponsePayload); + var performanceMonitor = new PerformanceMonitor("DispatcherCompletionConsumerMonitor", + Guid.NewGuid()); + + await scope.ServiceProvider.GetRequiredService().HandleCompletionRequest(context.Message); + performanceMonitor.LogPerformance((new Dictionary(){ + {"WorkItemId", dispatcherReturnResponse.WorkItemId.ToString()} + })); + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Dispatcher Completion Failure. " + + $"The Dispatcher Completion Request is placed on LCU.Dispatcher.Completion_error."); + return; + } + catch (Exception exception) + { + Log.Fatal(exception, $"Unexpected Failure"); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/DetermineOperationConsumer.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/DetermineOperationConsumer.cs new file mode 100644 index 0000000..db2dd4e --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/DetermineOperationConsumer.cs @@ -0,0 +1,52 @@ +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.DispatcherModels.Models; +using LCU.Library.Internal.DispatcherModels.Models.Requests; +using LCU.Library.Internal.Logging.Performance; +using MassTransit; +using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; + +namespace LCU.Api.Internal.Dispatcher.Consumers +{ + public class DetermineOperationConsumer : IConsumer + { + private readonly IServiceScopeFactory _scopeFactory; + + public DetermineOperationConsumer(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task Consume(ConsumeContext context) + { + try + { + using var scope = _scopeFactory.CreateAsyncScope(); + var performanceMonitor = new PerformanceMonitor("DispatcherDetermineOperationConsumerMonitor", Guid.NewGuid()); + await scope.ServiceProvider.GetRequiredService().HandleDetermineOperationRequest(context.Message); + performanceMonitor.LogPerformance((new Dictionary(){ + {"JobId", context.Message.CurrentJob.JobId.ToString()}, + {"WorkItemId", context.Message.CurrentWorkItem.WorkItemId.ToString()}, + {"WorkItemExecutionId", context.Message.CurrentWorkItem?.CurrentWorkItemExecution?.WorkItemExecutionId.ToString()} + })); + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Dispatcher Determine Operation Failure. " + + $"The Dispatcher Determine Operation Request is placed on LCU.Dispatcher.DetermineOperation_error."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Unexpected Dispatcher Determine Operation Failure. " + + $"The Dispatcher Determine Operation Request is placed on LCU.Dispatcher.DetermineOperation_error."); + throw; + } + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/ExecutionConsumer.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/ExecutionConsumer.cs new file mode 100644 index 0000000..7d504d0 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/ExecutionConsumer.cs @@ -0,0 +1,47 @@ +using MassTransit; +using Microsoft.Extensions.DependencyInjection; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +using System.Collections.Generic; +using LCU.Library.Internal.Logging.Performance; +namespace LCU.Api.Internal.Dispatcher.Consumers; + +public class ExecutionConsumer : IConsumer +{ + private readonly IServiceScopeFactory _scopeFactory; + + public ExecutionConsumer(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task Consume(ConsumeContext context) + { + try + { + using var scope = _scopeFactory.CreateAsyncScope(); + var performanceMonitor = new PerformanceMonitor("DispatcherExecutionConsumerMonitor", Guid.NewGuid()); + await scope.ServiceProvider.GetRequiredService().HandleExecutionRequest(context.Message); + performanceMonitor.LogPerformance((new Dictionary(){ + {"WorkItemId", context.Message.WorkItemId.ToString()}, + {"WorkItemExecutionId", context.Message.WorkItemExecutionId.ToString()} + })); + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Dispatcher Execution Failure. " + + $"The Dispatcher Execution Request is placed on LCU.Dispatcher.Execution_error."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Unexpected Dispatcher Execution Failure. " + + $"The Dispatcher Execution Request is placed on LCU.Dispatcher.Execution_error."); + throw; + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/JobFailureConsumer.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/JobFailureConsumer.cs new file mode 100644 index 0000000..37f780a --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/JobFailureConsumer.cs @@ -0,0 +1,50 @@ +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.DispatcherModels.Models.Requests; +using LCU.Library.Internal.Logging.Performance; +using MassTransit; +using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; + +namespace LCU.Api.Internal.Dispatcher.Consumers +{ + public class JobFailureConsumer : IConsumer + { + private readonly IServiceScopeFactory _scopeFactory; + + public JobFailureConsumer(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task Consume(ConsumeContext context) + { + try + { + using var scope = _scopeFactory.CreateAsyncScope(); + var performanceMonitor = new PerformanceMonitor("DispatcherJobFailureConsumerMonitor", Guid.NewGuid()); + await scope.ServiceProvider.GetRequiredService().HandleJobFailureRequest(context.Message); + performanceMonitor.LogPerformance((new Dictionary(){ + {"JobId", context.Message.JobId.ToString()}, + {"WorkItemId", context.Message.WorkItemId.ToString()} + })); + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Dispatcher Job Failure. " + + $"The Dispatcher Job Failure Request is placed on LCU.Dispatcher.JobFailure_error."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Unexpected Dispatcher Job Failure. " + + $"The Dispatcher Job Failure Request is placed on LCU.Dispatcher.JobFailure_error."); + throw; + } + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/SendErroredJobAlertConsumer.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/SendErroredJobAlertConsumer.cs new file mode 100644 index 0000000..22cd89c --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Consumers/SendErroredJobAlertConsumer.cs @@ -0,0 +1,57 @@ +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.DispatcherModels.Models.Requests; +using LCU.Library.Internal.Logging.Performance; +using MassTransit; +using Microsoft.Extensions.DependencyInjection; +using System.Collections.Generic; + +namespace LCU.Api.Internal.Dispatcher.Consumers +{ + public class SendErroredJobAlertConsumer : IConsumer> + { + private readonly IServiceScopeFactory _scopeFactory; + + public SendErroredJobAlertConsumer(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task Consume(ConsumeContext> context) + { + try + { + using var scope = _scopeFactory.CreateAsyncScope(); + var performanceMonitor = new PerformanceMonitor("DispatcherBatchErroredJobsConsumerMonitor", Guid.NewGuid()); + + var batchedErroredJobAlertRequestlist = new List(); + for (int index = 0; index < context.Message.Length; index++) + { + ConsumeContext messageContext = context.Message[index]; + batchedErroredJobAlertRequestlist.Add(messageContext.Message); + } + + await scope.ServiceProvider.GetRequiredService().HandleSendingErroredJobAlerts(batchedErroredJobAlertRequestlist); + performanceMonitor.LogPerformance((new Dictionary(){ + {"NumberOfBatchedErrors", batchedErroredJobAlertRequestlist.Count().ToString()} + })); + } + catch (ServiceException serviceException) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(serviceException, $"Dispatcher Send Errored Job Alert. " + + $"The Dispatcher Send Errored Job Alert Request is placed on LCU.Dispatcher.SendErroredJobAlert_error."); + throw; + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Unexpected Dispatcher Send Errored Job Alert. " + + $"The Dispatcher Send Errored Job Alert Request is placed on LCU.Dispatcher.SendErroredJobAlert_error."); + throw; + } + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/CleanUpJobProcessController.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/CleanUpJobProcessController.cs new file mode 100644 index 0000000..16ddb70 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/CleanUpJobProcessController.cs @@ -0,0 +1,42 @@ +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.Threading; + +namespace LCU.Api.Internal.Dispatcher.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class CleanUpJobProcessController : ControllerBase +{ + private readonly ICleanUpJobsService _cleanUpJobsService; + + public CleanUpJobProcessController(ICleanUpJobsService cleanUpJobsService) + { + _cleanUpJobsService = cleanUpJobsService; + } + + [HttpPost("RunCleanUpJobProcessNow", Name = "RunCleanUpJobProcessNow")] + [ProducesResponseType(200)] + [ProducesResponseType(500)] + public async Task RunCleanUpJobNow() + { + try + { + Log.Information("Manually Kicked Off Dispatcher's Clean Up Job Hosted Service."); + var token = new CancellationToken(); + await _cleanUpJobsService.PerformCleanUpJobs(token); + + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/CleanUpJobSettingController.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/CleanUpJobSettingController.cs new file mode 100644 index 0000000..99585b6 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/CleanUpJobSettingController.cs @@ -0,0 +1,161 @@ +using LCU.Api.Internal.Dispatcher.Services; +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; + +namespace LCU.Api.Internal.Dispatcher.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class CleanUpJobSettingController : ControllerBase +{ + private readonly ICleanUpJobSettingService _cleanUpJobSettingService; + + public CleanUpJobSettingController(ICleanUpJobSettingService cleanUpJobSettingService) + { + _cleanUpJobSettingService = cleanUpJobSettingService; + } + + [HttpGet("GetAllCleanUpJobSetting", Name = "GetAllCleanUpJobSetting")] + [ProducesResponseType(typeof(IEnumerable), 200)] + [ProducesResponseType(204)] + [ProducesResponseType(500)] + public async Task Get() + { + try + { + var result = await _cleanUpJobSettingService.GetAsync(); + if (result == null || !result.Any()) + { + return NoContent(); + } + + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("GetByCleanUpJobSettingIdAsync", Name = "GetByCleanUpJobSettingIdAsync")] + [ProducesResponseType(typeof(CleanUpJobSetting), 200)] + [ProducesResponseType(204)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task GetByCleanUpJobSettingIdAsync(int cleanUpJobSettingId) + { + try + { + if (cleanUpJobSettingId <= 0) + { + return BadRequest("Invalid CleanUpJobSettingId."); + } + + var result = await _cleanUpJobSettingService.GetByCleanUpJobSettingIdAsync(cleanUpJobSettingId); + if (result == null) + { + return NoContent(); + } + + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost] + [ProducesResponseType(typeof(CleanUpJobSetting), 200)] + [ProducesResponseType(400)] + [ProducesResponseType(406)] + [ProducesResponseType(500)] + public async Task Post([FromBody] CleanUpJobSetting request) + { + try + { + var result = await _cleanUpJobSettingService.SaveAsync(request); + if (result == null) + { + return StatusCode(StatusCodes.Status406NotAcceptable); + } + + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPut()] + [ProducesResponseType(typeof(CleanUpJobSetting), 200)] + [ProducesResponseType(406)] + [ProducesResponseType(500)] + public async Task Put([FromBody] CleanUpJobSetting request) + { + try + { + var result = await _cleanUpJobSettingService.UpdateAsync(request); + if (result == null) + { + return StatusCode(StatusCodes.Status406NotAcceptable); + } + + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpDelete()] + [ProducesResponseType(200)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task Delete(int cleanUpJobSettingId) + { + try + { + if (cleanUpJobSettingId <= 0) + { + return BadRequest("Invalid CleanUpJobSettingId"); + } + + await _cleanUpJobSettingService.DeleteAsync(cleanUpJobSettingId); + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/CleanUpJobTagController.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/CleanUpJobTagController.cs new file mode 100644 index 0000000..f92ee4f --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/CleanUpJobTagController.cs @@ -0,0 +1,192 @@ +using LCU.Api.Internal.Dispatcher.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; + +namespace LCU.Api.Internal.Dispatcher.Controllers; + +[Route("api/[controller]")] +[ApiController] +public class CleanUpJobTagController : ControllerBase +{ + private readonly ICleanUpJobTagService _cleanUpJobTagService; + public CleanUpJobTagController(ICleanUpJobTagService cleanUpJobTagService) + { + _cleanUpJobTagService = cleanUpJobTagService; + } + + [HttpGet(Name = "GetAllCleanUpJobTag")] + [ProducesResponseType(typeof(IEnumerable), 200)] + [ProducesResponseType(204)] + [ProducesResponseType(500)] + public async Task Get() + { + try + { + var result = await _cleanUpJobTagService.GetAsync(); + if (result == null || !result.Any()) + { + return NoContent(); + } + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("GetByCleanUpJobTagIdAsync", Name = "GetByCleanUpJobTagIdAsync")] + [ProducesResponseType(typeof(CleanUpJobTag), 200)] + [ProducesResponseType(204)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task GetByCleanUpJobTagIdAsync(int cleanUpJobTagId) + { + try + { + if (cleanUpJobTagId <= 0) + { + return BadRequest("Invalid CleanUpJobTagIdId."); + } + + var result = await _cleanUpJobTagService.GetByCleanUpJobTagIdAsync(cleanUpJobTagId); + if (result == null) + { + return NoContent(); + } + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("GetTagsByCleanUpJobSettingIdAsync", Name = "GetTagsByCleanUpJobSettingIdAsync")] + [ProducesResponseType(typeof(IEnumerable), 200)] + [ProducesResponseType(204)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task GetByCleanUpJobSettingIdAsync(int cleanUpJobSettingId) + { + try + { + if (cleanUpJobSettingId <= 0) + { + return BadRequest("Invalid CleanUpJobSettingId."); + } + + var result = await _cleanUpJobTagService.GetByCleanUpJobSettingIdAsync(cleanUpJobSettingId); + if (result == null) + { + return NoContent(); + } + + return Ok(result); + } + catch (ArgumentNullException) + { + return StatusCode(StatusCodes.Status404NotFound); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost()] + [ProducesResponseType(typeof(CleanUpJobTag), 200)] + [ProducesResponseType(406)] + [ProducesResponseType(500)] + public async Task Post([FromBody] CleanUpJobTag request) + { + try + { + var result = await _cleanUpJobTagService.SaveAsync(request); + if (result == null) + { + return StatusCode(StatusCodes.Status406NotAcceptable); + } + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpDelete("DeleteByCleanUpJobTagId", Name = "DeleteByCleanUpJobTagId")] + [ProducesResponseType(200)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task Delete(int cleanUpJobTagId) + { + try + { + if (cleanUpJobTagId <= 0) + { + return BadRequest("Invalid CleanUpJobTagId"); + } + + await _cleanUpJobTagService.DeleteAsync(cleanUpJobTagId); + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpDelete("DeleteByCleanUpJobSettingId", Name = "DeleteByCleanUpJobSettingId")] + [ProducesResponseType(200)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task DeleteByCleanUpJobSettingId(int cleanUpJobSettingId) + { + try + { + if (cleanUpJobSettingId <= 0) + { + return BadRequest("Invalid CleanUpJobSettingId"); + } + + await _cleanUpJobTagService.DeleteByCleanUpJobSettingIdAsync(cleanUpJobSettingId); + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/ExecutionMessageController.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/ExecutionMessageController.cs new file mode 100644 index 0000000..6ddbf9d --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/ExecutionMessageController.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using LCU.Api.Internal.Dispatcher.Services; +namespace LCU.Api.Internal.Dispatcher.Controllers; + +[Route("[controller]")] +[ApiController] +public class ExecutionMessageController : ControllerBase +{ + private readonly IExecutionMessageService _executionMessageService; + public ExecutionMessageController(IExecutionMessageService executionMessageService) + { + _executionMessageService = executionMessageService; + } + + [HttpPost()] + [ProducesResponseType(typeof(List), 200)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task Post([FromBody] List request) + { + try + { + if (request == null || !request.Any()) + { + return BadRequest("Execution messages are required."); + } + + await _executionMessageService.SaveExecutionMessages(request); + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/HostedServicesController.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/HostedServicesController.cs new file mode 100644 index 0000000..26717f4 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/HostedServicesController.cs @@ -0,0 +1,163 @@ +using LCU.Api.Internal.Dispatcher.HostedServices; +using LCU.Library.Internal.ApiSecurity.Attributes; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace LCU.Api.Internal.Dispatcher.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class HostedServicesController : ControllerBase + { + private readonly ICleanUpJobsHostedService _cleanUpJobsHostedServiceStarter; + private readonly IBatchErroredJobsHostedService _batchErroredJobsServiceStarter; + private readonly IJobSchedulerHostedService _jobSchedulerHostedServiceStarter; + + public HostedServicesController(ICleanUpJobsHostedService cleanUpJobsHostedService, + IBatchErroredJobsHostedService batchErroredJobsServiceStarter, + IJobSchedulerHostedService jobSchedulerHostedServiceStarter) + { + _cleanUpJobsHostedServiceStarter = cleanUpJobsHostedService; + _batchErroredJobsServiceStarter = batchErroredJobsServiceStarter; + _jobSchedulerHostedServiceStarter = jobSchedulerHostedServiceStarter; + } + + [ApiKey] + [HttpPost("StartCleanUpJobs", Name = "StartCleanUpJobs")] + [ProducesResponseType(200)] + [ProducesResponseType(500)] + public async Task StartCleanUpJobs() + { + try + { + await _cleanUpJobsHostedServiceStarter.StartAsync(); + + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [ApiKey] + [HttpPost("StopCleanUpJobs", Name = "StopCleanUpJobs")] + [ProducesResponseType(200)] + [ProducesResponseType(500)] + public async Task StopCleanUpJobs() + { + try + { + await _cleanUpJobsHostedServiceStarter.StopAsync(); + + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [ApiKey] + [HttpPost("StartBatchErroredJobs", Name = "StartBatchErroredJobs")] + [ProducesResponseType(200)] + [ProducesResponseType(500)] + public async Task StartBatchErroredJobs() + { + try + { + await _batchErroredJobsServiceStarter.StartAsync(); + + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [ApiKey] + [HttpPost("StopBatchErroredJobs", Name = "StopBatchErroredJobs")] + [ProducesResponseType(200)] + [ProducesResponseType(500)] + public async Task StopBatchErroredJobs() + { + try + { + await _batchErroredJobsServiceStarter.StopAsync(); + + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [ApiKey] + [HttpPost("StartJobScheduler", Name = "StartJobScheduler")] + [ProducesResponseType(200)] + [ProducesResponseType(500)] + public async Task StartJobScheduler() + { + try + { + await _jobSchedulerHostedServiceStarter.StartAsync(); + + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [ApiKey] + [HttpPost("StopJobScheduler", Name = "StopJobScheduler")] + [ProducesResponseType(200)] + [ProducesResponseType(500)] + public async Task StopJobScheduler() + { + try + { + await _jobSchedulerHostedServiceStarter.StopAsync(); + + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/IngestionController.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/IngestionController.cs new file mode 100644 index 0000000..b179801 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/IngestionController.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using LCU.Api.Internal.Dispatcher.Extensions; +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +namespace LCU.Api.Internal.Dispatcher.Controllers; + +[Route("api/[controller]")] +[ApiController] +public class IngestionController : ControllerBase +{ + private readonly IIngestionService _ingestionService; + + public IngestionController(IIngestionService ingestionService) + { + _ingestionService = ingestionService; + } + + // POST api/Post + [HttpPost] + [ProducesResponseType(201)] + [ProducesResponseType(typeof(List), 400)] + [ProducesResponseType(500)] + public async Task Post([FromBody] Job request) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState.GetErrorMessages()); + } + + if (!request.WorkItems.Any()) + { + return BadRequest("Workitems are required."); + } + + await _ingestionService.HandleIngestionRequest(request); + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/JobController.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/JobController.cs new file mode 100644 index 0000000..dc9f188 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/JobController.cs @@ -0,0 +1,304 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using LCU.Api.Internal.Dispatcher.Services; +using LCU.Library.Internal.DispatcherModels.Models.Requests; + +namespace LCU.Api.Internal.Dispatcher.Controllers; + +[Route("api/[controller]")] +[ApiController] +public class JobController : ControllerBase +{ + private readonly IJobService _jobService; + public JobController(IJobService jobService) + { + _jobService = jobService; + } + + [HttpPost("GetFilteredJobs")] + [ProducesResponseType(typeof(PagedResponse), 200)] + [ProducesResponseType(500)] + public async Task GetFilteredJobs([FromBody] Filter filter) + { + try + { + var filterResult = await _jobService.GetFilteredJobs(filter); + var result = new PagedResponse(filterResult, filter); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet(Name = "GetAllJobs")] + [ProducesResponseType(typeof(IEnumerable), 200)] + [ProducesResponseType(500)] + public async Task Get() + { + try + { + var result = await _jobService.GetAsync(); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("GetInactiveJobs", Name = "GetInactiveJobs")] + [ProducesResponseType(typeof(IEnumerable), 200)] + [ProducesResponseType(500)] + public async Task GetInactive() + { + try + { + var result = await _jobService.GetInactiveAsync(); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("GetInactiveJobsExcludeSoftDeleted", Name = "GetInactiveJobsExcludeSoftDeleted")] + [ProducesResponseType(typeof(IEnumerable), 200)] + [ProducesResponseType(500)] + public async Task GetInactiveExcludeSoftDeleted() + { + try + { + var result = await _jobService.GetInactiveExcludeSoftDeletedTrackedAsync(); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("GetSoftDeletedJobs", Name = "GetSoftDeletedJobs")] + [ProducesResponseType(typeof(IEnumerable), 200)] + [ProducesResponseType(500)] + public async Task GetSoftDeleted(DateTime cutOffDate) + { + try + { + var result = await _jobService.GetSoftDeletedTrackedAsync(cutOffDate); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("GetJobByIdExcludingWorkItems", Name = "GetJobByIdExcludingWorkItems")] + [ProducesResponseType(typeof(Job), 200)] + [ProducesResponseType(500)] + public async Task GetJobByIdExcludingWorkItems(int jobId) + { + try + { + var result = await _jobService.GetByIdExcludingWorkItemsAsync(jobId); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPut("RunJobNow", Name = "RunJobNow")] + [ProducesResponseType(typeof(Job), 200)] + [ProducesResponseType(500)] + public async Task RunJobNow([FromBody] Job job) + { + try + { + var result = await _jobService.RunJobNowAsync(job); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPut("RunJobNowById", Name = "RunJobNowById")] + [ProducesResponseType(typeof(Job), 200)] + [ProducesResponseType(500)] + public async Task RunJobNowById(int id) + { + try + { + var result = await _jobService.RunJobNowByIdAsync(id); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPut("DeactivateJob", Name = "DeactivateJob")] + [ProducesResponseType(typeof(Job), 200)] + [ProducesResponseType(500)] + public async Task DeactivateJob([FromBody] Job job) + { + try + { + var result = await _jobService.DeactivateJobAsync(job); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPut("RescheduleJob", Name = "RescheduleJob")] + [ProducesResponseType(typeof(Job), 200)] + [ProducesResponseType(500)] + public async Task RescheduleJob([FromBody] Job job) + { + try + { + var result = await _jobService.RescheduleJobAsync(job); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPut("RemoveSoftDeleteById", Name = "RemoveSoftDeleteById")] + [ProducesResponseType(typeof(Job), 200)] + [ProducesResponseType(500)] + public async Task RemoveSoftDeleteById(int id) + { + try + { + var result = await _jobService.RemoveSoftDeleteById(id); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpDelete()] + [ProducesResponseType(200)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task Delete(int jobId) + { + try + { + if (jobId <= 0) + { + return BadRequest("Invalid Id"); + } + + await _jobService.DeleteAsync(jobId); + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost("Copy")] + [ProducesResponseType(typeof(Job), 200)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task Copy(CopyJobRequest request) + { + try + { + if (request == null) + { + return BadRequest("Request cannot be null"); + } + + var job = await _jobService.CopyJobAsync(request); + return Ok(job); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/JobTagController.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/JobTagController.cs new file mode 100644 index 0000000..516bea3 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/JobTagController.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using LCU.Api.Internal.Dispatcher.Services; +namespace LCU.Api.Internal.Dispatcher.Controllers; + +[Route("[controller]")] +[ApiController] +public class JobTagController : ControllerBase +{ + private readonly IJobTagService _jobTagService; + public JobTagController(IJobTagService jobTagService) + { + _jobTagService = jobTagService; + } + + [HttpGet] + [ProducesResponseType(typeof(IEnumerable), 200)] + [ProducesResponseType(500)] + public async Task Get() + { + try + { + var result = await _jobTagService.GetAsync(); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("GetByTagIdAsync")] + [ProducesResponseType(typeof(IEnumerable), 200)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task GetByTagIdAsync(int tagId) + { + try + { + if (tagId <= 0) + { + return BadRequest("Invalid TagId."); + } + + var result = await _jobTagService.GetByTagIdAsync(tagId); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost()] + [ProducesResponseType(typeof(JobTag), 200)] + [ProducesResponseType(500)] + public async Task Post([FromBody] JobTag request) + { + try + { + var result = await _jobTagService.SaveAsync(request); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpDelete()] + [ProducesResponseType(200)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task Delete(int jobTagId) + { + try + { + if (jobTagId <= 0) + { + return BadRequest("Invalid JobTagId"); + } + + await _jobTagService.Delete(jobTagId); + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/QueueCompletionController.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/QueueCompletionController.cs new file mode 100644 index 0000000..5831bcb --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/QueueCompletionController.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using LCU.Api.Internal.Dispatcher.Extensions; +using LCU.Api.Internal.Dispatcher.Services.QueueServices; +namespace LCU.Api.Internal.Dispatcher.Controllers; + +[Route("api/[controller]")] +[ApiController] +public class QueueCompletionController : ControllerBase +{ + private readonly IQueueCompletionService _queueCompletionService; + + public QueueCompletionController(IQueueCompletionService completionService) + { + _queueCompletionService = completionService; + } + + [HttpPost] + [ProducesResponseType(202)] + [ProducesResponseType(typeof(List), 400)] + [ProducesResponseType(500)] + public async Task Post([FromBody] DispatcherReturnResponse request) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState.GetErrorMessages()); + } + + await _queueCompletionService.QueueCompletion(request); + return Accepted(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/QueueExecutionController.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/QueueExecutionController.cs new file mode 100644 index 0000000..66671ad --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/QueueExecutionController.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using LCU.Api.Internal.Dispatcher.Extensions; +using LCU.Api.Internal.Dispatcher.Services.QueueServices; +namespace LCU.Api.Internal.Dispatcher.Controllers; + +[Route("api/[controller]")] +[ApiController] +public class QueueExecutionController : ControllerBase +{ + private readonly IQueueExecutionService _queueExecutionService; + + public QueueExecutionController(IQueueExecutionService executionService) + { + _queueExecutionService = executionService; + } + + [HttpPost("QueueRetryExecution")] + [ProducesResponseType(202)] + [ProducesResponseType(typeof(List), 400)] + [ProducesResponseType(500)] + public async Task QueueRetryExecutionController([FromBody] WorkItemExecution request) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState.GetErrorMessages()); + } + + await _queueExecutionService.QueueRetryExecution(request); + return Accepted(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/StatusController.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/StatusController.cs new file mode 100644 index 0000000..8efd7fc --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/StatusController.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc; +using LCU.Api.Internal.Dispatcher.Models; +namespace LCU.Api.Internal.Dispatcher.Controllers; + +[Route("api/[controller]")] +[ApiController] +public class StatusController : ControllerBase +{ + private readonly IEnvironmentView _environmentView; + + public StatusController(IEnvironmentView environmentView) + { + _environmentView = environmentView; + } + + [HttpGet()] + [ProducesResponseType(200)] + [ProducesResponseType(500)] + public IActionResult Get() + { + try + { + return Ok(_environmentView); + } + catch (Exception ex) + { + Log.Error(ex, "Failure to return status"); + return StatusCode((int)System.Net.HttpStatusCode.InternalServerError); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/TagController.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/TagController.cs new file mode 100644 index 0000000..04a7f30 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/TagController.cs @@ -0,0 +1,137 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using LCU.Api.Internal.Dispatcher.Services; +namespace LCU.Api.Internal.Dispatcher.Controllers; + +[Route("[controller]")] +[ApiController] +public class TagController : ControllerBase +{ + private readonly ITagService _tagService; + public TagController(ITagService tagService) + { + _tagService = tagService; + } + + [HttpGet] + [ProducesResponseType(typeof(IEnumerable), 200)] + [ProducesResponseType(500)] + public async Task Get() + { + try + { + var result = await _tagService.GetAsync(); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpGet("GetByIdAsync")] + [ProducesResponseType(typeof(Tag), 200)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task GetByIdAsync(int tagId) + { + try + { + if (tagId <= 0) + { + return BadRequest("Invalid TagId."); + } + + var result = await _tagService.GetByIdAsync(tagId); + return Ok(result); + } + catch (ArgumentNullException) + { + return StatusCode(StatusCodes.Status404NotFound); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPost()] + [ProducesResponseType(typeof(Tag), 200)] + [ProducesResponseType(500)] + public async Task Post([FromBody] Tag request) + { + try + { + var result = await _tagService.SaveAsync(request); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpPut()] + [ProducesResponseType(typeof(Tag), 200)] + [ProducesResponseType(500)] + public async Task Put([FromBody] Tag request) + { + try + { + var result = await _tagService.UpdateAsync(request); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + + [HttpDelete()] + [ProducesResponseType(200)] + [ProducesResponseType(400)] + [ProducesResponseType(500)] + public async Task Delete(int tagId) + { + try + { + if (tagId <= 0) + { + return BadRequest("Invalid TagId"); + } + + await _tagService.DeleteAsync(tagId); + return Ok(); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/WorkItemController.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/WorkItemController.cs new file mode 100644 index 0000000..654c91f --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Controllers/WorkItemController.cs @@ -0,0 +1,39 @@ +using LCU.Api.Internal.Dispatcher.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; + +namespace LCU.Api.Internal.Dispatcher.Controllers; + +[Route("api/[controller]")] +[ApiController] +public class WorkItemController : ControllerBase +{ + private readonly IWorkItemService _workItemService; + + public WorkItemController(IWorkItemService workItemService) + { + _workItemService = workItemService; + } + + [HttpPost("GetWorkItemsByJobId")] + [ProducesResponseType(typeof(IEnumerable), 200)] + [ProducesResponseType(500)] + public async Task GetWorkItemsByJobId(int jobId) + { + try + { + var result = await _workItemService.GetByJobIdWithExecutionsAsync(jobId); + return Ok(result); + } + catch (ServiceException) + { + return StatusCode(StatusCodes.Status500InternalServerError); + } + catch (Exception exception) + { + Log.Error(exception, "Unexpected Exception"); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/DataAccess/INotificationsApiDao.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/DataAccess/INotificationsApiDao.cs new file mode 100644 index 0000000..7adb303 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/DataAccess/INotificationsApiDao.cs @@ -0,0 +1,8 @@ +using LCU.Library.Internal.NotificationsModels.Models; + +namespace LCU.Api.Internal.Dispatcher.DataAccess; + +public interface INotificationsApiDao +{ + Task SendEmailAsync(EmailRequest request); +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/DataAccess/NotificationsApiDao.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/DataAccess/NotificationsApiDao.cs new file mode 100644 index 0000000..0025d79 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/DataAccess/NotificationsApiDao.cs @@ -0,0 +1,21 @@ +using LCU.Api.Internal.DispatcherSharedLib.Configurations; +using LCU.Library.Internal.LcuHttpClient.HttpRequest; +using LCU.Library.Internal.NotificationsModels.Models; +namespace LCU.Api.Internal.Dispatcher.DataAccess; + +public class NotificationsApiDao : INotificationsApiDao +{ + private readonly NotificationApiConfiguration _configuration; + private readonly IPostRequest _postRequest; + + public NotificationsApiDao(NotificationApiConfiguration configuration, IPostRequest postRequest) + { + _configuration = configuration; + _postRequest = postRequest; + } + + public async Task SendEmailAsync(EmailRequest request) + { + await _postRequest.Async(new Uri(_configuration.EmailEndpointUrl), request); + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Extensions/ModelStateMessages.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Extensions/ModelStateMessages.cs new file mode 100644 index 0000000..efa08c4 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Extensions/ModelStateMessages.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ModelBinding; +namespace LCU.Api.Internal.Dispatcher.Extensions; + +public static class ModelStateMessages +{ + public static List GetErrorMessages(this ModelStateDictionary dictionary) + { + return dictionary.SelectMany(m => m.Value.Errors) + .Select(m => m.ErrorMessage) + .ToList(); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/BatchErroredJobsHostedService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/BatchErroredJobsHostedService.cs new file mode 100644 index 0000000..e651ecc --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/BatchErroredJobsHostedService.cs @@ -0,0 +1,68 @@ +using System.Threading; +using Microsoft.Extensions.DependencyInjection; +using NCrontab; +using LCU.Api.Internal.DispatcherSharedLib.Utilities; +using LCU.Api.Internal.Dispatcher.Configurations; +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +using LCU.Library.Internal.LCUHostedService.LCUBackgroundService; +namespace LCU.Api.Internal.Dispatcher.HostedServices; + +public class BatchErroredJobsHostedService : LCUBackgroundService, IBatchErroredJobsHostedService +{ + private readonly ApiConfiguration _apiConfiguration; + private readonly IServiceScopeFactory _scopeFactory; + private readonly CrontabSchedule _batchErroredJobsSchedule; + private DateTime _nextScheduleRun; + public const string DefaultBatchErroredJobsCronPattern = "*/15 * * * *"; + private CancellationToken _cancellationToken; + + public BatchErroredJobsHostedService(IServiceScopeFactory scopeFactory, + ApiConfiguration apiConfiguration) + { + _scopeFactory = scopeFactory; + _apiConfiguration = apiConfiguration; + _batchErroredJobsSchedule = CronHelpers.SetCronSchedule(_apiConfiguration.BatchErroredJobsCronSchedule, + DefaultBatchErroredJobsCronPattern); + _nextScheduleRun = _batchErroredJobsSchedule.GetNextOccurrence(DateTime.Now); + _cancellationToken = new CancellationToken(); + } + + protected override async Task ExecuteAsync(CancellationToken cancellationToken) + { + if (!_apiConfiguration.FeatureFlags.RunBatchErroredJobsHostedService) + { + Log.Error("Batch Errored Jobs Hosted Service is disabled by feature flag. Service not started."); + return; + } + + Log.Debug("Dispatcher's Batch Errored Jobs Hosted Service is starting."); + try + { + while (!cancellationToken.IsCancellationRequested) + { + await Task.Delay(UntilNextExecution(), cancellationToken); // wait until next time + + using (var scope = _scopeFactory.CreateAsyncScope()) + { + Log.Debug("Running Batch Errored Jobs Service"); + await scope.ServiceProvider.GetRequiredService().PerformBatchErroredJobs(_cancellationToken); + } + + _nextScheduleRun = _batchErroredJobsSchedule.GetNextOccurrence(DateTime.Now); + } + } + catch(OperationCanceledException taskCanceledEx) + { + Log.Error(taskCanceledEx, $"Manual start/stop of Dispatcher's Batch Errored Jobs Hosted Service was initiated."); + } + catch (Exception exception) + { + Log.Error(exception, + "Error trying to startup main thread for Dispatcher's Batch Errored Jobs Hosted Service"); + } + + Log.Debug("Dispatcher's Batch Errored Jobs Hosted Service is stopping."); + } + + private int UntilNextExecution() => Math.Max(0, (int)_nextScheduleRun.Subtract(DateTime.Now).TotalMilliseconds); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/CleanUpJobsHostedService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/CleanUpJobsHostedService.cs new file mode 100644 index 0000000..cb354da --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/CleanUpJobsHostedService.cs @@ -0,0 +1,69 @@ +using LCU.Api.Internal.Dispatcher.Configurations; +using LCU.Api.Internal.DispatcherSharedLib.Utilities; +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +using Microsoft.Extensions.DependencyInjection; +using NCrontab; +using System.Threading; +using LCU.Library.Internal.LCUHostedService.LCUBackgroundService; + +namespace LCU.Api.Internal.Dispatcher.HostedServices; + +public class CleanUpJobsHostedService : LCUBackgroundService, ICleanUpJobsHostedService +{ + private readonly ApiConfiguration _apiConfiguration; + private readonly IServiceScopeFactory _scopeFactory; + private readonly CrontabSchedule _cleanUpJobSchedule; + private DateTime _nextScheduleRun; + public const string DefaultCleanUpJobCronPattern = "* 22 * * *"; + private CancellationToken _cancellationToken; + + public CleanUpJobsHostedService(IServiceScopeFactory scopeFactory, + ApiConfiguration apiConfiguration) + { + _scopeFactory = scopeFactory; + _apiConfiguration = apiConfiguration; + _cleanUpJobSchedule = CronHelpers.SetCronSchedule(_apiConfiguration.CleanUpJobsCronSchedule, + DefaultCleanUpJobCronPattern); + _nextScheduleRun = _cleanUpJobSchedule.GetNextOccurrence(DateTime.Now); + _cancellationToken = new CancellationToken(); + } + + protected override async Task ExecuteAsync(CancellationToken cancellationToken) + { + if (!_apiConfiguration.FeatureFlags.RunCleanUpJobsHostedService) + { + Log.Error("Clean Up Job Hosted Service is disabled by feature flag. Service not started."); + return; + } + + Log.Debug("Dispatcher's Clean Up Job Hosted Service is starting."); + try + { + while (!cancellationToken.IsCancellationRequested) + { + await Task.Delay(UntilNextExecution(), cancellationToken); // wait until next time + + using (var scope = _scopeFactory.CreateAsyncScope()) + { + Log.Debug("Running Clean Up Job Service"); + await scope.ServiceProvider.GetRequiredService().PerformCleanUpJobs(_cancellationToken); + } + + _nextScheduleRun = _cleanUpJobSchedule.GetNextOccurrence(DateTime.Now); + } + } + catch(OperationCanceledException taskCanceledEx) + { + Log.Error(taskCanceledEx, $"Manual start/stop of Dispatcher's Clean Up Job Hosted Service was initiated."); + } + catch (Exception exception) + { + Log.Error(exception, + "Error trying to startup main thread for Dispatcher's Clean Up Job Hosted Service"); + } + + Log.Debug("Dispatcher's Clean Up Job Hosted Service is stopping."); + } + + private int UntilNextExecution() => Math.Max(0, (int)_nextScheduleRun.Subtract(DateTime.Now).TotalMilliseconds); +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/IBatchErroredJobsHostedService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/IBatchErroredJobsHostedService.cs new file mode 100644 index 0000000..51eed7f --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/IBatchErroredJobsHostedService.cs @@ -0,0 +1,8 @@ +using LCU.Library.Internal.LCUHostedService.LCUBackgroundService; + +namespace LCU.Api.Internal.Dispatcher.HostedServices +{ + public interface IBatchErroredJobsHostedService : ILCUBackgroundService + { + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/ICleanUpJobsHostedService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/ICleanUpJobsHostedService.cs new file mode 100644 index 0000000..fd4ae12 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/ICleanUpJobsHostedService.cs @@ -0,0 +1,8 @@ +using LCU.Library.Internal.LCUHostedService.LCUBackgroundService; + +namespace LCU.Api.Internal.Dispatcher.HostedServices +{ + public interface ICleanUpJobsHostedService : ILCUBackgroundService + { + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/IJobSchedulerHostedService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/IJobSchedulerHostedService.cs new file mode 100644 index 0000000..fe2b1dc --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/IJobSchedulerHostedService.cs @@ -0,0 +1,8 @@ +using LCU.Library.Internal.LCUHostedService.LCUBackgroundService; + +namespace LCU.Api.Internal.Dispatcher.HostedServices +{ + public interface IJobSchedulerHostedService : ILCUBackgroundService + { + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/JobSchedulerHostedService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/JobSchedulerHostedService.cs new file mode 100644 index 0000000..5141397 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/HostedServices/JobSchedulerHostedService.cs @@ -0,0 +1,58 @@ +using System.Threading; +using Microsoft.Extensions.DependencyInjection; +using NCrontab; +using LCU.Api.Internal.DispatcherSharedLib.Utilities; +using LCU.Api.Internal.Dispatcher.Configurations; +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +using LCU.Library.Internal.LCUHostedService.LCUBackgroundService; +namespace LCU.Api.Internal.Dispatcher.HostedServices; + +public class JobSchedulerHostedService : LCUBackgroundService, IJobSchedulerHostedService +{ + private readonly ApiConfiguration _apiConfiguration; + private readonly IServiceScopeFactory _scopeFactory; + private CancellationToken _cancellationToken; + + public JobSchedulerHostedService(IServiceScopeFactory scopeFactory, + ApiConfiguration apiConfiguration) + { + _scopeFactory = scopeFactory; + _apiConfiguration = apiConfiguration; + _cancellationToken = new CancellationToken(); + } + + protected override async Task ExecuteAsync(CancellationToken cancellationToken) + { + if (!_apiConfiguration.FeatureFlags.RunJobSchedulerHostedService) + { + Log.Error("Job Scheduler Hosted Service is disabled by feature flag. Service not started."); + return; + } + + Log.Debug("Dispatcher's Job Schedule Hosted Service is starting."); + try + { + while (!cancellationToken.IsCancellationRequested) + { + await Task.Delay(_apiConfiguration.DefaultDelayMilliseconds, cancellationToken); // wait until next time + + using (var scope = _scopeFactory.CreateAsyncScope()) + { + Log.Debug("Running Job Schedule Service"); + await scope.ServiceProvider.GetRequiredService().PerformSchedule(_cancellationToken); + } + } + } + catch(OperationCanceledException taskCanceledEx) + { + Log.Error(taskCanceledEx, $"Manual start/stop of Dispatcher's Job Scheduler Hosted Service was initiated."); + } + catch (Exception exception) + { + Log.Error(exception, + "Error trying to startup main thread for Dispatcher's Job Schedule Hosted Service"); + } + + Log.Debug("Dispatcher's Job Schedule Hosted Service is stopping."); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/LCU.Api.Internal.Dispatcher.csproj b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/LCU.Api.Internal.Dispatcher.csproj new file mode 100644 index 0000000..af42fbe --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/LCU.Api.Internal.Dispatcher.csproj @@ -0,0 +1,96 @@ + + + + net8.0 + InProcess + 2.8.1 + + 1.1.0 - Added NotificationsApi EmailRequest + 1.2.0 - Added FileOperations CreateDirectory and RenameDirectory + 1.3.0 - Added required end points and methods to handle Job Run now, Deactivation, Reschedule and Delete. + 1.4.0 - Fix to always copy HTML Templates for Error Monitoring on build. + 1.4.1 - Fix scheduler for Error Monitoring + 1.5.0 - Addition of the JobErrorRecipient and functionality. + 1.6.0 - Add Dynamic Zip and UnzipFileRequest + 1.7.0 - Add ArchiveRequest + 1.8.0 - Add Encrypt/Decrypt and SucceedOnError to Move/Copy/Rename/Delete File + 1.9.0 - Upgrade to .net 6, upgrade to use new apiresponse, and some refactoring + 2.0.0 - No longer Queueing Ingestion, removal of QueueLib, and clean up to be fully using MassTransit + 2.1.0 - Added DocumentIngestor IngestorRequest + 2.2.0 - Added DocumentIngestor IngestorPdfPartitionerRequest + 2.3.0 - Added MemberDocumentNotificationRequest Workitem + 2.4.0 - Added RemoteDepositCaptureXmlRequest Workitem and updated DocumentIngestor models + 2.5.0 - Added DeleteDirectoryRequest Workitem and updated DocumentIngestor models + 2.6.0 - Added UpdatePersonForCheckHoldRequest and DataCleanup Workitems + 2.7.0 - Update FileOperations and FTP Api nuget for additions to SFTP upload and modify file + 2.7.1 - Added HistoricalDocumentIngestor, HistoricalDocumentBatchIngestion, and HistoricalIngestorPdfPartitioner WorkItems + 2.8.0 - Added ExtractRequest and ProcessingEmailRequest WorkItems from NoticeProcessor API + 2.8.1 - Use new ActionBuilder and ActionService classes and other Refactors. + + + 1.1.0 - Added NotificationsApi EmailRequest + 1.2.0 - Added FileOperations CreateDirectory and RenameDirectory + 1.3.0 - Added required end points and methods to handle Job Run now, Deactivation, Reschedule and Delete. + 1.4.0 - Fix to always copy HTML Templates for Error Monitoring on build. + 1.4.1 - Fix scheduler for Error Monitoring. + 1.5.0 - Addition of the JobErrorRecipient and functionality. + 1.6.0 - Add Dynamic Zip and UnzipFileRequest + 1.7.0 - Add ArchiveRequest + 1.8.0 - Add Encrypt/Decrypt and SucceedOnError to Move/Copy/Rename/Delete File + 1.9.0 - Upgrade to .net 6, upgrade to use new apiresponse, and some refactoring + 2.0.0 - No longer Queueing Ingestion, removal of QueueLib, and clean up to be fully using MassTransit + 2.1.0 - Added DocumentIngestor IngestorRequest + 2.2.0 - Added DocumentIngestor IngestorPdfPartitionerRequest + 2.3.0 - Added MemberDocumentNotificationRequest Workitem + 2.4.0 - Added RemoteDepositCaptureXmlRequest Workitem and updated DocumentIngestor models + 2.5.0 - Added DeleteDirectoryRequest Workitem and updated DocumentIngestor models + 2.6.0 - Added UpdatePersonForCheckHoldRequest and DataCleanup Workitems + 2.7.0 - Update FileOperations and FTP Api nuget for additions to SFTP upload and modify file + 2.7.1 - Added HistoricalDocumentIngestor, HistoricalDocumentBatchIngestion, and HistoricalIngestorPdfPartitioner WorkItems + 2.8.0 - Added ExtractRequest and ProcessingEmailRequest WorkItems from NoticeProcessor API + 2.8.1 - Use new ActionBuilder and ActionService classes and other Refactors. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + + diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Models/EnvironmentView.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Models/EnvironmentView.cs new file mode 100644 index 0000000..69d8843 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Models/EnvironmentView.cs @@ -0,0 +1,30 @@ +using System; +using System.Reflection; +using Microsoft.AspNetCore.Hosting; +namespace LCU.Api.Internal.Dispatcher.Models; + +public class EnvironmentView : IEnvironmentView +{ + public string Application => _env.ApplicationName; + public DateTime CurrentDate => DateTime.Now; + public string BuildVersion => Assembly.GetExecutingAssembly().GetName().Version.ToString(); + public DateTime BuildDate => AssemblyBuildDate(); + public string Environment => _env.EnvironmentName; + private readonly IWebHostEnvironment _env; + + public EnvironmentView(IWebHostEnvironment env) + { + _env = env; + } + + public static DateTime AssemblyBuildDate() + { + //https://stackoverflow.com/a/804310/266594 + System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly(); + System.IO.FileInfo fileInfo = new System.IO.FileInfo(assembly.Location); + DateTime lastModified = fileInfo.LastWriteTime; + + return lastModified; + } + +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Models/IEnvironmentView.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Models/IEnvironmentView.cs new file mode 100644 index 0000000..971e3b7 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Models/IEnvironmentView.cs @@ -0,0 +1,11 @@ +using System; +namespace LCU.Api.Internal.Dispatcher.Models; + +public interface IEnvironmentView +{ + string Application { get; } + DateTime CurrentDate { get; } + string BuildVersion { get; } + DateTime BuildDate { get; } + string Environment { get; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Program.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Program.cs new file mode 100644 index 0000000..ba050ec --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Program.cs @@ -0,0 +1,396 @@ +using System.IO; +using System.Reflection; +using Microsoft.AspNetCore.Builder; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.OpenApi.Models; +using Newtonsoft.Json; +using MassTransit; +using LCU.Library.Internal.Configuration; +using LCU.Library.Internal.LcuMassTransit; +using LCU.Library.Internal.LcuMassTransitModels; +using LCU.Library.Internal.Logging; +using LCU.Library.Internal.Logging.Middleware; +using LCU.Library.Internal.Logging.Models; +using LCU.Api.Internal.Dispatcher.Configurations; +using LCU.Api.Internal.Dispatcher.Consumers; +using LCU.Api.Internal.Dispatcher.HostedServices; +using LCU.Api.Internal.Dispatcher.Models; +using LCU.Api.Internal.Dispatcher.Services; +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +using LCU.Api.Internal.Dispatcher.Services.QueueServices; +using LCU.Api.Internal.DispatcherLib.Persistence; +using LCU.Api.Internal.DispatcherLib.Repositories; +using LCU.Api.Internal.DispatcherSharedLib; +using System.Collections.Generic; +using LCU.Library.Internal.ApiSecurity.Constants; +using LCU.Library.Internal.ApiSecurity; +using LCU.Library.Internal.LcuHttpClient; +using LCU.Api.Internal.Dispatcher.DataAccess; + +string MyAllowSpecificOrigins = "_myAllowSpecificOrigins"; +ApiConfiguration _apiConfiguration; + +var builder = BuildBuilder(args); +BuildServicesAndConfigurations(builder); +BuildAppAndRun(builder); + +WebApplicationBuilder BuildBuilder(string[] args) +{ + var builder = WebApplication.CreateBuilder(args); + builder.Configuration.AddJsonFile("/app/config/environment-appsettings/appsettings.json", optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile("/app/config/workload-appsettings/appsettings.json", optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile("/vault/secrets/dispatcher-api-database-connection.json", optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile("/vault/secrets/dispatcher-api-seq-api-key.json", optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile("/vault/secrets/dispatcher-api-rabbitmq-username.json", optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile("/vault/secrets/dispatcher-api-rabbitmq-password.json", optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile("/vault/secrets/dispatcher-api-external-api-key-set.json", optional: true, reloadOnChange: true); + builder.Host.UseSerilog(); + return builder; +} + +void BuildServicesAndConfigurations(WebApplicationBuilder builder) +{ + var services = builder.Services; + var configuration = builder.Configuration; + services.AddControllers(); + services.AddEndpointsApiExplorer(); + services.AddSwaggerGen(); + services.AddHttpClient(); + + _apiConfiguration = configuration.Bind(services, "Primary"); + + var connectionsConfiguration = configuration.Bind(services); + + var loggerConfiguration = new LoggingConfiguration(_apiConfiguration.AppName, + _apiConfiguration.SeqApiKey, + _apiConfiguration.SeqUrl); + var lcuLogging = new LcuLogging(loggerConfiguration); + lcuLogging.UseLcuLogging(); + + var connectionConfiguration = MassTransitConfigurationSetUp.AddConnectionConfiguration(ref services, configuration); + //debugging + Console.WriteLine(JsonConvert.SerializeObject(connectionConfiguration)); + + var receiveEndpointConfigurations = new ReceiveEndpointConfigurations() + { + ExecutionReceiveEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, + "ExecutionReceiveEndpoint"), + CompletionReceiveEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, + "CompletionReceiveEndpoint"), + DetermineOperationReceiveEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, + "DetermineOperationReceiveEndpoint"), + JobFailureReceiveEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, + "JobFailureReceiveEndpoint"), + SendErroredJobAlertReceiveEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, + "SendErroredJobAlertReceiveEndpoint") + }; + services.AddSingleton(receiveEndpointConfigurations); + //debugging + Console.WriteLine(JsonConvert.SerializeObject(receiveEndpointConfigurations)); + + services.AddCors(options => + { + options.AddPolicy(MyAllowSpecificOrigins, + builder => + { + builder.WithOrigins( + _apiConfiguration.CorsAllowedOrigins + ).AllowAnyHeader().AllowAnyMethod(); + }); + }); + + services.AddDbContextPool(options => + { + options.UseSqlServer( + connectionsConfiguration.Sql.DispatcherDb, + sqlServerOptionsAction: sqlOptions => + { + sqlOptions.EnableRetryOnFailure( + maxRetryCount: connectionsConfiguration.Sql.SQLOptions.MaxRetryCount, + maxRetryDelay: connectionsConfiguration.Sql.SQLOptions.MaxRetryDelay, + errorNumbersToAdd: connectionsConfiguration.Sql.SQLOptions.ErrorNumbersToAdd + ); + sqlOptions.CommandTimeout(connectionsConfiguration.Sql.SQLOptions.CommandTimeout); + } + ); + options.EnableDetailedErrors(connectionsConfiguration.Sql.SQLOptions.EnableDetailedErrors); + }); + + AddMassTransitServices(ref services, connectionConfiguration, receiveEndpointConfigurations, _apiConfiguration); + services.AddDispatcherServices(configuration); + services.AddLCUHttpServices(); + AddServices(services); + AddRepositories(services); + AddHostedServices(services); + + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = $"{_apiConfiguration.AppName}", Version = "v1" }); + + //Add Security Entry Fields In Swagger + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Id = HeaderKeys.ApiKey, // Must match the "AddSecurityDefinition" + Type = ReferenceType.SecurityScheme + } + }, + new List() + } + }); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Id = HeaderKeys.Client, // Must match the "AddSecurityDefinition" + Type = ReferenceType.SecurityScheme + } + }, + new List() + } + }); + + c.AddSecurityDefinition(HeaderKeys.ApiKey, + new OpenApiSecurityScheme + { + Description = HeaderKeys.ApiKey, + In = ParameterLocation.Header, + Name = HeaderKeys.ApiKey, + Type = SecuritySchemeType.ApiKey, + Scheme = "apikey" + }); + + c.AddSecurityDefinition(HeaderKeys.Client, + new OpenApiSecurityScheme + { + Description = HeaderKeys.Client, + In = ParameterLocation.Header, + Name = HeaderKeys.Client, + Type = SecuritySchemeType.ApiKey, + Scheme = "apikey" + }); + }); + + builder.Services.AddApiKeySecurity(builder.Configuration); +} + +void AddMassTransitServices(ref IServiceCollection services, + ConnectionConfiguration connectionConfiguration, + ReceiveEndpointConfigurations receiveEndpointConfigurations, + ApiConfiguration apiConfiguration) +{ + services.AddMassTransit(x => + { + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + x.AddConsumer(); + + x.UsingRabbitMq((context, cfg) => + { + cfg.Host(connectionConfiguration.HostUrl, "/", host => + { + host.Username(connectionConfiguration.UserName); + host.Password(connectionConfiguration.Password); + }); + + if (apiConfiguration.FeatureFlags.RunExecutionConsumer) + { + cfg.ReceiveEndpoint(receiveEndpointConfigurations.ExecutionReceiveEndpoint.ReceiveEndpoint, e => + { + e.ConfigureConsumer(context); + e.SetQuorumQueue(); + e.PrefetchCount = receiveEndpointConfigurations.CompletionReceiveEndpoint.PrefetchLimit; + }); + } + + if (apiConfiguration.FeatureFlags.RunCompletionConsumer) + { + cfg.ReceiveEndpoint(receiveEndpointConfigurations.CompletionReceiveEndpoint.ReceiveEndpoint, e => + { + e.ConfigureConsumer(context); + e.SetQuorumQueue(); + e.PrefetchCount = receiveEndpointConfigurations.CompletionReceiveEndpoint.PrefetchLimit; + }); + } + + if (apiConfiguration.FeatureFlags.RunDetermineOperationConsumer) + { + cfg.ReceiveEndpoint(receiveEndpointConfigurations.DetermineOperationReceiveEndpoint.ReceiveEndpoint, e => + { + e.ConfigureConsumer(context); + e.SetQuorumQueue(); + e.PrefetchCount = receiveEndpointConfigurations.DetermineOperationReceiveEndpoint.PrefetchLimit; + }); + } + + if (apiConfiguration.FeatureFlags.RunJobFailureConsumer) + { + cfg.ReceiveEndpoint(receiveEndpointConfigurations.JobFailureReceiveEndpoint.ReceiveEndpoint, e => + { + e.ConfigureConsumer(context); + e.SetQuorumQueue(); + e.PrefetchCount = receiveEndpointConfigurations.JobFailureReceiveEndpoint.PrefetchLimit; + }); + } + + if (apiConfiguration.FeatureFlags.RunSendErroredJobAlertConsumer) + { + cfg.ReceiveEndpoint(receiveEndpointConfigurations.SendErroredJobAlertReceiveEndpoint.ReceiveEndpoint, e => + { + e.ConfigureConsumer(context, cons => + { + cons.Options(options => options + .SetMessageLimit(200) + .SetTimeLimit(5000) + .SetConcurrencyLimit(1)); + }); + e.SetQuorumQueue(); + e.PrefetchCount = receiveEndpointConfigurations.SendErroredJobAlertReceiveEndpoint.PrefetchLimit; + }); + } + }); + }); + services.AddMassTransitHostedService(); +} + +void AddServices(IServiceCollection services) +{ + services.AddScoped(); + + AddDispatcherOperationServices(services); + AddQueueServices(services); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); +} + +void AddDispatcherOperationServices(IServiceCollection services) +{ + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); +} + +void AddQueueServices(IServiceCollection services) +{ + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); +} + +void AddRepositories(IServiceCollection services) +{ + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); +} + +void AddHostedServices(IServiceCollection services) +{ + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); +} + +void BuildAppAndRun(WebApplicationBuilder builder) +{ + var app = builder.Build(); + + if (_apiConfiguration.FeatureFlags.RunCleanUpJobsHostedService) + { + var service = app.Services.GetRequiredService(); + service.StartAsync().Wait(); + } + + if (_apiConfiguration.FeatureFlags.RunJobSchedulerHostedService) + { + var service = app.Services.GetRequiredService(); + service.StartAsync().Wait(); + } + + if (_apiConfiguration.FeatureFlags.RunBatchErroredJobsHostedService) + { + var service = app.Services.GetRequiredService(); + service.StartAsync().Wait(); + } + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseSwagger(c => + { + c.PreSerializeFilters.Add((swaggerDoc, request) => + { + const string prefixHeader = "X-Forwarded-Prefix"; + if (request.Headers.ContainsKey(prefixHeader)) return; + + var serverUrl = request.Headers[prefixHeader]; + if (!String.IsNullOrWhiteSpace(serverUrl)) + { + swaggerDoc.Servers = new List() + { + new() { Description = serverUrl, Url = serverUrl } + }; + } + }); + }); + app.UseSwaggerUI(c => + { + c.RoutePrefix = "swagger"; + c.SwaggerEndpoint("v1/swagger.json", "LCU.Api.Internal.Dispatcher v1"); + }); + app.UseMiddleware(); + app.UseSerilogRequestLogging(options => + { + options.EnrichDiagnosticContext = RequestResponseLoggingMiddleware.RequestDataParser.EnrichFromRequest; + }); + + app.UseRouting(); + app.UseCors(MyAllowSpecificOrigins); + app.UseHttpsRedirection(); + app.UseAuthorization(); + app.MapControllers(); + app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); + + app.Run(); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/CleanUpJobSettingService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/CleanUpJobSettingService.cs new file mode 100644 index 0000000..45552ff --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/CleanUpJobSettingService.cs @@ -0,0 +1,114 @@ +using LCU.Api.Internal.DispatcherLib.Repositories; +using Microsoft.Data.SqlClient; +using System.Collections.Generic; + +namespace LCU.Api.Internal.Dispatcher.Services +{ + public class CleanUpJobSettingService : ICleanUpJobSettingService + { + private readonly ICleanUpJobSettingRepository _cleanUpJobSettingRepository; + private readonly IUnitOfWork _unitOfWork; + + public CleanUpJobSettingService(IUnitOfWork unitOfWork, + ICleanUpJobSettingRepository cleanUpJobSettingRepository) + { + _unitOfWork = unitOfWork; + _cleanUpJobSettingRepository = cleanUpJobSettingRepository; + } + + public async Task> GetAsync() + { + try + { + return await _cleanUpJobSettingRepository.ListAsync(); + } + catch (Exception exception) + { + Log.Error(exception, "Failure retrieving CleanUpJobSetting."); + throw new ServiceException("Failure retrieving CleanUpJobSetting."); + } + } + + public async Task GetByCleanUpJobSettingIdAsync(int cleanUpJobSettingId) + { + try + { + return await _cleanUpJobSettingRepository.FindByCleanUpJobSettingIdAsync(cleanUpJobSettingId); + } + catch (ArgumentNullException exception) + { + Log.Error(exception, $"Failure getting dispatcher Clean Up Job Settings by Id. CleanUpJobSettingId was null"); + return null; + } + catch (Exception exception) + { + Log.Error(exception, "Failure retrieving dispatcher Clean Up Job Settings. CleanUpJobSettingId: {@cleanUpJobSettingId}", cleanUpJobSettingId); + throw new ServiceException($"Failure retrieving dispatcher Clean Up Job Settings. CleanUpJobSettingId: {cleanUpJobSettingId}"); + } + } + + public async Task SaveAsync(CleanUpJobSetting request) + { + try + { + var result = await _cleanUpJobSettingRepository.AddAsync(request); + await _unitOfWork.CompleteAsync(); + return result; + } + catch (Exception exception) + { + Log.Error(exception, "Failure saving dispatcher Clean Up Job Settings. CleanUpJobSettingId: {@request.CleanUpJobSettingId}", request.CleanUpJobSettingId); + throw new ServiceException($"Failure saving dispatcher Clean Up Job Settings. CleanUpJobSettingId: {request.CleanUpJobSettingId}"); + } + } + + public async Task UpdateAsync(CleanUpJobSetting request) + { + try + { + var existing = await GetByCleanUpJobSettingIdAsync(request.CleanUpJobSettingId); + if(existing == null) + { + return null; + } + + existing.CompletionDaysPast = request.CompletionDaysPast; + _cleanUpJobSettingRepository.Update(existing); + await _unitOfWork.CompleteAsync(); + + return existing; + } + catch (SqlException sqlException) + { + Log.Error(sqlException, "SQL Failure updating dispatcher Clean Up Job Settings. CleanUpJobSettingId: {@request.CleanUpJobSettingId}", request.CleanUpJobSettingId); + throw new ServiceException($"SQL Failure updating dispatcher Clean Up Job Settings. CleanUpJobSettingId: {request.CleanUpJobSettingId}"); + } + catch (Exception exception) + { + Log.Error(exception, "SQL Failure updating dispatcher Clean Up Job Settings. CleanUpJobSettingId: {@request.CleanUpJobSettingId}", request.CleanUpJobSettingId); + throw new ServiceException($"SQL Failure updating dispatcher Clean Up Job Settings. CleanUpJobSettingId: {request.CleanUpJobSettingId}"); + } + } + + public async Task DeleteAsync(int cleanUpJobSettingId) + { + try + { + var cleanUpJobSetting = await _cleanUpJobSettingRepository.FindByCleanUpJobSettingIdAsync(cleanUpJobSettingId); + ArgumentNullException.ThrowIfNull(cleanUpJobSetting); + _cleanUpJobSettingRepository.Remove(cleanUpJobSetting); + await _unitOfWork.CompleteAsync(); + } + catch (SqlException sqlException) + { + Log.Error(sqlException, "SQL Failure removing dispatcher Clean Up Job Settings. CleanUpJobSettingId: {@cleanUpJobSettingId}", cleanUpJobSettingId); + throw new ServiceException($"SQL Failure removing dispatcher Clean Up Job Settings. CleanUpJobSettingId: {cleanUpJobSettingId}"); + } + catch (Exception exception) + { + Log.Error(exception, "Failure removing dispatcher Clean Up Job Settings. CleanUpJobSettingId: {@cleanUpJobSettingId}", cleanUpJobSettingId); + throw new ServiceException($"Failure removing dispatcher Clean Up Job Settings. CleanUpJobSettingId: {cleanUpJobSettingId}"); + } + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/CleanUpJobTagService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/CleanUpJobTagService.cs new file mode 100644 index 0000000..338783c --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/CleanUpJobTagService.cs @@ -0,0 +1,135 @@ +using LCU.Api.Internal.DispatcherLib.Repositories; +using Microsoft.Data.SqlClient; +using System.Collections.Generic; + +namespace LCU.Api.Internal.Dispatcher.Services; + +public class CleanUpJobTagService : ICleanUpJobTagService +{ + private readonly ICleanUpJobTagRepository _cleanUpJobTagRepository; + private readonly IUnitOfWork _unitOfWork; + + public CleanUpJobTagService(ICleanUpJobTagRepository cleanUpJobRepository, + IUnitOfWork unitOfWork) + { + _cleanUpJobTagRepository = cleanUpJobRepository; + _unitOfWork = unitOfWork; + } + + public async Task> GetAsync() + { + try + { + return await _cleanUpJobTagRepository.ListAsync(); + } + catch (Exception exception) + { + Log.Error(exception, "Failure retrieving CleanUpJobag."); + throw new ServiceException("Failure retrieving CleanUpJobTag."); + } + } + + public async Task GetByCleanUpJobTagIdAsync(int cleanUpJobTagId) + { + try + { + ArgumentNullException.ThrowIfNull(cleanUpJobTagId); + return await _cleanUpJobTagRepository.FindByCleanUpJobTagIdAsync(cleanUpJobTagId); + } + catch (ArgumentNullException exception) + { + Log.Error(exception, $"Failure getting dispatcher Clean Up Job Tag by Id. CleanUpJobTagId was null"); + return null; + } + catch (Exception exception) + { + Log.Error(exception, "Failure retrieving dispatcher Clean Up Job Tag. CleanUpJobTagId: {@cleanUpJobTagId}", cleanUpJobTagId); + throw new ServiceException($"Failure retrieving dispatcher Clean Up Job Tag. CleanUpJobTagId: {cleanUpJobTagId}"); + } + } + + public async Task> GetByCleanUpJobSettingIdAsync(int cleanUpJobSettingId) + { + try + { + ArgumentNullException.ThrowIfNull(cleanUpJobSettingId); + return await _cleanUpJobTagRepository.FindByCleanUpJobSettingIdAsync(cleanUpJobSettingId); + } + catch (ArgumentNullException exception) + { + Log.Error(exception, $"Failure getting dispatcher Clean Up Job Tags bySettings Id. CleanUpJobSettingId was null"); + return null; + } + catch (Exception exception) + { + Log.Error(exception, "Failure retrieving dispatcher Clean Up Job Tag. CleanUpJobSettingId: {@cleanUpJobSettingId}", cleanUpJobSettingId); + throw new ServiceException($"Failure retrieving dispatcher Clean Up Job Tag. CleanUpJobSettingId: {cleanUpJobSettingId}"); + } + } + + public async Task SaveAsync(CleanUpJobTag request) + { + try + { + var result = await _cleanUpJobTagRepository.AddAsync(request); + await _unitOfWork.CompleteAsync(); + return result; + } + catch (Exception exception) + { + Log.Error(exception, "Failure saving dispatcher Clean Up Job Tag. CleanUpJobTagId: {@request.CleanUpJobTagId}", request.CleanUpJobTagId); + throw new ServiceException($"Failure saving dispatcher Clean Up Job Tag. CleanUpJobTagId: {request.CleanUpJobTagId}"); + } + } + + public async Task DeleteAsync(int cleanUpJobTagId) + { + try + { + var cleanUpJobTag = await _cleanUpJobTagRepository.FindByCleanUpJobTagIdAsync(cleanUpJobTagId); + ArgumentNullException.ThrowIfNull(cleanUpJobTag); + _cleanUpJobTagRepository.Remove(cleanUpJobTag); + await _unitOfWork.CompleteAsync(); + } + catch (SqlException sqlException) + { + Log.Error(sqlException, "SQL Failure removing dispatcher Clean Up Job Tag. CleanUpJobTagId: {@cleanUpJobTagId}", cleanUpJobTagId); + throw new ServiceException($"SQL Failure removing dispatcher Clean Up Job Tag. CleanUpJobTagId: {cleanUpJobTagId}"); + } + catch (Exception exception) + { + Log.Error(exception, "Failure removing dispatcher Clean Up Job Tag. CleanUpJobTagId: {@cleanUpJobTagId}", cleanUpJobTagId); + throw new ServiceException($"Failure removing dispatcher Clean Up Job Tag. CleanUpJobTagId: {cleanUpJobTagId}"); + } + } + + public async Task DeleteByCleanUpJobSettingIdAsync(int cleanUpJobSettingId) + { + try + { + var cleanUpJobTags = await _cleanUpJobTagRepository.FindByCleanUpJobSettingIdAsync(cleanUpJobSettingId); + + if(!cleanUpJobTags.Any()) + { + throw new Exception($"No CleanUpJobTags found."); + } + + foreach (var tag in cleanUpJobTags) + { + _cleanUpJobTagRepository.Remove(tag); + } + + await _unitOfWork.CompleteAsync(); + } + catch (SqlException sqlException) + { + Log.Error(sqlException, "SQL Failure removing dispatcher Clean Up Job Tag. CleanUpJobSettingId: {@cleanUpJobSettingId}", cleanUpJobSettingId); + throw new ServiceException($"SQL Failure removing dispatcher Clean Up Job Tag. CleanUpJobSettingId: {cleanUpJobSettingId}"); + } + catch (Exception exception) + { + Log.Error(exception, "Failure removing dispatcher Clean Up Job Tag. CleanUpJobSettingId: {@cleanUpJobSettingId}", cleanUpJobSettingId); + throw new ServiceException($"Failure removing dispatcher Clean Up Job Tag. CleanUpJobSettingId: {cleanUpJobSettingId}"); + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/BatchErroredJobsService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/BatchErroredJobsService.cs new file mode 100644 index 0000000..1307b94 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/BatchErroredJobsService.cs @@ -0,0 +1,72 @@ +using LCU.Api.Internal.Dispatcher.Services.QueueServices; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.DispatcherModels.Models.Requests; +using System.Collections.Generic; +using System.Threading; +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; + +public class BatchErroredJobsService : IBatchErroredJobsService +{ + private readonly IQueueSendErroredJobAlertService _queueSendErroredJobAlertService; + private readonly IJobService _jobService; + + public BatchErroredJobsService(IJobService jobService, + IQueueSendErroredJobAlertService queueSendErroredJobAlertService) + { + _jobService = jobService; + _queueSendErroredJobAlertService = queueSendErroredJobAlertService; + } + + public async Task PerformBatchErroredJobs(CancellationToken cancellationToken) + { + try + { + var jobs = await _jobService.GetErrorMonitoringJobs(); + if (jobs == null || !jobs.Any()) + { + return; + } + + var batchedErroredJobAlertRequest = CreateBatchedErroredJobAlerts(jobs); + + await _queueSendErroredJobAlertService.QueueSendErroredJobAlert(batchedErroredJobAlertRequest); + } + catch (Exception ex) + { + //Fatal as we need to know if we can't get out our errors + Log.ForContext(nameof(PropertyEnum.Group), + nameof(GroupEnum.ITDevelopment)) + .Fatal(ex, $"Failed to perform Error Monitoring."); + } + } + + private static BatchedErroredJobAlertRequest CreateBatchedErroredJobAlerts(IEnumerable jobs) + { + var batchedErroredJobAlertRequests = new BatchedErroredJobAlertRequest() + { + ErroredJobAlertRequests = new List() + }; + + foreach (var job in jobs) + { + var failedOrProcessingWorkItem = job.WorkItems + .FirstOrDefault(wi => wi.WorkItemExecutions != null && + wi.WorkItemExecutions.Any(wie => wie.Status == StatusType.Failed || wie.Status == StatusType.Processing)); + + if (failedOrProcessingWorkItem == null || failedOrProcessingWorkItem.CurrentWorkItemExecution == null) + { + Log.Warning($"Failed or Processing workItem not found during CreateBatchedErroredJobAlerts. JobId and Name: {job.JobId}: {job.Name}"); + continue; + } + + batchedErroredJobAlertRequests.ErroredJobAlertRequests.Add(new ErroredJobAlertRequest + { + CurrentJob = job, + CurrentWorkItem = failedOrProcessingWorkItem + }); + } + + return batchedErroredJobAlertRequests; + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/CleanUpJobsService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/CleanUpJobsService.cs new file mode 100644 index 0000000..d7622de --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/CleanUpJobsService.cs @@ -0,0 +1,172 @@ +using LCU.Api.Internal.Dispatcher.Configurations; +using LCU.Api.Internal.DispatcherLib.Repositories; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.DispatcherModels.Models.Results; +using Newtonsoft.Json; +using System.Threading; + +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; + +public class CleanUpJobsService : ICleanUpJobsService +{ + private readonly IJobService _jobService; + private readonly ICleanUpJobSettingService _cleanUpJobSettingService; + private readonly IUnitOfWork _unitOfWork; + private readonly ApiConfiguration _apiConfiguration; + + public CleanUpJobsService(IJobService jobService, + ICleanUpJobSettingService cleanUpJobSettingService, + IUnitOfWork unitOfWork, + ApiConfiguration apiConfiguration) + { + _jobService = jobService; + _cleanUpJobSettingService = cleanUpJobSettingService; + _unitOfWork = unitOfWork; + _apiConfiguration = apiConfiguration; + } + + public async Task PerformCleanUpJobs(CancellationToken cancellationToken) + { + try + { + var result = new CleanUpJobBatchResult(); + + result = await PerformSoftCleanUpJobTask(result); + result = await PerformHardDeleteCleanUpJobTask(result); + + await _unitOfWork.CompleteAsync(); + + if (result.IsFailure) + { + Log.ForContext(nameof(PropertyEnum.Group), + nameof(GroupEnum.ITDevelopment)) + .Error("CleanUpJob Batch Result: {@CleanUpJobBatchResult}", result); + } + } + catch (Exception ex) + { + Log.ForContext(nameof(PropertyEnum.Group), + nameof(GroupEnum.ITDevelopment)) + .Error(ex, $"Failed to perform Clean Up Jobs."); + } + } + + public async Task PerformSoftCleanUpJobTask(CleanUpJobBatchResult result) + { + var jobs = await _jobService.GetInactiveExcludeSoftDeletedTrackedAsync(); + + var cleanUpJobSettings = await _cleanUpJobSettingService.GetAsync(); + + foreach (var setting in cleanUpJobSettings) + { + if (setting.CleanUpJobTags == null || !setting.CleanUpJobTags.Any()) + { + continue; + } + + foreach (var job in jobs) + { + result = PerformCleanUpTaskForJob(result, job, setting); + } + } + + return result; + } + + public bool ValidateShouldSoftDelete(CleanUpJobSetting setting, Job job, CleanUpJobBatchResult result) + { + var shouldSoftDelete = ShouldSoftDeleteByTag(setting, job); + + if (!shouldSoftDelete) + { + return false; + } + + return ShouldSoftDeleteByDate(setting, job, result); + } + + public bool ShouldSoftDeleteByDate(CleanUpJobSetting setting, Job job, CleanUpJobBatchResult result) + { + try + { + if (!job.WorkItems.Any() || !job.WorkItems.FirstOrDefault().WorkItemExecutions.Any()) + { + result.AddToFailureCount($"Job would be cleaned but cannot determine if it is old enough to be cleaned due to not having any workItemExecutions. Job: {job.JobId} - {job.Name}"); + return false; + } + + var currentDate = DateTime.Now; + var cutoffDate = currentDate.AddDays(setting.CompletionDaysPast); + var jobLastCompletedTime = job.WorkItems.FirstOrDefault() + .WorkItemExecutions.OrderByDescending(wie => wie.CompletedTimestamp).FirstOrDefault().CompletedTimestamp.Value; + + return DateTime.Compare(cutoffDate, jobLastCompletedTime) > 0; + } + catch (Exception ex) + { + var message = $"Error while attempting to compare CompletedTimestamp for SoftDeletion. Job: {job.JobId} - {job.Name}. ExeptionMessage: {ex.Message}"; + Log.Error(message); + throw new Exception(message); + } + } + + public bool ShouldSoftDeleteByTag(CleanUpJobSetting setting, Job job) + { + if (job.JobTags.Count != setting.CleanUpJobTags.Count) + { + return false; + } + + var orderedJobTagIds = job.JobTags.Select(t => t.TagId).OrderBy(t => t).ToList(); + var orderedCleanUpJobTagIds = setting.CleanUpJobTags.Select(t => t.TagId).OrderBy(t => t).ToList(); + + return orderedJobTagIds.SequenceEqual(orderedCleanUpJobTagIds); + } + + public async Task PerformHardDeleteCleanUpJobTask(CleanUpJobBatchResult result) + { + var softDeletedJobs = await _jobService.GetSoftDeletedTrackedAsync(DateTime.Now.AddDays(_apiConfiguration.CleanUpJobsDaysToDelete)); + foreach (var job in softDeletedJobs) + { + try + { + await _jobService.DeleteFromCleanupAsync(job); + } + catch (Exception ex) + { + Log.Error(ex.Message, "Failure to hard delete dispatcher job through Clean Up Job Task. Job: {@Job}", job); + result.AddToFailureCount(ex.Message); + continue; + } + } + + return result; + } + + private CleanUpJobBatchResult PerformCleanUpTaskForJob(CleanUpJobBatchResult result, Job job, CleanUpJobSetting setting) + { + try + { + var shouldSoftDelete = ValidateShouldSoftDelete(setting, job, result); + + if (!shouldSoftDelete) + { + return result; + } + + job.SoftDeleteDate = DateTime.Now; + + var updatedJob = _jobService.UpdateExcludingWorkItemsAsyncWithoutSave(job); + if (updatedJob == null || updatedJob.Result.JobId != job.JobId) + { + throw new Exception($"Failure to setup soft delete update for job. Job:{JsonConvert.SerializeObject(job)}"); + } + } + catch (Exception ex) + { + result.AddToFailureCount(ex.Message); + } + + return result; + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/CompletionService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/CompletionService.cs new file mode 100644 index 0000000..0db0188 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/CompletionService.cs @@ -0,0 +1,84 @@ +using Newtonsoft.Json; +using LCU.Library.Internal.ApiResponse.Models; +using LCU.Api.Internal.Dispatcher.Services.QueueServices; +using LCU.Library.Internal.DispatcherModels.Models.Requests; +using LCU.Library.Internal.DispatcherModels.Enums; +using System.Collections.Generic; +using RabbitMQ.Client; +using LCU.Api.Internal.MaidBotLib.Models; +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; + +public class CompletionService : ICompletionService +{ + private readonly IJobService _jobService; + private readonly IWorkItemService _workItemService; + private readonly IExecutionMessageService _executionMessageService; + private readonly IQueueJobFailureService _queueJobFailureService; + private readonly IQueueDetermineOperationService _queueDetermineOperationService; + + public CompletionService(IJobService jobService, + IWorkItemService workItemService, + IExecutionMessageService executionMessageService, + IQueueJobFailureService queueJobFailureService, + IQueueDetermineOperationService queueDetermineOperationService) + { + _jobService = jobService; + _workItemService = workItemService; + _executionMessageService = executionMessageService; + _queueJobFailureService = queueJobFailureService; + _queueDetermineOperationService = queueDetermineOperationService; + } + + public async Task HandleCompletionRequest(Response response) + { + var dispatcherReturnResponse = JsonConvert.DeserializeObject(response.ResponsePayload); + try + { + ArgumentNullException.ThrowIfNull(dispatcherReturnResponse); + + var job = await _jobService.GetByWorkItemIdExcludingWorkItemsAsync(dispatcherReturnResponse.WorkItemId); + + if (!response.Successful) + { + await _queueJobFailureService.QueueJobFailure(job.JobId, + dispatcherReturnResponse.WorkItemId, + response.ResponseStack == null || response.ResponseStack.Any() ? response.ResponseStack : new List()); + return; + } + + var workItem = await _workItemService.GetByIdAsync(dispatcherReturnResponse.WorkItemId); + if (workItem.CurrentWorkItemExecution == null) + { + Log.Warning($"CurrentWorkitemExecution was null for WorkItemId:{dispatcherReturnResponse.WorkItemId}"); + return; + } + await _executionMessageService.SaveExecutionMessages(workItem.CurrentWorkItemExecution.WorkItemExecutionId, response.ResponseStack); + + if (workItem.CanceledJobCounter > 0) + { + workItem = await _workItemService.ResetCanceledJobCounter(workItem); + } + + job.JobStatus = JobStatus.DeterminingOperation; + var updatedJob = await _jobService.UpdateAsync(job); + + await _queueDetermineOperationService.QueueDetermineOperation(updatedJob, workItem, SetDetermineOperationType(dispatcherReturnResponse.Skipped)); + } + catch (Exception exception) + { + var exceptionMessage = $"Failed to perform completion. Job may be stuck in an inoperable status. WorkItemId: {dispatcherReturnResponse.WorkItemId}"; + Log.Error(exception, exceptionMessage); + throw new ServiceException(exceptionMessage); + } + } + + public DetermineOperationType SetDetermineOperationType(bool isSkipped) + { + if(isSkipped) + { + return DetermineOperationType.Skipped; + } + + return DetermineOperationType.Completed; + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/DetermineOperationService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/DetermineOperationService.cs new file mode 100644 index 0000000..3526058 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/DetermineOperationService.cs @@ -0,0 +1,82 @@ +using LCU.Api.Internal.Dispatcher.Services.QueueServices; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.DispatcherModels.Entities; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.DispatcherModels.Models.Requests; + +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices +{ + public class DetermineOperationService : IDetermineOperationService + { + private readonly IJobService _jobService; + private readonly IWorkItemService _workItemService; + private readonly IWorkItemExecutionService _workItemExecutionService; + private readonly IQueueExecutionService _queueExecutionService; + + public DetermineOperationService(IJobService jobService, + IWorkItemService workItemService, + IWorkItemExecutionService workItemExecutionService, + IQueueExecutionService queueExecutionService) + { + _jobService = jobService; + _workItemService = workItemService; + _workItemExecutionService = workItemExecutionService; + _queueExecutionService = queueExecutionService; + } + + public async Task HandleDetermineOperationRequest(DetermineOperationRequest determineOperationRequest) + { + try + { + switch (determineOperationRequest.DetermineOperationType) + { + case DetermineOperationType.Failed: + await HandleFailedWorkItem(determineOperationRequest); + break; + case DetermineOperationType.Skipped: + await HandleWorkItemCompletion(determineOperationRequest, StatusType.Skipped); + break; + case DetermineOperationType.Completed: + await HandleWorkItemCompletion(determineOperationRequest, StatusType.Completed); + break; + default: + throw new NotImplementedException(); + } + return; + } + catch (ServiceException serviceException) + { + Log.Error(serviceException, $"Dispatcher Determine Operation Failure. " + + $"The Dispatcher Determine Operation is placed on LCU.Dispatcher.DetermineOperation_error."); + throw new ServiceException($"Failed to handle DetermineOperationRequest."); + } + catch (Exception exception) + { + Log.Error(exception, $"Failed to handle DetermineOperationRequest."); + throw new ServiceException($"Failed to handle DetermineOperationRequest."); + } + } + + public async Task HandleFailedWorkItem(DetermineOperationRequest determineOperationRequest) + { + await _jobService.CompleteJobAndWorkItem(determineOperationRequest.CurrentJob, determineOperationRequest.CurrentWorkItem.CurrentWorkItemExecution, StatusType.Failed); + } + + public async Task HandleWorkItemCompletion(DetermineOperationRequest determineOperationRequest, StatusType status) + { + var nextWorkItem = await _workItemService.GetNextWorkItemByJobId(determineOperationRequest.CurrentJob.JobId, determineOperationRequest.CurrentWorkItem.Order); + if (nextWorkItem == null) + { + await _jobService.CompleteJobAndWorkItem(determineOperationRequest.CurrentJob, determineOperationRequest.CurrentWorkItem.CurrentWorkItemExecution, status); + return; + } + + await _jobService.CompleteWorkItemAndContinueProcessingJob(determineOperationRequest.CurrentJob, + determineOperationRequest.CurrentWorkItem.CurrentWorkItemExecution, + status); + var newWorkItemExecution = await _workItemExecutionService.CreateWorkItemExecution(nextWorkItem.WorkItemId); + + await _queueExecutionService.QueueExecution(newWorkItemExecution); + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/ExecutionService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/ExecutionService.cs new file mode 100644 index 0000000..a5e31bd --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/ExecutionService.cs @@ -0,0 +1,96 @@ +using LCU.Api.Internal.Dispatcher.Services.QueueServices; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.LcuHttpClient.HttpRequest; +using System.Collections.Generic; +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +public class ExecutionService : IExecutionService +{ + private readonly IPostRequest _postRequest; + private readonly IWorkItemService _workItemService; + private readonly IJobService _jobService; + private readonly IQueueCompletionService _queueCompletionService; + + private const string HttRequestIsNullMessage = "Http Request is null"; + + public ExecutionService(IPostRequest postRequest, + IWorkItemService workItemService, + + IJobService jobService, + IQueueCompletionService queueCompletionService) + { + _postRequest = postRequest; + _workItemService = workItemService; + _jobService = jobService; + _queueCompletionService = queueCompletionService; + } + + public async Task HandleExecutionRequest(WorkItemExecution request) + { + try + { + var executionResponse = await ExecuteRequest(request); + if (!executionResponse.IsSuccess) + { + await _queueCompletionService.QueueCompletion(new DispatcherReturnResponse() { WorkItemId = executionResponse.WorkItemExecution.WorkItemId }, + executionResponse.Messages, executionResponse.IsSuccess); + } + } + catch (Exception exception) + { + Log.Error(exception, $"Failed to perform execution. WorkItemId: {request.WorkItemId}"); + throw new ServiceException($"Failed to perform execution. WorkItemId: {request.WorkItemId}"); + } + } + + public async Task ExecuteRequest(WorkItemExecution request) + { + try + { + ArgumentNullException.ThrowIfNull(request); + + var requestedWorkItem = await _workItemService.GetByIdAsync(request.WorkItemId); + + await UpdateJobStatusToProcessing(requestedWorkItem); + + var result = await _postRequest.AsyncWithNewtonSerializedNoThrow(new Uri(requestedWorkItem.Url), requestedWorkItem.SerializedPayload); + if (!result.IsSuccessStatusCode) + { + var content = await result.Content.ReadAsStringAsync(); + return new ExecutionResponse(false, request, content); + } + return new ExecutionResponse(true, request); + } + catch (Exception ex) + { + var errorMessage = $"Exception during ExecuteRequest. Exception Message: {ex.Message}. Inner Exception: {ex.InnerException}"; + Log.Error(errorMessage); + return new ExecutionResponse(false, request, errorMessage); + } + } + + private async Task UpdateJobStatusToProcessing(WorkItem requestedWorkItem) + { + var job = await _jobService.GetByWorkItemIdExcludingWorkItemsAsync(requestedWorkItem.WorkItemId); + if (job.JobStatus == JobStatus.Processing) + { + return; + } + job.JobStatus = JobStatus.Processing; + await _jobService.UpdateAsync(job); + Log.Information("Job Id {JobId} was saved with status {Status}", job.JobId, job.JobStatus); + } + + public struct ExecutionResponse + { + public ExecutionResponse(bool isSuccess, WorkItemExecution workItemExecution, string messages = null) + { + IsSuccess = isSuccess; + WorkItemExecution = workItemExecution; + Messages = messages != null ? new List() { messages } : null; + } + + public bool IsSuccess { get; set; } + public List Messages { get; set; } + public WorkItemExecution WorkItemExecution { get; set; } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IBatchErroredJobsService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IBatchErroredJobsService.cs new file mode 100644 index 0000000..82c87a5 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IBatchErroredJobsService.cs @@ -0,0 +1,7 @@ +using System.Threading; +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; + +public interface IBatchErroredJobsService +{ + Task PerformBatchErroredJobs(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/ICleanUpJobsService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/ICleanUpJobsService.cs new file mode 100644 index 0000000..7382e8a --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/ICleanUpJobsService.cs @@ -0,0 +1,8 @@ +using System.Threading; + +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; + +public interface ICleanUpJobsService +{ + Task PerformCleanUpJobs(CancellationToken cancellationToken); +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/ICompletionService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/ICompletionService.cs new file mode 100644 index 0000000..d75770d --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/ICompletionService.cs @@ -0,0 +1,7 @@ +using LCU.Library.Internal.ApiResponse.Models; +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; + +public interface ICompletionService +{ + Task HandleCompletionRequest(Response response); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IDetermineOperationService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IDetermineOperationService.cs new file mode 100644 index 0000000..580190b --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IDetermineOperationService.cs @@ -0,0 +1,9 @@ +using LCU.Library.Internal.DispatcherModels.Models.Requests; + +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices +{ + public interface IDetermineOperationService + { + Task HandleDetermineOperationRequest(DetermineOperationRequest determineOperationRequest); + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IExecutionService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IExecutionService.cs new file mode 100644 index 0000000..87ef357 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IExecutionService.cs @@ -0,0 +1,6 @@ +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; + +public interface IExecutionService +{ + Task HandleExecutionRequest(WorkItemExecution request); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IIngestionService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IIngestionService.cs new file mode 100644 index 0000000..01852cf --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IIngestionService.cs @@ -0,0 +1,7 @@ +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; + +public interface IIngestionService +{ + Task HandleIngestionRequest(Job request); + Task PerformIngestion(Job queueRequest); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IJobFailureService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IJobFailureService.cs new file mode 100644 index 0000000..5218020 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IJobFailureService.cs @@ -0,0 +1,9 @@ +using LCU.Library.Internal.DispatcherModels.Models.Requests; + +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices +{ + public interface IJobFailureService + { + Task HandleJobFailureRequest(JobFailureRequest jobFailureRequest); + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IJobScheduleService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IJobScheduleService.cs new file mode 100644 index 0000000..165d861 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IJobScheduleService.cs @@ -0,0 +1,7 @@ +using System.Threading; +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; + +public interface IJobScheduleService +{ + Task PerformSchedule(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/ISendErroredJobAlertService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/ISendErroredJobAlertService.cs new file mode 100644 index 0000000..b1d02fd --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/ISendErroredJobAlertService.cs @@ -0,0 +1,10 @@ +using LCU.Library.Internal.DispatcherModels.Models.Requests; +using System.Collections.Generic; + +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices +{ + public interface ISendErroredJobAlertService + { + Task HandleSendingErroredJobAlerts(List batchedErroredJobAlertRequestList); + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IngestionService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IngestionService.cs new file mode 100644 index 0000000..bee0ef1 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/IngestionService.cs @@ -0,0 +1,159 @@ +using LCU.Library.Internal.DispatcherModels.Enums; +using System.Collections.Generic; +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; + +public class IngestionService : IIngestionService +{ + private readonly IJobService _jobService; + private readonly IWorkItemService _workItemService; + private readonly IJobErrorRecipientService _jobErrorRecipientService; + private readonly IJobTagService _jobTagService; + + public IngestionService(IJobService jobService, + IWorkItemService workItemService, + IJobErrorRecipientService jobErrorRecipientService, + IJobTagService jobTagService) + { + _jobService = jobService; + _workItemService = workItemService; + _jobErrorRecipientService = jobErrorRecipientService; + _jobTagService = jobTagService; + } + + public async Task HandleIngestionRequest(Job request) + { + try + { + ArgumentNullException.ThrowIfNull(request); + var result = await PerformIngestion(request); + + if (!result) + { + throw new ServiceException("Failure to PerformIngestion."); + } + } + catch (Exception exception) + { + Log.Error(exception, $"Failed to perform execution."); + throw new ServiceException($"Failed to perform ingestion."); + } + } + + public async Task PerformIngestion(Job request) + { + try + { + if (request.IsNewJob) + { + return await SaveNewRequest(request); + } + + return await UpdateRequest(request); + } + catch (Exception ex) + { + Log.Information(ex, $"Failed to perform ingestion."); + return false; + } + } + + public async Task SaveNewRequest(Job request) + { + request = await _jobService.SaveAsync(request); + if (request == null) + { + return false; + } + + var updatedWorkItems = await _workItemService.PopulateActionFieldsAndUpdateList(request.WorkItems); + if (updatedWorkItems == null) + { + return false; + } + + return true; + } + + public async Task UpdateRequest(Job request) + { + var existingJob = await _jobService.GetByIdAsync(request.JobId); + + await UpdateRequestWorkItems(request, existingJob); + UpdateRequestErrorRecipients(request, existingJob); + await UpdateRequestJobTags(request, existingJob); + request.JobStatus = (request.NextRunTime == null && request.CronSchedule == null) ? JobStatus.Inactive : existingJob.JobStatus; + + var updatedJob = await _jobService.UpdateAsync(request); + if (updatedJob == null) + { + return false; + } + + return true; + } + + public async Task UpdateRequestWorkItems(Job request, Job existingJob) + { + var removedWorkItems = existingJob.WorkItems.Where(wi => + !request.WorkItems.Any(newWI => + newWI.WorkItemId == wi.WorkItemId)).ToList(); + await _workItemService.DeleteList(removedWorkItems); + + var newWorkItems = request.WorkItems.Where(wi => wi.IsNewWorkItem).ToList(); + _workItemService.SaveList(newWorkItems); + + var updatedWorkItems = await _workItemService.PopulateActionFieldsAndUpdateList(request.WorkItems); + if (updatedWorkItems == null) + { + throw new Exception($"WorkItem Error while Populating Action Fields and updating."); + } + } + + public void UpdateRequestErrorRecipients(Job request, Job existingJob) + { + var removedJobErrorRecipients = existingJob.JobErrorRecipients.Where(recipient => + !request.JobErrorRecipients.Any(newRecipient => + newRecipient.Recipient == recipient.Recipient)).ToList(); + _jobErrorRecipientService.DeleteList(removedJobErrorRecipients); + + var newJobErrorRecipients = new List(); + foreach (var recipient in request.JobErrorRecipients) + { + var jobErrorRecipient = existingJob.JobErrorRecipients.FirstOrDefault(existing => + existing.Recipient == recipient.Recipient); + + if (jobErrorRecipient == null) + { + newJobErrorRecipients.Add(recipient); + } + } + + _jobErrorRecipientService.SaveList(newJobErrorRecipients); + } + + public async Task UpdateRequestJobTags(Job request, Job existingJob) + { + var removedJobTags = existingJob.JobTags.Where(tag => + !request.JobTags.Any(jobTags => + jobTags.TagId == tag.TagId)).ToList(); + + foreach (var tag in removedJobTags) + { + await _jobTagService.Delete(tag.JobTagId); + } + + var newJobTags = new List(); + foreach (var tag in request.JobTags) + { + var existingTag = existingJob.JobTags.FirstOrDefault(existing => + existing.TagId == tag.TagId); + + if (existingTag == null) + { + newJobTags.Add(tag); + var result = await _jobTagService.SaveAsync(tag); + } + + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/JobFailureService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/JobFailureService.cs new file mode 100644 index 0000000..8f66913 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/JobFailureService.cs @@ -0,0 +1,91 @@ +using LCU.Api.Internal.Dispatcher.Services.QueueServices; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.DispatcherModels.Entities; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.DispatcherModels.Models; +using LCU.Library.Internal.DispatcherModels.Models.Requests; +using LCU.Library.Internal.DocumentIngestorModels.Models.IndexFileDocuments; +using LCU.Library.Internal.Exceptions; +using System; +using System.Collections.Generic; + +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices +{ + public class JobFailureService : IJobFailureService + { + private readonly IWorkItemService _workItemService; + private readonly IWorkItemExecutionService _workItemExecutionService; + private readonly IJobService _jobService; + private readonly IExecutionMessageService _executionMessageService; + private readonly IQueueDetermineOperationService _queueDetermineOperationService; + private readonly IQueueSendErroredJobAlertService _queueSendErroredJobAlertService; + + public JobFailureService(IWorkItemService workItemService, + IWorkItemExecutionService workItemExecutionService, + IJobService jobService, + IExecutionMessageService executionMessageService, + IQueueDetermineOperationService queueDetermineOperationService, + IQueueSendErroredJobAlertService queueSendErroredJobAlertService) + { + _workItemService = workItemService; + _workItemExecutionService = workItemExecutionService; + _jobService = jobService; + _executionMessageService = executionMessageService; + _queueDetermineOperationService = queueDetermineOperationService; + _queueSendErroredJobAlertService = queueSendErroredJobAlertService; + } + + public async Task HandleJobFailureRequest(JobFailureRequest jobFailureRequest) + { + try + { + var workItem = await _workItemService.GetByIdAsync(jobFailureRequest.WorkItemId); + + var updatedWorkItemExecution = await _workItemExecutionService.UpdateFailedWorkItemExecution(workItem.CurrentWorkItemExecution); + + if (workItem.CancelJobOnFailure) + { + workItem = await _workItemService.IncreaseCanceledJobCounter(workItem); + } + + var job = await _jobService.GetByWorkItemIdExcludingWorkItemsIncludeErrorRecipientsAsync(jobFailureRequest.WorkItemId); + + if (workItem.RequiresJobCancellation) + { + var message = $"This Job will be canceled and if scheduleable, rescheduled. The " + + $"WorkItem's canceled Job counter is currently at {workItem.CanceledJobCounter} and has " + + $"not yet went over the cancel Job limit of {workItem.CancelJobLimit}."; + jobFailureRequest.ExecutionMessages.Add(message); + job.JobStatus = JobStatus.DeterminingOperation; + } + else + { + job.JobStatus = JobStatus.Failed; + } + + await _executionMessageService.SaveExecutionMessages(workItem.CurrentWorkItemExecution.WorkItemExecutionId, jobFailureRequest.ExecutionMessages); + var updatedJob = await _jobService.UpdateAsync(job); + + if (updatedJob.JobStatus == JobStatus.DeterminingOperation) + { + await _queueDetermineOperationService.QueueDetermineOperation(updatedJob, workItem, DetermineOperationType.Failed); + } + else + { + await _queueSendErroredJobAlertService.QueueSendErroredJobAlert(updatedJob, workItem); + } + } + catch (ServiceException serviceException) + { + Log.Error(serviceException, $"Dispatcher Job Failure Request Failure. " + + $"The Dispatcher Job Failure Request is placed on LCU.Dispatcher.JobFailure_error."); + throw new ServiceException($"Failed to handle JobFailureRequest."); + } + catch(Exception exception) + { + Log.Error(exception, $"Failed to handle JobFailureRequest."); + throw new ServiceException($"Failed to handle JobFailureRequest."); + } + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/JobScheduleService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/JobScheduleService.cs new file mode 100644 index 0000000..6d7e703 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/JobScheduleService.cs @@ -0,0 +1,67 @@ +using System.Threading; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Api.Internal.Dispatcher.Services.QueueServices; +using LCU.Library.Internal.DispatcherModels.Enums; +using System; +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; + +public class JobScheduleService : IJobScheduleService +{ + private readonly IQueueExecutionService _queueExecutionService; + private readonly IJobService _jobService; + private readonly IWorkItemService _workItemService; + private readonly IWorkItemExecutionService _workItemExecutionService; + + public JobScheduleService(IQueueExecutionService queueExecutionService, + IJobService jobService, + IWorkItemService workItemService, + IWorkItemExecutionService workItemExecutionService) + { + _queueExecutionService = queueExecutionService; + _jobService = jobService; + _workItemService = workItemService; + _workItemExecutionService = workItemExecutionService; + } + + public async Task PerformSchedule(CancellationToken cancellationToken) + { + try + { + var readyToScheduleJobs = await _jobService.GetJobsReadyToBeScheduled(); + + foreach (var job in readyToScheduleJobs) + { + var workItem = await _workItemService.GetFirstOrDefaultWorkItemByJobId(job.JobId); + + if (workItem == null) + { + // THIS SHOULD NEVER HAPPEN + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error($"Failure in Dispatcher Job Scheduler. Attempting to schedule Job: {job.JobId} - {job.Name} and NO workItems were found to process."); + continue; + } + + var workItemExecution = await _workItemExecutionService.CreateWorkItemExecution(workItem.WorkItemId); + + job.JobStatus = JobStatus.PendingToRun; + + if (job.SoftDeleteDate != null) + { + job.SoftDeleteDate = null; + } + + await _jobService.UpdateAsync(job); + Log.Information("Job Id {JobId} was saved with status {Status}", job.JobId, job.JobStatus); + + await _queueExecutionService.QueueExecution(workItemExecution); + } + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Error(exception, $"Failed to perform schedule."); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/SendErroredJobAlertService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/SendErroredJobAlertService.cs new file mode 100644 index 0000000..666bea6 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/DispatcherOperationServices/SendErroredJobAlertService.cs @@ -0,0 +1,146 @@ +using LCU.Api.Internal.Dispatcher.Configurations; +using LCU.Library.Internal.DispatcherModels.Models.Requests; +using System.Collections.Generic; +using System.IO; + +namespace LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices +{ + public class SendErroredJobAlertService : ISendErroredJobAlertService + { + private readonly ApiConfiguration _apiConfiguration; + private readonly INotificationService _notificationService; + + public SendErroredJobAlertService(ApiConfiguration apiConfiguration, INotificationService notificationService) + { + _apiConfiguration = apiConfiguration; + _notificationService = notificationService; + } + + public Task HandleSendingErroredJobAlerts(List batchedErroredJobAlertRequestList) + { + var erroredJobAlertRequests = CombineFromBatch(batchedErroredJobAlertRequestList); + + return SendErroredJobsReport(erroredJobAlertRequests); + } + + public async Task SendErroredJobsReport(List erroredJobAlertRequests) + { + var groupedJobsByRecipients = GroupJobsByRecipients(erroredJobAlertRequests); + var result = true; + foreach (var recipientGroup in groupedJobsByRecipients) + { + var successful = await HandleBatchErroredJobs(recipientGroup.Value, recipientGroup.Key); + if (!successful) + { + result = false; + } + } + + if (!result) + { + throw new ServiceException($"Failure to Send Errored Jobs Report. Result: {result}"); + } + } + + public List CombineFromBatch(List batchedErroredJobAlertRequestList) + { + var erroredJobAlertRequests = new List(); + foreach (var batch in batchedErroredJobAlertRequestList) + { + erroredJobAlertRequests.AddRange(batch.ErroredJobAlertRequests); + } + return erroredJobAlertRequests; + } + + public Dictionary> GroupJobsByRecipients(List erroredJobAlertRequests) + { + var groupedRecipients = new Dictionary>(); + foreach (var erroredJobAlertRequest in erroredJobAlertRequests) + { + AddRecipients(erroredJobAlertRequest, groupedRecipients); + } + + return groupedRecipients; + } + + public void AddRecipients(ErroredJobAlertRequest erroredJbAlertRequest, Dictionary> groupedRecipients) + { + if (erroredJbAlertRequest.CurrentJob.JobErrorRecipients == null || + erroredJbAlertRequest.CurrentJob.JobErrorRecipients.Count == 0) + { + AddRecipient(_apiConfiguration.DefaultErrorRecipient, erroredJbAlertRequest, groupedRecipients); + return; + } + + foreach (var jobErrorRecipient in erroredJbAlertRequest.CurrentJob.JobErrorRecipients) + { + AddRecipient(jobErrorRecipient.Recipient, erroredJbAlertRequest, groupedRecipients); + } + } + + public void AddRecipient(string defaultErrorRecipient, ErroredJobAlertRequest erroredJobAlertRequest, Dictionary> groupedRecipients) + { + var recipientLower = defaultErrorRecipient.ToLower(); + if (!groupedRecipients.ContainsKey(recipientLower)) + { + groupedRecipients.Add(recipientLower, new List()); + } + + groupedRecipients[recipientLower].Add(erroredJobAlertRequest); + } + + public async Task HandleBatchErroredJobs(List erroredJobAlertRequests, string recipient) + { + string rowsOfErrors = ""; + foreach (ErroredJobAlertRequest erroredJobAlertRequest in erroredJobAlertRequests) + { + var jobErrorDetail = SetUpJobErrorDetails(erroredJobAlertRequest); + if (jobErrorDetail != null) + { + rowsOfErrors += CreateErrorRow(jobErrorDetail); + } + } + + var emailTemplate = CreateBatchErroredJobsEmail(rowsOfErrors); + var successfullySent = await _notificationService.SendEmailAsync(recipient, + _apiConfiguration.BatchErroredJobsSubject, + emailTemplate); + return successfullySent; + } + + public JobErrorDetail SetUpJobErrorDetails(ErroredJobAlertRequest erroredJobAlertRequest) + { + var jobErrorDetail = new JobErrorDetail() + { + JobName = erroredJobAlertRequest?.CurrentJob?.Name, + WorkItemName = erroredJobAlertRequest?.CurrentWorkItem?.Name, + JobStatus = erroredJobAlertRequest?.CurrentJob?.JobStatus.ToString(), + StartTime = erroredJobAlertRequest?.CurrentWorkItem?.CurrentWorkItemExecution?.StartedTimestamp.ToString() + }; + + return jobErrorDetail; + } + + public string CreateErrorRow(JobErrorDetail jobErrorDetails) + { + using var errorRowReader = new StreamReader(_apiConfiguration.ErrorRowTemplate); + string errorRowOutput = errorRowReader.ReadToEnd(); + + foreach (var property in jobErrorDetails.GetType().GetProperties()) + { + string value = property.GetValue(jobErrorDetails) == null ? string.Empty : + property.GetValue(jobErrorDetails).ToString(); + errorRowOutput = errorRowOutput.Replace($"{{{property.Name}}}", value); + } + + return errorRowOutput; + } + + public string CreateBatchErroredJobsEmail(string errorRows) + { + using var batchErroredJobsReader = new StreamReader(_apiConfiguration.BatchErroredJobsTemplate); + string batchErroredJobsOutput = batchErroredJobsReader.ReadToEnd(); + return batchErroredJobsOutput.Replace("{TableRows}", errorRows); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/ExecutionMessageService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/ExecutionMessageService.cs new file mode 100644 index 0000000..3609eae --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/ExecutionMessageService.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using LCU.Api.Internal.DispatcherLib.Repositories; +namespace LCU.Api.Internal.Dispatcher.Services; + +public class ExecutionMessageService : IExecutionMessageService +{ + private readonly IUnitOfWork _unitOfWork; + private readonly IExecutionMessageRepository _executionMessageRepository; + + public ExecutionMessageService(IUnitOfWork unitOfWork, + IExecutionMessageRepository executionMessageRepository) + { + _unitOfWork = unitOfWork; + _executionMessageRepository = executionMessageRepository; + } + + public async Task SaveExecutionMessages(List executionMessageList) + { + try + { + if (executionMessageList == null || !executionMessageList.Any()) + { + return; + } + + foreach (var message in executionMessageList) + { + if (string.IsNullOrWhiteSpace(message.Message)) + { + continue; + } + + var savedExecutionMessage = await SaveAsync(message); + ArgumentNullException.ThrowIfNull(savedExecutionMessage); + } + } + catch (Exception exception) + { + Log.Error(exception, "During SaveExecutionMessages there was an error. "); + throw new ServiceException($"During SaveExecutionMessages there was an error. " + + "ExceptionMessage: {exception.Message}"); + } + } + + public async Task SaveExecutionMessages(int workItemExecutionId, List messageList) + { + try + { + if (messageList == null || !messageList.Any()) + { + return; + } + + foreach (var message in messageList) + { + if (string.IsNullOrWhiteSpace(message)) + { + continue; + } + + var executionMessage = new ExecutionMessage(workItemExecutionId, message); + var savedExecutionMessage = await SaveAsync(executionMessage); + ArgumentNullException.ThrowIfNull(savedExecutionMessage); + } + } + catch (Exception exception) + { + Log.Error(exception, $"During SaveExecutionMessages there was an error. " + + $"WorkItemExecutionId: {workItemExecutionId}"); + throw new ServiceException($"During SaveExecutionMessages there was an error. " + + $"WorkItemExecutionId: {workItemExecutionId}, " + + $"ExceptionMessage: {exception.Message}"); + } + } + + public async Task SaveAsync(ExecutionMessage workItemExecution) + { + try + { + var result = await _executionMessageRepository.AddAsync(workItemExecution); + await _unitOfWork.CompleteAsync(); + return result; + } + catch (Exception exception) + { + Log.Error(exception, "Failure saving dispatcher Execution Messages", workItemExecution); + throw new ServiceException($"Failure saving dispatcher Execution Messages. ExceptionMessage: { exception.Message }"); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/ICleanUpJobSettingService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/ICleanUpJobSettingService.cs new file mode 100644 index 0000000..1bf059e --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/ICleanUpJobSettingService.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace LCU.Api.Internal.Dispatcher.Services +{ + public interface ICleanUpJobSettingService + { + Task> GetAsync(); + Task GetByCleanUpJobSettingIdAsync(int cleanUpJobSettingId); + Task SaveAsync(CleanUpJobSetting request); + Task UpdateAsync(CleanUpJobSetting request); + Task DeleteAsync(int cleanUpJobSettingId); + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/ICleanUpJobTagService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/ICleanUpJobTagService.cs new file mode 100644 index 0000000..5704101 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/ICleanUpJobTagService.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace LCU.Api.Internal.Dispatcher.Services; + +public interface ICleanUpJobTagService +{ + Task> GetAsync(); + Task GetByCleanUpJobTagIdAsync(int cleanUpJobTagId); + Task> GetByCleanUpJobSettingIdAsync(int cleanUpJobSettingId); + Task SaveAsync(CleanUpJobTag request); + Task DeleteAsync(int cleanUpJobTagId); + Task DeleteByCleanUpJobSettingIdAsync(int cleanUpJobSettingId); +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IExecutionMessageService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IExecutionMessageService.cs new file mode 100644 index 0000000..a47460f --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IExecutionMessageService.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +namespace LCU.Api.Internal.Dispatcher.Services; + +public interface IExecutionMessageService +{ + Task SaveExecutionMessages(List executionMessageList); + Task SaveExecutionMessages(int workItemExecutionId, List messageList); + Task SaveAsync(ExecutionMessage workItemExecution); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IJobErrorRecipientService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IJobErrorRecipientService.cs new file mode 100644 index 0000000..703cfa4 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IJobErrorRecipientService.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.Dispatcher.Services; + +public interface IJobErrorRecipientService +{ + void SaveList(ICollection jobErrorRecipient); + void DeleteList(ICollection jobErrorRecipient); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IJobService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IJobService.cs new file mode 100644 index 0000000..6965cf9 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IJobService.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.DispatcherModels.Models.Requests; +using LCU.Library.Internal.DispatcherModels.Models.Results; + +namespace LCU.Api.Internal.Dispatcher.Services; + +public interface IJobService +{ + Task> GetFilteredJobs(Filter filter); + Task> GetAsync(); + Task> GetInactiveAsync(); + Task> GetInactiveExcludeSoftDeletedTrackedAsync(); + Task> GetSoftDeletedTrackedAsync(DateTime cutOffDate); + Task GetByIdAsync(int id); + Task GetByIdExcludingWorkItemsAsync(int id); + Task GetByWorkItemIdAsync(int workItemId); + Task GetByWorkItemIdExcludingWorkItemsAsync(int workItemId); + Task GetByWorkItemIdExcludingWorkItemsIncludeErrorRecipientsAsync(int workItemId); + Task> GetJobsReadyToBeScheduled(); + Task> GetErrorMonitoringJobs(); + Task SaveAsync(Job job); + Task CompleteJobAndWorkItem(Job job, WorkItemExecution currentWorkItemExecution, StatusType finalStatus); + Task CompleteWorkItemAndContinueProcessingJob(Job job, WorkItemExecution currentWorkItemExecution, StatusType finalStatus); + Task UpdateAsync(Job job); + Task UpdateExcludingWorkItemsAsyncWithoutSave(Job job); + Task RemoveSoftDeleteById(int id); + Task RunJobNowAsync(Job job); + Task RunJobNowByIdAsync(int id); + Task DeactivateJobAsync(Job job); + Task RescheduleJobAsync(Job job); + Task DeleteAsync(int jobId); + Task DeleteFromCleanupAsync(Job job); + Task CopyJobAsync(CopyJobRequest request); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IJobTagService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IJobTagService.cs new file mode 100644 index 0000000..208a26d --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IJobTagService.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +namespace LCU.Api.Internal.Dispatcher.Services; + +public interface IJobTagService +{ + Task> GetAsync(); + Task> GetByTagIdAsync(int tagId); + Task SaveAsync(JobTag jobTag); + Task Delete(int jobTagId); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/INotificationService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/INotificationService.cs new file mode 100644 index 0000000..d8a7c03 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/INotificationService.cs @@ -0,0 +1,7 @@ +using System.Threading.Tasks; +namespace LCU.Api.Internal.Dispatcher.Services; + +public interface INotificationService +{ + Task SendEmailAsync(string recipient, string subject, string emailTemplate); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/ITagService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/ITagService.cs new file mode 100644 index 0000000..42cd316 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/ITagService.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +namespace LCU.Api.Internal.Dispatcher.Services; + +public interface ITagService +{ + Task> GetAsync(); + Task GetByIdAsync(int tagId); + Task SaveAsync(Tag tag); + Task UpdateAsync(Tag tag); + Task DeleteAsync(int tagId); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IWorkItemExecutionService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IWorkItemExecutionService.cs new file mode 100644 index 0000000..e76c03c --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IWorkItemExecutionService.cs @@ -0,0 +1,17 @@ +using LCU.Library.Internal.DispatcherModels.Enums; +using System.Collections.Generic; +namespace LCU.Api.Internal.Dispatcher.Services; + +public interface IWorkItemExecutionService +{ + Task GetByIdAsync(int workItemExecutionId); + Task UpdateCompleteWorkItemExecution(WorkItemExecution workItemExecution, StatusType workItemFinalStatus); + Task UpdateCompleteWorkItemExecutionWithoutSave(WorkItemExecution workItemExecution, StatusType workItemFinalStatus); + Task UpdateAsync(WorkItemExecution workItemExecution); + Task CreateWorkItemExecution(int workItemId); + Task SaveAsync(WorkItemExecution workItemExecution); + Task UpdateProcessingWorkItemExecution(WorkItemExecution workItemExecution); + Task UpdateFailedWorkItemExecution(WorkItemExecution workItemExecution); + Task> GetAllUnFinishedWorkItemExecutions(int workItemExecutionId); + Task> GetAllByWorkItemId(int workItemId); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IWorkItemService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IWorkItemService.cs new file mode 100644 index 0000000..1bbefbd --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/IWorkItemService.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +namespace LCU.Api.Internal.Dispatcher.Services; + +public interface IWorkItemService +{ + Task GetByIdAsync(int workItemId); + Task> GetByJobIdAsync(int jobId); + Task> GetByJobIdWithExecutionsAsync(int jobId); + Task GetFirstOrDefaultWorkItemByJobId(int jobId); + Task GetNextWorkItemByJobId(int jobId, int currentWorkItemOrder); + void SaveList(ICollection workItems); + Task> PopulateActionFieldsAndUpdateList(ICollection workItems); + Task ResetCanceledJobCounter(WorkItem workItem); + Task IncreaseCanceledJobCounter(WorkItem workItem); + Task UpdateAsync(WorkItem workItem); + Task DeleteList(ICollection workItems); + WorkItem PopulateActionFields(WorkItem workItem); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/JobErrorRecipientService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/JobErrorRecipientService.cs new file mode 100644 index 0000000..e7437f2 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/JobErrorRecipientService.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using Microsoft.Data.SqlClient; +using LCU.Api.Internal.DispatcherLib.Repositories; +namespace LCU.Api.Internal.Dispatcher.Services; + +public class JobErrorRecipientService : IJobErrorRecipientService +{ + private readonly IUnitOfWork _unitOfWork; + private readonly IJobErrorRecipientRepository _jobErrorRecipientRepository; + + public JobErrorRecipientService(IUnitOfWork unitOfWork, + IJobErrorRecipientRepository jobErrorRecipientRepository) + { + _unitOfWork = unitOfWork; + _jobErrorRecipientRepository = jobErrorRecipientRepository; + } + + public void SaveList(ICollection jobErrorRecipient) + { + try + { + _jobErrorRecipientRepository.AddList(jobErrorRecipient); + _unitOfWork.Complete(); + } + catch (SqlException ex) + { + Log.Error(ex, $"SQL Failure saving list of job error recipients."); + throw new ServiceException($"SQL Failure saving list of job error recipients."); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure saving list of job error recipients."); + throw new ServiceException($"Failure saving list of job error recipients."); + } + } + + public void DeleteList(ICollection jobErrorRecipient) + { + try + { + _jobErrorRecipientRepository.RemoveList(jobErrorRecipient); + _unitOfWork.Complete(); + } + catch (SqlException ex) + { + Log.Error(ex, $"SQL Failure removing list of job error recipients."); + throw new ServiceException($"SQL Failure removing list of job error recipients."); + } + catch (Exception ex) + { + Log.Error(ex, $"Failure removing list of job error recipients."); + throw new ServiceException($"Failure removing list of job error recipients."); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/JobService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/JobService.cs new file mode 100644 index 0000000..abde657 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/JobService.cs @@ -0,0 +1,583 @@ +using System.Collections.Generic; +using Microsoft.Data.SqlClient; +using Newtonsoft.Json; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Api.Internal.DispatcherLib.Repositories; +using LCU.Api.Internal.DispatcherSharedLib.Utilities; +using LCU.Api.Internal.Dispatcher.Configurations; +using LCU.Library.Internal.TypeExtensions.ObjectExtensions; +using LCU.Library.Internal.DispatcherModels.Models.Requests; +using LCU.Library.Internal.DispatcherModels.Models.Results; +using LCU.Library.Internal.Logging.Performance; + +namespace LCU.Api.Internal.Dispatcher.Services; + +public class JobService : IJobService +{ + private readonly IJobRepository _jobRepository; + private readonly IWorkItemService _workItemService; + private readonly IWorkItemExecutionService _workItemExecutionService; + private readonly IUnitOfWork _unitOfWork; + private readonly ApiConfiguration _apiConfiguration; + + public JobService(IUnitOfWork unitOfWork, + IJobRepository jobRepository, + IWorkItemService workItemService, + IWorkItemExecutionService workItemExecutionService, + ApiConfiguration apiConfiguration) + { + _unitOfWork = unitOfWork; + _jobRepository = jobRepository; + _workItemService = workItemService; + _workItemExecutionService = workItemExecutionService; + _apiConfiguration = apiConfiguration; + } + + public async Task> GetFilteredJobs(Filter filter) + { + try + { + var performanceMonitorId = Guid.NewGuid(); + var getFilteredJobsQueryMonitor = new PerformanceMonitor("GetFilteredJobs", performanceMonitorId); + + var filteredJobs = await _jobRepository.FilteredListAsync(filter, + _apiConfiguration.NumberOfWorkItemExecutions, + _apiConfiguration.NumberOfExecutionMessages); + + getFilteredJobsQueryMonitor.LogPerformance(); + + return filteredJobs; + } + catch (Exception exception) + { + Log.Error(exception, "Failure retrieving filtered dispatcher jobs."); + throw new ServiceException("Failure retrieving filtered dispatcher jobs."); + } + } + + public async Task> GetAsync() + { + try + { + return await _jobRepository.ListAsync(_apiConfiguration.NumberOfWorkItemExecutions, _apiConfiguration.NumberOfExecutionMessages); + } + catch (Exception exception) + { + Log.Error(exception, "Failure retrieving dispatcher jobs."); + throw new ServiceException("Failure retrieving dispatcher jobs."); + } + } + + public async Task> GetInactiveAsync() + { + try + { + return await _jobRepository.ListInactiveAsync(_apiConfiguration.NumberOfWorkItemExecutions, _apiConfiguration.NumberOfExecutionMessages); + } + catch (Exception exception) + { + Log.Error(exception, "Failure retrieving inactive dispatcher jobs."); + throw new ServiceException("Failure retrieving inactive dispatcher jobs."); + } + } + + public async Task> GetInactiveExcludeSoftDeletedTrackedAsync() + { + try + { + return await _jobRepository.ListInactiveExcludeSoftDeletedTrackedAsync(_apiConfiguration.NumberOfWorkItemExecutions, _apiConfiguration.NumberOfExecutionMessages); + } + catch (Exception exception) + { + Log.Error(exception, "Failure retrieving non-SoftDeleted inactive dispatcher jobs."); + throw new ServiceException("Failure retrieving non-SoftDeleted inactive dispatcher jobs."); + } + } + + public async Task> GetSoftDeletedTrackedAsync(DateTime cutOffDate) + { + try + { + return await _jobRepository.ListSoftDeletedTrackedAsync(_apiConfiguration.NumberOfWorkItemExecutions, _apiConfiguration.NumberOfExecutionMessages, cutOffDate); + } + catch (Exception exception) + { + Log.Error(exception, "Failure retrieving soft deleted dispatcher jobs."); + throw new ServiceException("Failure retrieving soft deleted dispatcher jobs."); + } + } + + public async Task GetByIdAsync(int id) + { + try + { + var job = await _jobRepository.FindByIdAsync(id, + _apiConfiguration.NumberOfWorkItemExecutions, + _apiConfiguration.NumberOfExecutionMessages); + ArgumentNullException.ThrowIfNull(job); + return job; + } + catch (Exception exception) + { + Log.Error(exception, $"Failure retrieving dispatcher jobs with id {id}."); + throw new ServiceException($"Failure retrieving dispatcher jobs with id {id}."); + } + } + + public async Task GetByIdExcludingWorkItemsAsync(int id) + { + try + { + var job = await _jobRepository.FindByIdExcludingWorkItemsAsync(id); + ArgumentNullException.ThrowIfNull(job); + return job; + } + catch (Exception exception) + { + Log.Error(exception, $"Failure retrieving dispatcher jobs with id {id}."); + throw new ServiceException($"Failure retrieving dispatcher jobs with id {id}."); + } + } + + public async Task GetByWorkItemIdAsync(int workItemId) + { + try + { + var job = await _jobRepository.FindByWorkItemIdAsync(workItemId, + _apiConfiguration.NumberOfWorkItemExecutions, + _apiConfiguration.NumberOfExecutionMessages); + ArgumentNullException.ThrowIfNull(job); + return job; + } + catch (Exception exception) + { + Log.Error(exception, $"Failure retrieving dispatcher jobs by WorkItemId {workItemId}."); + throw new ServiceException($"Failure retrieving dispatcher jobs by WorkItemId {workItemId}."); + } + } + + public async Task GetByWorkItemIdExcludingWorkItemsAsync(int workItemId) + { + try + { + var job = await _jobRepository.FindByWorkItemIdExcludingWorkItemsAsync(workItemId); + ArgumentNullException.ThrowIfNull(job); + return job; + } + catch (Exception exception) + { + Log.Error(exception, $"Failure retrieving dispatcher jobs excluding WorkItems by WorkItemId {workItemId}."); + throw new ServiceException($"Failure retrieving dispatcher jobs excluding WorkItems by WorkItemId {workItemId}."); + } + } + + public async Task GetByWorkItemIdExcludingWorkItemsIncludeErrorRecipientsAsync(int workItemId) + { + try + { + var job = await _jobRepository.FindByWorkItemIdExcludingWorkItemsIncludeErrorRecipientsAsync(workItemId); + ArgumentNullException.ThrowIfNull(job); + return job; + } + catch (Exception exception) + { + Log.Error(exception, $"Failure retrieving dispatcher jobs excluding WorkItems by WorkItemId {workItemId}."); + throw new ServiceException($"Failure retrieving dispatcher jobs excluding WorkItems by WorkItemId {workItemId}."); + } + } + + public async Task> GetJobsReadyToBeScheduled() + { + try + { + var result = await _jobRepository.FindJobsReadyToBeScheduled(DateTime.Now); + return result; + } + catch (Exception exception) + { + Log.Error(exception, "Failure getting pending Jobs"); + throw new ServiceException("Failure getting pending Jobs"); + } + } + + public async Task> GetErrorMonitoringJobs() + { + try + { + var result = await _jobRepository.FindJobsPastDateByStatuses(DateTime.Now, + _apiConfiguration.NumberOfWorkItemExecutions, + _apiConfiguration.NumberOfExecutionMessages, + _apiConfiguration.JobStatusesToMonitorByTimeLimit); + ArgumentNullException.ThrowIfNull(result); + var erroredAndStuckJobs = result.Where(job => job.JobStatus == JobStatus.Failed).ToList(); + if (_apiConfiguration.JobStatusesToMonitorByTimeLimit is not null && _apiConfiguration.JobStatusesToMonitorByTimeLimit.Count() > 0) + { + erroredAndStuckJobs.AddRange(GetJobsInStatusLongerThanTimeLimit(result.Except(erroredAndStuckJobs).ToList(), + _apiConfiguration.ProcessingLimitInMinutes)); + } + + return erroredAndStuckJobs; + } + catch (Exception exception) + { + Log.Error(exception, "Failure getting next errored and stuck in processing Jobs"); + throw new ServiceException("Failure getting next errored and stuck in processing Jobs"); + } + } + + public List GetJobsInStatusLongerThanTimeLimit(List matchingStatusJobs, int processingLimitInMinutes) + { + var stuckInStatusJobs = new List(); + + foreach (var job in matchingStatusJobs) + { + var currentWorkItem = job.WorkItems.FirstOrDefault(wi => wi.CurrentWorkItemExecution != null); + if (currentWorkItem == null) + { + Log.Warning($"Current Work Item came back as null when attempting to get stuck in processing Jobs. JobId: {job.JobId}"); + continue; + } + + var startTime = Convert.ToDateTime(currentWorkItem.CurrentWorkItemExecution.StartedTimestamp); + var minutesInStatus = DateTime.Now.Subtract(startTime).TotalMinutes; + if (minutesInStatus > processingLimitInMinutes) + { + stuckInStatusJobs.Add(job); + } + } + + return stuckInStatusJobs; + } + + public async Task SaveAsync(Job job) + { + try + { + var result = await _jobRepository.AddAsync(job); + await _unitOfWork.CompleteAsync(); + ArgumentNullException.ThrowIfNull(result); + return result; + } + catch (Exception exception) + { + Log.Error(exception, "Failure saving dispatcher job", job); + throw new ServiceException("Failure saving dispatcher job"); + } + } + + public async Task CompleteJobAndWorkItem(Job job, WorkItemExecution currentWorkItemExecution, StatusType workItemFinalStatus) + { + try + { + job.ResetRunTimeAndStatus(); + CronHelpers.SetNextScheduledRunTime(ref job); + var updatedJob = await UpdateExcludingWorkItemsAsyncWithoutSave(job); + if (updatedJob == null) + { + throw new Exception($"Failure to setup update for job. Job:{JsonConvert.SerializeObject(job)}"); + } + + var updatedWorkItemExecution = await _workItemExecutionService.UpdateCompleteWorkItemExecutionWithoutSave(currentWorkItemExecution, workItemFinalStatus); + if (updatedWorkItemExecution == null) + { + throw new Exception($"Failure to setup update for WorkItemExecution. WorkItemExecution:{JsonConvert.SerializeObject(currentWorkItemExecution)}"); + } + await _unitOfWork.CompleteAsync(); + Log.Information("Job Id {JobId} was saved with status {Status}", job.JobId, job.JobStatus); + } + catch (SqlException sqlException) + { + var message = $"SQL Failure to Complete Job and WorkItemExecution. JobId: {job.JobId}, WorkItemExecutionId: {currentWorkItemExecution.WorkItemExecutionId}."; + Log.Error(sqlException, message); + throw new ServiceException($"{message} ExceptionMessage: {sqlException.Message}"); + } + catch (Exception exception) + { + var message = $"Failure to Complete Job and WorkItemExecution. JobId: {job.JobId}, WorkItemExecutionId: {currentWorkItemExecution.WorkItemExecutionId}."; + Log.Error(exception, message); + throw new ServiceException($"{message} ExceptionMessage: {exception.Message}"); + } + } + + public async Task CompleteWorkItemAndContinueProcessingJob(Job job, WorkItemExecution currentWorkItemExecution, StatusType workItemFinalStatus) + { + try + { + job.JobStatus = JobStatus.Processing; + var updatedJob = await UpdateExcludingWorkItemsAsyncWithoutSave(job); + if (updatedJob == null) + { + throw new Exception($"Failure to setup update for job. Job:{JsonConvert.SerializeObject(job)}"); + } + + var updatedWorkItemExecution = await _workItemExecutionService.UpdateCompleteWorkItemExecutionWithoutSave(currentWorkItemExecution, workItemFinalStatus); + if (updatedWorkItemExecution == null) + { + throw new Exception($"Failure to setup update for WorkItemExecution. WorkItemExecution:{JsonConvert.SerializeObject(currentWorkItemExecution)}"); + } + await _unitOfWork.CompleteAsync(); + Log.Information("Job Id {JobId} was saved with status {Status}", job.JobId, job.JobStatus); + } + catch (SqlException sqlException) + { + var message = $"SQL Failure to Complete Job and WorkItemExecution. JobId: {job.JobId}, WorkItemExecutionId: {currentWorkItemExecution.WorkItemExecutionId}."; + Log.Error(sqlException, message); + throw new ServiceException($"{message} ExceptionMessage: {sqlException.Message}"); + } + catch (Exception exception) + { + var message = $"Failure to Complete Job and WorkItemExecution. JobId: {job.JobId}, WorkItemExecutionId: {currentWorkItemExecution.WorkItemExecutionId}."; + Log.Error(exception, message); + throw new ServiceException($"{message} ExceptionMessage: {exception.Message}"); + } + } + + public async Task UpdateAsync(Job job) + { + try + { + var updatedJob = await _jobRepository.Update(job); + await _unitOfWork.CompleteAsync(); + + return updatedJob; + } + catch (SqlException sqlException) + { + Log.Error(sqlException, "SQL Failure updating dispatcher job", job); + throw new ServiceException("SQL Failure updating dispatcher job"); + } + catch (Exception exception) + { + Log.Error(exception, "Failure updating dispatcher job", job); + throw new ServiceException("Failure updating dispatcher job"); + } + } + + public async Task UpdateExcludingWorkItemsAsyncWithoutSave(Job job) + { + return await _jobRepository.Update(job); + } + + public async Task RemoveSoftDeleteById(int id) + { + try + { + var existing = await GetByIdAsync(id); + ArgumentNullException.ThrowIfNull(existing); + + existing.SoftDeleteDate = null; + _jobRepository.Update(existing); + await _unitOfWork.CompleteAsync(); + + return existing; + } + catch (Exception exception) + { + Log.Error(exception, $"Failure to remove soft delete date for dispatcher job. JobId: {id}"); + throw new ServiceException($"Failure to remove soft delete date for dispatcher job. JobId: {id}"); + } + } + + public async Task DeleteAsync(int jobId) + { + try + { + var job = await _jobRepository.FindByIdAsync(jobId, + _apiConfiguration.NumberOfWorkItemExecutions, + _apiConfiguration.NumberOfExecutionMessages); + ArgumentNullException.ThrowIfNull(job); + + _jobRepository.Remove(job); + await _unitOfWork.CompleteAsync(); + } + catch (SqlException sqlException) + { + Log.Error(sqlException, $"SQL Failure removing dispatcher job. JobId: {jobId}"); + throw new ServiceException($"SQL Failure removing dispatcher job. JobId: {jobId}"); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure removing dispatcher job. JobId: {jobId}"); + throw new ServiceException($"Failure removing dispatcher job. JobId: {jobId}"); + } + } + + public async Task DeleteFromCleanupAsync(Job job) + { + try + { + ArgumentNullException.ThrowIfNull(job); + + _jobRepository.Remove(job); + } + catch (SqlException sqlException) + { + Log.Error(sqlException, $"SQL Failure removing dispatcher job. JobId: {job.JobId}"); + throw new ServiceException($"SQL Failure removing dispatcher job. JobId: {job.JobId}"); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure removing dispatcher job. JobId: {job.JobId}"); + throw new ServiceException($"Failure removing dispatcher job. JobId: {job.JobId}"); + } + } + + public async Task RunJobNowAsync(Job job) + { + try + { + job = await ResetJob(job); + job.NextRunTime = DateTime.Now; + job.JobStatus = JobStatus.ReadyToSchedule; + var updatedJob = await UpdateAsync(job); + return updatedJob; + } + catch (Exception exception) + { + Log.Error(exception, "Failure to set job to run now."); + throw new ServiceException("Failure to set job to run now."); + } + } + + public async Task RunJobNowByIdAsync(int id) + { + try + { + var job = await GetByIdAsync(id); + var updatedJob = await RunJobNowAsync(job); + return updatedJob; + } + catch (Exception exception) + { + Log.Error(exception, $"Failure to set job to run now by id. Id: {id}"); + throw new ServiceException($"Failure to set job to run now by id. Id: {id}"); + } + } + + public async Task DeactivateJobAsync(Job job) + { + try + { + job = await ResetJob(job); + var updatedJob = await UpdateAsync(job); + return updatedJob; + } + catch (Exception exception) + { + + Log.Error(exception, "Failure to deactivate job."); + throw new ServiceException("Failure to deactivate job."); + } + } + + public async Task RescheduleJobAsync(Job job) + { + try + { + job = await ResetJob(job); + CronHelpers.SetNextScheduledRunTime(ref job); + var updatedJob = await UpdateAsync(job); + Log.Information("Job Id {JobId} was saved with status {Status}", job.JobId, job.JobStatus); + return updatedJob; + } + catch (Exception exception) + { + Log.Error(exception, "Failure to reschedule job."); + throw new ServiceException("Failure to reschedule job."); + } + } + + public async Task ResetJob(Job job) + { + foreach (var workItem in job.WorkItems) + { + var executionsToReset = await _workItemExecutionService.GetAllUnFinishedWorkItemExecutions(workItem.WorkItemId); + foreach (var workItemExecution in executionsToReset) + { + workItemExecution.StartedTimestamp = workItemExecution.StartedTimestamp == null ? + DateTime.Now : + workItemExecution.StartedTimestamp; + workItemExecution.CompletedTimestamp = workItemExecution.CompletedTimestamp == null ? + DateTime.Now : + workItemExecution.CompletedTimestamp; + + var updatedExecution = await _workItemExecutionService.UpdateAsync(workItemExecution); + ArgumentNullException.ThrowIfNull(updatedExecution); + } + } + + job.ResetRunTimeAndStatus(); + return job; + } + + public async Task CopyJobAsync(CopyJobRequest request) + { + try + { + var job = (await GetByIdAsync(request.JobId)).DeepCopy(); + + if (job == null) + { + throw new Exception($"Job with id {request.JobId} not found."); + } + + job.Name = $"(COPY) {job.Name}"; + job.JobId = 0; + job.CronSchedule = null; + job.CreatedAt = DateTime.Now; + job.ModifiedDate = null; + job.ResetRunTimeAndStatus(); + + foreach (var errorRecipient in job.JobErrorRecipients) + { + errorRecipient.JobErrorRecipientId = 0; + errorRecipient.JobId = 0; + } + + if (request.ChangeEnvironment) + { + job.JobTags = null; + + foreach (var workItem in job.WorkItems) + { + workItem.WorkItemId = 0; + workItem.JobId = 0; + workItem.Url = null; + workItem.WorkItemExecutions = null; + } + + return job; + } + + foreach (var workItem in job.WorkItems) + { + workItem.WorkItemId = 0; + workItem.JobId = 0; + workItem.Url = null; + workItem.WorkItemExecutions = null; + } + + foreach (var jobTag in job.JobTags) + { + jobTag.JobTagId = 0; + jobTag.JobId = 0; + jobTag.Tag = null; + } + + var savedJob = await SaveAsync(job); + + if (savedJob == null) + { + throw new Exception("Failure to save new job."); + } + + return savedJob; + } + catch (Exception exception) + { + var message = $"Failure to copy dispatcher job. Request: {JsonConvert.SerializeObject(request)}"; + Log.Error(exception, message); + throw new ServiceException($"{message}. Exception: {exception.Message}"); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/JobTagService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/JobTagService.cs new file mode 100644 index 0000000..a312cc0 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/JobTagService.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using Microsoft.Data.SqlClient; +using LCU.Api.Internal.DispatcherLib.Repositories; +namespace LCU.Api.Internal.Dispatcher.Services; + +public class JobTagService : IJobTagService +{ + private readonly IJobTagRepository _jobTagRepository; + private readonly IUnitOfWork _unitOfWork; + + public JobTagService(IUnitOfWork unitOfWork, + IJobTagRepository jobTagRepository) + { + _unitOfWork = unitOfWork; + _jobTagRepository = jobTagRepository; + } + + public async Task> GetAsync() + { + try + { + return await _jobTagRepository.ListAsync(); + } + catch (Exception exception) + { + Log.Error(exception, "Failure retrieving JobTags."); + throw new ServiceException("Failure retrieving JobTags."); + } + } + + public async Task> GetByTagIdAsync(int tagId) + { + try + { + ArgumentNullException.ThrowIfNull(tagId); + return await _jobTagRepository.FindByTagIdAsync(tagId); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure retrieving dispatcher Job Tags. TagId: {tagId}"); + throw new ServiceException($"Failure retrieving dispatcher Job Tags. TagId: {tagId}"); + } + } + + public async Task SaveAsync(JobTag jobTag) + { + try + { + var result = await _jobTagRepository.AddAsync(jobTag); + await _unitOfWork.CompleteAsync(); + ArgumentNullException.ThrowIfNull(result); + return result; + } + catch (Exception exception) + { + Log.Error(exception, $"Failure saving dispatcher JobTag. JobTagId: {jobTag.JobTagId}"); + throw new ServiceException($"Failure saving dispatcher JobTag. JobTagId: {jobTag.JobTagId}"); + } + } + + public async Task Delete(int jobTagId) + { + try + { + var jobTag = await _jobTagRepository.FindByIdAsync(jobTagId); + ArgumentNullException.ThrowIfNull(jobTag); + _jobTagRepository.Remove(jobTag); + await _unitOfWork.CompleteAsync(); + } + catch (SqlException sqlException) + { + Log.Error(sqlException, $"SQL Failure removing dispatcher JobTag. JobTagId: {jobTagId}"); + throw new ServiceException($"SQL Failure removing dispatcher JobTag. JobTagId: {jobTagId}"); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure removing dispatcher JobTag. JobTagId: {jobTagId}"); + throw new ServiceException($"Failure removing dispatcher JobTag. JobTagId: {jobTagId}"); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/NotificationService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/NotificationService.cs new file mode 100644 index 0000000..6cf3952 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/NotificationService.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using LCU.Library.Internal.NotificationsModels.Models; +using LCU.Api.Internal.DispatcherSharedLib.Configurations; +using LCU.Api.Internal.DispatcherSharedLib.Utilities; +using LCU.Api.Internal.Dispatcher.DataAccess; + +namespace LCU.Api.Internal.Dispatcher.Services; + +public class NotificationService : INotificationService +{ + private readonly INotificationsApiDao _notificationsApiDao; + + public NotificationService(INotificationsApiDao notificationsApiDao) + { + _notificationsApiDao = notificationsApiDao; + } + + public async Task SendEmailAsync(string recipient, string subject, string emailTemplate) + { + try + { + var emailRequest = new EmailRequest + { + Recipient = new List() { recipient }, + Subject = subject, + Body = emailTemplate + }; + + await _notificationsApiDao.SendEmailAsync(emailRequest); + + return true; + } + catch (Exception exception) + { + Log.Error(exception, $"Failure to send email from Dispatcher"); + return false; + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueCompletionService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueCompletionService.cs new file mode 100644 index 0000000..1b9007e --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueCompletionService.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace LCU.Api.Internal.Dispatcher.Services.QueueServices; + +public interface IQueueCompletionService +{ + Task QueueCompletion(DispatcherReturnResponse request, List executionMessages = null, bool successful = true); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueDetermineOperationService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueDetermineOperationService.cs new file mode 100644 index 0000000..cbb625c --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueDetermineOperationService.cs @@ -0,0 +1,10 @@ +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.DispatcherModels.Models.Requests; + +namespace LCU.Api.Internal.Dispatcher.Services.QueueServices +{ + public interface IQueueDetermineOperationService + { + Task QueueDetermineOperation(Job job, WorkItem workItem, DetermineOperationType type); + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueExecutionService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueExecutionService.cs new file mode 100644 index 0000000..6ee5491 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueExecutionService.cs @@ -0,0 +1,7 @@ +namespace LCU.Api.Internal.Dispatcher.Services.QueueServices; + +public interface IQueueExecutionService +{ + Task QueueExecution(WorkItemExecution request); + Task QueueRetryExecution(WorkItemExecution request); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueJobFailureService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueJobFailureService.cs new file mode 100644 index 0000000..5aff306 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueJobFailureService.cs @@ -0,0 +1,10 @@ +using LCU.Library.Internal.DispatcherModels.Models.Requests; +using System.Collections.Generic; + +namespace LCU.Api.Internal.Dispatcher.Services.QueueServices +{ + public interface IQueueJobFailureService + { + public Task QueueJobFailure(int jobId, int workItemId, List messages); + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueSendErroredJobAlertService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueSendErroredJobAlertService.cs new file mode 100644 index 0000000..95a05a2 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/IQueueSendErroredJobAlertService.cs @@ -0,0 +1,10 @@ +using LCU.Library.Internal.DispatcherModels.Models.Requests; + +namespace LCU.Api.Internal.Dispatcher.Services.QueueServices +{ + public interface IQueueSendErroredJobAlertService + { + public Task QueueSendErroredJobAlert(BatchedErroredJobAlertRequest request); + public Task QueueSendErroredJobAlert(Job job, WorkItem workItem); + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueCompletionService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueCompletionService.cs new file mode 100644 index 0000000..31008b5 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueCompletionService.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Threading; +using MassTransit; +using Newtonsoft.Json; +using ApiResponse = LCU.Library.Internal.ApiResponse.Models; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Api.Internal.Dispatcher.Configurations; +namespace LCU.Api.Internal.Dispatcher.Services.QueueServices; + +public class QueueCompletionService : IQueueCompletionService +{ + private readonly ISendEndpoint _sendEndpoint; + private readonly ReceiveEndpointConfigurations _queueReceiveEndpointsConfiguration; + + public QueueCompletionService(IBusControl busControl, + ReceiveEndpointConfigurations queueReceiveEndpointsConfiguration) + { + _queueReceiveEndpointsConfiguration = queueReceiveEndpointsConfiguration; + _sendEndpoint = busControl.GetSendEndpoint(_queueReceiveEndpointsConfiguration.CompletionReceiveEndpoint.ReceiveEndpointUri).Result; + } + + public async Task QueueCompletion(DispatcherReturnResponse request, List messages = null, bool successful = true) + { + try + { + ArgumentNullException.ThrowIfNull(request); + + var response = new ApiResponse.Response() + { + Successful = successful, + ResponseStack = messages == null ? new List() : messages, + ResponsePayload = JsonConvert.SerializeObject(request) + }; + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(response, cancellationTokenSource.Token); + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Failed to queue completion workitem request. WorkItemId: {request?.WorkItemId}"); + throw new ServiceException($"Failed to queue completion workitem request. WorkItemId: {request?.WorkItemId}"); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueDetermineOperationService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueDetermineOperationService.cs new file mode 100644 index 0000000..8cedb8c --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueDetermineOperationService.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Threading; +using LCU.Api.Internal.Dispatcher.Configurations; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.DispatcherModels.Models.Requests; +using MassTransit; +using Newtonsoft.Json; +using ApiResponse = LCU.Library.Internal.ApiResponse.Models; + +namespace LCU.Api.Internal.Dispatcher.Services.QueueServices +{ + public class QueueDetermineOperationService : IQueueDetermineOperationService + { + private readonly ISendEndpoint _sendEndpoint; + private readonly ReceiveEndpointConfigurations _queueReceiveEndpointsConfiguration; + + public QueueDetermineOperationService(IBusControl busControl, + ReceiveEndpointConfigurations queueReceiveEndpointsConfiguration) + { + _queueReceiveEndpointsConfiguration = queueReceiveEndpointsConfiguration; + _sendEndpoint = busControl.GetSendEndpoint(_queueReceiveEndpointsConfiguration.DetermineOperationReceiveEndpoint.ReceiveEndpointUri).Result; + } + + public async Task QueueDetermineOperation(Job job, WorkItem workItem, DetermineOperationType type) + { + try + { + var request = new DetermineOperationRequest + { + CurrentJob = job, + CurrentWorkItem = workItem, + DetermineOperationType = type + }; + + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(request, cancellationTokenSource.Token); + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Failed to queue determine operation request. JobName: {job.Name} WorkItemId: {workItem.WorkItemId}"); + throw new ServiceException($"Failed to queue determine operation request. JobName: {job.Name} WorkItemId: {workItem.WorkItemId}"); + } + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueExecutionService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueExecutionService.cs new file mode 100644 index 0000000..5aa8b59 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueExecutionService.cs @@ -0,0 +1,55 @@ +using System.Threading; +using MassTransit; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Api.Internal.Dispatcher.Configurations; +namespace LCU.Api.Internal.Dispatcher.Services.QueueServices; + +public class QueueExecutionService : IQueueExecutionService +{ + private readonly ISendEndpoint _sendEndpoint; + private readonly IWorkItemExecutionService _workItemExecutionService; + private readonly ReceiveEndpointConfigurations _queueReceiveEndpointsConfiguration; + + public QueueExecutionService(IBusControl busControl, + IWorkItemExecutionService workItemExecutionService, + ReceiveEndpointConfigurations queueReceiveEndpointsConfiguration) + { + _queueReceiveEndpointsConfiguration = queueReceiveEndpointsConfiguration; + _workItemExecutionService = workItemExecutionService; + _sendEndpoint = busControl.GetSendEndpoint(_queueReceiveEndpointsConfiguration.ExecutionReceiveEndpoint.ReceiveEndpointUri).Result; + } + + public async Task QueueExecution(WorkItemExecution request) + { + try + { + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(request, cancellationTokenSource.Token); + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Failed to queue workitemexecution request. WorkItemExecutionId: {request?.WorkItemExecutionId}"); + throw new ServiceException($"Failed to queue workitemexecution request. WorkItemExecutionId: {request?.WorkItemExecutionId}"); + } + } + + public async Task QueueRetryExecution(WorkItemExecution request) + { + try + { + var updatedWorkItemExection = _workItemExecutionService.UpdateProcessingWorkItemExecution(request); + + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(request, cancellationTokenSource.Token); + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Failed to queue workitemexecution request. WorkItemExecutionId: {request?.WorkItemExecutionId}"); + throw new ServiceException($"Failed to queue workitemexecution request. WorkItemExecutionId: {request?.WorkItemExecutionId}"); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueJobFailureService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueJobFailureService.cs new file mode 100644 index 0000000..406d172 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueJobFailureService.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Threading; +using LCU.Api.Internal.Dispatcher.Configurations; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.ApiResponse.Models; +using LCU.Library.Internal.DispatcherModels.Entities; +using LCU.Library.Internal.DispatcherModels.Models; +using LCU.Library.Internal.DispatcherModels.Models.Requests; +using MassTransit; +using Newtonsoft.Json; +using ApiResponse = LCU.Library.Internal.ApiResponse.Models; + +namespace LCU.Api.Internal.Dispatcher.Services.QueueServices +{ + public class QueueJobFailureService : IQueueJobFailureService + { + private readonly ISendEndpoint _sendEndpoint; + private readonly ReceiveEndpointConfigurations _queueReceiveEndpointsConfiguration; + + public QueueJobFailureService(IBusControl busControl, + ReceiveEndpointConfigurations queueReceiveEndpointsConfiguration) + { + _queueReceiveEndpointsConfiguration = queueReceiveEndpointsConfiguration; + _sendEndpoint = busControl.GetSendEndpoint(_queueReceiveEndpointsConfiguration.JobFailureReceiveEndpoint.ReceiveEndpointUri).Result; + } + public async Task QueueJobFailure(int jobId, int workItemId, List messages) + { + try + { + var request = new JobFailureRequest() + { + JobId = jobId, + WorkItemId = workItemId, + ExecutionMessages = messages + }; + + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(request, cancellationTokenSource.Token); + } + catch (Exception exception) + { + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Failed to queue job failure request. JobId: {jobId}, WorkItemId: {workItemId}"); + throw new ServiceException($"Failed to queue job failure request. JobId: {jobId}, WorkItemId: {workItemId}"); + } + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueSendErroredJobAlertService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueSendErroredJobAlertService.cs new file mode 100644 index 0000000..688bad5 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/QueueServices/QueueSendErroredJobAlertService.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Threading; +using LCU.Api.Internal.Dispatcher.Configurations; +using LCU.Api.Internal.MaidBotLib.Models; +using LCU.Library.Internal.DispatcherModels.Models.Requests; +using MassTransit; +using Newtonsoft.Json; +using ApiResponse = LCU.Library.Internal.ApiResponse.Models; + +namespace LCU.Api.Internal.Dispatcher.Services.QueueServices +{ + public class QueueSendErroredJobAlertService : IQueueSendErroredJobAlertService + { + private readonly ISendEndpoint _sendEndpoint; + private readonly ReceiveEndpointConfigurations _queueReceiveEndpointsConfiguration; + + public QueueSendErroredJobAlertService(IBusControl busControl, + ReceiveEndpointConfigurations queueReceiveEndpointConfigurations) + { + _queueReceiveEndpointsConfiguration = queueReceiveEndpointConfigurations; + _sendEndpoint = busControl.GetSendEndpoint(_queueReceiveEndpointsConfiguration.SendErroredJobAlertReceiveEndpoint.ReceiveEndpointUri).Result; + } + public async Task QueueSendErroredJobAlert(BatchedErroredJobAlertRequest request) + { + try + { + ArgumentNullException.ThrowIfNull(request); + var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await _sendEndpoint.Send(request, cancellationTokenSource.Token); + } + catch (Exception exception) + { + var workItemIds = string.Join(",", + request.ErroredJobAlertRequests + .Where(x => x.CurrentWorkItem != null) + .Select(x => x.CurrentWorkItem.WorkItemId)); + + Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), + Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)) + .Fatal(exception, $"Failed to queue send errored job alert workitem requests. WorkItemIds: {workItemIds}"); + throw new ServiceException($"Failed to queue send errored job alert workitem requests. WorkItemIds: {workItemIds}"); + } + } + + public async Task QueueSendErroredJobAlert(Job job, WorkItem workItem) + { + var request = new ErroredJobAlertRequest() + { + CurrentJob = job, + CurrentWorkItem = workItem + }; + var requestList = new List() + { + request + }; + var batchedRequest = new BatchedErroredJobAlertRequest() + { + ErroredJobAlertRequests = requestList + }; + await QueueSendErroredJobAlert(batchedRequest); + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/TagService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/TagService.cs new file mode 100644 index 0000000..bd6daa0 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/TagService.cs @@ -0,0 +1,135 @@ +using System.Collections.Generic; +using Microsoft.Data.SqlClient; +using LCU.Api.Internal.DispatcherLib.Repositories; +namespace LCU.Api.Internal.Dispatcher.Services; + +public class TagService : ITagService +{ + private readonly ITagRepository _tagRepository; + private readonly IUnitOfWork _unitOfWork; + private readonly IJobTagService _jobTagService; + + public TagService(IUnitOfWork unitOfWork, + ITagRepository tagRepository, IJobTagService jobTagService) + { + _unitOfWork = unitOfWork; + _tagRepository = tagRepository; + _jobTagService = jobTagService; + } + + public async Task> GetAsync() + { + try + { + return await _tagRepository.ListAsync(); + } + catch (Exception exception) + { + Log.Error(exception, "Failure retrieving dispatcher Tags."); + throw new ServiceException("Failure retrieving dispatcher Tags."); + } + } + + public async Task GetByIdAsync(int id) + { + try + { + var tag = await _tagRepository.FindByIdAsync(id); + ArgumentNullException.ThrowIfNull(tag); + return tag; + } + catch (ArgumentNullException nullEx) + { + Log.Error(nullEx, $"Failure retrieving dispatcher Tags. TagId: {id}"); + throw new ArgumentNullException($"Failure retrieving dispatcher Tags. TagId: {id}"); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure retrieving dispatcher Tags. TagId: {id}"); + throw new ServiceException($"Failure retrieving dispatcher Tags. TagId: {id}"); + } + } + + public async Task SaveAsync(Tag tag) + { + try + { + var result = await _tagRepository.AddAsync(tag); + await _unitOfWork.CompleteAsync(); + ArgumentNullException.ThrowIfNull(result); + return result; + } + catch (Exception exception) + { + Log.Error(exception, $"Failure saving dispatcher Tag. TagId: {tag.TagId}"); + throw new ServiceException($"Failure saving dispatcher Tag. TagId: {tag.TagId}"); + } + } + + public async Task UpdateAsync(Tag tag) + { + try + { + _tagRepository.Update(tag); + await _unitOfWork.CompleteAsync(); + + return tag; + } + catch (SqlException sqlException) + { + Log.Error(sqlException, $"SQL Failure updating dispatcher Tag. TagId: {tag.TagId}"); + throw new ServiceException($"SQL Failure updating dispatcher Tag. TagId: {tag.TagId}"); + } + catch (Exception exception) + { + Log.Error(exception, $"SQL Failure updating dispatcher Tag. TagId: {tag.TagId}"); + throw new ServiceException($"SQL Failure updating dispatcher Tag. TagId: {tag.TagId}"); + } + } + + public async Task DeleteAsync(int tagId) + { + try + { + var jobTags = await _jobTagService.GetByTagIdAsync(tagId); + if (jobTags == null || !jobTags.Any()) + { + await RemoveJobTags(jobTags.ToList()); + } + + var tag = await _tagRepository.FindByIdAsync(tagId); + ArgumentNullException.ThrowIfNull(tag); + _tagRepository.Remove(tag); + await _unitOfWork.CompleteAsync(); + } + catch (SqlException sqlException) + { + Log.Error(sqlException, $"SQL Failure removing dispatcher Tag. TagId: {tagId}"); + throw new ServiceException($"SQL Failure removing dispatcher Tag. TagId: {tagId}"); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure removing dispatcher Tag. TagId: {tagId}"); + throw new ServiceException($"Failure removing dispatcher Tag. TagId: {tagId}"); + } + } + + private async Task RemoveJobTags(List jobTags) + { + bool isSuccessful = false; + try + { + foreach (var jobTag in jobTags) + { + await _jobTagService.Delete(jobTag.JobTagId); + isSuccessful = true; + } + return isSuccessful; + } + catch (Exception exception) + { + Log.Error(exception, "Failure to remove job tags."); + throw new ServiceException("Failure to remove job tags."); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/WorkItemExecutionService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/WorkItemExecutionService.cs new file mode 100644 index 0000000..9d466e1 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/WorkItemExecutionService.cs @@ -0,0 +1,150 @@ +using System.Collections.Generic; +using Microsoft.Data.SqlClient; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Api.Internal.DispatcherLib.Repositories; +namespace LCU.Api.Internal.Dispatcher.Services; + +public class WorkItemExecutionService : IWorkItemExecutionService +{ + private readonly IUnitOfWork _unitOfWork; + private readonly IWorkItemExecutionRepository _workItemExecutionRepository; + + public WorkItemExecutionService(IUnitOfWork unitOfWork, + IWorkItemExecutionRepository workItemExecutionRepository) + { + _unitOfWork = unitOfWork; + _workItemExecutionRepository = workItemExecutionRepository; + } + + public async Task GetByIdAsync(int id) + { + WorkItemExecution workItemExecution = null; + try + { + workItemExecution = await _workItemExecutionRepository.FindByIdAsync(id); + ArgumentNullException.ThrowIfNull(workItemExecution); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure retrieving dispatcher WorkItemExecution with id {id}."); + throw new ServiceException($"Failure retrieving dispatcher WorkItemExecution with id {id}."); + } + + return workItemExecution; + } + + public async Task UpdateCompleteWorkItemExecution(WorkItemExecution workItemExecution, StatusType finalStatus) + { + workItemExecution = WorkItemExecution.SetAsFinal(workItemExecution, finalStatus); + var updatedFailedWorkItem = await UpdateAsync(workItemExecution); + return updatedFailedWorkItem; + } + + public async Task UpdateCompleteWorkItemExecutionWithoutSave(WorkItemExecution workItemExecution, StatusType finalStatus) + { + workItemExecution = WorkItemExecution.SetAsFinal(workItemExecution, finalStatus); + var updatedFailedWorkItem = await UpdateAsyncWithoutSave(workItemExecution); + return updatedFailedWorkItem; + } + + public async Task UpdateProcessingWorkItemExecution(WorkItemExecution workItemExecution) + { + workItemExecution.Status = StatusType.Processing; + return await UpdateAsync(workItemExecution); + } + public async Task UpdateFailedWorkItemExecution(WorkItemExecution workItemExecution) + { + workItemExecution.Status = StatusType.Failed; + return await UpdateAsync(workItemExecution); + } + + public async Task UpdateAsync(WorkItemExecution workItemExecution) + { + try + { + var updatedWorkItemExecution = await UpdateAsyncWithoutSave(workItemExecution); + await _unitOfWork.CompleteAsync(); + + return updatedWorkItemExecution; + } + catch (SqlException sqlException) + { + Log.Error(sqlException, $"SQL Failure updating dispatcher WorkItemExecution with id {workItemExecution.WorkItemId}"); + throw new ServiceException($"SQL Failure updating dispatcher WorkItemExecution with id {workItemExecution.WorkItemId}"); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure updating dispatcher WorkItemExecution with id {workItemExecution.WorkItemId}"); + throw new ServiceException($"Failure updating dispatcher WorkItemExecution with id {workItemExecution.WorkItemId}"); + } + } + + public async Task UpdateAsyncWithoutSave(WorkItemExecution workItemExecution) + { + try + { + _workItemExecutionRepository.Update(workItemExecution); + + return workItemExecution; + } + catch (SqlException sqlException) + { + Log.Error(sqlException, $"SQL Failure updating dispatcher WorkItemExecution with id {workItemExecution.WorkItemId}"); + throw new ServiceException($"SQL Failure updating dispatcher WorkItemExecution with id {workItemExecution.WorkItemId}"); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure updating dispatcher WorkItemExecution with id {workItemExecution.WorkItemId}"); + throw new ServiceException($"Failure updating dispatcher WorkItemExecution with id {workItemExecution.WorkItemId}"); + } + } + + public async Task CreateWorkItemExecution(int workItemId) + { + var newWorkItemExecution = new WorkItemExecution(workItemId); + return await SaveAsync(newWorkItemExecution); + } + + public async Task SaveAsync(WorkItemExecution workItemExecution) + { + try + { + var result = await _workItemExecutionRepository.AddAsync(workItemExecution); + await _unitOfWork.CompleteAsync(); + return result; + } + catch (Exception exception) + { + Log.Error(exception, "Failure saving dispatcher WorkItemExecution", workItemExecution); + throw new ServiceException("Failure saving dispatcher WorkItemExecution"); + } + } + + public async Task> GetAllUnFinishedWorkItemExecutions(int workItemId) + { + try + { + var result = await _workItemExecutionRepository.FindUnFinishedWorkItemExecutions(workItemId); + return result; + } + catch (Exception exception) + { + Log.Error(exception, $"Failure retrieving dispatcher WorkItemExecutions with id {workItemId}."); + throw new ServiceException($"Failure retrieving dispatcher WorkItemExecutions with id {workItemId}."); + } + } + + public async Task> GetAllByWorkItemId(int workItemId) + { + try + { + var result = await _workItemExecutionRepository.FindAllByWorkItemId(workItemId); + return result; + } + catch (Exception exception) + { + Log.Error(exception, $"Failure retrieving dispatcher WorkItemExecutions with WorkItemId {workItemId}."); + throw new ServiceException($"Failure retrieving dispatcher WorkItemExecutions with WorkItemId {workItemId}."); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/WorkItemService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/WorkItemService.cs new file mode 100644 index 0000000..e775504 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Services/WorkItemService.cs @@ -0,0 +1,258 @@ +using LCU.Api.Internal.Dispatcher.Configurations; +using LCU.Api.Internal.DispatcherLib.Repositories; +using LCU.Api.Internal.DispatcherSharedLib.BuilderServices; +using LCU.Library.Internal.ApiResponse.Models; +using LCU.Library.Internal.LcuMassTransitModels; +using Microsoft.Data.SqlClient; +using Newtonsoft.Json; +using System.Collections.Generic; +namespace LCU.Api.Internal.Dispatcher.Services; + +public class WorkItemService : IWorkItemService +{ + private readonly IUnitOfWork _unitOfWork; + private readonly IWorkItemRepository _workItemRepository; + private readonly IWorkItemExecutionService _workItemExecutionService; + private readonly IActionBuilderService _actionBuilderService; + private readonly ConnectionConfiguration _connectionConfiguration; + private readonly ReceiveEndpointConfiguration _completionReceiveEndpoint; + private readonly ApiConfiguration _apiConfiguration; + + public WorkItemService(IUnitOfWork unitOfWork, + IWorkItemRepository workItemRepository, + IWorkItemExecutionService workItemExecutionService, + IActionBuilderService actionBuilderService, + ConnectionConfiguration connectionConfiguration, + ReceiveEndpointConfigurations queueReceiveEndpointsConfigurations, + ApiConfiguration apiConfiguration) + { + _unitOfWork = unitOfWork; + _workItemRepository = workItemRepository; + _workItemExecutionService = workItemExecutionService; + _actionBuilderService = actionBuilderService; + _connectionConfiguration = connectionConfiguration; + _completionReceiveEndpoint = queueReceiveEndpointsConfigurations.CompletionReceiveEndpoint; + _apiConfiguration = apiConfiguration; + } + + public async Task GetByIdAsync(int id) + { + WorkItem workItem = null; + try + { + workItem = await _workItemRepository.FindByIdAsync(id); + ArgumentNullException.ThrowIfNull(workItem); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure retrieving dispatcher workitem with id {id}."); + throw new ServiceException($"Failure retrieving dispatcher workitem with id {id}."); + } + + return workItem; + } + + public async Task> GetByJobIdAsync(int jobId) + { + IEnumerable workItems = null; + try + { + workItems = await _workItemRepository.FindByJobIdAsync(jobId); + ArgumentNullException.ThrowIfNull(workItems); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure retrieving dispatcher workitems with JobId {jobId}."); + throw new ServiceException($"Failure retrieving dispatcher workitems with JobId {jobId}."); + } + + return workItems; + } + + + public async Task> GetByJobIdWithExecutionsAsync(int jobId) + { + IEnumerable workItems = null; + try + { + workItems = await _workItemRepository.FindByJobIdWithExecutionsAsync(jobId, + _apiConfiguration.NumberOfWorkItemExecutions, + _apiConfiguration.NumberOfExecutionMessages); + ArgumentNullException.ThrowIfNull(workItems); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure retrieving dispatcher workitems and executions with JobId {jobId}."); + throw new ServiceException($"Failure retrieving dispatcher workitems and executions with JobId {jobId}."); + } + + return workItems; + } + + public async Task GetFirstOrDefaultWorkItemByJobId(int jobId) + { + WorkItem workItem = null; + try + { + workItem = await _workItemRepository.GetFirstOrDefaultWorkItemByJobId(jobId); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure retrieving first or default workItem with JobId: {jobId}."); + throw new ServiceException($"Failure retrieving first or default workItem with JobId: {jobId}."); + } + + return workItem; + } + + public async Task GetNextWorkItemByJobId(int jobId, int currentWorkItemOrder) + { + WorkItem workItem = null; + try + { + workItem = await _workItemRepository.GetNextWorkItemByJobId(jobId, currentWorkItemOrder); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure retrieving next workItem with JobId: {jobId}."); + throw new ServiceException($"Failure retrieving next workItem with JobId: {jobId}."); + } + + return workItem; + } + + public async Task> PopulateActionFieldsAndUpdateList(ICollection workItems) + { + try + { + var updatedWorkItems = new List(); + foreach (var workItem in workItems) + { + var newWorkItem = workItem; + newWorkItem = PopulateActionFields(newWorkItem); + newWorkItem = await UpdateAsync(newWorkItem); + updatedWorkItems.Add(newWorkItem); + } + + return updatedWorkItems; + } + catch (Exception exception) + { + Log.Error(exception, $"Failure populating action fields and updating dispatcher workitems."); + } + + return null; + } + + public async Task ResetCanceledJobCounter(WorkItem workItem) + { + var trackedWorkItem = await _workItemRepository.GetTrackedWorkItem(workItem.WorkItemId); + trackedWorkItem.CanceledJobCounter = 0; + await _unitOfWork.CompleteAsync(); + return await _workItemRepository.FindByIdAsync(trackedWorkItem.WorkItemId); + } + + public async Task IncreaseCanceledJobCounter(WorkItem workItem) + { + var trackedWorkItem = await _workItemRepository.GetTrackedWorkItem(workItem.WorkItemId); + trackedWorkItem.CanceledJobCounter++; + await _unitOfWork.CompleteAsync(); + return await _workItemRepository.FindByIdAsync(trackedWorkItem.WorkItemId); + } + + public async Task UpdateAsync(WorkItem workItem) + { + try + { + _workItemRepository.Update(workItem); + await _unitOfWork.CompleteAsync(); + + return workItem; + } + catch (SqlException sqlException) + { + Log.Error(sqlException, $"SQL Failure updating dispatcher workitem with id {workItem.WorkItemId}"); + throw new ServiceException($"SQL Failure updating dispatcher workitem with id {workItem.WorkItemId}"); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure updating dispatcher workitem with id {workItem.WorkItemId}"); + throw new ServiceException($"Failure updating dispatcher workitem with id {workItem.WorkItemId}"); + } + } + + public void SaveList(ICollection workItems) + { + try + { + _workItemRepository.AddList(workItems); + _unitOfWork.Complete(); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure saving list of workitems."); + throw new ServiceException($"Failure saving list of workitems."); + } + } + + public async Task DeleteList(ICollection workItems) + { + try + { + foreach (var workItem in workItems) + { + var workItemExecutions = await _workItemExecutionService.GetAllByWorkItemId(workItem.WorkItemId); + workItem.WorkItemExecutions = workItemExecutions.ToList(); + } + + _workItemRepository.RemoveList(workItems); + await _unitOfWork.CompleteAsync(); + } + catch (SqlException sqlException) + { + Log.Error(sqlException, $"SQL Failure removing dispatcher workitems"); + throw new ServiceException($"SQL Failure removing dispatcher workitems"); + } + catch (Exception exception) + { + Log.Error(exception, $"Failure removing dispatcher workitems"); + throw new ServiceException($"Failure removing dispatcher workitems"); + } + + } + + public WorkItem PopulateActionFields(WorkItem workItem) + { + var actionBuilder = _actionBuilderService.BuildAction(workItem.ActionType); + var responseItem = CreateResponseItem(workItem.WorkItemId); + workItem = actionBuilder.AddUrlAndHttpMethod(workItem); + workItem = actionBuilder.AddWorkItemReturnResponse(workItem, responseItem); + return workItem; + } + + private ResponseItem CreateResponseItem(int workItemId) + { + var dispatcherReturnResponse = new DispatcherReturnResponse + { + WorkItemId = workItemId + }; + var serializeDispatcherReturnResponse = JsonConvert.SerializeObject(dispatcherReturnResponse); + + var newReturnItem = new ResponseItem + { + ResponsePayload = serializeDispatcherReturnResponse, + ReturnQueueConnectionConfig = new QueueConnectionConfig() + { + Url = _connectionConfiguration.HostUrl, + Username = _connectionConfiguration.UserName, + Password = _connectionConfiguration.Password + }, + ReturnQueueConsumerConfig = new QueueConsumerConfig() + { + ExchangeName = _completionReceiveEndpoint.ReceiveEndpoint + } + }; + + return newReturnItem; + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Templates/BatchErroredJobsTemplate.html b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Templates/BatchErroredJobsTemplate.html new file mode 100644 index 0000000..1c6ea98 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Templates/BatchErroredJobsTemplate.html @@ -0,0 +1,58 @@ + + + + + + + Dispatcher: Error Monitoring + + +

Dispatcher: Error Monitoring

+ + + + + + + + + + + {TableRows} + +
Job NameWorkItem NameJobStatusStartTime
+ + + \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Templates/ErrorRowTemplate.html b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Templates/ErrorRowTemplate.html new file mode 100644 index 0000000..f5de722 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Templates/ErrorRowTemplate.html @@ -0,0 +1,6 @@ + + {JobName} + {WorkItemName} + {JobStatus} + {StartTime} + \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Using.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Using.cs new file mode 100644 index 0000000..677e240 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/Using.cs @@ -0,0 +1,7 @@ +global using System; +global using System.Threading.Tasks; +global using System.Linq; +global using Serilog; +global using LCU.Library.Internal.Exceptions; +global using LCU.Library.Internal.DispatcherModels.Entities; +global using LCU.Library.Internal.DispatcherModels.Models; \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/appsettings.json b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/appsettings.json new file mode 100644 index 0000000..aae1fff --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/appsettings.json @@ -0,0 +1,216 @@ +{ + "Primary": { + "AppName": "Dispatcher", + "SeqUrl": "http://localhost:5080", + "SeqApiKey": "9MQu0vLDwQ1LCFzGixZM", + "CorsAllowedOrigins": [ + "http://localhost:3000", + "http://lcu-internal.com", + "https://lcu-internal.com", + "https://qa-api.lcu-internal.com", + "https://uat-api.lcu-internal.com", + "https://api.lcu-internal.com" + ], + "SchedulersCronSchedule": "* * * * *", + "BatchErroredJobsCronSchedule": "*/15 * * * *", + "CleanUpJobsCronSchedule": "00 22 * * *", + "ProcessingLimitInMinutes": 30, + "NumberOfWorkItemExecutions": 10, + "NumberOfExecutionMessages": 20, + "CleanUpJobsDaysToDelete": -30, + "BatchErroredJobsTemplate": "./Templates/BatchErroredJobsTemplate.html", + "ErrorRowTemplate": "./Templates/ErrorRowTemplate.html", + "BatchErroredJobsSubject": "Dispatcher: Error Monitoring", + "DefaultErrorRecipient": "JoshuaAnnear@Landmarkcu.com", + "JobStatusesToMonitorByTimeLimit": [ + "Processing", + "DeterminingOperation", + "PendingToRun" + ], + "DefaultDelayMilliseconds": 5000, + "FeatureFlags": { + "RunCompletionConsumer": false, + "RunExecutionConsumer": false, + "RunJobSchedulerHostedService": false, + "RunBatchErroredJobsHostedService": false, + "RunCleanUpJobsHostedService": false, + "RunDetermineOperationConsumer": false, + "RunJobFailureConsumer": false, + "RunSendErroredJobAlertConsumer": false + } + }, + "FileOperationsApiConfiguration": { + "Url": "https://localhost:56595", + "MoveFileEndpoint": "/api/FileOperations/MoveFile", + "CopyFileEndpoint": "/api/FileOperations/CopyFile", + "ModifyFileEndpoint": "/api/FileOperations/ModifyFile", + "RenameFileEndpoint": "/api/FileOperations/RenameFile", + "DeleteFileEndpoint": "/api/FileOperations/DeleteFile", + "ZipDirectoryEndpoint": "/api/FileOperations/ZipDirectory", + "CreateDirectoryEndpoint": "/api/FileOperations/CreateDirectory", + "RenameDirectoryEndpoint": "/api/FileOperations/RenameDirectory", + "UnzipFileEndpoint": "/api/FileOperations/UnzipFile", + "EncryptFileDataEndpoint": "/api/FileOperations/EncryptFileData", + "DecryptFileDataEndpoint": "/api/FileOperations/DecryptFileData", + "UnzipPasswordProtectedFileEndpoint": "/api/FileOperations/UnzipPasswordProtectedFile", + "DeleteDirectoryEndpoint": "/api/FileOperations/DeleteDirectory", + "FileValidationEndpoint": "/FileValidation/QueueValidateFile", + "ExtractIndexFileDataEndpoint": "/api/IndexFile/QueueExtractIndexFileData", + "ValidateFileSizeInRange": "/FileValidation/QueueValidateFileSizeInRange", + "DeleteEmptyDirectoryEndpoint": "/api/FileOperations/DeleteEmptyDirectory" + }, + "FtpApiConfiguration": { + "Url": "https://localhost:44351", + "SftpFileEndpoint": "/api/Sftp", + "QueueSftpDynamicFileEndpoint": "/api/Sftp/QueueSftpDynamicFileRequest", + "QueueSftpUploadFileEndpoint": "/api/Sftp/QueueSftpUploadRequest", + "QueueSftpDownloadFileEndpoint": "/api/Sftp/QueueSftpDownloadRequest" + }, + "NotificationApiConfiguration": { + "Url": "https://qa-notifications-api.apps.azure.lcu-internal.com", + "EmailEndpoint": "/api/Email" + }, + "ArchiverApiConfiguration": { + "Url": "https://localhost:44353", + "QueueArchiverEndpoint": "/QueueArchiver/QueueArchiver" + }, + "BlendApiConfiguration": { + "Url": "https://localhost:54004", + "QueueDataImportEndpoint": "/api/Processing/QueueSyncDataFromBlend" + }, + "DialerApiConfiguration": { + "Url": "https://localhost:1234", + "QueueDialerUploadEndpoint": "/api/DialerUpload/QueueDialerUploadRequest", + "QueueUploadDoNotCallEndpoint": "/api/UploadDoNotCall/QueueUploadDoNotCallRequest", + "QueueCreateVoAppsCampaignEndpoint": "/api/CreateVoAppsCampaign/QueueCreateVoAppsCampaignRequest", + "QueueUploadVoAppsCampaignResultsEndpoint": "/api/UploadVoAppsCampaignResults/QueueUploadVoAppsCampaignRequest", + "QueueUploadVoAppsCampaignErrorRequestEndpoint": "/api/UploadVoAppsCampaignResultsError/QueueUploadVoAppsCampaignErrorRequest" + }, + "DocumentIngestorApiConfiguration": { + "Url": "https://localhost:7139", + "QueueDocumentIngestorEndpoint": "/DocumentIngestor/QueueDocumentIngestor", + "QueueDocumentBatchIngestionEndpoint": "/BatchIngestion/QueueBatchIngestion", + "QueueIngestorPdfPartitionerEndpoint": "/PdfPartitioner/QueueIngestorPdfPartitioner", + "QueueMemberDocumentNotifierEndpoint": "/MemberDocumentNotifier/QueueMemberDocumentNotifier", + "QueueIngestionFolderCleanUpEndpoint": "/IngestionFolderCleanUp/QueueIngestionFolderCleanUp", + "QueueProcessStatusTypeEmailRequestEndpoint": "/QueueProcessStatusTypeEmail/QueueProcessStatusTypeEmailRequest" + }, + "DocumentTypeCleanupApiConfiguration": { + "Url": "https://localhost:7066", + "QueueDocumentRetentionCleanupEndpoint": "/DocumentTypeCleanup/QueueDocumentRetentionCleanup" + }, + "HistoricalDocumentIngestorApiConfiguration": { + "Url": "https://localhost:44346", + "QueueDocumentIngestorEndpoint": "/DocumentIngestor/QueueDocumentIngestor", + "QueueDocumentBatchIngestionEndpoint": "/BatchIngestion/QueueBatchIngestion", + "QueueIngestorPdfPartitionerEndpoint": "/PdfPartitioner/QueueIngestorPdfPartitioner" + }, + "RemoteDepositCaptureApiConfiguration": { + "Url": "https://localhost:44305", + "QueueRemoteDepositCaptureXmlEndpoint": "/api/RemoteDepositCaptureXml/QueueRemoteDepositCaptureXmlRequest" + }, + "DataCleanUpApiConfiguration": { + "Url": "https://localhost:44357", + "QueueDataCleanUpEndpoint": "/QueueDataCleanUp/QueueDataCleanUp", + "QueueUpdatePersonForCheckHoldsEndpoint": "/QueueDataCleanUp/QueueUpdatePersonForCheckHolds", + "QueueNcoaUpdateEndpoint": "/QueueDataCleanUp/QueueUpdateNcoaProcess", + "QueueUpdateFannieMaeMortgageEndpoint": "/QueueDataCleanUp/QueueUpdateFannieMaeMortgage", + "QueueIndirectFundingEndpoint": "/QueueDataCleanUp/QueueIndirectFunding" + }, + "NoticeProcessorApiConfiguration": { + "Url": "https://localhost:7275", + "QueueExtractEndpoint": "/QueueExtractRequest/RunNoticeProcessorByExtractRequestNow", + "QueueProcessingEmailSummaryEndpoint": "/QueueProcessingEmailRequest/RunProcessingEmailRequestNow" + }, + "MomBotApiConfiguration": { + "Url": "https://localhost:5001", + "QueueMomBotEndpoint": "/api/QueueMomBot" + }, + "RealTimeAlertsApiConfiguration": { + "Url": "https://localhost:59626", + "QueueInitiateRealTimeAlertsEndpoint": "/api/TransactionAlerts/QueueInitiateTransactionAlertsRequest", + "QueueErroredTransactionCleanUpEndpoint": "/api/TransactionAlerts/QueueInitiateErroredTransactionCleanUpRequest" + }, + "RepoApiConfiguration": { + "Url": "https://localhost:7066", + "AccountBatchEndpoint": "/api/RepoBatchProcessing/QueueRepoBatchRequest" + }, + "ConnectionsConfiguration": { + "SQL": { + "DispatcherDb": "data source=localhost;initial catalog=Dispatcher;Integrated Security=SSPI;MultipleActiveResultSets=True;App=EntityFramework;Encrypt=false", + "SQLOptions": { + "MaxRetryCount": 5, + "MaxRetryDelay": "0.00:05:00", + "ErrorNumbersToAdd": [], + "EnableDetailedErrors": true, + "CommandTimeout": 60 + } + } + }, + "DispatcherRequestBuilderConfiguration": { + "Url": "https://localhost:44380/api/Ingestion" + }, + "DispatcherCompletionConsumer": { + "QueueName": "LCU.Queue.Dispatcher.Completion", + "ExchangeName": "LCU.Dispatcher.Direct", + "Subject": "LCU.Queue.Dispatcher.Completion" + }, + "ConnectionConfiguration": { + "HostUrl": "localhost", + "username": "guest", + "password": "guest" + }, + "ExecutionReceiveEndpoint": { + "ReceiveEndpoint": "LCU.Dispatcher.Execution", + "ReceiveEndpointType": "exchange", + "PrefetchLimit": 1 + }, + "CompletionReceiveEndpoint": { + "ReceiveEndpoint": "LCU.Dispatcher.Completion", + "ReceiveEndpointType": "exchange", + "PrefetchLimit": 1 + }, + "DetermineOperationReceiveEndpoint": { + "ReceiveEndpoint": "LCU.Dispatcher.DetermineOperation", + "ReceiveEndpointType": "exchange", + "PrefetchLimit": 1 + }, + "JobFailureReceiveEndpoint": { + "ReceiveEndpoint": "LCU.Dispatcher.JobFailure", + "ReceiveEndpointType": "exchange", + "PrefetchLimit": 1 + }, + "SendErroredJobAlertReceiveEndpoint": { + "ReceiveEndpoint": "LCU.Dispatcher.SendErroredJobAlert", + "ReceiveEndpointType": "exchange", + "PrefetchLimit": 100 + }, + "ApiSecurityConfiguration": { + "ApiKeyAuthenticationFailureBehavior": "Error", + "ExternalApiKeySet": { + "DispatcherHostedServiceClient": "VALUE-IN-VAULT" + } + }, + "IncentiveManagerConfiguration": { + "Url": "https://localhost:20001/api", + "QueueCreditCardImportEndpoint": "/ActivityImport/QueueCreditCardImportRequest", + "QueueVehicleImportEndpoint": "/ActivityImport/QueueIndirectVehicleImportRequest", + "QueueActionCreditCardEndpoint": "/ActivityImport/QueueActionCreditCardRequest", + "QueueActionVehicleEndpoint": "/ActivityImport/QueueActionVehicleRequest", + "QueueAlliedGapMbpEndpoint": "/ActivityImport/QueueAlliedGapMbpRequest", + "QueueIncentiveProcessEndpoint": "/Processing/QueueIncentiveProcess", + "QueueNightlyIncentiveProcessEndpoint": "/Processing/QueueNightlyIncentiveProcess", + "QueuePayoutProcessEndpoint": "/Processing/QueuePayoutProcess", + "QueueReportingProcessEndpoint": "/Processing/QueueReportingRequest", + "QueueGoalDispersalProcessEndpoint": "/Processing/QueueGoalDispersalSyncProcess", + "QueueDayforceProcessEndpoint": "/Processing/QueueDayforceSyncProcess" + }, + "FileUpdaterApiConfiguration": { + "Url": "https://localhost:56076", + "QueueIndexFieldMapper": "/api/FileUpdater/QueueIndexFieldMapper" + }, + "TemenosNotificationApiConfiguration": { + "Url": "https://localhost:7076", + "QueueDealerAchNotification": "/api/QueueDealerAchNotification" + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/libman.json b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/libman.json new file mode 100644 index 0000000..ceee271 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.Dispatcher/libman.json @@ -0,0 +1,5 @@ +{ + "version": "1.0", + "defaultProvider": "cdnjs", + "libraries": [] +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherIntegrationTests/DispatcherRequestBuilderTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherIntegrationTests/DispatcherRequestBuilderTest.cs new file mode 100644 index 0000000..f61f124 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherIntegrationTests/DispatcherRequestBuilderTest.cs @@ -0,0 +1,126 @@ +using System; +using System.IO; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using LCU.Library.Internal.DispatcherModels.Models; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Api.Internal.DispatcherSharedLib; +using LCU.Api.Internal.DispatcherSharedLib.BuilderServices; +using LCU.Library.Internal.FileOperationsModels.Models; +using LCU.Library.Internal.LcuHttpClient; +using System.Threading.Tasks; + +namespace LCU.Api.Internal.DispatcherIntegrationTests; + +public class DispatcherRequestBuilderTest +{ + private readonly IServiceCollection services; + private readonly IRequestBuilderService _requestBuilder; + private const string TestPath1 = "\\\\zeus\\DataIntegrationsQA\\Test_FileOperationsApi_Folder_1"; + private const string TestPath2 = "\\\\zeus\\DataIntegrationsQA\\Test_FileOperationsApi_Folder_1"; + + public DispatcherRequestBuilderTest() + { + services = new ServiceCollection(); + + var builder = new ConfigurationBuilder() + .SetBasePath(Path.Combine(AppContext.BaseDirectory)) + .AddJsonFile("appsettings.integration.json"); + var config = builder.Build(); + services.AddDispatcherServices(config); + services.AddLCUHttpServices(); + var serviceProvider = services.BuildServiceProvider(); + _requestBuilder = serviceProvider.GetService(); + } + + + [SetUp] + public void Setup() + { + // !!!!!! In order to run these test you will need the following set up locally. !!!!!! + // 1.) Local Dispatcher Database. + // 2.) Local docker with Rabbit installed and running. + // 3.) Local instance of Dispatcher Api running along with other apis being tested (eg. FileOperationsApi). + // Which will also need to be using local Rabbit queues. + // 4.) Can use the the following two locations on QA for file operation testing. + // - \\zeus\DataIntegrationsQA\Test_FileOperationsApi_Folder_1 + // - \\zeus\DataIntegrationsQA\Test_FileOperationsApi_Folder_2 + // !!!!!! ---------------------------------------------------------------------- !!!!!! + } + + [Test] + [Ignore("Can only be run locally.")] + public async Task Test_CreateDirectory() + { + //Create Job + var job = new JobDetails() + { + Name = "Test", + RequestingApplication = "Dispatcher Integration Test", + CreatedBy = "Dispatcher Integration Test", + CreatedAt = DateTime.Now, + NextRunTime = DateTime.Now + }; + + _requestBuilder.CreateJob(job); + + //Create WorkItem + var createDirectoryRequest = new CreateDirectoryRequest() + { + DirectoryName = "FirstDispatcherIntegrationTest", + DirectoryPath = TestPath1 + }; + + var workItemDetailsCreateDirectory = new WorkItemDetails() + { + Name = "Test", + Order = 0 + }; + + _requestBuilder.AddWorkItem(createDirectoryRequest, workItemDetailsCreateDirectory, ActionType.CreateDirectory); + + //Send + var result = await _requestBuilder.SendDispatcherRequest(); + + Assert.True(result); + } + + [Test] + [Ignore("Can only be run locally.")] + public async Task Test_RenameDirectory() + { + //Create Job + var job = new JobDetails() + { + Name = "Test - Test_RenameDirectory", + RequestingApplication = "Dispatcher Integration Test - Test_RenameDirectory Builder", + CreatedBy = "Dispatcher Integration Test", + CreatedAt = DateTime.Now, + NextRunTime = DateTime.Now + }; + + _requestBuilder.CreateJob(job); + + //Create WorkItem + var renameDirectoryRequest = new RenameDirectoryRequest() + { + SourceDirectoryName = "FirstDispatcherIntegrationTest", + SourceDirectoryPath = TestPath1, + NewDirectoryName = "RenameDispatcherIntegrationTest2" + }; + + var workItemDetailsCreateDirectory = new WorkItemDetails() + { + Name = "Test - Test_RenameDirectory", + Order = 0 + }; + + _requestBuilder.AddWorkItem(renameDirectoryRequest, workItemDetailsCreateDirectory, ActionType.RenameDirectory); + + //Send + var result = await _requestBuilder.SendDispatcherRequest(); + + Assert.True(result); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherIntegrationTests/LCU.Api.Internal.DispatcherIntegrationTests.csproj b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherIntegrationTests/LCU.Api.Internal.DispatcherIntegrationTests.csproj new file mode 100644 index 0000000..0c12819 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherIntegrationTests/LCU.Api.Internal.DispatcherIntegrationTests.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + + false + + + + + + + + + PreserveNewest + true + PreserveNewest + + + + + + + + + + + + + + + + + diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherIntegrationTests/appsettings.integration.json b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherIntegrationTests/appsettings.integration.json new file mode 100644 index 0000000..8a81e3e --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherIntegrationTests/appsettings.integration.json @@ -0,0 +1,78 @@ +{ + "Serilog": { + "LevelSwitches": { "$controlSwitch": "Information" }, + "MinimumLevel": { + "ControlledBy": "$controlSwitch", + "Override": { + "Microsoft": "Information", + "System": "Warning", + "Microsoft.AspNetCore": "Warning" + } + }, + "WriteTo": [ + { + "Name": "Seq", + "Args": { + "serverUrl": "https://qa-seq.lcu-internal.com", + "apiKey": "VfIDjFsJC1zLalKeesnu", + "controlLevelSwitch": "$controlSwitch" + } + } + ], + "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ], + "Properties": { + "Application": "Dispatcher", + "Environment": "Debug" + } + }, + "AllowedHosts": "*", + "Primary": { + "AttemptsLimit": 2, + "SchedulersCronSchedule": "* * * * *", + "Apis": { + "FileOperationApi": { + "MoveFile": "https://localhost:44385/api/FileOperations/MoveFile", + "CopyFile": "https://localhost:44385/api/FileOperations/CopyFile", + "RenameFile": "https://localhost:44385/api/FileOperations/RenameFile", + "DeleteFile": "https://localhost:44385/api/FileOperations/DeleteFile", + "ZipDirectory": "https://localhost:44385/api/FileOperations/ZipDirectory", + "CreateDirectory": "https://localhost:44385/api/FileOperations/CreateDirectory", + "RenameDirectory": "https://localhost:44385/api/FileOperations/RenameDirectory" + }, + "FtpApi": { + "SftpFile": "https://localhost:44383/api/Sftp" + }, + "NotificationApi": { + "Email": "http://localhost:54954/api/Email" + } + } + }, + "Connections": { + "SQL": { + "DispatcherDb": "data source=localhost;initial catalog=Dispatcher;Integrated Security=SSPI;MultipleActiveResultSets=True;App=EntityFramework" + } + }, + "DispatcherConfig": { + "Url": "https://localhost:44380/api/QueueIngestion" + }, + "DispatcherIngestionConsumer": { + "QueueName": "LCU.Queue.Dispatcher.Ingestion", + "ExchangeName": "LCU.Dispatcher.Direct", + "Subject": "LCU.Queue.Dispatcher.Ingestion" + }, + "ErroredDispatcherIngestionConsumer": { + "QueueName": "LCU.Queue.Dispatcher.Ingestion.Errored", + "ExchangeName": "LCU.Dispatcher.Direct", + "Subject": "LCU.Queue.Dispatcher.Ingestion.Errored" + }, + "DispatcherExecutionConsumer": { + "QueueName": "LCU.Queue.Dispatcher.Execution", + "ExchangeName": "LCU.Dispatcher.Direct", + "Subject": "LCU.Queue.Dispatcher.Execution" + }, + "DispatcherCompletionConsumer": { + "QueueName": "LCU.Queue.Dispatcher.Completion", + "ExchangeName": "LCU.Dispatcher.Direct", + "Subject": "LCU.Queue.Dispatcher.Completion" + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Enumerables/StatusTypeMatch.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Enumerables/StatusTypeMatch.cs new file mode 100644 index 0000000..fd1f581 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Enumerables/StatusTypeMatch.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using LCU.Library.Internal.DispatcherModels.Entities; +using LCU.Library.Internal.DispatcherModels.Models; +namespace LCU.Api.Internal.DispatcherLib.Extensions.Enumerables; + +public static class StatusTypeMatch +{ + public static IQueryable FilterByStatusType(this IQueryable jobs, Filter filter) + { + if (filter?.StatusFilters == null || !filter.StatusFilters.Any()) + { + return jobs; + } + + var enabledStatuses = filter.StatusFilters.Where(statusFilter => statusFilter.IsEnabled).Select(j => j.JobStatus).ToList(); + + if (!enabledStatuses.Any()) + { + return jobs; + } + + return jobs.Where(job => enabledStatuses.Contains(job.JobStatus)); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/ContainsTagIdQueryable.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/ContainsTagIdQueryable.cs new file mode 100644 index 0000000..906023b --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/ContainsTagIdQueryable.cs @@ -0,0 +1,18 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; +using LCU.Library.Internal.DispatcherModels.Models; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Extensions.Queryables; + +public static class ContainsTagIdQueryable +{ + public static IQueryable FilterOnTagId(this IQueryable queryable, Filter filter) + { + if (filter?.TagIdFilters == null || !filter.TagIdFilters.Any()) + { + return queryable; + } + + return queryable.Where(job => job.JobTags.Any(jobTag => filter.TagIdFilters.Contains(jobTag.TagId))); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/IncludeAllQueryable.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/IncludeAllQueryable.cs new file mode 100644 index 0000000..6710fc0 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/IncludeAllQueryable.cs @@ -0,0 +1,70 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Extensions.Queryables; + +public static class IncludeAllQueryable +{ + /// + /// Since this runs as Split Query, the OrderyByDescending only works during the querying but not in our result set. + /// OrderbyDescending on WorkItemExecutions will have to be done prior to use if need be. + /// + /// + /// + /// + public static IQueryable IncludeAll(this IQueryable queryable, int numberOfWorkItemExecutions, int numberOfExecutionMessages) + { + return queryable.Include(job => job.WorkItems) + .ThenInclude(wi => wi.WorkItemExecutions + .OrderByDescending(workItemExecution => workItemExecution.StartedTimestamp) + .Take(numberOfWorkItemExecutions)) + .ThenInclude(workItemExecution => workItemExecution.ExecutionMessages + .OrderByDescending(executionMessage => executionMessage.MessageDate) + .Take(numberOfExecutionMessages)) + .Include(job => job.JobErrorRecipients) + .Include(jobTag => jobTag.JobTags) + .ThenInclude(jobTag => jobTag.Tag) + .AsSplitQuery(); + } + + public static IQueryable IncludeErrorRecipients(this IQueryable queryable) + { + return queryable.Include(job => job.JobErrorRecipients); + } + + public static IQueryable IncludeAll(this IQueryable queryable) + { + return queryable.Include(workItem => workItem.WorkItemExecutions); + } + + public static IQueryable IncludeAll(this IQueryable queryable, int numberOfWorkItemExecutions, int numberOfExecutionMessages) + { + return queryable.Include(wi => wi.WorkItemExecutions + .OrderByDescending(workItemExecution => workItemExecution.StartedTimestamp) + .Take(numberOfWorkItemExecutions)) + .ThenInclude(workItemExecution => workItemExecution.ExecutionMessages + .OrderByDescending(executionMessage => executionMessage.MessageDate) + .Take(numberOfExecutionMessages)); + } + + public static IQueryable IncludeAll(this IQueryable queryable) + { + return queryable.Include(jobTag => jobTag.Tag); + } + + public static IQueryable IncludeAll(this IQueryable queryable) + { + return queryable.Include(workItemExecution => workItemExecution.ExecutionMessages); + } + + public static IQueryable IncludeAll(this IQueryable queryable) + { + return queryable.Include(cleanUpJobSetting => cleanUpJobSetting.CleanUpJobTags) + .ThenInclude(cleanUpJobTag => cleanUpJobTag.Tag); + } + + public static IQueryable IncludeAll(this IQueryable queryable) + { + return queryable.Include(cleanUpJobTag => cleanUpJobTag.Tag); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/JobDescriptionQueryable.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/JobDescriptionQueryable.cs new file mode 100644 index 0000000..67935c2 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/JobDescriptionQueryable.cs @@ -0,0 +1,18 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; +using LCU.Library.Internal.DispatcherModels.Models; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Extensions.Queryables; + +public static class JobDescriptionQueryable +{ + public static IQueryable FilterJobDescription(this IQueryable queryable, Filter filter) + { + if (filter?.JobDescription == null) + { + return queryable; + } + + return queryable.Where(job => job.Description.Contains(filter.JobDescription)); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/JobNameQueryable.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/JobNameQueryable.cs new file mode 100644 index 0000000..aaa41a1 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/JobNameQueryable.cs @@ -0,0 +1,18 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; +using LCU.Library.Internal.DispatcherModels.Models; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Extensions.Queryables; + +public static class JobNameQueryable +{ + public static IQueryable FilterJobName(this IQueryable queryable, Filter filter) + { + if (filter?.JobName == null) + { + return queryable; + } + + return queryable.Where(job => job.Name.Contains(filter.JobName)); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/NextRunTimeGreaterThanDateQueryable.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/NextRunTimeGreaterThanDateQueryable.cs new file mode 100644 index 0000000..f0dc09f --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/NextRunTimeGreaterThanDateQueryable.cs @@ -0,0 +1,18 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; +using LCU.Library.Internal.DispatcherModels.Models; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Extensions.Queryables; + +public static class NextRunTimeGreaterThanDateQueryable +{ + public static IQueryable FilterNextRunTimeGreaterThanDate(this IQueryable queryable, Filter filter) + { + if (filter?.NextRuntimeGreaterThanDate == null) + { + return queryable; + } + + return queryable.Where(job => job.NextRunTime >= filter.NextRuntimeGreaterThanDate); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/SortableFieldsQueryable.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/SortableFieldsQueryable.cs new file mode 100644 index 0000000..341d0f9 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Extensions/Queryables/SortableFieldsQueryable.cs @@ -0,0 +1,27 @@ +using System.Text; +using System.Linq; +using System.Linq.Dynamic.Core; +using LCU.Library.Internal.DispatcherModels.Models; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Extensions.Queryables; + +public static class SortableFieldsQueryable +{ + public static IQueryable OrderBySortableFields(this IQueryable queryable, Filter filter) + { + if (filter?.SortableFields == null || !filter.SortableFields.Any()) + { + return queryable; + } + + var orderBy = new StringBuilder(); + foreach (var field in filter.SortableFields.OrderBy(sortableField => sortableField.Position)) + { + var addedComma = orderBy.ToString() == "" ? "" : " ,"; + var desc = field.IsDescending ? " desc" : ""; + orderBy.Append($"{addedComma}{field.Field}{desc}"); + } + + return queryable.OrderBy(orderBy.ToString()); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/LCU.Api.Internal.DispatcherLib.csproj b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/LCU.Api.Internal.DispatcherLib.csproj new file mode 100644 index 0000000..2036001 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/LCU.Api.Internal.DispatcherLib.csproj @@ -0,0 +1,47 @@ + + + + net8.0 + latest + 2.0.3 + + 1.1.0 - Added EmailRequest and the new ActionType Email. + 1.2.0 - Adding JobErrorRecipients + 1.3.0 - Adding SftpDynamicFileRequest + 1.4.0 - Upgrade to netstandard2.1 + 2.0.0 - Removed Models and helper classes. DispatcherSharedLib and DispatcherModels should be the used libraries. + 2.0.1 - Update NuGet packages + 2.0.2 - Update NuGet packages + 2.0.3 - Updates to Optimize Queryables + + + 1.1.0 - Added EmailRequest and the new ActionType Email. + 1.2.0 - Adding JobErrorRecipients + 1.3.0 - Adding SftpDynamicFileRequest + 1.4.0 - Upgrade to netstandard2.1 + 2.0.0 - Removed Models and helper classes. DispatcherSharedLib and DispatcherModels should be the used libraries. + 2.0.1 - Update NuGet packages + 2.0.2 - Update NuGet packages + 2.0.3 - Updates to Optimize Queryables + + + + + + + + + + + + + + + + + + + + + + diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Persistence/AppDbContext.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Persistence/AppDbContext.cs new file mode 100644 index 0000000..d9a5684 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Persistence/AppDbContext.cs @@ -0,0 +1,32 @@ +using Microsoft.EntityFrameworkCore; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Persistence; + +public class AppDbContext : DbContext +{ + public DbSet Jobs { get; set; } + public DbSet JobErrorRecipients { get; set; } + public DbSet JobTags { get; set; } + public DbSet Tags { get; set; } + public DbSet WorkItems { get; set; } + public DbSet WorkItemExecutions { get; set; } + public DbSet ExecutionMessages { get; set; } + public DbSet CleanUpJobSettings { get; set; } + public DbSet CleanUpJobTags { get; set; } + public AppDbContext(DbContextOptions options) : base(options) { } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.Entity().ToTable("Job"); + builder.Entity().ToTable("JobErrorRecipient"); + builder.Entity().ToTable("JobTag"); + builder.Entity().ToTable("Tag"); + builder.Entity().ToTable("WorkItem"); + builder.Entity().ToTable("WorkItemExecution"); + builder.Entity().ToTable("ExecutionMessage"); + builder.Entity().ToTable("CleanUpJobSetting"); + builder.Entity().ToTable("CleanUpJobTag"); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/BaseRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/BaseRepository.cs new file mode 100644 index 0000000..89f5efe --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/BaseRepository.cs @@ -0,0 +1,12 @@ +using LCU.Api.Internal.DispatcherLib.Persistence; +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public abstract class BaseRepository +{ + protected readonly AppDbContext _context; + + public BaseRepository(AppDbContext context) + { + _context = context; + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/CleanUpJobSettingRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/CleanUpJobSettingRepository.cs new file mode 100644 index 0000000..3e6b3dd --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/CleanUpJobSettingRepository.cs @@ -0,0 +1,45 @@ +using LCU.Api.Internal.DispatcherLib.Extensions.Queryables; +using LCU.Api.Internal.DispatcherLib.Persistence; +using LCU.Library.Internal.DispatcherModels.Entities; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LCU.Api.Internal.DispatcherLib.Repositories +{ + public class CleanUpJobSettingRepository : BaseRepository, ICleanUpJobSettingRepository + { + public CleanUpJobSettingRepository(AppDbContext context) : base(context) { } + + public async Task> ListAsync() + { + return await _context.CleanUpJobSettings.IncludeAll().ToListAsync(); + } + + public async Task FindByCleanUpJobSettingIdAsync(int cleanUpJobSettingId) + { + return await _context.CleanUpJobSettings.Where(cleanUpJobSetting => cleanUpJobSetting.CleanUpJobSettingId == cleanUpJobSettingId) + .IncludeAll() + .FirstOrDefaultAsync(); + } + + public async Task AddAsync(CleanUpJobSetting request) + { + var result = await _context.CleanUpJobSettings.AddAsync(request); + return result.Entity; + } + + public void Update(CleanUpJobSetting existing) + { + _context.CleanUpJobSettings.Update(existing); + } + + public void Remove(CleanUpJobSetting cleanUpJobSetting) + { + _context.CleanUpJobSettings.Remove(cleanUpJobSetting); + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/CleanUpJobTagRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/CleanUpJobTagRepository.cs new file mode 100644 index 0000000..fda6be9 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/CleanUpJobTagRepository.cs @@ -0,0 +1,51 @@ +using LCU.Api.Internal.DispatcherLib.Extensions.Queryables; +using LCU.Api.Internal.DispatcherLib.Persistence; +using LCU.Library.Internal.DispatcherModels.Entities; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public class CleanUpJobTagRepository : BaseRepository, ICleanUpJobTagRepository +{ + public CleanUpJobTagRepository(AppDbContext context) : base(context) { } + + public async Task> ListAsync() + { + return await _context.CleanUpJobTags + .AsNoTracking() + .IncludeAll() + .ToListAsync(); + } + + public async Task FindByCleanUpJobTagIdAsync(int cleanUpJobTagId) + { + return await _context.CleanUpJobTags.Where(cleanUpJobtag => cleanUpJobtag.CleanUpJobTagId == cleanUpJobTagId) + .AsNoTracking() + .IncludeAll() + .FirstOrDefaultAsync(); + } + + public async Task> FindByCleanUpJobSettingIdAsync(int cleanUpJobSettingId) + { + return await _context.CleanUpJobTags.Where(cleanUpJobtag => cleanUpJobtag.CleanUpJobSettingId == cleanUpJobSettingId) + .AsNoTracking() + .IncludeAll() + .ToListAsync(); + } + + public async Task AddAsync(CleanUpJobTag request) + { + var result = await _context.CleanUpJobTags.AddAsync(request); + return result.Entity; + } + + public void Remove(CleanUpJobTag cleanUpJobTag) + { + _context.CleanUpJobTags.Remove(cleanUpJobTag); + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/ExecutionMessageRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/ExecutionMessageRepository.cs new file mode 100644 index 0000000..a431def --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/ExecutionMessageRepository.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore; +using System.Threading.Tasks; +using LCU.Api.Internal.DispatcherLib.Persistence; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public class ExecutionMessageRepository : BaseRepository, IExecutionMessageRepository +{ + public ExecutionMessageRepository(AppDbContext context) : base(context) { } + + public async Task FindByIdAsync(int id) + { + return await _context.ExecutionMessages + .AsNoTracking() + .FirstOrDefaultAsync(workItemExecution => workItemExecution.ExecutionMessageId == id); + } + + public async Task AddAsync(ExecutionMessage workItemExecution) + { + var result = await _context.ExecutionMessages.AddAsync(workItemExecution); + return result.Entity; + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/ICleanUpJobSettingRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/ICleanUpJobSettingRepository.cs new file mode 100644 index 0000000..d894502 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/ICleanUpJobSettingRepository.cs @@ -0,0 +1,17 @@ +using LCU.Library.Internal.DispatcherModels.Entities; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace LCU.Api.Internal.DispatcherLib.Repositories +{ + public interface ICleanUpJobSettingRepository + { + Task> ListAsync(); + Task FindByCleanUpJobSettingIdAsync(int cleanUpJobSettingId); + Task AddAsync(CleanUpJobSetting request); + void Update(CleanUpJobSetting existing); + void Remove(CleanUpJobSetting cleanUpJobSetting); + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/ICleanUpJobTagRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/ICleanUpJobTagRepository.cs new file mode 100644 index 0000000..f8d3a2d --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/ICleanUpJobTagRepository.cs @@ -0,0 +1,16 @@ +using LCU.Library.Internal.DispatcherModels.Entities; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public interface ICleanUpJobTagRepository +{ + Task> ListAsync(); + Task FindByCleanUpJobTagIdAsync(int cleanUpJobTagId); + Task> FindByCleanUpJobSettingIdAsync(int cleanUpJobSettingId); + Task AddAsync(CleanUpJobTag request); + void Remove(CleanUpJobTag cleanUpJobTag); +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IExecutionMessageRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IExecutionMessageRepository.cs new file mode 100644 index 0000000..fff6139 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IExecutionMessageRepository.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public interface IExecutionMessageRepository +{ + Task FindByIdAsync(int id); + Task AddAsync(ExecutionMessage workItemExecution); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IJobErrorRecipientRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IJobErrorRecipientRepository.cs new file mode 100644 index 0000000..ed68e64 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IJobErrorRecipientRepository.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public interface IJobErrorRecipientRepository +{ + void AddList(ICollection jobErrorRecipients); + void RemoveList(ICollection jobErrorRecipients); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IJobRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IJobRepository.cs new file mode 100644 index 0000000..73666ab --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IJobRepository.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.DispatcherModels.Entities; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.DispatcherModels.Models; +using LCU.Library.Internal.DispatcherModels.Models.Results; + +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public interface IJobRepository +{ + Task> FilteredListAsync(Filter filter, + int numberOfWorkItemExecutions, + int numberOfExecutionMessages); + Task> ListAsync(int numberOfWorkItemExecutions, int numberOfExecutionMessages); + Task> ListInactiveAsync(int numberOfWorkItemExecutions, int numberOfExecutionMessages); + Task> ListInactiveExcludeSoftDeletedTrackedAsync(int numberOfWorkItemExecutions, int numberOfExecutionMessages); + Task> ListSoftDeletedTrackedAsync(int numberOfWorkItemExecutions, int numberOfExecutionMessages, DateTime cutOffDate); + Task FindByIdAsync(int id, int numberOfWorkItemExecutions, int numberOfExecutionMessages); + Task FindByIdExcludingWorkItemsAsync(int id); + Task FindByWorkItemIdAsync(int workItemId, + int numberOfWorkItemExecutions, + int numberOfExecutionMessages); + Task FindByWorkItemIdExcludingWorkItemsAsync(int workItemId); + Task FindByWorkItemIdExcludingWorkItemsIncludeErrorRecipientsAsync(int workItemId); + Task> FindJobsPastDateByStatuses(DateTime date, + int numberOfWorkItemExecutions, + int numberOfExecutionMessages, + List jobStatuses); + Task> FindJobsReadyToBeScheduled(DateTime date); + Task AddAsync(Job job); + Task Update(Job job); + void Remove(Job job); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IJobTagRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IJobTagRepository.cs new file mode 100644 index 0000000..c9c731d --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IJobTagRepository.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public interface IJobTagRepository +{ + Task> ListAsync(); + Task FindByIdAsync(int jobTagId); + Task> FindByTagIdAsync(int tagId); + Task AddAsync(JobTag jobTag); + void Remove(JobTag jobTag); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/ITagRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/ITagRepository.cs new file mode 100644 index 0000000..5882aa9 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/ITagRepository.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public interface ITagRepository +{ + Task> ListAsync(); + Task FindByIdAsync(int tagId); + Task AddAsync(Tag tag); + void Update(Tag tag); + void Remove(Tag tag); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IUnitOfWork.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IUnitOfWork.cs new file mode 100644 index 0000000..95f2b34 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IUnitOfWork.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public interface IUnitOfWork +{ + Task CompleteAsync(); + void Complete(); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IWorkItemExecutionRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IWorkItemExecutionRepository.cs new file mode 100644 index 0000000..a48b0e6 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IWorkItemExecutionRepository.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public interface IWorkItemExecutionRepository +{ + Task> ListAsync(); + Task FindByIdAsync(int id); + Task AddAsync(WorkItemExecution workItemExecution); + void Update(WorkItemExecution workItemExecution); + Task> FindUnFinishedWorkItemExecutions(int workItemId); + Task> FindAllByWorkItemId(int workItemId); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IWorkItemRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IWorkItemRepository.cs new file mode 100644 index 0000000..918520f --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/IWorkItemRepository.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public interface IWorkItemRepository +{ + Task> ListAsync(); + Task FindByIdAsync(int id); + Task> FindByJobIdAsync(int jobId); + Task> FindByJobIdWithExecutionsAsync(int jobId, int numberOfWorkItemExecutions, int numberOfExecutionMessages); + Task GetFirstOrDefaultWorkItemByJobId(int jobId); + Task GetNextWorkItemByJobId(int jobId, int currentWorkItemOrder); + Task GetTrackedWorkItem(int workItemId); + Task AddAsync(WorkItem workItem); + void AddList(IEnumerable workItems); + void Update(WorkItem workItem); + void RemoveList(IEnumerable workItems); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/JobErrorRecipientRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/JobErrorRecipientRepository.cs new file mode 100644 index 0000000..f5c46f6 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/JobErrorRecipientRepository.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using LCU.Api.Internal.DispatcherLib.Persistence; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public class JobErrorRecipientRepository : BaseRepository, IJobErrorRecipientRepository +{ + public JobErrorRecipientRepository(AppDbContext context) : base(context) { } + + public void AddList(ICollection jobErrorRecipients) + { + _context.JobErrorRecipients.AddRangeAsync(jobErrorRecipients); + } + + public void RemoveList(ICollection jobErrorRecipients) + { + _context.JobErrorRecipients.RemoveRange(jobErrorRecipients); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/JobRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/JobRepository.cs new file mode 100644 index 0000000..09c0368 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/JobRepository.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using LCU.Library.Internal.DispatcherModels.Models; +using LCU.Library.Internal.DispatcherModels.Entities; +using LCU.Api.Internal.DispatcherLib.Persistence; +using LCU.Api.Internal.DispatcherLib.Extensions.Queryables; +using LCU.Api.Internal.DispatcherLib.Extensions.Enumerables; +using LCU.Library.Internal.DispatcherModels.Models.Results; +using LCU.Library.Internal.DispatcherModels.Enums; +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public class JobRepository : BaseRepository, IJobRepository +{ + public JobRepository(AppDbContext context) : base(context) { } + + public async Task> FilteredListAsync(Filter filter, + int numberOfWorkItemExecutions, + int numberOfExecutionMessages) + { + var results = new PagedResult(); + var skip = (filter.Page - 1) * filter.Limit; + + results.Total = await _context.Jobs.Where(job => job.SoftDeleteDate == null) + .IncludeAll(numberOfWorkItemExecutions, numberOfExecutionMessages) + .FilterJobName(filter) + .FilterJobDescription(filter) + .FilterNextRunTimeGreaterThanDate(filter) + .FilterOnTagId(filter) + .FilterByStatusType(filter) + .AsNoTracking() + .CountAsync(); + + //Queryable + var jobs = await _context.Jobs.Where(job => job.SoftDeleteDate == null) + .IncludeAll(numberOfWorkItemExecutions, numberOfExecutionMessages) + .FilterJobName(filter) + .FilterJobDescription(filter) + .FilterNextRunTimeGreaterThanDate(filter) + .FilterOnTagId(filter) + .FilterByStatusType(filter) + .OrderBySortableFields(filter) + .AsNoTracking() + .ToListAsync(); + + results.Items = jobs.Skip(skip).Take(filter.Limit); + + return results; + } + + public async Task> ListAsync(int numberOfWorkItemExecutions, int numberOfExecutionMessages) + { + return await _context.Jobs.IncludeAll(numberOfWorkItemExecutions, numberOfExecutionMessages) + .AsNoTracking() + .ToListAsync(); + } + + public async Task> ListInactiveAsync(int numberOfWorkItemExecutions, int numberOfExecutionMessages) + { + return await _context.Jobs.Where(job => string.IsNullOrWhiteSpace(job.CronSchedule) && job.NextRunTime == null) + .IncludeAll(numberOfWorkItemExecutions, numberOfExecutionMessages) + .AsNoTracking() + .ToListAsync(); + } + + public async Task> ListInactiveExcludeSoftDeletedTrackedAsync(int numberOfWorkItemExecutions, int numberOfExecutionMessages) + { + return await _context.Jobs.Where(job => string.IsNullOrWhiteSpace(job.CronSchedule) && job.NextRunTime == null && job.SoftDeleteDate == null) + .IncludeAll(numberOfWorkItemExecutions, numberOfExecutionMessages) + .ToListAsync(); + } + + public async Task> ListSoftDeletedTrackedAsync(int numberOfWorkItemExecutions, int numberOfExecutionMessages, DateTime cutOffDate) + { + return await _context.Jobs.Where(job => job.SoftDeleteDate != null && cutOffDate > job.SoftDeleteDate) + .IncludeAll(numberOfWorkItemExecutions, numberOfExecutionMessages) + .ToListAsync(); + } + + public async Task FindByIdAsync(int id, int numberOfWorkItemExecutions, int numberOfExecutionMessages) + { + return await _context.Jobs.Where(job => job.JobId == id) + .IncludeAll(numberOfWorkItemExecutions, numberOfExecutionMessages) + .AsNoTracking() + .FirstOrDefaultAsync(); + } + + public async Task FindByIdExcludingWorkItemsAsync(int id) + { + return await _context.Jobs.Where(job => job.JobId == id) + .AsNoTracking() + .FirstOrDefaultAsync(); + } + + public async Task FindByWorkItemIdAsync(int workItemId, int numberOfWorkItemExecutions, int numberOfExecutionMessages) + { + return await _context.Jobs.Where(job => job.WorkItems.Any(workItem => workItem.WorkItemId == workItemId)) + .IncludeAll(numberOfWorkItemExecutions, numberOfExecutionMessages) + .AsNoTracking() + .FirstOrDefaultAsync(); + } + + public async Task FindByWorkItemIdExcludingWorkItemsAsync(int workItemId) + { + return await _context.Jobs.Where(job => job.WorkItems.Any(workItem => workItem.WorkItemId == workItemId)) + .AsNoTracking() + .FirstOrDefaultAsync(); + } + + public async Task FindByWorkItemIdExcludingWorkItemsIncludeErrorRecipientsAsync(int workItemId) + { + return await _context.Jobs.Where(job => job.WorkItems.Any(workItem => workItem.WorkItemId == workItemId)) + .IncludeErrorRecipients() + .AsNoTracking() + .FirstOrDefaultAsync(); + } + + public async Task> FindJobsPastDateByStatuses(DateTime date, + int numberOfWorkItemExecutions, + int numberOfExecutionMessages, + List jobStatuses) + { + return await _context.Jobs.Where(job => date > job.NextRunTime && (jobStatuses.Contains(job.JobStatus) || job.JobStatus == JobStatus.Failed)) + .IncludeAll(numberOfWorkItemExecutions, numberOfExecutionMessages) + .AsNoTracking() + .ToListAsync(); + } + + public async Task> FindJobsReadyToBeScheduled(DateTime date) + { + var jobs = await _context.Jobs.Where(job => (job.JobStatus == JobStatus.ReadyToSchedule && job.NextRunTime < date)) + .AsNoTracking() + .ToListAsync(); + return jobs; + } + + public async Task AddAsync(Job job) + { + var result = await _context.Jobs.AddAsync(job); + return result.Entity; + } + + public async Task Update(Job job) + { + var trackedJob = await _context.Jobs + .FirstAsync(j => j.JobId == job.JobId); + + _context.Entry(trackedJob).CurrentValues.SetValues(job); + + return trackedJob; + } + + public void Remove(Job job) + { + _context.Jobs.Remove(job); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/JobTagRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/JobTagRepository.cs new file mode 100644 index 0000000..09aafcd --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/JobTagRepository.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using LCU.Api.Internal.DispatcherLib.Persistence; +using LCU.Api.Internal.DispatcherLib.Extensions.Queryables; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public class JobTagRepository : BaseRepository, IJobTagRepository +{ + public JobTagRepository(AppDbContext context) : base(context) { } + + public async Task> ListAsync() + { + return await _context.JobTags.IncludeAll() + .AsNoTracking() + .ToListAsync(); + } + + public async Task FindByIdAsync(int jobTagId) + { + return await _context.JobTags.Where(jobTag => jobTag.JobTagId == jobTagId) + .IncludeAll() + .AsNoTracking() + .FirstOrDefaultAsync(); + } + + public async Task> FindByTagIdAsync(int tagId) + { + return await _context.JobTags.Where(tag => tag.TagId == tagId) + .AsNoTracking() + .ToListAsync(); + } + + public async Task AddAsync(JobTag jobTag) + { + var result = await _context.JobTags.AddAsync(jobTag); + return result.Entity; + } + + public void Remove(JobTag jobTag) + { + _context.JobTags.Remove(jobTag); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/TagRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/TagRepository.cs new file mode 100644 index 0000000..fb8d64c --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/TagRepository.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using LCU.Api.Internal.DispatcherLib.Persistence; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public class TagRepository : BaseRepository, ITagRepository +{ + public TagRepository(AppDbContext context) : base(context) { } + + public async Task> ListAsync() + { + return await _context.Tags.AsNoTracking().ToListAsync(); + } + + public async Task FindByIdAsync(int tagId) + { + return await _context.Tags.Where(tag => tag.TagId == tagId) + .AsNoTracking() + .FirstOrDefaultAsync(); + } + + public async Task AddAsync(Tag tag) + { + var result = await _context.Tags.AddAsync(tag); + return result.Entity; + } + + public void Update(Tag tag) + { + _context.Tags.Update(tag); + } + + public void Remove(Tag tag) + { + _context.Tags.Remove(tag); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/UnitOfWork.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/UnitOfWork.cs new file mode 100644 index 0000000..2ad039b --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/UnitOfWork.cs @@ -0,0 +1,23 @@ +using LCU.Api.Internal.DispatcherLib.Persistence; +using System.Threading.Tasks; +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public class UnitOfWork : IUnitOfWork +{ + private readonly AppDbContext _context; + + public UnitOfWork(AppDbContext context) + { + _context = context; + } + + public async Task CompleteAsync() + { + await _context.SaveChangesAsync(); + } + + public void Complete() + { + _context.SaveChanges(); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/WorkItemExecutionRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/WorkItemExecutionRepository.cs new file mode 100644 index 0000000..f8e8a15 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/WorkItemExecutionRepository.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using LCU.Api.Internal.DispatcherLib.Persistence; +using LCU.Library.Internal.DispatcherModels.Entities; +using LCU.Api.Internal.DispatcherLib.Extensions.Queryables; +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public class WorkItemExecutionRepository : BaseRepository, IWorkItemExecutionRepository +{ + public WorkItemExecutionRepository(AppDbContext context) : base(context) { } + + public async Task> ListAsync() + { + return await _context.WorkItemExecutions.AsNoTracking().ToListAsync(); + } + + public async Task FindByIdAsync(int id) + { + return await _context.WorkItemExecutions + .AsNoTracking() + .FirstOrDefaultAsync(workItemExecution => workItemExecution.WorkItemExecutionId == id); + } + + public async Task AddAsync(WorkItemExecution workItemExecution) + { + var result = await _context.WorkItemExecutions.AddAsync(workItemExecution); + return result.Entity; + } + + public void Update(WorkItemExecution workItem) + { + _context.WorkItemExecutions.Update(workItem); + } + + public async Task> FindUnFinishedWorkItemExecutions(int workItemId) + { + var result = await _context.WorkItemExecutions.Where(workItemExecution => workItemExecution.StartedTimestamp == null || workItemExecution.CompletedTimestamp == null) + .Where(workItemExecution => workItemExecution.WorkItemId == workItemId).AsNoTracking().ToListAsync(); + + return result; + } + + public async Task> FindAllByWorkItemId(int workItemId) + { + var result = await _context.WorkItemExecutions.Where(workItemExecution => workItemExecution.WorkItemId == workItemId) + .AsNoTracking() + .IncludeAll() + .ToListAsync(); + + return result; + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/WorkItemRepository.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/WorkItemRepository.cs new file mode 100644 index 0000000..7e5e791 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherLib/Repositories/WorkItemRepository.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using LCU.Api.Internal.DispatcherLib.Persistence; +using LCU.Api.Internal.DispatcherLib.Extensions.Queryables; +using LCU.Library.Internal.DispatcherModels.Entities; +namespace LCU.Api.Internal.DispatcherLib.Repositories; + +public class WorkItemRepository : BaseRepository, IWorkItemRepository +{ + public WorkItemRepository(AppDbContext context) : base(context) { } + + public async Task> ListByJobIdAsync(int jobId) + { + return await _context.WorkItems.Where(wi => wi.JobId == jobId) + .IncludeAll() + .AsNoTracking() + .ToListAsync(); + } + + public async Task> ListAsync() + { + return await _context.WorkItems.IncludeAll() + .AsNoTracking() + .ToListAsync(); + } + + public async Task FindByIdAsync(int id) + { + return await _context.WorkItems.Where(workItem => workItem.WorkItemId == id) + .IncludeAll() + .AsNoTracking() + .FirstOrDefaultAsync(); + } + + public async Task> FindByJobIdAsync(int jobId) + { + return await _context.WorkItems.Where(workItem => workItem.JobId == jobId) + .AsNoTracking() + .ToListAsync(); + } + + public async Task> FindByJobIdWithExecutionsAsync(int jobId, int numberOfWorkItemExecutions, int numberOfExecutionMessages) + { + return await _context.WorkItems.Where(workItem => workItem.JobId == jobId) + .IncludeAll(numberOfWorkItemExecutions, numberOfExecutionMessages) + .AsNoTracking() + .ToListAsync(); + } + + public async Task GetFirstOrDefaultWorkItemByJobId(int jobId) + { + return await _context.WorkItems.Where(workItem => workItem.JobId == jobId) + .OrderBy(wi => wi.Order) + .AsNoTracking() + .FirstOrDefaultAsync(); + } + + public async Task GetNextWorkItemByJobId(int jobId, int currentWorkItemOrder) + { + return await _context.WorkItems.Where(workItem => workItem.JobId == jobId && workItem.Order > currentWorkItemOrder) + .OrderBy(wi => wi.Order) + .AsNoTracking() + .FirstOrDefaultAsync(); + } + + public async Task GetTrackedWorkItem(int workItemId) + { + return await _context.WorkItems.FirstAsync(w => w.WorkItemId == workItemId); + } + + public async Task AddAsync(WorkItem workItem) + { + var result = await _context.WorkItems.AddAsync(workItem); + return result.Entity; + } + + public void Update(WorkItem workItem) + { + _context.WorkItems.Update(workItem); + } + + public void AddList(IEnumerable workItems) + { + _context.WorkItems.AddRange(workItems); + } + + public void RemoveList(IEnumerable workItems) + { + _context.WorkItems.RemoveRange(workItems); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/ActionBuilderService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/ActionBuilderService.cs new file mode 100644 index 0000000..4c813b3 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/ActionBuilderService.cs @@ -0,0 +1,351 @@ +using LCU.Api.Internal.DispatcherSharedLib.Configurations; +using LCU.Api.Internal.FTPLib.Models; +using LCU.API.Internal.BlendApiWrapperModels.Models.Request; +using LCU.Library.Internal.ArchiverApiModel.Models; +using LCU.Library.Internal.DataCleanUpModel.Models; +using LCU.Library.Internal.DialerApiModels.Requests; +using LCU.Library.Internal.DialerApiModels.Requests.CreateVoAppsCampaign; +using LCU.Library.Internal.DialerApiModels.Requests.UploadVoAppsCampaignResults; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.DocumentIngestorModels.Models; +using LCU.Library.Internal.DocumentIngestorModels.Requests; +using LCU.Library.Internal.DocumentTypeCleanupModels.Models; +using LCU.Library.Internal.FileOperationsModels.Models; +using LCU.Library.Internal.FileOperationsModels.Models.Requests; +using LCU.Library.Internal.FileUpdaterModels.Models.Requests; +using LCU.Library.Internal.IncentiveApiModels.Requests; +using LCU.Library.Internal.MomBotModels.Models; +using LCU.Library.Internal.NoticeProcessorModel.Models; +using LCU.Library.Internal.NotificationsModels.Models; +using LCU.Library.Internal.RealTimeAlertsModels.Models.Requests; +using LCU.Library.Internal.RemoteDepositCaptureModel.Models; +using LCU.Library.Internal.RepoApiModels.Models.Requests; +using LCU.Library.Internal.TemenosNotificationApiModels.Requests; +using System; + +namespace LCU.Api.Internal.DispatcherSharedLib.BuilderServices; + +public class ActionBuilderService : IActionBuilderService +{ + private readonly FileOperationsApiConfiguration _fileOperationsApiConfiguration; + private readonly FtpApiConfiguration _ftpApiConfiguration; + private readonly NotificationApiConfiguration _notificationApiConfiguration; + private readonly ArchiverApiConfiguration _archiverApiConfiguration; + private readonly DocumentIngestorApiConfiguration _documentIngestorApiConfiguration; + private readonly DocumentTypeCleanupApiConfiguration _documentTypeCleanupApiConfiguration; + private readonly HistoricalDocumentIngestorApiConfiguration _historicalDocumentIngestorApiConfiguration; + private readonly RemoteDepositCaptureApiConfiguration _remoteDepositCaptureApiConfiguration; + private readonly DataCleanUpApiConfiguration _dataCleanUpApiConfiguration; + private readonly NoticeProcessorApiConfiguration _noticeProcessorApiConfiguration; + private readonly MomBotApiConfiguration _momBotApiConfiguration; + private readonly BlendApiConfiguration _blendApiConfiguration; + private readonly DialerApiConfiguration _dialerApiConfiguration; + private readonly IncentiveManagerConfiguration _incentiveManagerApiConfiguration; + private readonly FileUpdaterApiConfiguration _fileUpdaterApiConfiguration; + private readonly TemenosNotificationApiConfiguration _temenosNotificationApiConfiguration; + private readonly RealTimeAlertsApiConfiguration _realTimeAlertsApiConfiguration; + private readonly RepoApiConfiguration _repoApiConfiguration; + + public ActionBuilderService(FileOperationsApiConfiguration fileOperationsApiConfiguration, + FtpApiConfiguration ftpApiConfiguration, + NotificationApiConfiguration notificationApiConfiguration, + ArchiverApiConfiguration archiverApiConfiguration, + DocumentIngestorApiConfiguration documentIngestorApiConfiguration, + DocumentTypeCleanupApiConfiguration documentTypeCleanupApiConfiguration, + HistoricalDocumentIngestorApiConfiguration historicalDocumentIngestorApiConfiguration, + RemoteDepositCaptureApiConfiguration remoteDepositCaptureApiConfiguration, + DataCleanUpApiConfiguration dataCleanUpApiConfiguration, + NoticeProcessorApiConfiguration noticeProcessorApiConfiguration, + MomBotApiConfiguration momBotApiConfiguration, + BlendApiConfiguration blendApiConfiguration, + DialerApiConfiguration dialerApiConfiguration, + IncentiveManagerConfiguration incentiveManagerConfiguration, + FileUpdaterApiConfiguration fileUpdaterApiConfiguration, + TemenosNotificationApiConfiguration temenosNotificationApiConfiguration, + RealTimeAlertsApiConfiguration realTimeAlertsApiConfiguration, + RepoApiConfiguration repoApiConfiguration) + { + _fileOperationsApiConfiguration = fileOperationsApiConfiguration; + _ftpApiConfiguration = ftpApiConfiguration; + _notificationApiConfiguration = notificationApiConfiguration; + _archiverApiConfiguration = archiverApiConfiguration; + _documentIngestorApiConfiguration = documentIngestorApiConfiguration; + _documentTypeCleanupApiConfiguration = documentTypeCleanupApiConfiguration; + _historicalDocumentIngestorApiConfiguration = historicalDocumentIngestorApiConfiguration; + _remoteDepositCaptureApiConfiguration = remoteDepositCaptureApiConfiguration; + _dataCleanUpApiConfiguration = dataCleanUpApiConfiguration; + _noticeProcessorApiConfiguration = noticeProcessorApiConfiguration; + _momBotApiConfiguration = momBotApiConfiguration; + _blendApiConfiguration = blendApiConfiguration; + _dialerApiConfiguration = dialerApiConfiguration; + _incentiveManagerApiConfiguration = incentiveManagerConfiguration; + _fileUpdaterApiConfiguration = fileUpdaterApiConfiguration; + _temenosNotificationApiConfiguration = temenosNotificationApiConfiguration; + _realTimeAlertsApiConfiguration = realTimeAlertsApiConfiguration; + _repoApiConfiguration = repoApiConfiguration; + } + + public IActionService BuildAction(ActionType actionType) + { + var actionTypeStringified = Enum.GetName(typeof(ActionType), actionType); + return BuildAction(actionTypeStringified); + } + + public IActionService BuildAction(string actionType) + { + return actionType switch + { + #region FileOperationsApi + nameof(ActionType.MoveFile) => new ActionService(_fileOperationsApiConfiguration.MoveFileEndpointUrl, + HttpMethodType.Post, + ActionType.MoveFile), + nameof(ActionType.ModifyFile) => new ActionService(_fileOperationsApiConfiguration.ModifyFileEndpointUrl, + HttpMethodType.Post, + ActionType.ModifyFile), + nameof(ActionType.CopyFile) => new ActionService(_fileOperationsApiConfiguration.CopyFileEndpointUrl, + HttpMethodType.Post, + ActionType.CopyFile), + nameof(ActionType.DeleteFile) => new ActionService(_fileOperationsApiConfiguration.DeleteFileEndpointUrl, + HttpMethodType.Post, + ActionType.DeleteFile), + nameof(ActionType.RenameFile) => new ActionService(_fileOperationsApiConfiguration.RenameFileEndpointUrl, + HttpMethodType.Post, + ActionType.RenameFile), + nameof(ActionType.ZipDirectory) => new ActionService(_fileOperationsApiConfiguration.ZipDirectoryEndpointUrl, + HttpMethodType.Post, + ActionType.ZipDirectory), + nameof(ActionType.CreateDirectory) => new ActionService(_fileOperationsApiConfiguration.CreateDirectoryEndpointUrl, + HttpMethodType.Post, + ActionType.CreateDirectory), + nameof(ActionType.RenameDirectory) => new ActionService(_fileOperationsApiConfiguration.RenameDirectoryEndpointUrl, + HttpMethodType.Post, + ActionType.RenameDirectory), + nameof(ActionType.UnzipFile) => new ActionService(_fileOperationsApiConfiguration.UnzipFileEndpointUrl, + HttpMethodType.Post, + ActionType.UnzipFile), + nameof(ActionType.EncryptFileData) => new ActionService(_fileOperationsApiConfiguration.EncryptFileDataEndpointUrl, + HttpMethodType.Post, + ActionType.EncryptFileData), + nameof(ActionType.DecryptFileData) => new ActionService(_fileOperationsApiConfiguration.DecryptFileDataEndpointUrl, + HttpMethodType.Post, + ActionType.DecryptFileData), + nameof(ActionType.UnzipPasswordProtectedFile) => new ActionService(_fileOperationsApiConfiguration.UnzipPasswordProtectedFileEndpointUrl, + HttpMethodType.Post, + ActionType.UnzipPasswordProtectedFile), + nameof(ActionType.DeleteDirectory) => new ActionService(_fileOperationsApiConfiguration.DeleteDirectoryEndpointUrl, + HttpMethodType.Post, + ActionType.DeleteDirectory), + nameof(ActionType.ExtractIndexFileData) => new ActionService(_fileOperationsApiConfiguration.ExtractIndexFileDataEndpointUrl, + HttpMethodType.Post, + ActionType.ExtractIndexFileData), + nameof(ActionType.ValidateFileRequest) => new ActionService(_fileOperationsApiConfiguration.FileValidationEndpointUrl, + HttpMethodType.Post, + ActionType.ValidateFileRequest), + nameof(ActionType.ValidateFileSizeInRange) => new ActionService(_fileOperationsApiConfiguration.ValidateFileSizeInRangeUrl, + HttpMethodType.Post, + ActionType.ValidateFileSizeInRange), + nameof(ActionType.DeleteEmptyDirectoryRequest) => new ActionService(_fileOperationsApiConfiguration.DeleteEmptyDirectoryEndpointUrl, + HttpMethodType.Post, + ActionType.DeleteEmptyDirectoryRequest), + #endregion FileOperationsApi + + #region FtpApi + nameof(ActionType.SftpDownloadFile) => new ActionService(_ftpApiConfiguration.QueueSftpDownloadFileEndpointUrl, + HttpMethodType.Post, + ActionType.SftpDownloadFile), + nameof(ActionType.SftpUploadFile) => new ActionService(_ftpApiConfiguration.QueueSftpUploadFileEndpointUrl, + HttpMethodType.Post, + ActionType.SftpUploadFile), + #endregion FtpApi + + #region NotificationsApi + nameof(ActionType.Email) => new ActionService(_notificationApiConfiguration.EmailEndpointUrl, + HttpMethodType.Post, + ActionType.Email), + #endregion NotificationsApi + + #region ArchiverApi + nameof(ActionType.CoreArchiveRequest) => new ActionService(_archiverApiConfiguration.QueueArchiverEndpointUrl, + HttpMethodType.Post, + ActionType.CoreArchiveRequest), + #endregion ArchiverApi + + #region DocumentIngestorApi + nameof(ActionType.DocumentIngestor) => new ActionService(_documentIngestorApiConfiguration.QueueDocumentIngestorEndpointUrl, + HttpMethodType.Post, + ActionType.DocumentIngestor), + nameof(ActionType.DocumentBatchIngestion) => new ActionService(_documentIngestorApiConfiguration.QueueDocumentBatchIngestionEndpointUrl, + HttpMethodType.Post, + ActionType.DocumentBatchIngestion), + nameof(ActionType.IngestorPdfPartitioner) => new ActionService(_documentIngestorApiConfiguration.QueueIngestorPdfPartitionerEndpointUrl, + HttpMethodType.Post, + ActionType.IngestorPdfPartitioner), + nameof(ActionType.MemberDocumentNotification) => new ActionService(_documentIngestorApiConfiguration.QueueMemberDocumentNotifierEndpointUrl, + HttpMethodType.Post, + ActionType.MemberDocumentNotification), + nameof(ActionType.IngestionFolderCleanUp) => new ActionService(_documentIngestorApiConfiguration.QueueIngestionFolderCleanUpEndpointUrl, + HttpMethodType.Post, + ActionType.IngestionFolderCleanUp), + nameof(ActionType.DocumentIngestorProcessStatusEmail) => new ActionService(_documentIngestorApiConfiguration.QueueProcessStatusEmailRequestEndpointUrl, + HttpMethodType.Post, + ActionType.DocumentIngestorProcessStatusEmail), + + #endregion DocumentIngestorApi + + #region DocumentTypeCleanupApi + nameof(ActionType.DocumentRetentionCleanupRequest) => new ActionService(_documentTypeCleanupApiConfiguration.QueueDocumentRetentionCleanupEndpointUrl, + HttpMethodType.Post, + ActionType.DocumentRetentionCleanupRequest), + + #endregion DocumentTypeCleanupApi + + #region HistoricalDocumentIngestorApi + nameof(ActionType.HistoricalDocumentIngestor) => new ActionService(_historicalDocumentIngestorApiConfiguration.QueueDocumentIngestorEndpointUrl, + HttpMethodType.Post, + ActionType.HistoricalDocumentIngestor), + nameof(ActionType.HistoricalDocumentBatchIngestion) => new ActionService(_historicalDocumentIngestorApiConfiguration.QueueDocumentBatchIngestionEndpointUrl, + HttpMethodType.Post, + ActionType.HistoricalDocumentBatchIngestion), + nameof(ActionType.HistoricalIngestorPdfPartitioner) => new ActionService(_historicalDocumentIngestorApiConfiguration.QueueIngestorPdfPartitionerEndpointUrl, + HttpMethodType.Post, + ActionType.HistoricalIngestorPdfPartitioner), + #endregion HistoricalDocumentIngestorApi + + #region RemoteDepositCaptureApi + nameof(ActionType.RemoteDepositCaptureXmlFile) => new ActionService(_remoteDepositCaptureApiConfiguration.QueueRemoteDepositCaptureXmlEndpointUrl, + HttpMethodType.Post, + ActionType.RemoteDepositCaptureXmlFile), + #endregion RemoteDepositCaptureApi + + #region DataCleanUpApi + nameof(ActionType.DataCleanUp) => new ActionService(_dataCleanUpApiConfiguration.QueueDataCleanUpEndpointUrl, + HttpMethodType.Post, + ActionType.DataCleanUp), + nameof(ActionType.AtmPersonCheckHolds) => new ActionService(_dataCleanUpApiConfiguration.QueueUpdatePersonForCheckHoldsEndpointUrl, + HttpMethodType.Post, + ActionType.AtmPersonCheckHolds), + nameof(ActionType.DataCleanupNcoaUpdateRequest) => new ActionService(_dataCleanUpApiConfiguration.QueueNcoaUpdateRequestEndpointUrl, + HttpMethodType.Post, + ActionType.DataCleanupNcoaUpdateRequest), + nameof(ActionType.DataCleanUpFannieMaeRequest) => new ActionService(_dataCleanUpApiConfiguration.QueueUpdateFannieMaeMortgageRequestEndpointUrl, + HttpMethodType.Post, + ActionType.DataCleanUpFannieMaeRequest), + nameof(ActionType.DataCleanUpIndirectFundingRequest) => new ActionService(_dataCleanUpApiConfiguration.QueueIndirectFundingEndpointUrl, + HttpMethodType.Post, + ActionType.DataCleanUpIndirectFundingRequest), + #endregion DataCleanUpApi + + #region NoticeProcessorApi + nameof(ActionType.NoticeProcessorExtractRequest) => new ActionService(_noticeProcessorApiConfiguration.QueueExtractEndpointUrl, + HttpMethodType.Post, + ActionType.NoticeProcessorExtractRequest), + nameof(ActionType.NoticeProcessorSummaryEmailRequest) => new ActionService(_noticeProcessorApiConfiguration.QueueProcessingEmailSummaryEndpointUrl, + HttpMethodType.Post, + ActionType.NoticeProcessorSummaryEmailRequest), + #endregion DataCleanUpApi + + #region MomBotApi + nameof(ActionType.MomBotRequest) => new ActionService(_momBotApiConfiguration.QueueMomBotEndpointUrl, HttpMethodType.Post, ActionType.MomBotRequest), + #endregion MomBotApi + + #region BlendApi + nameof(ActionType.BlendDataRequest) => new ActionService(_blendApiConfiguration.QueueDataImportEndpointUrl, + HttpMethodType.Post, + ActionType.BlendDataRequest), + #endregion BlendApi + + #region DialerApi + nameof(ActionType.DialerUploadRequest) => new ActionService(_dialerApiConfiguration.QueueDialerUploadEndpointUrl, + HttpMethodType.Post, + ActionType.DialerUploadRequest), + nameof(ActionType.UploadDoNotCallRequest) => new ActionService(_dialerApiConfiguration.QueueUploadDoNotCallEndpointUrl, + HttpMethodType.Post, + ActionType.UploadDoNotCallRequest), + nameof(ActionType.CreateVoAppsCampaignsRequest) => new ActionService(_dialerApiConfiguration.QueueCreateVoAppsCampaignEndpointUrl, + HttpMethodType.Post, + ActionType.CreateVoAppsCampaignsRequest), + nameof(ActionType.UploadVoAppsCampaignResultsRequest) => new ActionService(_dialerApiConfiguration.QueueUploadVoAppsCampaignResultsEndpointUrl, + HttpMethodType.Post, + ActionType.UploadVoAppsCampaignResultsRequest), + nameof(ActionType.UploadVoAppsCampaignResultsErrorRequest) => new ActionService(_dialerApiConfiguration.QueueUploadVoAppsCampaignErrorRequestEndpointUrl, + HttpMethodType.Post, + ActionType.UploadVoAppsCampaignResultsErrorRequest), + #endregion DialerApi + + #region IncentiveManagerApi + nameof(ActionType.ImportCreditCardSalesRequest) => new ActionService(_incentiveManagerApiConfiguration.QueueCreditCardImportEndpointUrl, + HttpMethodType.Post, + ActionType.ImportCreditCardSalesRequest), + + nameof(ActionType.ImportIndirectVehicleSalesRequest) => new ActionService(_incentiveManagerApiConfiguration.QueueVehicleImportEndpointUrl, + HttpMethodType.Post, + ActionType.ImportIndirectVehicleSalesRequest), + + nameof(ActionType.IncentiveProcessRequest) => new ActionService(_incentiveManagerApiConfiguration.QueueIncentiveProcessEndpointUrl, + HttpMethodType.Post, + ActionType.IncentiveProcessRequest), + + nameof(ActionType.NightlyIncentiveProcessRequest) => new ActionService(_incentiveManagerApiConfiguration.QueueNightlyIncentiveProcessEndpointUrl, + HttpMethodType.Post, + ActionType.NightlyIncentiveProcessRequest), + + nameof(ActionType.ActionCreditCardRequest) => new ActionService(_incentiveManagerApiConfiguration.QueueActionCreditCardEndpointUrl, + HttpMethodType.Post, + ActionType.ActionCreditCardRequest), + + nameof(ActionType.ActionVehicleRequest) => new ActionService(_incentiveManagerApiConfiguration.QueueActionVehicleEndpointUrl, + HttpMethodType.Post, + ActionType.ActionVehicleRequest), + + nameof(ActionType.AlliedGapMbpRequest) => new ActionService(_incentiveManagerApiConfiguration.QueueAlliedGapMbpEndpointUrl, + HttpMethodType.Post, + ActionType.AlliedGapMbpRequest), + + nameof(ActionType.DayforceProcessRequest) => new ActionService(_incentiveManagerApiConfiguration.QueueDayforceProcessEndpointUrl, + HttpMethodType.Post, + ActionType.DayforceProcessRequest), + + nameof(ActionType.GoalDispersalRequest) => new ActionService(_incentiveManagerApiConfiguration.QueueGoalDispersalProcessEndpointUrl, + HttpMethodType.Post, + ActionType.GoalDispersalRequest), + + nameof(ActionType.PayoutIncentiveRequest) => new ActionService(_incentiveManagerApiConfiguration.QueuePayoutProcessEndpointUrl, + HttpMethodType.Post, + ActionType.PayoutIncentiveRequest), + + nameof(ActionType.ReportingRequest) => new ActionService(_incentiveManagerApiConfiguration.QueueReportingProcessEndpointUrl, + HttpMethodType.Post, + ActionType.ReportingRequest), + #endregion IncentiveManagerApi + + #region FileUpdaterApi + nameof(ActionType.IndexFieldMapperRequest) => new ActionService(_fileUpdaterApiConfiguration.QueueIndexFieldMapperUrl, + HttpMethodType.Post, + ActionType.IndexFieldMapperRequest), + #endregion + + #region TemenosNotificationApi + nameof(ActionType.DealerAchNotificationRequest) => new ActionService(_temenosNotificationApiConfiguration.QueueDealerAchNotificationUrl, + HttpMethodType.Post, + ActionType.DealerAchNotificationRequest), + #endregion + + #region RealTimeAlertsApi + nameof(ActionType.InitiateRealTimeAlertsRequest) => new ActionService(_realTimeAlertsApiConfiguration.QueueInitiateRealTimeAlertsEndpointUrl, + HttpMethodType.Post, + ActionType.InitiateRealTimeAlertsRequest), + nameof(ActionType.InitiateRealTimeAlertsErroredProcessingRequest) => new ActionService(_realTimeAlertsApiConfiguration.QueueErroredTransactionCleanUpEndpointUrl, + HttpMethodType.Post, + ActionType.InitiateRealTimeAlertsErroredProcessingRequest), + #endregion + + #region RepoApi + nameof(ActionType.RepoBatchProcessRequest) => new ActionService(_repoApiConfiguration.AccountBatchEndpointUrl, + HttpMethodType.Post, + ActionType.RepoBatchProcessRequest), + #endregion RepoApi + + _ => throw new NotImplementedException(), + }; + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/ActionService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/ActionService.cs new file mode 100644 index 0000000..326a4fc --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/ActionService.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using LCU.Library.Internal.ApiResponse.Models; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.DispatcherModels.Entities; +using LCU.Library.Internal.DispatcherModels.Models; +namespace LCU.Api.Internal.DispatcherSharedLib.BuilderServices; + +public class ActionService : IActionService where TRequest : ReturnResponse +{ + private readonly string Url; + private readonly HttpMethodType HttpMethodType; + private readonly ActionType ActionType; + + public ActionService(string url, HttpMethodType httpMethodType, ActionType actionType) + { + Url = url; + HttpMethodType = httpMethodType; + ActionType = actionType; + } + + /// + /// NOTE: This will only work if the proper configuration has been setup. + /// + /// + /// + public WorkItem AddUrlAndHttpMethod(WorkItem workItem) + { + if (Url is null) + { + throw new ArgumentNullException("Url has not been set by configuration."); + } + + workItem.Url = Url; + workItem.HttpMethod = HttpMethodType; + return workItem; + } + + public WorkItem CreateNewWorkitem(object request, WorkItemDetails workItemDetails) + { + var workItemRequest = (TRequest)request; + return new WorkItem + { + WorkItemId = workItemDetails.WorkItemId, + JobId = workItemDetails.JobId, + Name = workItemDetails.Name, + Description = workItemDetails.Description, + Order = workItemDetails.Order, + Url = "", + HttpMethod = HttpMethodType.Unknown, + SerializedPayload = JsonConvert.SerializeObject(workItemRequest), + ActionType = ActionType, + CancelJobOnFailure = workItemDetails.CancelJobOnFailure, + CancelJobLimit = workItemDetails.CancelJobLimit, + CanceledJobCounter = 0 + }; + } + + public Type GetActionType() + { + return typeof(TRequest); + } + + public WorkItem AddWorkItemReturnResponse(WorkItem workItem, ResponseItem responseItem) + { + var deserilizedObject = JsonConvert.DeserializeObject(workItem.SerializedPayload); + deserilizedObject.ResponseItems = RemoveDispatcherReturnResponse(deserilizedObject.ResponseItems); + deserilizedObject.ResponseItems.Add(responseItem); + workItem.SerializedPayload = JsonConvert.SerializeObject(deserilizedObject); + return workItem; + } + + private List RemoveDispatcherReturnResponse(List responseItems) + { + var toRemove = new List(); + if (responseItems == null) + { + return toRemove; + } + + foreach (var responseItem in responseItems) + { + try + { + var existingReturnResponse = JsonConvert.DeserializeObject(responseItem.ResponsePayload); + toRemove.Add(responseItem); + } + catch + { + continue; + } + } + + foreach (var responseItem in toRemove) + { + responseItems.Remove(responseItem); + } + + return responseItems; + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/IActionBuilderService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/IActionBuilderService.cs new file mode 100644 index 0000000..cbf5be4 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/IActionBuilderService.cs @@ -0,0 +1,8 @@ +using LCU.Library.Internal.DispatcherModels.Enums; +namespace LCU.Api.Internal.DispatcherSharedLib.BuilderServices; + +public interface IActionBuilderService +{ + IActionService BuildAction(ActionType type); + IActionService BuildAction(string type); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/IActionService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/IActionService.cs new file mode 100644 index 0000000..284dc4f --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/IActionService.cs @@ -0,0 +1,13 @@ +using System; +using LCU.Library.Internal.ApiResponse.Models; +using LCU.Library.Internal.DispatcherModels.Entities; +using LCU.Library.Internal.DispatcherModels.Models; +namespace LCU.Api.Internal.DispatcherSharedLib.BuilderServices; + +public interface IActionService +{ + WorkItem AddUrlAndHttpMethod(WorkItem workItem); + WorkItem CreateNewWorkitem(object request, WorkItemDetails workItemDetails); + Type GetActionType(); + WorkItem AddWorkItemReturnResponse(WorkItem workItem, ResponseItem responseItem); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/IRequestBuilderService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/IRequestBuilderService.cs new file mode 100644 index 0000000..e4196b4 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/IRequestBuilderService.cs @@ -0,0 +1,16 @@ +using System; +using LCU.Library.Internal.DispatcherModels.Models; +using LCU.Library.Internal.ApiResponse.Models; +using LCU.Library.Internal.DispatcherModels.Enums; +using System.Threading.Tasks; +namespace LCU.Api.Internal.DispatcherSharedLib.BuilderServices; + +public interface IRequestBuilderService +{ + void ClearJob(); + void CreateJob(JobDetails jobDetails); + Task SendDispatcherRequest(ReturnResponse returnResponse = null); + Type GetDispatcherTypeByString(string type); + void AddWorkItem(object request, WorkItemDetails workItemDetails, ActionType actionType); + void AddWorkItem(object request, WorkItemDetails workItemDetails, string actionType); +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/RequestBuilderService.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/RequestBuilderService.cs new file mode 100644 index 0000000..c6d3281 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/BuilderServices/RequestBuilderService.cs @@ -0,0 +1,152 @@ +using Serilog; +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using LCU.Library.Internal.ApiResponse.Models; +using LCU.Library.Internal.DispatcherModels.Entities; +using LCU.Library.Internal.DispatcherModels.Models; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Api.Internal.DispatcherSharedLib.Utilities; +using LCU.Api.Internal.DispatcherSharedLib.Configurations; +using LCU.Library.Internal.LcuHttpClient.HttpRequest; +using System.Net.Http; +using System.Threading.Tasks; +namespace LCU.Api.Internal.DispatcherSharedLib.BuilderServices; + +public class RequestBuilderService : IRequestBuilderService +{ + private readonly IPostRequest _postRequest; + private readonly IActionBuilderService _actionBuilderService; + private readonly DispatcherRequestBuilderConfiguration _requestBuilderConfiguration; + private Job JobRequest; + + public RequestBuilderService(IPostRequest postRequest, + IActionBuilderService actionBuilderService, + DispatcherRequestBuilderConfiguration requestBuilderConfiguration) + { + _postRequest = postRequest; + _actionBuilderService = actionBuilderService; + _requestBuilderConfiguration = requestBuilderConfiguration; + } + + public void ClearJob() + { + JobRequest = null; + } + + public void CreateJob(JobDetails jobDetails) + { + ClearJob(); + + JobRequest = new Job + { + JobId = jobDetails.JobId, + RequestingApplication = jobDetails.RequestingApplication, + Name = jobDetails.Name, + Description = jobDetails.Description, + CreatedBy = jobDetails.CreatedBy, + CreatedAt = jobDetails.CreatedAt, + ModifiedBy = jobDetails.ModifiedBy, + ModifiedDate = jobDetails.ModifiedDate, + CronSchedule = jobDetails.CronSchedule, + NextRunTime = jobDetails.NextRunTime, + SkipHolidays = jobDetails.SkipHolidays, + JobErrorRecipients = CreateJobErrorRecipients(jobDetails), + WorkItems = new List(), + JobTags = jobDetails.JobTags, + JobStatus = jobDetails.JobStatus + }; + + CronHelpers.SetNextScheduledRunTime(ref JobRequest); + } + + public List CreateJobErrorRecipients(JobDetails jobDetails) + { + var jobErrorRecipients = new List(); + + foreach (var recipient in jobDetails.ErrorRecipients) + { + if (string.IsNullOrEmpty(recipient)) + { + continue; + } + + var newJobErrorRecipient = new JobErrorRecipient() + { + JobId = jobDetails.JobId, + Recipient = recipient + }; + jobErrorRecipients.Add(newJobErrorRecipient); + } + + return jobErrorRecipients; + } + + public async Task SendDispatcherRequest(ReturnResponse returnResponse = null) + { + try + { + if (JobRequest == null) + { + Log.Error("Cannot send null Job."); + return false; + } + + JobRequest.ReturnResponse = returnResponse; + var serializedJobRequest = JsonConvert.SerializeObject(JobRequest); + + var result = await _postRequest.AsyncWithNewtonSerializedNoThrow(new Uri(_requestBuilderConfiguration.Url), serializedJobRequest); + + if (result.IsSuccessStatusCode) + { + ClearJob(); + } + + return result.IsSuccessStatusCode; + } + catch (Exception exception) + { + Log.Error(exception, "Failure to send job request"); + return false; + } + } + + public Type GetDispatcherTypeByString(string type) + { + var actionBuilder = _actionBuilderService.BuildAction(type); + return actionBuilder.GetActionType(); + } + + public void AddWorkItem(object request, WorkItemDetails workItemDetails, ActionType actionType) + { + var actionBuilder = _actionBuilderService.BuildAction(actionType); + AddWorkItemToJob(request, workItemDetails, actionBuilder); + } + + public void AddWorkItem(object request, WorkItemDetails workItemDetails, string actionType) + { + var actionBuilder = _actionBuilderService.BuildAction(actionType); + AddWorkItemToJob(request, workItemDetails, actionBuilder); + } + + private void AddWorkItemToJob(object request, WorkItemDetails workItemDetails, IActionService actionBuilder) + { + try + { + var newWorkitem = actionBuilder.CreateNewWorkitem(request, workItemDetails); + if (JobRequest == null) + { + Log.Error("Cannot add workitem to null Job."); + throw new InvalidOperationException("Cannot add workitem to null Job."); + } + + JobRequest.WorkItems.Add(newWorkitem); + } + catch (Exception exception) + { + Log.Error(exception, "Error creating WorkItem."); + ClearJob(); + throw; + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/ArchiverApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/ArchiverApiConfiguration.cs new file mode 100644 index 0000000..4918451 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/ArchiverApiConfiguration.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class ArchiverApiConfiguration +{ + public string Url { get; set; } + public string QueueArchiverEndpoint { get; set; } + + [NotMapped] + public string QueueArchiverEndpointUrl => Url + QueueArchiverEndpoint; +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/BlendApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/BlendApiConfiguration.cs new file mode 100644 index 0000000..8727777 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/BlendApiConfiguration.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class BlendApiConfiguration +{ + public string Url { get; set; } + public string QueueDataImportEndpoint { get; set; } + + [NotMapped] + public string QueueDataImportEndpointUrl => Url + QueueDataImportEndpoint; +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DataCleanUpApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DataCleanUpApiConfiguration.cs new file mode 100644 index 0000000..69a42db --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DataCleanUpApiConfiguration.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class DataCleanUpApiConfiguration +{ + public string Url { get; set; } + public string QueueDataCleanUpEndpoint { get; set; } + public string QueueUpdatePersonForCheckHoldsEndpoint { get; set; } + public string QueueNcoaUpdateEndpoint { get; set; } + public string QueueUpdateFannieMaeMortgageEndpoint { get; set; } + public string QueueIndirectFundingEndpoint { get; set; } + [NotMapped] + public string QueueDataCleanUpEndpointUrl => Url + QueueDataCleanUpEndpoint; + [NotMapped] + public string QueueUpdatePersonForCheckHoldsEndpointUrl => Url + QueueUpdatePersonForCheckHoldsEndpoint; + [NotMapped] + public string QueueNcoaUpdateRequestEndpointUrl => Url + QueueNcoaUpdateEndpoint; + [NotMapped] + public string QueueUpdateFannieMaeMortgageRequestEndpointUrl => Url + QueueUpdateFannieMaeMortgageEndpoint; + [NotMapped] + public string QueueIndirectFundingEndpointUrl => Url + QueueIndirectFundingEndpoint; +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DialerApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DialerApiConfiguration.cs new file mode 100644 index 0000000..f38c191 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DialerApiConfiguration.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class DialerApiConfiguration +{ + public string Url { get; set; } + public string QueueDialerUploadEndpoint { get; set; } + public string QueueUploadDoNotCallEndpoint { get; set; } + public string QueueCreateVoAppsCampaignEndpoint { get; set; } + public string QueueUploadVoAppsCampaignResultsEndpoint { get; set; } + public string QueueUploadVoAppsCampaignErrorRequestEndpoint { get; set; } + + [NotMapped] + public string QueueDialerUploadEndpointUrl => Url + QueueDialerUploadEndpoint; + [NotMapped] + public string QueueUploadDoNotCallEndpointUrl => Url + QueueUploadDoNotCallEndpoint; + [NotMapped] + public string QueueCreateVoAppsCampaignEndpointUrl => Url + QueueCreateVoAppsCampaignEndpoint; + [NotMapped] + public string QueueUploadVoAppsCampaignResultsEndpointUrl => Url + QueueUploadVoAppsCampaignResultsEndpoint; + [NotMapped] + public string QueueUploadVoAppsCampaignErrorRequestEndpointUrl => Url + QueueUploadVoAppsCampaignErrorRequestEndpoint; +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DispatcherRequestBuilderConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DispatcherRequestBuilderConfiguration.cs new file mode 100644 index 0000000..c5a466d --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DispatcherRequestBuilderConfiguration.cs @@ -0,0 +1,6 @@ +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class DispatcherRequestBuilderConfiguration +{ + public string Url { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DocumentIngestorApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DocumentIngestorApiConfiguration.cs new file mode 100644 index 0000000..506c24b --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DocumentIngestorApiConfiguration.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class DocumentIngestorApiConfiguration +{ + public string Url { get; set; } + public string QueueDocumentIngestorEndpoint { get; set; } + public string QueueDocumentBatchIngestionEndpoint { get; set; } + public string QueueIngestorPdfPartitionerEndpoint { get; set; } + public string QueueMemberDocumentNotifierEndpoint { get; set; } + public string QueueIngestionFolderCleanUpEndpoint { get; set; } + public string QueueProcessStatusTypeEmailRequestEndpoint { get; set; } + + [NotMapped] + public string QueueDocumentIngestorEndpointUrl => Url + QueueDocumentIngestorEndpoint; + [NotMapped] + public string QueueDocumentBatchIngestionEndpointUrl => Url + QueueDocumentBatchIngestionEndpoint; + [NotMapped] + public string QueueIngestorPdfPartitionerEndpointUrl => Url + QueueIngestorPdfPartitionerEndpoint; + [NotMapped] + public string QueueMemberDocumentNotifierEndpointUrl => Url + QueueMemberDocumentNotifierEndpoint; + [NotMapped] + public string QueueIngestionFolderCleanUpEndpointUrl => Url + QueueIngestionFolderCleanUpEndpoint; + [NotMapped] + public string QueueProcessStatusEmailRequestEndpointUrl => Url + QueueProcessStatusTypeEmailRequestEndpoint; +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DocumentTypeCleanupApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DocumentTypeCleanupApiConfiguration.cs new file mode 100644 index 0000000..2b2b56f --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/DocumentTypeCleanupApiConfiguration.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class DocumentTypeCleanupApiConfiguration +{ + public string Url { get; set; } + public string QueueDocumentRetentionCleanupEndpoint { get; set; } + + [NotMapped] + public string QueueDocumentRetentionCleanupEndpointUrl => Url + QueueDocumentRetentionCleanupEndpoint; + +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/FileOperationsApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/FileOperationsApiConfiguration.cs new file mode 100644 index 0000000..0f00f51 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/FileOperationsApiConfiguration.cs @@ -0,0 +1,75 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class FileOperationsApiConfiguration +{ + public string Url { get; set; } + public string MoveFileEndpoint { get; set; } + public string CopyFileEndpoint { get; set; } + public string ModifyFileEndpoint { get; set; } + public string RenameFileEndpoint { get; set; } + public string DeleteFileEndpoint { get; set; } + public string ZipDirectoryEndpoint { get; set; } + public string CreateDirectoryEndpoint { get; set; } + public string RenameDirectoryEndpoint { get; set; } + public string UnzipFileEndpoint { get; set; } + public string EncryptFileDataEndpoint { get; set; } + public string DecryptFileDataEndpoint { get; set; } + public string UnzipPasswordProtectedFileEndpoint { get; set; } + public string DeleteDirectoryEndpoint { get; set; } + public string FileValidationEndpoint { get; set; } + public string ExtractIndexFileDataEndpoint { get; set; } + public string ValidateFileSizeInRange { get; set; } + public string DeleteEmptyDirectoryEndpoint { get; set; } + + [NotMapped] + public string MoveFileEndpointUrl => Url + MoveFileEndpoint; + + [NotMapped] + public string CopyFileEndpointUrl => Url + CopyFileEndpoint; + + [NotMapped] + public string ModifyFileEndpointUrl => Url + ModifyFileEndpoint; + + [NotMapped] + public string RenameFileEndpointUrl => Url + RenameFileEndpoint; + + [NotMapped] + public string DeleteFileEndpointUrl => Url + DeleteFileEndpoint; + + [NotMapped] + public string ZipDirectoryEndpointUrl => Url + ZipDirectoryEndpoint; + + [NotMapped] + public string CreateDirectoryEndpointUrl => Url + CreateDirectoryEndpoint; + + [NotMapped] + public string RenameDirectoryEndpointUrl => Url + RenameDirectoryEndpoint; + + [NotMapped] + public string UnzipFileEndpointUrl => Url + UnzipFileEndpoint; + + [NotMapped] + public string EncryptFileDataEndpointUrl => Url + EncryptFileDataEndpoint; + + [NotMapped] + public string DecryptFileDataEndpointUrl => Url + DecryptFileDataEndpoint; + + [NotMapped] + public string UnzipPasswordProtectedFileEndpointUrl => Url + UnzipPasswordProtectedFileEndpoint; + + [NotMapped] + public string DeleteDirectoryEndpointUrl => Url + DeleteDirectoryEndpoint; + + [NotMapped] + public string FileValidationEndpointUrl => Url + FileValidationEndpoint; + + [NotMapped] + public string ExtractIndexFileDataEndpointUrl => Url + ExtractIndexFileDataEndpoint; + + [NotMapped] + public string ValidateFileSizeInRangeUrl => Url + ValidateFileSizeInRange; + + [NotMapped] + public string DeleteEmptyDirectoryEndpointUrl => Url + DeleteEmptyDirectoryEndpoint; +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/FileUpdaterApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/FileUpdaterApiConfiguration.cs new file mode 100644 index 0000000..8a8b0b4 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/FileUpdaterApiConfiguration.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations +{ + public class FileUpdaterApiConfiguration + { + public string Url { get; set; } + public string QueueIndexFieldMapper { get; set; } + [NotMapped] + public string QueueIndexFieldMapperUrl => Url + QueueIndexFieldMapper; + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/FtpApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/FtpApiConfiguration.cs new file mode 100644 index 0000000..3b3634c --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/FtpApiConfiguration.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class FtpApiConfiguration +{ + public string Url { get; set; } + public string SftpFileEndpoint { get; set; } + public string QueueSftpDynamicFileEndpoint { get; set; } + public string QueueSftpUploadFileEndpoint { get; set; } + public string QueueSftpDownloadFileEndpoint { get; set; } + + [NotMapped] + public string SftpFileEndpointUrl => Url + SftpFileEndpoint; + + [NotMapped] + public string QueueSftpDynamicFileEndpointUrl => Url + QueueSftpDynamicFileEndpoint; + + [NotMapped] + public string QueueSftpUploadFileEndpointUrl => Url + QueueSftpUploadFileEndpoint; + + [NotMapped] + public string QueueSftpDownloadFileEndpointUrl => Url + QueueSftpDownloadFileEndpoint; +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/HistoricalDocumentIngestorApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/HistoricalDocumentIngestorApiConfiguration.cs new file mode 100644 index 0000000..02fbed6 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/HistoricalDocumentIngestorApiConfiguration.cs @@ -0,0 +1,17 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class HistoricalDocumentIngestorApiConfiguration +{ + public string Url { get; set; } + public string QueueDocumentIngestorEndpoint { get; set; } + public string QueueDocumentBatchIngestionEndpoint { get; set; } + public string QueueIngestorPdfPartitionerEndpoint { get; set; } + + [NotMapped] + public string QueueDocumentIngestorEndpointUrl => Url + QueueDocumentIngestorEndpoint; + [NotMapped] + public string QueueDocumentBatchIngestionEndpointUrl => Url + QueueDocumentBatchIngestionEndpoint; + [NotMapped] + public string QueueIngestorPdfPartitionerEndpointUrl => Url + QueueIngestorPdfPartitionerEndpoint; +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/IncentiveManagerConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/IncentiveManagerConfiguration.cs new file mode 100644 index 0000000..4395e5f --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/IncentiveManagerConfiguration.cs @@ -0,0 +1,42 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations +{ + public class IncentiveManagerConfiguration + { + public string Url { get; set; } + public string QueueCreditCardImportEndpoint { get; set; } + public string QueueVehicleImportEndpoint { get; set; } + public string QueueIncentiveProcessEndpoint { get; set; } + public string QueueNightlyIncentiveProcessEndpoint { get; set; } + public string QueuePayoutProcessEndpoint { get; set; } + public string QueueActionCreditCardEndpoint { get; set; } + public string QueueActionVehicleEndpoint { get; set; } + public string QueueAlliedGapMbpEndpoint { get; set; } + public string QueueReportingProcessEndpoint { get; set; } + public string QueueGoalDispersalProcessEndpoint { get; set; } + public string QueueDayforceProcessEndpoint { get; set; } + [NotMapped] + public string QueueCreditCardImportEndpointUrl => Url + QueueCreditCardImportEndpoint; + [NotMapped] + public string QueueVehicleImportEndpointUrl => Url + QueueVehicleImportEndpoint; + [NotMapped] + public string QueueActionCreditCardEndpointUrl => Url + QueueActionCreditCardEndpoint; + [NotMapped] + public string QueueActionVehicleEndpointUrl => Url + QueueActionVehicleEndpoint; + [NotMapped] + public string QueueAlliedGapMbpEndpointUrl => Url + QueueAlliedGapMbpEndpoint; + [NotMapped] + public string QueueIncentiveProcessEndpointUrl => Url + QueueIncentiveProcessEndpoint; + [NotMapped] + public string QueueNightlyIncentiveProcessEndpointUrl => Url + QueueNightlyIncentiveProcessEndpoint; + [NotMapped] + public string QueuePayoutProcessEndpointUrl => Url + QueuePayoutProcessEndpoint; + [NotMapped] + public string QueueReportingProcessEndpointUrl => Url + QueueReportingProcessEndpoint; + [NotMapped] + public string QueueGoalDispersalProcessEndpointUrl => Url + QueueGoalDispersalProcessEndpoint; + [NotMapped] + public string QueueDayforceProcessEndpointUrl => Url + QueueDayforceProcessEndpoint; + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/MomBotApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/MomBotApiConfiguration.cs new file mode 100644 index 0000000..e8af316 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/MomBotApiConfiguration.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text; + +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class MomBotApiConfiguration +{ + public string Url { get; set; } + public string QueueMomBotEndpoint { get; set; } + + [NotMapped] + public string QueueMomBotEndpointUrl => Url + QueueMomBotEndpoint; +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/NoticeProcessorApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/NoticeProcessorApiConfiguration.cs new file mode 100644 index 0000000..1dbf13e --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/NoticeProcessorApiConfiguration.cs @@ -0,0 +1,14 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class NoticeProcessorApiConfiguration +{ + public string Url { get; set; } + public string QueueExtractEndpoint { get; set; } + public string QueueProcessingEmailSummaryEndpoint { get; set; } + + [NotMapped] + public string QueueExtractEndpointUrl => Url + QueueExtractEndpoint; + [NotMapped] + public string QueueProcessingEmailSummaryEndpointUrl => Url + QueueProcessingEmailSummaryEndpoint; +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/NotificationApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/NotificationApiConfiguration.cs new file mode 100644 index 0000000..6ce9e0a --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/NotificationApiConfiguration.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class NotificationApiConfiguration +{ + public string Url { get; set; } + public string EmailEndpoint { get; set; } + + [NotMapped] + public string EmailEndpointUrl => Url + EmailEndpoint; +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/RealTimeAlertsApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/RealTimeAlertsApiConfiguration.cs new file mode 100644 index 0000000..e74e264 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/RealTimeAlertsApiConfiguration.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class RealTimeAlertsApiConfiguration +{ + public string Url { get; set; } + public string QueueInitiateRealTimeAlertsEndpoint { get; set; } + public string QueueErroredTransactionCleanUpEndpoint { get; set; } + + [NotMapped] + public string QueueInitiateRealTimeAlertsEndpointUrl => Url + QueueInitiateRealTimeAlertsEndpoint; + + [NotMapped] + public string QueueErroredTransactionCleanUpEndpointUrl => Url + QueueErroredTransactionCleanUpEndpoint; +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/RemoteDepositCaptureApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/RemoteDepositCaptureApiConfiguration.cs new file mode 100644 index 0000000..92d235d --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/RemoteDepositCaptureApiConfiguration.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class RemoteDepositCaptureApiConfiguration +{ + public string Url { get; set; } + public string QueueRemoteDepositCaptureXmlEndpoint { get; set; } + + [NotMapped] + public string QueueRemoteDepositCaptureXmlEndpointUrl => Url + QueueRemoteDepositCaptureXmlEndpoint; +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/RepoApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/RepoApiConfiguration.cs new file mode 100644 index 0000000..83aaf67 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/RepoApiConfiguration.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class RepoApiConfiguration +{ + public string Url { get; set; } + public string AccountBatchEndpoint { get; set; } + + [NotMapped] + public string AccountBatchEndpointUrl => Url + AccountBatchEndpoint; +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/TemenosNotificationApiConfiguration.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/TemenosNotificationApiConfiguration.cs new file mode 100644 index 0000000..2bdfa41 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Configurations/TemenosNotificationApiConfiguration.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace LCU.Api.Internal.DispatcherSharedLib.Configurations; + +public class TemenosNotificationApiConfiguration +{ + public string Url { get; set; } + public string QueueDealerAchNotification { get; set; } + + [NotMapped] + public string QueueDealerAchNotificationUrl => Url + QueueDealerAchNotification; +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/DependencyInjection.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/DependencyInjection.cs new file mode 100644 index 0000000..4a36f09 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/DependencyInjection.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using LCU.Library.Internal.Configuration; +using LCU.Api.Internal.DispatcherSharedLib.Utilities; +using LCU.Api.Internal.DispatcherSharedLib.BuilderServices; +using LCU.Api.Internal.DispatcherSharedLib.Configurations; +namespace LCU.Api.Internal.DispatcherSharedLib; + +public static class DependencyInjection +{ + public static IServiceCollection AddDispatcherServices(this IServiceCollection services, IConfiguration configuration) + { + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + configuration.Bind(services); + + services.AddScoped(); + services.AddScoped(); + + return services; + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/LCU.Api.Internal.DispatcherSharedLib.csproj b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/LCU.Api.Internal.DispatcherSharedLib.csproj new file mode 100644 index 0000000..571f5e4 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/LCU.Api.Internal.DispatcherSharedLib.csproj @@ -0,0 +1,297 @@ + + + + net8.0 + latest + true + 5.7.21 + + 1.0.0 - Initial Creation + 1.1.0 - FileOperationsLib and ApiResponse Version upgrade. + 1.2.0 - ZipDirectoryRequest added. + 1.3.0 - SftpRequest readded. + 2.0.0 - Change so that all the librarys references are bundled with nuget. + 2.1.0 - Upgrade FtpLib version + 2.2.0 - Added DependencyInjection Class + 3.0.0 - Change to Job and WorkItem model to now use WorkItemExecution, added Name, and description. + 3.1.0 - Added Notification and refactored DispatcherRequestBuilder + 3.2.0 - Update FileOperationsLib nuget + 3.3.0 - Added FileOperations CreateDirectory and RenameDirectory + 3.3.1 - Upgrade FileOperationsLib nuget to 3.3.0 + 3.3.2 - Add ability to add job error recipients. + 3.4.0 - Adding SftpDynamicFileRequest. + 3.5.0 - Add Dynamic Zip and UnzipFileRequest + 3.6.0 - Add ArchiveRequest + 3.6.1 - Updated AutomicApiWrapperModels Nuget + 3.7.0 - Add Encrypt/Decrypt and SucceedOnError to Move/Copy/Rename/Delete File + 3.8.0 - Upgrade to netstandard2.1 + 4.0.0 - Removing models and added helpers. Models are in new LCU.Library.Internal.DispatcherModels library. + 4.1.0 - Update to use masstransit and change to use ArchiverApi rather than AutomicApiWrapper + 4.2.0 - Update FTP Lib nuget + 4.3.0 - Add Job Tags + 4.4.0 - Add JobDetails with Job Tags + 4.5.0 - Upgrade ApiResponse.Models nuget + 4.6.0 - Upgrade FileOperations Nuget and Add ModifyFile Workitem + 4.7.0 - Upgrade FTP Nuget and Update SftpUpload Request + 4.8.0 - Upgrade FTP Nuget + 4.9.0 - Upgrade FTP Nuget to 2.9.0 + 4.10.0 - Added DocumentIngestor IngestorRequest + 4.11.0 - Missed model version upgraded. + 4.12.0 - Upgrade FileOperationsLib, FtpLib, and DocumentIngestorModels lib + 4.13.0 - Added BatchIngestionRequest and UnzipPasswordProtectedRequest + 4.14.0 - Upgrade FileOperationsLib to 3.20.0 + 4.15.0 - Added IngestorPdfPartitioner WorkItem + 4.16.0 - Added MemberDocumentNotificationRequest Workitem + 4.17.0 - Added RemoteDepositCaptureXmlRequest Workitem and updated DocumentIngestor models + 4.18.0 - Added DeleteDirectoryRequest Workitem and updated DocumentIngestor models + 4.19.0 - Added UpdatePersonForCheckHoldRequest and DataCleanup Workitems + 4.19.1 - Update DocumentIngestorModels Nuget + 4.20.0 - Update FileOperations and FTP API Nuget for models + 4.20.1 - Added HistoricalDocumentIngestor, HistoricalDocumentBatchIngestion, and HistoricalIngestorPdfPartitioner WorkItems + 4.21.0 - Add ExtractRequest and ProcessingSummaryEmail request from NoticeProcessor API + 4.21.1 - Update DocumentIngestorModels Nuget + 4.22.0 - Added WorkItem properties for Cancel Job on Failure. + 5.0.0 - Enhancments and refactors: Added ActionBuilderService and ActionService classes to handle the workitem action building. Other refactors. + 5.0.1 - Add NcoaUpdate workItem + 5.0.2 - Update FTP Nuget + 5.0.3 - Update File Operatations Nuget + 5.0.4 - Add IngestionFolderCleanUp WorkItem + 5.0.5 - Correct IngestionFolderCleanUp in ActionBuilderService + 5.0.6 - Use latest version of DocumentIngestorModels + 5.0.7 - Update DataCleanUpNuget for NCOA Request + 5.0.8 - Add MomBotRequest WorkItem + 5.0.9 - Add BlendApi Data Sync Request WorkItem + 5.0.10 - Update FTP Nuget for ResumeSupportState toggle on download + 5.0.11 - Added DialerUploadRequest WorkItem. + 5.0.12 - Added UploadDoNotCallRequest WorkItem. + 5.0.13 - Update FileOperationsLib nuget + 5.0.14 - Added FannieMaeMortgageRequest WorkItem. + 5.0.15 - Added CreateVoAppsCampaignsRequest WorkItem. + 5.1.0 - Updated CreateVoAppsCampaignsRequest WorkItem. + 5.1.1 - Updated CreateVoAppsCampaignsRequest WorkItem. + 5.1.2 - Updated CreateVoAppsCampaignsRequest WorkItem. + 5.1.3 - Added UploadVoAppsCampaignRequest WorkItem + 5.1.4 - Updated model validation on DialerApiModels + 5.2.0 - Added new fields on DialerUpload model + 5.3.0 - Updated CreateVoAppsCampaignRequest model with new fields. + 5.4.0 - Updated CreateVoAppsCampaignRequest model. + 5.5.0 - Added ImportCreditCardSalesRequest model. + 5.5.1 - Added DataCleanUpIndirectFunding WorkItem. + 5.6.0 - Alter BlendApi Data Sync Request WorkItem + 5.6.1 - Update IndirectFundRequest + 5.6.2 - Added ImportIndirectVehicleSalesRequest model. + 5.6.3 - Updated BlendModels Nuget for ApplicationProcess + 5.6.4 - Added SkipHolidays to Job model + 5.6.5 - Updated Dialer API Nuget for the UploadVoAppsCampaignResultsRequest updates + 5.6.6 - Updating the Document Ingestors Models + 5.6.7 - Add ValidateFileRequest FileOperations WorkItem + 5.6.8 - Added ExtractIndexFileData WorkItem + 5.6.9 - Update NuGet packages + 5.6.10 - Updated Incentive Manager import models. + 5.6.11 - Added IndexFieldMapperRequest model. + 5.6.12 - Added DealerAchNotification model. + 5.6.13 - Updated ClientHelper. + 5.6.14 - Update JSON handling in Client Helper. + 5.6.15 - Update clienthelper to allow async + 5.6.16 - Update clienthelper to add wait + 5.6.17 - Remove retry from client helper + 5.6.18 - Ensure respone is sent with error information for write-back + 5.6.19 - Updated FileUpdaterModels version + 5.6.20 - Add RealTimeAlerts WorkItem + 5.6.21 - Update NuGet packages + 5.6.22 - Update Nuget packages for Osg Tax + 5.6.23 - Add RealTimeAlerts Errored Process WorkItem + 5.7.0 - Upgrade to dotnet8 + 5.7.1 - Update NuGet packages + 5.7.2 - Update NuGet packages + 5.7.3 - Update nuget for Osg DNA Notices Split + 5.7.4 - Add Incentive Mgr Work Items + 5.7.5 - Update Nuget packages + 5.7.6 - Update DocumentIngestorModels Nuget package + 5.7.7 - Update DocumentIngestorModel Nuget + 5.7.8 - Update NuGet packages + 5.7.9 - Update FileOperations NuGet packages + 5.7.10 - Added DeleteEmptyDirectoryRequest model + 5.7.11 - Add JobStatus in RequestBuilder service + 5.7.12 - Added DocumentRetentionCleanup WorkItem + 5.7.13 - Update Incentive Mgr Nuget package + 5.7.14 - Update Dialer Api Nuget package + 5.7.15 - Update File Operations Api Nuget package + 5.7.16 - Update CreateJob Method to handle JobStatus + 5.7.17 - Update Dialer Api Nuget package Error Process + 5.7.18 - Update for Repo API WorkItem + 5.7.19 - Update DocumentIngestorModels Nuget Package + 5.7.20 - Update Dialer Api Nuget package for campaigns + 5.7.21 - Update DocumentIngestorModels Nuget package + + + 1.0.0 - Initial Creation + 1.1.0 - FileOperationsLib and ApiResponse Version upgrade. + 1.2.0 - ZipDirectoryRequest added. + 1.3.0 - SftpRequest readded. + 2.0.0 - Change so that all the librarys references are bundled with nuget. + 2.1.0 - Upgrade FtpLib version + 2.2.0 - Added DependencyInjection Class + 3.0.0 - Change to Job and WorkItem model to now use WorkItemExecution, added Name, and description. + 3.1.0 - Added Notification and refactored DispatcherRequestBuilder + 3.2.0 - Update FileOperationsLib nuget + 3.3.0 - Added FileOperations CreateDirectory and RenameDirectory + 3.3.1 - Upgrade FileOperationsLib nuget to 3.3.0 + 3.3.2 - Add ability to add job error recipients. + 3.4.0 - Adding SftpDynamicFileRequest. + 3.5.0 - Add Dynamic Zip and UnzipFileRequest + 3.6.0 - Add ArchiveRequest + 3.6.1 - Updated AutomicApiWrapperModels Nuget + 3.7.0 - Add Encrypt/Decrypt and SucceedOnError to Move/Copy/Rename/Delete File + 3.8.0 - Upgrade to netstandard2.1 + 4.0.0 - Removing models and added helpers. Models are in new LCU.Library.Internal.DispatcherModels library. + 4.1.0 - Update to use masstransit and change to use ArchiverApi rather than AutomicApiWrapper + 4.2.0 - Update FTP Lib nuget + 4.3.0 - Add Job Tags + 4.4.0 - Add JobDetails with Job Tags + 4.5.0 - Upgrade ApiResponse.Models nuget + 4.6.0 - Upgrade FileOperations Nuget and Add ModifyFile Workitem + 4.7.0 - Upgrade FTP Nuget and Update SftpUpload Request + 4.8.0 - Upgrade FTP Nuget + 4.9.0 - Upgrade FTP Nuget to 2.9.0 + 4.10.0 - Added DocumentIngestor IngestorRequest + 4.11.0 - Missed model version upgraded. + 4.12.0 - Upgrade FileOperationsLib, FtpLib, and DocumentIngestorModels lib + 4.13.0 - Added BatchIngestionRequest and UnzipPasswordProtectedRequest + 4.14.0 - Upgrade FileOperationsLib to 3.20.0 + 4.15.0 - Added IngestorPdfPartitioner WorkItem + 4.16.0 - Added MemberDocumentNotificationRequest Workitem + 4.17.0 - Added RemoteDepositCaptureXmlRequest Workitem and updated DocumentIngestor models + 4.18.0 - Added DeleteDirectoryRequest Workitem and updated DocumentIngestor models + 4.19.0 - Added UpdatePersonForCheckHoldRequest and DataCleanup Workitems + 4.19.1 - Update DocumentIngestorModels Nuget + 4.20.0 - Update FileOperations and FTP API Nuget for models + 4.20.1 - Added HistoricalDocumentIngestor, HistoricalDocumentBatchIngestion, and HistoricalIngestorPdfPartitioner WorkItems + 4.21.0 - Add ExtractRequest and ProcessingSummaryEmail request from NoticeProcessor API + 4.21.1 - Update DocumentIngestorModels Nuget + 4.22.0 - Added WorkItem properties for Cancel Job on Failure. + 5.0.0 - Enhancments and refactors: Added ActionBuilderService and ActionService classes to handle the workitem action building. Other refactors. + 5.0.1 - Add NcoaUpdate workItem + 5.0.2 - Update FTP Nuget + 5.0.3 - Update File Operatations Nuget + 5.0.4 - Add IngestionFolderCleanUp WorkItem + 5.0.5 - Correct IngestionFolderCleanUp in ActionBuilderService + 5.0.6 - Use latest version of DocumentIngestorModels + 5.0.7 - Update DataCleanUpNuget for NCOA Request + 5.0.8 - Add MomBotRequest WorkItem + 5.0.9 - Add BlendApi Data Sync Request WorkItem + 5.0.10 - Update FTP Nuget for ResumeSupportState toggle on download + 5.0.11 - Added DialerUploadRequest WorkItem. + 5.0.12 - Added UploadDoNotCallRequest WorkItem. + 5.0.13 - Update FileOperationsLib nuget + 5.0.14 - Added FannieMaeMortgageRequest WorkItem. + 5.0.15 - Added CreateVoAppsCampaignsRequest WorkItem. + 5.1.0 - Updated CreateVoAppsCampaignsRequest WorkItem. + 5.1.1 - Updated CreateVoAppsCampaignsRequest WorkItem. + 5.1.2 - Updated CreateVoAppsCampaignsRequest WorkItem. + 5.1.3 - Added UploadVoAppsCampaignRequest WorkItem + 5.1.4 - Updated model validation on DialerApiModels + 5.2.0 - Added new fields on DialerUpload model + 5.3.0 - Updated CreateVoAppsCampaignRequest model with new fields. + 5.4.0 - Updated CreateVoAppsCampaignRequest model. + 5.5.0 - Added ImportCreditCardSalesRequest model. + 5.5.1 - Added DataCleanUpIndirectFunding WorkItem. + 5.6.0 - Alter BlendApi Data Sync Request WorkItem + 5.6.1 - Update IndirectFundRequest + 5.6.2 - Added ImportIndirectVehicleSalesRequest model. + 5.6.3 - Updated BlendModels Nuget for ApplicationProcess + 5.6.4 - Added SkipHolidays to Job model + 5.6.5 - Updated Dialer API Nuget for the UploadVoAppsCampaignResultsRequest updates + 5.6.6 - Updating the Document Ingestors Models + 5.6.7 - Add ValidateFileRequest FileOperations WorkItem + 5.6.8 - Added ExtractIndexFileData WorkItem + 5.6.9 - Update NuGet packages + 5.6.10 - Updated Incentive Manager import models. + 5.6.11 - Added IndexFieldMapperRequest model. + 5.6.12 - Added DealerAchNotification model. + 5.6.13 - Updated ClientHelper. + 5.6.14 - Update JSON handling in Client Helper. + 5.6.16 - Update clienthelper to add wait + 5.6.17 - Remove retry from client helper + 5.6.18 - Ensure respone is sent with error information for write-back + 5.6.19 - Updated FileUpdaterModels version + 5.6.20 - Add RealTimeAlerts WorkItem + 5.6.21 - Update NuGet packages + 5.6.22 - Update Nuget packages for Osg Tax + 5.6.23 - Add RealTimeAlerts Errored Process WorkItem + 5.7.0 - Upgrade to dotnet8 + 5.7.1 - Update NuGet packages + 5.7.2 - Update NuGet packages + 5.7.3 - Update nuget for Osg DNA Notices Split + 5.7.4 - Add Incentive Mgr Work Items + 5.7.5 - Update Nuget packages + 5.7.6 - Update DocumentIngestorModels Nuget package + 5.7.7 - Update DocumentIngestorModel Nuget + 5.7.8 - Update NuGet packages + 5.7.9 - Update FileOperations NuGet packages + 5.7.10 - Added DeleteEmptyDirectoryRequest model + 5.7.11 - Add JobStatus in RequestBuilder service + 5.7.12 - Added DocumentRetentionCleanup WorkItem + 5.7.13 - Update Incentive Mgr Nuget package + 5.7.14 - Update Dialer Api Nuget package + 5.7.15 - Update File Operations Api Nuget package + 5.7.16 - Update CreateJob Method to handle JobStatus + 5.7.17 - Update Dialer Api Nuget package Error Process + 5.7.18 - Update for Repo API WorkItem + 5.7.19 - Update DocumentIngestorModels Nuget Package + 5.7.20 - Update Dialer Api Nuget package for campaigns + 5.7.21 - Update DocumentIngestorModels Nuget package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage + + + + + + <_ReferenceCopyLocalPaths Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference')->WithMetadataValue('PrivateAssets', 'All'))" /> + + + + + + + + + + + + diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Utilities/CronHelpers.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Utilities/CronHelpers.cs new file mode 100644 index 0000000..49e1eed --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Utilities/CronHelpers.cs @@ -0,0 +1,38 @@ +using System; +using NCrontab; +using Serilog; +using LCU.Library.Internal.DispatcherModels.Entities; +using LCU.Library.Internal.DispatcherModels.Enums; +namespace LCU.Api.Internal.DispatcherSharedLib.Utilities; + +public static class CronHelpers +{ + public static void SetNextScheduledRunTime(ref Job job) + { + var isCronSchedule = !string.IsNullOrEmpty(job.CronSchedule); + if (isCronSchedule) + { + CrontabSchedule schedule = CrontabSchedule.Parse(job.CronSchedule); + job.NextRunTime = schedule.GetNextOccurrence(DateTime.Now); + + if (job.SkipHolidays) + { + job.NextRunTime = HolidayHelpers.HandleHolidays(job.NextRunTime); + } + job.JobStatus = JobStatus.ReadyToSchedule; + } + } + + public static CrontabSchedule SetCronSchedule(string cron, string cronDefault) + { + try + { + return CrontabSchedule.Parse(cron); + } + catch (Exception exception) + { + Log.Error(exception, "Failure parsing cron-tab in appsettings."); + return CrontabSchedule.Parse(cronDefault); + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Utilities/HolidayHelpers.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Utilities/HolidayHelpers.cs new file mode 100644 index 0000000..4c3e156 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSharedLib/Utilities/HolidayHelpers.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace LCU.Api.Internal.DispatcherSharedLib.Utilities; + +public static class HolidayHelpers +{ + public static DateTime? HandleHolidays(DateTime? nextRunTime) + { + if (nextRunTime != null) + { + List holidays = GetHolidays(nextRunTime.Value.Year); + var today = Convert.ToDateTime(nextRunTime).Date; + var isHoliday = holidays.Where(x => today == x).Any(); + + if (isHoliday) + { + nextRunTime = nextRunTime.Value.AddDays(1); + } + } + return nextRunTime; + } + + private static List GetHolidays(int year) + { + List holidays = new List + { + GetNewYearsDay(year), + GetMemorialDay(year), + GetIndependenceDay(year), + GetLaborDay(year), + GetThanksgivingDay(year), + GetChristmasDay(year) + }; + + return holidays; + } + + /// New Years Day - Jan 1st + public static DateTime GetNewYearsDay(int year) + { + return new DateTime(year, 1, 1).Date; + } + + /// Memorial Day - last Monday in May + public static DateTime GetMemorialDay(int year) + { + DateTime memorialDay = new DateTime(year, 5, 31); + var dayOfWeek = memorialDay.DayOfWeek; + while (dayOfWeek != DayOfWeek.Monday) + { + memorialDay = memorialDay.AddDays(-1); + dayOfWeek = memorialDay.DayOfWeek; + } + return memorialDay.Date; + } + + /// Independence Day - July 4th + public static DateTime GetIndependenceDay(int year) + { + return new DateTime(year, 7, 4).Date; + } + + /// Labor Day -- 1st Monday in September + public static DateTime GetLaborDay(int year) + { + DateTime laborDay = new DateTime(year, 9, 1); + var dayOfWeek = laborDay.DayOfWeek; + while (dayOfWeek != DayOfWeek.Monday) + { + laborDay = laborDay.AddDays(1); + dayOfWeek = laborDay.DayOfWeek; + } + return laborDay.Date; + } + + /// Thanksgiving Day - 4th Thursday in November + public static DateTime GetThanksgivingDay(int year) + { + var thanksgiving = (from day in Enumerable.Range(1, 30) + where new DateTime(year, 11, day).DayOfWeek == DayOfWeek.Thursday + select day).ElementAt(3); + return new DateTime(year, 11, thanksgiving).Date; + } + + /// Christmas Day - Dec 25th + public static DateTime GetChristmasDay(int year) + { + return new DateTime(year, 12, 25).Date; + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/CleanUpJobSetting.sql b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/CleanUpJobSetting.sql new file mode 100644 index 0000000..899042e --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/CleanUpJobSetting.sql @@ -0,0 +1,5 @@ +CREATE TABLE [dbo].[CleanUpJobSetting] +( + [CleanUpJobSettingId] INT NOT NULL PRIMARY KEY IDENTITY, + [CompletionDaysPast] INT NOT NULL +) \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/CleanUpJobTag.sql b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/CleanUpJobTag.sql new file mode 100644 index 0000000..48c4f2b --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/CleanUpJobTag.sql @@ -0,0 +1,8 @@ +CREATE TABLE [dbo].[CleanUpJobTag] +( + [CleanUpJobTagId] INT NOT NULL PRIMARY KEY IDENTITY, + [CleanUpJobSettingId] INT NOT NULL, + [TagId] INT NOT NULL + CONSTRAINT [FK_CleanUpJobTag_CleanUpJobSetting] FOREIGN KEY ([CleanUpJobSettingId]) REFERENCES [CleanUpJobSetting]([CleanUpJobSettingId]) + CONSTRAINT [FK_CleanUpJobTag_Tag] FOREIGN KEY ([TagId]) REFERENCES [Tag]([TagId]) +) \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/ExecutionMessage.sql b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/ExecutionMessage.sql new file mode 100644 index 0000000..f2376a3 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/ExecutionMessage.sql @@ -0,0 +1,16 @@ +CREATE TABLE [dbo].[ExecutionMessage] +( + [ExecutionMessageId] INT NOT NULL PRIMARY KEY IDENTITY, + [WorkItemExecutionId] INT NOT NULL, + [User] varchar(200) NULL, + [MessageDate] DATETIME NULL, + CONSTRAINT [FK_ExecutionMessage_WorkItemExecution] FOREIGN KEY ([WorkItemExecutionId]) REFERENCES [WorkItemExecution]([WorkItemExecutionId]), + [Message] VARCHAR(4000) NULL +) + +GO +CREATE NONCLUSTERED INDEX [ix_WorkItemExecutionId] ON [dbo].[ExecutionMessage] +( +[WorkItemExecutionId] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) +GO \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/ExecutionMessage_History.sql b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/ExecutionMessage_History.sql new file mode 100644 index 0000000..2d2c20e --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/ExecutionMessage_History.sql @@ -0,0 +1,16 @@ +CREATE TABLE [dbo].[ExecutionMessage_History] +( + [ExecutionMessageId] INT NOT NULL, + [WorkItemExecutionId] INT NOT NULL, + [User] varchar(200) NULL, + [MessageDate] DATETIME NULL, + [Message] VARCHAR(4000) NULL +) + +GO + +CREATE NONCLUSTERED INDEX [ix_WorkItemExecutionId] ON [dbo].[ExecutionMessage_History] +( +[WorkItemExecutionId] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) +GO \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/Job.sql b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/Job.sql new file mode 100644 index 0000000..752c402 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/Job.sql @@ -0,0 +1,36 @@ +CREATE TABLE [dbo].[Job] +( + [JobId] INT NOT NULL PRIMARY KEY IDENTITY, + [RequestingApplication] VARCHAR(200) NOT NULL, + [Name] VARCHAR(200) NOT NULL, + [Description] VARCHAR(8000) NULL, + [CreatedBy] VARCHAR(200) NOT NULL, + [CreatedAt] DATETIME NOT NULL, + [ModifiedBy] VARCHAR(200) NULL, + [ModifiedDate] DATETIME NULL, + [CronSchedule] VARCHAR(50) NULL, + [NextRunTime] DATETIME NULL, + [AttemptsToExecute] INT NULL, + [SoftDeleteDate] DATETIME NULL, + [SkipHolidays] BIT NOT NULL DEFAULT 0, + [JobStatus] INT NOT NULL Default 0 +) + +GO +CREATE NONCLUSTERED INDEX [ix_SoftDeleteDate] ON [dbo].[Job] +( +[SoftDeleteDate] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) +GO +GO +CREATE NONCLUSTERED INDEX [ix_NextRunTime] ON [dbo].[Job] +( +[NextRunTime] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) +GO +GO +CREATE NONCLUSTERED INDEX [ix_JobStatus] ON [dbo].[Job] +( +[JobStatus] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) +GO \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/JobErrorRecipient.sql b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/JobErrorRecipient.sql new file mode 100644 index 0000000..36c7961 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/JobErrorRecipient.sql @@ -0,0 +1,14 @@ +CREATE TABLE [dbo].[JobErrorRecipient] +( + [JobErrorRecipientId] INT NOT NULL PRIMARY KEY IDENTITY, + [JobId] INT NOT NULL, + [Recipient] VARCHAR(200) NOT NULL, + CONSTRAINT [FK_JobErrorRecipient_Job] FOREIGN KEY ([JobId]) REFERENCES [Job]([JobId]) +) + +GO +CREATE NONCLUSTERED INDEX [ix_JobId] ON [dbo].[JobErrorRecipient] +( +[JobId] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) +GO \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/JobTag.sql b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/JobTag.sql new file mode 100644 index 0000000..362de79 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/JobTag.sql @@ -0,0 +1,21 @@ +CREATE TABLE [dbo].[JobTag] +( + [JobTagId] INT NOT NULL PRIMARY KEY IDENTITY, + [JobId] INT NOT NULL, + [TagId] INT NOT NULL, + CONSTRAINT [FK_JobTag_Job] FOREIGN KEY ([JobId]) REFERENCES [Job]([JobId]), + CONSTRAINT [FK_JobTag_Tag] FOREIGN KEY ([TagId]) REFERENCES [Tag]([TagId]) +) + +GO +CREATE NONCLUSTERED INDEX [ix_JobId] ON [dbo].[JobTag] +( +[JobId] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) +GO +GO +CREATE NONCLUSTERED INDEX [ix_TagId] ON [dbo].[JobTag] +( +[TagId] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) +GO \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/LCU.Api.Internal.DispatcherSql.refactorlog b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/LCU.Api.Internal.DispatcherSql.refactorlog new file mode 100644 index 0000000..248eedb --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/LCU.Api.Internal.DispatcherSql.refactorlog @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/LCU.Api.Internal.DispatcherSql.sqlproj b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/LCU.Api.Internal.DispatcherSql.sqlproj new file mode 100644 index 0000000..bce1423 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/LCU.Api.Internal.DispatcherSql.sqlproj @@ -0,0 +1,81 @@ + + + + Debug + AnyCPU + LCU.Api.Internal.DispatcherSql + 2.0 + 4.1 + {b82adfbd-f6bd-431a-88ce-5540190d225e} + Microsoft.Data.Tools.Schema.Sql.Sql130DatabaseSchemaProvider + Database + + + LCU.Api.Internal.DispatcherSql + LCU.Api.Internal.DispatcherSql + 1033, CI + BySchemaAndSchemaType + True + v4.7 + CS + Properties + False + True + True + + + + bin\Release\ + $(MSBuildProjectName).sql + False + pdbonly + true + false + true + prompt + 4 + + + bin\Debug\ + $(MSBuildProjectName).sql + false + true + full + false + true + true + prompt + 4 + + + 11.0 + + True + 11.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/Tag.sql b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/Tag.sql new file mode 100644 index 0000000..6bf30ac --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/Tag.sql @@ -0,0 +1,5 @@ +CREATE TABLE [dbo].[Tag] +( + [TagId] INT NOT NULL PRIMARY KEY IDENTITY, + [TagName] VARCHAR(300) NOT NULL +) diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/WorkItem.sql b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/WorkItem.sql new file mode 100644 index 0000000..4ac0d46 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/WorkItem.sql @@ -0,0 +1,23 @@ +CREATE TABLE [dbo].[WorkItem] +( + [WorkItemId] INT NOT NULL PRIMARY KEY IDENTITY, + [JobId] INT NOT NULL, + [Name] VARCHAR(200) NOT NULL, + [Description] VARCHAR(8000) NULL, + [Order] INT NOT NULL, + [SerializedPayload] VARCHAR(8000) NOT NULL, + [Url] VARCHAR(200) NULL, + [HttpMethod] INT NULL, + [ActionType] INT NULL, + [CancelJobOnFailure] BIT NOT NULL DEFAULT 0, + [CancelJobLimit] INT NOT NULL DEFAULT 0, + [CanceledJobCounter] INT NOT NULL DEFAULT 0, + CONSTRAINT [FK_WorkItem_Job] FOREIGN KEY ([JobId]) REFERENCES [Job]([JobId]) +) + +GO +CREATE NONCLUSTERED INDEX [ix_JobId] ON [dbo].[WorkItem] +( +[JobId] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) +GO \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/WorkItemExecution.sql b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/WorkItemExecution.sql new file mode 100644 index 0000000..2c6a694 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/WorkItemExecution.sql @@ -0,0 +1,16 @@ +CREATE TABLE [dbo].[WorkItemExecution] +( + [WorkItemExecutionId] INT NOT NULL PRIMARY KEY IDENTITY, + [WorkItemId] INT NOT NULL, + [Status] INT NOT NULL, + [StartedTimestamp] DATETIME NULL, + [CompletedTimestamp] DATETIME NULL, + [AttemptsToExecute] INT NULL + CONSTRAINT [FK_WorkItemExecution_WorkItem] FOREIGN KEY ([WorkItemId]) REFERENCES [WorkItem]([WorkItemId]), +) + +GO +CREATE INDEX [idx_WorkItemExecution_WorkItemId] +ON [WorkItemExecution] ([WorkItemId]) +INCLUDE ([WorkItemExecutionId], [Status], [StartedTimestamp], [CompletedTimestamp], [AttemptsToExecute]) +GO \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/WorkItemExecution_History.sql b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/WorkItemExecution_History.sql new file mode 100644 index 0000000..3fb98b4 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherSql/WorkItemExecution_History.sql @@ -0,0 +1,17 @@ +CREATE TABLE [dbo].[WorkItemExecution_History] +( + [WorkItemExecutionId] INT NOT NULL, + [WorkItemId] INT NOT NULL, + [Status] INT NOT NULL, + [StartedTimestamp] DATETIME NULL, + [CompletedTimestamp] DATETIME NULL, + [AttemptsToExecute] INT NULL +) + +GO + +CREATE NONCLUSTERED INDEX [ix_WorkItemId] ON [dbo].[WorkItemExecution_History] +( +[WorkItemId] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) +GO \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/BuilderServiceTests/ActionBuilderServiceTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/BuilderServiceTests/ActionBuilderServiceTest.cs new file mode 100644 index 0000000..ce24f86 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/BuilderServiceTests/ActionBuilderServiceTest.cs @@ -0,0 +1,529 @@ +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Api.Internal.DispatcherSharedLib.BuilderServices; +using LCU.Api.Internal.DispatcherSharedLib.Configurations; +namespace LCU.Api.Internal.DispatcherTests.ServiceTests.DispatcherOperationServices; + +public class ActionBuilderServiceTest +{ + private readonly ActionBuilderService _actionBuilderService; + private readonly FileOperationsApiConfiguration _fileOperationsApiConfiguration; + private readonly FtpApiConfiguration _ftpApiConfiguration; + private readonly NotificationApiConfiguration _notificationApiConfiguration; + private readonly ArchiverApiConfiguration _archiverApiConfiguration; + private readonly DocumentIngestorApiConfiguration _documentIngestorApiConfiguration; + private readonly DocumentTypeCleanupApiConfiguration _documentTypeCleanupApiConfiguration; + private readonly HistoricalDocumentIngestorApiConfiguration _historicalDocumentIngestorApiConfiguration; + private readonly RemoteDepositCaptureApiConfiguration _remoteDepositCaptureApiConfiguration; + private readonly DataCleanUpApiConfiguration _dataCleanUpApiConfiguration; + private readonly NoticeProcessorApiConfiguration _noticeProcessorApiConfiguration; + private readonly MomBotApiConfiguration _momBotApiConfiguration; + private readonly BlendApiConfiguration _blendApiConfiguration; + private readonly DialerApiConfiguration _dialerApiConfiguration; + private readonly IncentiveManagerConfiguration _incentiveManagerConfiguration; + private readonly FileUpdaterApiConfiguration _fileUpdaterApiConfiguration; + private readonly TemenosNotificationApiConfiguration _temenosNotificationApiConfiguration; + private readonly RealTimeAlertsApiConfiguration _realTimeAlertsApiConfiguration; + private readonly RepoApiConfiguration _repoApiConfiguration; + + public ActionBuilderServiceTest() + { + _momBotApiConfiguration = new MomBotApiConfiguration + { + Url = "https://Test", + QueueMomBotEndpoint = "/QueueMomBotEndpoint" + }; + + _fileOperationsApiConfiguration = new FileOperationsApiConfiguration + { + Url = "https://Test", + MoveFileEndpoint = "/MoveFile", + CopyFileEndpoint = "/CopyFile", + ModifyFileEndpoint = "/ModifyFile", + RenameFileEndpoint = "/RenameFile", + DeleteFileEndpoint = "/DeleteFile", + ZipDirectoryEndpoint = "/ZipDirectory", + CreateDirectoryEndpoint = "/CreateDirectory", + RenameDirectoryEndpoint = "/RenameDirectory", + UnzipFileEndpoint = "/UnzipFile", + EncryptFileDataEndpoint = "/EncryptFileData", + DecryptFileDataEndpoint = "/DecryptFileData", + UnzipPasswordProtectedFileEndpoint = "/UnzipPasswordProtectedFile", + DeleteDirectoryEndpoint = "/DeleteDirectory", + FileValidationEndpoint = "/ValidateFile", + ExtractIndexFileDataEndpoint = "/ExtractIndexFileData", + DeleteEmptyDirectoryEndpoint = "/DeleteEmptyDirectory" + }; + + _ftpApiConfiguration = new FtpApiConfiguration + { + Url = "https://Test", + SftpFileEndpoint = "/SftpFileEndpoint", + QueueSftpDynamicFileEndpoint = "/QueueSftpDynamicFileEndpoint", + QueueSftpUploadFileEndpoint = "/QueueSftpUploadFileEndpoint", + QueueSftpDownloadFileEndpoint = "/QueueSftpDownloadFileEndpoint" + }; + + _notificationApiConfiguration = new NotificationApiConfiguration + { + Url = "https://Test", + EmailEndpoint = "/EmailEndpoint", + }; + + _archiverApiConfiguration = new ArchiverApiConfiguration + { + Url = "https://Test", + QueueArchiverEndpoint = "/QueueArchiverEndpoint" + }; + + _documentIngestorApiConfiguration = new DocumentIngestorApiConfiguration + { + Url = "https://Test", + QueueDocumentIngestorEndpoint = "/QueueDocumentIngestorEndpoint", + QueueDocumentBatchIngestionEndpoint = "/QueueDocumentBatchIngestionEndpoint", + QueueIngestorPdfPartitionerEndpoint = "/QueueIngestorPdfPartitionerEndpoint", + QueueMemberDocumentNotifierEndpoint = "/QueueMemberDocumentNotifierEndpoint", + QueueIngestionFolderCleanUpEndpoint = "/QueueIngestionFolderCleanUpEndpoint" + }; + + _documentTypeCleanupApiConfiguration = new DocumentTypeCleanupApiConfiguration + { + Url = "https://Test", + QueueDocumentRetentionCleanupEndpoint = "/QueueDocumentRetentionCleanupEndpoint" + }; + + _historicalDocumentIngestorApiConfiguration = new HistoricalDocumentIngestorApiConfiguration + { + Url = "https://Test/Historical", + QueueDocumentIngestorEndpoint = "/QueueDocumentIngestorEndpoint", + QueueDocumentBatchIngestionEndpoint = "/QueueDocumentBatchIngestionEndpoint", + QueueIngestorPdfPartitionerEndpoint = "/QueueIngestorPdfPartitionerEndpoint", + }; + + _remoteDepositCaptureApiConfiguration = new RemoteDepositCaptureApiConfiguration + { + Url = "https://Test", + QueueRemoteDepositCaptureXmlEndpoint = "/QueueRemoteDepositCaptureXmlEndpoint" + }; + + _dataCleanUpApiConfiguration = new DataCleanUpApiConfiguration + { + Url = "https://Test", + QueueDataCleanUpEndpoint = "/QueueDataCleanUpEndpoint", + QueueUpdatePersonForCheckHoldsEndpoint = "/QueueUpdatePersonForCheckHoldsEndpoint", + QueueNcoaUpdateEndpoint = "/QueueNcoaUpdateRequestEndpoint", + QueueIndirectFundingEndpoint = "/QueueIndirectFundingEndpoint" + }; + + _noticeProcessorApiConfiguration = new NoticeProcessorApiConfiguration + { + Url = "https://Test", + QueueExtractEndpoint = "/QueueExtractEndpoint", + QueueProcessingEmailSummaryEndpoint = "/QueueProcessingEmailSummaryEndpoint" + }; + + _blendApiConfiguration = new BlendApiConfiguration + { + Url = "https://Test", + QueueDataImportEndpoint = "/QueueDataImportEndpoint" + }; + + _dialerApiConfiguration = new DialerApiConfiguration + { + Url = "https://Test", + QueueDialerUploadEndpoint = "/QueueDialerUploadEndpoint", + QueueUploadDoNotCallEndpoint = "/QueueUploadDoNotCallEndpoint", + QueueCreateVoAppsCampaignEndpoint = "/QueueCreateVoAppsCampaignEndpoint", + QueueUploadVoAppsCampaignResultsEndpoint = "/QueueUploadVoAppsCampaignResultsEndpoint" + }; + + _incentiveManagerConfiguration = new IncentiveManagerConfiguration + { + Url = "https://Test", + QueueCreditCardImportEndpoint = "/QueueCreditCardImportEndpoint", + QueueVehicleImportEndpoint = "/QueueVehicleImportEndpoint" + }; + + _fileUpdaterApiConfiguration = new FileUpdaterApiConfiguration + { + Url = "https://Test", + QueueIndexFieldMapper = "/QueueIndexFieldMapper" + }; + + _temenosNotificationApiConfiguration = new TemenosNotificationApiConfiguration + { + Url = "https://Test", + QueueDealerAchNotification = "/QueueDealerAchNotification" + }; + + _realTimeAlertsApiConfiguration = new RealTimeAlertsApiConfiguration + { + Url = "https://Test", + QueueInitiateRealTimeAlertsEndpoint = "/QueueInitiateRealTimeAlertsEndpoint", + QueueErroredTransactionCleanUpEndpoint = "/QueueErroredTransactionCleanUpEndpoint" + }; + + _repoApiConfiguration = new RepoApiConfiguration + { + Url = "https://Test", + AccountBatchEndpoint = "/api/RepoBatchProcessing/QueueRepoBatchRequest" + }; + + + _actionBuilderService = new ActionBuilderService(_fileOperationsApiConfiguration, + _ftpApiConfiguration, + _notificationApiConfiguration, + _archiverApiConfiguration, + _documentIngestorApiConfiguration, + _documentTypeCleanupApiConfiguration, + _historicalDocumentIngestorApiConfiguration, + _remoteDepositCaptureApiConfiguration, + _dataCleanUpApiConfiguration, + _noticeProcessorApiConfiguration, + _momBotApiConfiguration, + _blendApiConfiguration, + _dialerApiConfiguration, + _incentiveManagerConfiguration, + _fileUpdaterApiConfiguration, + _temenosNotificationApiConfiguration, + _realTimeAlertsApiConfiguration, + _repoApiConfiguration); + } + + internal void BuildActionService_Test(ActionType actionType, string url) + { + var actionService = _actionBuilderService.BuildAction(actionType); + var type = actionService.GetType(); + var workItem = new WorkItem + { + ActionType = actionType + }; + workItem = actionService.AddUrlAndHttpMethod(workItem); + + Assert.Equal(url, workItem.Url); + Assert.Equal(actionType, workItem.ActionType); + } + + #region FileOperationsApi + [Fact] + public void BuildActionService_MoveFile() + { + BuildActionService_Test(ActionType.MoveFile, "https://Test/MoveFile"); + } + + [Fact] + public void BuildActionService_CopyFile() + { + BuildActionService_Test(ActionType.CopyFile, "https://Test/CopyFile"); + } + + [Fact] + public void BuildActionService_ModifyFile() + { + BuildActionService_Test(ActionType.ModifyFile, "https://Test/ModifyFile"); + } + + [Fact] + public void BuildActionService_RenameFile() + { + BuildActionService_Test(ActionType.RenameFile, "https://Test/RenameFile"); + } + + [Fact] + public void BuildActionService_DeleteFile() + { + BuildActionService_Test(ActionType.DeleteFile, "https://Test/DeleteFile"); + } + + [Fact] + public void BuildActionService_ZipDirectory() + { + BuildActionService_Test(ActionType.ZipDirectory, "https://Test/ZipDirectory"); + } + + [Fact] + public void BuildActionService_CreateDirectory() + { + BuildActionService_Test(ActionType.CreateDirectory, "https://Test/CreateDirectory"); + } + + [Fact] + public void BuildActionService_RenameDirectory() + { + BuildActionService_Test(ActionType.RenameDirectory, "https://Test/RenameDirectory"); + } + + [Fact] + public void BuildActionService_UnzipFile() + { + BuildActionService_Test(ActionType.UnzipFile, "https://Test/UnzipFile"); + } + + [Fact] + public void BuildActionService_EncryptFileData() + { + BuildActionService_Test(ActionType.EncryptFileData, "https://Test/EncryptFileData"); + } + + [Fact] + public void BuildActionService_DecryptFileData() + { + BuildActionService_Test(ActionType.DecryptFileData, "https://Test/DecryptFileData"); + } + + [Fact] + public void BuildActionService_UnzipPasswordProtectedFile() + { + BuildActionService_Test(ActionType.UnzipPasswordProtectedFile, "https://Test/UnzipPasswordProtectedFile"); + } + + [Fact] + public void BuildActionService_DeleteDirectory() + { + BuildActionService_Test(ActionType.DeleteDirectory, "https://Test/DeleteDirectory"); + } + + [Fact] + public void BuildActionService_ExtractIndexFileData() + { + BuildActionService_Test(ActionType.ExtractIndexFileData, "https://Test/ExtractIndexFileData"); + } + + [Fact] + public void BuildActionService_ValidateFile() + { + BuildActionService_Test(ActionType.ValidateFileRequest, "https://Test/ValidateFile"); + } + + [Fact] + public void BuildActionService_DeleteEmptyDirectory() + { + BuildActionService_Test(ActionType.DeleteEmptyDirectoryRequest, "https://Test/DeleteEmptyDirectory"); + } + + #endregion FileOperationsApi + + #region FtpApi + [Fact] + public void BuildActionService_SftpUploadFile() + { + BuildActionService_Test(ActionType.SftpUploadFile, "https://Test/QueueSftpUploadFileEndpoint"); + } + + [Fact] + public void BuildActionService_SftpDownloadFile() + { + BuildActionService_Test(ActionType.SftpDownloadFile, "https://Test/QueueSftpDownloadFileEndpoint"); + } + #endregion FtpApi + + #region NotificationsApi + [Fact] + public void BuildActionService_Email() + { + BuildActionService_Test(ActionType.Email, "https://Test/EmailEndpoint"); + } + #endregion NotificationsApi + + #region ArchiverApi + [Fact] + public void BuildActionService_CoreArchiveRequest() + { + BuildActionService_Test(ActionType.CoreArchiveRequest, "https://Test/QueueArchiverEndpoint"); + } + #endregion ArchiverApi + + #region DocumentIngestorApi + [Fact] + public void BuildActionService_DocumentIngestor() + { + BuildActionService_Test(ActionType.DocumentIngestor, "https://Test/QueueDocumentIngestorEndpoint"); + } + + [Fact] + public void BuildActionService_DocumentBatchIngestion() + { + BuildActionService_Test(ActionType.DocumentBatchIngestion, "https://Test/QueueDocumentBatchIngestionEndpoint"); + } + + [Fact] + public void BuildActionService_IngestorPdfPartitioner() + { + BuildActionService_Test(ActionType.IngestorPdfPartitioner, "https://Test/QueueIngestorPdfPartitionerEndpoint"); + } + + [Fact] + public void BuildActionService_MemberDocumentNotification() + { + BuildActionService_Test(ActionType.MemberDocumentNotification, "https://Test/QueueMemberDocumentNotifierEndpoint"); + } + [Fact] + public void BuildActionService_IngestionFolderCleanUp() + { + BuildActionService_Test(ActionType.IngestionFolderCleanUp, "https://Test/QueueIngestionFolderCleanUpEndpoint"); + } + #endregion DocumentIngestorApi + + #region DocumentTypeCleanupApi + [Fact] + public void BuildActionService_DocumentTypeCleanup() + { + BuildActionService_Test(ActionType.DocumentRetentionCleanupRequest, "https://Test/QueueDocumentRetentionCleanupEndpoint"); + } + #endregion DocumentTypeCleanupApi + + #region HistoricalDocumentIngestorApi + [Fact] + public void BuildActionService_HistoricalDocumentIngestor() + { + BuildActionService_Test(ActionType.HistoricalDocumentIngestor, "https://Test/Historical/QueueDocumentIngestorEndpoint"); + } + + [Fact] + public void BuildActionService_HistoricalDocumentBatchIngestion() + { + BuildActionService_Test(ActionType.HistoricalDocumentBatchIngestion, "https://Test/Historical/QueueDocumentBatchIngestionEndpoint"); + } + + [Fact] + public void BuildActionService_HistoricalIngestorPdfPartitioner() + { + BuildActionService_Test(ActionType.HistoricalIngestorPdfPartitioner, "https://Test/Historical/QueueIngestorPdfPartitionerEndpoint"); + } + #endregion HistoricalDocumentIngestorApi + + #region RemoteDepositCaptureApi + [Fact] + public void BuildActionService_RemoteDepositCaptureXmlFile() + { + BuildActionService_Test(ActionType.RemoteDepositCaptureXmlFile, "https://Test/QueueRemoteDepositCaptureXmlEndpoint"); + } + #endregion RemoteDepositCaptureApi + + #region DataCleanUpApi + [Fact] + public void BuildActionService_DataCleanUp() + { + BuildActionService_Test(ActionType.DataCleanUp, "https://Test/QueueDataCleanUpEndpoint"); + } + + [Fact] + public void BuildActionService_AtmPersonCheckHolds() + { + BuildActionService_Test(ActionType.AtmPersonCheckHolds, "https://Test/QueueUpdatePersonForCheckHoldsEndpoint"); + } + + [Fact] + public void BuildActionService_NcoaUpdateRequest() + { + BuildActionService_Test(ActionType.DataCleanupNcoaUpdateRequest, "https://Test/QueueNcoaUpdateRequestEndpoint"); + } + + [Fact] + public void BuildActionService_IndirectFundingRequest() + { + BuildActionService_Test(ActionType.DataCleanUpIndirectFundingRequest, "https://Test/QueueIndirectFundingEndpoint"); + } + #endregion DataCleanUpApi + + #region NoticeProcessorApi + [Fact] + public void BuildActionService_NoticeProcessorExtractRequest() + { + BuildActionService_Test(ActionType.NoticeProcessorExtractRequest, "https://Test/QueueExtractEndpoint"); + } + + [Fact] + public void BuildActionService_NoticeProcessorSummaryEmailRequest() + { + BuildActionService_Test(ActionType.NoticeProcessorSummaryEmailRequest, "https://Test/QueueProcessingEmailSummaryEndpoint"); + } + #endregion NoticeProcessorApi + + #region MomBotApi + [Fact] + public void BuildActionService_MomBotRequest() + { + BuildActionService_Test(ActionType.MomBotRequest, "https://Test/QueueMomBotEndpoint"); + } + #endregion MomBotApi + + #region BlendApi + [Fact] + public void BuildActionService_BlendApiDataSyncRequest() + { + BuildActionService_Test(ActionType.BlendDataRequest, "https://Test/QueueDataImportEndpoint"); + } + #endregion BlendApi + + #region DialerApi + [Fact] + public void BuildActionService_DialerUploadRequest() + { + BuildActionService_Test(ActionType.DialerUploadRequest, "https://Test/QueueDialerUploadEndpoint"); + } + + [Fact] + public void BuildActionService_UploadDoNotCallRequest() + { + BuildActionService_Test(ActionType.UploadDoNotCallRequest, "https://Test/QueueUploadDoNotCallEndpoint"); + } + + [Fact] + public void BuildActionService_CreateVoAppsCampaignsRequest() + { + BuildActionService_Test(ActionType.CreateVoAppsCampaignsRequest, "https://Test/QueueCreateVoAppsCampaignEndpoint"); + } + #endregion DialerApi + + #region IncentiveApi + [Fact] + public void BuildActionService_ImportCreditCardSalesRequest() + { + BuildActionService_Test(ActionType.ImportCreditCardSalesRequest, "https://Test/QueueCreditCardImportEndpoint"); + } + + [Fact] + public void BuildActionService_ImportIndirectVehicleSalesRequest() + { + BuildActionService_Test(ActionType.ImportIndirectVehicleSalesRequest, "https://Test/QueueVehicleImportEndpoint"); + } + #endregion + + #region FileUpdaterApi + [Fact] + public void BuildActionService_IndexFieldMapperRequest() + { + BuildActionService_Test(ActionType.IndexFieldMapperRequest, "https://Test/QueueIndexFieldMapper"); + } + #endregion FileUpdaterApi + + #region TemenosNotificationApi + [Fact] + public void BuildActionService_DealerAchNotificationRequest() + { + BuildActionService_Test(ActionType.DealerAchNotificationRequest, "https://Test/QueueDealerAchNotification"); + } + #endregion TemenosNotificationApi + + #region RealTimeAlertsApi + [Fact] + public void BuildActionService_InitiateRealTimeAlertsRequest() + { + BuildActionService_Test(ActionType.InitiateRealTimeAlertsRequest, "https://Test/QueueInitiateRealTimeAlertsEndpoint"); + } + + [Fact] + public void BuildActionService_InitiateRealTimeAlertsErroredProcessingRequest() + { + BuildActionService_Test(ActionType.InitiateRealTimeAlertsErroredProcessingRequest, "https://Test/QueueErroredTransactionCleanUpEndpoint"); + } + #endregion RealTimeAlertsApi + + #region RepoApi + [Fact] + public void BuildActionService_RepoBatchRequest() + { + BuildActionService_Test(ActionType.RepoBatchProcessRequest, "https://Test/api/RepoBatchProcessing/QueueRepoBatchRequest"); + } + #endregion RepoApi +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ControllerTests/IngestionControllerTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ControllerTests/IngestionControllerTest.cs new file mode 100644 index 0000000..d51ca20 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ControllerTests/IngestionControllerTest.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using LCU.Api.Internal.Dispatcher.Controllers; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +namespace LCU.Api.Internal.DispatcherTests.ControllerTests; + +public class IngestionControllerTest +{ + private readonly IngestionController controller; + private readonly Mock _mockIngestionService; + + public IngestionControllerTest() + { + _mockIngestionService = new Mock(); + controller = new IngestionController(_mockIngestionService.Object); + } + + // Post tests + [Fact] + public async Task MoveFilePost_OK() + { + // arrange + var testTask = new WorkItem() + { + WorkItemId = 0, + JobId = 0, + Order = 1, + SerializedPayload = "", + Url = "", + HttpMethod = HttpMethodType.Post + }; + + var testJob = new Job() + { + JobId = 0, + RequestingApplication = "", + WorkItems = new List() { testTask } + }; + + // act + var result = await controller.Post(testJob); + Assert.NotNull(result); + var okResult = result as OkResult; + + // assert + Assert.NotNull(okResult); + Assert.Equal(200, okResult.StatusCode); + } + + // Post tests + [Fact] + public async Task MoveFilePost_NoWorkItems_BadRequest() + { + // arrange + var testJob = new Job() + { + JobId = 0, + RequestingApplication = "", + WorkItems = new List() + }; + + // act + var result = await controller.Post(testJob); + + // assert + Assert.NotNull(result); + var badRequest = result as BadRequestObjectResult; + Assert.Equal(400, badRequest.StatusCode); + Assert.Equal("Workitems are required.", badRequest.Value); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ControllerTests/JobControllerTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ControllerTests/JobControllerTest.cs new file mode 100644 index 0000000..01b9153 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ControllerTests/JobControllerTest.cs @@ -0,0 +1,132 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using LCU.Api.Internal.Dispatcher.Controllers; +using LCU.Api.Internal.Dispatcher.Services; +using LCU.Library.Internal.DispatcherModels.Models.Results; +using LCU.Library.Internal.DispatcherModels.Models.Requests; + +namespace LCU.Api.Internal.DispatcherTests.ControllerTests; + +public class JobControllerTest +{ + private readonly JobController controller; + private readonly Mock _mockJobService; + + public JobControllerTest() + { + _mockJobService = new Mock(); + controller = new JobController(_mockJobService.Object); + } + + [Fact] + public async void PagedFilter_Ok() + { + + // Arrange + var filter = new Filter() + { + Limit = 2, + Page = 1 + }; + + _mockJobService.Setup(service => service.GetFilteredJobs(filter)) + .ReturnsAsync(new PagedResult() + { + Total = 4, + Items = new List() + { + new Job() {JobId = 1}, + new Job() {JobId = 2}, + new Job() {JobId = 3}, + new Job() {JobId = 4} + } + }); + + // Act + var result = await controller.GetFilteredJobs(filter); + + // Assert + Assert.NotNull(result); + + var okObject = result as OkObjectResult; + Assert.NotNull(okObject); + + var pagedResponse = okObject.Value as PagedResponse; + Assert.NotNull(pagedResponse); + + Assert.Equal(4, pagedResponse.TotalItems); + + Assert.Equal(1, pagedResponse.Items.ElementAt(0).JobId); + Assert.Equal(2, pagedResponse.Items.ElementAt(1).JobId); + + Assert.Equal(0, pagedResponse.PreviousPage); + Assert.Equal(2, pagedResponse.NextPage); + Assert.Equal(2, pagedResponse.TotalPages); + } + + [Fact] + public async void Copy_OK() + { + var jobId = DateTime.Now.Millisecond; + var request = new CopyJobRequest + { + JobId = jobId, + ChangeEnvironment = false + }; + var response = new Job + { + JobId = DateTime.Now.Millisecond + 1 + }; + + _mockJobService.Setup(x => x.CopyJobAsync(request)) + .ReturnsAsync(response).Verifiable(); + + var result = await controller.Copy(request); + Assert.NotNull(result); + + Assert.IsType(result); + var status = result as OkObjectResult; + Assert.NotNull(status); + Assert.Equal(200, status.StatusCode); + + var jobResponse = status.Value as Job; + Assert.NotNull(jobResponse); + Assert.NotEqual(request.JobId, jobResponse.JobId); + Assert.NotEqual(0, jobResponse.JobId); + _mockJobService.Verify(); + } + + [Fact] + public async void Copy_BadRequest() + { + CopyJobRequest request = null; + + var result = await controller.Copy(request); + Assert.NotNull(result); + + Assert.IsType(result); + var status = result as BadRequestObjectResult; + Assert.NotNull(status); + Assert.Equal(400, status.StatusCode); + } + + [Fact] + public async void Copy_InternalServerError() + { + var request = new CopyJobRequest + { + JobId = DateTime.Now.Millisecond + }; + _mockJobService.Setup(x => x.CopyJobAsync(request)) + .Throws(new Exception("Test")).Verifiable(); + + var result = await controller.Copy(request); + Assert.NotNull(result); + + Assert.IsType(result); + var status = result as StatusCodeResult; + Assert.NotNull(status); + Assert.Equal(500, status.StatusCode); + _mockJobService.Verify(); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/HelperTests/CronHelpersTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/HelperTests/CronHelpersTest.cs new file mode 100644 index 0000000..65919b5 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/HelperTests/CronHelpersTest.cs @@ -0,0 +1,125 @@ +using LCU.Api.Internal.DispatcherSharedLib.Utilities; +using NCrontab; + +namespace LCU.Api.Common.NotificationsTests.HelperTests; + +public class CronHelpersTest +{ + public CronHelpersTest() + { + + } + + [Fact] + public void SetCronSchedule_Ok() + { + var job = new Job() + { + CronSchedule = "* * * * 1-5" + }; + + var DefaultCleanUpJobCronPattern = "* * * * 1-7"; + + CronHelpers.SetCronSchedule(job.CronSchedule, DefaultCleanUpJobCronPattern); + + // Assert + Assert.NotNull(job.CronSchedule); + Assert.Equal("* * * * 1-5", job.CronSchedule); + } + + [Fact] + public void SetCronSchedule_Error() + { + var job = new Job() + { + CronSchedule = "DDDDDDD" + }; + + var DefaultCleanUpJobCronPattern = "DDDDDDD"; + Assert.Throws(() => CronHelpers.SetCronSchedule(job.CronSchedule, DefaultCleanUpJobCronPattern)); + } + + [Fact] + public void SetCronSchedule_DefaultSet() + { + var job = new Job() + { + CronSchedule = "FFFFFFF" + }; + + var DefaultCleanUpJobCronPattern = "0,15,30,45 7-17 * * 1-5"; + var result = CronHelpers.SetCronSchedule(job.CronSchedule, DefaultCleanUpJobCronPattern); + + Assert.NotNull(result); + Assert.Equal(DefaultCleanUpJobCronPattern, result.ToString()); + } + + [Fact] + public void SetNextScheduledRunTime_Ok() + { + var job = new Job() + { + CronSchedule = "0 0 1-30 1 *" + }; + + CrontabSchedule.Parse(job.CronSchedule); + CronHelpers.SetNextScheduledRunTime(ref job); + + // Assert + Assert.NotNull(job.NextRunTime); + } + + [Fact] + public void SetNextScheduledRunTimeNull_Ok() + { + var job = new Job() + { + CronSchedule = "0 0 1-30 1 *" + }; + + var DefaultCleanUpJobCronPattern = "* 22 * * *"; + CronHelpers.SetCronSchedule(job.CronSchedule, DefaultCleanUpJobCronPattern); + + // Assert + Assert.Null(job.NextRunTime); + } + + [Fact(Skip="Not Holiday")] + public void SetNextScheduledRunTime_NoSKipHoliday() + { + var job = new Job() + { + CronSchedule = "0 0 1-30 1 *" + }; + + CrontabSchedule schedule = CrontabSchedule.Parse(job.CronSchedule); + var nextRunTime = schedule.GetNextOccurrence(DateTime.Now); + var nextRunTimeAfterHoliday = HolidayHelpers.HandleHolidays(nextRunTime); + + CronHelpers.SetNextScheduledRunTime(ref job); + + // Assert + Assert.NotNull(job.NextRunTime); + Assert.NotEqual(nextRunTimeAfterHoliday, job.NextRunTime); + } + + [Fact] + public void SetNextScheduledRunTime_SKipHoliday() + { + var job = new Job() + { + CronSchedule = "0 0 1-30 1 *", + SkipHolidays = true + }; + + CrontabSchedule schedule = CrontabSchedule.Parse(job.CronSchedule); + var nextRunTime = schedule.GetNextOccurrence(DateTime.Now); + var nextRunTimeAfterHoliday = HolidayHelpers.HandleHolidays(nextRunTime); + + CronHelpers.SetNextScheduledRunTime(ref job); + + // Assert + Assert.NotNull(job.NextRunTime); + Assert.Equal(nextRunTimeAfterHoliday, job.NextRunTime); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/HelperTests/HolidayHelpersTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/HelperTests/HolidayHelpersTest.cs new file mode 100644 index 0000000..d10fe0f --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/HelperTests/HolidayHelpersTest.cs @@ -0,0 +1,218 @@ +using LCU.Api.Internal.DispatcherSharedLib.Utilities; +using NCrontab; +using System; + +namespace LCU.Api.Common.NotificationsTests.HelperTests; + +public class HolidayHelpersTest +{ + + public HolidayHelpersTest() + { + + } + + [Fact(Skip = "Not Holiday")] + public void HandleHolidaysSkipHolidayFalse_Ok() + { + var job = new Job() + { + CronSchedule = "0 0 1-30 1 *" + }; + + CrontabSchedule schedule = CrontabSchedule.Parse(job.CronSchedule); + var nextRunTime = schedule.GetNextOccurrence(DateTime.Now); + var nextRunTimeAfterHoliday = HolidayHelpers.HandleHolidays(nextRunTime); + + CronHelpers.SetNextScheduledRunTime(ref job); + + // Assert + Assert.NotNull(job); + Assert.NotEqual(nextRunTimeAfterHoliday, job.NextRunTime); + } + + [Fact] + public void HandleHolidaysSkipHolidayTrue_Ok() + { + var job = new Job() + { + CronSchedule = "0 0 1-30 1 *", + SkipHolidays = true + }; + + CrontabSchedule schedule = CrontabSchedule.Parse(job.CronSchedule); + var nextRunTime = schedule.GetNextOccurrence(DateTime.Now); + var nextRunTimeAfterHoliday = HolidayHelpers.HandleHolidays(nextRunTime); + + CronHelpers.SetNextScheduledRunTime(ref job); + + // Assert + Assert.NotNull(job); + Assert.Equal(nextRunTimeAfterHoliday, job.NextRunTime); + } + + [Fact] + public void HandleHolidaysSkipIndependenceDayTrue_Ok() + { + // Arrange + var job = new Job() + { + CronSchedule = "0 0 4-31 7 *", + SkipHolidays = true + }; + + CrontabSchedule schedule = CrontabSchedule.Parse(job.CronSchedule); + var nextRunTime = schedule.GetNextOccurrence(DateTime.Now); + + CronHelpers.SetNextScheduledRunTime(ref job); + var nextRunTimeAfterHoliday = HolidayHelpers.HandleHolidays(nextRunTime); + + // Assert + Assert.NotNull(job); + Assert.Equal(nextRunTimeAfterHoliday, job.NextRunTime); + } + + [Fact] + public void HandleHolidaysGetChristmas_Ok() + { + // Arrange + var job = new Job() + { + CronSchedule = "0 0 1-30 12 *" + }; + + CrontabSchedule schedule = CrontabSchedule.Parse(job.CronSchedule); + var nextRunTime = schedule.GetNextOccurrence(DateTime.Now); + var result = HolidayHelpers.GetChristmasDay(nextRunTime.Year); + + DateTime holidayValue = new DateTime(DateTime.Now.Year, 12, 25); + + // Assert + Assert.NotNull(result.ToString()); + Assert.Equal(holidayValue.ToString(), result.ToString()); + } + + [Fact] + public void HandleHolidaysGetNewYearsDay_Ok() + { + // Arrange + var job = new Job() + { + CronSchedule = "0 0 1-30 12 *" + }; + + CrontabSchedule schedule = CrontabSchedule.Parse(job.CronSchedule); + var nextRunTime = schedule.GetNextOccurrence(DateTime.Now); + var result = HolidayHelpers.GetNewYearsDay(nextRunTime.Year); + + DateTime holidayValue = new DateTime(DateTime.Now.Year, 1, 1); + + // Assert + Assert.NotNull(result.ToString()); + Assert.Equal(holidayValue.ToString(), result.ToString()); + } + + [Fact] + public void HandleHolidaysGetIndependenceDay_Ok() + { + // Arrange + var job = new Job() + { + CronSchedule = "0 0 1-30 12 *", + SkipHolidays = true + }; + + CrontabSchedule schedule = CrontabSchedule.Parse(job.CronSchedule); + var nextRunTime = schedule.GetNextOccurrence(DateTime.Now); + var result = HolidayHelpers.GetIndependenceDay(nextRunTime.Year); + + DateTime holidayValue = new DateTime(DateTime.Now.Year, 7, 4); + + // Assert + Assert.NotNull(result.ToString()); + Assert.Equal(holidayValue.ToString(), result.ToString()); + } + + [Fact] + public void HandleHolidaysGetMemorialDay_Ok() + { + // Arrange + var job = new Job() + { + CronSchedule = "0 0 1-31 5 *", + SkipHolidays = true + }; + + CrontabSchedule schedule = CrontabSchedule.Parse(job.CronSchedule); + var nextRunTimeYear = schedule.GetNextOccurrence(DateTime.Now); + + CronHelpers.SetNextScheduledRunTime(ref job); + var result = HolidayHelpers.GetMemorialDay(nextRunTimeYear.Year); + var nextRunTimeAfterHoliday = HolidayHelpers.HandleHolidays(result); + + // Assert + Assert.NotNull(result.ToString()); + Assert.Equal(nextRunTimeAfterHoliday.ToString(), result.AddDays(1).ToString()); + } + + [Fact] + public void HandleHolidaysGetThanksgivingDay_Ok() + { + // Arrange + var job = new Job() + { + CronSchedule = "0 0 1-30 11 *", + SkipHolidays = true + }; + + CrontabSchedule schedule = CrontabSchedule.Parse(job.CronSchedule); + var nextRunTimeYear = schedule.GetNextOccurrence(DateTime.Now); + + CronHelpers.SetNextScheduledRunTime(ref job); + var result = HolidayHelpers.GetThanksgivingDay(nextRunTimeYear.Year); + var nextRunTimeAfterHoliday = HolidayHelpers.HandleHolidays(result); + + // Assert + Assert.NotNull(result.ToString()); + Assert.Equal(nextRunTimeAfterHoliday.ToString(), result.AddDays(1).ToString()); + } + + [Fact] + public void HandleHolidaysGetLaborDay_Ok() + { + // Arrange + var job = new Job() + { + CronSchedule = "0 0 1-30 9 *", + SkipHolidays = true + }; + + CrontabSchedule schedule = CrontabSchedule.Parse(job.CronSchedule); + var nextRunTimeYear = schedule.GetNextOccurrence(DateTime.Now); + CronHelpers.SetNextScheduledRunTime(ref job); + var result = HolidayHelpers.GetLaborDay(nextRunTimeYear.Year); + var nextRunTimeAfterHoliday = HolidayHelpers.HandleHolidays(result); + + // Assert + Assert.NotNull(result.ToString()); + Assert.Equal(nextRunTimeAfterHoliday.ToString(), result.AddDays(1).ToString()); + } + + [Fact] + public void HandleHolidaysNull_Ok() + { + // Arrange + DateTime dateTest = new DateTime(); //min date is 01-01-0001 + var nextRunTime = HolidayHelpers.GetLaborDay(dateTest.Year); + + // Assert + Assert.NotNull(nextRunTime.ToString()); + } + + [Fact] + public void HandleHolidaysGetSpecificHolidayNoYear_Error() + { + // Assert + Assert.Throws(() => HolidayHelpers.GetLaborDay(0)); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/LCU.Api.Internal.DispatcherTests.csproj b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/LCU.Api.Internal.DispatcherTests.csproj new file mode 100644 index 0000000..ffc0d19 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/LCU.Api.Internal.DispatcherTests.csproj @@ -0,0 +1,33 @@ + + + + net8.0 + + false + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/RepositoryTests/DbContextFromJson.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/RepositoryTests/DbContextFromJson.cs new file mode 100644 index 0000000..4e8e49e --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/RepositoryTests/DbContextFromJson.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.IO; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json.Linq; +using LCU.Api.Internal.DispatcherLib.Persistence; +namespace LCU.Api.Internal.DispatcherTests.RepositoryTests; + +public class DbContextTestSet +{ + public IReadOnlyCollection Jobs { get; set; } + public IReadOnlyCollection WorkItems { get; set; } +} + +public abstract class DbContextFromJsonTest +{ + public DbContextTestSet GetTestAccounts(string filePath) + { + var testSet = new DbContextTestSet(); + + // Get the absolute path to the JSON file + var path = Path.IsPathRooted(filePath) + ? filePath + : Path.GetRelativePath(Directory.GetCurrentDirectory(), filePath); + + if (!File.Exists(path)) + { + throw new ArgumentException($"Could not find file at path: {path}"); + } + + var fileData = File.ReadAllText(filePath); + var allData = JObject.Parse(fileData); + + var data = allData["jobs"]; + testSet.Jobs = data?.ToObject>(); + + var events = allData["workitems"]; + testSet.WorkItems = events?.ToObject>(); + + return testSet; + } + + public AppDbContext GetBlankContext() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + return new AppDbContext(options); + } + + public AppDbContext GetContextWithData(string jsonFileName) + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + var context = new AppDbContext(options); + + var testData = GetTestAccounts(jsonFileName); + AddTo(context.Jobs, testData.Jobs); + AddTo(context.WorkItems, testData.WorkItems); + + context.SaveChanges(); + context.ChangeTracker.Clear(); + + return context; + } + + private static void AddTo(DbSet collection, IEnumerable range) where T : class + { + if (range != null) collection.AddRange(range); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/RepositoryTests/JobRepositoryTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/RepositoryTests/JobRepositoryTest.cs new file mode 100644 index 0000000..92dc639 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/RepositoryTests/JobRepositoryTest.cs @@ -0,0 +1,395 @@ +using System.Collections.Generic; +using LCU.Api.Internal.DispatcherLib.Repositories; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.DispatcherModels.Constants; +namespace LCU.Api.Internal.DispatcherTests.RepositoryTests; + +public class JobRepositoryTest : DbContextFromJsonTest +{ + private readonly JobRepository _jobRepository; + + public JobRepositoryTest() + { + var dbContext = GetContextWithData("RepositoryTests\\job_repo_basic.json"); + _jobRepository = new JobRepository(dbContext); + } + + [Fact] + public async void FilteredListAsync_DefaultFilter() + { + // Act + var filter = new Filter(); + var resultsBasic = await _jobRepository.FilteredListAsync(filter, 10, 20); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Equal(4, resultsBasic.Items.Count()); + var first = resultsBasic.Items.First(); + Assert.Equal(1, first.JobId); + } + + [Fact] + public async void FilteredListAsync_OrderbyNextRunTime() + { + // Act + var filter = new Filter() + { + StatusFilters = null, + SortableFields = new List() + { + new SortableField() + { + Field = JobSortableFields.NextRunTime, + IsDescending = true, + Position = 0 + } + } + }; + var resultsBasic = await _jobRepository.FilteredListAsync(filter, 10, 20); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Equal(8, resultsBasic.Items.Count()); + var first = resultsBasic.Items.First(); + Assert.Equal(3, first.JobId); + } + + [Fact] + public async void FilteredListAsync_OrderbyName() + { + // Act + var filter = new Filter() + { + + TagIdFilters = null, + StatusFilters = null, + SortableFields = new List() + { + new SortableField() + { + Field = JobSortableFields.Name, + IsDescending = true, + Position = 0 + } + } + }; + var resultsBasic = await _jobRepository.FilteredListAsync(filter, 10, 20); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Equal(8, resultsBasic.Items.Count()); + var first = resultsBasic.Items.First(); + Assert.Equal(1085, first.JobId); + } + + [Fact] + public async void FilteredListAsync_OrderbyDescription() + { + // Act + var filter = new Filter() + { + + TagIdFilters = null, + StatusFilters = null, + SortableFields = new List() + { + new SortableField() + { + Field = JobSortableFields.Description, + IsDescending = true, + Position = 0 + } + } + }; + var resultsBasic = await _jobRepository.FilteredListAsync(filter, 10, 20); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Equal(8, resultsBasic.Items.Count()); + var first = resultsBasic.Items.First(); + Assert.Equal(1085, first.JobId); + } + + [Fact] + public async void FilteredListAsync_StatusFailed_Return_2() + { + // Act + var filter = new Filter() + { + StatusFilters = new List() + { + new StatusFilter() + { + IsEnabled = true, + JobStatus = JobStatus.Failed + } + } + }; + var resultsBasic = await _jobRepository.FilteredListAsync(filter, 10, 20); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Equal(0, resultsBasic.Items.Count()); + } + + [Fact] + public async void FilteredListAsync_StatusInactive_Return_2() + { + // Act + var filter = new Filter() + { + StatusFilters = new List() + { + new StatusFilter() + { + IsEnabled = true, + JobStatus = JobStatus.Inactive + } + } + }; + var resultsBasic = await _jobRepository.FilteredListAsync(filter, 10, 20); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Equal(4, resultsBasic.Items.Count()); + } + + [Fact] + public async void FilteredListWithPagingAsync_StatusInactive_Return_2() + { + // Act + var filter = new Filter() + { + Page = 1, + Limit = 1, + StatusFilters = new List() + { + new StatusFilter() + { + IsEnabled = true, + JobStatus = JobStatus.Inactive + } + } + }; + var resultsBasic = await _jobRepository.FilteredListAsync(filter, 10, 20); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Single(resultsBasic.Items); + } + + [Fact] + public async void FilteredListAsync_NextRunTimeGreaterThenDate_Return_0() + { + // Act + var filter = new Filter() + { + NextRuntimeGreaterThanDate = Convert.ToDateTime("8/20/2025") + }; + var resultsBasic = await _jobRepository.FilteredListAsync(filter, 10, 20); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Empty(resultsBasic.Items); + } + + [Fact] + public async void FilteredListAsync_NextRunTimeGreaterThenDate_Return_2() + { + // Act + var filter = new Filter() + { + NextRuntimeGreaterThanDate = Convert.ToDateTime("1/13/2022") + }; + var resultsBasic = await _jobRepository.FilteredListAsync(filter, 10, 20); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Equal(2, resultsBasic.Items.Count()); + } + + [Fact] + public async void FilteredListAsync_TagId2_Return_0() + { + // Act + var filter = new Filter() + { + TagIdFilters = new List { 2 } + }; + var resultsBasic = await _jobRepository.FilteredListAsync(filter, 10, 20); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Empty(resultsBasic.Items); + } + + [Fact] + public async void FilteredListAsync_TagId1_Return_2() + { + // Act + var filter = new Filter() + { + StatusFilters = null, + SortableFields = null, + TagIdFilters = new List { 1 } + }; + var resultsBasic = await _jobRepository.FilteredListAsync(filter, 10, 20); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Equal(4, resultsBasic.Items.Count()); + } + + [Fact] + public async void ListAsync_Test() + { + // Act + var resultsBasic = await _jobRepository.ListAsync(10, 20); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Equal(9, resultsBasic.Count()); + } + + [Fact] + public async void FindAsync_Found() + { + // Act + var resultsBasic = await _jobRepository.FindByIdAsync(1, 10, 20); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Equal(1, resultsBasic.JobId); + Assert.NotNull(resultsBasic.WorkItems); + Assert.Equal(2, resultsBasic.WorkItems.Count); + } + + [Fact] + public async void FindAsync_Missing() + { + // Act + var resultsBasic = await _jobRepository.FindByIdAsync(1000, 10, 20); + + // Assert + Assert.Null(resultsBasic); + } + + [Fact] + public async void Update_Test() + { + var foundJob = await _jobRepository.FindByIdAsync(1, 10, 20); + Assert.NotNull(foundJob); + Assert.Equal("Test.RequestingApplication.1", foundJob.RequestingApplication); + + foundJob.RequestingApplication = "Test.RequestingApplication.3"; + + // Act + _jobRepository.Update(foundJob); + + //Act / Assert + var updatedJob = await _jobRepository.FindByIdAsync(1, 10, 20); + Assert.NotNull(updatedJob); + updatedJob.RequestingApplication = "Test.RequestingApplication.3"; + } + + [Fact] + public async void AddAsync_Test() + { + var jobtag = new Tag(); + jobtag.TagId = 1; + jobtag.TagName = "Reports"; + // Arrange + var job = new Job() + { + JobId = 100, + JobTags = new List() + { + new JobTag() + { + JobId = 100, + Tag = jobtag, + TagId = 1 + }, + }, + + RequestingApplication = "Test.RequestingApplication.1", + WorkItems = new List() + { + new WorkItem () + { + WorkItemId = 100, + JobId = 100, + Order = 1, + SerializedPayload = "{TEST:null}", + Url = "Test.Application.1", + HttpMethod = HttpMethodType.Post, + }, + new WorkItem () + { + WorkItemId = 200, + JobId = 100, + Order = 2, + SerializedPayload = "{TEST:null}", + Url = "Test.Application.21", + HttpMethod = HttpMethodType.Post, + } + } + }; + + // Act + var resultsBasic = await _jobRepository.AddAsync(job); + + // Assert + Assert.NotNull(resultsBasic); + Assert.True(resultsBasic.JobId > 0); + Assert.True(resultsBasic.JobTags.Count > 0); + Assert.NotNull(resultsBasic.WorkItems); + Assert.Equal(100, resultsBasic.WorkItems.First().WorkItemId); + Assert.Equal(resultsBasic.JobId, resultsBasic.WorkItems.First().JobId); + } + + [Fact] + public async void FindJobsReadyToBeScheduled_Return_2() + { + // Act + var date = DateTime.Now; + var resultsBasic = await _jobRepository.FindJobsReadyToBeScheduled(date); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Equal(2, resultsBasic.Count()); + } + + [Fact] + public async void ListInactiveAsync_Test() + { + // Act + var resultsBasic = await _jobRepository.ListInactiveAsync(10, 20); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Equal(3, resultsBasic.Count()); + } + + [Fact] + public async void ListSoftDeletedAsync_Test() + { + // Act + var resultsBasic = await _jobRepository.ListSoftDeletedTrackedAsync(10, 20, new DateTime(2023, 04, 01)); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Single(resultsBasic); + } + + [Fact] + public async void ListInactiveExcludeSoftDeletedAsync_Test() + { + // Act + var resultsBasic = await _jobRepository.ListInactiveExcludeSoftDeletedTrackedAsync(10, 20); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Equal(2, resultsBasic.Count()); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/RepositoryTests/WorkItemRepositoryTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/RepositoryTests/WorkItemRepositoryTest.cs new file mode 100644 index 0000000..8086ede --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/RepositoryTests/WorkItemRepositoryTest.cs @@ -0,0 +1,102 @@ +using LCU.Api.Internal.DispatcherLib.Repositories; +using LCU.Library.Internal.DispatcherModels.Enums; +namespace LCU.Api.Internal.DispatcherTests.RepositoryTests; + +public class WorkItemRepositoryTest : DbContextFromJsonTest +{ + private readonly WorkItemRepository _workItemRepository; + + public WorkItemRepositoryTest() + { + var dbContext = GetContextWithData("RepositoryTests\\job_repo_basic.json"); + _workItemRepository = new WorkItemRepository(dbContext); + } + + [Fact] + public async void ListAsync_Test() + { + // Act + var resultsBasic = await _workItemRepository.ListAsync(); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Equal(17, resultsBasic.Count()); + } + + [Fact] + public async void FindAsync_Found() + { + // Act + var resultsBasic = await _workItemRepository.FindByIdAsync(1); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Equal(1, resultsBasic.WorkItemId); + } + + [Fact] + public async void FindAsync_Missing() + { + // Act + var resultsBasic = await _workItemRepository.FindByIdAsync(1000); + + // Assert + Assert.Null(resultsBasic); + } + + [Fact] + public async void Update_Test() + { + var foundWorkItem = await _workItemRepository.FindByIdAsync(1); + Assert.NotNull(foundWorkItem); + Assert.Equal("{TEST1: null}", foundWorkItem.SerializedPayload); + + foundWorkItem.SerializedPayload = "{TEST_New: null}"; + + // Act + _workItemRepository.Update(foundWorkItem); + + //Act / Assert + var updatedWorkItem = await _workItemRepository.FindByIdAsync(1); + Assert.NotNull(updatedWorkItem); + updatedWorkItem.SerializedPayload = "{TEST_New: null}"; + } + + [Fact] + public async void AddAsync_Test() + { + // Arrange + var workItem = new WorkItem() + { + WorkItemId = 100, + JobId = 100, + Name = "test", + Order = 1, + SerializedPayload = "{TEST:null}", + Url = "Test.Application.1", + HttpMethod = HttpMethodType.Post + }; + + // Act + var resultsBasic = await _workItemRepository.AddAsync(workItem); + + // Assert + Assert.NotNull(resultsBasic); + Assert.True(resultsBasic.WorkItemId > 0); + Assert.Equal(1, resultsBasic.Order); + Assert.Equal("{TEST:null}", resultsBasic.SerializedPayload); + Assert.Equal("Test.Application.1", resultsBasic.Url); + Assert.Equal(HttpMethodType.Post, resultsBasic.HttpMethod); + } + + [Fact] + public async void GetFirstOrDefaultWorkItemByJobId_Found() + { + // Arrange/Act + var resultsBasic = await _workItemRepository.GetFirstOrDefaultWorkItemByJobId(11); + + // Assert + Assert.NotNull(resultsBasic); + Assert.Equal(211, resultsBasic.WorkItemId); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/RepositoryTests/job_repo_basic.json b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/RepositoryTests/job_repo_basic.json new file mode 100644 index 0000000..9bb31f2 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/RepositoryTests/job_repo_basic.json @@ -0,0 +1,440 @@ +{ + "jobs": [ + { + "JobId": 1, + "RequestingApplication": "Test.RequestingApplication.1", + "name": "JobName", + "description": "description", + "createdBy": "Joshua Annear", + "NextRunTime": null, + "SoftDeleteDate": null, + "JobStatus": 1, + "WorkItems": [ + { + "WorkItemId": 1, + "JobId": 1, + "name": "test", + "Status": "Pending", + "StartedTimestamp": "11/30/2020", + "CompletedTimestamp": null, + "Order": 1, + "SerializedPayload": "{TEST1: null}", + "Url": "Test.Application.1.com/test", + "HttpMethod": "Post", + "AttemptsToExcute": 0, + "ActionType": "MoveFile" + }, + { + "WorkItemId": 2, + "JobId": 1, + "name": "test", + "Status": "Pending", + "StartedTimestamp": "11/30/2020", + "CompletedTimestamp": null, + "Order": 2, + "SerializedPayload": "{TEST:null}", + "Url": "Test.Application.2.com/test", + "HttpMethod": "Post", + "AttemptsToExcute": 0, + "ActionType": "MoveFile" + } + ], + "JobTags": [ + { + "JobTagId": 1, + "JobId": 1, + "TagId": 1 + } + ] + + }, + { + "JobId": 2, + "RequestingApplication": "Test.RequestingApplication.1", + "name": "JobName", + "description": "description", + "createdBy": "Joshua Annear", + "ReceivedTimestamp": "11/30/2020", + "NextRunTime": null, + "JobStatus": 1, + "WorkItems": [ + { + "WorkItemId": 3, + "JobId": 2, + "name": "test", + "Status": "Pending", + "StartedTimestamp": "11/30/2020", + "CompletedTimestamp": null, + "Order": 1, + "SerializedPayload": "{TEST:null}", + "Url": "Test.Application.1.com/test", + "HttpMethod": "Post", + "AttemptsToExcute": 0, + "ActionType": "MoveFile" + }, + { + "WorkItemId": 4, + "JobId": 2, + "name": "test", + "Status": "Pending", + "StartedTimestamp": "11/30/2020", + "CompletedTimestamp": null, + "Order": 2, + "SerializedPayload": "{TEST:null}", + "Url": "Test.Application.2.com/test", + "HttpMethod": "Post", + "AttemptsToExcute": 0, + "ActionType": "MoveFile" + } + ] + }, + { + "JobId": 3, + "RequestingApplication": "Test.RequestingApplication.1", + "name": "JobName", + "description": "description", + "createdBy": "Joshua Annear", + "NextRunTime": "8/19/2025", + "SoftDeleteDate": null, + "JobStatus": 1, + "WorkItems": [ + { + "WorkItemId": 5, + "JobId": 1, + "name": "test", + "Status": "Pending", + "StartedTimestamp": "11/30/2020", + "CompletedTimestamp": null, + "Order": 1, + "SerializedPayload": "{TEST1: null}", + "Url": "Test.Application.1.com/test", + "HttpMethod": "Post", + "AttemptsToExcute": 0, + "ActionType": "MoveFile" + }, + { + "WorkItemId": 6, + "JobId": 1, + "name": "test", + "Status": "Pending", + "StartedTimestamp": "11/30/2020", + "CompletedTimestamp": null, + "Order": 2, + "SerializedPayload": "{TEST:null}", + "Url": "Test.Application.2.com/test", + "HttpMethod": "Post", + "AttemptsToExcute": 0, + "ActionType": "MoveFile" + } + ], + "JobTags": [ + { + "JobTagId": 3, + "JobId": 3, + "TagId": 1 + } + ] + }, + { + "JobId": 4, + "RequestingApplication": "Test.RequestingApplication.1", + "name": "JobName", + "description": "description", + "createdBy": "Joshua Annear", + "NextRunTime": "08/19/2025", + "SoftDeleteDate": null, + "JobStatus": 1, + "WorkItems": [ + { + "WorkItemId": 7, + "JobId": 1, + "name": "test", + "Status": "Pending", + "StartedTimestamp": "11/30/2020", + "CompletedTimestamp": null, + "Order": 1, + "SerializedPayload": "{TEST1: null}", + "Url": "Test.Application.1.com/test", + "HttpMethod": "Post", + "AttemptsToExcute": 0, + "ActionType": "MoveFile" + }, + { + "WorkItemId": 8, + "JobId": 1, + "name": "test", + "Status": "Pending", + "StartedTimestamp": "11/30/2020", + "CompletedTimestamp": null, + "Order": 2, + "SerializedPayload": "{TEST:null}", + "Url": "Test.Application.2.com/test", + "HttpMethod": "Post", + "AttemptsToExcute": 0, + "ActionType": "MoveFile" + } + ], + "JobTags": [ + { + "JobTagId": 4, + "JobId": 4, + "TagId": 1 + } + ] + }, + { + "jobId": 1085, + "requestingApplication": "Dispatcher Manager", + "name": "test FAIL", + "description": "test", + "createdBy": "Joshua Annear", + "createdAt": "2021-06-09T08:33:21.393", + "modifiedBy": "Joshua Annear", + "modifiedDate": "2022-01-13T11:05:28.993", + "cronSchedule": null, + "nextRunTime": "2022-01-13T11:05:56.237", + "jobErrorRecipients": [ + { + "jobErrorRecipientId": 4, + "jobId": 1085, + "recipient": "JoshuaAnnear@landmarkcu.com" + } + ], + "workItems": [ + { + "workItemId": 1297, + "jobId": 1085, + "name": "test", + "description": "test", + "order": 0, + "serializedPayload": "{\"SourceDirectory\":\"test\",\"DirectoryName\":\"test\",\"ZipPath\":\"test\",\"ZipFileName\":\"test\",\"FileNameSetting\":{\"DateFormatSettings\":[],\"RegexSettings\":[],\"DaysFromToday\":0},\"DynamicDirectoryName\":\"test\",\"FormattedSourceDirectory\":\"test\\\\\",\"ZipDestination\":\"test\\\\test.zip\",\"ResponseItems\":[{\"ReturnQueueConsumerConfig\":{\"QueueName\":\"LCU.Queue.Dispatcher.Completion\",\"ExchangeName\":\"LCU.Dispatcher.Direct\",\"Subject\":\"LCU.Queue.Dispatcher.Completion\"},\"ReturnQueueConnectionConfig\":{\"Url\":\"localhost\",\"Port\":5672,\"Username\":\"guest\",\"Password\":\"guest\"},\"ResponsePayload\":\"{\\\"WorkItemId\\\":1297}\"}]}", + "url": "https://localhost:44326/api/FileOperations/ZipDirectory", + "httpMethod": 1, + "actionType": 6, + "workItemExecutions": [ + { + "workItemExecutionId": 1545, + "workItemId": 1297, + "status": 1, + "startedTimestamp": "2021-06-09T08:34:02.6", + "completedTimestamp": "2022-01-13T11:05:56.207", + "systemMessage": null, + "attemptsToExecute": null + }, + { + "workItemExecutionId": 1608, + "workItemId": 1297, + "status": 3, + "startedTimestamp": "2022-01-13T11:06:04.627", + "completedTimestamp": null, + "systemMessage": "", + "attemptsToExecute": null + } + ] + } + ] + }, + { + "jobId": 68, + "requestingApplication": "Dispatcher Manager", + "name": "Rename and Move File", + "description": "Rename and Move File", + "createdBy": "Joshua Annear", + "createdAt": "2021-02-15T14:47:05.93", + "modifiedBy": "Joshua Annear", + "modifiedDate": "2022-01-13T11:04:39.633", + "cronSchedule": "55 * * * *", + "nextRunTime": "2022-01-13T11:55:00", + "jobErrorRecipients": [], + "workItems": [ + { + "workItemId": 278, + "jobId": 68, + "name": "RenameFile", + "description": null, + "order": 0, + "serializedPayload": "{\"SourcePath\":\"\\\\\\\\zeus\\\\DataIntegrationsQA\\\\Test_FileOperationsApi_Folder_1\",\"SourceFile\":\"Test_New.txt\",\"NewFilename\":\"Test2.txt\",\"SourceNameSetting\":{\"DateFormatSettings\":[],\"RegexSettings\":null,\"DaysFromToday\":0},\"NewNameSetting\":{\"DateFormatSettings\":[],\"RegexSettings\":null,\"DaysFromToday\":0},\"SucceedOnError\":true,\"FinalSourceFile\":\"Test_New.txt\",\"Source\":\"\\\\\\\\zeus\\\\DataIntegrationsQA\\\\Test_FileOperationsApi_Folder_1\\\\Test_New.txt\",\"FinalNewFileName\":\"Test2.txt\",\"NewFilenamePath\":\"\\\\\\\\zeus\\\\DataIntegrationsQA\\\\Test_FileOperationsApi_Folder_1\\\\Test2.txt\",\"ResponseItems\":[{\"ReturnQueueConsumerConfig\":{\"QueueName\":\"LCU.Queue.Dispatcher.Completion\",\"ExchangeName\":\"LCU.Dispatcher.Direct\",\"Subject\":\"LCU.Queue.Dispatcher.Completion\"},\"ReturnQueueConnectionConfig\":{\"Url\":\"localhost\",\"Port\":5672,\"Username\":\"guest\",\"Password\":\"guest\"},\"ResponsePayload\":\"{\\\"WorkItemId\\\":278}\"}]}", + "url": "https://localhost:44326/api/FileOperations/RenameFile", + "httpMethod": 1, + "actionType": 2, + "workItemExecutions": [ + { + "workItemExecutionId": 1607, + "workItemId": 278, + "status": 3, + "startedTimestamp": "2021-11-23T13:17:03.343", + "completedTimestamp": "2021-11-23T13:21:43.68", + "systemMessage": "Request is invalid. Response: File does not exists at path \\\\zeus\\DataIntegrationsQA\\Test_FileOperationsApi_Folder_1\\Test_New.txt \n", + "attemptsToExecute": null + }, + { + "workItemExecutionId": 1609, + "workItemId": 278, + "status": 3, + "startedTimestamp": "2022-01-13T13:42:06.3", + "completedTimestamp": null, + "systemMessage": "", + "attemptsToExecute": null + } + ] + }, + { + "workItemId": 1307, + "jobId": 68, + "name": "test3", + "description": null, + "order": 1, + "serializedPayload": "{\"SourcePath\":\"\\\\\\\\zeus\\\\DataIntegrationsQA\\\\Test_FileOperationsApi_Folder_1\",\"SourceFile\":\"Test2.txt\",\"DestinationPath\":\"\\\\\\\\zeus\\\\DataIntegrationsQA\\\\Test_FileOperationsApi_Folder_2\",\"DestinationName\":\"test3.txt\",\"Overwrite\":false,\"SucceedOnError\":false,\"FileNameSetting\":{\"DateFormatSettings\":[],\"RegexSettings\":[],\"DaysFromToday\":0},\"FinalSourceFile\":\"Test2.txt\",\"FinalDestinationName\":\"test3.txt\",\"Source\":\"\\\\\\\\zeus\\\\DataIntegrationsQA\\\\Test_FileOperationsApi_Folder_1\\\\Test2.txt\",\"Destination\":\"\\\\\\\\zeus\\\\DataIntegrationsQA\\\\Test_FileOperationsApi_Folder_2\\\\test3.txt\",\"ResponseItems\":[{\"ReturnQueueConsumerConfig\":{\"QueueName\":\"LCU.Queue.Dispatcher.Completion\",\"ExchangeName\":\"LCU.Dispatcher.Direct\",\"Subject\":\"LCU.Queue.Dispatcher.Completion\"},\"ReturnQueueConnectionConfig\":{\"Url\":\"localhost\",\"Port\":5672,\"Username\":\"guest\",\"Password\":\"guest\"},\"ResponsePayload\":\"{\\\"WorkItemId\\\":1307}\"}]}", + "url": "https://localhost:44326/api/FileOperations/MoveFile", + "httpMethod": 1, + "actionType": 0, + "workItemExecutions": [ + { + "workItemExecutionId": 1601, + "workItemId": 1307, + "status": 2, + "startedTimestamp": "2021-11-23T12:30:26.777", + "completedTimestamp": "2021-11-23T13:13:10.357", + "systemMessage": "", + "attemptsToExecute": null + }, + { + "workItemExecutionId": 1604, + "workItemId": 1307, + "status": 2, + "startedTimestamp": "2021-11-23T13:14:20.7", + "completedTimestamp": "2021-11-23T13:14:37.663", + "systemMessage": null, + "attemptsToExecute": null + } + ] + } + ] + }, + { + "JobId": 11, + "RequestingApplication": "Test.RequestingApplication.1", + "name": "JobName", + "description": "test", + "createdBy": "Joshua Annear", + "NextRunTime": "1/1/2023", + "WorkItems": [ + { + "WorkItemId": 111, + "JobId": 11, + "name": "test", + "Status": "Pending", + "StartedTimestamp": "11/30/2020", + "CompletedTimestamp": null, + "Order": 1, + "SerializedPayload": "{TEST1: null}", + "Url": "Test.Application.1.com/test", + "HttpMethod": "Post", + "AttemptsToExcute": 0, + "ActionType": "MoveFile" + }, + { + "WorkItemId": 211, + "JobId": 11, + "name": "test", + "Status": "Pending", + "StartedTimestamp": "11/30/2020", + "CompletedTimestamp": null, + "Order": 0, + "SerializedPayload": "{TEST:null}", + "Url": "Test.Application.2.com/test", + "HttpMethod": "Post", + "AttemptsToExcute": 0, + "ActionType": "MoveFile" + } + ], + "JobTags": [ + { + "JobTagId": 111, + "JobId": 11, + "TagId": 1 + } + ] + + }, + { + "JobId": 21, + "RequestingApplication": "Test.RequestingApplication.1", + "name": "JobName", + "description": "test", + "createdBy": "Joshua Annear", + "ReceivedTimestamp": "11/30/2020", + "NextRunTime": "1/1/2023", + "WorkItems": [ + { + "WorkItemId": 321, + "JobId": 21, + "name": "test", + "Status": "Pending", + "StartedTimestamp": "11/30/2020", + "CompletedTimestamp": null, + "Order": 1, + "SerializedPayload": "{TEST:null}", + "Url": "Test.Application.1.com/test", + "HttpMethod": "Post", + "AttemptsToExcute": 0, + "ActionType": "MoveFile" + }, + { + "WorkItemId": 421, + "JobId": 21, + "name": "test", + "Status": "Pending", + "StartedTimestamp": "11/30/2020", + "CompletedTimestamp": null, + "Order": 2, + "SerializedPayload": "{TEST:null}", + "Url": "Test.Application.2.com/test", + "HttpMethod": "Post", + "AttemptsToExcute": 0, + "ActionType": "MoveFile" + } + ] + }, + { + "JobId": 23, + "RequestingApplication": "Test.RequestingApplication.1", + "name": "JobName", + "description": "test", + "createdBy": "Joshua Annear", + "ReceivedTimestamp": "11/30/2020", + "SoftDeleteDate": "2023-03-06", + "NextRunTime": null, + "WorkItems": [ + { + "WorkItemId": 456, + "JobId": 21, + "name": "test", + "Status": "Pending", + "StartedTimestamp": "11/30/2020", + "CompletedTimestamp": null, + "Order": 1, + "SerializedPayload": "{TEST:null}", + "Url": "Test.Application.1.com/test", + "HttpMethod": "Post", + "AttemptsToExcute": 0, + "ActionType": "MoveFile" + }, + { + "WorkItemId": 567, + "JobId": 21, + "name": "test", + "Status": "Pending", + "StartedTimestamp": "11/30/2020", + "CompletedTimestamp": null, + "Order": 2, + "SerializedPayload": "{TEST:null}", + "Url": "Test.Application.2.com/test", + "HttpMethod": "Post", + "AttemptsToExcute": 0, + "ActionType": "MoveFile" + } + ] + } + ] +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/CleanUpJobSettingServiceTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/CleanUpJobSettingServiceTest.cs new file mode 100644 index 0000000..08e85de --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/CleanUpJobSettingServiceTest.cs @@ -0,0 +1,129 @@ +using LCU.Api.Internal.Dispatcher.Services; +using LCU.Api.Internal.DispatcherLib.Repositories; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +namespace LCU.Api.Internal.DispatcherTests.ServiceTests; + +public class CleanUpJobSettingServiceTest +{ + private readonly CleanUpJobSettingService _cleanUpJobSettingService; + private readonly Mock _mockUnitOfWork; + private readonly Mock _mockCleanUpJobSettingRepository; + + public CleanUpJobSettingServiceTest() + { + _mockCleanUpJobSettingRepository = new Mock(); + _mockUnitOfWork = new Mock(); + + _cleanUpJobSettingService = new CleanUpJobSettingService(_mockUnitOfWork.Object, + _mockCleanUpJobSettingRepository.Object); + } + + [Fact] + public async Task GetAsync_Success() + { + // Arrange + _mockCleanUpJobSettingRepository.Setup(repo => repo.ListAsync()).ReturnsAsync(new List() + { + new CleanUpJobSetting(), + new CleanUpJobSetting(), + new CleanUpJobSetting() + }); + + // Act + var result = await _cleanUpJobSettingService.GetAsync(); + + // Assert + Assert.NotNull(result); + Assert.Equal(3, result.Count()); + } + + [Fact] + public async Task GetByCleanUpJobSettingIdAsync_Success() + { + // Arrange + _mockCleanUpJobSettingRepository.Setup(repo => repo.FindByCleanUpJobSettingIdAsync(It.IsAny())).ReturnsAsync(new CleanUpJobSetting()); + + // Act + var result = await _cleanUpJobSettingService.GetByCleanUpJobSettingIdAsync(100); + + // Assert + Assert.NotNull(result); + } + + [Fact] + public async Task GetByCleanUpJobSettingIdAsync_Null() + { + // Arrange + _mockCleanUpJobSettingRepository.Setup(repo => repo.FindByCleanUpJobSettingIdAsync(It.IsAny())).ReturnsAsync((CleanUpJobSetting)null); + + // Act + var result = await _cleanUpJobSettingService.GetByCleanUpJobSettingIdAsync(100); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task SaveAsync_Success() + { + // Arrange + _mockCleanUpJobSettingRepository.Setup(repo => repo.AddAsync(It.IsAny())).ReturnsAsync(new CleanUpJobSetting()); + + // Act + var result = await _cleanUpJobSettingService.SaveAsync(new CleanUpJobSetting()); + + // Assert + Assert.NotNull(result); + _mockCleanUpJobSettingRepository.Verify(x => x.AddAsync(It.IsAny()), Times.Once); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); + } + + [Fact] + public async void UpdateAsync_Success() + { + // Arrange + var cleanUpJobSettingToUpdate = new CleanUpJobSetting() + { + CleanUpJobSettingId = 100, + CompletionDaysPast = 15 + }; + + _mockCleanUpJobSettingRepository.Setup(repo => repo.FindByCleanUpJobSettingIdAsync(It.IsAny())).ReturnsAsync(cleanUpJobSettingToUpdate); + _mockCleanUpJobSettingRepository.Setup(repo => repo.Update(It.IsAny())).Verifiable(); + + // Act + var result = await _cleanUpJobSettingService.UpdateAsync(cleanUpJobSettingToUpdate); + + // Assert + Assert.NotNull(result); + Assert.Equal(cleanUpJobSettingToUpdate.CleanUpJobSettingId, result.CleanUpJobSettingId); + Assert.Equal(cleanUpJobSettingToUpdate.CompletionDaysPast, result.CompletionDaysPast); + _mockCleanUpJobSettingRepository.VerifyAll(); + } + + + [Fact] + public async Task DeleteAsync_Success() + { + // Arrange + var cleanUpJobSettingReturned = new CleanUpJobSetting() + { + CleanUpJobSettingId = 100, + CompletionDaysPast = 15 + }; + + _mockCleanUpJobSettingRepository.Setup(repo => repo.FindByCleanUpJobSettingIdAsync(It.IsAny())).ReturnsAsync(cleanUpJobSettingReturned); + _mockCleanUpJobSettingRepository.Setup(repo => repo.Remove(It.IsAny())).Verifiable(); + + // Act + var cleanUpJobSettingIdToDelete = 100; + await _cleanUpJobSettingService.DeleteAsync(cleanUpJobSettingIdToDelete); ; + + // Assert + _mockCleanUpJobSettingRepository.VerifyAll(); + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/CleanUpJobTagServiceTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/CleanUpJobTagServiceTest.cs new file mode 100644 index 0000000..819b192 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/CleanUpJobTagServiceTest.cs @@ -0,0 +1,145 @@ +using LCU.Api.Internal.Dispatcher.Services; +using LCU.Api.Internal.DispatcherLib.Repositories; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LCU.Api.Internal.DispatcherTests.ServiceTests; + +public class CleanUpJobTagServiceTest +{ + private readonly CleanUpJobTagService _cleanUpJobTagService; + private readonly Mock _mockUnitOfWork; + private readonly Mock _mockCleanUpJobTagRepository; + + public CleanUpJobTagServiceTest() + { + _mockCleanUpJobTagRepository = new Mock(); + _mockUnitOfWork = new Mock(); + + _cleanUpJobTagService = new CleanUpJobTagService(_mockCleanUpJobTagRepository.Object, + _mockUnitOfWork.Object); + } + + [Fact] + public async Task GetAsync_Success() + { + // Arrange + _mockCleanUpJobTagRepository.Setup(repo => repo.ListAsync()).ReturnsAsync(new List() + { + new CleanUpJobTag(), + new CleanUpJobTag(), + new CleanUpJobTag() + }); + + // Act + var result = await _cleanUpJobTagService.GetAsync(); + + // Assert + Assert.NotNull(result); + Assert.Equal(3, result.Count()); + } + + [Fact] + public async Task GetByCleanUpJobTagIdAsync_Success() + { + // Arrange + _mockCleanUpJobTagRepository.Setup(repo => repo.FindByCleanUpJobTagIdAsync(It.IsAny())).ReturnsAsync(new CleanUpJobTag()); + + // Act + var result = await _cleanUpJobTagService.GetByCleanUpJobTagIdAsync(100); + + // Assert + Assert.NotNull(result); + } + + [Fact] + public async Task GetByCleanUpJobSettingIdAsync_Success() + { + // Arrange + _mockCleanUpJobTagRepository.Setup(repo => repo.FindByCleanUpJobSettingIdAsync(It.IsAny())).ReturnsAsync(new List() + { + new CleanUpJobTag(), + new CleanUpJobTag(), + new CleanUpJobTag() + }); + + // Act + var result = await _cleanUpJobTagService.GetByCleanUpJobSettingIdAsync(100); + + // Assert + Assert.NotNull(result); + Assert.Equal(3, result.Count()); + } + + [Fact] + public async Task SaveAsync_Success() + { + // Arrange + _mockCleanUpJobTagRepository.Setup(repo => repo.AddAsync(It.IsAny())).ReturnsAsync(new CleanUpJobTag()); + + // Act + var result = await _cleanUpJobTagService.SaveAsync(new CleanUpJobTag()); + + // Assert + Assert.NotNull(result); + _mockCleanUpJobTagRepository.Verify(x => x.AddAsync(It.IsAny()), Times.Once); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); + } + + [Fact] + public async Task DeleteAsync_Success() + { + // Arrange + var cleanUpJobTagReturned = new CleanUpJobTag() + { + CleanUpJobTagId = 100, + CleanUpJobSettingId = 16, + TagId = 10 + }; + + _mockCleanUpJobTagRepository.Setup(repo => repo.FindByCleanUpJobTagIdAsync(It.IsAny())).ReturnsAsync(cleanUpJobTagReturned); + _mockCleanUpJobTagRepository.Setup(repo => repo.Remove(It.IsAny())).Verifiable(); + + // Act + var cleanUpJobSettingIdToDelete = 100; + await _cleanUpJobTagService.DeleteAsync(cleanUpJobSettingIdToDelete); ; + + // Assert + _mockCleanUpJobTagRepository.VerifyAll(); + } + + [Fact] + public async Task DeleteByCleanUpJobSettingIdAsync_Success() + { + // Arrange + var tagOne = new CleanUpJobTag() + { + CleanUpJobTagId = 100, + CleanUpJobSettingId = 16, + TagId = 10 + }; + var tagTwo = new CleanUpJobTag() + { + CleanUpJobTagId = 101, + CleanUpJobSettingId = 16, + TagId = 12 + }; + + var tagList = new List(); + tagList.Add(tagOne); + tagList.Add(tagTwo); + + _mockCleanUpJobTagRepository.Setup(repo => repo.FindByCleanUpJobSettingIdAsync(It.IsAny())).ReturnsAsync(tagList); + _mockCleanUpJobTagRepository.Setup(repo => repo.Remove(tagOne)).Verifiable(); + _mockCleanUpJobTagRepository.Setup(repo => repo.Remove(tagTwo)).Verifiable(); + + // Act + await _cleanUpJobTagService.DeleteByCleanUpJobSettingIdAsync(16); ; + + // Assert + _mockCleanUpJobTagRepository.VerifyAll(); + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/BatchErroredJobsServiceTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/BatchErroredJobsServiceTest.cs new file mode 100644 index 0000000..96f073f --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/BatchErroredJobsServiceTest.cs @@ -0,0 +1,101 @@ +using LCU.Api.Internal.Dispatcher.Services; +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +using LCU.Api.Internal.Dispatcher.Services.QueueServices; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.DispatcherModels.Models.Requests; +using System.Collections.Generic; +using System.Threading; +namespace LCU.Api.Internal.DispatcherTests.ServiceTests.DispatcherOperationServices; + +public class BatchErroredJobsServiceTest +{ + private readonly BatchErroredJobsService _service; + private readonly Mock _mockJobService; + private readonly Mock _mockQueueSendErroredJobAlertService; + + public BatchErroredJobsServiceTest() + { + _mockJobService = new Mock(); + _mockQueueSendErroredJobAlertService = new Mock(); + _service = new BatchErroredJobsService(_mockJobService.Object, + _mockQueueSendErroredJobAlertService.Object); + } + + [Fact] + public void Basic_Construct_Test() + { + Assert.NotNull(_service); + } + + [Fact] + public async Task PerformBatchErroredJobs_NoJobs_DoesNotQueue() + { + // Arrange + _mockJobService.Setup(x => x.GetErrorMonitoringJobs()) + .ReturnsAsync(new List()); + + // Act + await _service.PerformBatchErroredJobs(CancellationToken.None); + + // Assert + _mockQueueSendErroredJobAlertService.Verify( + x => x.QueueSendErroredJobAlert(It.IsAny()), + Times.Never); + } + + [Fact] + public async Task PerformBatchErroredJobs_JobsWithFailedWorkItems_QueuesBatch() + { + // Arrange + var failedExecution = new WorkItemExecution + { + Status = StatusType.Failed + }; + var workItem = new WorkItem + { + WorkItemId = 1, + WorkItemExecutions = new List { failedExecution } + }; + var job = new Job + { + JobId = 10, + WorkItems = new List { workItem } + }; + _mockJobService.Setup(x => x.GetErrorMonitoringJobs()) + .ReturnsAsync(new List { job }); + + BatchedErroredJobAlertRequest capturedBatch = null; + _mockQueueSendErroredJobAlertService + .Setup(x => x.QueueSendErroredJobAlert(It.IsAny())) + .Callback(batch => capturedBatch = batch) + .Returns(Task.CompletedTask); + + // Act + await _service.PerformBatchErroredJobs(CancellationToken.None); + + // Assert + _mockQueueSendErroredJobAlertService.Verify( + x => x.QueueSendErroredJobAlert(It.IsAny()), + Times.Once); + + Assert.NotNull(capturedBatch); + Assert.Single(capturedBatch.ErroredJobAlertRequests); + Assert.Equal(job, capturedBatch.ErroredJobAlertRequests[0].CurrentJob); + Assert.Equal(workItem, capturedBatch.ErroredJobAlertRequests[0].CurrentWorkItem); + } + + [Fact] + public async Task PerformBatchErroredJobs_Exception_IsHandledAndLogged() + { + // Arrange + _mockJobService.Setup(x => x.GetErrorMonitoringJobs()) + .ThrowsAsync(new System.Exception("Test exception")); + + // Act & Assert + await _service.PerformBatchErroredJobs(CancellationToken.None); + + _mockQueueSendErroredJobAlertService.Verify( + x => x.QueueSendErroredJobAlert(It.IsAny()), + Times.Never); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/CleanUpJobsServiceTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/CleanUpJobsServiceTest.cs new file mode 100644 index 0000000..6478e48 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/CleanUpJobsServiceTest.cs @@ -0,0 +1,382 @@ +using LCU.Api.Internal.Dispatcher.Configurations; +using LCU.Api.Internal.Dispatcher.Services; +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +using LCU.Api.Internal.DispatcherLib.Repositories; +using LCU.Library.Internal.DispatcherModels.Models.Results; +using System.Collections.Generic; + +namespace LCU.Api.Internal.DispatcherTests.ServiceTests.DispatcherOperationServices; + +public class CleanUpJobsServiceTest +{ + private readonly Mock _mockJobService; + private readonly Mock _mockCleanUpJobSettingService; + private readonly Mock _mockUnitOfWork; + private readonly CleanUpJobsService _cleanUpJobsService; + + public CleanUpJobsServiceTest() + { + var apiConfiguration = new ApiConfiguration() + { + CleanUpJobsDaysToDelete = -20 + }; + _mockJobService = new Mock(); + _mockCleanUpJobSettingService = new Mock(); + _mockUnitOfWork = new Mock(); + _cleanUpJobsService = new CleanUpJobsService(_mockJobService.Object, + _mockCleanUpJobSettingService.Object, + _mockUnitOfWork.Object, + apiConfiguration); + } + + [Fact] + public void Basic_Construct_Test() + { + Assert.NotNull(_cleanUpJobsService); + } + + [Fact] + public void ShouldSoftDeleteByDate_True() + { + // Arrange + var setting = new CleanUpJobSetting() + { + CompletionDaysPast = -15 + }; + + var execution = new WorkItemExecution() + { + WorkItemExecutionId = 1, + WorkItemId = 2, + CompletedTimestamp = DateTime.Now.AddDays(-30), + StartedTimestamp = DateTime.Now.AddDays(-30).AddMinutes(-1) + }; + + var workItem = new WorkItem() + { + JobId = 1, + WorkItemId = 2, + WorkItemExecutions = new List() + { + execution + }, + Order = 1 + }; + + var job = new Job() + { + JobId = 1, + WorkItems = new List() + { + workItem + }, + }; + + var result = new CleanUpJobBatchResult(); + + // Act + var shouldDelete = _cleanUpJobsService.ShouldSoftDeleteByDate(setting, job, result); + + // Assert + Assert.True(shouldDelete); + } + + [Fact] + public void ShouldSoftDeleteByDate_False() + { + // Arrange + var setting = new CleanUpJobSetting() + { + CompletionDaysPast = -45 + }; + + var execution = new WorkItemExecution() + { + WorkItemExecutionId = 1, + WorkItemId = 2, + CompletedTimestamp = DateTime.Now.AddDays(-30), + StartedTimestamp = DateTime.Now.AddDays(-30).AddMinutes(-1) + }; + + var workItem = new WorkItem() + { + JobId = 1, + WorkItemId = 2, + WorkItemExecutions = new List() + { + execution + }, + Order = 1 + }; + + var job = new Job() + { + JobId = 1, + WorkItems = new List() + { + workItem + }, + }; + + var result = new CleanUpJobBatchResult(); + + // Act + var shouldDelete = _cleanUpJobsService.ShouldSoftDeleteByDate(setting, job, result); + + // Assert + Assert.False(shouldDelete); + } + + [Fact] + public void ShouldSoftDeleteByDate_NoWorkItemExecutions_False() + { + // Arrange + var setting = new CleanUpJobSetting() + { + CompletionDaysPast = -45 + }; + + var workItem = new WorkItem() + { + JobId = 1, + WorkItemId = 2, + WorkItemExecutions = new List(), + Order = 1 + }; + + var job = new Job() + { + JobId = 1, + WorkItems = new List() + { + workItem + }, + }; + + var result = new CleanUpJobBatchResult(); + + // Act + var shouldDelete = _cleanUpJobsService.ShouldSoftDeleteByDate(setting, job, result); + + // Assert + Assert.False(shouldDelete); + Assert.Equal(1, result.FailureCount); + } + + [Fact] + public void ShouldSoftDeleteByDate_NoWorkItems_False() + { + // Arrange + var setting = new CleanUpJobSetting() + { + CompletionDaysPast = -45 + }; + + var job = new Job() + { + JobId = 1, + WorkItems = new List(), + }; + + var result = new CleanUpJobBatchResult(); + + // Act + var shouldDelete = _cleanUpJobsService.ShouldSoftDeleteByDate(setting, job, result); + + // Assert + Assert.False(shouldDelete); + Assert.Equal(1, result.FailureCount); + } + + [Fact] + public void ShouldSoftDeleteByTag_Okay() + { + // Arrange + var setting = new CleanUpJobSetting() + { + CleanUpJobTags = new List() + { + new CleanUpJobTag() + { + TagId = 1 + }, + new CleanUpJobTag() + { + TagId = 3 + }, + new CleanUpJobTag() + { + TagId = 5 + } + } + }; + + var job = new Job() + { + JobTags = new List() + { + new JobTag() + { + TagId = 1 + }, + new JobTag() + { + TagId = 3 + }, + new JobTag() + { + TagId = 5 + } + } + }; + + // Act + var shouldDelete = _cleanUpJobsService.ShouldSoftDeleteByTag(setting, job); + + // Assert + Assert.True(shouldDelete); + } + + [Fact] + public void ShouldSoftDeleteByTag_Okay_UnorderedTagId() + { + // Arrange + var setting = new CleanUpJobSetting() + { + CleanUpJobTags = new List() + { + new CleanUpJobTag() + { + TagId = 1 + }, + new CleanUpJobTag() + { + TagId = 3 + }, + new CleanUpJobTag() + { + TagId = 5 + } + } + }; + + var job = new Job() + { + JobTags = new List() + { + new JobTag() + { + TagId = 3 + }, + new JobTag() + { + TagId = 5 + }, + new JobTag() + { + TagId = 1 + } + } + }; + + // Act + var shouldDelete = _cleanUpJobsService.ShouldSoftDeleteByTag(setting, job); + + // Assert + Assert.True(shouldDelete); + } + + [Fact] + public void ShouldSoftDeleteByTag_False_NumberOfTags() + { + // Arrange + var setting = new CleanUpJobSetting() + { + CleanUpJobTags = new List() + { + new CleanUpJobTag() + { + TagId = 1 + }, + new CleanUpJobTag() + { + TagId = 3 + }, + new CleanUpJobTag() + { + TagId = 5 + } + } + }; + + var job = new Job() + { + JobTags = new List() + { + new JobTag() + { + TagId = 3 + }, + new JobTag() + { + TagId = 5 + } + } + }; + + // Act + var shouldDelete = _cleanUpJobsService.ShouldSoftDeleteByTag(setting, job); + + // Assert + Assert.False(shouldDelete); + } + + [Fact] + public void ShouldSoftDeleteByTag_False_DifferentTags() + { + // Arrange + var setting = new CleanUpJobSetting() + { + CleanUpJobTags = new List() + { + new CleanUpJobTag() + { + TagId = 1 + }, + new CleanUpJobTag() + { + TagId = 3 + }, + new CleanUpJobTag() + { + TagId = 5 + } + } + }; + + var job = new Job() + { + JobTags = new List() + { + new JobTag() + { + TagId = 3 + }, + new JobTag() + { + TagId = 5 + }, + new JobTag() + { + TagId = 100 + } + } + }; + + // Act + var shouldDelete = _cleanUpJobsService.ShouldSoftDeleteByTag(setting, job); + + // Assert + Assert.False(shouldDelete); + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/CompletionServiceTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/CompletionServiceTest.cs new file mode 100644 index 0000000..0785462 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/CompletionServiceTest.cs @@ -0,0 +1,210 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using LCU.Library.Internal.ApiResponse.Models; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Api.Internal.Dispatcher.Services; +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +using LCU.Api.Internal.Dispatcher.Services.QueueServices; +namespace LCU.Api.Internal.DispatcherTests.ServiceTests.DispatcherOperationServices; + +public class CompletionServiceTest +{ + private readonly CompletionService _completionService; + private readonly Mock _mockJobService; + private readonly Mock _mockWorkItemService; + private readonly Mock _mockExecutionMessageService; + private readonly Mock _mockQueueJobFailureService; + private readonly Mock _mockQueueDetermineOperationService; + + public CompletionServiceTest() + { + _mockJobService = new Mock(); + _mockWorkItemService = new Mock(); + _mockExecutionMessageService = new Mock(); + _mockQueueJobFailureService = new Mock(); + _mockQueueDetermineOperationService = new Mock(); + _completionService = new CompletionService(_mockJobService.Object, + _mockWorkItemService.Object, + _mockExecutionMessageService.Object, + _mockQueueJobFailureService.Object, + _mockQueueDetermineOperationService.Object); + } + + [Fact] + public async Task HandleCompletionRequest_CurrentWorkItemExecution_Null() + { + var dispatcherReturnResponse = new DispatcherReturnResponse + { + WorkItemId = 1 + }; + var response = new Response + { + ResponsePayload = JsonConvert.SerializeObject(dispatcherReturnResponse), + Successful = true + }; + var workItem = new WorkItem + { + WorkItemExecutions = new List + { + new WorkItemExecution + { + WorkItemId = 1, + WorkItemExecutionId = 1, + CompletedTimestamp = Convert.ToDateTime("1/1/2022") + } + } + }; + var job = new Job() + { + JobId = 1, + Name = "JobOne" + }; + _mockJobService.Setup(x => x.GetByWorkItemIdExcludingWorkItemsAsync(It.IsAny())).ReturnsAsync(job); + _mockWorkItemService.Setup(x => x.GetByIdAsync(It.IsAny())).ReturnsAsync(workItem); + + await _completionService.HandleCompletionRequest(response); + + _mockJobService.Verify(x => x.GetByWorkItemIdExcludingWorkItemsAsync(It.IsAny()), Times.Once); + _mockQueueJobFailureService.Verify(x => x.QueueJobFailure(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); + _mockWorkItemService.Verify(x => x.GetByIdAsync(It.IsAny()), Times.Once); + _mockExecutionMessageService.Verify(x => x.SaveExecutionMessages(It.IsAny(), It.IsAny>()), Times.Never); + _mockWorkItemService.Verify(x => x.ResetCanceledJobCounter(It.IsAny()), Times.Never); + _mockJobService.Verify(x => x.UpdateAsync(It.IsAny()), Times.Never); + _mockQueueDetermineOperationService.Verify(x => x.QueueDetermineOperation(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public async Task HandleCompletionRequest_Successful_False() + { + var dispatcherReturnResponse = new DispatcherReturnResponse + { + WorkItemId = 1 + }; + var response = new Response + { + ResponsePayload = JsonConvert.SerializeObject(dispatcherReturnResponse), + Successful = false + }; + var workItemExecution = new WorkItemExecution + { + WorkItemExecutionId = 1, + WorkItemId = 1, + CompletedTimestamp = null, + Status = StatusType.Processing + }; + var workItem = new WorkItem + { + WorkItemId = 1, + CancelJobOnFailure = false, + Url = "http://testing.com", + SerializedPayload = "{\"Test\":\"Test\"}", + HttpMethod = HttpMethodType.Post, + WorkItemExecutions = new List + { + workItemExecution + } + }; + var job = new Job + { + JobId = 1 + }; + + _mockJobService.Setup(x => x.GetByWorkItemIdExcludingWorkItemsAsync(It.IsAny())).ReturnsAsync(job); + _mockWorkItemService.Setup(x => x.GetByIdAsync(It.IsAny())).ReturnsAsync(workItem); + _mockExecutionMessageService.Setup(x => x.SaveExecutionMessages(It.IsAny(), It.IsAny>())).Verifiable(); + _mockQueueJobFailureService.Setup(x => x.QueueJobFailure(It.IsAny(), It.IsAny(), It.IsAny>())).Verifiable(); + _mockQueueDetermineOperationService.Setup(x => x.QueueDetermineOperation(It.IsAny(), It.IsAny(), It.IsAny())).Verifiable(); + + await _completionService.HandleCompletionRequest(response); + + _mockJobService.Verify(x => x.GetByWorkItemIdExcludingWorkItemsAsync(It.IsAny()), Times.Once); + _mockQueueJobFailureService.Verify(x => x.QueueJobFailure(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once); + _mockWorkItemService.Verify(x => x.GetByIdAsync(It.IsAny()), Times.Never); + _mockExecutionMessageService.Verify(x => x.SaveExecutionMessages(It.IsAny(), It.IsAny>()), Times.Never); + _mockWorkItemService.Verify(x => x.ResetCanceledJobCounter(It.IsAny()), Times.Never); + _mockJobService.Verify(x => x.UpdateAsync(It.IsAny()), Times.Never); + _mockQueueDetermineOperationService.Verify(x => x.QueueDetermineOperation(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + + [Fact] + public async Task HandleCompletionRequest_Successful_true_ResetCanceledJobCounter_CompleteJob() + { + var dispatcherReturnResponse = new DispatcherReturnResponse + { + WorkItemId = 1 + }; + var response = new Response + { + ResponsePayload = JsonConvert.SerializeObject(dispatcherReturnResponse), + Successful = true + }; + var workItemExecution = new WorkItemExecution + { + WorkItemExecutionId = 1, + WorkItemId = 1, + CompletedTimestamp = null, + Status = StatusType.Failed + }; + var workItem = new WorkItem + { + WorkItemId = 1, + CancelJobOnFailure = true, + CanceledJobCounter = 1, + CancelJobLimit = 1, + Url = "http://testing.com", + SerializedPayload = "{\"Test\":\"Test\"}", + HttpMethod = HttpMethodType.Post, + Order = 1, + WorkItemExecutions = new List + { + workItemExecution + } + }; + var job = new Job + { + JobId = 1, + WorkItems = new List + { + workItem + } + }; + + var updatedWorkItem = new WorkItem + { + WorkItemId = 1, + CancelJobOnFailure = true, + CanceledJobCounter = 0, + CancelJobLimit = 1, + Url = "http://testing.com", + SerializedPayload = "{\"Test\":\"Test\"}", + HttpMethod = HttpMethodType.Post, + Order = 1, + WorkItemExecutions = new List + { + workItemExecution + } + }; + var updatedJob = new Job + { + JobId = 1, + JobStatus = JobStatus.DeterminingOperation + }; + + _mockJobService.Setup(x => x.GetByWorkItemIdExcludingWorkItemsAsync(It.IsAny())).ReturnsAsync(job); + _mockWorkItemService.Setup(x => x.GetByIdAsync(It.IsAny())).ReturnsAsync(workItem); + _mockExecutionMessageService.Setup(x => x.SaveExecutionMessages(It.IsAny(), It.IsAny>())).Verifiable(); + _mockQueueJobFailureService.Setup(x => x.QueueJobFailure(It.IsAny(), It.IsAny(), It.IsAny>())).Verifiable(); + _mockJobService.Setup(x => x.UpdateAsync(It.IsAny())).ReturnsAsync(updatedJob); + _mockQueueDetermineOperationService.Setup(x => x.QueueDetermineOperation(It.IsAny(), It.IsAny(), It.IsAny())).Verifiable(); + + await _completionService.HandleCompletionRequest(response); + + _mockJobService.Verify(x => x.GetByWorkItemIdExcludingWorkItemsAsync(It.IsAny()), Times.Once); + _mockQueueJobFailureService.Verify(x => x.QueueJobFailure(It.IsAny(), It.IsAny(), It.IsAny>()), Times.Never); + _mockWorkItemService.Verify(x => x.GetByIdAsync(It.IsAny()), Times.Once); + _mockExecutionMessageService.Verify(x => x.SaveExecutionMessages(It.IsAny(), It.IsAny>()), Times.Once); + _mockWorkItemService.Verify(x => x.ResetCanceledJobCounter(It.IsAny()), Times.Once); + _mockJobService.Verify(x => x.UpdateAsync(It.IsAny()), Times.Once); + _mockQueueDetermineOperationService.Verify(x => x.QueueDetermineOperation(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/ExecutionServiceTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/ExecutionServiceTest.cs new file mode 100644 index 0000000..fa978b4 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/ExecutionServiceTest.cs @@ -0,0 +1,127 @@ +using System.Collections.Generic; +using System.Net.Http; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Api.Internal.Dispatcher.Services; +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +using LCU.Library.Internal.LcuHttpClient.HttpRequest; +using LCU.Api.Internal.Dispatcher.Services.QueueServices; +namespace LCU.Api.Internal.DispatcherTests.ServiceTests.DispatcherOperationServices; + +public class ExecutionServiceTest +{ + private readonly ExecutionService _executionService; + private readonly Mock _mockPostRequest; + private readonly Mock _mockWorkItemService; + private readonly Mock _mockJobService; + private readonly Mock _mockQueueCompletionService; + + public ExecutionServiceTest() + { + _mockPostRequest = new Mock(); + _mockWorkItemService = new Mock(); + _mockJobService = new Mock(); + _mockQueueCompletionService = new Mock(); + _executionService = new ExecutionService(_mockPostRequest.Object, + _mockWorkItemService.Object, + _mockJobService.Object, + _mockQueueCompletionService.Object); + } + + [Fact] + public async Task HandleExecutionRequest_Execution_Successful() + { + var workItemExecution = new WorkItemExecution + { + WorkItemExecutionId = 1, + WorkItemId = 1, + CompletedTimestamp = null, + Status = StatusType.Processing + }; + var workItem = new WorkItem + { + WorkItemId = 1, + CancelJobOnFailure = false, + Url = "http://testing.com", + SerializedPayload = "{\"Test\":\"Test\"}", + HttpMethod = HttpMethodType.Post, + WorkItemExecutions = new List + { + workItemExecution + } + }; + var job = new Job + { + JobId = 1 + }; + + var httpRequestMessage = new HttpRequestMessage(); + var httpResponseMessage = new HttpResponseMessage + { + StatusCode = System.Net.HttpStatusCode.OK + }; + + _mockWorkItemService.Setup(x => x.GetByIdAsync(It.IsAny())).ReturnsAsync(workItem); + _mockPostRequest.Setup(x => x.AsyncWithNewtonSerializedNoThrow(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(() => httpResponseMessage); + _mockJobService.Setup(x => x.GetByWorkItemIdExcludingWorkItemsAsync(It.IsAny())).ReturnsAsync(job); + _mockJobService.Setup(x => x.SaveAsync(It.IsAny())).ReturnsAsync(job); + _mockQueueCompletionService.Setup(x => x.QueueCompletion(It.IsAny(), It.IsAny>(), It.IsAny())).Verifiable(); + + await _executionService.HandleExecutionRequest(workItemExecution); + + _mockWorkItemService.Verify(x => x.GetByIdAsync(It.IsAny()), Times.Once); + _mockPostRequest.Verify(x => x.AsyncWithNewtonSerializedNoThrow(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + _mockJobService.Verify(x => x.GetByWorkItemIdExcludingWorkItemsAsync(It.IsAny()), Times.Once); + _mockJobService.Verify(x => x.UpdateAsync(It.IsAny()), Times.Once); + _mockQueueCompletionService.Verify(x => x.QueueCompletion(It.IsAny(), It.IsAny>(), It.IsAny()), Times.Never); + } + + [Fact] + public async Task HandleExecutionRequest_Execution_NOT_Successful() + { + var workItemExecution = new WorkItemExecution + { + WorkItemExecutionId = 1, + WorkItemId = 1, + CompletedTimestamp = null, + Status = StatusType.Processing + }; + var workItem = new WorkItem + { + WorkItemId = 1, + CancelJobOnFailure = false, + Url = "http://testing.com", + SerializedPayload = "{\"Test\":\"Test\"}", + HttpMethod = HttpMethodType.Post, + WorkItemExecutions = new List + { + workItemExecution + } + }; + var job = new Job + { + JobId = 1 + }; + + var httpRequestMessage = new HttpRequestMessage(); + var httpResponseMessage = new HttpResponseMessage + { + StatusCode = System.Net.HttpStatusCode.BadRequest + }; + + _mockWorkItemService.Setup(x => x.GetByIdAsync(It.IsAny())).ReturnsAsync(workItem); + _mockPostRequest.Setup(x => x.AsyncWithNewtonSerializedNoThrow(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(() => httpResponseMessage); + _mockJobService.Setup(x => x.GetByWorkItemIdExcludingWorkItemsAsync(It.IsAny())).ReturnsAsync(job); + _mockJobService.Setup(x => x.SaveAsync(It.IsAny())).ReturnsAsync(job); + _mockQueueCompletionService.Setup(x => x.QueueCompletion(It.IsAny(), It.IsAny>(), It.IsAny())).Verifiable(); + + await _executionService.HandleExecutionRequest(workItemExecution); + + _mockWorkItemService.Verify(x => x.GetByIdAsync(It.IsAny()), Times.Once); + _mockPostRequest.Verify(x => x.AsyncWithNewtonSerializedNoThrow(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + _mockJobService.Verify(x => x.GetByWorkItemIdExcludingWorkItemsAsync(It.IsAny()), Times.Once); + _mockJobService.Verify(x => x.UpdateAsync(It.IsAny()), Times.Once); + _mockQueueCompletionService.Verify(x => x.QueueCompletion(It.IsAny(), It.IsAny>(), It.IsAny()), Times.Once); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/IngestionServiceTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/IngestionServiceTest.cs new file mode 100644 index 0000000..cb7927e --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/DispatcherOperationServices/IngestionServiceTest.cs @@ -0,0 +1,643 @@ +using System.Collections.Generic; +using LCU.Api.Internal.Dispatcher.Services; +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +namespace LCU.Api.Internal.DispatcherTests.ServiceTests.DispatcherOperationServices; + +public class IngestionServiceTest +{ + private readonly IngestionService _ingestionService; + private readonly Mock _mockJobService; + private readonly Mock _mockWorkItemService; + private readonly Mock _mockJobErrorRecipientService; + private readonly Mock _mockJobTagService; + + public IngestionServiceTest() + { + _mockJobService = new Mock(); + _mockWorkItemService = new Mock(); + _mockJobErrorRecipientService = new Mock(); + _mockJobTagService = new Mock(); + _ingestionService = new IngestionService(_mockJobService.Object, + _mockWorkItemService.Object, + _mockJobErrorRecipientService.Object, + _mockJobTagService.Object); + } + + //[Fact] + //public void HandleIngestionRequest_Request_Null() + //{ + // Job request = null; + // var result = Assert.ThrowsAsync(async () => await _ingestionService.HandleIngestionRequest(request)); + + // //Saving New + // _mockJobService.Verify(x => x.SaveAsync(It.IsAny()), Times.Never); + // _mockWorkItemService.Verify(x => x.PopulateActionFieldsAndUpdateList(It.IsAny>()), Times.Never); + + // //Updating + // _mockJobService.Verify(x => x.GetByIdAsync(It.IsAny()), Times.Never); + // _mockWorkItemService.Verify(x => x.DeleteRangeAsync(It.IsAny>()), Times.Never); + // _mockWorkItemService.Verify(x => x.SaveRangeAsync(It.IsAny>()), Times.Never); + // _mockWorkItemService.Verify(x => x.PopulateActionFieldsAndUpdateList(It.IsAny>()), Times.Never); + // _mockJobErrorRecipientService.Verify(x => x.DeleteList(It.IsAny>()), Times.Never); + // _mockJobErrorRecipientService.Verify(x => x.SaveList(It.IsAny>()), Times.Never); + // _mockJobTagService.Verify(x => x.Delete(It.IsAny()), Times.Never); + // _mockJobTagService.Verify(x => x.SaveAsync(It.IsAny()), Times.Never); + //} + + //[Fact] + //public void HandleIngestionRequest_Request_SaveNewJob_SaveReturnedNull() + //{ + // Job returnJob = null; + // _mockJobService.Setup(x => x.SaveAsync(It.IsAny())).ReturnsAsync(returnJob); + + // var request = new Job + // { + // Name = "Test" + // }; + // var result = Assert.ThrowsAsync(async () => await _ingestionService.HandleIngestionRequest(request)); + + // //Saving New + // _mockJobService.Verify(x => x.SaveAsync(It.IsAny()), Times.Once); + // _mockWorkItemService.Verify(x => x.PopulateActionFieldsAndUpdateList(It.IsAny>()), Times.Never); + + // //Updating + // _mockJobService.Verify(x => x.GetByIdAsync(It.IsAny()), Times.Never); + // _mockWorkItemService.Verify(x => x.DeleteRangeAsync(It.IsAny>()), Times.Never); + // _mockWorkItemService.Verify(x => x.SaveRangeAsync(It.IsAny>()), Times.Never); + // _mockWorkItemService.Verify(x => x.PopulateActionFieldsAndUpdateList(It.IsAny>()), Times.Never); + // _mockJobErrorRecipientService.Verify(x => x.DeleteList(It.IsAny>()), Times.Never); + // _mockJobErrorRecipientService.Verify(x => x.SaveList(It.IsAny>()), Times.Never); + // _mockJobTagService.Verify(x => x.Delete(It.IsAny()), Times.Never); + // _mockJobTagService.Verify(x => x.SaveAsync(It.IsAny()), Times.Never); + //} + + //[Fact] + //public void HandleIngestionRequest_Request_SaveNewJob_UpdatedWorkItems_Null() + //{ + // var returnJob = new Job + // { + // JobId = 1, + // Name = "Test" + // }; + // _mockJobService.Setup(x => x.SaveAsync(It.IsAny())).ReturnsAsync(returnJob); + + // List returnWorkItems = null; + // _mockWorkItemService.Setup(x => x.PopulateActionFieldsAndUpdateList(It.IsAny>())).ReturnsAsync(returnWorkItems); + + // var request = new Job + // { + // Name = "Test" + // }; + // var result = Assert.ThrowsAsync(async () => await _ingestionService.HandleIngestionRequest(request)); + + // //Saving New + // _mockJobService.Verify(x => x.SaveAsync(It.IsAny()), Times.Once); + // _mockWorkItemService.Verify(x => x.PopulateActionFieldsAndUpdateList(It.IsAny>()), Times.Once); + + // //Updating + // _mockJobService.Verify(x => x.GetByIdAsync(It.IsAny()), Times.Never); + // _mockWorkItemService.Verify(x => x.DeleteRangeAsync(It.IsAny>()), Times.Never); + // _mockWorkItemService.Verify(x => x.SaveRangeAsync(It.IsAny>()), Times.Never); + // _mockJobErrorRecipientService.Verify(x => x.DeleteList(It.IsAny>()), Times.Never); + // _mockJobErrorRecipientService.Verify(x => x.SaveList(It.IsAny>()), Times.Never); + // _mockJobTagService.Verify(x => x.Delete(It.IsAny()), Times.Never); + // _mockJobTagService.Verify(x => x.SaveAsync(It.IsAny()), Times.Never); + //} + + //[Fact] + //public void HandleIngestionRequest_Request_UpdateRequest_PopulateActionFieldsAndUpdateList_ReturnsNull() + //{ + // var returnJob = new Job + // { + // JobId = 1, + // Name = "Test Job", + // WorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // }, + // new WorkItem + // { + // WorkItemId = 2, + // Name="Test WorkItem 2" + // } + // } + // }; + // _mockJobService.Setup(x => x.GetByIdAsync(It.IsAny())).ReturnsAsync(returnJob); + + // List returnWorkItems = null; + // _mockWorkItemService.Setup(x => x.PopulateActionFieldsAndUpdateList(It.IsAny>())).ReturnsAsync(returnWorkItems); + + // var request = new Job + // { + // JobId = 1, + // Name = "Test", + // WorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // } + // } + // }; + // var result = Assert.ThrowsAsync(async () => await _ingestionService.HandleIngestionRequest(request)); + + // //Saving New + // _mockJobService.Verify(x => x.SaveAsync(It.IsAny()), Times.Never); + + + // //Updating + // _mockJobService.Verify(x => x.GetByIdAsync(It.IsAny()), Times.Once); + // _mockWorkItemService.Verify(x => x.DeleteRangeAsync(It.Is>(workItems => workItems.Count == 1)), Times.Once); + // _mockWorkItemService.Verify(x => x.SaveRangeAsync(It.Is>(workItems => workItems.Count == 0)), Times.Once); + // _mockWorkItemService.Verify(x => x.PopulateActionFieldsAndUpdateList(It.IsAny>()), Times.Once); + // _mockJobErrorRecipientService.Verify(x => x.DeleteList(It.IsAny>()), Times.Never); + // _mockJobErrorRecipientService.Verify(x => x.SaveList(It.IsAny>()), Times.Never); + // _mockJobTagService.Verify(x => x.Delete(It.IsAny()), Times.Never); + // _mockJobTagService.Verify(x => x.SaveAsync(It.IsAny()), Times.Never); + //} + + //[Fact] + //public void HandleIngestionRequest_Request_UpdateRequest_UpdateJobReturns_Null() + //{ + // var returnJob = new Job + // { + // JobId = 1, + // Name = "Test Job", + // WorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // }, + // new WorkItem + // { + // WorkItemId = 2, + // Name="Test WorkItem 2" + // } + // }, + // JobErrorRecipients = new List(), + // JobTags = new List() + // }; + // _mockJobService.Setup(x => x.GetByIdAsync(It.IsAny())).ReturnsAsync(returnJob); + + // var returnWorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // } + // }; + // _mockWorkItemService.Setup(x => x.PopulateActionFieldsAndUpdateList(It.IsAny>())).ReturnsAsync(returnWorkItems); + + // Job returnUpdateJob = null; + // _mockJobService.Setup(x => x.UpdateAsync(It.IsAny())).ReturnsAsync(returnUpdateJob); + + // var request = new Job + // { + // JobId = 1, + // Name = "Test", + // WorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // } + // }, + // JobErrorRecipients = new List(), + // JobTags = new List() + // }; + // var result = Assert.ThrowsAsync(async () => await _ingestionService.HandleIngestionRequest(request)); + + // //Saving New + // _mockJobService.Verify(x => x.SaveAsync(It.IsAny()), Times.Never); + + // //Updating + // _mockJobService.Verify(x => x.GetByIdAsync(It.IsAny()), Times.Once); + // _mockWorkItemService.Verify(x => x.DeleteRangeAsync(It.Is>(workItems => workItems.Count == 1)), Times.Once); + // _mockWorkItemService.Verify(x => x.SaveRangeAsync(It.Is>(workItems => workItems.Count == 0)), Times.Once); + // _mockWorkItemService.Verify(x => x.PopulateActionFieldsAndUpdateList(It.IsAny>()), Times.Once); + // _mockJobErrorRecipientService.Verify(x => x.DeleteList(It.Is>(jobErrorRecipient => jobErrorRecipient.Count == 0)), Times.Once); + // _mockJobErrorRecipientService.Verify(x => x.SaveList(It.Is>(jobErrorRecipient => jobErrorRecipient.Count == 0)), Times.Once); + // _mockJobTagService.Verify(x => x.Delete(It.IsAny()), Times.Never); + // _mockJobTagService.Verify(x => x.SaveAsync(It.IsAny()), Times.Never); + //} + + //[Fact] + //public async Task HandleIngestionRequest_Request_UpdateRequest_UpdateJobReturns() + //{ + // var returnJob = new Job + // { + // JobId = 1, + // Name = "Test Job", + // WorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // }, + // new WorkItem + // { + // WorkItemId = 2, + // Name="Test WorkItem 2" + // } + // }, + // JobErrorRecipients = new List(), + // JobTags = new List() + // }; + // _mockJobService.Setup(x => x.GetByIdAsync(It.IsAny())).ReturnsAsync(returnJob); + + // var returnWorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // } + // }; + // _mockWorkItemService.Setup(x => x.PopulateActionFieldsAndUpdateList(It.IsAny>())).ReturnsAsync(returnWorkItems); + + // var returnUpdateJob = new Job + // { + // JobId = 1, + // Name = "Test", + // WorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // } + // }, + // JobErrorRecipients = new List(), + // JobTags = new List() + // }; + // _mockJobService.Setup(x => x.UpdateAsync(It.IsAny())).ReturnsAsync(returnUpdateJob); + + // var request = new Job + // { + // JobId = 1, + // Name = "Test", + // WorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // } + // }, + // JobErrorRecipients = new List(), + // JobTags = new List() + // }; + // await _ingestionService.HandleIngestionRequest(request); + + // //Saving New + // _mockJobService.Verify(x => x.SaveAsync(It.IsAny()), Times.Never); + + // //Updating + // _mockJobService.Verify(x => x.GetByIdAsync(It.IsAny()), Times.Once); + // _mockWorkItemService.Verify(x => x.DeleteRangeAsync(It.Is>(workItems => workItems.Count == 1)), Times.Once); + // _mockWorkItemService.Verify(x => x.SaveRangeAsync(It.Is>(workItems => workItems.Count == 0)), Times.Once); + // _mockWorkItemService.Verify(x => x.PopulateActionFieldsAndUpdateList(It.IsAny>()), Times.Once); + // _mockJobErrorRecipientService.Verify(x => x.DeleteList(It.Is>(jobErrorRecipient => jobErrorRecipient.Count == 0)), Times.Once); + // _mockJobErrorRecipientService.Verify(x => x.SaveList(It.Is>(jobErrorRecipient => jobErrorRecipient.Count == 0)), Times.Once); + // _mockJobTagService.Verify(x => x.Delete(It.IsAny()), Times.Never); + // _mockJobTagService.Verify(x => x.SaveAsync(It.IsAny()), Times.Never); + //} + + //[Fact] + //public async Task HandleIngestionRequest_Request_UpdateRequest_Remove_WorkItems_JobErrorRecipients_JobTags() + //{ + // var returnJob = new Job + // { + // JobId = 1, + // Name = "Test Job", + // WorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // }, + // new WorkItem + // { + // WorkItemId = 2, + // Name="Test WorkItem 2" + // } + // }, + // JobErrorRecipients = new List + // { + // new JobErrorRecipient + // { + // JobErrorRecipientId = 3, + // Recipient = "Test@test.com" + // }, + // new JobErrorRecipient + // { + // JobErrorRecipientId = 4, + // Recipient = "Test2@test.com" + // } + // }, + // JobTags = new List + // { + // new JobTag + // { + // JobId= 5, + // TagId = 6, + // }, + // new JobTag + // { + // JobId= 7, + // TagId = 8, + // } + // } + // }; + // _mockJobService.Setup(x => x.GetByIdAsync(It.IsAny())).ReturnsAsync(returnJob); + + // var returnWorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // } + // }; + // _mockWorkItemService.Setup(x => x.PopulateActionFieldsAndUpdateList(It.IsAny>())).ReturnsAsync(returnWorkItems); + + // var returnUpdateJob = new Job + // { + // JobId = 1, + // Name = "Test", + // WorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // } + // }, + // JobErrorRecipients = new List + // { + // new JobErrorRecipient + // { + // JobErrorRecipientId = 3, + // Recipient = "Test@test.com" + // } + // }, + // JobTags = new List + // { + // new JobTag + // { + // JobId= 5, + // TagId = 6, + // } + // } + // }; + // _mockJobService.Setup(x => x.UpdateAsync(It.IsAny())).ReturnsAsync(returnUpdateJob); + + // var request = new Job + // { + // JobId = 1, + // Name = "Test", + // WorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // } + // }, + // JobErrorRecipients = new List + // { + // new JobErrorRecipient + // { + // JobErrorRecipientId = 3, + // Recipient = "Test@test.com" + // } + // }, + // JobTags = new List + // { + // new JobTag + // { + // JobId= 5, + // TagId = 6, + // } + // } + // }; + // await _ingestionService.HandleIngestionRequest(request); + + // //Saving New + // _mockJobService.Verify(x => x.SaveAsync(It.IsAny()), Times.Never); + + // //Updating + // _mockJobService.Verify(x => x.GetByIdAsync(It.IsAny()), Times.Once); + // _mockWorkItemService.Verify(x => x.DeleteRangeAsync(It.Is>(workItems => workItems.Count == 1)), Times.Once); + // _mockWorkItemService.Verify(x => x.SaveRangeAsync(It.Is>(workItems => workItems.Count == 0)), Times.Once); + // _mockWorkItemService.Verify(x => x.PopulateActionFieldsAndUpdateList(It.IsAny>()), Times.Once); + // _mockJobErrorRecipientService.Verify(x => x.DeleteList(It.Is>(jobErrorRecipient => jobErrorRecipient.Count == 1)), Times.Once); + // _mockJobErrorRecipientService.Verify(x => x.SaveList(It.Is>(jobErrorRecipient => jobErrorRecipient.Count == 0)), Times.Once); + // _mockJobTagService.Verify(x => x.Delete(It.IsAny()), Times.Once); + // _mockJobTagService.Verify(x => x.SaveAsync(It.IsAny()), Times.Never); + //} + + //[Fact] + //public async Task HandleIngestionRequest_Request_UpdateRequest_Add_WorkItems_JobErrorRecipients_JobTags() + //{ + // var returnJob = new Job + // { + // JobId = 1, + // Name = "Test Job", + // WorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // }, + // new WorkItem + // { + // WorkItemId = 2, + // Name="Test WorkItem 2" + // } + // }, + // JobErrorRecipients = new List + // { + // new JobErrorRecipient + // { + // JobErrorRecipientId = 3, + // Recipient = "Test@test.com" + // }, + // new JobErrorRecipient + // { + // JobErrorRecipientId = 4, + // Recipient = "Test2@test.com" + // } + // }, + // JobTags = new List + // { + // new JobTag + // { + // JobId= 5, + // TagId = 6, + // }, + // new JobTag + // { + // JobId= 7, + // TagId = 8, + // } + // } + // }; + // _mockJobService.Setup(x => x.GetByIdAsync(It.IsAny())).ReturnsAsync(returnJob); + + // var returnWorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // } + // }; + // _mockWorkItemService.Setup(x => x.PopulateActionFieldsAndUpdateList(It.IsAny>())).ReturnsAsync(returnWorkItems); + + // var returnUpdateJob = new Job + // { + // JobId = 1, + // Name = "Test", + // WorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // }, + // new WorkItem + // { + // WorkItemId = 2, + // Name="Test WorkItem 2" + // }, + // new WorkItem + // { + // WorkItemId = 3, + // Name="Test WorkItem 3" + // } + // }, + // JobErrorRecipients = new List + // { + // new JobErrorRecipient + // { + // JobErrorRecipientId = 3, + // Recipient = "Test@test.com" + // }, + // new JobErrorRecipient + // { + // JobErrorRecipientId = 4, + // Recipient = "Test2@test.com" + // }, + // new JobErrorRecipient + // { + // JobErrorRecipientId = 5, + // Recipient = "Test3@test.com" + // } + // }, + // JobTags = new List + // { + // new JobTag + // { + // JobId= 5, + // TagId = 6, + // }, + // new JobTag + // { + // JobId= 7, + // TagId = 8, + // }, + // new JobTag + // { + // JobId= 9, + // TagId = 10, + // } + // } + // }; + // _mockJobService.Setup(x => x.UpdateAsync(It.IsAny())).ReturnsAsync(returnUpdateJob); + + // var request = new Job + // { + // JobId = 1, + // Name = "Test", + // WorkItems = new List + // { + // new WorkItem + // { + // WorkItemId = 1, + // Name="Test WorkItem 1" + // }, + // new WorkItem + // { + // WorkItemId = 2, + // Name="Test WorkItem 2" + // }, + // new WorkItem + // { + // Name="Test WorkItem 3" + // } + // }, + // JobErrorRecipients = new List + // { + // new JobErrorRecipient + // { + // JobErrorRecipientId = 3, + // Recipient = "Test@test.com" + // }, + // new JobErrorRecipient + // { + // JobErrorRecipientId = 4, + // Recipient = "Test2@test.com" + // }, + // new JobErrorRecipient + // { + + // Recipient = "Test3@test.com" + // } + // }, + // JobTags = new List + // { + // new JobTag + // { + // JobId= 5, + // TagId = 6, + // }, + // new JobTag + // { + // JobId= 7, + // TagId = 8, + // }, + // new JobTag + // { + // JobId= 9, + // TagId = 10, + // } + // } + // }; + // await _ingestionService.HandleIngestionRequest(request); + + // //Saving New + // _mockJobService.Verify(x => x.SaveAsync(It.IsAny()), Times.Never); + + // //Updating + // _mockJobService.Verify(x => x.GetByIdAsync(It.IsAny()), Times.Once); + // _mockWorkItemService.Verify(x => x.DeleteRangeAsync(It.Is>(workItems => workItems.Count == 0)), Times.Once); + // _mockWorkItemService.Verify(x => x.SaveRangeAsync(It.Is>(workItems => workItems.Count == 1)), Times.Once); + // _mockWorkItemService.Verify(x => x.PopulateActionFieldsAndUpdateList(It.IsAny>()), Times.Once); + // _mockJobErrorRecipientService.Verify(x => x.DeleteList(It.Is>(jobErrorRecipient => jobErrorRecipient.Count == 0)), Times.Once); + // _mockJobErrorRecipientService.Verify(x => x.SaveList(It.Is>(jobErrorRecipient => jobErrorRecipient.Count == 1)), Times.Once); + // _mockJobTagService.Verify(x => x.Delete(It.IsAny()), Times.Never); + // _mockJobTagService.Verify(x => x.SaveAsync(It.IsAny()), Times.Once); + //} +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/JobServiceTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/JobServiceTest.cs new file mode 100644 index 0000000..dc987bc --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/JobServiceTest.cs @@ -0,0 +1,990 @@ +using LCU.Api.Internal.Dispatcher.Configurations; +using LCU.Api.Internal.Dispatcher.Services; +using LCU.Api.Internal.DispatcherLib.Repositories; +using LCU.Api.Internal.DispatcherSharedLib.Utilities; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.DispatcherModels.Models.Requests; +using LCU.Library.Internal.DispatcherModels.Models.Results; +using NCrontab; +using System.Collections.Generic; + +namespace LCU.Api.Internal.DispatcherTests.ServiceTests; + +public class JobServiceTest +{ + private readonly JobService _jobService; + private readonly Mock _mockUnitOfWork; + private readonly Mock _mockJobRepository; + private readonly Mock _mockWorkItemService; + private readonly Mock _mockWorkItemExecutionService; + private readonly ApiConfiguration _apiConfiguration; + + public JobServiceTest() + { + _mockJobRepository = new Mock(); + _mockWorkItemService = new Mock(); + _mockWorkItemExecutionService = new Mock(); + _mockUnitOfWork = new Mock(); + + _apiConfiguration = new ApiConfiguration() + { + NumberOfWorkItemExecutions = 10, + NumberOfExecutionMessages = 20, + ProcessingLimitInMinutes = 30, + JobStatusesToMonitorByTimeLimit = new List() { JobStatus.Processing } + }; + + + _jobService = new JobService(_mockUnitOfWork.Object, + _mockJobRepository.Object, + _mockWorkItemService.Object, + _mockWorkItemExecutionService.Object, + _apiConfiguration); + } + + [Fact] + public async Task GetListAsync_Success() + { + // Arrange + _mockJobRepository.Setup(repo => repo.ListAsync(10, 20)).ReturnsAsync(new List() + { + new Job(), + new Job() + }); + + // Act + var result = await _jobService.GetAsync(); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count()); + } + + [Fact] + public async Task GetByIdAsync_Success() + { + // Arrange + _mockJobRepository.Setup(repo => repo.FindByIdAsync(It.IsAny(), 10, 20)).ReturnsAsync(new Job()); + + // Act + var result = await _jobService.GetByIdAsync(1); + + // Assert + Assert.NotNull(result); + } + + [Fact] + public async Task SaveAsync_Success() + { + // Arrange + _mockJobRepository.Setup(repo => repo.AddAsync(It.IsAny())).ReturnsAsync(new Job()); + + // Act + var result = await _jobService.SaveAsync(new Job()); + + // Assert + Assert.NotNull(result); + _mockJobRepository.Verify(x => x.AddAsync(It.IsAny()), Times.Once); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); + } + + [Fact] + public async Task UpdateAsync_Success() + { + // Arrange + var jobToUpdate = new Job() + { + JobId = 1, + RequestingApplication = "App" + }; + + _mockJobRepository.Setup(repo => repo.Update(It.IsAny())).ReturnsAsync(jobToUpdate).Verifiable(); + + // Act + var result = await _jobService.UpdateAsync(jobToUpdate); + + // Assert + Assert.NotNull(result); + Assert.Equal(jobToUpdate.JobId, result.JobId); + Assert.Equal(jobToUpdate.RequestingApplication, result.RequestingApplication); + _mockJobRepository.Verify(x => x.FindByIdAsync(It.IsAny(), 10, 20), Times.Never); + _mockJobRepository.Verify(x => x.Update(It.IsAny()), Times.Once); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); + } + + [Fact] + public async Task CompleteJobAndWorkItem_Success_NoReschedule() + { + // Arrange + var jobToUpdate = new Job() + { + JobId = 1, + RequestingApplication = "App", + NextRunTime = Convert.ToDateTime("1/1/2022") + }; + + _mockJobRepository.Setup(repo => repo.FindByIdExcludingWorkItemsAsync(1)).ReturnsAsync(jobToUpdate); + var updatedJob = new Job() + { + JobId = 1, + RequestingApplication = "App", + NextRunTime = null + }; + _mockJobRepository.Setup(repo => repo.Update(It.IsAny())).ReturnsAsync(updatedJob).Verifiable(); + + var updatedWorkitemExecution = new WorkItemExecution() + { + WorkItemExecutionId = 1, + Status = StatusType.Completed + }; + _mockWorkItemExecutionService.Setup(repo => repo.UpdateCompleteWorkItemExecutionWithoutSave(It.IsAny(), StatusType.Completed)).ReturnsAsync(updatedWorkitemExecution); + + _mockUnitOfWork.Setup(uow => uow.CompleteAsync()).Returns(Task.CompletedTask); + + // Act + await _jobService.CompleteJobAndWorkItem(jobToUpdate, new WorkItemExecution(100), StatusType.Completed); + + // Assert + _mockJobRepository.Verify(x => x.FindByIdExcludingWorkItemsAsync(1), Times.Never); + _mockJobRepository.Verify(x => x.Update(It.IsAny()), Times.Once); + _mockWorkItemExecutionService.Verify(x => x.UpdateCompleteWorkItemExecutionWithoutSave(It.IsAny(), It.IsAny()), Times.Once); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); + } + + [Fact] + public async Task CompleteJobAndWorkItem_Success_Reschedule() + { + // Arrange + var jobToUpdate = new Job() + { + JobId = 1, + RequestingApplication = "App", + NextRunTime = Convert.ToDateTime("1/1/2022"), + CronSchedule = "5 * * * *" + }; + + _mockJobRepository.Setup(repo => repo.FindByIdExcludingWorkItemsAsync(1)).ReturnsAsync(jobToUpdate); + var updatedJob = new Job() + { + JobId = 1, + RequestingApplication = "App", + NextRunTime = DateTime.Now.AddSeconds(5) + }; + _mockJobRepository.Setup(repo => repo.Update(It.IsAny())).ReturnsAsync(updatedJob).Verifiable(); + + var updatedWorkitemExecution = new WorkItemExecution() + { + WorkItemExecutionId = 1, + Status = StatusType.Completed + }; + _mockWorkItemExecutionService.Setup(repo => repo.UpdateCompleteWorkItemExecutionWithoutSave(It.IsAny(), It.IsAny())).ReturnsAsync(updatedWorkitemExecution); + + _mockUnitOfWork.Setup(uow => uow.CompleteAsync()).Returns(Task.CompletedTask); + + // Act + await _jobService.CompleteJobAndWorkItem(jobToUpdate, new WorkItemExecution(100), StatusType.Completed); + + // Assert + _mockJobRepository.Verify(x => x.FindByIdExcludingWorkItemsAsync(1), Times.Never); + _mockJobRepository.Verify(x => x.Update(It.IsAny()), Times.Once); + _mockWorkItemExecutionService.Verify(x => x.UpdateCompleteWorkItemExecutionWithoutSave(It.IsAny(), It.IsAny()), Times.Once); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); + } + + [Fact] + public void HolidayHelper_Success_SkipMemorialDayHolidayNextOccurrence() + { + // Arrange + var jobToUpdate = new Job() + { + JobId = 1, + SkipHolidays = true, + RequestingApplication = "App", + CronSchedule = "0 0 1-30 5 *" + }; + + CrontabSchedule schedule = CrontabSchedule.Parse(jobToUpdate.CronSchedule); + var nextRunTime = schedule.GetNextOccurrence(DateTime.Now); + + var memorialDay = HolidayHelpers.GetMemorialDay(nextRunTime.Year); //stage next possible holiday date, reset cron + jobToUpdate.NextRunTime = memorialDay; + jobToUpdate.CronSchedule = $"0 0 {memorialDay.Day} {memorialDay.Month} *"; + + // Assert + Assert.NotNull(jobToUpdate); + Assert.Equal(memorialDay, jobToUpdate.NextRunTime); + } + + [Fact] + public void HolidayHelper_Success_SkipThanksGivingHolidayNextOccurrence() + { + // Arrange + var jobToUpdate = new Job() + { + JobId = 1, + SkipHolidays = true, + RequestingApplication = "App", + CronSchedule = "0 0 22-30 11 *" + }; + + CrontabSchedule schedule = CrontabSchedule.Parse(jobToUpdate.CronSchedule); + var nextRunTime = schedule.GetNextOccurrence(DateTime.Now); + + var thanksgivingDay = HolidayHelpers.GetThanksgivingDay(nextRunTime.Year); //stage next possible holiday date, reset cron + jobToUpdate.NextRunTime = thanksgivingDay; + jobToUpdate.CronSchedule = $"0 0 {thanksgivingDay.Day} {thanksgivingDay.Month} *"; + + // Assert + Assert.NotNull(jobToUpdate); + Assert.Equal(thanksgivingDay, jobToUpdate.NextRunTime); + } + + [Fact] + public void HolidayHelper_Success_SkipIndependenceDayHolidayNextOccurrence() + { + // Arrange + var jobToUpdate = new Job() + { + JobId = 1, + SkipHolidays = true, + RequestingApplication = "App", + CronSchedule = "0 0 4-31 7 *" + }; + + CrontabSchedule schedule = CrontabSchedule.Parse(jobToUpdate.CronSchedule); + var nextRunTime = schedule.GetNextOccurrence(DateTime.Now); + + CronHelpers.SetNextScheduledRunTime(ref jobToUpdate); + var nextRunTimeAfterHoliday = HolidayHelpers.HandleHolidays(nextRunTime); + + // Assert + Assert.NotNull(jobToUpdate); + Assert.Equal(nextRunTimeAfterHoliday, jobToUpdate.NextRunTime); + } + + [Fact] + public void HolidayHelper_Success_SkipLaborDayHolidayNextOccurrence() + { + // Arrange + var jobToUpdate = new Job() + { + JobId = 1, + SkipHolidays = true, + RequestingApplication = "App", + CronSchedule = "0 0 1-30 9 *" + }; + + CrontabSchedule schedule = CrontabSchedule.Parse(jobToUpdate.CronSchedule); + var nextRunTime = schedule.GetNextOccurrence(DateTime.Now); + + var laborDay = HolidayHelpers.GetLaborDay(nextRunTime.Year); //stage next possible holiday date, reset cron + jobToUpdate.NextRunTime = laborDay; + jobToUpdate.CronSchedule = $"0 0 {laborDay.Day} {laborDay.Month} *"; + var nextRunTimeAfterHoliday = HolidayHelpers.GetLaborDay(nextRunTime.Year); + + // Assert + Assert.NotNull(jobToUpdate); + Assert.Equal(nextRunTimeAfterHoliday, jobToUpdate.NextRunTime); + } + + [Fact] + public void GetJobsProcessingLongerThanTime_Return_OneProcessingJob_LongerThen_30minutes() + { + // Arrange + var jobs = new List() + { + new Job() + { + Name = "Test Process Job", + NextRunTime = DateTime.Now.AddHours(-1), + WorkItems = new List() { + new WorkItem() + { + Name = "Test Process WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Processing, + StartedTimestamp = DateTime.Now.AddMinutes(-31) + } + } + } + }, + JobStatus = JobStatus.Processing + } + }; + + // Act + var result = _jobService.GetJobsInStatusLongerThanTimeLimit(jobs, 30); + + // Assert + Assert.NotNull(result); + Assert.Single(result); + } + + [Fact] + public void GetJobsProcessingLongerThanTime_Return_NoneProcessingJob_NotLongerThen_30minutes() + { + // Arrange + var jobs = new List() + { + new Job() + { + Name = "Test Failed Job", + NextRunTime = DateTime.Now.AddHours(-1), + JobStatus = JobStatus.Failed, + WorkItems = new List() { + new WorkItem() + { + Name = "Test Failed WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Failed, + StartedTimestamp = DateTime.Now.AddMinutes(-10) + } + } + } + } + }, + new Job() + { + Name = "Test Process Job", + NextRunTime = DateTime.Now.AddHours(-1), + JobStatus = JobStatus.Processing, + WorkItems = new List() { + new WorkItem() + { + Name = "Test Process WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Processing, + StartedTimestamp = DateTime.Now.AddMinutes(-29) + } + } + } + } + } + }; + + // Act + var result = _jobService.GetJobsInStatusLongerThanTimeLimit(jobs, 30); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + } + + [Fact] + public void GetJobsProcessingLongerThanTime_Return_NoneProcessingJob_NoneAreProcessing() + { + // Arrange + var jobs = new List() + { + new Job() + { + Name = "Test Failed Job", + NextRunTime = DateTime.Now.AddHours(-1), + JobStatus = JobStatus.Failed, + WorkItems = new List() { + new WorkItem() + { + Name = "Test Failed WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Failed, + StartedTimestamp = DateTime.Now.AddMinutes(-5) + } + } + } + } + }, + new Job() + { + Name = "Test Failed 2 Job", + NextRunTime = DateTime.Now.AddHours(-1), + JobStatus = JobStatus.Processing, + WorkItems = new List() { + new WorkItem() + { + Name = "Test Failed 2 WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Failed, + StartedTimestamp = DateTime.Now.AddMinutes(-29) + } + } + } + } + } + }; + + // Act + var result = _jobService.GetJobsInStatusLongerThanTimeLimit(jobs, 30); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + } + + [Fact] + public void GetJobsProcessingLongerThanTime_Return_TwoJobs_MultipleStatuses() + { + // Arrange + var jobs = new List() + { + new Job() + { + Name = "Test Failed Job", + NextRunTime = DateTime.Now.AddHours(-1), + JobStatus = JobStatus.DeterminingOperation, + WorkItems = new List() { + new WorkItem() + { + Name = "Test Failed WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Failed, + StartedTimestamp = Convert.ToDateTime("4/1/2021") + } + } + } + } + }, + new Job() + { + Name = "Test Failed 2 Job", + NextRunTime = DateTime.Now.AddHours(-1), + JobStatus = JobStatus.Processing, + WorkItems = new List() { + new WorkItem() + { + Name = "Test Failed 2 WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Failed, + StartedTimestamp = DateTime.Now.AddMinutes(-31) + } + } + } + } + } + }; + + // Act + var result = _jobService.GetJobsInStatusLongerThanTimeLimit(jobs, 30); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count()); + } + + [Fact] + public async Task GetErroredAndProcessingJobs_Return_TwoJobs_OneFailed_OneProcessingLongerThen_30minutes() + { + var jobs = new List() + { + new Job() + { + Name = "Test Pending Job", + NextRunTime = DateTime.Now.AddHours(-1), + WorkItems = new List() { + new WorkItem() + { + Name = "Test Pending WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Pending, + StartedTimestamp = Convert.ToDateTime("4/1/2021") + } + } + } + }, + JobStatus = JobStatus.Failed + }, + new Job() + { + Name = "Test Failed Job", + NextRunTime = DateTime.Now.AddHours(-1), + WorkItems = new List() { + new WorkItem() + { + Name = "Test Failed WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Failed, + StartedTimestamp = Convert.ToDateTime("4/1/2021") + } + } + } + }, + JobStatus = JobStatus.Failed + }, + new Job() + { + Name = "Test Process Job", + NextRunTime = DateTime.Now.AddHours(-1), + WorkItems = new List() { + new WorkItem() + { + Name = "Test Process WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Processing, + StartedTimestamp = DateTime.Now.AddMinutes(-31) + } + } + } + }, + JobStatus = JobStatus.Processing + } + }; + + _mockJobRepository.Setup(repo => repo.FindJobsPastDateByStatuses(It.IsAny(), 10, 20, It.IsAny>())).ReturnsAsync(jobs); + + // Act + var result = await _jobService.GetErrorMonitoringJobs(); + + // Assert + Assert.NotNull(result); + Assert.Equal(3, result.ToList().Count); + Assert.Single(result.Where(r => r.CurrentStatus == StatusType.Failed)); + Assert.Single(result.Where(r => r.CurrentStatus == StatusType.Processing)); + } + + [Fact] + public async Task GetErroredAndProcessingJobs_Return_OneFailedJob() + { + var jobs = new List() + { + new Job() + { + Name = "Test Pending Job", + NextRunTime = DateTime.Now.AddHours(-1), + WorkItems = new List() { + new WorkItem() + { + Name = "Test Pending WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Pending, + StartedTimestamp = DateTime.Now.AddMinutes(-5) + } + } + } + }, + JobStatus = JobStatus.PendingToRun + }, + new Job() + { + Name = "Test Failed Job", + NextRunTime = DateTime.Now.AddHours(-1), + WorkItems = new List() { + new WorkItem() + { + Name = "Test Failed WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Failed, + StartedTimestamp = Convert.ToDateTime("4/1/2021") + } + } + } + }, + JobStatus = JobStatus.Failed + }, + new Job() + { + Name = "Test Process Job", + NextRunTime = DateTime.Now.AddHours(-1), + WorkItems = new List() { + new WorkItem() + { + Name = "Test Process WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Processing, + StartedTimestamp = DateTime.Now.AddMinutes(-5) + } + } + } + }, + JobStatus = JobStatus.Inactive + } + }; + + _mockJobRepository.Setup(repo => repo.FindJobsPastDateByStatuses(It.IsAny(), 10, 20, It.IsAny>())).ReturnsAsync(jobs); + + // Act + var result = await _jobService.GetErrorMonitoringJobs(); + + // Assert + Assert.NotNull(result); + Assert.Single(result); + Assert.Single(result.Where(r => r.CurrentStatus == StatusType.Failed)); + Assert.Empty(result.Where(r => r.CurrentStatus == StatusType.Processing)); + } + + [Fact] + public async Task GetErroredAndProcessingJobs_Return_Empty() + { + var jobs = new List() + { + new Job() + { + Name = "Test Pending Job", + NextRunTime = DateTime.Now.AddHours(-1), + WorkItems = new List() { + new WorkItem() + { + Name = "Test Pending WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Pending, + StartedTimestamp = DateTime.Now.AddMinutes(-12) + } + } + } + } + }, + new Job() + { + Name = "Test Process 1 Job", + NextRunTime = DateTime.Now.AddHours(-1), + WorkItems = new List() { + new WorkItem() + { + Name = "Test Process 1 WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Processing, + StartedTimestamp = DateTime.Now.AddMinutes(-2) + } + } + } + } + }, + new Job() + { + Name = "Test Process 2 Job", + NextRunTime = DateTime.Now.AddHours(-1), + WorkItems = new List() { + new WorkItem() + { + Name = "Test Process 2 WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Processing, + StartedTimestamp = DateTime.Now.AddMinutes(-5) + } + } + } + } + } + }; + + _mockJobRepository.Setup(repo => repo.FindJobsPastDateByStatuses(It.IsAny(), 10, 20, It.IsAny>())).ReturnsAsync(jobs); + + // Act + var result = await _jobService.GetErrorMonitoringJobs(); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + } + + [Fact] + public async Task FilterPaged_Ok() + { + var pagedJobResult = new PagedResult(); + + var filter = new Filter() + { + Page = 1, + Limit = 2, + StatusFilters = null, + SortableFields = null, + TagIdFilters = new List { 1 } + }; + + _mockJobRepository.Setup(repo => repo.FilteredListAsync(filter, _apiConfiguration.NumberOfWorkItemExecutions, _apiConfiguration.NumberOfExecutionMessages)) + .ReturnsAsync(pagedJobResult).Verifiable(); + + // Act + var result = await _jobService.GetFilteredJobs(filter); + + // Assert + Assert.NotNull(result); + Assert.Equal(pagedJobResult, result); + } + + [Fact] + public async Task DeleteAsync_Success() + { + var jobReturned = new Job + { + JobId = 1, + WorkItems = new List + { + new WorkItem + { + WorkItemId = 1 + }, + new WorkItem + { + WorkItemId = 2 + } + } + }; + _mockJobRepository.Setup(repo => repo.FindByIdAsync(1, 10, 20)).ReturnsAsync(jobReturned); + + // Act + var jobIdToDelete = 1; + await _jobService.DeleteAsync(jobIdToDelete); + + // Assert + _mockJobRepository.Verify(service => service.FindByIdAsync(It.Is(x => x == 1), It.Is(x => x == 10), It.Is(x => x == 20)), Times.Once); + _mockWorkItemService.Verify(service => service.DeleteList(It.Is>(workItems => workItems.Count == 2)), Times.Never); + } + + [Fact] + public async Task GetJobsReadyToBeScheduled_Return_Empty() + { + var jobs = new List(); + + _mockJobRepository.Setup(repo => repo.FindJobsReadyToBeScheduled(It.IsAny())).ReturnsAsync(jobs); + + // Act + var result = await _jobService.GetJobsReadyToBeScheduled(); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + } + + [Fact] + public async Task GetJobsReadyToBeScheduled_Returns_One() + { + var jobs = new List + { + new Job + { + JobId = 1, + } + }; + + _mockJobRepository.Setup(repo => repo.FindJobsReadyToBeScheduled(It.IsAny())).ReturnsAsync(jobs); + + // Act + var result = await _jobService.GetJobsReadyToBeScheduled(); + + // Assert + Assert.NotNull(result); + Assert.Single(result); + } + + [Fact] + public async Task GetInactiveAsync_Success() + { + // Arrange + _mockJobRepository.Setup(repo => repo.ListInactiveAsync(10, 20)).ReturnsAsync(new List() + { + new Job(), + new Job() + }); + + // Act + var result = await _jobService.GetInactiveAsync(); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count()); + } + + [Fact] + public async Task GetSoftDeletedAsync_Success() + { + // Arrange + _mockJobRepository.Setup(repo => repo.ListSoftDeletedTrackedAsync(10, 20, It.IsAny())).ReturnsAsync(new List() + { + new Job(), + new Job() + }); + + // Act + var result = await _jobService.GetSoftDeletedTrackedAsync(DateTime.Now.AddDays(-1)); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count()); + } + + [Fact] + public async Task RemoveSoftDeleteById_Success() + { + // Arrange + var existingJob = new Job() + { + JobId = 100, + SoftDeleteDate = DateTime.Now, + }; + + _mockJobRepository.Setup(repo => repo.FindByIdAsync(It.IsAny(), 10, 20)).ReturnsAsync(existingJob); + _mockJobRepository.Setup(repo => repo.Update(It.IsAny())).Verifiable(); + + // Act + var result = await _jobService.RemoveSoftDeleteById(100); + + // Assert + Assert.NotNull(result); + Assert.Equal(existingJob.JobId, result.JobId); + Assert.Null(result.SoftDeleteDate); + _mockJobRepository.Verify(x => x.FindByIdAsync(It.IsAny(), 10, 20), Times.Once); + _mockJobRepository.Verify(x => x.Update(It.IsAny()), Times.Once); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); + } + + [Fact] + public void RemoveSoftDeleteById_Throws() + { + // Arrange + _mockJobRepository.Setup(repo => repo.FindByIdAsync(It.IsAny(), 10, 20)).ReturnsAsync((Job)null); + + // Act/Assert + var result = Assert.ThrowsAsync(async () => await _jobService.RemoveSoftDeleteById(100)); + } + + [Fact] + public async Task GetInactiveExcludeSoftDeletedAsync_Success() + { + // Arrange + _mockJobRepository.Setup(repo => repo.ListInactiveExcludeSoftDeletedTrackedAsync(10, 20)).ReturnsAsync(new List() + { + new Job(), + new Job() + }); + + // Act + var result = await _jobService.GetInactiveExcludeSoftDeletedTrackedAsync(); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count()); + } + + [Fact] + public async void CopyJobAsync_Success_KeepEnvironment() + { + var job = new Job + { + JobId = DateTime.Now.Millisecond, + CronSchedule = new Guid().ToString(), + NextRunTime = DateTime.Now.AddHours(1) + }; + + var request = new CopyJobRequest + { + JobId = job.JobId, + ChangeEnvironment = false + }; + + var newJob = new Job + { + JobId = DateTime.Now.Millisecond + 1 + }; + + _mockJobRepository.Setup(x => x.FindByIdAsync(job.JobId, 10, 20)) + .ReturnsAsync(job).Verifiable(); + _mockJobRepository.Setup(x => x.AddAsync(It.IsAny())) + .ReturnsAsync(newJob).Verifiable(); + + var result = await _jobService.CopyJobAsync(request); + Assert.NotNull(result); + Assert.Null(result.CronSchedule); + Assert.Null(result.NextRunTime); + Assert.NotEqual(request.JobId, result.JobId); + Assert.Equal(newJob.JobId, result.JobId); + _mockJobRepository.Verify(x => x.FindByIdAsync(It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Once()); + _mockJobRepository.Verify(x => x.AddAsync(It.IsAny()), Times.Once); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); + } + + [Fact] + public async void CopyJobAsync_Success_ChangeEnvironment() + { + var job = new Job + { + JobId = DateTime.Now.Millisecond, + CronSchedule = new Guid().ToString(), + NextRunTime = DateTime.Now.AddHours(1) + }; + + var request = new CopyJobRequest + { + JobId = job.JobId, + ChangeEnvironment = true + }; + + _mockJobRepository.Setup(x => x.FindByIdAsync(job.JobId, 10, 20)) + .ReturnsAsync(job).Verifiable(); + + var result = await _jobService.CopyJobAsync(request); + Assert.NotNull(result); + Assert.Null(result.CronSchedule); + Assert.Null(result.NextRunTime); + Assert.Equal(0, result.JobId); + _mockJobRepository.Verify(x => x.FindByIdAsync(It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Once()); + _mockJobRepository.Verify(x => x.AddAsync(It.IsAny()), Times.Never); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Never); + } + + [Fact] + public void CopyJobAsync_Failure_NotFound() + { + var request = new CopyJobRequest + { + JobId = DateTime.Now.Millisecond + }; + + Job? jobReturned = null; + + _mockJobRepository.Setup(x => x.FindByIdAsync(request.JobId, 10, 20)) + .ReturnsAsync(jobReturned).Verifiable(); + + Assert.ThrowsAsync(async () => await _jobService.CopyJobAsync(request)); + + _mockJobRepository.Verify(x => x.FindByIdAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Never); + } + + [Fact] + public void CopyJobAsync_Failure_Exception() + { + _mockJobRepository.Setup(x => x.FindByIdAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .ThrowsAsync(new Exception("Test")).Verifiable(); + + Assert.ThrowsAsync(async () => + await _jobService.CopyJobAsync(It.IsAny())); + + _mockJobRepository.Verify(x => x.FindByIdAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Never); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/JobTagServiceTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/JobTagServiceTest.cs new file mode 100644 index 0000000..71bc119 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/JobTagServiceTest.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using LCU.Api.Internal.Dispatcher.Services; +using LCU.Api.Internal.DispatcherLib.Repositories; +namespace LCU.Api.Internal.DispatcherTests.ServiceTests; + +public class JobTagServiceTest +{ + private readonly JobTagService _jobTagService; + private readonly Mock _mockJobTagRepository; + + public JobTagServiceTest() + { + _mockJobTagRepository = new Mock(); + var mockUnitOfWork = new Mock(); + + _jobTagService = new JobTagService(mockUnitOfWork.Object, + _mockJobTagRepository.Object); + } + + [Fact] + public async void GetListAsync_Success() + { + // Arrange + _mockJobTagRepository.Setup(jobTagRepository => jobTagRepository.ListAsync()).ReturnsAsync( + new List() + { + new JobTag(), + new JobTag() + }); + + // Act + var result = await _jobTagService.GetAsync(); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count()); + } + + [Fact] + public async void GetJobTagsByTagIdAsync_Success() + { + // Arrange + _mockJobTagRepository.Setup(jobTagRepository => jobTagRepository.FindByTagIdAsync(2)).ReturnsAsync( + new List() + { + new JobTag {JobId = 1, TagId = 2}, + new JobTag {JobId = 3, TagId = 2 } + }); + + // Act + var result = await _jobTagService.GetByTagIdAsync(2); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count()); + } + + [Fact] + public async void SaveAsync_Success() + { + // Arrange + _mockJobTagRepository.Setup(jobTagRepository => jobTagRepository.AddAsync(It.IsAny())).ReturnsAsync(new JobTag()); + + // Act + var result = await _jobTagService.SaveAsync(new JobTag()); + + // Assert + Assert.NotNull(result); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/SendErroredJobAlertServiceTests.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/SendErroredJobAlertServiceTests.cs new file mode 100644 index 0000000..a2c099d --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/SendErroredJobAlertServiceTests.cs @@ -0,0 +1,389 @@ +using LCU.Api.Internal.Dispatcher.Configurations; +using LCU.Api.Internal.Dispatcher.Services.DispatcherOperationServices; +using LCU.Api.Internal.Dispatcher.Services; +using System.Collections.Generic; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.DispatcherModels.Models.Requests; + +namespace LCU.Api.Internal.DispatcherTests.ServiceTests +{ + public class SendErroredJobAlertServiceTests + { + private readonly Mock _mockNotificationService; + private readonly ApiConfiguration _apiConfigiguration; + private readonly SendErroredJobAlertService _sendErroredJobAlertService; + + public SendErroredJobAlertServiceTests() + { + _mockNotificationService = new Mock(); + _apiConfigiguration = new ApiConfiguration + { + DefaultErrorRecipient = "testertest@landmarkcu.com", + BatchErroredJobsTemplate = "./TemplatesTest/Test_BatchErroredJobsTemplate.html", + ErrorRowTemplate = "./TemplatesTest/Test_ErrorRowTemplate.html" + }; + _sendErroredJobAlertService = new SendErroredJobAlertService(_apiConfigiguration, _mockNotificationService.Object); + } + + [Fact] + public void GroupJobsByRecipients_Returns_OnlyDefault() + { + var job = new Job() + { + JobId = 1 + }; + + var erroredJobAlertRequests = new List + { + new ErroredJobAlertRequest + { + CurrentJob = job, + CurrentWorkItem = new WorkItem() + } + }; + + var result = _sendErroredJobAlertService.GroupJobsByRecipients(erroredJobAlertRequests); + + Assert.NotNull(result); + Assert.Single(result); + var resultErroredJobAlertRequest = result["testertest@landmarkcu.com"].FirstOrDefault(); + Assert.Equal(job, resultErroredJobAlertRequest.CurrentJob); + } + + [Fact] + public void GroupJobsByRecipients_Returns_FourRecipients_And_Default() + { + var job1 = new Job() + { + JobId = 1 + }; + + var job2 = new Job() + { + JobErrorRecipients = new List() + { + new JobErrorRecipient() + { + Recipient = "Test_Recipient_1", + }, + new JobErrorRecipient() + { + Recipient = "Test_Recipient_2", + } + } + }; + + var job3 = new Job() + { + JobErrorRecipients = new List() + { + new JobErrorRecipient() + { + Recipient = "Test_Recipient_2", + }, + new JobErrorRecipient() + { + Recipient = "Test_Recipient_3", + }, + new JobErrorRecipient() + { + Recipient = "Test_Recipient_4", + } + } + }; + + var erroredJobAlertRequests = new List + { + new ErroredJobAlertRequest + { + CurrentJob = job1, + CurrentWorkItem = new WorkItem() + }, + new ErroredJobAlertRequest + { + CurrentJob = job2, + CurrentWorkItem = new WorkItem() + }, + new ErroredJobAlertRequest + { + CurrentJob = job3, + CurrentWorkItem = new WorkItem() + } + }; + + var result = _sendErroredJobAlertService.GroupJobsByRecipients(erroredJobAlertRequests); + + Assert.NotNull(result); + Assert.Equal(job1, result["testertest@landmarkcu.com"].FirstOrDefault().CurrentJob); + Assert.Equal(job2, result["test_recipient_1"].FirstOrDefault().CurrentJob); + Assert.Equal(job3, result["test_recipient_4"].FirstOrDefault().CurrentJob); + } + + [Fact] + public void GroupJobsByRecipients_Returns_FourRecipients_And_DefaultAsTwoJobs() + { + var job1 = new Job() + { + JobId = 1 + }; + + var job2 = new Job() + { + JobErrorRecipients = new List() + { + new JobErrorRecipient() + { + Recipient = "TesterTest@landmarkcu.com", + }, + new JobErrorRecipient() + { + Recipient = "Test_Recipient_1", + }, + new JobErrorRecipient() + { + Recipient = "Test_Recipient_2", + } + } + }; + + var job3 = new Job() + { + JobErrorRecipients = new List() + { + new JobErrorRecipient() + { + Recipient = "Test_Recipient_2", + }, + new JobErrorRecipient() + { + Recipient = "Test_Recipient_3", + }, + new JobErrorRecipient() + { + Recipient = "Test_Recipient_4", + } + } + }; + + var erroredJobAlertRequests = new List + { + new ErroredJobAlertRequest + { + CurrentJob = job1, + CurrentWorkItem = new WorkItem() + }, + new ErroredJobAlertRequest + { + CurrentJob = job2, + CurrentWorkItem = new WorkItem() + }, + new ErroredJobAlertRequest + { + CurrentJob = job3, + CurrentWorkItem = new WorkItem() + } + }; + + var result = _sendErroredJobAlertService.GroupJobsByRecipients(erroredJobAlertRequests); + + Assert.NotNull(result); + Assert.Equal(2, result["testertest@landmarkcu.com"].Count); + Assert.Equal(job2, result["test_recipient_1"].FirstOrDefault().CurrentJob); + Assert.Equal(job3, result["test_recipient_4"].FirstOrDefault().CurrentJob); + } + + [Fact] + public void GroupJobsByRecipients_Returns_FourRecipients_And_DefaultWithLower_DefaultAsTwoJobs() + { + var job1 = new Job() + { + JobId = 1 + }; + + var job2 = new Job() + { + JobErrorRecipients = new List() + { + new JobErrorRecipient() + { + Recipient = "TesterTest@landmarkcu.com", + }, + new JobErrorRecipient() + { + Recipient = "Test_Recipient_1", + }, + new JobErrorRecipient() + { + Recipient = "Test_Recipient_2", + } + } + }; + + var job3 = new Job() + { + JobErrorRecipients = new List() + { + new JobErrorRecipient() + { + Recipient = "Test_Recipient_2", + }, + new JobErrorRecipient() + { + Recipient = "Test_Recipient_3", + }, + new JobErrorRecipient() + { + Recipient = "Test_Recipient_4", + } + } + }; + + + var erroredJobAlertRequests = new List + { + new ErroredJobAlertRequest + { + CurrentJob = job1, + CurrentWorkItem = new WorkItem() + }, + new ErroredJobAlertRequest + { + CurrentJob = job2, + CurrentWorkItem = new WorkItem() + }, + new ErroredJobAlertRequest + { + CurrentJob = job3, + CurrentWorkItem = new WorkItem() + } + }; + var result = _sendErroredJobAlertService.GroupJobsByRecipients(erroredJobAlertRequests); + + Assert.NotNull(result); + Assert.Equal(2, result["testertest@landmarkcu.com"].Count); + Assert.Equal(job2, result["test_recipient_1"].FirstOrDefault().CurrentJob); + Assert.Equal(job3, result["test_recipient_4"].FirstOrDefault().CurrentJob); + } + + [Fact] + public void AddRecipients_Returns_AddsThree_Groups() + { + var job = new Job() + { + JobErrorRecipients = new List() + { + new JobErrorRecipient() + { + Recipient = "Test_Recipient_2", + }, + new JobErrorRecipient() + { + Recipient = "Test_Recipient_3", + }, + new JobErrorRecipient() + { + Recipient = "Test_Recipient_4", + } + } + }; + + var groupedRecipients = new Dictionary>(); + + var erroredJobAlertRequest = new ErroredJobAlertRequest + { + CurrentJob = job, + CurrentWorkItem = new WorkItem() + }; + + _sendErroredJobAlertService.AddRecipients(erroredJobAlertRequest, groupedRecipients); + + Assert.NotNull(groupedRecipients); + Assert.Equal(3, groupedRecipients.Count); + Assert.NotNull(groupedRecipients["test_recipient_2"]); + Assert.NotNull(groupedRecipients["test_recipient_3"]); + Assert.NotNull(groupedRecipients["test_recipient_4"]); + } + + [Fact] + public void SetUpJobErrorDetails_NoWorkItem_ErroredOrProcessing_Throws() + { + var erroredJobAlertRequest = new ErroredJobAlertRequest + { + CurrentJob = new Job() + }; + + Assert.ThrowsAsync(async () => _sendErroredJobAlertService.SetUpJobErrorDetails(erroredJobAlertRequest)); + } + + [Fact] + public void SetUpJobErrorDetails_NoWorkItem_ErroredOrProcessing_Return_JobErrorDetail() + { + var workItem = new WorkItem() + { + Name = "Test WorkItem", + WorkItemExecutions = new List(){ + new WorkItemExecution() + { + Status = StatusType.Failed, + StartedTimestamp = Convert.ToDateTime("4/1/2021") + } + } + }; + + var job = new Job() + { + Name = "Test Job", + NextRunTime = DateTime.Now.AddHours(-1), + WorkItems = new List() { + workItem + }, + JobStatus = JobStatus.Failed + }; + + var erroredJobAlertRequest = new ErroredJobAlertRequest + { + CurrentJob = job, + CurrentWorkItem = workItem + }; + + var result = _sendErroredJobAlertService.SetUpJobErrorDetails(erroredJobAlertRequest); + + Assert.NotNull(result); + Assert.Equal("Test Job", result.JobName); + Assert.Equal("Test WorkItem", result.WorkItemName); + Assert.Equal("Failed", result.JobStatus); + Assert.Equal(Convert.ToDateTime("4/1/2021").ToString(), result.StartTime); + } + + [Fact] + public void CreateErrorRow_Return_Filled_Test_ErrorRowTemplate() + { + var jobErrorDetails = new JobErrorDetail() + { + JobName = "Test Job", + WorkItemName = "Test WorkItem", + JobStatus = "Failed", + StartTime = Convert.ToDateTime("4/1/2021").ToString() + }; + + var result = _sendErroredJobAlertService.CreateErrorRow(jobErrorDetails); + + Assert.Contains("Test Job", result); + Assert.Contains("Test WorkItem", result); + Assert.Contains("Failed", result); + Assert.Contains(Convert.ToDateTime("4/1/2021").ToString(), result); + } + + [Fact] + public void CreateBatchErroredJobsEmail_Return_Filled_Test_ErrorRowTemplate() + { + var stringHtmlErrors = "
Test Job
Test WorkItem
Failed
4/1/2021 12:00:00 AM
"; + var result = _sendErroredJobAlertService.CreateBatchErroredJobsEmail(stringHtmlErrors); + + Assert.Contains("Test Job", result); + Assert.Contains("Test WorkItem", result); + Assert.Contains("Failed", result); + Assert.Contains(Convert.ToDateTime("4/1/2021").ToString(), result); + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/TagServiceTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/TagServiceTest.cs new file mode 100644 index 0000000..c2dd23d --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/TagServiceTest.cs @@ -0,0 +1,116 @@ +using System.Collections.Generic; +using LCU.Api.Internal.Dispatcher.Services; +using LCU.Api.Internal.DispatcherLib.Repositories; +namespace LCU.Api.Internal.DispatcherTests.ServiceTests; + +public class TagServiceTest +{ + private readonly TagService _tagService; + private readonly Mock _mockJobTagService; + private readonly Mock _mockTagRepository; + + public TagServiceTest() + { + _mockTagRepository = new Mock(); + var mockUnitOfWork = new Mock(); + _mockJobTagService = new Mock(); + + _tagService = new TagService(mockUnitOfWork.Object, + _mockTagRepository.Object, + _mockJobTagService.Object); + } + + [Fact] + public async void GetListAsync_Success() + { + // Arrange + _mockTagRepository.Setup(tagRepository => tagRepository.ListAsync()).ReturnsAsync( + new List() + { + new Tag(), + new Tag() + }); + + // Act + var result = await _tagService.GetAsync(); + + // Assert + Assert.NotNull(result); + Assert.Equal(2, result.Count()); + } + + [Fact] + public async void GetByIdAsync_Success() + { + // Arrange + _mockTagRepository.Setup(tagRepository => tagRepository.FindByIdAsync(It.IsAny())).ReturnsAsync(new Tag()); + + // Act + var result = await _tagService.GetByIdAsync(1); + + // Assert + Assert.NotNull(result); + } + + [Fact] + public async void SaveAsync_Success() + { + // Arrange + _mockTagRepository.Setup(tagRepository => tagRepository.AddAsync(It.IsAny())).ReturnsAsync(new Tag()); + + // Act + var result = await _tagService.SaveAsync(new Tag()); + + // Assert + Assert.NotNull(result); + } + + [Fact] + public async void UpdateAsync_Success() + { + // Arrange + var jobTagToUpdate = new Tag() + { + TagId = 1, + TagName = "Test" + }; + + _mockTagRepository.Setup(tagRepository => tagRepository.Update(It.IsAny())).Verifiable(); + + // Act + var result = await _tagService.UpdateAsync(jobTagToUpdate); + + // Assert + Assert.NotNull(result); + Assert.Equal(jobTagToUpdate.TagId, result.TagId); + Assert.Equal(jobTagToUpdate.TagName, result.TagName); + _mockTagRepository.VerifyAll(); + } + + [Fact] + public void DeleteAsync_Success() + { + // Arrange + var jobTagToDelete = new JobTag() + { + TagId = 1, + JobTagId = 2 + }; + + var tagToDelete = new Tag() + { + TagId = 1, + TagName = "Test" + }; + + _mockTagRepository.Setup(tagRepository => tagRepository.FindByIdAsync(It.IsAny())).ReturnsAsync(tagToDelete); + _mockTagRepository.Setup(tagRepository => tagRepository.Remove(It.IsAny())).Verifiable(); + + // Act + _tagService.DeleteAsync(tagToDelete.TagId); + + // Assert + Assert.True(true); + _mockTagRepository.VerifyAll(); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/WorkItemExecutionServiceTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/WorkItemExecutionServiceTest.cs new file mode 100644 index 0000000..7d79fd7 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/WorkItemExecutionServiceTest.cs @@ -0,0 +1,102 @@ +using LCU.Api.Internal.Dispatcher.Services; +using LCU.Api.Internal.DispatcherLib.Repositories; +using LCU.Library.Internal.DispatcherModels.Enums; +namespace LCU.Api.Internal.DispatcherTests.ServiceTests; + +public class WorkItemExecutionServiceTest +{ + private readonly WorkItemExecutionService _workItemExecutionService; + private readonly Mock _mockUnitOfWork; + private readonly Mock _workItemExecutionRepository; + + public WorkItemExecutionServiceTest() + { + _mockUnitOfWork = new Mock(); + _workItemExecutionRepository = new Mock(); + + _workItemExecutionService = new WorkItemExecutionService(_mockUnitOfWork.Object, + _workItemExecutionRepository.Object); + } + + [Fact] + public async void GetByIdAsync_Success() + { + // Arrange + var workItemExecution = new WorkItemExecution() + { + WorkItemExecutionId = 1 + }; + _workItemExecutionRepository.Setup(repo => repo.FindByIdAsync(1)).ReturnsAsync(workItemExecution); + + // Act + var result = await _workItemExecutionService.GetByIdAsync(1); + + // Assert + Assert.NotNull(result); + Assert.Equal(1, result.WorkItemExecutionId); + _workItemExecutionRepository.Verify(x => x.FindByIdAsync(1), Times.Once); + } + + [Fact] + public void GetAsync_Exception() + { + _workItemExecutionRepository.Setup(x => x.FindByIdAsync(1)).ThrowsAsync(new Exception("Error")); + Assert.ThrowsAsync(async () => await _workItemExecutionService.GetByIdAsync(1)); + _workItemExecutionRepository.Verify(x => x.FindByIdAsync(1), Times.Once); + } + + + [Fact] + public async void UpdateCompleteWorkItemExecutionWithoutSave_Success() + { + // Arrange + var workItemExecution = new WorkItemExecution() + { + WorkItemExecutionId = 1 + }; + + var updatingWorkItemExecution = new WorkItemExecution() + { + WorkItemExecutionId = 1, + Status = StatusType.Completed + }; + _workItemExecutionRepository.Setup(repo => repo.Update(updatingWorkItemExecution)); + + // Act + var result = await _workItemExecutionService.UpdateCompleteWorkItemExecutionWithoutSave(workItemExecution, StatusType.Completed); + + // Assert + Assert.NotNull(result); + Assert.Equal(StatusType.Completed, result.Status); + _workItemExecutionRepository.Verify(x => x.FindByIdAsync(It.IsAny()), Times.Never); + _workItemExecutionRepository.Verify(x => x.Update(It.IsAny()), Times.Once); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Never); + } + + [Fact] + public async void UpdateCompleteWorkItemExecution_Success() + { + // Arrange + var workItemExecution = new WorkItemExecution() + { + WorkItemExecutionId = 1 + }; + + var updatingWorkItemExecution = new WorkItemExecution() + { + WorkItemExecutionId = 1, + Status = StatusType.Completed + }; + _workItemExecutionRepository.Setup(repo => repo.Update(updatingWorkItemExecution)); + + // Act + var result = await _workItemExecutionService.UpdateCompleteWorkItemExecution(workItemExecution, StatusType.Completed); + + // Assert + Assert.NotNull(result); + Assert.Equal(StatusType.Completed, result.Status); + _workItemExecutionRepository.Verify(x => x.FindByIdAsync(It.IsAny()), Times.Never); + _workItemExecutionRepository.Verify(x => x.Update(It.IsAny()), Times.Once); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/WorkItemServiceTest.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/WorkItemServiceTest.cs new file mode 100644 index 0000000..7ef6a79 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/ServiceTests/WorkItemServiceTest.cs @@ -0,0 +1,226 @@ +using System.Collections.Generic; +using LCU.Library.Internal.LcuMassTransitModels; +using LCU.Api.Internal.Dispatcher.Services; +using LCU.Api.Internal.DispatcherLib.Repositories; +using LCU.Api.Internal.Dispatcher.Configurations; +using LCU.Api.Internal.DispatcherSharedLib.BuilderServices; +namespace LCU.Api.Internal.DispatcherTests.ServiceTests; + +public class WorkItemServiceTest +{ + private readonly WorkItemService _workItemService; + private readonly Mock _mockUnitOfWork; + private readonly Mock _mockWorkItemRepository; + private readonly Mock _mockWorkItemExecutionService; + private readonly Mock _mockActionBuilderService; + private readonly ConnectionConfiguration _connectionConfiguration; + private readonly ReceiveEndpointConfigurations _receiveEndpointConfigurations; + private readonly ApiConfiguration _apiConfiguration; + + public WorkItemServiceTest() + { + _mockUnitOfWork = new Mock(); + _mockWorkItemRepository = new Mock(); + _mockWorkItemExecutionService = new Mock(); + _mockActionBuilderService = new Mock(); + _connectionConfiguration = new ConnectionConfiguration(); + _apiConfiguration = new ApiConfiguration() + { + NumberOfExecutionMessages = 20, + NumberOfWorkItemExecutions = 10 + }; + _receiveEndpointConfigurations = new ReceiveEndpointConfigurations + { + CompletionReceiveEndpoint = new ReceiveEndpointConfiguration() + }; + + _workItemService = new WorkItemService(_mockUnitOfWork.Object, + _mockWorkItemRepository.Object, + _mockWorkItemExecutionService.Object, + _mockActionBuilderService.Object, + _connectionConfiguration, + _receiveEndpointConfigurations, + _apiConfiguration); + } + + [Fact] + public async Task GetByIdAsync_Success() + { + // Arrange + var workItem = new WorkItem() + { + WorkItemId = 1 + }; + _mockWorkItemRepository.Setup(repo => repo.FindByIdAsync(1)).ReturnsAsync(workItem); + + // Act + var result = await _workItemService.GetByIdAsync(1); + + // Assert + Assert.NotNull(result); + Assert.Equal(1, result.WorkItemId); + _mockWorkItemRepository.Verify(x => x.FindByIdAsync(1), Times.Once); + } + + [Fact] + public void GetAsync_Exception() + { + _mockWorkItemRepository.Setup(x => x.FindByIdAsync(1)).ThrowsAsync(new Exception("Error")); + Assert.ThrowsAsync(async () => await _workItemService.GetByIdAsync(1)); + _mockWorkItemRepository.Verify(x => x.FindByIdAsync(1), Times.Once); + } + + [Fact] + public async Task GetByJobIdAsync_Success() + { + // Arrange + var workItems = new List() + { + new WorkItem() + { + WorkItemId = 1 + } + }; + _mockWorkItemRepository.Setup(repo => repo.FindByJobIdAsync(1)).ReturnsAsync(workItems); + + // Act + var result = await _workItemService.GetByJobIdAsync(1); + + // Assert + Assert.NotNull(result); + Assert.Equal(1, result.FirstOrDefault().WorkItemId); + _mockWorkItemRepository.Verify(x => x.FindByJobIdAsync(1), Times.Once); + } + + [Fact] + public void GetByJobIdAsync_Exception() + { + _mockWorkItemRepository.Setup(x => x.FindByJobIdAsync(1)).ThrowsAsync(new Exception("Error")); + Assert.ThrowsAsync(async () => await _workItemService.GetByJobIdAsync(1)); + _mockWorkItemRepository.Verify(x => x.FindByJobIdAsync(1), Times.Once); + } + + [Fact] + public async Task UpdateAsync_Success() + { + // Arrange + var workItem = new WorkItem() + { + WorkItemId = 1 + }; + + var updatingWorkItem = new WorkItem() + { + WorkItemId = 1, + Name = "Test" + }; + _mockWorkItemRepository.Setup(repo => repo.Update(updatingWorkItem)); + + // Act + var result = await _workItemService.UpdateAsync(updatingWorkItem); + + // Assert + Assert.NotNull(result); + Assert.Equal("Test", result.Name); + _mockWorkItemRepository.Verify(x => x.FindByIdAsync(It.IsAny()), Times.Never); + _mockWorkItemRepository.Verify(x => x.Update(It.IsAny()), Times.Once); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); + } + + [Fact] + public async Task DeleteList_Success() + { + // Arrange + var workItem1Executions = new List() + { + new WorkItemExecution + { + WorkItemId = 1, + WorkItemExecutionId = 1 + }, + new WorkItemExecution + { + WorkItemId = 1, + WorkItemExecutionId = 2 + } + }; + _mockWorkItemExecutionService.Setup(service => service.GetAllByWorkItemId(1)).ReturnsAsync(workItem1Executions); + + var workItem2Executions = new List() + { + new WorkItemExecution + { + WorkItemId = 2, + WorkItemExecutionId = 3 + }, + new WorkItemExecution + { + WorkItemId = 2, + WorkItemExecutionId = 4 + } + }; + _mockWorkItemExecutionService.Setup(service => service.GetAllByWorkItemId(2)).ReturnsAsync(workItem2Executions); + + var deletingWorkItems = new List + { + new WorkItem + { + WorkItemId = 1, + WorkItemExecutions = new List + { + new WorkItemExecution + { + WorkItemId = 1, + WorkItemExecutionId = 1 + }, + new WorkItemExecution + { + WorkItemId = 1, + WorkItemExecutionId = 2 + } + } + }, + new WorkItem + { + WorkItemId = 2, + WorkItemExecutions = new List + { + new WorkItemExecution + { + WorkItemId = 2, + WorkItemExecutionId = 3 + }, + new WorkItemExecution + { + WorkItemId = 2, + WorkItemExecutionId = 4 + } + } + } + + }; + _mockWorkItemRepository.Setup(repo => repo.RemoveList(deletingWorkItems)); + + + var startingDeletingWorkItems = new List + { + new WorkItem + { + WorkItemId = 1 + }, + new WorkItem + { + WorkItemId = 2 + } + + }; + // Act + await _workItemService.DeleteList(startingDeletingWorkItems); + + // Assert + _mockWorkItemExecutionService.Verify(x => x.GetAllByWorkItemId(It.Is(x => x == 1)), Times.Once); + _mockWorkItemExecutionService.Verify(x => x.GetAllByWorkItemId(It.Is(x => x == 2)), Times.Once); + _mockWorkItemRepository.Verify(x => x.RemoveList(It.IsAny>()), Times.Once); + _mockUnitOfWork.Verify(x => x.CompleteAsync(), Times.Once); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/TemplatesTest/Test_BatchErroredJobsTemplate.html b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/TemplatesTest/Test_BatchErroredJobsTemplate.html new file mode 100644 index 0000000..edd07b6 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/TemplatesTest/Test_BatchErroredJobsTemplate.html @@ -0,0 +1,3 @@ +
+ {TableRows} +
\ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/TemplatesTest/Test_ErrorRowTemplate.html b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/TemplatesTest/Test_ErrorRowTemplate.html new file mode 100644 index 0000000..b071435 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/TemplatesTest/Test_ErrorRowTemplate.html @@ -0,0 +1,6 @@ +
+
{JobName}
+
{WorkItemName}
+
{JobStatus}
+
{StartTime}
+
\ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/Using.cs b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/Using.cs new file mode 100644 index 0000000..e4c038e --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/Using.cs @@ -0,0 +1,8 @@ +global using System; +global using System.Threading.Tasks; +global using System.Linq; +global using Moq; +global using Xunit; +global using LCU.Library.Internal.Exceptions; +global using LCU.Library.Internal.DispatcherModels.Entities; +global using LCU.Library.Internal.DispatcherModels.Models; \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/additional_test_args.runsettings b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/additional_test_args.runsettings new file mode 100644 index 0000000..16d6a41 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Api.Internal.DispatcherTests/additional_test_args.runsettings @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + .*microsoft.* + + + + + + + + + + + diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Constants/ActionMessages.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Constants/ActionMessages.cs new file mode 100644 index 0000000..26ce4e4 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Constants/ActionMessages.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +namespace LCU.Library.Internal.DispatcherModels.Constants; + +public static class ActionMessages +{ + public static readonly List Messages = new() + { + Retry, + Skip, + RunJob, + CancelJob, + ActivateJob, + DeactivateJob + }; + + public const string Retry = "Retry Attempted"; + public const string Skip = "Skipped Item"; + public const string RunJob = "Run Now has closed this Execution and a new Execution has been started"; + public const string CancelJob = "Cancel Job"; + public const string ActivateJob = "Activate Job"; + public const string DeactivateJob = "Deactivate Job"; + +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Constants/JobSortableFields.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Constants/JobSortableFields.cs new file mode 100644 index 0000000..5c9c159 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Constants/JobSortableFields.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +namespace LCU.Library.Internal.DispatcherModels.Constants; + +public static class JobSortableFields +{ + public static readonly List JobFields = new() + { + Description, + Name, + NextRunTime + }; + + public const string Description = "Description"; + public const string Name = "Name"; + public const string NextRunTime = "NextRunTime"; +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/CleanUpJobSetting.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/CleanUpJobSetting.cs new file mode 100644 index 0000000..1988744 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/CleanUpJobSetting.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace LCU.Library.Internal.DispatcherModels.Entities +{ + public class CleanUpJobSetting + { + public int CleanUpJobSettingId { get; set; } + [Required] + [Range(-1000, 0, ErrorMessage = "Value must be 0 or a negative number. ")] + public int CompletionDaysPast { get; set; } + [ForeignKey("CleanUpJobSettingId")] + public ICollection CleanUpJobTags { get; set; } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/CleanUpJobTag.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/CleanUpJobTag.cs new file mode 100644 index 0000000..93a4338 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/CleanUpJobTag.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace LCU.Library.Internal.DispatcherModels.Entities +{ + public class CleanUpJobTag + { + public int CleanUpJobTagId { get; set; } + [Required] + public int CleanUpJobSettingId { get; set; } + public int TagId { get; set; } + [ForeignKey("TagId")] + public Tag Tag { get; set; } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/ExecutionMessage.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/ExecutionMessage.cs new file mode 100644 index 0000000..69cb238 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/ExecutionMessage.cs @@ -0,0 +1,26 @@ +using System; +using System.ComponentModel.DataAnnotations; +namespace LCU.Library.Internal.DispatcherModels.Entities; + +public class ExecutionMessage +{ + public ExecutionMessage() + { + + } + + public ExecutionMessage(int workItemExecutionId, string message) + { + WorkItemExecutionId = workItemExecutionId; + Message = message; + User = Environment.UserName; + MessageDate = DateTime.Now; + } + + public int ExecutionMessageId { get; set; } + [Required] + public int WorkItemExecutionId { get; set; } + public string User { get; set; } + public DateTime? MessageDate { get; set; } + public string Message { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/Job.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/Job.cs new file mode 100644 index 0000000..c4b5d7f --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/Job.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using LCU.Library.Internal.ApiResponse.Models; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.DispatcherModels.Models; +namespace LCU.Library.Internal.DispatcherModels.Entities; + +public class Job : Errorable +{ + public int JobId { get; set; } + + [Required] + [StringLength(200, ErrorMessage = "Requesting Application Max Length is 200")] + public string RequestingApplication { get; set; } + + [Required] + [StringLength(200, ErrorMessage = "Name Max Length is 200")] + public string Name { get; set; } + + [StringLength(8000, ErrorMessage = "Description Max Length is 8000")] + public string Description { get; set; } + + [Required] + [StringLength(200, ErrorMessage = "Created by Max Length is 200")] + public string CreatedBy { get; set; } + + public DateTime CreatedAt { get; set; } + + [StringLength(200, ErrorMessage = "Modified by Max Length is 200")] + public string ModifiedBy { get; set; } + + public DateTime? ModifiedDate { get; set; } + + public string CronSchedule { get; set; } + + public DateTime? NextRunTime { get; set; } + + public DateTime? SoftDeleteDate { get; set; } + + public bool SkipHolidays { get; set; } = false; + public JobStatus JobStatus { get; set; } = JobStatus.Inactive; + + [ForeignKey("JobId")] + public ICollection JobErrorRecipients { get; set; } = new List(); + + [ForeignKey("JobId")] + public ICollection JobTags { get; set; } = new List(); + + [Required] + [ForeignKey("JobId")] + public ICollection WorkItems { get; set; } = new List(); + + [NotMapped] + public bool IsNewJob + { + get + { + return JobId <= 0; + } + } + + public void ResetRunTimeAndStatus() + { + NextRunTime = null; + JobStatus = JobStatus.Inactive; + } + + + [NotMapped] + public bool IsPending + { + get + { + return WorkItems.All(workItem => workItem.CurrentWorkItemExecution == null); + } + } + + [NotMapped] + public StatusType CurrentStatus + { + get + { + if (NextRunTime == null) + { + return StatusType.Inactive; + } + + var currentWorkItem = WorkItems.FirstOrDefault(workItem => workItem.CurrentWorkItemExecution != null); + return IsPending || currentWorkItem == null ? StatusType.Pending : currentWorkItem.CurrentWorkItemExecution.Status; + } + } + + [NotMapped] + public ReturnResponse ReturnResponse { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/JobErrorRecipient.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/JobErrorRecipient.cs new file mode 100644 index 0000000..846ccb3 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/JobErrorRecipient.cs @@ -0,0 +1,8 @@ +namespace LCU.Library.Internal.DispatcherModels.Entities; + +public class JobErrorRecipient +{ + public int JobErrorRecipientId { get; set; } + public int JobId { get; set; } + public string Recipient { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/JobTag.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/JobTag.cs new file mode 100644 index 0000000..f60bcd0 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/JobTag.cs @@ -0,0 +1,11 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace LCU.Library.Internal.DispatcherModels.Entities; + +public class JobTag +{ + public int JobTagId { get; set; } + public int JobId { get; set; } + public int TagId { get; set; } + [ForeignKey("TagId")] + public Tag Tag { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/Tag.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/Tag.cs new file mode 100644 index 0000000..2a254a2 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/Tag.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; +namespace LCU.Library.Internal.DispatcherModels.Entities; + +public class Tag +{ + public int TagId { get; set; } + [Required] + [StringLength(300, ErrorMessage = "Requesting Application Max Length is 300")] + public string TagName { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/WorkItem.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/WorkItem.cs new file mode 100644 index 0000000..85a3af8 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/WorkItem.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.DispatcherModels.Helpers; +namespace LCU.Library.Internal.DispatcherModels.Entities; + +public class WorkItem +{ + public int WorkItemId { get; set; } + public int JobId { get; set; } + [Required] + [StringLength(200, ErrorMessage = "Name Max Length is 200")] + public string Name { get; set; } + [StringLength(8000, ErrorMessage = "Description Max Length is 8000")] + public string Description { get; set; } + [Required] + public int Order { get; set; } + public string SerializedPayload { get; set; } + public string Url { get; set; } + public HttpMethodType HttpMethod { get; set; } + [Required] + public ActionType ActionType { get; set; } + public bool CancelJobOnFailure { get; set; } + public int CancelJobLimit { get; set; } + public int CanceledJobCounter { get; set; } + [ForeignKey("WorkItemId")] + public ICollection WorkItemExecutions { get; set; } + [NotMapped] + public WorkItemExecution CurrentWorkItemExecution + { + get + { + var currentWorkItemExecution = WorkItemExecutions?.FirstOrDefault(wie => wie.CompletedTimestamp == null); + if (currentWorkItemExecution != null) + { + currentWorkItemExecution = Clone.DeepClone(currentWorkItemExecution); + } + return currentWorkItemExecution; + } + } + [NotMapped] + public bool IsNewWorkItem => WorkItemId <= 0; + + [NotMapped] + public bool RequiresJobCancellation => CancelJobOnFailure && CanceledJobCounter <= CancelJobLimit; +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/WorkItemExecution.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/WorkItemExecution.cs new file mode 100644 index 0000000..5391c45 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Entities/WorkItemExecution.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using LCU.Library.Internal.DispatcherModels.Enums; +using LCU.Library.Internal.DispatcherModels.Models; +namespace LCU.Library.Internal.DispatcherModels.Entities; + +public class WorkItemExecution : Errorable +{ + public WorkItemExecution() + { + + } + + public WorkItemExecution(int workItemId) + { + WorkItemId = workItemId; + Status = StatusType.Processing; + StartedTimestamp = DateTime.Now; + } + + public int WorkItemExecutionId { get; set; } + [Required] + public int WorkItemId { get; set; } + [Required] + public StatusType Status { get; set; } + public DateTime? StartedTimestamp { get; set; } + public DateTime? CompletedTimestamp { get; set; } + [ForeignKey("WorkItemExecutionId")] + public ICollection ExecutionMessages { get; set; } + + public static WorkItemExecution SetAsFinal(WorkItemExecution workItemExecution, StatusType finalStatus) + { + workItemExecution.Status = finalStatus; + workItemExecution.CompletedTimestamp = DateTime.Now; + return workItemExecution; + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/ActionType.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/ActionType.cs new file mode 100644 index 0000000..3c8dd4c --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/ActionType.cs @@ -0,0 +1,69 @@ +namespace LCU.Library.Internal.DispatcherModels.Enums; + +public enum ActionType +{ + MoveFile = 0, + CopyFile = 1, + RenameFile = 2, + DeleteFile = 3, + //SftpFile = 4, + //SftpDynamicFile = 5, + ZipDirectory = 6, + Email = 7, + CreateDirectory = 8, + RenameDirectory = 9, + UnzipFile = 10, + CoreArchiveRequest = 11, + EncryptFileData = 12, + DecryptFileData = 13, + SftpUploadFile = 14, + SftpDownloadFile = 15, + ModifyFile = 16, + DocumentIngestor = 17, + DocumentBatchIngestion = 18, + UnzipPasswordProtectedFile = 19, + IngestorPdfPartitioner = 20, + MemberDocumentNotification = 21, + RemoteDepositCaptureXmlFile = 22, + DeleteDirectory = 23, + DataCleanUp = 24, + AtmPersonCheckHolds = 25, + HistoricalDocumentIngestor = 26, + HistoricalDocumentBatchIngestion = 27, + HistoricalIngestorPdfPartitioner = 28, + NoticeProcessorExtractRequest = 29, + NoticeProcessorSummaryEmailRequest = 30, + DataCleanupNcoaUpdateRequest = 31, + IngestionFolderCleanUp = 32, + MomBotRequest = 33, + BlendDataRequest = 34, + DialerUploadRequest = 35, + UploadDoNotCallRequest = 36, + DataCleanUpFannieMaeRequest = 37, + CreateVoAppsCampaignsRequest = 38, + UploadVoAppsCampaignResultsRequest = 39, + ImportCreditCardSalesRequest = 40, + DataCleanUpIndirectFundingRequest = 41, + ImportIndirectVehicleSalesRequest = 42, + ValidateFileRequest = 43, + ExtractIndexFileData = 44, + IndexFieldMapperRequest = 45, + DealerAchNotificationRequest = 46, + InitiateRealTimeAlertsRequest = 47, + InitiateRealTimeAlertsErroredProcessingRequest = 48, + IncentiveProcessRequest = 49, + NightlyIncentiveProcessRequest = 50, + ActionCreditCardRequest = 51, + ActionVehicleRequest = 52, + AlliedGapMbpRequest = 53, + DayforceProcessRequest = 54, + GoalDispersalRequest = 55, + PayoutIncentiveRequest = 56, + ReportingRequest = 57, + ValidateFileSizeInRange = 58, + DeleteEmptyDirectoryRequest = 59, + DocumentRetentionCleanupRequest = 60, + UploadVoAppsCampaignResultsErrorRequest = 61, + RepoBatchProcessRequest = 62, + DocumentIngestorProcessStatusEmail = 63 +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/DetermineOperationType.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/DetermineOperationType.cs new file mode 100644 index 0000000..958917b --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/DetermineOperationType.cs @@ -0,0 +1,8 @@ +namespace LCU.Library.Internal.DispatcherModels.Enums; + +public enum DetermineOperationType +{ + Completed = 0, + Failed = 1, + Skipped = 2 +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/HttpMethodType.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/HttpMethodType.cs new file mode 100644 index 0000000..b1ef3ab --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/HttpMethodType.cs @@ -0,0 +1,7 @@ +namespace LCU.Library.Internal.DispatcherModels.Enums; + +public enum HttpMethodType +{ + Unknown, + Post +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/JobStatus.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/JobStatus.cs new file mode 100644 index 0000000..e721280 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/JobStatus.cs @@ -0,0 +1,12 @@ +namespace LCU.Library.Internal.DispatcherModels.Enums +{ + public enum JobStatus + { + Inactive = 0, + ReadyToSchedule = 1, + PendingToRun = 2, + Processing = 3, + DeterminingOperation = 4, + Failed = 5 + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/StatusType.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/StatusType.cs new file mode 100644 index 0000000..7d7c508 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Enums/StatusType.cs @@ -0,0 +1,11 @@ +namespace LCU.Library.Internal.DispatcherModels.Enums; + +public enum StatusType +{ + Pending, + Processing, + Completed, + Failed, + Inactive, + Skipped +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Helpers/Clone.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Helpers/Clone.cs new file mode 100644 index 0000000..4a43e2d --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Helpers/Clone.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; +namespace LCU.Library.Internal.DispatcherModels.Helpers; + +public static class Clone +{ + public static T DeepClone(this T source) + { + if (source is null) + { + return default; + } + + var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace }; + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source), deserializeSettings); + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/LCU.Library.Internal.DispatcherModels.csproj b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/LCU.Library.Internal.DispatcherModels.csproj new file mode 100644 index 0000000..ba23182 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/LCU.Library.Internal.DispatcherModels.csproj @@ -0,0 +1,146 @@ + + + + net8.0 + True + 1.20.14 + + 1.0.0 - Initial Creation + 1.1.0 - Addition of Tag, JobTag, new filter for tags, and List of JobTags in Job model. + 1.2.0 - Addition of new Sftp action types in enum. + 1.3.0 - Addition of JobSortable field constants, Job Filter model changes. + 1.4.0 - Addition of JobTags to JobDetails model. + 1.5.0 - Upgrade ApiResponse.Models nuget + 1.6.0 - Tag name attribute update + 1.7.0 - Addition of Pagination models + 1.8.0 - Addition of Execution Messages model + 1.9.0 - Update to Action Message constant + 1.10.0 - Update to Action Type model + 1.11.0 - Added DocumentIngestor to ActionType + 1.11.1 - WorkItemExecution change for creation constructor to set StartedTimestamp and create a new SetAsCompleted helper method. + 1.12.0 - Added BatchIngestion and UnzipPasswordProtected ActionTypes + 1.13.0 - Added DocumentIngestor IngestorPdfPartitionerRequest + 1.14.0 - Added DocumentIngestor's MemberDocumentNotificationRequest + 1.15.0 - Added RemoteDepositCaptureXmlRequest and updated DocumentIngestorModels nuget + 1.16.0 - Added DeleteDirectoryRequest Workitem and updated DocumentIngestorModels nuget + 1.17.0 - Added UpdatePersonForCheckHoldRequest and DataCleanup Workitems + 1.18.0 - Update FileOperations and FTP API Nuget for models + 1.18.1 - Added HistoricalDocumentIngestor, HistoricalDocumentBatchIngestion, and HistoricalIngestorPdfPartitioner WorkItems + 1.19.0 - Add ExtractRequest and ProcessingSummaryEmail request from NoticeProcessor API + 1.19.1 - Added new WorkItem Properties for Canceling Jobs on Failure. + 1.19.2 - Refactors/Cleanup + 1.19.3 - Removed unused Job property ReadyForScheduling and added defaults for Job property collections. + 1.19.4 - Add NcoaUpdate workItem + 1.19.5 - Removed SystemMessage field from WorkItemExecution + 1.19.6 - Changed ErrorMessage and limit for Job, JobDetails, WorkItem, and WorkItemDetails Description + 1.19.7 - Update Job model and add CleanUpJobSetting and CleanUpJobTag + 1.19.8 - Add IngestionFolderCleanUp WorkItem + 1.19.9 - Add MomBot WorkItem + 1.19.10 - Removed SftpFile and SftpDynamicFile from ActionType Enum + 1.19.11 - Add BlendApi Data Sync Request WorkItem + 1.19.12 - Added DialerUploadRequest WorkItem + 1.19.13 - Added UploadDoNotCallRequest WorkItem + 1.19.14 - Added FannieMaeMortgageRequest WorkItem + 1.19.15 - Added CreateVoAppsCampaignsRequest WorkItem + 1.19.16 - Added UploadVoAppsCampaignRequest WorkItem + 1.19.17 - Added ImportCreditCardSalesRequest WorkItem + 1.19.18 - Added DataCleanUpIndirectFundingRequest WorkItem + 1.19.19 - Added ImportIndirectVehicleSalesRequest WorkItem + 1.19.20 - Added SkipHolidays to Job + 1.19.21 - Add ValidateFileRequest FileOperations WorkItem + 1.19.22 - Added ExtractIndexFileData WorkItem + 1.19.23 - Added IndexFieldMapperRequest WorkItem + 1.19.24 - Added DealerAchNotification WorkItem + 1.19.25 - Add RealTimeAlerts WorkItem + 1.19.26 - Add RealTimeAlerts Errored Process WorkItem + 1.20.0 - Upgrade to dotnet8 + 1.20.1 - Add CopyJobRequest and CopyJobResponse + 1.20.2 - Remove CopyJobResponse + 1.20.3 - Add Incentive Mgr Work Items + 1.20.4 - Add ValidateFileSizeInRangeRequest + 1.20.5 - Added DeleteEmptyDirectoryRequest WorkItem + 1.20.6 - Added DocumentRetentionCleanup WorkItem + 1.20.7 - Update DispatcherReturnResponse + 1.20.8 - Add DetermineOperationType Enum + 1.20.9 - Update Filter Defaults for JobStatus + 1.20.10 - Add JobStatus to JobDetails + 1.20.11 - Added VoAppsCampaignErrorResults WorkItem + 1.20.12 - Update JobErrorDetail to use JobStatus + 1.20.13 - Update for RepoAPI + 1.20.14 - Update DocumentIngestorModels Nuget package + + + 1.0.0 - Initial Creation + 1.1.0 - Addition of Tag, JobTag, new filter for tags, and List of JobTags in Job model. + 1.2.0 - Addition of new Sftp action types in enum. + 1.3.0 - Addition of JobSortable field constants, Job Filter model changes. + 1.4.0 - Addition of JobTags to JobDetails model. + 1.5.0 - Upgrade ApiResponse.Models nuget + 1.6.0 - Tag name attribute update + 1.7.0 - Addition of Pagination models + 1.8.0 - Addition of Execution Messages model + 1.9.0 - Update to Action Message constant + 1.10.0 - Update to Action Type model + 1.11.0 - Added DocumentIngestor to ActionType + 1.11.1 - WorkItemExecution change for creation constructor to set StartedTimestamp and create a new SetAsCompleted helper method. + 1.12.0 - Added BatchIngestion and UnzipPasswordProtected ActionTypes + 1.13.0 - Added DocumentIngestor IngestorPdfPartitionerRequest + 1.14.0 - Added DocumentIngestor's MemberDocumentNotificationRequest + 1.15.0 - Added RemoteDepositCaptureXmlRequest and updated DocumentIngestorModels nuget + 1.16.0 - Added DeleteDirectoryRequest Workitem and updated DocumentIngestorModels nuget + 1.17.0 - Added UpdatePersonForCheckHoldRequest and DataCleanup Workitems + 1.18.0 - Update FileOperations and FTP API Nuget for models + 1.18.1 - Added HistoricalDocumentIngestor, HistoricalDocumentBatchIngestion, and HistoricalIngestorPdfPartitioner WorkItems + 1.19.0 - Add ExtractRequest and ProcessingSummaryEmail request from NoticeProcessor API + 1.19.1 - Added new WorkItem Properties for Canceling Jobs on Failure. + 1.19.2 - Refactors/Cleanup + 1.19.3 - Removed unused Job property ReadyForScheduling and added defaults for Job property collections. + 1.19.4 - Add NcoaUpdate workItem + 1.19.5 - Removed SystemMessage field from WorkItemExecution + 1.19.6 - Changed ErrorMessage and limit for Job, JobDetails, WorkItem, and WorkItemDetails Description + 1.19.7 - Update Job model and add CleanUpJobSetting and CleanUpJobTag + 1.19.8 - Add IngestionFolderCleanUp WorkItem + 1.19.9 - Add MomBot WorkItem + 1.19.10 - Removed SftpFile and SftpDynamicFile from ActionType Enum + 1.19.11 - Add BlendApi Data Sync Request WorkItem + 1.19.12 - Added DialerUploadRequest WorkItem + 1.19.13 - Added UploadDoNotCallRequest WorkItem + 1.19.14 - Added FannieMaeMortgageRequest WorkItem + 1.19.15 - Added CreateVoAppsCampaignsRequest WorkItem + 1.19.16 - Added UploadVoAppsCampaignRequest WorkItem + 1.19.17 - Added ImportCreditCardSalesRequest WorkItem + 1.19.18 - Added DataCleanUpIndirectFundingRequest WorkItem + 1.19.19 - Added ImportIndirectVehicleSalesRequest WorkItem + 1.19.20 - Added SkipHolidays to Job + 1.19.21 - Add ValidateFileRequest FileOperations WorkItem + 1.19.22 - Added ExtractIndexFileData WorkItem + 1.19.23 - Added IndexFieldMapperRequest WorkItem + 1.19.24 - Added DealerAchNotification WorkItem + 1.19.25 - Add RealTimeAlerts WorkItem + 1.19.26 - Add RealTimeAlerts Errored Process WorkItem + 1.20.0 - Upgrade to dotnet8 + 1.20.1 - Add CopyJobRequest and CopyJobResponse + 1.20.2 - Remove CopyJobResponse + 1.20.3 - Add Incentive Mgr Work Items + 1.20.4 - Add ValidateFileSizeInRangeRequest + 1.20.5 - Added DeleteEmptyDirectoryRequest WorkItem + 1.20.6 - Added DocumentRetentionCleanup WorkItem + 1.20.7 - Update DispatcherReturnResponse + 1.20.8 - Add DetermineOperationType Enum + 1.20.9 - Update Filter Defaults for JobStatus + 1.20.10 - Add JobStatus to JobDetails + 1.20.11 - Added VoAppsCampaignErrorResults WorkItem + 1.20.12 - Update JobErrorDetail to use JobStatus + 1.20.13 - Update for RepoAPI + 1.20.14 - Update DocumentIngestorModels Nuget package + + latest + + + + + + + + + diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/DispatcherReturnResponse.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/DispatcherReturnResponse.cs new file mode 100644 index 0000000..da89e73 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/DispatcherReturnResponse.cs @@ -0,0 +1,7 @@ +namespace LCU.Library.Internal.DispatcherModels.Models; + +public class DispatcherReturnResponse +{ + public int WorkItemId { get; set; } + public bool Skipped { get; set; } = false; +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Errorable.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Errorable.cs new file mode 100644 index 0000000..661c14d --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Errorable.cs @@ -0,0 +1,6 @@ +namespace LCU.Library.Internal.DispatcherModels.Models; + +public class Errorable +{ + public int? AttemptsToExecute { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Filter.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Filter.cs new file mode 100644 index 0000000..09e12dd --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Filter.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +namespace LCU.Library.Internal.DispatcherModels.Models; + +public class Filter +{ + /// + /// Filter has default of all statuses minus Inactive and default to NextRunTime Ascending for sorting. + /// This prevents null NextRunTimes to display first, user will see the most recent NextRunTime + /// + public Filter() + { + var listStatus = new List(); + foreach (var item in Enum.GetValues(typeof(Enums.JobStatus))) + { + if ((Enums.JobStatus)item != Enums.JobStatus.Inactive) + { + StatusFilter statusFilter = new() { IsEnabled = true, JobStatus = (Enums.JobStatus)item }; + listStatus.Add(statusFilter); + } + } + + var listSortableFields = new List(); + + SortableField sortField = new() { Field = Constants.JobSortableFields.NextRunTime, IsDescending = false }; + listSortableFields.Add(sortField); + + StatusFilters = listStatus; + SortableFields = listSortableFields; + Page = 1; + Limit = 10; + } + public string JobName { get; set; } + public string JobDescription { get; set; } + public DateTime? NextRuntimeGreaterThanDate { get; set; } = null; + public List StatusFilters { get; set; } + public List TagIdFilters { get; set; } + public List SortableFields { get; set; } + [Range(1, 1000)] + public int Page { get; set; } + [Range(1, 1000)] + public int Limit { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/JobDetails.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/JobDetails.cs new file mode 100644 index 0000000..d6dbfd8 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/JobDetails.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using LCU.Library.Internal.DispatcherModels.Entities; +using LCU.Library.Internal.DispatcherModels.Enums; +namespace LCU.Library.Internal.DispatcherModels.Models; + +public class JobDetails +{ + public int JobId { get; set; } = 0; + + [StringLength(200, ErrorMessage = "Requesting Application Max Length is 200")] + public string RequestingApplication { get; set; } + + [Required] + [StringLength(200, ErrorMessage = "Name Max Length is 200")] + public string Name { get; set; } + + [StringLength(8000, ErrorMessage = "Description Max Length is 8000")] + public string Description { get; set; } + + public string CreatedBy { get; set; } + + public DateTime CreatedAt { get; set; } + + public string ModifiedBy { get; set; } + + public DateTime? ModifiedDate { get; set; } + + public string CronSchedule { get; set; } + + public DateTime? NextRunTime { get; set; } + + public bool SkipHolidays { get; set; } = false; + + public JobStatus JobStatus { get; set; } = JobStatus.Inactive; + + public List ErrorRecipients { get; set; } + + public List JobTags { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/JobErrorDetail.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/JobErrorDetail.cs new file mode 100644 index 0000000..84f8a9c --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/JobErrorDetail.cs @@ -0,0 +1,9 @@ +namespace LCU.Library.Internal.DispatcherModels.Models; + +public class JobErrorDetail +{ + public string JobName { get; set; } + public string WorkItemName { get; set; } + public string JobStatus { get; set; } + public string StartTime { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/PagedResponse.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/PagedResponse.cs new file mode 100644 index 0000000..99b7df7 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/PagedResponse.cs @@ -0,0 +1,31 @@ +using LCU.Library.Internal.DispatcherModels.Models.Results; +using System; +using System.Collections.Generic; +namespace LCU.Library.Internal.DispatcherModels.Models; + +public class PagedResponse where T : class +{ + public PagedResponse() { } + public PagedResponse(PagedResult pagedResult, Filter filter) + { + TotalItems = pagedResult.Total; + Items = pagedResult.Items; + TotalPages = pagedResult.Total == 0 ? 1 : (int)Math.Ceiling((decimal)pagedResult.Total / filter.Limit); + CurrentPage = filter.Page > this.TotalPages ? this.TotalPages : filter.Page; + + if (pagedResult.Total > (filter.Page * filter.Limit)) + { + NextPage = filter.Page + 1; + } + if (filter.Page > 1) + { + PreviousPage = filter.Page - 1; + } + } + public IEnumerable Items { get; set; } + public int NextPage { get; set; } + public int PreviousPage { get; set; } + public int TotalItems { get; set; } + public int CurrentPage { get; set; } + public int TotalPages { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/BatchedErroredJobAlertRequest.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/BatchedErroredJobAlertRequest.cs new file mode 100644 index 0000000..f8ebed9 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/BatchedErroredJobAlertRequest.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace LCU.Library.Internal.DispatcherModels.Models.Requests +{ + public class BatchedErroredJobAlertRequest + { + public List ErroredJobAlertRequests { get; set; } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/CopyJobRequest.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/CopyJobRequest.cs new file mode 100644 index 0000000..3c4f0b7 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/CopyJobRequest.cs @@ -0,0 +1,7 @@ +namespace LCU.Library.Internal.DispatcherModels.Models.Requests; + +public class CopyJobRequest +{ + public int JobId { get; set; } + public bool ChangeEnvironment { get; set; } = true; +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/DetermineOperationRequest.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/DetermineOperationRequest.cs new file mode 100644 index 0000000..89e2b01 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/DetermineOperationRequest.cs @@ -0,0 +1,12 @@ +using LCU.Library.Internal.DispatcherModels.Entities; +using LCU.Library.Internal.DispatcherModels.Enums; + +namespace LCU.Library.Internal.DispatcherModels.Models.Requests +{ + public class DetermineOperationRequest + { + public Job CurrentJob { get; set; } + public WorkItem CurrentWorkItem { get; set; } + public DetermineOperationType DetermineOperationType { get; set; } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/ErroredJobAlertRequest.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/ErroredJobAlertRequest.cs new file mode 100644 index 0000000..a1d1044 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/ErroredJobAlertRequest.cs @@ -0,0 +1,10 @@ +using LCU.Library.Internal.DispatcherModels.Entities; + +namespace LCU.Library.Internal.DispatcherModels.Models.Requests +{ + public class ErroredJobAlertRequest + { + public Job CurrentJob { get; set; } + public WorkItem CurrentWorkItem { get; set; } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/JobFailureRequest.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/JobFailureRequest.cs new file mode 100644 index 0000000..a447e77 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Requests/JobFailureRequest.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace LCU.Library.Internal.DispatcherModels.Models.Requests +{ + public class JobFailureRequest + { + public int JobId { get; set; } + public int WorkItemId { get; set; } + public List ExecutionMessages { get; set; } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Results/CleanUpJobBatchResult.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Results/CleanUpJobBatchResult.cs new file mode 100644 index 0000000..523c0a4 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Results/CleanUpJobBatchResult.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; + +namespace LCU.Library.Internal.DispatcherModels.Models.Results +{ + public class CleanUpJobBatchResult + { + public List Messages { get; set; } = new List(); + public int FailureCount { get; set; } = 0; + + + [NotMapped] + public bool IsSuccessfull + { + get + { + return FailureCount == 0; + } + } + + [NotMapped] + public bool IsFailure + { + get + { + return FailureCount > 0; + } + } + + public void AddToFailureCount(string message) + { + Messages.Add(message); + FailureCount++; + } + } +} diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Results/PagedResult.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Results/PagedResult.cs new file mode 100644 index 0000000..56c1745 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/Results/PagedResult.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; +namespace LCU.Library.Internal.DispatcherModels.Models.Results; + +public class PagedResult where T : class +{ + public IEnumerable Items { get; set; } + public int Total { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/SortableField.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/SortableField.cs new file mode 100644 index 0000000..dce3546 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/SortableField.cs @@ -0,0 +1,8 @@ +namespace LCU.Library.Internal.DispatcherModels.Models; + +public class SortableField +{ + public string Field { get; set; } + public int Position { get; set; } + public bool IsDescending { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/StatusFilter.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/StatusFilter.cs new file mode 100644 index 0000000..ee316ba --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/StatusFilter.cs @@ -0,0 +1,8 @@ +using LCU.Library.Internal.DispatcherModels.Enums; +namespace LCU.Library.Internal.DispatcherModels.Models; + +public class StatusFilter +{ + public bool IsEnabled { get; set; } + public JobStatus JobStatus { get; set; } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/WorkItemDetails.cs b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/WorkItemDetails.cs new file mode 100644 index 0000000..3938c50 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/LCU.Library.Internal.DispatcherModels/Models/WorkItemDetails.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; + +namespace LCU.Library.Internal.DispatcherModels.Models +{ + public class WorkItemDetails + { + public int WorkItemId { get; set; } = 0; + public int JobId { get; set; } = 0; + [Required] + [StringLength(200, ErrorMessage = "Name Max Length is 200")] + public string Name { get; set; } + [StringLength(8000, ErrorMessage = "Description Max Length is 8000")] + public string Description { get; set; } + public bool CancelJobOnFailure { get; set; } = false; + [Range(0, 100, ErrorMessage = "Limit Range must be between 0-100.")] + public int CancelJobLimit { get; set; } = 0; + [Required] + public int Order { get; set; } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/README.md b/LCU.API.Internal.Dispatcher/README.md new file mode 100644 index 0000000..e623303 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/README.md @@ -0,0 +1,89 @@ +# Steps to Add New WorkItem to Existing Setup Api +1. Add new ActionType +
a. Location: LCU.Library.Internal.DispatcherModels Library --> Enums namespace --> ActionType.cs class +
b. Should add number the enum correlates to and to make sure to use the next number inline. +2. Update the API's Nuget version in LCU.Api.Internal.DispatcherSharedLib Library. +
a. Right click on LCU.Api.Internal.DispatcherSharedLib Library and select Manage Nuget Packages. +
b. Find API which will be updated. +
c. Update +3. Add Configuration to API's Configuration Class +
a. Location: LCU.Api.Internal.DispatcherSharedLib Library --> Configurations namespace--> ..Configuration.cs class +
b. Add new Endpoint Property +
c. Add NotMapped Url Property +4. Add new Endpoint to API's Configuration to Appsettings. +5. Add new ActionService Build to switch. +
a. Location: LCU.Api.Internal.DispatcherSharedLib Library --> BuilderServices namespace --> ActionBuilderService.cs class --> BuildAction method +
b. Add to #region for API. Example #Region FileOperationsApi. +
c. Add new switch following the existing switch's pattern. +6. Add new unit test to the ActionBuildServiceTest. +7. Update Version/Description/PackageReleaseNotes LCU.Api.Internal.DispatcherSharedLib. +
a. Increase by 0.0.1 for new WorkItems. +8. Update Version/Description/PackageReleaseNotes LCU.Library.Internal.DispatcherModels. +
a. Increase by 0.0.1 for new ActionTypes. +9. Add new endpoint to Release Variables. +
a. Set variable to be release. + +# Steps to Add New WorkItem to New API Setup +1. Add new ActionType +
a. Location: LCU.Library.Internal.DispatcherModels Library --> Enums namespace --> ActionType.cs class +
b. Should add number the enum correlates to and to make sure to use the next number inline. +2. Install the new API's Nuget version in LCU.Api.Internal.DispatcherSharedLib Library. +
a. Right click on LCU.Api.Internal.DispatcherSharedLib Library and select Manage Nuget Packages. +
b. Find API which will be installed. +
c. install +3. Create new Configurations class for new API +
a. Location: LCU.Api.Internal.DispatcherSharedLib Library --> Configurations namespace--> ..Configuration.cs class +
b. Add URL Property +
c. Add New Endpoint Property +
d. Add NotMapped URL Property that will be URL + Endpoint. Follow already existing Configration classes as example. +4. Add new Endpoint to API's Configuration to Appsettings. +5. Bind New Configration Model in DependencyInjection.cs +
a. Location: LCU.Api.Internal.DispatcherSharedLib --> DependencyInjection.cs +6. Add new ActionService Build to switch. +
a. Location: LCU.Api.Internal.DispatcherSharedLib Library --> BuilderServices namespace --> ActionBuilderService.cs class --> BuildAction method +
b. Pull in new Configration class. +
c. Create new #region for API. Use #Region FileOperationsApi as an example. +
d. Add new switch following the existing switch's pattern. +7. Add new Region for Api and new unit test to the ActionBuildServiceTest. +8. Update Version/Description/PackageReleaseNotes LCU.Api.Internal.DispatcherSharedLib. +
a. Increase by 0.0.1 for new WorkItems. +9. Update Version/Description/PackageReleaseNotes LCU.Library.Internal.DispatcherModels. Increase by 0.0.1 for new ActionTypes. +
a. Increase by 0.0.1 for new ActionTypes. +10. Add new URL and new endpoint to Release Variables. +
a. Set variable to be release. +
b. Set each URL appropriately for each environment + +Notes: + - Only HttpMethodType.Post is set up to be used. + - The model being added needs to have extend the ReturnResponse model + - Will need to add the QueueReturnResponse.QueueResponse([Model]) to the API upon completion. + +#Filter and Sorting +Filtering and sorting are handled by creating IQueryable and IEnumarable classes/methods. IQueryable is the preferred route as it will utilize the power of the database querying rather than filtering/sorting after the data has been gathered. Only use IEnumarable if it is more complicated to write the linq statement then already written C# code. + +Once a Filter Property and a IQueryable/IEnumarable class/method are created you will just need to append in the desired entities repository method in which is being filtered. + +Adding Filtering Steps: +1. Add new property to Filter model. Use another model if needed more information. +2. Create IQueryable/IEnumarable classes/methods in the proper namespaces. +3. Append new method to your context query call or after if using an IEnumarable. +4. Test + +Adding Sorting Steps: +1. Add string constant +2. Add string constant to list of constants. + +# Steps to setup local database +1. Download and install Sql Server 2022 Developer edition +2. Right click the LCU.Api.Internal.DispatcherSql project and select the Publish option +3. Click Edit in the Target Database Connection Settings. Use the following settings: + Server Name: localhost + Authentication: Windows Authentication + User Name: LCU\YOUR NAME (This should already be populated) + Password: + Database Name: master +5. Test connection and ensure it was successful. Click Ok. The target database connection will now be populated. +6. Enter Dispatcher for Database Name +7. Enter LCU.Api.Internal.DispatcherSql.sql for Publish Script Name +8. Click Publish. You now have a local sql server setup with the dispatcher database. +9. You can download and install sql management studio to view database locally. \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/azdo_pipelines/azure-k8s-pipeline.yaml b/LCU.API.Internal.Dispatcher/azdo_pipelines/azure-k8s-pipeline.yaml new file mode 100644 index 0000000..c8a4610 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/azdo_pipelines/azure-k8s-pipeline.yaml @@ -0,0 +1,72 @@ +parameters: +- name: target_environment + displayName: Target Environment + type: string + default: qa + values: + - qa + - uat + - prd +- name: k8s_namespace + displayName: K8s Namespace to deploy to + type: string + default: landmarkcu + values: + - landmarkcu +variables: +- template: ./config-files/${{ parameters.target_environment }}-pvars.yaml +- name: source_branch_name + value: $[replace(replace(variables['Build.SourceBranch'], 'refs/heads/', ''), '/', '-')] +- name: docker_container_repository_name + value: landmarkcu/lcu.api.internal.dispatcher-dispatcher +resources: + repositories: + - repository: azdo_pipeline_templates + type: git + name: Infrastructure Platform/azdo-pipeline-templates + ref: main +stages: + +# DOTNET BUILD AND NUGET PUSH +- template: dotnet-nuget-build-push.yaml@azdo_pipeline_templates + parameters: + azdo_agent_pool: $(pvar_azdo_agent_pool_nuget) + azure_artifacts_feed: 'LCU-Internal' + branches_to_push: + - develop + - main + +# DACPAC BUILD AND PUSH +- template: db-build-deploy.yaml@azdo_pipeline_templates + parameters: + azdo_agent_pool: $(pvar_azdo_agent_pool_db) + azdo_deployment_environment: $(pvar_azdo_deployment_environment_db) + sql_project_path: './LCU.Api.Internal.DispatcherSql/LCU.Api.Internal.DispatcherSql.sqlproj' + built_sql_dacpac_file_path: './LCU.Api.Internal.DispatcherSql/bin/Release/LCU.Api.Internal.DispatcherSql.dacpac' + name_of_artifact: LCU_API_Internal_DispatcherSql_dacpac + db_name: $(pvar_LCU_API_Internal_DispatcherSql_db_name) + sql_server_name: $(pvar_LCU_API_Internal_DispatcherSql_sql_server_name) + +# DOCKER BUILD AND PUSH +- template: docker-build-push.yaml@azdo_pipeline_templates + parameters: + azdo_agent_pool: $(pvar_azdo_agent_pool) + docker_registry_service_connection: $(pvar_docker_registry_service_connection) + docker_container_repository_name: $(docker_container_repository_name) + dockerfile_path: ./Dockerfile + docker_image_tags: | + build-$(Build.BuildId) + $(source_branch_name) + +# K8S DEPLOY +- template: k8s-deploy.yaml@azdo_pipeline_templates + parameters: + dependsOn: CI + condition: or(eq(variables['Build.SourceBranchName'], 'develop'), eq(variables['Build.SourceBranchName'], 'main')) + azdo_agent_pool: $(pvar_azdo_agent_pool) + kubectl_version: $(pvar_kubectl_version) + k8s_service_connection: $(pvar_k8s_service_connection) + k8s_namespace: ${{ parameters.k8s_namespace }} + k8s_kustomization_path: ./kustomize/overlays/${{ parameters.target_environment }} + docker_registry_service_connection: $(pvar_docker_registry_service_connection) + docker_image_to_deploy: $(docker_container_repository_name):build-$(Build.BuildId) diff --git a/LCU.API.Internal.Dispatcher/azdo_pipelines/azure-pipelines-feature-branches.yaml b/LCU.API.Internal.Dispatcher/azdo_pipelines/azure-pipelines-feature-branches.yaml new file mode 100644 index 0000000..ee44aef --- /dev/null +++ b/LCU.API.Internal.Dispatcher/azdo_pipelines/azure-pipelines-feature-branches.yaml @@ -0,0 +1,10 @@ +trigger: + branches: + include: + - '*' + exclude: + - 'main' + - 'develop' +extends: + template: azure-k8s-pipeline.yaml + diff --git a/LCU.API.Internal.Dispatcher/azdo_pipelines/azure-pipelines-prd.yaml b/LCU.API.Internal.Dispatcher/azdo_pipelines/azure-pipelines-prd.yaml new file mode 100644 index 0000000..7d13684 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/azdo_pipelines/azure-pipelines-prd.yaml @@ -0,0 +1,9 @@ +trigger: + branches: + include: + - 'main' +extends: + template: azure-k8s-pipeline.yaml + parameters: + target_environment: prd + k8s_namespace: landmarkcu diff --git a/LCU.API.Internal.Dispatcher/azdo_pipelines/azure-pipelines-qa.yaml b/LCU.API.Internal.Dispatcher/azdo_pipelines/azure-pipelines-qa.yaml new file mode 100644 index 0000000..f581dd4 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/azdo_pipelines/azure-pipelines-qa.yaml @@ -0,0 +1,9 @@ +trigger: + branches: + include: + - 'develop' +extends: + template: azure-k8s-pipeline.yaml + parameters: + target_environment: qa + k8s_namespace: landmarkcu diff --git a/LCU.API.Internal.Dispatcher/azdo_pipelines/azure-pipelines-uat.yaml b/LCU.API.Internal.Dispatcher/azdo_pipelines/azure-pipelines-uat.yaml new file mode 100644 index 0000000..4747234 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/azdo_pipelines/azure-pipelines-uat.yaml @@ -0,0 +1,9 @@ +trigger: + branches: + include: + - 'main' +extends: + template: azure-k8s-pipeline.yaml + parameters: + target_environment: uat + k8s_namespace: landmarkcu diff --git a/LCU.API.Internal.Dispatcher/azdo_pipelines/config-files/prd-pvars.yaml b/LCU.API.Internal.Dispatcher/azdo_pipelines/config-files/prd-pvars.yaml new file mode 100644 index 0000000..4e6619b --- /dev/null +++ b/LCU.API.Internal.Dispatcher/azdo_pipelines/config-files/prd-pvars.yaml @@ -0,0 +1,12 @@ +--- +variables: + pvar_azdo_agent_pool: vmss-sub-df-lcu-apps-prd-lz-uscz + pvar_docker_registry_service_connection: Docker Registry LMCU + pvar_k8s_service_connection: svcon-sub-df-lcu-apps-prd-aks-k8s-uscz + pvar_kubectl_version: 1.25.9 + pvar_azdo_agent_pool_nuget: LCUOnPrem + pvar_azdo_agent_pool_db: LCUOnPrem + pvar_azdo_deployment_environment_db: DB-PRD + + pvar_LCU_API_Internal_DispatcherSql_db_name: Dispatcher + pvar_LCU_API_Internal_DispatcherSql_sql_server_name: D1CLS01P diff --git a/LCU.API.Internal.Dispatcher/azdo_pipelines/config-files/qa-pvars.yaml b/LCU.API.Internal.Dispatcher/azdo_pipelines/config-files/qa-pvars.yaml new file mode 100644 index 0000000..cdea599 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/azdo_pipelines/config-files/qa-pvars.yaml @@ -0,0 +1,12 @@ +--- +variables: + pvar_azdo_agent_pool: vmss-sub-df-lcu-apps-qa-lz-uscz + pvar_docker_registry_service_connection: Docker Registry LMCU + pvar_k8s_service_connection: svcon-sub-df-lcu-apps-qa-aks-k8s-uscz + pvar_kubectl_version: 1.25.9 + pvar_azdo_agent_pool_nuget: LCUOnPrem + pvar_azdo_agent_pool_db: LCUOnPrem + pvar_azdo_deployment_environment_db: DB-QA + + pvar_LCU_API_Internal_DispatcherSql_db_name: Dispatcher + pvar_LCU_API_Internal_DispatcherSql_sql_server_name: DAZCDTBWEB01Q diff --git a/LCU.API.Internal.Dispatcher/azdo_pipelines/config-files/uat-pvars.yaml b/LCU.API.Internal.Dispatcher/azdo_pipelines/config-files/uat-pvars.yaml new file mode 100644 index 0000000..5e72d16 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/azdo_pipelines/config-files/uat-pvars.yaml @@ -0,0 +1,12 @@ +--- +variables: + pvar_azdo_agent_pool: vmss-sub-df-lcu-apps-uat-lz-uscz + pvar_docker_registry_service_connection: Docker Registry LMCU + pvar_k8s_service_connection: svcon-sub-df-lcu-apps-uat-aks-k8s-uscz + pvar_kubectl_version: 1.25.9 + pvar_azdo_agent_pool_nuget: LCUOnPrem + pvar_azdo_agent_pool_db: LCUOnPrem + pvar_azdo_deployment_environment_db: DB-UAT + + pvar_LCU_API_Internal_DispatcherSql_db_name: Dispatcher + pvar_LCU_API_Internal_DispatcherSql_sql_server_name: D1CLS01U diff --git a/LCU.API.Internal.Dispatcher/azure-pipelines.yml b/LCU.API.Internal.Dispatcher/azure-pipelines.yml new file mode 100644 index 0000000..a0ea760 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/azure-pipelines.yml @@ -0,0 +1,87 @@ +# ASP.NET Core + +name: 'Dispatcher.$(Build.SourceBranchName).$(Date:yyyyMMdd)$(Rev:.r)' +trigger: + branches: + include: + - '*' + +pool: + name: LCUOnPrem + demands: + - msbuild + - visualstudio + - vstest + +variables: + buildConfiguration: 'Release' + +steps: +- task: DotNetCoreCLI@2 + inputs: + command: 'restore' + feedsToUse: 'select' + vstsFeed: '58ebbac4-8e98-4281-bb3f-58003852f4a7' + +- task: DotNetCoreCLI@2 + inputs: + command: 'build' + projects: '**/*.csproj' + arguments: '--configuration $(buildConfiguration)' + +- task: DotNetCoreCLI@2 + displayName: Test + inputs: + command: test + projects: '**/*Tests.csproj' + arguments: '--configuration $(BuildConfiguration) --collect "Code coverage"' + +- task: DotNetCoreCLI@2 + displayName: 'dotnet publish $(buildConfiguration)' + inputs: + command: 'publish' + publishWebProjects: false + projects: '**/*.csproj' + arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)' + zipAfterPublish: true + +- task: NuGetCommand@2 + condition: and (succeeded(), or( eq(variables['Build.SourceBranchName'], 'develop'), eq(variables['Build.SourceBranchName'], 'main')) ) + displayName: 'NuGet push' + inputs: + command: push + packagesToPush: 'LCU.Api.Internal.DispatcherSharedLib/**/*.nupkg' + publishVstsFeed: '58ebbac4-8e98-4281-bb3f-58003852f4a7' + allowPackageConflicts: true + +- task: NuGetCommand@2 + condition: and (succeeded(), or( eq(variables['Build.SourceBranchName'], 'develop'), eq(variables['Build.SourceBranchName'], 'main')) ) + displayName: 'NuGet push' + inputs: + command: push + packagesToPush: 'LCU.Library.Internal.DispatcherModels/**/*.nupkg' + publishVstsFeed: '58ebbac4-8e98-4281-bb3f-58003852f4a7' + allowPackageConflicts: true + +- task: MSBuild@1 + displayName: Build sql project + inputs: + solution: './LCU.Api.Internal.DispatcherSql/LCU.Api.Internal.DispatcherSql.sqlproj' + configuration: 'Release' + +- task: CopyFiles@2 + displayName: Copy dacpac to artifact staging directory + inputs: + SourceFolder: './LCU.Api.Internal.DispatcherSql/bin/$(BuildConfiguration)/' + Contents: 'LCU.Api.Internal.DispatcherSql.dacpac' + TargetFolder: '$(build.artifactstagingdirectory)' + OverWrite: true + +- task: PublishBuildArtifacts@1 + condition: and (succeeded(), or( eq(variables['Build.SourceBranchName'], 'develop'), eq(variables['Build.SourceBranchName'], 'main')) ) + displayName: 'Publish Artifact: dist' + inputs: + PathtoPublish: '$(build.artifactstagingdirectory)' + ArtifactName: 'dist' + + diff --git a/LCU.API.Internal.Dispatcher/kustomize/base/Deployment.yaml b/LCU.API.Internal.Dispatcher/kustomize/base/Deployment.yaml new file mode 100644 index 0000000..1cd04b3 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/base/Deployment.yaml @@ -0,0 +1,44 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dispatcher-api +spec: + replicas: 1 + selector: + matchLabels: + app: dispatcher-api + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app: dispatcher-api + spec: + volumes: [] + containers: + - name: dispatcher-api + env: [] + volumeMounts: [] + image: dispatcher-api + resources: + limits: + memory: "1536Mi" + cpu: "500m" + ports: + - containerPort: 8080 + livenessProbe: + initialDelaySeconds: 5 + periodSeconds: 5 + httpGet: + path: /api/status + port: 8080 + readinessProbe: + initialDelaySeconds: 5 + periodSeconds: 5 + httpGet: + path: /api/status + port: 8080 diff --git a/LCU.API.Internal.Dispatcher/kustomize/base/Service.yaml b/LCU.API.Internal.Dispatcher/kustomize/base/Service.yaml new file mode 100644 index 0000000..2e2d2a5 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/base/Service.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: dispatcher-api +spec: + selector: + app: dispatcher-api + ports: + - port: 8080 + targetPort: 8080 diff --git a/LCU.API.Internal.Dispatcher/kustomize/base/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/base/kustomization.yaml new file mode 100644 index 0000000..26e5294 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/base/kustomization.yaml @@ -0,0 +1,9 @@ +resources: + - Deployment.yaml + - Service.yaml + - virtual-service.yaml + - secrets-provider-class.yaml + +images: +- name: dispatcher-api + newName: landmarkcu/lcu.api.internal.dispatcher-dispatcher \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/base/secrets-provider-class.yaml b/LCU.API.Internal.Dispatcher/kustomize/base/secrets-provider-class.yaml new file mode 100644 index 0000000..c36e24d --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/base/secrets-provider-class.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: dispatcher-api-csi-key-vault-secrets-provider +spec: + provider: azure + parameters: + usePodIdentity: "false" + useVMManagedIdentity: "true" + tenantId: 63cd1554-2783-4a86-ae50-b9e6c21c9499 + \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/base/virtual-service.yaml b/LCU.API.Internal.Dispatcher/kustomize/base/virtual-service.yaml new file mode 100644 index 0000000..dae0b8c --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/base/virtual-service.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: dispatcher-api +spec: + hosts: [] + gateways: + - istio-system/ingress-gateway + http: + - name: dispatcher-api + match: + - uri: + prefix: / + route: + - destination: + host: dispatcher-api + port: + number: 8080 diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/keyvaults/normal/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/keyvaults/normal/kustomization.yaml new file mode 100644 index 0000000..0ae1552 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/keyvaults/normal/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +patches: + - target: + kind: Deployment + path: set_secrets_deployment.yaml + - target: + kind: SecretProviderClass + path: set_secrets_provider.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/keyvaults/normal/set_secrets_deployment.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/keyvaults/normal/set_secrets_deployment.yaml new file mode 100644 index 0000000..02a0fc2 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/keyvaults/normal/set_secrets_deployment.yaml @@ -0,0 +1,49 @@ +- op: add + path: /spec/template/spec/volumes/- + value: + name: dispatcher-api-secrets + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: dispatcher-api-csi-key-vault-secrets-provider + +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + name: dispatcher-api-secrets + mountPath: /vault/secrets/dispatcher-api-database-connection.json + readOnly: true + subPath: dispatcher-api-database-connection + +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + name: dispatcher-api-secrets + mountPath: /vault/secrets/dispatcher-api-seq-api-key.json + readOnly: true + subPath: dispatcher-api-seq-api-key + +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + name: dispatcher-api-secrets + mountPath: /vault/secrets/dispatcher-api-rabbitmq-username.json + readOnly: true + subPath: dispatcher-api-rabbitmq-username + +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + name: dispatcher-api-secrets + mountPath: /vault/secrets/dispatcher-api-rabbitmq-password.json + readOnly: true + subPath: dispatcher-api-rabbitmq-password + +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + name: dispatcher-api-secrets + mountPath: /vault/secrets/dispatcher-api-external-api-key-set.json + readOnly: true + subPath: dispatcher-api-external-api-key-set \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/keyvaults/normal/set_secrets_provider.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/keyvaults/normal/set_secrets_provider.yaml new file mode 100644 index 0000000..c466f39 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/keyvaults/normal/set_secrets_provider.yaml @@ -0,0 +1,24 @@ +- op: add + path: /spec/parameters/objects + value: | + array: + - | + objectName: dispatcher-api-database-connection + objectType: secret + objectVersion: "" + - | + objectName: dispatcher-api-seq-api-key + objectType: secret + objectVersion: "" + - | + objectName: dispatcher-api-rabbitmq-username + objectType: secret + objectVersion: "" + - | + objectName: dispatcher-api-rabbitmq-password + objectType: secret + objectVersion: "" + - | + objectName: dispatcher-api-external-api-key-set + objectType: secret + objectVersion: "" \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/completion/appsettings/appsettings.json b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/completion/appsettings/appsettings.json new file mode 100644 index 0000000..761c9a7 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/completion/appsettings/appsettings.json @@ -0,0 +1,14 @@ +{ + "Primary": { + "FeatureFlags": { + "RunBatchErroredJobsHostedService": false, + "RunCleanUpJobsHostedService": false, + "RunCompletionConsumer": true, + "RunExecutionConsumer": false, + "RunJobSchedulerHostedService": false, + "RunDetermineOperationConsumer": false, + "RunJobFailureConsumer": false, + "RunSendErroredJobAlertConsumer": false + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/completion/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/completion/kustomization.yaml new file mode 100644 index 0000000..49a61b7 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/completion/kustomization.yaml @@ -0,0 +1,40 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +#Test istio process +namePrefix: completion- + +configMapGenerator: + - name: dispatcher-api-appsettings + files: + - appsettings/appsettings.json +generatorOptions: + disableNameSuffixHash: true + +patches: + - target: + kind: Deployment + path: set_appsettings_deployment.yaml + +replacements: + - source: + kind: Deployment + fieldPath: metadata.name + targets: + - select: + kind: Deployment + fieldPaths: + - spec.selector.matchLabels.app + - spec.template.metadata.labels.app + - spec.template.spec.containers.*.name + + - select: + kind: Service + fieldPaths: + - spec.selector.app + + - select: + kind: VirtualService + fieldPaths: + - spec.http.*.route.*.destination.host + - spec.http.*.name diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/completion/set_appsettings_deployment.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/completion/set_appsettings_deployment.yaml new file mode 100644 index 0000000..b8bcc99 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/completion/set_appsettings_deployment.yaml @@ -0,0 +1,12 @@ +- op: add + path: /spec/template/spec/volumes/- + value: + name: volume-appsettings + configMap: + name: dispatcher-api-appsettings + +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + name: volume-appsettings + mountPath: /app/config/workload-appsettings diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/determine-operation/appsettings/appsettings.json b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/determine-operation/appsettings/appsettings.json new file mode 100644 index 0000000..b0aef90 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/determine-operation/appsettings/appsettings.json @@ -0,0 +1,14 @@ +{ + "Primary": { + "FeatureFlags": { + "RunBatchErroredJobsHostedService": false, + "RunCleanUpJobsHostedService": false, + "RunCompletionConsumer": false, + "RunExecutionConsumer": false, + "RunJobSchedulerHostedService": false, + "RunDetermineOperationConsumer": true, + "RunJobFailureConsumer": false, + "RunSendErroredJobAlertConsumer": false + } + } +} diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/determine-operation/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/determine-operation/kustomization.yaml new file mode 100644 index 0000000..43ccd7c --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/determine-operation/kustomization.yaml @@ -0,0 +1,40 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +#Test istio process +namePrefix: determine-operation- + +configMapGenerator: + - name: dispatcher-api-appsettings + files: + - appsettings/appsettings.json +generatorOptions: + disableNameSuffixHash: true + +patches: + - target: + kind: Deployment + path: set_appsettings_deployment.yaml + +replacements: + - source: + kind: Deployment + fieldPath: metadata.name + targets: + - select: + kind: Deployment + fieldPaths: + - spec.selector.matchLabels.app + - spec.template.metadata.labels.app + - spec.template.spec.containers.*.name + + - select: + kind: Service + fieldPaths: + - spec.selector.app + + - select: + kind: VirtualService + fieldPaths: + - spec.http.*.route.*.destination.host + - spec.http.*.name diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/determine-operation/set_appsettings_deployment.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/determine-operation/set_appsettings_deployment.yaml new file mode 100644 index 0000000..b8bcc99 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/determine-operation/set_appsettings_deployment.yaml @@ -0,0 +1,12 @@ +- op: add + path: /spec/template/spec/volumes/- + value: + name: volume-appsettings + configMap: + name: dispatcher-api-appsettings + +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + name: volume-appsettings + mountPath: /app/config/workload-appsettings diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/execution/appsettings/appsettings.json b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/execution/appsettings/appsettings.json new file mode 100644 index 0000000..0b6d664 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/execution/appsettings/appsettings.json @@ -0,0 +1,14 @@ +{ + "Primary": { + "FeatureFlags": { + "RunBatchErroredJobsHostedService": false, + "RunCleanUpJobsHostedService": false, + "RunCompletionConsumer": false, + "RunExecutionConsumer": true, + "RunJobSchedulerHostedService": false, + "RunDetermineOperationConsumer": false, + "RunJobFailureConsumer": false, + "RunSendErroredJobAlertConsumer": false + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/execution/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/execution/kustomization.yaml new file mode 100644 index 0000000..2256f06 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/execution/kustomization.yaml @@ -0,0 +1,40 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +#Test istio process +namePrefix: execution- + +configMapGenerator: + - name: dispatcher-api-appsettings + files: + - appsettings/appsettings.json +generatorOptions: + disableNameSuffixHash: true + +patches: + - target: + kind: Deployment + path: set_appsettings_deployment.yaml + +replacements: + - source: + kind: Deployment + fieldPath: metadata.name + targets: + - select: + kind: Deployment + fieldPaths: + - spec.selector.matchLabels.app + - spec.template.metadata.labels.app + - spec.template.spec.containers.*.name + + - select: + kind: Service + fieldPaths: + - spec.selector.app + + - select: + kind: VirtualService + fieldPaths: + - spec.http.*.route.*.destination.host + - spec.http.*.name diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/execution/set_appsettings_deployment.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/execution/set_appsettings_deployment.yaml new file mode 100644 index 0000000..b8bcc99 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/execution/set_appsettings_deployment.yaml @@ -0,0 +1,12 @@ +- op: add + path: /spec/template/spec/volumes/- + value: + name: volume-appsettings + configMap: + name: dispatcher-api-appsettings + +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + name: volume-appsettings + mountPath: /app/config/workload-appsettings diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-failure/appsettings/appsettings.json b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-failure/appsettings/appsettings.json new file mode 100644 index 0000000..58fb91f --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-failure/appsettings/appsettings.json @@ -0,0 +1,14 @@ +{ + "Primary": { + "FeatureFlags": { + "RunBatchErroredJobsHostedService": false, + "RunCleanUpJobsHostedService": false, + "RunCompletionConsumer": false, + "RunExecutionConsumer": false, + "RunJobSchedulerHostedService": false, + "RunDetermineOperationConsumer": false, + "RunJobFailureConsumer": true, + "RunSendErroredJobAlertConsumer": false + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-failure/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-failure/kustomization.yaml new file mode 100644 index 0000000..139ebd3 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-failure/kustomization.yaml @@ -0,0 +1,40 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +#Test istio process +namePrefix: job-failure- + +configMapGenerator: + - name: dispatcher-api-appsettings + files: + - appsettings/appsettings.json +generatorOptions: + disableNameSuffixHash: true + +patches: + - target: + kind: Deployment + path: set_appsettings_deployment.yaml + +replacements: + - source: + kind: Deployment + fieldPath: metadata.name + targets: + - select: + kind: Deployment + fieldPaths: + - spec.selector.matchLabels.app + - spec.template.metadata.labels.app + - spec.template.spec.containers.*.name + + - select: + kind: Service + fieldPaths: + - spec.selector.app + + - select: + kind: VirtualService + fieldPaths: + - spec.http.*.route.*.destination.host + - spec.http.*.name diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-failure/set_appsettings_deployment.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-failure/set_appsettings_deployment.yaml new file mode 100644 index 0000000..b8bcc99 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-failure/set_appsettings_deployment.yaml @@ -0,0 +1,12 @@ +- op: add + path: /spec/template/spec/volumes/- + value: + name: volume-appsettings + configMap: + name: dispatcher-api-appsettings + +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + name: volume-appsettings + mountPath: /app/config/workload-appsettings diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-scheduler/appsettings/appsettings.json b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-scheduler/appsettings/appsettings.json new file mode 100644 index 0000000..1082330 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-scheduler/appsettings/appsettings.json @@ -0,0 +1,15 @@ +{ + "Primary": { + "DefaultDelayMilliseconds": 2000, + "FeatureFlags": { + "RunBatchErroredJobsHostedService": false, + "RunCleanUpJobsHostedService": false, + "RunCompletionConsumer": false, + "RunExecutionConsumer": false, + "RunJobSchedulerHostedService": true, + "RunDetermineOperationConsumer": false, + "RunJobFailureConsumer": false, + "RunSendErroredJobAlertConsumer": false + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-scheduler/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-scheduler/kustomization.yaml new file mode 100644 index 0000000..950a73b --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-scheduler/kustomization.yaml @@ -0,0 +1,40 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +#Test istio process +namePrefix: job-scheduler- + +configMapGenerator: + - name: dispatcher-api-appsettings + files: + - appsettings/appsettings.json +generatorOptions: + disableNameSuffixHash: true + +patches: + - target: + kind: Deployment + path: set_appsettings_deployment.yaml + +replacements: + - source: + kind: Deployment + fieldPath: metadata.name + targets: + - select: + kind: Deployment + fieldPaths: + - spec.selector.matchLabels.app + - spec.template.metadata.labels.app + - spec.template.spec.containers.*.name + + - select: + kind: Service + fieldPaths: + - spec.selector.app + + - select: + kind: VirtualService + fieldPaths: + - spec.http.*.route.*.destination.host + - spec.http.*.name diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-scheduler/set_appsettings_deployment.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-scheduler/set_appsettings_deployment.yaml new file mode 100644 index 0000000..b8bcc99 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/job-scheduler/set_appsettings_deployment.yaml @@ -0,0 +1,12 @@ +- op: add + path: /spec/template/spec/volumes/- + value: + name: volume-appsettings + configMap: + name: dispatcher-api-appsettings + +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + name: volume-appsettings + mountPath: /app/config/workload-appsettings diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/normal/appsettings/appsettings.json b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/normal/appsettings/appsettings.json new file mode 100644 index 0000000..472efe8 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/normal/appsettings/appsettings.json @@ -0,0 +1,22 @@ +{ + "Primary": { + "BatchErroredJobsCronSchedule": "0 7-19 * * *", + "CleanUpJobsCronSchedule": "00 22 * * *", + "CleanUpJobsDaysToDelete": -30, + "FeatureFlags": { + "RunBatchErroredJobsHostedService": true, + "RunCleanUpJobsHostedService": true, + "RunCompletionConsumer": false, + "RunExecutionConsumer": false, + "RunJobSchedulerHostedService": false, + "RunDetermineOperationConsumer": false, + "RunJobFailureConsumer": false, + "RunSendErroredJobAlertConsumer": false + }, + "JobStatusesToMonitorByTimeLimit": [ + "Processing", + "DeterminingOperation", + "PendingToRun" + ] + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/normal/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/normal/kustomization.yaml new file mode 100644 index 0000000..3491491 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/normal/kustomization.yaml @@ -0,0 +1,40 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +#Test istio process +namePrefix: normal- + +configMapGenerator: + - name: dispatcher-api-appsettings + files: + - appsettings/appsettings.json +generatorOptions: + disableNameSuffixHash: true + +patches: + - target: + kind: Deployment + path: set_appsettings_deployment.yaml + +replacements: + - source: + kind: Deployment + fieldPath: metadata.name + targets: + - select: + kind: Deployment + fieldPaths: + - spec.selector.matchLabels.app + - spec.template.metadata.labels.app + - spec.template.spec.containers.*.name + + - select: + kind: Service + fieldPaths: + - spec.selector.app + + - select: + kind: VirtualService + fieldPaths: + - spec.http.*.route.*.destination.host + - spec.http.*.name diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/normal/set_appsettings_deployment.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/normal/set_appsettings_deployment.yaml new file mode 100644 index 0000000..b8bcc99 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/normal/set_appsettings_deployment.yaml @@ -0,0 +1,12 @@ +- op: add + path: /spec/template/spec/volumes/- + value: + name: volume-appsettings + configMap: + name: dispatcher-api-appsettings + +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + name: volume-appsettings + mountPath: /app/config/workload-appsettings diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/send-errored-job-alert/appsettings/appsettings.json b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/send-errored-job-alert/appsettings/appsettings.json new file mode 100644 index 0000000..9adde69 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/send-errored-job-alert/appsettings/appsettings.json @@ -0,0 +1,14 @@ +{ + "Primary": { + "FeatureFlags": { + "RunBatchErroredJobsHostedService": false, + "RunCleanUpJobsHostedService": false, + "RunCompletionConsumer": false, + "RunExecutionConsumer": false, + "RunJobSchedulerHostedService": false, + "RunDetermineOperationConsumer": false, + "RunJobFailureConsumer": false, + "RunSendErroredJobAlertConsumer": true + } + } +} \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/send-errored-job-alert/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/send-errored-job-alert/kustomization.yaml new file mode 100644 index 0000000..9d0c9b1 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/send-errored-job-alert/kustomization.yaml @@ -0,0 +1,40 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +#Test istio process +namePrefix: send-errored-job-alert- + +configMapGenerator: + - name: dispatcher-api-appsettings + files: + - appsettings/appsettings.json +generatorOptions: + disableNameSuffixHash: true + +patches: + - target: + kind: Deployment + path: set_appsettings_deployment.yaml + +replacements: + - source: + kind: Deployment + fieldPath: metadata.name + targets: + - select: + kind: Deployment + fieldPaths: + - spec.selector.matchLabels.app + - spec.template.metadata.labels.app + - spec.template.spec.containers.*.name + + - select: + kind: Service + fieldPaths: + - spec.selector.app + + - select: + kind: VirtualService + fieldPaths: + - spec.http.*.route.*.destination.host + - spec.http.*.name diff --git a/LCU.API.Internal.Dispatcher/kustomize/components/workloads/send-errored-job-alert/set_appsettings_deployment.yaml b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/send-errored-job-alert/set_appsettings_deployment.yaml new file mode 100644 index 0000000..b8bcc99 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/components/workloads/send-errored-job-alert/set_appsettings_deployment.yaml @@ -0,0 +1,12 @@ +- op: add + path: /spec/template/spec/volumes/- + value: + name: volume-appsettings + configMap: + name: dispatcher-api-appsettings + +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + name: volume-appsettings + mountPath: /app/config/workload-appsettings diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/local/common/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/local/common/kustomization.yaml new file mode 100644 index 0000000..42ba81b --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/local/common/kustomization.yaml @@ -0,0 +1,8 @@ +resources: + - ../../../base +patchesStrategicMerge: + - set_appsettings_deployment.yaml + - set_secrets_provider_class.yaml +images: +- name: dispatcher-api + newName: landmarkcu/lcu.api.internal.dispatcher-dispatcher diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/local/common/set_appsettings_deployment.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/local/common/set_appsettings_deployment.yaml new file mode 100644 index 0000000..cc2b67b --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/local/common/set_appsettings_deployment.yaml @@ -0,0 +1,12 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dispatcher-api +spec: + template: + spec: + containers: + - name: dispatcher-api + env: + - name: ASPNETCORE_ENVIRONMENT + value: "Development" diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/local/common/set_secrets_provider_class.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/local/common/set_secrets_provider_class.yaml new file mode 100644 index 0000000..0c36870 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/local/common/set_secrets_provider_class.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: dispatcher-api-csi-key-vault-secrets-provider +spec: + parameters: + userAssignedIdentityID: 3286ed9a-f85c-4c50-b742-dbd3c1c3fe1f + keyvaultName: kv-lz-apps-qa-uscz diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/local/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/local/kustomization.yaml new file mode 100644 index 0000000..25a65b6 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/local/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - normal diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/local/normal/configmap.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/local/normal/configmap.yaml new file mode 100644 index 0000000..2232d04 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/local/normal/configmap.yaml @@ -0,0 +1,89 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: dispatcherapi-configmap +data: + primary.json: | + { + "Primary": { + "SeqUrl": "https://qa-seq.lcu-internal.com", + "CorsAllowedOrigins": [ + "http://localhost:3000", + "http://lcu-internal.com", + "https://lcu-internal.com", + "https://qa-api.lcu-internal.com", + "https://uat-api.lcu-internal.com", + "https://api.lcu-internal.com" + ], + "ErrorMonitoringCronSchedule": "0 7-19 * * *", + "CleanUpJobsCronSchedule": "00 22 * * *", + "CleanUpJobsDaysToDelete": -30, + "FeatureFlags": { + "RunErrorMonitoringHostedService": true, + "RunCleanUpJobsHostedService": true, + "RunCompletionConsumer": false, + "RunExecutionConsumer": false, + "RunJobSchedulerHostedService": false, + "RunDetermineOperationConsumer": true, + "RunJobFailureConsumer": true, + "RunSendErroredJobAlertConsumer": true + } + }, + "FileOperationsApiConfiguration": { + "Url": "https://qa-fileoperationsapi.lcu-internal.com" + }, + "FtpApiConfiguration": { + "Url": "https://qa-ftpapi.lcu-internal.com" + }, + "NotificationApiConfiguration": { + "Url": "https://qa-notificationsapi.lcu-internal.com" + }, + "ArchiverApiConfiguration": { + "Url": "https://qa-archiver-api.apps.azure.lcu-internal.com" + }, + "BlendApiConfiguration": { + "Url": "https://qa-blend-api-wrapper-api.apps.azure.lcu-internal.com" + }, + "DialerApiConfiguration": { + "Url": "https://qa-dialer-api.apps.azure.lcu-internal.com" + }, + "DocumentIngestorApiConfiguration": { + "Url": "https://qa-document-ingestor-api.apps.azure.lcu-internal.com" + }, + "DocumentTypeCleanupApiConfiguration": { + "Url": "https://qa-document-type-cleanup-api.apps.azure.lcu-internal.com" + }, + "HistoricalDocumentIngestorApiConfiguration": { + "Url": "https://qa-document-ingestor-historical-api.apps.azure.lcu-internal.com" + }, + "RemoteDepositCaptureApiConfiguration": { + "Url": "https://qa-remote-deposit-capture-api.apps.azure.lcu-internal.com" + }, + "DataCleanUpApiConfiguration": { + "Url": "https://qa-data-clean-up-api.apps.azure.lcu-internal.com" + }, + "NoticeProcessorApiConfiguration": { + "Url": "https://qa-notice-processor-api.apps.azure.lcu-internal.com" + }, + "MomBotApiConfiguration": { + "Url": "https://qa-mombotapi.lcu-internal.com" + }, + "RealTimeAlertsApiConfiguration": { + "Url": "https://qa-real-time-alerts-api.apps.azure.lcu-internal.com" + }, + "DispatcherRequestBuilderConfiguration": { + "Url": "https://qa-dispatcherapi.lcu-internal.com/api/Ingestion" + }, + "ConnectionConfiguration": { + "HostUrl": "d1msg01q.lcu-internal.com" + }, + "IncentiveManagerConfiguration": { + "Url": "https://qa-incentive-manager-api.apps.azure.lcu-internal.com/api" + }, + "FileUpdaterApiConfiguration": { + "Url": "https://qa-file-updater-api.apps.azure.lcu-internal.com" + }, + "TemenosNotificationApiConfiguration": { + "Url": "https://qa-temenos-notification-api.apps.azure.lcu-internal.com" + } + } diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/local/normal/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/local/normal/kustomization.yaml new file mode 100644 index 0000000..6dd0c3b --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/local/normal/kustomization.yaml @@ -0,0 +1,5 @@ +resources: + - configmap.yaml + - ../common +patchesStrategicMerge: + - set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/local/normal/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/local/normal/set_virtual_service.yaml new file mode 100644 index 0000000..130e6a1 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/local/normal/set_virtual_service.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: networking.istio.io/v1beta1 +kind: VirtualService +metadata: + name: dispatcher-api +spec: + hosts: + - qa-dispatcher-api.apps.azure.lcu-internal.com diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/appsettings/appsettings.json b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/appsettings/appsettings.json new file mode 100644 index 0000000..5f096b3 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/appsettings/appsettings.json @@ -0,0 +1,72 @@ +{ + "Primary": { + "SeqUrl": "https://seq.lcu-internal.com", + "CorsAllowedOrigins": [ + "http://localhost:3000", + "http://lcu-internal.com", + "https://lcu-internal.com", + "https://api.lcu-internal.com" + ], + "DefaultErrorRecipient": "ITOperations@landmarkcu.com" + }, + "FileOperationsApiConfiguration": { + "Url": "https://fileoperationsapi.lcu-internal.com" + }, + "FtpApiConfiguration": { + "Url": "https://ftpapi.lcu-internal.com" + }, + "NotificationApiConfiguration": { + "Url": "https://prd-notifications-api.apps.azure.lcu-internal.com" + }, + "ArchiverApiConfiguration": { + "Url": "https://prd-archiver-api.apps.azure.lcu-internal.com" + }, + "BlendApiConfiguration": { + "Url": "https://prd-blend-api-wrapper-api.apps.azure.lcu-internal.com" + }, + "DialerApiConfiguration": { + "Url": "https://prd-dialer-api.apps.azure.lcu-internal.com" + }, + "DocumentIngestorApiConfiguration": { + "Url": "https://prd-document-ingestor-api.apps.azure.lcu-internal.com" + }, + "DocumentTypeCleanupApiConfiguration": { + "Url": "https://prd-document-type-cleanup-api.apps.azure.lcu-internal.com" + }, + "HistoricalDocumentIngestorApiConfiguration": { + "Url": "https://prd-document-ingestor-historical-api.apps.azure.lcu-internal.com" + }, + "RemoteDepositCaptureApiConfiguration": { + "Url": "https://prd-remote-deposit-capture-api.apps.azure.lcu-internal.com" + }, + "DataCleanUpApiConfiguration": { + "Url": "https://prd-data-clean-up-api.apps.azure.lcu-internal.com" + }, + "NoticeProcessorApiConfiguration": { + "Url": "https://prd-notice-processor-api.apps.azure.lcu-internal.com" + }, + "MomBotApiConfiguration": { + "Url": "https://mombotapi.lcu-internal.com" + }, + "RealTimeAlertsApiConfiguration": { + "Url": "https://prd-real-time-alerts-api.apps.azure.lcu-internal.com" + }, + "DispatcherRequestBuilderConfiguration": { + "Url": "https://dispatcherapi.lcu-internal.com/api/Ingestion" + }, + "ConnectionConfiguration": { + "HostUrl": "rabbitmq.lcu-internal.com" + }, + "IncentiveManagerConfiguration": { + "Url": "https://prd-incentive-manager-api.apps.azure.lcu-internal.com/api" + }, + "FileUpdaterApiConfiguration": { + "Url": "https://prd-file-updater-api.apps.azure.lcu-internal.com" + }, + "TemenosNotificationApiConfiguration": { + "Url": "https://prd-temenos-notification-api.apps.azure.lcu-internal.com" + }, + "RepoApiConfiguration": { + "Url": "https://prd-repo-api.apps.azure.lcu-internal.com" + } + } diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/completion/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/completion/kustomization.yaml new file mode 100644 index 0000000..60f07bb --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/completion/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/completion + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/completion/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/completion/set_virtual_service.yaml new file mode 100644 index 0000000..2d10862 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/completion/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: prd-dispatcher-completion-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/component/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/component/kustomization.yaml new file mode 100644 index 0000000..75fab81 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/component/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +patches: + - target: + kind: Deployment + path: set_appsettings_deployment.yaml + - target: + kind: SecretProviderClass + path: set_secrets_provider_class.yaml \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/component/set_appsettings_deployment.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/component/set_appsettings_deployment.yaml new file mode 100644 index 0000000..335b8be --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/component/set_appsettings_deployment.yaml @@ -0,0 +1,5 @@ +- op: add + path: /spec/template/spec/containers/0/env/- + value: + name: ASPNETCORE_ENVIRONMENT + value: "Production" \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/component/set_secrets_provider_class.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/component/set_secrets_provider_class.yaml new file mode 100644 index 0000000..8719a77 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/component/set_secrets_provider_class.yaml @@ -0,0 +1,7 @@ +- op: add + path: /spec/parameters/userAssignedIdentityID + value: 3286ed9a-f85c-4c50-b742-dbd3c1c3fe1f + +- op: add + path: /spec/parameters/keyvaultName + value: kv-lz-apps-qa-uscz diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/determine-operation/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/determine-operation/kustomization.yaml new file mode 100644 index 0000000..8db8a17 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/determine-operation/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/determine-operation + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/determine-operation/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/determine-operation/set_virtual_service.yaml new file mode 100644 index 0000000..58dcc4b --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/determine-operation/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: prd-dispatcher-determine-operation-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/execution/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/execution/kustomization.yaml new file mode 100644 index 0000000..1f9f3af --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/execution/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/execution + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/execution/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/execution/set_virtual_service.yaml new file mode 100644 index 0000000..5c9c257 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/execution/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: prd-dispatcher-execution-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/job-failure/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/job-failure/kustomization.yaml new file mode 100644 index 0000000..3e91a48 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/job-failure/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/job-failure + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/job-failure/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/job-failure/set_virtual_service.yaml new file mode 100644 index 0000000..25566a1 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/job-failure/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: prd-dispatcher-job-failure-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/job-scheduler/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/job-scheduler/kustomization.yaml new file mode 100644 index 0000000..065e74b --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/job-scheduler/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/job-scheduler + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/job-scheduler/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/job-scheduler/set_virtual_service.yaml new file mode 100644 index 0000000..905e650 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/job-scheduler/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: prd-dispatcher-job-scheduler-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/kustomization.yaml new file mode 100644 index 0000000..73577e7 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/kustomization.yaml @@ -0,0 +1,20 @@ +resources: + - normal + - completion + - determine-operation + - execution + - job-failure + - job-scheduler + - send-errored-job-alert + +configMapGenerator: + - name: dispatcher-api-environment-appsettings + files: + - appsettings/appsettings.json +generatorOptions: + disableNameSuffixHash: true + +patches: + - target: + kind: Deployment + path: set_configmap_deployment.yaml \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/normal/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/normal/kustomization.yaml new file mode 100644 index 0000000..a1e6b06 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/normal/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/normal + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/normal/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/normal/set_virtual_service.yaml new file mode 100644 index 0000000..61dc16e --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/normal/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: prd-dispatcher-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/send-errored-job-alert/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/send-errored-job-alert/kustomization.yaml new file mode 100644 index 0000000..a5e6833 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/send-errored-job-alert/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/send-errored-job-alert + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/send-errored-job-alert/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/send-errored-job-alert/set_virtual_service.yaml new file mode 100644 index 0000000..5dc264f --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/send-errored-job-alert/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: prd-dispatcher-send-errored-job-alert-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/set_configmap_deployment.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/set_configmap_deployment.yaml new file mode 100644 index 0000000..872cade --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/prd/set_configmap_deployment.yaml @@ -0,0 +1,12 @@ +- op: add + path: /spec/template/spec/volumes/- + value: + name: volume-environment-appsettings + configMap: + name: dispatcher-api-environment-appsettings + +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + name: volume-environment-appsettings + mountPath: /app/config/environment-appsettings diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/appsettings/appsettings.json b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/appsettings/appsettings.json new file mode 100644 index 0000000..21990f9 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/appsettings/appsettings.json @@ -0,0 +1,74 @@ +{ + "Primary": { + "SeqUrl": "https://qa-seq.lcu-internal.com", + "CorsAllowedOrigins": [ + "http://localhost:3000", + "http://lcu-internal.com", + "https://lcu-internal.com", + "https://qa-api.lcu-internal.com", + "https://uat-api.lcu-internal.com", + "https://api.lcu-internal.com" + ], + "DefaultErrorRecipient": "ITDevelopment@landmarkcu.com" + }, + "FileOperationsApiConfiguration": { + "Url": "https://qa-fileoperationsapi.lcu-internal.com" + }, + "FtpApiConfiguration": { + "Url": "https://qa-ftpapi.lcu-internal.com" + }, + "NotificationApiConfiguration": { + "Url": "https://qa-notifications-api.apps.azure.lcu-internal.com" + }, + "ArchiverApiConfiguration": { + "Url": "https://qa-archiver-api.apps.azure.lcu-internal.com" + }, + "BlendApiConfiguration": { + "Url": "https://qa-blend-api-wrapper-api.apps.azure.lcu-internal.com" + }, + "DialerApiConfiguration": { + "Url": "https://qa-dialer-api.apps.azure.lcu-internal.com" + }, + "DocumentIngestorApiConfiguration": { + "Url": "https://qa-document-ingestor-api.apps.azure.lcu-internal.com" + }, + "DocumentTypeCleanupApiConfiguration": { + "Url": "https://qa-document-type-cleanup-api.apps.azure.lcu-internal.com" + }, + "HistoricalDocumentIngestorApiConfiguration": { + "Url": "https://qa-document-ingestor-historical-api.apps.azure.lcu-internal.com" + }, + "RemoteDepositCaptureApiConfiguration": { + "Url": "https://qa-remote-deposit-capture-api.apps.azure.lcu-internal.com" + }, + "DataCleanUpApiConfiguration": { + "Url": "https://qa-data-clean-up-api.apps.azure.lcu-internal.com" + }, + "NoticeProcessorApiConfiguration": { + "Url": "https://qa-notice-processor-api.apps.azure.lcu-internal.com" + }, + "MomBotApiConfiguration": { + "Url": "https://qa-mombotapi.lcu-internal.com" + }, + "RealTimeAlertsApiConfiguration": { + "Url": "https://qa-real-time-alerts-api.apps.azure.lcu-internal.com" + }, + "DispatcherRequestBuilderConfiguration": { + "Url": "https://qa-dispatcherapi.lcu-internal.com/api/Ingestion" + }, + "ConnectionConfiguration": { + "HostUrl": "qa-rabbitmq.lcu-internal.com" + }, + "IncentiveManagerConfiguration": { + "Url": "https://qa-incentive-manager-api.apps.azure.lcu-internal.com/api" + }, + "FileUpdaterApiConfiguration": { + "Url": "https://qa-file-updater-api.apps.azure.lcu-internal.com" + }, + "TemenosNotificationApiConfiguration": { + "Url": "https://qa-temenos-notification-api.apps.azure.lcu-internal.com" + }, + "RepoApiConfiguration": { + "Url": "https://qa-repo-api.apps.azure.lcu-internal.com" + } + } \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/completion/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/completion/kustomization.yaml new file mode 100644 index 0000000..60f07bb --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/completion/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/completion + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/completion/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/completion/set_virtual_service.yaml new file mode 100644 index 0000000..ee9d3dd --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/completion/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: qa-dispatcher-completion-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/component/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/component/kustomization.yaml new file mode 100644 index 0000000..75fab81 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/component/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +patches: + - target: + kind: Deployment + path: set_appsettings_deployment.yaml + - target: + kind: SecretProviderClass + path: set_secrets_provider_class.yaml \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/component/set_appsettings_deployment.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/component/set_appsettings_deployment.yaml new file mode 100644 index 0000000..818dec4 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/component/set_appsettings_deployment.yaml @@ -0,0 +1,5 @@ +- op: add + path: /spec/template/spec/containers/0/env/- + value: + name: ASPNETCORE_ENVIRONMENT + value: Development \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/component/set_secrets_provider_class.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/component/set_secrets_provider_class.yaml new file mode 100644 index 0000000..8719a77 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/component/set_secrets_provider_class.yaml @@ -0,0 +1,7 @@ +- op: add + path: /spec/parameters/userAssignedIdentityID + value: 3286ed9a-f85c-4c50-b742-dbd3c1c3fe1f + +- op: add + path: /spec/parameters/keyvaultName + value: kv-lz-apps-qa-uscz diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/determine-operation/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/determine-operation/kustomization.yaml new file mode 100644 index 0000000..8db8a17 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/determine-operation/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/determine-operation + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/determine-operation/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/determine-operation/set_virtual_service.yaml new file mode 100644 index 0000000..8fce635 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/determine-operation/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: qa-dispatcher-determine-operation-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/execution/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/execution/kustomization.yaml new file mode 100644 index 0000000..1f9f3af --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/execution/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/execution + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/execution/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/execution/set_virtual_service.yaml new file mode 100644 index 0000000..a14bfa4 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/execution/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: qa-dispatcher-execution-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/job-failure/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/job-failure/kustomization.yaml new file mode 100644 index 0000000..3e91a48 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/job-failure/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/job-failure + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/job-failure/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/job-failure/set_virtual_service.yaml new file mode 100644 index 0000000..e49681e --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/job-failure/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: qa-dispatcher-job-failure-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/job-scheduler/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/job-scheduler/kustomization.yaml new file mode 100644 index 0000000..065e74b --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/job-scheduler/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/job-scheduler + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/job-scheduler/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/job-scheduler/set_virtual_service.yaml new file mode 100644 index 0000000..6e3e5bd --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/job-scheduler/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: qa-dispatcher-job-scheduler-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/kustomization.yaml new file mode 100644 index 0000000..73577e7 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/kustomization.yaml @@ -0,0 +1,20 @@ +resources: + - normal + - completion + - determine-operation + - execution + - job-failure + - job-scheduler + - send-errored-job-alert + +configMapGenerator: + - name: dispatcher-api-environment-appsettings + files: + - appsettings/appsettings.json +generatorOptions: + disableNameSuffixHash: true + +patches: + - target: + kind: Deployment + path: set_configmap_deployment.yaml \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/normal/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/normal/kustomization.yaml new file mode 100644 index 0000000..a1e6b06 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/normal/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/normal + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/normal/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/normal/set_virtual_service.yaml new file mode 100644 index 0000000..f75ea03 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/normal/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: qa-dispatcher-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/send-errored-job-alert/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/send-errored-job-alert/kustomization.yaml new file mode 100644 index 0000000..a5e6833 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/send-errored-job-alert/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/send-errored-job-alert + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/send-errored-job-alert/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/send-errored-job-alert/set_virtual_service.yaml new file mode 100644 index 0000000..4fc4805 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/send-errored-job-alert/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: qa-dispatcher-send-errored-job-alert-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/set_configmap_deployment.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/set_configmap_deployment.yaml new file mode 100644 index 0000000..872cade --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/qa/set_configmap_deployment.yaml @@ -0,0 +1,12 @@ +- op: add + path: /spec/template/spec/volumes/- + value: + name: volume-environment-appsettings + configMap: + name: dispatcher-api-environment-appsettings + +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + name: volume-environment-appsettings + mountPath: /app/config/environment-appsettings diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/appsettings/appsettings.json b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/appsettings/appsettings.json new file mode 100644 index 0000000..9c3fc78 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/appsettings/appsettings.json @@ -0,0 +1,74 @@ +{ + "Primary": { + "SeqUrl": "https://uat-seq.lcu-internal.com", + "CorsAllowedOrigins": [ + "http://localhost:3000", + "http://lcu-internal.com", + "https://lcu-internal.com", + "https://qa-api.lcu-internal.com", + "https://uat-api.lcu-internal.com", + "https://api.lcu-internal.com" + ], + "DefaultErrorRecipient": "ITDevelopment@landmarkcu.com" + }, + "FileOperationsApiConfiguration": { + "Url": "https://uat-fileoperationsapi.lcu-internal.com" + }, + "FtpApiConfiguration": { + "Url": "https://uat-ftpapi.lcu-internal.com" + }, + "NotificationApiConfiguration": { + "Url": "https://uat-notifications-api.apps.azure.lcu-internal.com" + }, + "ArchiverApiConfiguration": { + "Url": "https://uat-archiver-api.apps.azure.lcu-internal.com" + }, + "BlendApiConfiguration": { + "Url": "https://uat-blend-api-wrapper-api.apps.azure.lcu-internal.com" + }, + "DialerApiConfiguration": { + "Url": "https://uat-dialer-api.apps.azure.lcu-internal.com" + }, + "DocumentIngestorApiConfiguration": { + "Url": "https://uat-document-ingestor-api.apps.azure.lcu-internal.com" + }, + "DocumentTypeCleanupApiConfiguration": { + "Url": "https://uat-document-type-cleanup-api.apps.azure.lcu-internal.com" + }, + "HistoricalDocumentIngestorApiConfiguration": { + "Url": "https://uat-document-ingestor-historical-api.apps.azure.lcu-internal.com" + }, + "RemoteDepositCaptureApiConfiguration": { + "Url": "https://uat-remote-deposit-capture-api.apps.azure.lcu-internal.com" + }, + "DataCleanUpApiConfiguration": { + "Url": "https://uat-data-clean-up-api.apps.azure.lcu-internal.com" + }, + "NoticeProcessorApiConfiguration": { + "Url": "https://uat-notice-processor-api.apps.azure.lcu-internal.com" + }, + "MomBotApiConfiguration": { + "Url": "https://uat-mombotapi.lcu-internal.com" + }, + "RealTimeAlertsApiConfiguration": { + "Url": "https://uat-real-time-alerts-api.apps.azure.lcu-internal.com" + }, + "DispatcherRequestBuilderConfiguration": { + "Url": "https://uat-dispatcherapi.lcu-internal.com/api/Ingestion" + }, + "ConnectionConfiguration": { + "HostUrl": "uat-rabbitmq.lcu-internal.com" + }, + "IncentiveManagerConfiguration": { + "Url": "https://uat-incentive-manager-api.apps.azure.lcu-internal.com/api" + }, + "FileUpdaterApiConfiguration": { + "Url": "https://uat-file-updater-api.apps.azure.lcu-internal.com" + }, + "TemenosNotificationApiConfiguration": { + "Url": "https://uat-temenos-notification-api.apps.azure.lcu-internal.com" + }, + "RepoApiConfiguration": { + "Url": "https://uat-repo-api.apps.azure.lcu-internal.com" + } + } \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/completion/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/completion/kustomization.yaml new file mode 100644 index 0000000..60f07bb --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/completion/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/completion + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/completion/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/completion/set_virtual_service.yaml new file mode 100644 index 0000000..77b3279 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/completion/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: uat-dispatcher-completion-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/component/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/component/kustomization.yaml new file mode 100644 index 0000000..75fab81 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/component/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +patches: + - target: + kind: Deployment + path: set_appsettings_deployment.yaml + - target: + kind: SecretProviderClass + path: set_secrets_provider_class.yaml \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/component/set_appsettings_deployment.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/component/set_appsettings_deployment.yaml new file mode 100644 index 0000000..4fd0533 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/component/set_appsettings_deployment.yaml @@ -0,0 +1,5 @@ +- op: add + path: /spec/template/spec/containers/0/env/- + value: + name: ASPNETCORE_ENVIRONMENT + value: "Staging" \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/component/set_secrets_provider_class.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/component/set_secrets_provider_class.yaml new file mode 100644 index 0000000..8719a77 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/component/set_secrets_provider_class.yaml @@ -0,0 +1,7 @@ +- op: add + path: /spec/parameters/userAssignedIdentityID + value: 3286ed9a-f85c-4c50-b742-dbd3c1c3fe1f + +- op: add + path: /spec/parameters/keyvaultName + value: kv-lz-apps-qa-uscz diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/determine-operation/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/determine-operation/kustomization.yaml new file mode 100644 index 0000000..8db8a17 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/determine-operation/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/determine-operation + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/determine-operation/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/determine-operation/set_virtual_service.yaml new file mode 100644 index 0000000..dcdc9c9 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/determine-operation/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: uat-dispatcher-determine-operation-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/execution/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/execution/kustomization.yaml new file mode 100644 index 0000000..1f9f3af --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/execution/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/execution + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/execution/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/execution/set_virtual_service.yaml new file mode 100644 index 0000000..5d778ef --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/execution/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: uat-dispatcher-execution-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/job-failure/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/job-failure/kustomization.yaml new file mode 100644 index 0000000..3e91a48 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/job-failure/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/job-failure + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/job-failure/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/job-failure/set_virtual_service.yaml new file mode 100644 index 0000000..e258429 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/job-failure/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: uat-dispatcher-job-failure-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/job-scheduler/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/job-scheduler/kustomization.yaml new file mode 100644 index 0000000..065e74b --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/job-scheduler/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/job-scheduler + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/job-scheduler/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/job-scheduler/set_virtual_service.yaml new file mode 100644 index 0000000..116b9df --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/job-scheduler/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: uat-dispatcher-job-scheduler-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/kustomization.yaml new file mode 100644 index 0000000..73577e7 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/kustomization.yaml @@ -0,0 +1,20 @@ +resources: + - normal + - completion + - determine-operation + - execution + - job-failure + - job-scheduler + - send-errored-job-alert + +configMapGenerator: + - name: dispatcher-api-environment-appsettings + files: + - appsettings/appsettings.json +generatorOptions: + disableNameSuffixHash: true + +patches: + - target: + kind: Deployment + path: set_configmap_deployment.yaml \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/normal/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/normal/kustomization.yaml new file mode 100644 index 0000000..a1e6b06 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/normal/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/normal + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/normal/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/normal/set_virtual_service.yaml new file mode 100644 index 0000000..3ddefbd --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/normal/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: uat-dispatcher-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/send-errored-job-alert/kustomization.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/send-errored-job-alert/kustomization.yaml new file mode 100644 index 0000000..a5e6833 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/send-errored-job-alert/kustomization.yaml @@ -0,0 +1,12 @@ +resources: + - ../../../base + +components: + - ../component + - ../../../components/keyvaults/normal + - ../../../components/workloads/send-errored-job-alert + +patches: + - target: + kind: VirtualService + path: set_virtual_service.yaml diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/send-errored-job-alert/set_virtual_service.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/send-errored-job-alert/set_virtual_service.yaml new file mode 100644 index 0000000..2236e71 --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/send-errored-job-alert/set_virtual_service.yaml @@ -0,0 +1,3 @@ +- op: add + path: /spec/hosts/- + value: uat-dispatcher-send-errored-job-alert-api.apps.azure.lcu-internal.com \ No newline at end of file diff --git a/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/set_configmap_deployment.yaml b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/set_configmap_deployment.yaml new file mode 100644 index 0000000..872cade --- /dev/null +++ b/LCU.API.Internal.Dispatcher/kustomize/overlays/uat/set_configmap_deployment.yaml @@ -0,0 +1,12 @@ +- op: add + path: /spec/template/spec/volumes/- + value: + name: volume-environment-appsettings + configMap: + name: dispatcher-api-environment-appsettings + +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + name: volume-environment-appsettings + mountPath: /app/config/environment-appsettings