Add notification for ticket reopening to Telegram admins
- Implemented NotifyAdminsTicketReopenedAsync method in TelegramNotifier to notify admins when a ticket is reopened, including a link to the ticket on the public site. - Updated ITelegramNotifier interface to include the new notification method. - Modified ReopenTicketCommandHandler to invoke the new notification method after a ticket is reopened. - Enhanced unit tests for ReopenTicketCommandHandler to verify the notification functionality.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using PnvPanel.Application.Common.Interfaces;
|
using PnvPanel.Application.Common.Interfaces;
|
||||||
|
using PnvPanel.Domain.Support;
|
||||||
using PnvPanel.Infrastructure.Telegram;
|
using PnvPanel.Infrastructure.Telegram;
|
||||||
using Telegram.Bot;
|
using Telegram.Bot;
|
||||||
using Telegram.Bot.Types.Enums;
|
using Telegram.Bot.Types.Enums;
|
||||||
@@ -97,6 +98,35 @@ internal sealed class TelegramNotifier(ITelegramBotClient botClient, IIdentitySe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task NotifyAdminsTicketReopenedAsync(Guid ticketId, string userName, TicketType type, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(options.Value.BotToken))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var typeLabel = type == TicketType.RoleRequest ? "заявка на роль" : "баг/предложение";
|
||||||
|
var text = $"🔓 Тикет от <b>{Escape(userName)}</b> переоткрыт ({typeLabel})";
|
||||||
|
|
||||||
|
InlineKeyboardMarkup? keyboard = null;
|
||||||
|
if (!string.IsNullOrWhiteSpace(options.Value.PublicSiteUrl))
|
||||||
|
{
|
||||||
|
var url = $"{options.Value.PublicSiteUrl.TrimEnd('/')}/admin/support?ticket={ticketId}";
|
||||||
|
keyboard = new InlineKeyboardMarkup(new[] { InlineKeyboardButton.WithUrl("🌐 Открыть на сайте", url) });
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var adminId in options.Value.ParseAdminTelegramUserIds())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await botClient.SendMessage(
|
||||||
|
adminId, text, parseMode: ParseMode.Html, replyMarkup: keyboard, cancellationToken: cancellationToken);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Админ мог не запускать бота (нет чата с ботом) — пропускаем, не валим команду.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task NotifyUsersNewsPublishedAsync(string title, CancellationToken cancellationToken)
|
public async Task NotifyUsersNewsPublishedAsync(string title, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(options.Value.BotToken))
|
if (string.IsNullOrWhiteSpace(options.Value.BotToken))
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using PnvPanel.Domain.Support;
|
||||||
|
|
||||||
namespace PnvPanel.Application.Common.Interfaces;
|
namespace PnvPanel.Application.Common.Interfaces;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -25,4 +27,8 @@ public interface ITelegramNotifier
|
|||||||
/// <summary>Рассылка о публикации новости всем активированным пользователям с привязанным Telegram
|
/// <summary>Рассылка о публикации новости всем активированным пользователям с привязанным Telegram
|
||||||
/// (кнопка-ссылка на сайт, где новость показана целиком).</summary>
|
/// (кнопка-ссылка на сайт, где новость показана целиком).</summary>
|
||||||
Task NotifyUsersNewsPublishedAsync(string title, CancellationToken cancellationToken);
|
Task NotifyUsersNewsPublishedAsync(string title, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>Пользователь переоткрыл решённый тикет — только кнопка-ссылка на сайт, без
|
||||||
|
/// инлайн-действий (аналогично баг-репортам).</summary>
|
||||||
|
Task NotifyAdminsTicketReopenedAsync(Guid ticketId, string userName, TicketType type, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using PnvPanel.Domain.Support;
|
|||||||
|
|
||||||
namespace PnvPanel.Application.Support.Reopen;
|
namespace PnvPanel.Application.Support.Reopen;
|
||||||
|
|
||||||
public sealed class ReopenTicketCommandHandler(IAppDbContext dbContext, ICurrentUser currentUser)
|
public sealed class ReopenTicketCommandHandler(IAppDbContext dbContext, ITelegramNotifier telegramNotifier, ICurrentUser currentUser)
|
||||||
: ICommandHandler<ReopenTicketCommand, Result>
|
: ICommandHandler<ReopenTicketCommand, Result>
|
||||||
{
|
{
|
||||||
public async Task<Result> Handle(ReopenTicketCommand command, CancellationToken cancellationToken)
|
public async Task<Result> Handle(ReopenTicketCommand command, CancellationToken cancellationToken)
|
||||||
@@ -24,6 +24,10 @@ public sealed class ReopenTicketCommandHandler(IAppDbContext dbContext, ICurrent
|
|||||||
return Result.Failure(SupportErrors.NotResolved);
|
return Result.Failure(SupportErrors.NotResolved);
|
||||||
|
|
||||||
ticket.Reopen();
|
ticket.Reopen();
|
||||||
|
|
||||||
|
var userName = currentUser.UserName ?? userId.ToString();
|
||||||
|
await telegramNotifier.NotifyAdminsTicketReopenedAsync(ticket.Id, userName, ticket.Type, cancellationToken);
|
||||||
|
|
||||||
return Result.Success();
|
return Result.Success();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using NSubstitute;
|
||||||
|
using PnvPanel.Application.Common.Interfaces;
|
||||||
using PnvPanel.Application.Support;
|
using PnvPanel.Application.Support;
|
||||||
using PnvPanel.Application.Support.Reopen;
|
using PnvPanel.Application.Support.Reopen;
|
||||||
using PnvPanel.Application.Tests.TestSupport;
|
using PnvPanel.Application.Tests.TestSupport;
|
||||||
@@ -8,6 +10,8 @@ namespace PnvPanel.Application.Tests.Support;
|
|||||||
|
|
||||||
public class ReopenTicketCommandHandlerTests
|
public class ReopenTicketCommandHandlerTests
|
||||||
{
|
{
|
||||||
|
private readonly ITelegramNotifier _telegramNotifier = Substitute.For<ITelegramNotifier>();
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Handle_WhenResolved_SetsOpen()
|
public async Task Handle_WhenResolved_SetsOpen()
|
||||||
{
|
{
|
||||||
@@ -19,12 +23,14 @@ public class ReopenTicketCommandHandlerTests
|
|||||||
await dbContext.SaveChangesAsync(CancellationToken.None);
|
await dbContext.SaveChangesAsync(CancellationToken.None);
|
||||||
|
|
||||||
var currentUser = FakeCurrentUser.Authenticated(userId);
|
var currentUser = FakeCurrentUser.Authenticated(userId);
|
||||||
var handler = new ReopenTicketCommandHandler(dbContext, currentUser);
|
var handler = new ReopenTicketCommandHandler(dbContext, _telegramNotifier, currentUser);
|
||||||
|
|
||||||
var result = await handler.Handle(new ReopenTicketCommand(ticket.Id), CancellationToken.None);
|
var result = await handler.Handle(new ReopenTicketCommand(ticket.Id), CancellationToken.None);
|
||||||
|
|
||||||
Assert.True(result.IsSuccess);
|
Assert.True(result.IsSuccess);
|
||||||
Assert.Equal(TicketStatus.Open, ticket.Status);
|
Assert.Equal(TicketStatus.Open, ticket.Status);
|
||||||
|
await _telegramNotifier.Received(1).NotifyAdminsTicketReopenedAsync(
|
||||||
|
ticket.Id, Arg.Any<string>(), TicketType.BugReport, Arg.Any<CancellationToken>());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -37,7 +43,7 @@ public class ReopenTicketCommandHandlerTests
|
|||||||
await dbContext.SaveChangesAsync(CancellationToken.None);
|
await dbContext.SaveChangesAsync(CancellationToken.None);
|
||||||
|
|
||||||
var currentUser = FakeCurrentUser.Authenticated(userId);
|
var currentUser = FakeCurrentUser.Authenticated(userId);
|
||||||
var handler = new ReopenTicketCommandHandler(dbContext, currentUser);
|
var handler = new ReopenTicketCommandHandler(dbContext, _telegramNotifier, currentUser);
|
||||||
|
|
||||||
var result = await handler.Handle(new ReopenTicketCommand(ticket.Id), CancellationToken.None);
|
var result = await handler.Handle(new ReopenTicketCommand(ticket.Id), CancellationToken.None);
|
||||||
|
|
||||||
@@ -55,7 +61,7 @@ public class ReopenTicketCommandHandlerTests
|
|||||||
await dbContext.SaveChangesAsync(CancellationToken.None);
|
await dbContext.SaveChangesAsync(CancellationToken.None);
|
||||||
|
|
||||||
var currentUser = FakeCurrentUser.Authenticated(Guid.NewGuid());
|
var currentUser = FakeCurrentUser.Authenticated(Guid.NewGuid());
|
||||||
var handler = new ReopenTicketCommandHandler(dbContext, currentUser);
|
var handler = new ReopenTicketCommandHandler(dbContext, _telegramNotifier, currentUser);
|
||||||
|
|
||||||
var result = await handler.Handle(new ReopenTicketCommand(ticket.Id), CancellationToken.None);
|
var result = await handler.Handle(new ReopenTicketCommand(ticket.Id), CancellationToken.None);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user