From c82a4f9131d62bcc3378f7afe9008b0771ad6901 Mon Sep 17 00:00:00 2001 From: dyatlov-a Date: Sat, 6 Jan 2024 23:56:26 +0200 Subject: [PATCH] Impl connector --- Inc.TeamAssistant.sln | 31 ++ .../AcceptEstimateCommandHandler.cs | 2 +- .../ActivateAssessmentCommandHandler.cs | 2 +- .../AddStoryForEstimateCommandHandler.cs | 2 +- ...dStoryToAssessmentSessionCommandHandler.cs | 2 +- ...toryToAssessmentSessionCommandValidator.cs | 1 + .../ChangeLanguageCommandHandler.cs | 2 +- ...onnectToAssessmentSessionCommandHandler.cs | 2 +- ...nectToAssessmentSessionCommandValidator.cs | 1 + .../CreateAssessmentSessionCommandHandler.cs | 2 +- ...ExitFromAssessmentSessionCommandHandler.cs | 2 +- .../FinishAssessmentSessionCommandHandler.cs | 2 +- .../ReVoteEstimateCommandHandler.cs | 2 +- .../SetEstimateForStoryCommandHandler.cs | 2 +- .../ShowHelp/ShowHelpCommandHandler.cs | 2 +- .../StartStorySelectionCommandHandler.cs | 2 +- .../ServiceCollectionExtensions.cs | 4 +- .../Services/SummaryByStoryBuilder.cs | 1 + .../AcceptEstimate/AcceptEstimateCommand.cs | 1 + .../ActivateAssessmentCommand.cs | 1 + .../AddStoryForEstimateCommand.cs | 1 + .../ExitFromAssessmentSessionCommand.cs | 1 + .../FinishAssessmentSessionCommand.cs | 1 + .../ReVoteEstimate/ReVoteEstimateCommand.cs | 1 + .../SetEstimateForStoryCommand.cs | 1 + .../StartStorySelectionCommand.cs | 1 + .../Inc.TeamAssistant.Appraiser.Model.csproj | 3 - .../Inc.TeamAssistant.CheckIn.Model.csproj | 3 - .../Begin/BeginCommandHandler.cs | 40 +++ .../CreateTeam/CreateTeamCommandHandler.cs | 41 +++ .../CommandHandlers/End/EndCommandHandler.cs | 37 +++ .../JoinToTeam/JoinToTeamCommandHandler.cs | 38 +++ .../LeaveFromTeamCommandHandler.cs | 40 +++ .../Contracts/IBotRepository.cs | 10 + .../Contracts/IPersonRepository.cs | 10 + .../Contracts/ITeamRepository.cs | 10 + ...TeamAssistant.Connector.Application.csproj | 14 + .../Messages.cs | 9 + .../ServiceCollectionExtensions.cs | 20 ++ .../Services/CommandFactory.cs | 110 +++++++ .../Services/TelegramBotConnector.cs | 38 +++ .../Services/TelegramBotMessageHandler.cs | 279 ++++++++++++++++++ .../BotRepository.cs | 124 ++++++++ ....TeamAssistant.Connector.DataAccess.csproj | 9 + .../LanguageIdTypeHandler.cs | 25 ++ .../MessageIdTypeHandler.cs | 25 ++ .../PersonRepository.cs | 65 ++++ .../ServiceCollectionExtensions.cs | 29 ++ .../TeamRepository.cs | 116 ++++++++ src/Inc.TeamAssistant.Connector.Domain/Bot.cs | 51 ++++ .../BotCommand.cs | 35 +++ .../BotCommandStage.cs | 8 + .../Inc.TeamAssistant.Connector.Domain.csproj | 5 + .../Person.cs | 27 ++ .../Team.cs | 47 +++ .../Commands/Begin/BeginCommand.cs | 11 + .../Commands/CreateTeam/CreateTeamCommand.cs | 10 + .../Commands/End/EndCommand.cs | 7 + .../Commands/JoinToTeam/JoinToTeamCommand.cs | 7 + .../LeaveFromTeam/LeaveFromTeamCommand.cs | 7 + .../Inc.TeamAssistant.Connector.Model.csproj | 5 + .../MessageContext.cs | 16 + .../IDialogContinuation.cs | 2 +- .../Internal/DialogContinuation.cs | 9 +- .../Inc.TeamAssistant.Gateway.csproj | 2 + src/Inc.TeamAssistant.Gateway/Program.cs | 30 +- .../CommandFactories/ComplexCommandFactory.cs | 2 +- .../CommandFactories/DynamicCommandFactory.cs | 2 +- .../CommandFactories/ICommandFactory.cs | 2 +- .../CommandFactories/StaticCommandFactory.cs | 2 +- .../Services/MessageBuilder.cs | 1 - .../Services/ServiceCollectionExtensions.cs | 3 +- .../Services/TelegramBotMessageHandler.cs | 2 +- .../wwwroot/langs/en.json | 5 +- .../wwwroot/langs/ru.json | 5 +- .../2023_01_25_0_ChangePersonsStore.cs | 1 - .../2023_11_07_0_DeleteUsersScheme.cs | 1 - .../2024_01_03_0_CreateConnectorScheme.cs | 27 ++ .../2024_01_03_1_CreateConnectorTables.cs | 153 ++++++++++ .../Button.cs | 2 +- .../CommandResult.cs | 2 +- .../IMessageBuilder.cs | 4 +- .../Inc.TeamAssistant.Primitives.csproj | 3 + .../NotificationMessage.cs | 2 +- .../ServiceCollectionExtensions.cs | 2 +- .../Inc.TeamAssistant.Reviewer.Model.csproj | 3 - 86 files changed, 1612 insertions(+), 58 deletions(-) create mode 100644 src/Inc.TeamAssistant.Connector.Application/CommandHandlers/Begin/BeginCommandHandler.cs create mode 100644 src/Inc.TeamAssistant.Connector.Application/CommandHandlers/CreateTeam/CreateTeamCommandHandler.cs create mode 100644 src/Inc.TeamAssistant.Connector.Application/CommandHandlers/End/EndCommandHandler.cs create mode 100644 src/Inc.TeamAssistant.Connector.Application/CommandHandlers/JoinToTeam/JoinToTeamCommandHandler.cs create mode 100644 src/Inc.TeamAssistant.Connector.Application/CommandHandlers/LeaveFromTeam/LeaveFromTeamCommandHandler.cs create mode 100644 src/Inc.TeamAssistant.Connector.Application/Contracts/IBotRepository.cs create mode 100644 src/Inc.TeamAssistant.Connector.Application/Contracts/IPersonRepository.cs create mode 100644 src/Inc.TeamAssistant.Connector.Application/Contracts/ITeamRepository.cs create mode 100644 src/Inc.TeamAssistant.Connector.Application/Inc.TeamAssistant.Connector.Application.csproj create mode 100644 src/Inc.TeamAssistant.Connector.Application/Messages.cs create mode 100644 src/Inc.TeamAssistant.Connector.Application/ServiceCollectionExtensions.cs create mode 100644 src/Inc.TeamAssistant.Connector.Application/Services/CommandFactory.cs create mode 100644 src/Inc.TeamAssistant.Connector.Application/Services/TelegramBotConnector.cs create mode 100644 src/Inc.TeamAssistant.Connector.Application/Services/TelegramBotMessageHandler.cs create mode 100644 src/Inc.TeamAssistant.Connector.DataAccess/BotRepository.cs create mode 100644 src/Inc.TeamAssistant.Connector.DataAccess/Inc.TeamAssistant.Connector.DataAccess.csproj create mode 100644 src/Inc.TeamAssistant.Connector.DataAccess/LanguageIdTypeHandler.cs create mode 100644 src/Inc.TeamAssistant.Connector.DataAccess/MessageIdTypeHandler.cs create mode 100644 src/Inc.TeamAssistant.Connector.DataAccess/PersonRepository.cs create mode 100644 src/Inc.TeamAssistant.Connector.DataAccess/ServiceCollectionExtensions.cs create mode 100644 src/Inc.TeamAssistant.Connector.DataAccess/TeamRepository.cs create mode 100644 src/Inc.TeamAssistant.Connector.Domain/Bot.cs create mode 100644 src/Inc.TeamAssistant.Connector.Domain/BotCommand.cs create mode 100644 src/Inc.TeamAssistant.Connector.Domain/BotCommandStage.cs create mode 100644 src/Inc.TeamAssistant.Connector.Domain/Inc.TeamAssistant.Connector.Domain.csproj create mode 100644 src/Inc.TeamAssistant.Connector.Domain/Person.cs create mode 100644 src/Inc.TeamAssistant.Connector.Domain/Team.cs create mode 100644 src/Inc.TeamAssistant.Connector.Model/Commands/Begin/BeginCommand.cs create mode 100644 src/Inc.TeamAssistant.Connector.Model/Commands/CreateTeam/CreateTeamCommand.cs create mode 100644 src/Inc.TeamAssistant.Connector.Model/Commands/End/EndCommand.cs create mode 100644 src/Inc.TeamAssistant.Connector.Model/Commands/JoinToTeam/JoinToTeamCommand.cs create mode 100644 src/Inc.TeamAssistant.Connector.Model/Commands/LeaveFromTeam/LeaveFromTeamCommand.cs create mode 100644 src/Inc.TeamAssistant.Connector.Model/Inc.TeamAssistant.Connector.Model.csproj create mode 100644 src/Inc.TeamAssistant.Connector.Model/MessageContext.cs create mode 100644 src/Inc.TeamAssistant.Migrations/2024_01_03_0_CreateConnectorScheme.cs create mode 100644 src/Inc.TeamAssistant.Migrations/2024_01_03_1_CreateConnectorTables.cs rename src/{Inc.TeamAssistant.Appraiser.Model/Common => Inc.TeamAssistant.Primitives}/Button.cs (51%) rename src/{Inc.TeamAssistant.Appraiser.Model/Common => Inc.TeamAssistant.Primitives}/CommandResult.cs (89%) rename src/{Inc.TeamAssistant.Appraiser.Application/Contracts => Inc.TeamAssistant.Primitives}/IMessageBuilder.cs (56%) rename src/{Inc.TeamAssistant.Appraiser.Model/Common => Inc.TeamAssistant.Primitives}/NotificationMessage.cs (97%) diff --git a/Inc.TeamAssistant.sln b/Inc.TeamAssistant.sln index 22afa879..d25ee5d2 100644 --- a/Inc.TeamAssistant.sln +++ b/Inc.TeamAssistant.sln @@ -72,6 +72,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inc.TeamAssistant.Reviewer. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inc.TeamAssistant.Reviewer.Model", "src\Inc.TeamAssistant.Reviewer.Model\Inc.TeamAssistant.Reviewer.Model.csproj", "{CC45D751-DEF6-47A9-BBB3-9F46AA1F2A18}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "telegram_connector", "telegram_connector", "{533DB9B2-8423-4379-9B57-AE87EC9091D0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inc.TeamAssistant.Connector.Application", "src\Inc.TeamAssistant.Connector.Application\Inc.TeamAssistant.Connector.Application.csproj", "{6D88ACED-9750-402F-AFDF-61A5E8478961}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inc.TeamAssistant.Connector.Domain", "src\Inc.TeamAssistant.Connector.Domain\Inc.TeamAssistant.Connector.Domain.csproj", "{27B45834-FCB5-46FE-999A-A6B12F69ACF2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inc.TeamAssistant.Connector.DataAccess", "src\Inc.TeamAssistant.Connector.DataAccess\Inc.TeamAssistant.Connector.DataAccess.csproj", "{1960CAA3-ACB5-4299-8B65-F764BDFF32FC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inc.TeamAssistant.Connector.Model", "src\Inc.TeamAssistant.Connector.Model\Inc.TeamAssistant.Connector.Model.csproj", "{066637C7-22D8-4F1F-A2F2-ADDDC363D011}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -184,6 +194,22 @@ Global {CC45D751-DEF6-47A9-BBB3-9F46AA1F2A18}.Debug|Any CPU.Build.0 = Debug|Any CPU {CC45D751-DEF6-47A9-BBB3-9F46AA1F2A18}.Release|Any CPU.ActiveCfg = Release|Any CPU {CC45D751-DEF6-47A9-BBB3-9F46AA1F2A18}.Release|Any CPU.Build.0 = Release|Any CPU + {6D88ACED-9750-402F-AFDF-61A5E8478961}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D88ACED-9750-402F-AFDF-61A5E8478961}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D88ACED-9750-402F-AFDF-61A5E8478961}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D88ACED-9750-402F-AFDF-61A5E8478961}.Release|Any CPU.Build.0 = Release|Any CPU + {27B45834-FCB5-46FE-999A-A6B12F69ACF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27B45834-FCB5-46FE-999A-A6B12F69ACF2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27B45834-FCB5-46FE-999A-A6B12F69ACF2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27B45834-FCB5-46FE-999A-A6B12F69ACF2}.Release|Any CPU.Build.0 = Release|Any CPU + {1960CAA3-ACB5-4299-8B65-F764BDFF32FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1960CAA3-ACB5-4299-8B65-F764BDFF32FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1960CAA3-ACB5-4299-8B65-F764BDFF32FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1960CAA3-ACB5-4299-8B65-F764BDFF32FC}.Release|Any CPU.Build.0 = Release|Any CPU + {066637C7-22D8-4F1F-A2F2-ADDDC363D011}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {066637C7-22D8-4F1F-A2F2-ADDDC363D011}.Debug|Any CPU.Build.0 = Debug|Any CPU + {066637C7-22D8-4F1F-A2F2-ADDDC363D011}.Release|Any CPU.ActiveCfg = Release|Any CPU + {066637C7-22D8-4F1F-A2F2-ADDDC363D011}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {B8F7A67F-CFA3-4CF2-BACB-B14C282BF202} = {1943C11E-7A4A-4300-BDC1-DA333BD3EBED} @@ -218,5 +244,10 @@ Global {529CC802-3552-4381-8D22-872890CC6A9E} = {70E979AF-B6E2-4044-9161-91FA02A2644F} {0BCBE76B-F740-44AE-8D47-5DD735EA6F3D} = {70E979AF-B6E2-4044-9161-91FA02A2644F} {CC45D751-DEF6-47A9-BBB3-9F46AA1F2A18} = {70E979AF-B6E2-4044-9161-91FA02A2644F} + {533DB9B2-8423-4379-9B57-AE87EC9091D0} = {73CA6C4E-FE60-4BEF-9902-FFA9241616F9} + {6D88ACED-9750-402F-AFDF-61A5E8478961} = {533DB9B2-8423-4379-9B57-AE87EC9091D0} + {27B45834-FCB5-46FE-999A-A6B12F69ACF2} = {533DB9B2-8423-4379-9B57-AE87EC9091D0} + {1960CAA3-ACB5-4299-8B65-F764BDFF32FC} = {533DB9B2-8423-4379-9B57-AE87EC9091D0} + {066637C7-22D8-4F1F-A2F2-ADDDC363D011} = {533DB9B2-8423-4379-9B57-AE87EC9091D0} EndGlobalSection EndGlobal diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AcceptEstimate/AcceptEstimateCommandHandler.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AcceptEstimate/AcceptEstimateCommandHandler.cs index 5d68a798..7b7df1bb 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AcceptEstimate/AcceptEstimateCommandHandler.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AcceptEstimate/AcceptEstimateCommandHandler.cs @@ -4,7 +4,7 @@ using MediatR; using Inc.TeamAssistant.Appraiser.Application.Extensions; using Inc.TeamAssistant.Appraiser.Application.Services; -using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; namespace Inc.TeamAssistant.Appraiser.Application.CommandHandlers.AcceptEstimate; diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ActivateAssessment/ActivateAssessmentCommandHandler.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ActivateAssessment/ActivateAssessmentCommandHandler.cs index 0b2ee6b3..3c7735f8 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ActivateAssessment/ActivateAssessmentCommandHandler.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ActivateAssessment/ActivateAssessmentCommandHandler.cs @@ -2,8 +2,8 @@ using Inc.TeamAssistant.Appraiser.Model.Commands.ActivateAssessment; using MediatR; using Inc.TeamAssistant.Appraiser.Application.Extensions; -using Inc.TeamAssistant.Appraiser.Model.Common; using Inc.TeamAssistant.DialogContinuations; +using Inc.TeamAssistant.Primitives; namespace Inc.TeamAssistant.Appraiser.Application.CommandHandlers.ActivateAssessment; diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStoryForEstimate/AddStoryForEstimateCommandHandler.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStoryForEstimate/AddStoryForEstimateCommandHandler.cs index 63d0a0de..bdb4be20 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStoryForEstimate/AddStoryForEstimateCommandHandler.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStoryForEstimate/AddStoryForEstimateCommandHandler.cs @@ -2,7 +2,7 @@ using Inc.TeamAssistant.Appraiser.Model.Commands.AddStoryForEstimate; using MediatR; using Inc.TeamAssistant.Appraiser.Application.Extensions; -using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; namespace Inc.TeamAssistant.Appraiser.Application.CommandHandlers.AddStoryForEstimate; diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStoryToAssessmentSession/AddStoryToAssessmentSessionCommandHandler.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStoryToAssessmentSession/AddStoryToAssessmentSessionCommandHandler.cs index d09b2421..be1188f3 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStoryToAssessmentSession/AddStoryToAssessmentSessionCommandHandler.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStoryToAssessmentSession/AddStoryToAssessmentSessionCommandHandler.cs @@ -4,8 +4,8 @@ using MediatR; using Inc.TeamAssistant.Appraiser.Application.Extensions; using Inc.TeamAssistant.Appraiser.Application.Services; -using Inc.TeamAssistant.Appraiser.Model.Common; using Inc.TeamAssistant.DialogContinuations; +using Inc.TeamAssistant.Primitives; namespace Inc.TeamAssistant.Appraiser.Application.CommandHandlers.AddStoryToAssessmentSession; diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStoryToAssessmentSession/Validators/AddStoryToAssessmentSessionCommandValidator.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStoryToAssessmentSession/Validators/AddStoryToAssessmentSessionCommandValidator.cs index 088a7f49..b84ac10d 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStoryToAssessmentSession/Validators/AddStoryToAssessmentSessionCommandValidator.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/AddStoryToAssessmentSession/Validators/AddStoryToAssessmentSessionCommandValidator.cs @@ -2,6 +2,7 @@ using Inc.TeamAssistant.Appraiser.Application.Contracts; using Inc.TeamAssistant.Appraiser.Model.Commands.AddStoryToAssessmentSession; using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; namespace Inc.TeamAssistant.Appraiser.Application.CommandHandlers.AddStoryToAssessmentSession.Validators; diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ChangeLanguage/ChangeLanguageCommandHandler.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ChangeLanguage/ChangeLanguageCommandHandler.cs index 5b7ac190..fb070dc6 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ChangeLanguage/ChangeLanguageCommandHandler.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ChangeLanguage/ChangeLanguageCommandHandler.cs @@ -2,7 +2,7 @@ using Inc.TeamAssistant.Appraiser.Model.Commands.ChangeLanguage; using MediatR; using Inc.TeamAssistant.Appraiser.Application.Extensions; -using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; namespace Inc.TeamAssistant.Appraiser.Application.CommandHandlers.ChangeLanguage; diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ConnectToAssessmentSession/ConnectToAssessmentSessionCommandHandler.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ConnectToAssessmentSession/ConnectToAssessmentSessionCommandHandler.cs index 3569f2ad..1142b28a 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ConnectToAssessmentSession/ConnectToAssessmentSessionCommandHandler.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ConnectToAssessmentSession/ConnectToAssessmentSessionCommandHandler.cs @@ -5,7 +5,7 @@ using MediatR; using Inc.TeamAssistant.Appraiser.Application.Extensions; using Inc.TeamAssistant.Appraiser.Application.Services; -using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; namespace Inc.TeamAssistant.Appraiser.Application.CommandHandlers.ConnectToAssessmentSession; diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ConnectToAssessmentSession/Validators/ConnectToAssessmentSessionCommandValidator.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ConnectToAssessmentSession/Validators/ConnectToAssessmentSessionCommandValidator.cs index 2d9abc8a..56f55588 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ConnectToAssessmentSession/Validators/ConnectToAssessmentSessionCommandValidator.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ConnectToAssessmentSession/Validators/ConnectToAssessmentSessionCommandValidator.cs @@ -3,6 +3,7 @@ using Inc.TeamAssistant.Appraiser.Model.Commands.ConnectToAssessmentSession; using Inc.TeamAssistant.Appraiser.Model.Commands.CreateAssessmentSession; using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; namespace Inc.TeamAssistant.Appraiser.Application.CommandHandlers.ConnectToAssessmentSession.Validators; diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/CreateAssessmentSession/CreateAssessmentSessionCommandHandler.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/CreateAssessmentSession/CreateAssessmentSessionCommandHandler.cs index 37c3a05e..e6140b68 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/CreateAssessmentSession/CreateAssessmentSessionCommandHandler.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/CreateAssessmentSession/CreateAssessmentSessionCommandHandler.cs @@ -1,8 +1,8 @@ using Inc.TeamAssistant.Appraiser.Application.Contracts; using Inc.TeamAssistant.Appraiser.Domain; using Inc.TeamAssistant.Appraiser.Model.Commands.CreateAssessmentSession; -using Inc.TeamAssistant.Appraiser.Model.Common; using Inc.TeamAssistant.DialogContinuations; +using Inc.TeamAssistant.Primitives; using MediatR; namespace Inc.TeamAssistant.Appraiser.Application.CommandHandlers.CreateAssessmentSession; diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ExitFromAssessmentSession/ExitFromAssessmentSessionCommandHandler.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ExitFromAssessmentSession/ExitFromAssessmentSessionCommandHandler.cs index 82aa9fc7..0ab31717 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ExitFromAssessmentSession/ExitFromAssessmentSessionCommandHandler.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ExitFromAssessmentSession/ExitFromAssessmentSessionCommandHandler.cs @@ -4,7 +4,7 @@ using MediatR; using Inc.TeamAssistant.Appraiser.Application.Extensions; using Inc.TeamAssistant.Appraiser.Application.Services; -using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; namespace Inc.TeamAssistant.Appraiser.Application.CommandHandlers.ExitFromAssessmentSession; diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/FinishAssessmentSession/FinishAssessmentSessionCommandHandler.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/FinishAssessmentSession/FinishAssessmentSessionCommandHandler.cs index c82b8ac9..a5656361 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/FinishAssessmentSession/FinishAssessmentSessionCommandHandler.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/FinishAssessmentSession/FinishAssessmentSessionCommandHandler.cs @@ -3,7 +3,7 @@ using Inc.TeamAssistant.Appraiser.Model.Commands.FinishAssessmentSession; using MediatR; using Inc.TeamAssistant.Appraiser.Application.Extensions; -using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; namespace Inc.TeamAssistant.Appraiser.Application.CommandHandlers.FinishAssessmentSession; diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ReVoteEstimate/ReVoteEstimateCommandHandler.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ReVoteEstimate/ReVoteEstimateCommandHandler.cs index 843b8104..209deb55 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ReVoteEstimate/ReVoteEstimateCommandHandler.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ReVoteEstimate/ReVoteEstimateCommandHandler.cs @@ -4,7 +4,7 @@ using MediatR; using Inc.TeamAssistant.Appraiser.Application.Extensions; using Inc.TeamAssistant.Appraiser.Application.Services; -using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; namespace Inc.TeamAssistant.Appraiser.Application.CommandHandlers.ReVoteEstimate; diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/SetEstimateForStory/SetEstimateForStoryCommandHandler.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/SetEstimateForStory/SetEstimateForStoryCommandHandler.cs index a0c86a02..6df84e5e 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/SetEstimateForStory/SetEstimateForStoryCommandHandler.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/SetEstimateForStory/SetEstimateForStoryCommandHandler.cs @@ -5,7 +5,7 @@ using Inc.TeamAssistant.Appraiser.Application.Extensions; using Inc.TeamAssistant.Appraiser.Application.Services; using Inc.TeamAssistant.Appraiser.Domain; -using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; namespace Inc.TeamAssistant.Appraiser.Application.CommandHandlers.SetEstimateForStory; diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ShowHelp/ShowHelpCommandHandler.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ShowHelp/ShowHelpCommandHandler.cs index 86a672fa..d51d31b5 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ShowHelp/ShowHelpCommandHandler.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/ShowHelp/ShowHelpCommandHandler.cs @@ -8,7 +8,7 @@ using Inc.TeamAssistant.Appraiser.Model.Commands.FinishAssessmentSession; using Inc.TeamAssistant.Appraiser.Model.Commands.ReVoteEstimate; using Inc.TeamAssistant.Appraiser.Model.Commands.ShowHelp; -using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; using MediatR; namespace Inc.TeamAssistant.Appraiser.Application.CommandHandlers.ShowHelp; diff --git a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/StartStorySelection/StartStorySelectionCommandHandler.cs b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/StartStorySelection/StartStorySelectionCommandHandler.cs index d4fe936e..835431d5 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/StartStorySelection/StartStorySelectionCommandHandler.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/CommandHandlers/StartStorySelection/StartStorySelectionCommandHandler.cs @@ -2,8 +2,8 @@ using Inc.TeamAssistant.Appraiser.Model.Commands.StartStorySelection; using MediatR; using Inc.TeamAssistant.Appraiser.Application.Extensions; -using Inc.TeamAssistant.Appraiser.Model.Common; using Inc.TeamAssistant.DialogContinuations; +using Inc.TeamAssistant.Primitives; namespace Inc.TeamAssistant.Appraiser.Application.CommandHandlers.StartStorySelection; diff --git a/src/Inc.TeamAssistant.Appraiser.Application/ServiceCollectionExtensions.cs b/src/Inc.TeamAssistant.Appraiser.Application/ServiceCollectionExtensions.cs index 090cf8ac..ca13346d 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/ServiceCollectionExtensions.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/ServiceCollectionExtensions.cs @@ -12,7 +12,9 @@ namespace Inc.TeamAssistant.Appraiser.Application; public static class ServiceCollectionExtensions { - public static IServiceCollection AddApplication(this IServiceCollection services, IConfiguration configuration) + public static IServiceCollection AddAppraiserApplication( + this IServiceCollection services, + IConfiguration configuration) { if (services is null) throw new ArgumentNullException(nameof(services)); diff --git a/src/Inc.TeamAssistant.Appraiser.Application/Services/SummaryByStoryBuilder.cs b/src/Inc.TeamAssistant.Appraiser.Application/Services/SummaryByStoryBuilder.cs index 7e5decfc..6e9f6f22 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/Services/SummaryByStoryBuilder.cs +++ b/src/Inc.TeamAssistant.Appraiser.Application/Services/SummaryByStoryBuilder.cs @@ -3,6 +3,7 @@ using Inc.TeamAssistant.Appraiser.Domain; using Inc.TeamAssistant.Appraiser.Model.Commands.AddStoryForEstimate; using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; using MediatR; namespace Inc.TeamAssistant.Appraiser.Application.Services; diff --git a/src/Inc.TeamAssistant.Appraiser.Model/Commands/AcceptEstimate/AcceptEstimateCommand.cs b/src/Inc.TeamAssistant.Appraiser.Model/Commands/AcceptEstimate/AcceptEstimateCommand.cs index ef316c36..0f76e301 100644 --- a/src/Inc.TeamAssistant.Appraiser.Model/Commands/AcceptEstimate/AcceptEstimateCommand.cs +++ b/src/Inc.TeamAssistant.Appraiser.Model/Commands/AcceptEstimate/AcceptEstimateCommand.cs @@ -1,4 +1,5 @@ using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; using MediatR; namespace Inc.TeamAssistant.Appraiser.Model.Commands.AcceptEstimate; diff --git a/src/Inc.TeamAssistant.Appraiser.Model/Commands/ActivateAssessment/ActivateAssessmentCommand.cs b/src/Inc.TeamAssistant.Appraiser.Model/Commands/ActivateAssessment/ActivateAssessmentCommand.cs index 238f8374..08f8c7f5 100644 --- a/src/Inc.TeamAssistant.Appraiser.Model/Commands/ActivateAssessment/ActivateAssessmentCommand.cs +++ b/src/Inc.TeamAssistant.Appraiser.Model/Commands/ActivateAssessment/ActivateAssessmentCommand.cs @@ -1,4 +1,5 @@ using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; using MediatR; namespace Inc.TeamAssistant.Appraiser.Model.Commands.ActivateAssessment; diff --git a/src/Inc.TeamAssistant.Appraiser.Model/Commands/AddStoryForEstimate/AddStoryForEstimateCommand.cs b/src/Inc.TeamAssistant.Appraiser.Model/Commands/AddStoryForEstimate/AddStoryForEstimateCommand.cs index b1cc71b6..d4c3e1ce 100644 --- a/src/Inc.TeamAssistant.Appraiser.Model/Commands/AddStoryForEstimate/AddStoryForEstimateCommand.cs +++ b/src/Inc.TeamAssistant.Appraiser.Model/Commands/AddStoryForEstimate/AddStoryForEstimateCommand.cs @@ -1,4 +1,5 @@ using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; using MediatR; namespace Inc.TeamAssistant.Appraiser.Model.Commands.AddStoryForEstimate; diff --git a/src/Inc.TeamAssistant.Appraiser.Model/Commands/ExitFromAssessmentSession/ExitFromAssessmentSessionCommand.cs b/src/Inc.TeamAssistant.Appraiser.Model/Commands/ExitFromAssessmentSession/ExitFromAssessmentSessionCommand.cs index 622b2829..5ee7ce87 100644 --- a/src/Inc.TeamAssistant.Appraiser.Model/Commands/ExitFromAssessmentSession/ExitFromAssessmentSessionCommand.cs +++ b/src/Inc.TeamAssistant.Appraiser.Model/Commands/ExitFromAssessmentSession/ExitFromAssessmentSessionCommand.cs @@ -1,4 +1,5 @@ using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; using MediatR; namespace Inc.TeamAssistant.Appraiser.Model.Commands.ExitFromAssessmentSession; diff --git a/src/Inc.TeamAssistant.Appraiser.Model/Commands/FinishAssessmentSession/FinishAssessmentSessionCommand.cs b/src/Inc.TeamAssistant.Appraiser.Model/Commands/FinishAssessmentSession/FinishAssessmentSessionCommand.cs index 92ee0a49..2780f0e4 100644 --- a/src/Inc.TeamAssistant.Appraiser.Model/Commands/FinishAssessmentSession/FinishAssessmentSessionCommand.cs +++ b/src/Inc.TeamAssistant.Appraiser.Model/Commands/FinishAssessmentSession/FinishAssessmentSessionCommand.cs @@ -1,4 +1,5 @@ using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; using MediatR; namespace Inc.TeamAssistant.Appraiser.Model.Commands.FinishAssessmentSession; diff --git a/src/Inc.TeamAssistant.Appraiser.Model/Commands/ReVoteEstimate/ReVoteEstimateCommand.cs b/src/Inc.TeamAssistant.Appraiser.Model/Commands/ReVoteEstimate/ReVoteEstimateCommand.cs index 4fa873fd..fea72778 100644 --- a/src/Inc.TeamAssistant.Appraiser.Model/Commands/ReVoteEstimate/ReVoteEstimateCommand.cs +++ b/src/Inc.TeamAssistant.Appraiser.Model/Commands/ReVoteEstimate/ReVoteEstimateCommand.cs @@ -1,4 +1,5 @@ using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; using MediatR; namespace Inc.TeamAssistant.Appraiser.Model.Commands.ReVoteEstimate; diff --git a/src/Inc.TeamAssistant.Appraiser.Model/Commands/SetEstimateForStory/SetEstimateForStoryCommand.cs b/src/Inc.TeamAssistant.Appraiser.Model/Commands/SetEstimateForStory/SetEstimateForStoryCommand.cs index 072d0108..846a4cf9 100644 --- a/src/Inc.TeamAssistant.Appraiser.Model/Commands/SetEstimateForStory/SetEstimateForStoryCommand.cs +++ b/src/Inc.TeamAssistant.Appraiser.Model/Commands/SetEstimateForStory/SetEstimateForStoryCommand.cs @@ -1,4 +1,5 @@ using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; using MediatR; namespace Inc.TeamAssistant.Appraiser.Model.Commands.SetEstimateForStory; diff --git a/src/Inc.TeamAssistant.Appraiser.Model/Commands/StartStorySelection/StartStorySelectionCommand.cs b/src/Inc.TeamAssistant.Appraiser.Model/Commands/StartStorySelection/StartStorySelectionCommand.cs index a1f1055f..e375dc0d 100644 --- a/src/Inc.TeamAssistant.Appraiser.Model/Commands/StartStorySelection/StartStorySelectionCommand.cs +++ b/src/Inc.TeamAssistant.Appraiser.Model/Commands/StartStorySelection/StartStorySelectionCommand.cs @@ -1,4 +1,5 @@ using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; using MediatR; namespace Inc.TeamAssistant.Appraiser.Model.Commands.StartStorySelection; diff --git a/src/Inc.TeamAssistant.Appraiser.Model/Inc.TeamAssistant.Appraiser.Model.csproj b/src/Inc.TeamAssistant.Appraiser.Model/Inc.TeamAssistant.Appraiser.Model.csproj index b30885b9..6ee10725 100644 --- a/src/Inc.TeamAssistant.Appraiser.Model/Inc.TeamAssistant.Appraiser.Model.csproj +++ b/src/Inc.TeamAssistant.Appraiser.Model/Inc.TeamAssistant.Appraiser.Model.csproj @@ -1,7 +1,4 @@ - - - diff --git a/src/Inc.TeamAssistant.CheckIn.Model/Inc.TeamAssistant.CheckIn.Model.csproj b/src/Inc.TeamAssistant.CheckIn.Model/Inc.TeamAssistant.CheckIn.Model.csproj index 505516e3..cda692d2 100644 --- a/src/Inc.TeamAssistant.CheckIn.Model/Inc.TeamAssistant.CheckIn.Model.csproj +++ b/src/Inc.TeamAssistant.CheckIn.Model/Inc.TeamAssistant.CheckIn.Model.csproj @@ -1,7 +1,4 @@ - - - diff --git a/src/Inc.TeamAssistant.Connector.Application/CommandHandlers/Begin/BeginCommandHandler.cs b/src/Inc.TeamAssistant.Connector.Application/CommandHandlers/Begin/BeginCommandHandler.cs new file mode 100644 index 00000000..46a2f839 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Application/CommandHandlers/Begin/BeginCommandHandler.cs @@ -0,0 +1,40 @@ +using Inc.TeamAssistant.Connector.Domain; +using Inc.TeamAssistant.Connector.Model.Commands.Begin; +using Inc.TeamAssistant.DialogContinuations; +using Inc.TeamAssistant.DialogContinuations.Model; +using Inc.TeamAssistant.Primitives; +using MediatR; + +namespace Inc.TeamAssistant.Connector.Application.CommandHandlers.Begin; + +internal sealed class BeginCommandHandler : IRequestHandler +{ + private readonly IDialogContinuation _dialogContinuation; + + public BeginCommandHandler(IDialogContinuation dialogContinuation) + { + _dialogContinuation = dialogContinuation ?? throw new ArgumentNullException(nameof(dialogContinuation)); + } + + public async Task Handle(BeginCommand command, CancellationToken token) + { + if (command is null) + throw new ArgumentNullException(nameof(command)); + + var nextStage = Enum.Parse(command.NextStage); + + var dialogState = _dialogContinuation.TryBegin(command.MessageContext.PersonId, nextStage); + + if (dialogState is null) + throw new ApplicationException("Can not start command dialog."); + + dialogState.AddItem(command.Command); + + if (command.MessageContext.Shared) + dialogState.TryAttachMessage(new ChatMessage( + command.MessageContext.ChatId, + command.MessageContext.MessageId)); + + return CommandResult.Build(command.Notification); + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Application/CommandHandlers/CreateTeam/CreateTeamCommandHandler.cs b/src/Inc.TeamAssistant.Connector.Application/CommandHandlers/CreateTeam/CreateTeamCommandHandler.cs new file mode 100644 index 00000000..7dc244d0 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Application/CommandHandlers/CreateTeam/CreateTeamCommandHandler.cs @@ -0,0 +1,41 @@ +using Inc.TeamAssistant.Connector.Application.Contracts; +using Inc.TeamAssistant.Connector.Domain; +using Inc.TeamAssistant.Connector.Model.Commands.CreateTeam; +using Inc.TeamAssistant.DialogContinuations; +using Inc.TeamAssistant.Primitives; +using MediatR; + +namespace Inc.TeamAssistant.Connector.Application.CommandHandlers.CreateTeam; + +internal sealed class CreateTeamCommandHandler : IRequestHandler +{ + private readonly ITeamRepository _teamRepository; + private readonly IDialogContinuation _dialogContinuation; + + public CreateTeamCommandHandler( + ITeamRepository teamRepository, + IDialogContinuation dialogContinuation) + { + _teamRepository = teamRepository ?? throw new ArgumentNullException(nameof(teamRepository)); + _dialogContinuation = dialogContinuation ?? throw new ArgumentNullException(nameof(dialogContinuation)); + } + + public async Task Handle(CreateTeamCommand command, CancellationToken token) + { + if (command is null) + throw new ArgumentNullException(nameof(command)); + + var team = new Team( + command.MessageContext.BotId, + command.MessageContext.ChatId, + command.Name); + + await _teamRepository.Upsert(team, token); + + _dialogContinuation.End(command.MessageContext.PersonId, BotCommandStage.EnterText); + + return CommandResult.Build(NotificationMessage.Create( + command.MessageContext.ChatId, + $"https://t.me/{command.BotName}?start={team.Id:N}")); + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Application/CommandHandlers/End/EndCommandHandler.cs b/src/Inc.TeamAssistant.Connector.Application/CommandHandlers/End/EndCommandHandler.cs new file mode 100644 index 00000000..6563df33 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Application/CommandHandlers/End/EndCommandHandler.cs @@ -0,0 +1,37 @@ +using Inc.TeamAssistant.Connector.Domain; +using Inc.TeamAssistant.Connector.Model.Commands.End; +using Inc.TeamAssistant.DialogContinuations; +using Inc.TeamAssistant.DialogContinuations.Model; +using Inc.TeamAssistant.Primitives; +using MediatR; + +namespace Inc.TeamAssistant.Connector.Application.CommandHandlers.End; + +internal sealed class EndCommandHandler : IRequestHandler +{ + private readonly IDialogContinuation _dialogContinuation; + + public EndCommandHandler(IDialogContinuation dialogContinuation) + { + _dialogContinuation = dialogContinuation ?? throw new ArgumentNullException(nameof(dialogContinuation)); + } + + public Task Handle(EndCommand command, CancellationToken token) + { + if (command is null) + throw new ArgumentNullException(nameof(command)); + + var currentStage = Enum.Parse(command.CurrentStage); + + var dialogState = _dialogContinuation.End(command.MessageContext.PersonId, currentStage); + + if (command.MessageContext.Shared) + dialogState.TryAttachMessage(new ChatMessage( + command.MessageContext.ChatId, + command.MessageContext.MessageId)); + + // TODO: remove messages + + return Task.FromResult(CommandResult.Empty); + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Application/CommandHandlers/JoinToTeam/JoinToTeamCommandHandler.cs b/src/Inc.TeamAssistant.Connector.Application/CommandHandlers/JoinToTeam/JoinToTeamCommandHandler.cs new file mode 100644 index 00000000..8f779786 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Application/CommandHandlers/JoinToTeam/JoinToTeamCommandHandler.cs @@ -0,0 +1,38 @@ +using Inc.TeamAssistant.Connector.Application.Contracts; +using Inc.TeamAssistant.Connector.Model.Commands.JoinToTeam; +using Inc.TeamAssistant.Primitives; +using MediatR; + +namespace Inc.TeamAssistant.Connector.Application.CommandHandlers.JoinToTeam; + +internal sealed class JoinToTeamCommandHandler : IRequestHandler +{ + private readonly ITeamRepository _teamRepository; + private readonly IPersonRepository _personRepository; + + public JoinToTeamCommandHandler(ITeamRepository teamRepository, IPersonRepository personRepository) + { + _teamRepository = teamRepository ?? throw new ArgumentNullException(nameof(teamRepository)); + _personRepository = personRepository ?? throw new ArgumentNullException(nameof(personRepository)); + } + + public async Task Handle(JoinToTeamCommand command, CancellationToken token) + { + if (command is null) + throw new ArgumentNullException(nameof(command)); + + var team = await _teamRepository.Find(command.TeamId, token); + if (team is null) + throw new ApplicationException($"Team {command.TeamId} was not found."); + + var person = await _personRepository.Find(command.MessageContext.PersonId, token); + if (person is null) + throw new ApplicationException($"Person {command.MessageContext.PersonId} was not found."); + + team.AddTeammate(person); + + await _teamRepository.Upsert(team, token); + + return CommandResult.Empty; + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Application/CommandHandlers/LeaveFromTeam/LeaveFromTeamCommandHandler.cs b/src/Inc.TeamAssistant.Connector.Application/CommandHandlers/LeaveFromTeam/LeaveFromTeamCommandHandler.cs new file mode 100644 index 00000000..6a1b2303 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Application/CommandHandlers/LeaveFromTeam/LeaveFromTeamCommandHandler.cs @@ -0,0 +1,40 @@ +using Inc.TeamAssistant.Connector.Application.Contracts; +using Inc.TeamAssistant.Connector.Domain; +using Inc.TeamAssistant.Connector.Model.Commands.LeaveFromTeam; +using Inc.TeamAssistant.DialogContinuations; +using Inc.TeamAssistant.Primitives; +using MediatR; + +namespace Inc.TeamAssistant.Connector.Application.CommandHandlers.LeaveFromTeam; + +internal sealed class LeaveFromTeamCommandHandler : IRequestHandler +{ + private readonly ITeamRepository _teamRepository; + private readonly IDialogContinuation _dialogContinuation; + + public LeaveFromTeamCommandHandler( + ITeamRepository teamRepository, + IDialogContinuation dialogContinuation) + { + _teamRepository = teamRepository ?? throw new ArgumentNullException(nameof(teamRepository)); + _dialogContinuation = dialogContinuation ?? throw new ArgumentNullException(nameof(dialogContinuation)); + } + + public async Task Handle(LeaveFromTeamCommand command, CancellationToken token) + { + if (command is null) + throw new ArgumentNullException(nameof(command)); + + var team = await _teamRepository.Find(command.TeamId, token); + if (team is null) + throw new ApplicationException($"Team {command.TeamId} was not found."); + + team.RemoveTeammate(command.MessageContext.PersonId); + + await _teamRepository.Upsert(team, token); + + _dialogContinuation.End(command.MessageContext.PersonId, BotCommandStage.SelectTeam); + + return CommandResult.Empty; + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Application/Contracts/IBotRepository.cs b/src/Inc.TeamAssistant.Connector.Application/Contracts/IBotRepository.cs new file mode 100644 index 00000000..2efe8cbf --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Application/Contracts/IBotRepository.cs @@ -0,0 +1,10 @@ +using Inc.TeamAssistant.Connector.Domain; + +namespace Inc.TeamAssistant.Connector.Application.Contracts; + +public interface IBotRepository +{ + Task> GetAll(CancellationToken token); + + Task Find(Guid id, CancellationToken token); +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Application/Contracts/IPersonRepository.cs b/src/Inc.TeamAssistant.Connector.Application/Contracts/IPersonRepository.cs new file mode 100644 index 00000000..f8399bf3 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Application/Contracts/IPersonRepository.cs @@ -0,0 +1,10 @@ +using Inc.TeamAssistant.Connector.Domain; + +namespace Inc.TeamAssistant.Connector.Application.Contracts; + +public interface IPersonRepository +{ + Task Find(long personId, CancellationToken token); + + Task Upsert(Person person, CancellationToken token); +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Application/Contracts/ITeamRepository.cs b/src/Inc.TeamAssistant.Connector.Application/Contracts/ITeamRepository.cs new file mode 100644 index 00000000..86c98555 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Application/Contracts/ITeamRepository.cs @@ -0,0 +1,10 @@ +using Inc.TeamAssistant.Connector.Domain; + +namespace Inc.TeamAssistant.Connector.Application.Contracts; + +public interface ITeamRepository +{ + Task Find(Guid teamId, CancellationToken token); + + Task Upsert(Team team, CancellationToken token); +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Application/Inc.TeamAssistant.Connector.Application.csproj b/src/Inc.TeamAssistant.Connector.Application/Inc.TeamAssistant.Connector.Application.csproj new file mode 100644 index 00000000..5862155e --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Application/Inc.TeamAssistant.Connector.Application.csproj @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/Inc.TeamAssistant.Connector.Application/Messages.cs b/src/Inc.TeamAssistant.Connector.Application/Messages.cs new file mode 100644 index 00000000..187cc4bd --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Application/Messages.cs @@ -0,0 +1,9 @@ +using Inc.TeamAssistant.Primitives; + +namespace Inc.TeamAssistant.Connector.Application; + +internal static class Messages +{ + public static MessageId Connector_EnterTeamName = new(nameof(Connector_EnterTeamName)); + public static MessageId Connector_SelectTeam = new(nameof(Connector_SelectTeam)); +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Application/ServiceCollectionExtensions.cs b/src/Inc.TeamAssistant.Connector.Application/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..8bbedcc2 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Application/ServiceCollectionExtensions.cs @@ -0,0 +1,20 @@ +using Inc.TeamAssistant.Connector.Application.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Inc.TeamAssistant.Connector.Application; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddConnectorApplication(this IServiceCollection services) + { + if (services is null) + throw new ArgumentNullException(nameof(services)); + + services + .AddSingleton() + .AddSingleton() + .AddHostedService(); + + return services; + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Application/Services/CommandFactory.cs b/src/Inc.TeamAssistant.Connector.Application/Services/CommandFactory.cs new file mode 100644 index 00000000..892a09f3 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Application/Services/CommandFactory.cs @@ -0,0 +1,110 @@ +using FluentValidation; +using FluentValidation.Results; +using Inc.TeamAssistant.Connector.Domain; +using Inc.TeamAssistant.Connector.Model; +using Inc.TeamAssistant.Connector.Model.Commands.Begin; +using Inc.TeamAssistant.Connector.Model.Commands.CreateTeam; +using Inc.TeamAssistant.Connector.Model.Commands.End; +using Inc.TeamAssistant.Connector.Model.Commands.JoinToTeam; +using Inc.TeamAssistant.Connector.Model.Commands.LeaveFromTeam; +using Inc.TeamAssistant.DialogContinuations; +using Inc.TeamAssistant.Primitives; +using MediatR; +using Telegram.Bot; + +namespace Inc.TeamAssistant.Connector.Application.Services; + +internal sealed class CommandFactory +{ + private readonly IDialogContinuation _dialogContinuation; + private readonly IMessageBuilder _messageBuilder; + + public CommandFactory(IDialogContinuation dialogContinuation, IMessageBuilder messageBuilder) + { + _dialogContinuation = dialogContinuation ?? throw new ArgumentNullException(nameof(dialogContinuation)); + _messageBuilder = messageBuilder ?? throw new ArgumentNullException(nameof(messageBuilder)); + } + + public async Task?> TryCreate( + ITelegramBotClient client, + Bot bot, + MessageContext messageContext, + BotCommand botCommand, + string message) + { + if (client is null) + throw new ArgumentNullException(nameof(client)); + if (bot is null) + throw new ArgumentNullException(nameof(bot)); + if (messageContext is null) + throw new ArgumentNullException(nameof(messageContext)); + if (botCommand is null) + throw new ArgumentNullException(nameof(botCommand)); + if (string.IsNullOrWhiteSpace(message)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(message)); + + var dialogState = _dialogContinuation.Find(messageContext.PersonId); + + if (dialogState is not null && messageContext.Text.Equals("/cancel", StringComparison.InvariantCultureIgnoreCase)) + return new EndCommand(messageContext, dialogState.ContinuationState.ToString()); + + if (dialogState?.ContinuationState == BotCommandStage.EnterText) + return new CreateTeamCommand(messageContext, bot.Name, message); + + if (dialogState?.ContinuationState == BotCommandStage.SelectTeam) + return new LeaveFromTeamCommand(messageContext, Guid.Parse(message.Replace("/", ""))); + + var newTeamCommand = "new_team"; + if (botCommand.Value.Equals(newTeamCommand, StringComparison.InvariantCultureIgnoreCase)) + { + var notification = NotificationMessage.Create( + messageContext.ChatId, + await _messageBuilder.Build(Messages.Connector_EnterTeamName, messageContext.LanguageId)); + + return new BeginCommand( + messageContext, + BotCommandStage.EnterText.ToString(), + newTeamCommand, + notification); + } + + var leaveTeamCommand = "leave_team"; + if (botCommand.Value.Equals(leaveTeamCommand, StringComparison.InvariantCultureIgnoreCase)) + { + var targetTeams = bot.Teams.Where(t => t.Teammates.Any(tm => tm.Id == messageContext.PersonId)).ToArray(); + if (!targetTeams.Any()) + throw new ValidationException(new[] + { + new ValidationFailure("Teams", "Вы не состоите не в одной команде.") + }); + if (targetTeams.Length == 1) + return new LeaveFromTeamCommand(messageContext, targetTeams[0].Id); + + var notification = NotificationMessage.Create( + messageContext.ChatId, + await _messageBuilder.Build(Messages.Connector_SelectTeam, messageContext.LanguageId)); + + foreach (var team in targetTeams) + notification.WithButton(new Button(team.Name, team.Id.ToString())); + + return new BeginCommand( + messageContext, + BotCommandStage.SelectTeam.ToString(), + leaveTeamCommand, + notification); + } + + var startCommand = "start"; + if (botCommand.Value.Equals(startCommand, StringComparison.InvariantCultureIgnoreCase)) + { + var token = message + .Replace($"/{startCommand}", string.Empty, StringComparison.InvariantCultureIgnoreCase) + .Trim(); + + if (Guid.TryParse(token, out var value)) + return new JoinToTeamCommand(messageContext, value); + } + + return null; + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Application/Services/TelegramBotConnector.cs b/src/Inc.TeamAssistant.Connector.Application/Services/TelegramBotConnector.cs new file mode 100644 index 00000000..322a9e70 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Application/Services/TelegramBotConnector.cs @@ -0,0 +1,38 @@ +using Inc.TeamAssistant.Connector.Application.Contracts; +using Microsoft.Extensions.Hosting; +using Telegram.Bot; + +namespace Inc.TeamAssistant.Connector.Application.Services; + +internal sealed class TelegramBotConnector : IHostedService +{ + private readonly TelegramBotMessageHandler _handler; + private readonly IBotRepository _botRepository; + + public TelegramBotConnector(TelegramBotMessageHandler handler, IBotRepository botRepository) + { + if (handler is null) + throw new ArgumentNullException(nameof(handler)); + + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + _botRepository = botRepository ?? throw new ArgumentNullException(nameof(botRepository)); + } + + public async Task StartAsync(CancellationToken token) + { + var bots = await _botRepository.GetAll(token); + + foreach (var bot in bots) + { + var client = new TelegramBotClient(bot.Token); + + client.StartReceiving( + (c, m, t) => _handler.Handle(c, m, bot.Id, t), + (c, e, t) => _handler.OnError(c, e, bot.Name, t), + cancellationToken: token); + } + + } + + public Task StopAsync(CancellationToken token) => Task.CompletedTask; +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Application/Services/TelegramBotMessageHandler.cs b/src/Inc.TeamAssistant.Connector.Application/Services/TelegramBotMessageHandler.cs new file mode 100644 index 00000000..9eb4cc24 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Application/Services/TelegramBotMessageHandler.cs @@ -0,0 +1,279 @@ +using System.Text; +using FluentValidation; +using Inc.TeamAssistant.Connector.Application.Contracts; +using Inc.TeamAssistant.Connector.Domain; +using Inc.TeamAssistant.Connector.Model; +using Inc.TeamAssistant.DialogContinuations; +using Inc.TeamAssistant.Languages; +using Inc.TeamAssistant.Primitives; +using MediatR; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Telegram.Bot; +using Telegram.Bot.Types; +using Telegram.Bot.Types.ReplyMarkups; + +namespace Inc.TeamAssistant.Connector.Application.Services; + +internal sealed class TelegramBotMessageHandler +{ + private readonly ILogger _logger; + private readonly IBotRepository _botRepository; + private readonly IDialogContinuation _dialogContinuation; + private readonly IPersonRepository _personRepository; + private readonly CommandFactory _commandFactory; + private readonly IServiceProvider _serviceProvider; + + public TelegramBotMessageHandler( + ILogger logger, + IBotRepository botRepository, + IDialogContinuation dialogContinuation, + IPersonRepository personRepository, + CommandFactory commandFactory, + IServiceProvider serviceProvider) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _botRepository = botRepository ?? throw new ArgumentNullException(nameof(botRepository)); + _dialogContinuation = dialogContinuation ?? throw new ArgumentNullException(nameof(dialogContinuation)); + _personRepository = personRepository ?? throw new ArgumentNullException(nameof(personRepository)); + _commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory)); + _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); + } + + public async Task Handle(ITelegramBotClient client, Update update, Guid botId, CancellationToken token) + { + if (client is null) + throw new ArgumentNullException(nameof(client)); + if (update is null) + throw new ArgumentNullException(nameof(update)); + + var bot = await _botRepository.Find(botId, token); + if (bot is null) + throw new ApplicationException($"Bot {botId} was not found."); + + var messageContext = await CreateMessageContext(update, bot.Id, token); + if (messageContext is null) + return; + + try + { + foreach (var botCommand in bot.Commands) + if (messageContext.Cmd?.StartsWith(botCommand.Value, StringComparison.InvariantCultureIgnoreCase) == + true) + { + var command = await _commandFactory.TryCreate( + client, + bot, + messageContext, + botCommand, + messageContext.Text); + + if (command is not null) + { + await Execute(client, messageContext, command, token); + return; + } + } + } + catch (ValidationException validationException) + { + await TrySend(client, messageContext.ChatId, ToMessage(validationException), token); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unhandled exception"); + + await TrySend( + client, + messageContext.ChatId, + "An unhandled exception has occurred. Try running the command again.", + token); + } + } + + public Task OnError(ITelegramBotClient client, Exception exception, string botName, CancellationToken token) + { + if (string.IsNullOrWhiteSpace(botName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(botName)); + + _logger.LogError(exception, "Bot {BotName} listened message with error", botName); + + return Task.CompletedTask; + } + + private async Task CreateMessageContext(Update update, Guid botId, CancellationToken token) + { + if (!string.IsNullOrWhiteSpace(update.Message?.Text) + && update.Message.From is not null + && !update.Message.From.IsBot) + { + var person = await EnsurePerson(update.Message.From, token); + var inProgressCommand = _dialogContinuation.Find(update.Message.From.Id); + + return new( + botId, + inProgressCommand is not null + ? inProgressCommand.Data.First() + : update.Message.Text.Replace("/", string.Empty), + update.Message.Text, + update.Message.Chat.Id, + update.Message.From.Id, + update.Message.From.FirstName, + update.Message.MessageId, + person.LanguageId); + } + + if (!string.IsNullOrWhiteSpace(update.CallbackQuery?.Data) + && update.CallbackQuery.Message is not null + && !update.CallbackQuery.From.IsBot) + { + var person = await EnsurePerson(update.CallbackQuery.From, token); + var inProgressCommand = _dialogContinuation.Find(update.CallbackQuery.From.Id); + + return new( + botId, + inProgressCommand is not null ? inProgressCommand.Data.First() : update.CallbackQuery.Data, + update.CallbackQuery.Data, + update.CallbackQuery.Message.Chat.Id, + update.CallbackQuery.From.Id, + update.CallbackQuery.From.FirstName, + update.CallbackQuery.Message.MessageId, + person.LanguageId); + } + + return null; + } + + private async Task EnsurePerson(User user, CancellationToken token) + { + if (user is null) + throw new ArgumentNullException(nameof(user)); + + var languageId = string.IsNullOrWhiteSpace(user.LanguageCode) + ? LanguageSettings.DefaultLanguageId + : new LanguageId(user.LanguageCode); + var person = new Person( + user.Id, + user.FirstName, + languageId, + user.Username); + + await _personRepository.Upsert(person, token); + + return person; + } + + private async Task Execute( + ITelegramBotClient client, + MessageContext messageContext, + IRequest command, + CancellationToken token) + { + if (client is null) + throw new ArgumentNullException(nameof(client)); + if (messageContext is null) + throw new ArgumentNullException(nameof(messageContext)); + if (command is null) + throw new ArgumentNullException(nameof(command)); + + using var scope = _serviceProvider.CreateScope(); + var mediatr = scope.ServiceProvider.GetRequiredService(); + var commandResult = await mediatr.Send(command, token); + + foreach (var notification in commandResult.Notifications) + await ProcessNotification(client, notification, messageContext, token); + } + + private async Task ProcessNotification( + ITelegramBotClient client, + NotificationMessage notificationMessage, + MessageContext messageContext, + CancellationToken token) + { + if (client is null) + throw new ArgumentNullException(nameof(client)); + if (notificationMessage is null) + throw new ArgumentNullException(nameof(notificationMessage)); + if (messageContext is null) + throw new ArgumentNullException(nameof(messageContext)); + + if (notificationMessage.TargetChatIds?.Any() == true) + foreach (var targetChatId in notificationMessage.TargetChatIds) + { + var message = await client.SendTextMessageAsync( + targetChatId, + notificationMessage.Text, + replyMarkup: ToReplyMarkup(notificationMessage), + cancellationToken: token); + + if (notificationMessage.Handler is not null) + { + var command = notificationMessage.Handler( + targetChatId, + messageContext.FirstName, + message.MessageId); + + await Execute(client, messageContext, command, token); + } + } + + if (notificationMessage.TargetMessages?.Any() == true) + foreach (var message in notificationMessage.TargetMessages) + { + await client.EditMessageTextAsync( + new(message.ChatId), + message.MessageId, + notificationMessage.Text, + replyMarkup: ToReplyMarkup(notificationMessage), + cancellationToken: token); + } + } + + private InlineKeyboardMarkup? ToReplyMarkup(NotificationMessage message) + { + const int rowCapacity = 7; + + return message.Buttons.Any() + ? new InlineKeyboardMarkup(message.Buttons + .Select(b => InlineKeyboardButton.WithCallbackData(b.Text, b.Data)) + .Chunk(rowCapacity)) + : null; + } + + private async Task TrySend(ITelegramBotClient client, long chatId, string message, CancellationToken token) + { + if (client is null) + throw new ArgumentNullException(nameof(client)); + if (string.IsNullOrWhiteSpace(message)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(message)); + + try + { + await client.SendTextMessageAsync( + chatId, + message, + cancellationToken: token); + } + catch (Exception ex) + { + _logger.LogError(ex, "Can not send message to chat {TargetChatId}", chatId); + } + } + + private static string ToMessage(ValidationException validationException) + { + if (validationException is null) + throw new ArgumentNullException(nameof(validationException)); + + return validationException.Errors.Any() + ? validationException.Errors.Aggregate( + new StringBuilder(), + (sb, e) => + { + sb.AppendLine(e.ErrorMessage); + return sb; + }, + sb => sb.ToString()) + : validationException.Message; + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.DataAccess/BotRepository.cs b/src/Inc.TeamAssistant.Connector.DataAccess/BotRepository.cs new file mode 100644 index 00000000..36fa6400 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.DataAccess/BotRepository.cs @@ -0,0 +1,124 @@ +using Dapper; +using Inc.TeamAssistant.Connector.Application.Contracts; +using Inc.TeamAssistant.Connector.Domain; +using Inc.TeamAssistant.Primitives; +using Npgsql; + +namespace Inc.TeamAssistant.Connector.DataAccess; + +internal sealed class BotRepository : IBotRepository +{ + private readonly string _connectionString; + + public BotRepository(string connectionString) + { + if (string.IsNullOrWhiteSpace(connectionString)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(connectionString)); + + _connectionString = connectionString; + } + + public async Task> GetAll(CancellationToken token) + { + var command = new CommandDefinition(@" + SELECT + b.id AS id, + b.name AS name, + b.token AS token + FROM connector.bots AS b;", + flags: CommandFlags.None, + cancellationToken: token); + + await using var connection = new NpgsqlConnection(_connectionString); + + var results = await connection.QueryAsync(command); + return results.ToArray(); + } + + public async Task Find(Guid id, CancellationToken token) + { + var command = new CommandDefinition(@" + SELECT + b.id AS id, + b.name AS name, + b.token AS token + FROM connector.bots AS b + WHERE b.id = @id; + + SELECT + bc.id AS id, + bc.value AS value, + bc.help_message_id AS helpmessageid + FROM connector.bot_commands AS bc + WHERE bc.bot_id = @id; + + SELECT + bcs.bot_command_id AS botcommandid, + bcs.value AS value + FROM connector.bot_command_stages AS bcs + JOIN connector.bot_commands AS bc ON bc.id = bcs.bot_command_id + WHERE bc.bot_id = @id; + + SELECT + t.id AS id, + t.bot_id AS botid, + t.chat_id AS chatid, + t.name AS name + FROM connector.teams AS t + WHERE t.bot_id = @id; + + SELECT + p.id AS id, + p.name AS name, + p.language_id AS languageid, + p.username AS username, + tm.team_id AS teamid + FROM connector.persons AS p + JOIN connector.teammates AS tm ON p.id = tm.person_id + JOIN connector.teams AS t ON t.id = tm.team_id + WHERE t.bot_id = @id;", + new { id }, + flags: CommandFlags.None, + cancellationToken: token); + + await using var connection = new NpgsqlConnection(_connectionString); + + var query = await connection.QueryMultipleAsync(command); + + var bot = await query.ReadSingleOrDefaultAsync(); + var botCommands = await query.ReadAsync(); + var botCommandStages = (await query.ReadAsync<( + Guid BotCommandId, + BotCommandStage Value)>()) + .ToLookup(s => s.BotCommandId); + var teams = await query.ReadAsync(); + var personsLookup = (await query.ReadAsync<( + long Id, + string Name, + LanguageId LanguageId, + string? Username, + Guid TeamId)>()) + .ToLookup(p => p.TeamId); + + if (bot is not null) + { + foreach (var botCommand in botCommands) + { + foreach (var botCommandStage in botCommandStages[botCommand.Id]) + botCommand.AddStage(botCommandStage.Value); + + bot.AddCommand(botCommand); + } + + foreach (var team in teams) + { + foreach (var person in personsLookup[team.Id]) + team.AddTeammate(new Person(person.Id, person.Name, person.LanguageId, person.Username)); + + bot.AddTeam(team); + } + } + + return bot; + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.DataAccess/Inc.TeamAssistant.Connector.DataAccess.csproj b/src/Inc.TeamAssistant.Connector.DataAccess/Inc.TeamAssistant.Connector.DataAccess.csproj new file mode 100644 index 00000000..278b143f --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.DataAccess/Inc.TeamAssistant.Connector.DataAccess.csproj @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/Inc.TeamAssistant.Connector.DataAccess/LanguageIdTypeHandler.cs b/src/Inc.TeamAssistant.Connector.DataAccess/LanguageIdTypeHandler.cs new file mode 100644 index 00000000..b1d28144 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.DataAccess/LanguageIdTypeHandler.cs @@ -0,0 +1,25 @@ +using System.Data; +using Dapper; +using Inc.TeamAssistant.Primitives; + +namespace Inc.TeamAssistant.Connector.DataAccess; + +internal sealed class LanguageIdTypeHandler : SqlMapper.TypeHandler +{ + public override void SetValue(IDbDataParameter parameter, LanguageId? languageId) + { + if (parameter is null) + throw new ArgumentNullException(nameof(parameter)); + + parameter.DbType = DbType.String; + parameter.Value = languageId?.Value; + } + + public override LanguageId Parse(object value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + return new((string)value); + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.DataAccess/MessageIdTypeHandler.cs b/src/Inc.TeamAssistant.Connector.DataAccess/MessageIdTypeHandler.cs new file mode 100644 index 00000000..211c0546 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.DataAccess/MessageIdTypeHandler.cs @@ -0,0 +1,25 @@ +using System.Data; +using Dapper; +using Inc.TeamAssistant.Primitives; + +namespace Inc.TeamAssistant.Connector.DataAccess; + +internal sealed class MessageIdTypeHandler : SqlMapper.TypeHandler +{ + public override void SetValue(IDbDataParameter parameter, MessageId? messageId) + { + if (parameter is null) + throw new ArgumentNullException(nameof(parameter)); + + parameter.DbType = DbType.String; + parameter.Value = messageId?.Value; + } + + public override MessageId Parse(object value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + return new((string)value); + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.DataAccess/PersonRepository.cs b/src/Inc.TeamAssistant.Connector.DataAccess/PersonRepository.cs new file mode 100644 index 00000000..01946c3c --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.DataAccess/PersonRepository.cs @@ -0,0 +1,65 @@ +using Dapper; +using Inc.TeamAssistant.Connector.Application.Contracts; +using Inc.TeamAssistant.Connector.Domain; +using Npgsql; + +namespace Inc.TeamAssistant.Connector.DataAccess; + +internal sealed class PersonRepository : IPersonRepository +{ + private readonly string _connectionString; + + public PersonRepository(string connectionString) + { + if (string.IsNullOrWhiteSpace(connectionString)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(connectionString)); + + _connectionString = connectionString; + } + + public async Task Find(long personId, CancellationToken token) + { + var command = new CommandDefinition(@" + SELECT + p.id AS id, + p.name AS name, + p.language_id AS languageid, + p.username AS username + FROM connector.persons AS p + WHERE p.id = @person_id;", + new { person_id = personId }, + flags: CommandFlags.None, + cancellationToken: token); + + await using var connection = new NpgsqlConnection(_connectionString); + + return await connection.QuerySingleOrDefaultAsync(command); + } + + public async Task Upsert(Person person, CancellationToken token) + { + if (person is null) + throw new ArgumentNullException(nameof(person)); + + var command = new CommandDefinition(@" + INSERT INTO connector.persons (id, name, language_id, username) + VALUES (@id, @name, @language_id, @username) + ON CONFLICT (id) DO UPDATE SET + name = EXCLUDED.name, + language_id = EXCLUDED.language_id, + username = EXCLUDED.username;", + new + { + id = person.Id, + name = person.Name, + language_id = person.LanguageId.Value, + username = person.Username + }, + flags: CommandFlags.None, + cancellationToken: token); + + await using var connection = new NpgsqlConnection(_connectionString); + + await connection.ExecuteAsync(command); + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.DataAccess/ServiceCollectionExtensions.cs b/src/Inc.TeamAssistant.Connector.DataAccess/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..bad85129 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.DataAccess/ServiceCollectionExtensions.cs @@ -0,0 +1,29 @@ +using Dapper; +using Inc.TeamAssistant.Connector.Application.Contracts; +using Microsoft.Extensions.DependencyInjection; + +namespace Inc.TeamAssistant.Connector.DataAccess; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddConnectorDataAccess(this IServiceCollection services, string connectionString) + { + if (services is null) + throw new ArgumentNullException(nameof(services)); + if (string.IsNullOrWhiteSpace(connectionString)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(connectionString)); + + SqlMapper.AddTypeHandler(new MessageIdTypeHandler()); + SqlMapper.AddTypeHandler(new LanguageIdTypeHandler()); + + services + .AddSingleton( + sp => ActivatorUtilities.CreateInstance(sp, connectionString)) + .AddSingleton( + sp => ActivatorUtilities.CreateInstance(sp, connectionString)) + .AddSingleton( + sp => ActivatorUtilities.CreateInstance(sp, connectionString)); + + return services; + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.DataAccess/TeamRepository.cs b/src/Inc.TeamAssistant.Connector.DataAccess/TeamRepository.cs new file mode 100644 index 00000000..4099565b --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.DataAccess/TeamRepository.cs @@ -0,0 +1,116 @@ +using Dapper; +using Inc.TeamAssistant.Connector.Application.Contracts; +using Inc.TeamAssistant.Connector.Domain; +using Npgsql; + +namespace Inc.TeamAssistant.Connector.DataAccess; + +internal sealed class TeamRepository : ITeamRepository +{ + private readonly string _connectionString; + + public TeamRepository(string connectionString) + { + if (string.IsNullOrWhiteSpace(connectionString)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(connectionString)); + + _connectionString = connectionString; + } + + public async Task Find(Guid teamId, CancellationToken token) + { + var command = new CommandDefinition(@" + SELECT + id AS id, + bot_id AS botid, + chat_id AS chatid, + name AS name + FROM connector.teams AS t + WHERE t.id = @team_id; + + SELECT + p.id AS id, + p.name AS name, + p.language_id AS languageid, + p.username AS username + FROM connector.persons AS p + JOIN connector.teammates AS tm ON p.id = tm.person_id + WHERE tm.team_id = @team_id;", + new { team_id = teamId }, + flags: CommandFlags.None, + cancellationToken: token); + + await using var connection = new NpgsqlConnection(_connectionString); + + var query = await connection.QueryMultipleAsync(command); + + var team = await query.ReadSingleOrDefaultAsync(); + var persons = await query.ReadAsync(); + + if (team is not null) + foreach (var person in persons) + team.AddTeammate(person); + + return team; + } + + public async Task Upsert(Team team, CancellationToken token) + { + if (team is null) + throw new ArgumentNullException(nameof(team)); + + var upsertTeam = new CommandDefinition(@" + INSERT INTO connector.teams (id, bot_id, chat_id, name) + VALUES (@id, @bot_id, @chat_id, @name) + ON CONFLICT (id) DO UPDATE SET + bot_id = EXCLUDED.bot_id, + chat_id = EXCLUDED.chat_id, + name = EXCLUDED.name;", + new + { + id = team.Id, + bot_id = team.BotId, + chat_id = team.ChatId, + name = team.Name + }, + flags: CommandFlags.None, + cancellationToken: token); + + var personIds = team.Teammates.Select(t => t.Id).ToArray(); + var upsertTeammates = new CommandDefinition(@" + MERGE INTO connector.teammates AS ttm + USING ( + SELECT DISTINCT ON (q.person_id) @team_id AS team_id, q.person_id AS person_id, q.mark_as_removed + FROM ( + SELECT tm.person_id, false AS mark_as_removed + FROM UNNEST(@person_ids) AS tm(person_id) + UNION + SELECT tm.person_id, true AS mark_as_removed + FROM connector.teammates AS tm + WHERE tm.team_id = @team_id) AS q + ORDER BY q.person_id, q.mark_as_removed + ) AS stm ON ttm.team_id = stm.team_id AND ttm.person_id = stm.person_id + WHEN NOT MATCHED THEN + INSERT VALUES(stm.team_id, stm.person_id) + WHEN MATCHED AND NOT stm.mark_as_removed THEN + UPDATE SET team_id = stm.team_id, person_id = stm.person_id + WHEN MATCHED THEN + DELETE;", + new + { + person_ids = personIds, + team_id = team.Id + }, + flags: CommandFlags.None, + cancellationToken: token); + + await using var connection = new NpgsqlConnection(_connectionString); + await connection.OpenAsync(token); + await using var transaction = await connection.BeginTransactionAsync(token); + + await connection.ExecuteAsync(upsertTeam); + await connection.ExecuteAsync(upsertTeammates); + + await transaction.CommitAsync(token); + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Domain/Bot.cs b/src/Inc.TeamAssistant.Connector.Domain/Bot.cs new file mode 100644 index 00000000..8c53850b --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Domain/Bot.cs @@ -0,0 +1,51 @@ +namespace Inc.TeamAssistant.Connector.Domain; + +public sealed class Bot +{ + public Guid Id { get; private set; } + public string Name { get; private set; } = default!; + public string Token { get; private set; } = default!; + + private readonly List _commands = new(); + public IReadOnlyCollection Commands => _commands; + + private readonly List _teams = new(); + public IReadOnlyCollection Teams => _teams; + + private Bot() + { + } + + public Bot(string name, string token) + : this() + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + if (string.IsNullOrWhiteSpace(token)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(token)); + + Id = Guid.NewGuid(); + Name = name; + Token = token; + } + + public Bot AddCommand(BotCommand botCommand) + { + if (botCommand is null) + throw new ArgumentNullException(nameof(botCommand)); + + _commands.Add(botCommand); + + return this; + } + + public Bot AddTeam(Team team) + { + if (team is null) + throw new ArgumentNullException(nameof(team)); + + _teams.Add(team); + + return this; + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Domain/BotCommand.cs b/src/Inc.TeamAssistant.Connector.Domain/BotCommand.cs new file mode 100644 index 00000000..9c83dbc3 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Domain/BotCommand.cs @@ -0,0 +1,35 @@ +using Inc.TeamAssistant.Primitives; + +namespace Inc.TeamAssistant.Connector.Domain; + +public sealed class BotCommand +{ + public Guid Id { get; private set; } + public string Value { get; private set; } = default!; + public MessageId HelpMessageId { get; private set; } = default!; + + private readonly List _stages = new(); + public IReadOnlyCollection Stages => _stages; + + private BotCommand() + { + } + + public BotCommand(string value, MessageId helpMessageId) + : this() + { + if (string.IsNullOrWhiteSpace(value)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(value)); + + Id = Guid.NewGuid(); + Value = value; + HelpMessageId = helpMessageId ?? throw new ArgumentNullException(nameof(helpMessageId)); + } + + public BotCommand AddStage(BotCommandStage stage) + { + _stages.Add(stage); + + return this; + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Domain/BotCommandStage.cs b/src/Inc.TeamAssistant.Connector.Domain/BotCommandStage.cs new file mode 100644 index 00000000..e516666d --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Domain/BotCommandStage.cs @@ -0,0 +1,8 @@ +namespace Inc.TeamAssistant.Connector.Domain; + +public enum BotCommandStage +{ + None = 0, + EnterText = 1, + SelectTeam = 2 +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Domain/Inc.TeamAssistant.Connector.Domain.csproj b/src/Inc.TeamAssistant.Connector.Domain/Inc.TeamAssistant.Connector.Domain.csproj new file mode 100644 index 00000000..7e392bd7 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Domain/Inc.TeamAssistant.Connector.Domain.csproj @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Inc.TeamAssistant.Connector.Domain/Person.cs b/src/Inc.TeamAssistant.Connector.Domain/Person.cs new file mode 100644 index 00000000..cdeafb0b --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Domain/Person.cs @@ -0,0 +1,27 @@ +using Inc.TeamAssistant.Primitives; + +namespace Inc.TeamAssistant.Connector.Domain; + +public sealed class Person +{ + public long Id { get; private set; } + public string Name { get; private set; } = default!; + public LanguageId LanguageId { get; private set; } = default!; + public string? Username { get; private set; } + + private Person() + { + } + + public Person(long id, string name, LanguageId languageId, string? username) + : this() + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + + Id = id; + Name = name; + LanguageId = languageId ?? throw new ArgumentNullException(nameof(languageId)); + Username = username; + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Domain/Team.cs b/src/Inc.TeamAssistant.Connector.Domain/Team.cs new file mode 100644 index 00000000..87d0f708 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Domain/Team.cs @@ -0,0 +1,47 @@ +namespace Inc.TeamAssistant.Connector.Domain; + +public sealed class Team +{ + public Guid Id { get; private set; } + public Guid BotId { get; private set; } + public long ChatId { get; private set; } + public string Name { get; private set; } = default!; + + private readonly List _teammates = new(); + public IReadOnlyCollection Teammates => _teammates; + + private Team() + { + } + + public Team(Guid botId, long chatId, string name) + : this() + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); + + Id = Guid.NewGuid(); + BotId = botId; + ChatId = chatId; + Name = name; + } + + public Team AddTeammate(Person person) + { + if (person is null) + throw new ArgumentNullException(nameof(person)); + + _teammates.Add(person); + + return this; + } + + public Team RemoveTeammate(long personId) + { + var person = _teammates.Single(p => p.Id == personId); + + _teammates.Remove(person); + + return this; + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Model/Commands/Begin/BeginCommand.cs b/src/Inc.TeamAssistant.Connector.Model/Commands/Begin/BeginCommand.cs new file mode 100644 index 00000000..776bca1b --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Model/Commands/Begin/BeginCommand.cs @@ -0,0 +1,11 @@ +using Inc.TeamAssistant.Primitives; +using MediatR; + +namespace Inc.TeamAssistant.Connector.Model.Commands.Begin; + +public sealed record BeginCommand( + MessageContext MessageContext, + string NextStage, + string Command, + NotificationMessage Notification) + : IRequest; \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Model/Commands/CreateTeam/CreateTeamCommand.cs b/src/Inc.TeamAssistant.Connector.Model/Commands/CreateTeam/CreateTeamCommand.cs new file mode 100644 index 00000000..6acfabd6 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Model/Commands/CreateTeam/CreateTeamCommand.cs @@ -0,0 +1,10 @@ +using Inc.TeamAssistant.Primitives; +using MediatR; + +namespace Inc.TeamAssistant.Connector.Model.Commands.CreateTeam; + +public sealed record CreateTeamCommand( + MessageContext MessageContext, + string BotName, + string Name) + : IRequest; \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Model/Commands/End/EndCommand.cs b/src/Inc.TeamAssistant.Connector.Model/Commands/End/EndCommand.cs new file mode 100644 index 00000000..7b72a433 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Model/Commands/End/EndCommand.cs @@ -0,0 +1,7 @@ +using Inc.TeamAssistant.Primitives; +using MediatR; + +namespace Inc.TeamAssistant.Connector.Model.Commands.End; + +public sealed record EndCommand(MessageContext MessageContext, string CurrentStage) + : IRequest; \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Model/Commands/JoinToTeam/JoinToTeamCommand.cs b/src/Inc.TeamAssistant.Connector.Model/Commands/JoinToTeam/JoinToTeamCommand.cs new file mode 100644 index 00000000..edc2d7e0 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Model/Commands/JoinToTeam/JoinToTeamCommand.cs @@ -0,0 +1,7 @@ +using Inc.TeamAssistant.Primitives; +using MediatR; + +namespace Inc.TeamAssistant.Connector.Model.Commands.JoinToTeam; + +public sealed record JoinToTeamCommand(MessageContext MessageContext, Guid TeamId) + : IRequest; \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Model/Commands/LeaveFromTeam/LeaveFromTeamCommand.cs b/src/Inc.TeamAssistant.Connector.Model/Commands/LeaveFromTeam/LeaveFromTeamCommand.cs new file mode 100644 index 00000000..7d7ea6a4 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Model/Commands/LeaveFromTeam/LeaveFromTeamCommand.cs @@ -0,0 +1,7 @@ +using Inc.TeamAssistant.Primitives; +using MediatR; + +namespace Inc.TeamAssistant.Connector.Model.Commands.LeaveFromTeam; + +public sealed record LeaveFromTeamCommand(MessageContext MessageContext, Guid TeamId) + : IRequest; \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Model/Inc.TeamAssistant.Connector.Model.csproj b/src/Inc.TeamAssistant.Connector.Model/Inc.TeamAssistant.Connector.Model.csproj new file mode 100644 index 00000000..cabac531 --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Model/Inc.TeamAssistant.Connector.Model.csproj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Connector.Model/MessageContext.cs b/src/Inc.TeamAssistant.Connector.Model/MessageContext.cs new file mode 100644 index 00000000..2c31349a --- /dev/null +++ b/src/Inc.TeamAssistant.Connector.Model/MessageContext.cs @@ -0,0 +1,16 @@ +using Inc.TeamAssistant.Primitives; + +namespace Inc.TeamAssistant.Connector.Model; + +public sealed record MessageContext( + Guid BotId, + string Cmd, + string Text, + long ChatId, + long PersonId, + string FirstName, + int MessageId, + LanguageId LanguageId) +{ + public bool Shared => ChatId != PersonId; +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.DialogContinuations/IDialogContinuation.cs b/src/Inc.TeamAssistant.DialogContinuations/IDialogContinuation.cs index 772630a7..695067aa 100644 --- a/src/Inc.TeamAssistant.DialogContinuations/IDialogContinuation.cs +++ b/src/Inc.TeamAssistant.DialogContinuations/IDialogContinuation.cs @@ -7,5 +7,5 @@ public interface IDialogContinuation { DialogState? Find(long userId); DialogState? TryBegin(long userId, T continuationState, ChatMessage? chatMessage = null); - void End(long userId, T continuationState, ChatMessage? chatMessage = null); + DialogState End(long userId, T continuationState, ChatMessage? chatMessage = null); } \ No newline at end of file diff --git a/src/Inc.TeamAssistant.DialogContinuations/Internal/DialogContinuation.cs b/src/Inc.TeamAssistant.DialogContinuations/Internal/DialogContinuation.cs index 939d082d..fe6b3106 100644 --- a/src/Inc.TeamAssistant.DialogContinuations/Internal/DialogContinuation.cs +++ b/src/Inc.TeamAssistant.DialogContinuations/Internal/DialogContinuation.cs @@ -8,10 +8,7 @@ internal sealed class DialogContinuation : IDialogContinuation { private readonly ConcurrentDictionary> _store = new(); - public DialogState? Find(long userId) - { - return _store.TryGetValue(userId, out var value) ? value : null; - } + public DialogState? Find(long userId) => _store.GetValueOrDefault(userId); public DialogState? TryBegin(long userId, T continuationState, ChatMessage? chatMessage = null) { @@ -26,7 +23,7 @@ internal sealed class DialogContinuation : IDialogContinuation return null; } - public void End(long userId, T continuationState, ChatMessage? chatMessage = null) + public DialogState End(long userId, T continuationState, ChatMessage? chatMessage = null) { if (continuationState is null) throw new ArgumentNullException(nameof(continuationState)); @@ -38,5 +35,7 @@ public void End(long userId, T continuationState, ChatMessage? chatMessage = nul value.TryAttachMessage(chatMessage); _store.Remove(userId, out _); + + return value; } } \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Gateway/Inc.TeamAssistant.Gateway.csproj b/src/Inc.TeamAssistant.Gateway/Inc.TeamAssistant.Gateway.csproj index f33c5bde..e74ff0c2 100644 --- a/src/Inc.TeamAssistant.Gateway/Inc.TeamAssistant.Gateway.csproj +++ b/src/Inc.TeamAssistant.Gateway/Inc.TeamAssistant.Gateway.csproj @@ -7,6 +7,8 @@ + + diff --git a/src/Inc.TeamAssistant.Gateway/Program.cs b/src/Inc.TeamAssistant.Gateway/Program.cs index bf95c4f6..87753047 100644 --- a/src/Inc.TeamAssistant.Gateway/Program.cs +++ b/src/Inc.TeamAssistant.Gateway/Program.cs @@ -6,6 +6,9 @@ using Inc.TeamAssistant.CheckIn.Application.Contracts; using Inc.TeamAssistant.CheckIn.DataAccess; using Inc.TeamAssistant.CheckIn.Model; +using Inc.TeamAssistant.Connector.Application; +using Inc.TeamAssistant.Connector.Application.Contracts; +using Inc.TeamAssistant.Connector.DataAccess; using Inc.TeamAssistant.Holidays; using Inc.TeamAssistant.Languages; using Inc.TeamAssistant.DialogContinuations; @@ -18,6 +21,7 @@ using Inc.TeamAssistant.Reviewer.DataAccess; using Prometheus; using Prometheus.DotNetRuntime; +using ITeamRepository = Inc.TeamAssistant.Connector.Application.Contracts.ITeamRepository; var builder = WebApplication.CreateBuilder(args); @@ -33,25 +37,29 @@ c.Lifetime = ServiceLifetime.Scoped; c.RegisterServicesFromAssemblyContaining(); c.RegisterServicesFromAssemblyContaining(); + //c.RegisterServicesFromAssemblyContaining(); c.RegisterServicesFromAssemblyContaining(); }) .AddScoped() + .AddScoped() + .AddScoped() + .AddHolidays(connectionString, holidayOptions) + .AddDialogContinuations() - .AddApplication(builder.Configuration) + .AddAppraiserApplication(builder.Configuration) .AddAppraiserDataAccess() - .AddServices(telegramBotOptions, builder.Environment.WebRootPath) - - .AddScoped() - .AddScoped() + .AddCheckInApplication(checkInOptions) .AddCheckInDataAccess(connectionString) - .AddReviewer(reviewerOptions) - .AddReviewerDataAccess(connectionString) - .AddMemoryCache() - .AddHolidays(connectionString, holidayOptions) - .AddDialogContinuations() - + //.AddReviewerApplication(reviewerOptions) + //.AddReviewerDataAccess(connectionString) + + .AddConnectorApplication() + .AddConnectorDataAccess(connectionString) + + .AddMemoryCache() + .AddServices(telegramBotOptions, builder.Environment.WebRootPath) .AddIsomorphic() .AddMvc(); diff --git a/src/Inc.TeamAssistant.Gateway/Services/CommandFactories/ComplexCommandFactory.cs b/src/Inc.TeamAssistant.Gateway/Services/CommandFactories/ComplexCommandFactory.cs index 939a6f22..3d17da31 100644 --- a/src/Inc.TeamAssistant.Gateway/Services/CommandFactories/ComplexCommandFactory.cs +++ b/src/Inc.TeamAssistant.Gateway/Services/CommandFactories/ComplexCommandFactory.cs @@ -1,4 +1,4 @@ -using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; using MediatR; namespace Inc.TeamAssistant.Gateway.Services.CommandFactories; diff --git a/src/Inc.TeamAssistant.Gateway/Services/CommandFactories/DynamicCommandFactory.cs b/src/Inc.TeamAssistant.Gateway/Services/CommandFactories/DynamicCommandFactory.cs index 82ad1ba5..50e12944 100644 --- a/src/Inc.TeamAssistant.Gateway/Services/CommandFactories/DynamicCommandFactory.cs +++ b/src/Inc.TeamAssistant.Gateway/Services/CommandFactories/DynamicCommandFactory.cs @@ -3,8 +3,8 @@ using Inc.TeamAssistant.Appraiser.Application.Contracts; using Inc.TeamAssistant.Appraiser.Model.Commands.ActivateAssessment; using Inc.TeamAssistant.Appraiser.Model.Commands.AddStoryToAssessmentSession; -using Inc.TeamAssistant.Appraiser.Model.Common; using Inc.TeamAssistant.DialogContinuations; +using Inc.TeamAssistant.Primitives; using MediatR; namespace Inc.TeamAssistant.Gateway.Services.CommandFactories; diff --git a/src/Inc.TeamAssistant.Gateway/Services/CommandFactories/ICommandFactory.cs b/src/Inc.TeamAssistant.Gateway/Services/CommandFactories/ICommandFactory.cs index 0a03cee5..371d8e8e 100644 --- a/src/Inc.TeamAssistant.Gateway/Services/CommandFactories/ICommandFactory.cs +++ b/src/Inc.TeamAssistant.Gateway/Services/CommandFactories/ICommandFactory.cs @@ -1,4 +1,4 @@ -using Inc.TeamAssistant.Appraiser.Model.Common; +using Inc.TeamAssistant.Primitives; using MediatR; namespace Inc.TeamAssistant.Gateway.Services.CommandFactories; diff --git a/src/Inc.TeamAssistant.Gateway/Services/CommandFactories/StaticCommandFactory.cs b/src/Inc.TeamAssistant.Gateway/Services/CommandFactories/StaticCommandFactory.cs index aed48620..3d8d6f56 100644 --- a/src/Inc.TeamAssistant.Gateway/Services/CommandFactories/StaticCommandFactory.cs +++ b/src/Inc.TeamAssistant.Gateway/Services/CommandFactories/StaticCommandFactory.cs @@ -9,8 +9,8 @@ using Inc.TeamAssistant.Appraiser.Model.Commands.SetEstimateForStory; using Inc.TeamAssistant.Appraiser.Model.Commands.ShowHelp; using Inc.TeamAssistant.Appraiser.Model.Commands.StartStorySelection; -using Inc.TeamAssistant.Appraiser.Model.Common; using Inc.TeamAssistant.Languages; +using Inc.TeamAssistant.Primitives; using MediatR; namespace Inc.TeamAssistant.Gateway.Services.CommandFactories; diff --git a/src/Inc.TeamAssistant.Gateway/Services/MessageBuilder.cs b/src/Inc.TeamAssistant.Gateway/Services/MessageBuilder.cs index 55135b46..c2d757d2 100644 --- a/src/Inc.TeamAssistant.Gateway/Services/MessageBuilder.cs +++ b/src/Inc.TeamAssistant.Gateway/Services/MessageBuilder.cs @@ -1,4 +1,3 @@ -using Inc.TeamAssistant.Appraiser.Application.Contracts; using Inc.TeamAssistant.Appraiser.Model; using Inc.TeamAssistant.Appraiser.Model.Common; using Inc.TeamAssistant.Primitives; diff --git a/src/Inc.TeamAssistant.Gateway/Services/ServiceCollectionExtensions.cs b/src/Inc.TeamAssistant.Gateway/Services/ServiceCollectionExtensions.cs index 8926ac48..fac1068c 100644 --- a/src/Inc.TeamAssistant.Gateway/Services/ServiceCollectionExtensions.cs +++ b/src/Inc.TeamAssistant.Gateway/Services/ServiceCollectionExtensions.cs @@ -3,6 +3,7 @@ using Inc.TeamAssistant.Gateway.Services.CommandFactories; using Inc.TeamAssistant.Gateway.Services.MessageProviders; using Inc.TeamAssistant.Languages; +using Inc.TeamAssistant.Primitives; namespace Inc.TeamAssistant.Gateway.Services; @@ -67,7 +68,7 @@ public static IServiceCollection AddServices( sp.GetRequiredService(), options.CacheAbsoluteExpiration)) - .AddScoped(); + .AddSingleton(); return services; } diff --git a/src/Inc.TeamAssistant.Gateway/Services/TelegramBotMessageHandler.cs b/src/Inc.TeamAssistant.Gateway/Services/TelegramBotMessageHandler.cs index 61dfd5d5..a33debc6 100644 --- a/src/Inc.TeamAssistant.Gateway/Services/TelegramBotMessageHandler.cs +++ b/src/Inc.TeamAssistant.Gateway/Services/TelegramBotMessageHandler.cs @@ -1,10 +1,10 @@ using FluentValidation; using Inc.TeamAssistant.Appraiser.Application.Contracts; using Inc.TeamAssistant.Appraiser.Domain.Exceptions; -using Inc.TeamAssistant.Appraiser.Model.Common; using Inc.TeamAssistant.Gateway.Extensions; using Inc.TeamAssistant.Gateway.Services.CommandFactories; using Inc.TeamAssistant.Languages; +using Inc.TeamAssistant.Primitives; using Inc.TeamAssistant.Users.Extensions; using MediatR; using Telegram.Bot; diff --git a/src/Inc.TeamAssistant.Gateway/wwwroot/langs/en.json b/src/Inc.TeamAssistant.Gateway/wwwroot/langs/en.json index 85400f9d..babac9fa 100644 --- a/src/Inc.TeamAssistant.Gateway/wwwroot/langs/en.json +++ b/src/Inc.TeamAssistant.Gateway/wwwroot/langs/en.json @@ -137,5 +137,8 @@ "Reviewer_MoveToDecline": "Decline", "Reviewer_MoveToNextRound": "Try again", "Reviewer_OperationApplied": "Operation completed", - "Reviewer_HasNotTeamsForPlayer": "No teams found for the user" + "Reviewer_HasNotTeamsForPlayer": "No teams found for the user", + + "Connector_EnterTeamName": "Reply to this message with team name", + "Connector_SelectTeam": "Select the team:" } diff --git a/src/Inc.TeamAssistant.Gateway/wwwroot/langs/ru.json b/src/Inc.TeamAssistant.Gateway/wwwroot/langs/ru.json index 289e8b07..07c885ff 100644 --- a/src/Inc.TeamAssistant.Gateway/wwwroot/langs/ru.json +++ b/src/Inc.TeamAssistant.Gateway/wwwroot/langs/ru.json @@ -137,5 +137,8 @@ "Reviewer_MoveToDecline": "Отклонить", "Reviewer_MoveToNextRound": "Попробовать снова", "Reviewer_OperationApplied": "Операция применена", - "Reviewer_HasNotTeamsForPlayer": "Не найдены команды для пользователя" + "Reviewer_HasNotTeamsForPlayer": "Не найдены команды для пользователя", + + "Connector_EnterTeamName": "В ответ на это сообщение необходимо ввести наименование команды", + "Connector_SelectTeam": "Выберите команду:" } \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Migrations/2023_01_25_0_ChangePersonsStore.cs b/src/Inc.TeamAssistant.Migrations/2023_01_25_0_ChangePersonsStore.cs index f338a496..89e529a0 100644 --- a/src/Inc.TeamAssistant.Migrations/2023_01_25_0_ChangePersonsStore.cs +++ b/src/Inc.TeamAssistant.Migrations/2023_01_25_0_ChangePersonsStore.cs @@ -154,6 +154,5 @@ INSERT INTO review.persons (id, language_id, first_name, last_name, username) public override void Down() { - throw new NotImplementedException(); } } \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Migrations/2023_11_07_0_DeleteUsersScheme.cs b/src/Inc.TeamAssistant.Migrations/2023_11_07_0_DeleteUsersScheme.cs index 17a9a112..bbb26df7 100644 --- a/src/Inc.TeamAssistant.Migrations/2023_11_07_0_DeleteUsersScheme.cs +++ b/src/Inc.TeamAssistant.Migrations/2023_11_07_0_DeleteUsersScheme.cs @@ -17,6 +17,5 @@ public override void Up() public override void Down() { - throw new NotImplementedException(); } } \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Migrations/2024_01_03_0_CreateConnectorScheme.cs b/src/Inc.TeamAssistant.Migrations/2024_01_03_0_CreateConnectorScheme.cs new file mode 100644 index 00000000..0866fdfa --- /dev/null +++ b/src/Inc.TeamAssistant.Migrations/2024_01_03_0_CreateConnectorScheme.cs @@ -0,0 +1,27 @@ +using FluentMigrator; + +namespace Inc.TeamAssistant.Migrations; + +[Migration(2024_01_03_0)] +public class CreateConnectorScheme : Migration +{ + public override void Up() + { + Create + .Schema("connector"); + + Execute.Sql( + "grant usage on schema connector to appraiser__api;", + "add permissions on usage connector schema to appraiser__api user"); + + Execute.Sql( + "alter default privileges in schema connector grant select, update, insert, delete on tables to appraiser__api;", + "add select, update, insert privileges to all tables in connector for appraiser__api user"); + } + + public override void Down() + { + Delete + .Schema("connector"); + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Migrations/2024_01_03_1_CreateConnectorTables.cs b/src/Inc.TeamAssistant.Migrations/2024_01_03_1_CreateConnectorTables.cs new file mode 100644 index 00000000..d02445bc --- /dev/null +++ b/src/Inc.TeamAssistant.Migrations/2024_01_03_1_CreateConnectorTables.cs @@ -0,0 +1,153 @@ +using FluentMigrator; + +namespace Inc.TeamAssistant.Migrations; + +[Migration(2024_01_03_1)] +public class CreateConnectorTables : Migration +{ + public override void Up() + { + Create + .Table("bots") + .InSchema("connector") + + .WithColumn("id") + .AsGuid().NotNullable() + .PrimaryKey("bots__pk__id") + + .WithColumn("name") + .AsString(50).NotNullable() + + .WithColumn("token") + .AsString(255).NotNullable(); + + Create + .Table("persons") + .InSchema("connector") + + .WithColumn("id") + .AsInt64().NotNullable() + .PrimaryKey("persons__pk__id") + + .WithColumn("name") + .AsString().NotNullable() + + .WithColumn("language_id") + .AsString().NotNullable() + + .WithColumn("username") + .AsString().Nullable(); + + Create + .Table("teams") + .InSchema("connector") + + .WithColumn("id") + .AsGuid().NotNullable() + .PrimaryKey("teams__pk__id") + + .WithColumn("bot_id") + .AsGuid().NotNullable() + .ForeignKey( + foreignKeyName: "teams__fk__bot_id__id", + primaryColumnName: "id", + primaryTableName: "bots", + primaryTableSchema: "connector") + + .WithColumn("chat_id") + .AsInt64().NotNullable() + + .WithColumn("name") + .AsString(255).NotNullable(); + + Create + .Table("teammates") + .InSchema("connector") + + .WithColumn("team_id") + .AsGuid().NotNullable() + .PrimaryKey("teammates__pk__team_id__person_id") + .ForeignKey( + foreignKeyName: "teammates__fk__team_id__id", + primaryColumnName: "id", + primaryTableName: "teams", + primaryTableSchema: "connector") + + .WithColumn("person_id") + .AsInt64().NotNullable() + .PrimaryKey("teammates__pk__team_id__person_id") + .ForeignKey( + foreignKeyName: "teammates__fk__person_id__id", + primaryColumnName: "id", + primaryTableName: "persons", + primaryTableSchema: "connector"); + + Create + .Table("bot_commands") + .InSchema("connector") + + .WithColumn("id") + .AsGuid().NotNullable() + .PrimaryKey("bot_commands__pk__id") + + .WithColumn("bot_id") + .AsGuid().NotNullable() + .ForeignKey( + foreignKeyName: "bot_commands__fk__bot_id__id", + primaryColumnName: "id", + primaryTableName: "bots", + primaryTableSchema: "connector") + + .WithColumn("value") + .AsString(50).NotNullable() + + .WithColumn("help_message_id") + .AsString(50).NotNullable(); + + Create + .Table("bot_command_stages") + .InSchema("connector") + + .WithColumn("id") + .AsGuid().NotNullable() + .PrimaryKey("bot_command_stages__pk__id") + + .WithColumn("bot_command_id") + .AsGuid().NotNullable() + .ForeignKey( + foreignKeyName: "bot_command_stages__fk__bot_command_id__id", + primaryColumnName: "id", + primaryTableName: "bot_commands", + primaryTableSchema: "connector") + + .WithColumn("value") + .AsInt32().NotNullable(); + } + + public override void Down() + { + Delete + .Table("bot_command_stages") + .InSchema("connector"); + + Delete + .Table("bot_commands") + .InSchema("connector"); + + Delete + .Table("teammates") + .InSchema("connector"); + + Delete + .Table("teams") + .InSchema("connector"); + + Delete + .Table("persons") + .InSchema("connector"); + + Delete + .Table("bots") + .InSchema("connector"); + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Appraiser.Model/Common/Button.cs b/src/Inc.TeamAssistant.Primitives/Button.cs similarity index 51% rename from src/Inc.TeamAssistant.Appraiser.Model/Common/Button.cs rename to src/Inc.TeamAssistant.Primitives/Button.cs index 6f14b9e5..468a4750 100644 --- a/src/Inc.TeamAssistant.Appraiser.Model/Common/Button.cs +++ b/src/Inc.TeamAssistant.Primitives/Button.cs @@ -1,3 +1,3 @@ -namespace Inc.TeamAssistant.Appraiser.Model.Common; +namespace Inc.TeamAssistant.Primitives; public sealed record Button(string Text, string Data); \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Appraiser.Model/Common/CommandResult.cs b/src/Inc.TeamAssistant.Primitives/CommandResult.cs similarity index 89% rename from src/Inc.TeamAssistant.Appraiser.Model/Common/CommandResult.cs rename to src/Inc.TeamAssistant.Primitives/CommandResult.cs index a2406312..c78c8653 100644 --- a/src/Inc.TeamAssistant.Appraiser.Model/Common/CommandResult.cs +++ b/src/Inc.TeamAssistant.Primitives/CommandResult.cs @@ -1,4 +1,4 @@ -namespace Inc.TeamAssistant.Appraiser.Model.Common; +namespace Inc.TeamAssistant.Primitives; public sealed record CommandResult(IReadOnlyCollection Notifications) { diff --git a/src/Inc.TeamAssistant.Appraiser.Application/Contracts/IMessageBuilder.cs b/src/Inc.TeamAssistant.Primitives/IMessageBuilder.cs similarity index 56% rename from src/Inc.TeamAssistant.Appraiser.Application/Contracts/IMessageBuilder.cs rename to src/Inc.TeamAssistant.Primitives/IMessageBuilder.cs index 26a10407..59dfa402 100644 --- a/src/Inc.TeamAssistant.Appraiser.Application/Contracts/IMessageBuilder.cs +++ b/src/Inc.TeamAssistant.Primitives/IMessageBuilder.cs @@ -1,6 +1,4 @@ -using Inc.TeamAssistant.Primitives; - -namespace Inc.TeamAssistant.Appraiser.Application.Contracts; +namespace Inc.TeamAssistant.Primitives; public interface IMessageBuilder { diff --git a/src/Inc.TeamAssistant.Primitives/Inc.TeamAssistant.Primitives.csproj b/src/Inc.TeamAssistant.Primitives/Inc.TeamAssistant.Primitives.csproj index 4450d6f4..c8f5b00c 100644 --- a/src/Inc.TeamAssistant.Primitives/Inc.TeamAssistant.Primitives.csproj +++ b/src/Inc.TeamAssistant.Primitives/Inc.TeamAssistant.Primitives.csproj @@ -1,2 +1,5 @@ + + + \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Appraiser.Model/Common/NotificationMessage.cs b/src/Inc.TeamAssistant.Primitives/NotificationMessage.cs similarity index 97% rename from src/Inc.TeamAssistant.Appraiser.Model/Common/NotificationMessage.cs rename to src/Inc.TeamAssistant.Primitives/NotificationMessage.cs index be940ef9..24364a1d 100644 --- a/src/Inc.TeamAssistant.Appraiser.Model/Common/NotificationMessage.cs +++ b/src/Inc.TeamAssistant.Primitives/NotificationMessage.cs @@ -1,6 +1,6 @@ using MediatR; -namespace Inc.TeamAssistant.Appraiser.Model.Common; +namespace Inc.TeamAssistant.Primitives; public sealed class NotificationMessage { diff --git a/src/Inc.TeamAssistant.Reviewer.Application/ServiceCollectionExtensions.cs b/src/Inc.TeamAssistant.Reviewer.Application/ServiceCollectionExtensions.cs index 79c71dab..8cc55af6 100644 --- a/src/Inc.TeamAssistant.Reviewer.Application/ServiceCollectionExtensions.cs +++ b/src/Inc.TeamAssistant.Reviewer.Application/ServiceCollectionExtensions.cs @@ -6,7 +6,7 @@ namespace Inc.TeamAssistant.Reviewer.Application; public static class ServiceCollectionExtensions { - public static IServiceCollection AddReviewer(this IServiceCollection services, ReviewerOptions options) + public static IServiceCollection AddReviewerApplication(this IServiceCollection services, ReviewerOptions options) { if (services is null) throw new ArgumentNullException(nameof(services)); diff --git a/src/Inc.TeamAssistant.Reviewer.Model/Inc.TeamAssistant.Reviewer.Model.csproj b/src/Inc.TeamAssistant.Reviewer.Model/Inc.TeamAssistant.Reviewer.Model.csproj index d76b2435..579b5163 100644 --- a/src/Inc.TeamAssistant.Reviewer.Model/Inc.TeamAssistant.Reviewer.Model.csproj +++ b/src/Inc.TeamAssistant.Reviewer.Model/Inc.TeamAssistant.Reviewer.Model.csproj @@ -1,7 +1,4 @@ - - -