diff --git a/src/Inc.TeamAssistant.Gateway/wwwroot/langs/en.json b/src/Inc.TeamAssistant.Gateway/wwwroot/langs/en.json index 091ac7fa..85c4d95c 100644 --- a/src/Inc.TeamAssistant.Gateway/wwwroot/langs/en.json +++ b/src/Inc.TeamAssistant.Gateway/wwwroot/langs/en.json @@ -260,6 +260,8 @@ "RandomCoffee_MeetingDescription": "Please arrange a meeting via private messaging", "RandomCoffee_NotEnoughParticipants": "There are not enough participants to form pairs", "RandomCoffee_InviteHelp": "Start random coffee meetings", + "RandomCoffee_RefuseHelp": "Refuse random coffee meetings", + "RandomCoffee_RefusedForCoffee": "Random coffee meetings are refused", "Constructor_NewBot": "New bot", "Constructor_AddBot": "Add", diff --git a/src/Inc.TeamAssistant.Gateway/wwwroot/langs/ru.json b/src/Inc.TeamAssistant.Gateway/wwwroot/langs/ru.json index d8e5b884..7d7ad831 100644 --- a/src/Inc.TeamAssistant.Gateway/wwwroot/langs/ru.json +++ b/src/Inc.TeamAssistant.Gateway/wwwroot/langs/ru.json @@ -260,6 +260,8 @@ "RandomCoffee_MeetingDescription": "Договоритесь в ЛС о проведении встречи", "RandomCoffee_NotEnoughParticipants": "Недостаточно участников для составления пар", "RandomCoffee_InviteHelp": "Начать проведение встреч", + "RandomCoffee_RefuseHelp": "Приостановить проведение встреч", + "RandomCoffee_RefusedForCoffee": "Проведение встреч приостановлено", "Constructor_NewBot": "Создать", "Constructor_AddBot": "Добавить", diff --git a/src/Inc.TeamAssistant.Migrations/2024_10_25_0_AddRandomCoffeeRefusedFlag.cs b/src/Inc.TeamAssistant.Migrations/2024_10_25_0_AddRandomCoffeeRefusedFlag.cs new file mode 100644 index 00000000..17148c20 --- /dev/null +++ b/src/Inc.TeamAssistant.Migrations/2024_10_25_0_AddRandomCoffeeRefusedFlag.cs @@ -0,0 +1,25 @@ +using FluentMigrator; + +namespace Inc.TeamAssistant.Migrations; + +[Migration(2024_10_25_0)] + +public sealed class AddRandomCoffeeRefusedFlag : Migration +{ + public override void Up() + { + Create + .Column("refused") + .OnTable("entries") + .InSchema("random_coffee") + .AsBoolean().Nullable(); + } + + public override void Down() + { + Delete + .Column("refused") + .FromTable("entries") + .InSchema("random_coffee"); + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.Migrations/2024_10_25_1_AddRandomCoffeeRefuseHelpToCommand.cs b/src/Inc.TeamAssistant.Migrations/2024_10_25_1_AddRandomCoffeeRefuseHelpToCommand.cs new file mode 100644 index 00000000..95cb37d0 --- /dev/null +++ b/src/Inc.TeamAssistant.Migrations/2024_10_25_1_AddRandomCoffeeRefuseHelpToCommand.cs @@ -0,0 +1,43 @@ +using FluentMigrator; + +namespace Inc.TeamAssistant.Migrations; + +[Migration(2024_10_25_1)] + +public class AddRandomCoffeeRefuseHelpToCommand : Migration +{ + public override void Up() + { + Execute.Sql( + """ + INSERT INTO connector.bot_commands(id, value, help_message_id, scopes) + VALUES + ('d6097f8b-de33-412c-a064-68069d458776', '/refuse', 'RandomCoffee_RefuseHelp', '[2]'::jsonb) + """, + "Insert new random coffee command"); + Execute.Sql( + """ + INSERT INTO connector.command_packs(feature_id, bot_command_id) + VALUES + ('39195e70-b83a-42b3-88e5-dbbf6789a3c8', 'd6097f8b-de33-412c-a064-68069d458776') + """, + "Insert new random coffee command"); + } + + public override void Down() + { + Execute.Sql( + """ + DELETE FROM connector.bot_commands + WHERE id = 'd6097f8b-de33-412c-a064-68069d458776'; + """, + "Clear new random coffee command"); + Execute.Sql( + """ + DELETE FROM connector.command_packs + WHERE feature_id = '39195e70-b83a-42b3-88e5-dbbf6789a3c8' + AND bot_command_id = 'd6097f8b-de33-412c-a064-68069d458776'; + """, + "Clear new random coffee command"); + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/AddPollAnswer/AddPollAnswerCommandHandler.cs b/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/AddPollAnswer/AddPollAnswerCommandHandler.cs index 20f92d6e..880c78e9 100644 --- a/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/AddPollAnswer/AddPollAnswerCommandHandler.cs +++ b/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/AddPollAnswer/AddPollAnswerCommandHandler.cs @@ -19,7 +19,7 @@ public async Task Handle(AddPollAnswerCommand command, Cancellati ArgumentNullException.ThrowIfNull(command); var randomCoffeeEntry = await _repository.Find(command.PollId, token); - if (randomCoffeeEntry is not null) + if (randomCoffeeEntry?.Refused is false) { const string optionYes = "0"; diff --git a/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/AttachPoll/AttachPollCommandHandler.cs b/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/AttachPoll/AttachPollCommandHandler.cs index 632ed831..69c715dc 100644 --- a/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/AttachPoll/AttachPollCommandHandler.cs +++ b/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/AttachPoll/AttachPollCommandHandler.cs @@ -20,7 +20,7 @@ public async Task Handle(AttachPollCommand command, CancellationT ArgumentNullException.ThrowIfNull(command); var randomCoffeeEntry = await _repository.Find(command.RandomCoffeeEntryId, token); - if (randomCoffeeEntry is null) + if (randomCoffeeEntry is null || randomCoffeeEntry.Refused is true) throw new TeamAssistantException($"RandomCoffeeEntry {command.RandomCoffeeEntryId} was not found."); randomCoffeeEntry.AttachPoll(command.PollId); diff --git a/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/InviteForCoffee/InviteForCoffeeCommandHandler.cs b/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/InviteForCoffee/InviteForCoffeeCommandHandler.cs index b40b2e9d..9f83026d 100644 --- a/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/InviteForCoffee/InviteForCoffeeCommandHandler.cs +++ b/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/InviteForCoffee/InviteForCoffeeCommandHandler.cs @@ -36,7 +36,10 @@ public async Task Handle(InviteForCoffeeCommand command, Cancella ArgumentNullException.ThrowIfNull(command); var existsRandomCoffeeEntry = await _repository.Find(command.MessageContext.ChatMessage.ChatId, token); - if (existsRandomCoffeeEntry is not null && command.OnDemand) + if (existsRandomCoffeeEntry?.Refused is false && command.OnDemand) + return CommandResult.Empty; + + if (existsRandomCoffeeEntry?.Refused is true && !command.OnDemand) return CommandResult.Empty; var randomCoffeeEntry = existsRandomCoffeeEntry ?? new RandomCoffeeEntry( diff --git a/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/RefuseForCoffee/RefuseForCoffeeCommandHandler.cs b/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/RefuseForCoffee/RefuseForCoffeeCommandHandler.cs new file mode 100644 index 00000000..e509f210 --- /dev/null +++ b/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/RefuseForCoffee/RefuseForCoffeeCommandHandler.cs @@ -0,0 +1,59 @@ +using Inc.TeamAssistant.Primitives; +using Inc.TeamAssistant.Primitives.Commands; +using Inc.TeamAssistant.Primitives.Exceptions; +using Inc.TeamAssistant.Primitives.Languages; +using Inc.TeamAssistant.Primitives.Notifications; +using Inc.TeamAssistant.RandomCoffee.Application.Contracts; +using Inc.TeamAssistant.RandomCoffee.Model.Commands.RefuseForCoffee; +using MediatR; + +namespace Inc.TeamAssistant.RandomCoffee.Application.CommandHandlers.RefuseForCoffee; + +public sealed class RefuseForCoffeeCommandHandler : IRequestHandler +{ + private readonly IRandomCoffeeRepository _repository; + private readonly ITeamAccessor _teamAccessor; + private readonly IMessageBuilder _messageBuilder; + + public RefuseForCoffeeCommandHandler( + IRandomCoffeeRepository repository, + ITeamAccessor teamAccessor, + IMessageBuilder messageBuilder) + { + _repository = repository ?? throw new ArgumentNullException(nameof(repository)); + _teamAccessor = teamAccessor ?? throw new ArgumentNullException(nameof(teamAccessor)); + _messageBuilder = messageBuilder ?? throw new ArgumentNullException(nameof(messageBuilder)); + } + + public async Task Handle(RefuseForCoffeeCommand command, CancellationToken token) + { + ArgumentNullException.ThrowIfNull(command); + + var existsRandomCoffeeEntry = await _repository.Find(command.MessageContext.ChatMessage.ChatId, token); + if (existsRandomCoffeeEntry is null || existsRandomCoffeeEntry.Refused is true) + return CommandResult.Empty; + + var owner = await _teamAccessor.FindPerson(existsRandomCoffeeEntry.OwnerId, token); + if (owner is null) + throw new TeamAssistantException($"Owner {existsRandomCoffeeEntry.OwnerId} was not found."); + + existsRandomCoffeeEntry.MoveToRefused(); + + await _repository.Upsert(existsRandomCoffeeEntry, token); + + var languageId = await _teamAccessor.GetClientLanguage(command.MessageContext.Bot.Id, owner.Id, token); + var notification = NotificationMessage + .Create( + existsRandomCoffeeEntry.ChatId, + await _messageBuilder.Build(Messages.RandomCoffee_RefusedForCoffee, languageId)); + var notifications = command.MessageContext.ChatMessage.OnlyChat + ? [notification] + : new[] + { + notification, + NotificationMessage.Delete(command.MessageContext.ChatMessage) + }; + + return CommandResult.Build(notifications); + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/RefuseForCoffee/Services/RefuseForCoffeeCommandCreator.cs b/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/RefuseForCoffee/Services/RefuseForCoffeeCommandCreator.cs new file mode 100644 index 00000000..8c12376c --- /dev/null +++ b/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/RefuseForCoffee/Services/RefuseForCoffeeCommandCreator.cs @@ -0,0 +1,20 @@ +using Inc.TeamAssistant.Primitives.Commands; +using Inc.TeamAssistant.RandomCoffee.Model.Commands.RefuseForCoffee; + +namespace Inc.TeamAssistant.RandomCoffee.Application.CommandHandlers.RefuseForCoffee.Services; + +internal sealed class RefuseForCoffeeCommandCreator : ICommandCreator +{ + public string Command => CommandList.RefuseForCoffee; + + public Task Create( + MessageContext messageContext, + CurrentTeamContext teamContext, + CancellationToken token) + { + ArgumentNullException.ThrowIfNull(messageContext); + ArgumentNullException.ThrowIfNull(teamContext); + + return Task.FromResult(new RefuseForCoffeeCommand(messageContext)); + } +} \ No newline at end of file diff --git a/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/SelectPairs/SelectPairsCommandHandler.cs b/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/SelectPairs/SelectPairsCommandHandler.cs index 6d57d2c1..64c2b98e 100644 --- a/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/SelectPairs/SelectPairsCommandHandler.cs +++ b/src/Inc.TeamAssistant.RandomCoffee.Application/CommandHandlers/SelectPairs/SelectPairsCommandHandler.cs @@ -42,6 +42,9 @@ public async Task Handle(SelectPairsCommand command, Cancellation if (randomCoffeeEntry is null) throw new TeamAssistantException($"RandomCoffeeEntry {command.RandomCoffeeEntryId} was not found."); + if (randomCoffeeEntry.Refused is true) + return CommandResult.Empty; + var botContext = await _botAccessor.GetBotContext(randomCoffeeEntry.BotId, token); var owner = await _teamAccessor.FindPerson(randomCoffeeEntry.OwnerId, token); if (owner is null) diff --git a/src/Inc.TeamAssistant.RandomCoffee.Application/CommandList.cs b/src/Inc.TeamAssistant.RandomCoffee.Application/CommandList.cs index 001d34e5..f807dbd3 100644 --- a/src/Inc.TeamAssistant.RandomCoffee.Application/CommandList.cs +++ b/src/Inc.TeamAssistant.RandomCoffee.Application/CommandList.cs @@ -5,4 +5,6 @@ internal static class CommandList public const string InviteForCoffee = "/invite"; public const string AddPollAnswer = "/poll_answer?pollId="; + + public const string RefuseForCoffee = "/refuse"; } \ No newline at end of file diff --git a/src/Inc.TeamAssistant.RandomCoffee.Application/Messages.cs b/src/Inc.TeamAssistant.RandomCoffee.Application/Messages.cs index ddea8226..b56030a5 100644 --- a/src/Inc.TeamAssistant.RandomCoffee.Application/Messages.cs +++ b/src/Inc.TeamAssistant.RandomCoffee.Application/Messages.cs @@ -11,4 +11,5 @@ internal static class Messages public static readonly MessageId RandomCoffee_NotSelectedPair = new(nameof(RandomCoffee_NotSelectedPair)); public static readonly MessageId RandomCoffee_MeetingDescription = new(nameof(RandomCoffee_MeetingDescription)); public static readonly MessageId RandomCoffee_NotEnoughParticipants = new(nameof(RandomCoffee_NotEnoughParticipants)); + public static readonly MessageId RandomCoffee_RefusedForCoffee = new(nameof(RandomCoffee_RefusedForCoffee)); } \ No newline at end of file diff --git a/src/Inc.TeamAssistant.RandomCoffee.Application/ServiceCollectionExtensions.cs b/src/Inc.TeamAssistant.RandomCoffee.Application/ServiceCollectionExtensions.cs index e13118b0..060e02b2 100644 --- a/src/Inc.TeamAssistant.RandomCoffee.Application/ServiceCollectionExtensions.cs +++ b/src/Inc.TeamAssistant.RandomCoffee.Application/ServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ using Inc.TeamAssistant.Primitives.FeatureProperties; using Inc.TeamAssistant.RandomCoffee.Application.CommandHandlers.AddPollAnswer.Services; using Inc.TeamAssistant.RandomCoffee.Application.CommandHandlers.InviteForCoffee.Services; +using Inc.TeamAssistant.RandomCoffee.Application.CommandHandlers.RefuseForCoffee.Services; using Inc.TeamAssistant.RandomCoffee.Application.CommandHandlers.SelectPairs.Services; using Inc.TeamAssistant.RandomCoffee.Application.Services; using Microsoft.Extensions.DependencyInjection; @@ -21,6 +22,7 @@ public static IServiceCollection AddRandomCoffeeApplication(this IServiceCollect .AddSingleton() .AddSingleton() + .AddSingleton() .AddHostedService(); diff --git a/src/Inc.TeamAssistant.RandomCoffee.DataAccess/RandomCoffeeRepository.cs b/src/Inc.TeamAssistant.RandomCoffee.DataAccess/RandomCoffeeRepository.cs index 764e6909..bce71ebd 100644 --- a/src/Inc.TeamAssistant.RandomCoffee.DataAccess/RandomCoffeeRepository.cs +++ b/src/Inc.TeamAssistant.RandomCoffee.DataAccess/RandomCoffeeRepository.cs @@ -91,7 +91,8 @@ INSERT INTO random_coffee.entries ( state, poll_id, participant_ids, - name) + name, + refused) VALUES ( @id, @created, @@ -102,7 +103,8 @@ INSERT INTO random_coffee.entries ( @state, @poll_id, @participant_ids::jsonb, - @name) + @name, + @refused) ON CONFLICT (id) DO UPDATE SET created = excluded.created, bot_id = excluded.bot_id, @@ -112,7 +114,8 @@ ON CONFLICT (id) DO UPDATE SET state = excluded.state, poll_id = excluded.poll_id, participant_ids = excluded.participant_ids, - name = excluded.name;", + name = excluded.name, + refused = excluded.refused;", new { id = randomCoffeeEntry.Id, @@ -124,7 +127,8 @@ ON CONFLICT (id) DO UPDATE SET state = randomCoffeeEntry.State, poll_id = randomCoffeeEntry.PollId, participant_ids = JsonSerializer.Serialize(randomCoffeeEntry.ParticipantIds), - name = randomCoffeeEntry.Name + name = randomCoffeeEntry.Name, + refused = randomCoffeeEntry.Refused }, flags: CommandFlags.None, cancellationToken: token); @@ -185,7 +189,8 @@ ON CONFLICT (id) DO UPDATE SET e.state AS state, e.poll_id AS pollid, e.participant_ids AS participantids, - e.name AS name + e.name AS name, + e.refused AS refused FROM random_coffee.entries AS e WHERE e.id = @id; diff --git a/src/Inc.TeamAssistant.RandomCoffee.Domain/RandomCoffeeEntry.cs b/src/Inc.TeamAssistant.RandomCoffee.Domain/RandomCoffeeEntry.cs index 86b4d98e..f83b2531 100644 --- a/src/Inc.TeamAssistant.RandomCoffee.Domain/RandomCoffeeEntry.cs +++ b/src/Inc.TeamAssistant.RandomCoffee.Domain/RandomCoffeeEntry.cs @@ -15,6 +15,8 @@ public sealed class RandomCoffeeEntry private readonly List _history = new(); public IReadOnlyCollection History => _history; + + public bool? Refused { get; private set; } private RandomCoffeeEntry() { @@ -51,6 +53,8 @@ public RandomCoffeeEntry MoveToWaiting(DateTimeOffset now, TimeSpan waitingInter ParticipantIds.Clear(); PollId = null; + Refused = false; + return this; } @@ -104,4 +108,11 @@ public RandomCoffeeHistory SelectPairs() return randomCoffeeHistory; } + + public RandomCoffeeEntry MoveToRefused() + { + Refused = true; + + return this; + } } \ No newline at end of file diff --git a/src/Inc.TeamAssistant.RandomCoffee.Model/Commands/RefuseForCoffee/RefuseForCoffeeCommand.cs b/src/Inc.TeamAssistant.RandomCoffee.Model/Commands/RefuseForCoffee/RefuseForCoffeeCommand.cs new file mode 100644 index 00000000..c24e70d5 --- /dev/null +++ b/src/Inc.TeamAssistant.RandomCoffee.Model/Commands/RefuseForCoffee/RefuseForCoffeeCommand.cs @@ -0,0 +1,6 @@ +using Inc.TeamAssistant.Primitives.Commands; + +namespace Inc.TeamAssistant.RandomCoffee.Model.Commands.RefuseForCoffee; + +public sealed record RefuseForCoffeeCommand(MessageContext MessageContext) + : IDialogCommand; \ No newline at end of file