diff --git a/backend/src/PnvPanel.Api/Telegram/TelegramNotifier.cs b/backend/src/PnvPanel.Api/Telegram/TelegramNotifier.cs
index db159f2..aa5b3f4 100644
--- a/backend/src/PnvPanel.Api/Telegram/TelegramNotifier.cs
+++ b/backend/src/PnvPanel.Api/Telegram/TelegramNotifier.cs
@@ -97,6 +97,35 @@ internal sealed class TelegramNotifier(ITelegramBotClient botClient, IIdentitySe
}
}
+ public async Task NotifyUsersNewsPublishedAsync(string title, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrWhiteSpace(options.Value.BotToken))
+ return;
+
+ var text = $"π° ΠΠΎΠ²Π°Ρ Π½ΠΎΠ²ΠΎΡΡΡ: {Escape(title)}";
+
+ InlineKeyboardMarkup? keyboard = null;
+ if (!string.IsNullOrWhiteSpace(options.Value.PublicSiteUrl))
+ {
+ var url = $"{options.Value.PublicSiteUrl.TrimEnd('/')}/news";
+ keyboard = new InlineKeyboardMarkup(new[] { InlineKeyboardButton.WithUrl("π ΠΡΠΊΡΡΡΡ Π½Π° ΡΠ°ΠΉΡΠ΅", url) });
+ }
+
+ var telegramUserIds = await identityService.GetActivatedLinkedTelegramUserIdsAsync(cancellationToken);
+ foreach (var telegramUserId in telegramUserIds)
+ {
+ try
+ {
+ await botClient.SendMessage(
+ telegramUserId, text, parseMode: ParseMode.Html, replyMarkup: keyboard, cancellationToken: cancellationToken);
+ }
+ catch
+ {
+ // ΠΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Ρ ΠΌΠΎΠ³ Π·Π°Π±Π»ΠΎΠΊΠΈΡΠΎΠ²Π°ΡΡ Π±ΠΎΡΠ° β Π½Π΅ ΠΊΡΠΈΡΠΈΡΠ½ΠΎ, ΠΏΡΠΎΠ΄ΠΎΠ»ΠΆΠ°Π΅ΠΌ ΡΠ°ΡΡΡΠ»ΠΊΡ ΠΎΡΡΠ°Π»ΡΠ½ΡΠΌ.
+ }
+ }
+ }
+
public async Task NotifyUserAsync(Guid userId, string message, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(options.Value.BotToken))
diff --git a/backend/src/PnvPanel.Application/Admin/News/CreatePostCommandHandler.cs b/backend/src/PnvPanel.Application/Admin/News/CreatePostCommandHandler.cs
index 4d6f73a..d6dee7d 100644
--- a/backend/src/PnvPanel.Application/Admin/News/CreatePostCommandHandler.cs
+++ b/backend/src/PnvPanel.Application/Admin/News/CreatePostCommandHandler.cs
@@ -6,7 +6,7 @@ using PnvPanel.Domain.News;
namespace PnvPanel.Application.Admin.News;
-public sealed class CreatePostCommandHandler(IAppDbContext dbContext, IRealtimeNotifier notifier)
+public sealed class CreatePostCommandHandler(IAppDbContext dbContext, IRealtimeNotifier notifier, ITelegramNotifier telegramNotifier)
: ICommandHandler>
{
public async Task> Handle(CreatePostCommand command, CancellationToken cancellationToken)
@@ -15,6 +15,7 @@ public sealed class CreatePostCommandHandler(IAppDbContext dbContext, IRealtimeN
dbContext.NewsPosts.Add(post);
await notifier.NotifyNewsPublishedAsync(post.Id, post.Title, post.CreatedAt, cancellationToken);
+ await telegramNotifier.NotifyUsersNewsPublishedAsync(post.Title, cancellationToken);
return Result.Success(NewsPostDto.FromDomain(post));
}
diff --git a/backend/src/PnvPanel.Application/Common/Interfaces/IIdentityService.cs b/backend/src/PnvPanel.Application/Common/Interfaces/IIdentityService.cs
index 6bd0c63..14ad92e 100644
--- a/backend/src/PnvPanel.Application/Common/Interfaces/IIdentityService.cs
+++ b/backend/src/PnvPanel.Application/Common/Interfaces/IIdentityService.cs
@@ -60,4 +60,8 @@ public interface IIdentityService
Task FindUserIdByTelegramUserIdAsync(long telegramUserId, CancellationToken cancellationToken);
Task GetTelegramLinkInfoAsync(Guid userId, CancellationToken cancellationToken);
+
+ /// Telegram ID Π°ΠΊΡΠΈΠ²Π½ΡΡ
(Π°ΠΊΡΠΈΠ²ΠΈΡΠΎΠ²Π°Π½Π½ΡΡ
, Π½Π΅ Π·Π°Π±Π»ΠΎΠΊΠΈΡΠΎΠ²Π°Π½Π½ΡΡ
) ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»Π΅ΠΉ Ρ ΠΏΡΠΈΠ²ΡΠ·ΠΊΠΎΠΉ β
+ /// Π΄Π»Ρ ΡΠ°ΡΡΡΠ»ΠΊΠΈ ΡΠ²Π΅Π΄ΠΎΠΌΠ»Π΅Π½ΠΈΠΉ Π²ΡΠΎΠ΄Π΅ ΠΏΡΠ±Π»ΠΈΠΊΠ°ΡΠΈΠΈ Π½ΠΎΠ²ΠΎΡΡΠΈ.
+ Task> GetActivatedLinkedTelegramUserIdsAsync(CancellationToken cancellationToken);
}
diff --git a/backend/src/PnvPanel.Application/Common/Interfaces/ITelegramNotifier.cs b/backend/src/PnvPanel.Application/Common/Interfaces/ITelegramNotifier.cs
index 372d49e..84437df 100644
--- a/backend/src/PnvPanel.Application/Common/Interfaces/ITelegramNotifier.cs
+++ b/backend/src/PnvPanel.Application/Common/Interfaces/ITelegramNotifier.cs
@@ -21,4 +21,8 @@ public interface ITelegramNotifier
/// ΠΠ°ΡΠ²ΠΊΠ° Π½Π° ΡΠΎΠ»Ρ β ΠΈΠ½Π»Π°ΠΉΠ½-ΠΊΠ½ΠΎΠΏΠΊΠΈ Β«ΠΠ΄ΠΎΠ±ΡΠΈΡΡ/ΠΡΠΊΠ»ΠΎΠ½ΠΈΡΡΒ», ΡΠ΅ΡΠ°Π΅ΡΡΡ ΠΏΠΎΠ»Π½ΠΎΡΡΡΡ Π² Telegram.
Task NotifyAdminsRoleRequestCreatedAsync(
Guid ticketId, string userName, string roleDescription, string justification, CancellationToken cancellationToken);
+
+ /// Π Π°ΡΡΡΠ»ΠΊΠ° ΠΎ ΠΏΡΠ±Π»ΠΈΠΊΠ°ΡΠΈΠΈ Π½ΠΎΠ²ΠΎΡΡΠΈ Π²ΡΠ΅ΠΌ Π°ΠΊΡΠΈΠ²ΠΈΡΠΎΠ²Π°Π½Π½ΡΠΌ ΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΠΌ Ρ ΠΏΡΠΈΠ²ΡΠ·Π°Π½Π½ΡΠΌ Telegram
+ /// (ΠΊΠ½ΠΎΠΏΠΊΠ°-ΡΡΡΠ»ΠΊΠ° Π½Π° ΡΠ°ΠΉΡ, Π³Π΄Π΅ Π½ΠΎΠ²ΠΎΡΡΡ ΠΏΠΎΠΊΠ°Π·Π°Π½Π° ΡΠ΅Π»ΠΈΠΊΠΎΠΌ).
+ Task NotifyUsersNewsPublishedAsync(string title, CancellationToken cancellationToken);
}
diff --git a/backend/src/PnvPanel.Infrastructure/Identity/IdentityService.cs b/backend/src/PnvPanel.Infrastructure/Identity/IdentityService.cs
index df29b4e..7c78afe 100644
--- a/backend/src/PnvPanel.Infrastructure/Identity/IdentityService.cs
+++ b/backend/src/PnvPanel.Infrastructure/Identity/IdentityService.cs
@@ -252,6 +252,14 @@ internal sealed class IdentityService(UserManager userManager, SignInMa
: new TelegramLinkInfo(false, null, null);
}
+ public async Task> GetActivatedLinkedTelegramUserIdsAsync(CancellationToken cancellationToken)
+ {
+ return await userManager.Users.AsNoTracking()
+ .Where(u => u.TelegramUserId != null && u.IsActivated && !u.IsBlocked)
+ .Select(u => u.TelegramUserId!.Value)
+ .ToListAsync(cancellationToken);
+ }
+
private async Task GetPrimaryRoleNameAsync(AppUser user)
{
var roles = await userManager.GetRolesAsync(user);