From a670d2e966bd77fcd1afdadc2cf2947f292988ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Fri, 28 Aug 2026 15:03:36 +0300 Subject: [PATCH] Telegram entegrasyonu --- .../Enums/NotificationChannels.cs | 2 +- .../NotificationIdentifierProvider.cs | 2 +- .../Sozsoft.Sender/SozsoftSenderModule.cs | 5 + .../Telegram/SozsoftTelegramSender.cs | 44 ++++++ .../Telegram/TelegramHttpClient.cs | 44 ++++++ .../Telegram/TelegramPostMessageInput.cs | 9 ++ .../Telegram/TelegramSendResult.cs | 6 + .../Telegram/TelegramSendResultDto.cs | 23 +++ .../Telegram/TelegramSettingNames.cs | 9 ++ .../Identity/Dto/UserInfoViewModel.cs | 1 + .../Identity/PlatformIdentityAppService.cs | 1 + .../UserInfoViewModelMappingExtensions.cs | 1 + .../Migrations/HostData.json | 138 ++++++++++++------ .../Migrations/LanguagesData.json | 42 ++++++ .../Migrations/ListFormSeeder_Saas.cs | 2 + .../Migrations/PermissionsData.json | 9 ++ .../PlatformConsts.cs | 10 ++ .../PlatformModuleExtensionConfigurator.cs | 7 + .../BackgroundWorkers/NotificationWorker.cs | 23 +++ .../Data/SeedConsts.cs | 9 ++ .../Extensions/AbpIdentityUserExtensions.cs | 10 ++ .../PlatformEfCoreEntityExtensionMappings.cs | 9 ++ ....cs => 20260828094327_Initial.Designer.cs} | 6 +- ...5_Initial.cs => 20260828094327_Initial.cs} | 1 + .../PlatformDbContextModelSnapshot.cs | 4 + ui/src/constants/notification-channel.enum.ts | 4 +- ui/src/proxy/admin/models.ts | 1 + .../views/admin/activityLog/ActivityLog.tsx | 1 + .../activityLog/components/LogFilter.tsx | 1 + .../admin/profile/components/General.tsx | 11 ++ .../views/admin/user-management/Details.tsx | 16 ++ 31 files changed, 403 insertions(+), 48 deletions(-) create mode 100644 api/modules/Sozsoft.Sender/Telegram/SozsoftTelegramSender.cs create mode 100644 api/modules/Sozsoft.Sender/Telegram/TelegramHttpClient.cs create mode 100644 api/modules/Sozsoft.Sender/Telegram/TelegramPostMessageInput.cs create mode 100644 api/modules/Sozsoft.Sender/Telegram/TelegramSendResult.cs create mode 100644 api/modules/Sozsoft.Sender/Telegram/TelegramSendResultDto.cs create mode 100644 api/modules/Sozsoft.Sender/Telegram/TelegramSettingNames.cs rename api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/{20260826070015_Initial.Designer.cs => 20260828094327_Initial.Designer.cs} (99%) rename api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/{20260826070015_Initial.cs => 20260828094327_Initial.cs} (99%) diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/Enums/NotificationChannels.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/Enums/NotificationChannels.cs index 9af2dfa7..95545aec 100644 --- a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/Enums/NotificationChannels.cs +++ b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain.Shared/Enums/NotificationChannels.cs @@ -9,5 +9,5 @@ public static class NotificationChannels public const string UiActivity = "UiActivity"; //UI Activity (ABP UI Activity) public const string UiToast = "UiToast"; //UI Toast (ABP UI Toast) Ayarlar/Sistem/Bildirimler/Chrome açık olması gerekiyor. public const string WhatsApp = "WhatsApp"; //WhatsApp (HTTP API, template-based) - // public const string Telegram = "Telegram"; + public const string Telegram = "Telegram"; //Telegram (Bot API, chat based) } diff --git a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationIdentifierProvider.cs b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationIdentifierProvider.cs index 119a209a..ecd1a9a1 100644 --- a/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationIdentifierProvider.cs +++ b/api/modules/Sozsoft.Notifications/Sozsoft.Notifications.Domain/NotificationIdentifierProvider.cs @@ -40,7 +40,7 @@ public class NotificationIdentifierProvider : INotificationIdentifierProvider NotificationChannels.UiActivity => user.Id.ToString(), NotificationChannels.UiToast => user.Id.ToString(), NotificationChannels.WhatsApp => user.PhoneNumber, - // NotificationChannels.Telegram => user.PhoneNumber, + NotificationChannels.Telegram => user.GetProperty("TelegramChatId"), _ => null, }; diff --git a/api/modules/Sozsoft.Sender/SozsoftSenderModule.cs b/api/modules/Sozsoft.Sender/SozsoftSenderModule.cs index 902c92d1..f39f2463 100644 --- a/api/modules/Sozsoft.Sender/SozsoftSenderModule.cs +++ b/api/modules/Sozsoft.Sender/SozsoftSenderModule.cs @@ -5,6 +5,7 @@ using Sozsoft.Sender.Mail.AmazonSes; using Sozsoft.Sender.Rocket; using Sozsoft.Sender.Sms; using Sozsoft.Sender.Sms.PostaGuvercini; +using Sozsoft.Sender.Telegram; using Sozsoft.Sender.WhatsApp; using Volo.Abp.BackgroundJobs; using Volo.Abp.Emailing; @@ -36,6 +37,7 @@ public class ErpSenderModule : AbpModule context.Services.AddScoped(sp => sp.GetRequiredService()); context.Services.AddScoped(sp => sp.GetRequiredService()); context.Services.AddScoped(sp => sp.GetRequiredService()); + context.Services.AddScoped(sp => sp.GetRequiredService()); context.Services.AddHttpClient(); context.Services.AddScoped(sp => sp.GetRequiredService()); @@ -46,6 +48,9 @@ public class ErpSenderModule : AbpModule context.Services.AddHttpClient(); context.Services.AddScoped(sp => sp.GetRequiredService()); + context.Services.AddHttpClient(); + context.Services.AddScoped(sp => sp.GetRequiredService()); + } } diff --git a/api/modules/Sozsoft.Sender/Telegram/SozsoftTelegramSender.cs b/api/modules/Sozsoft.Sender/Telegram/SozsoftTelegramSender.cs new file mode 100644 index 00000000..ab6e681f --- /dev/null +++ b/api/modules/Sozsoft.Sender/Telegram/SozsoftTelegramSender.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.Logging; +using Volo.Abp.DependencyInjection; + +namespace Sozsoft.Sender.Telegram; + +public interface ISozsoftTelegramSender : ITransientDependency +{ + Task SendAsync(string ChatId, string Message); +} + +public class SozsoftTelegramSender : ISozsoftTelegramSender +{ + private readonly ITelegramHttpClient httpClient; + private readonly ILogger logger; + + public SozsoftTelegramSender( + ITelegramHttpClient httpClient, + ILogger logger) + { + this.httpClient = httpClient; + this.logger = logger; + } + + public async Task SendAsync(string ChatId, string Message) + { + logger.LogInformation("Sending Telegram message. {ChatId}, {Message}", ChatId, Message); + var result = await httpClient.SendMessageAsync(ChatId, Message); + var success = result?.ok == true; + if (success) + { + logger.LogInformation("Telegram sent successfully. {ChatId}, {Message}", ChatId, Message); + } + else + { + logger.LogWarning("Failed to send Telegram message. {ChatId}, {Message}, {ErrorCode}, {Description}", + ChatId, Message, result?.error_code, result?.description); + } + + return new TelegramSendResult + { + Success = success, + }; + } +} diff --git a/api/modules/Sozsoft.Sender/Telegram/TelegramHttpClient.cs b/api/modules/Sozsoft.Sender/Telegram/TelegramHttpClient.cs new file mode 100644 index 00000000..034bd983 --- /dev/null +++ b/api/modules/Sozsoft.Sender/Telegram/TelegramHttpClient.cs @@ -0,0 +1,44 @@ +using System.Net.Http.Json; +using Volo.Abp.Settings; + +namespace Sozsoft.Sender.Telegram; + +public interface ITelegramHttpClient +{ + Task SendMessageAsync(string ChatId, string Message); +} + +public sealed class TelegramHttpClient : ITelegramHttpClient, IDisposable +{ + private readonly HttpClient httpClient; + private readonly ISettingProvider settingProvider; + + public TelegramHttpClient( + HttpClient httpClient, + ISettingProvider settingProvider) + { + this.httpClient = httpClient; + this.settingProvider = settingProvider; + } + + public async Task SendMessageAsync(string ChatId, string Message) + { + var url = await settingProvider.GetOrNullAsync(TelegramSettingNames.Url); + var botToken = await settingProvider.GetOrNullAsync(TelegramSettingNames.BotToken); + var parseMode = await settingProvider.GetOrNullAsync(TelegramSettingNames.ParseMode); + + var response = await httpClient.PostAsJsonAsync($"{url.TrimEnd('/')}/bot{botToken}/sendMessage", + new TelegramPostMessageInput + { + chat_id = ChatId, + text = Message, + parse_mode = parseMode.IsNullOrWhiteSpace() ? null : parseMode, + disable_web_page_preview = true + }); + + // Bot API hatayı 4xx gövdesinde açıklar; durum kodunu fırlatmak açıklamayı kaybettirir. + return await response.Content.ReadFromJsonAsync(); + } + + public void Dispose() => httpClient?.Dispose(); +} diff --git a/api/modules/Sozsoft.Sender/Telegram/TelegramPostMessageInput.cs b/api/modules/Sozsoft.Sender/Telegram/TelegramPostMessageInput.cs new file mode 100644 index 00000000..38fe827b --- /dev/null +++ b/api/modules/Sozsoft.Sender/Telegram/TelegramPostMessageInput.cs @@ -0,0 +1,9 @@ +namespace Sozsoft.Sender.Telegram; + +public class TelegramPostMessageInput +{ + public string chat_id { get; set; } + public string text { get; set; } + public string parse_mode { get; set; } + public bool disable_web_page_preview { get; set; } +} diff --git a/api/modules/Sozsoft.Sender/Telegram/TelegramSendResult.cs b/api/modules/Sozsoft.Sender/Telegram/TelegramSendResult.cs new file mode 100644 index 00000000..b7215513 --- /dev/null +++ b/api/modules/Sozsoft.Sender/Telegram/TelegramSendResult.cs @@ -0,0 +1,6 @@ +namespace Sozsoft.Sender.Telegram; + +public class TelegramSendResult +{ + public bool Success { get; set; } +} diff --git a/api/modules/Sozsoft.Sender/Telegram/TelegramSendResultDto.cs b/api/modules/Sozsoft.Sender/Telegram/TelegramSendResultDto.cs new file mode 100644 index 00000000..e0db0bf6 --- /dev/null +++ b/api/modules/Sozsoft.Sender/Telegram/TelegramSendResultDto.cs @@ -0,0 +1,23 @@ +namespace Sozsoft.Sender.Telegram; + +public class TelegramSendResultDto +{ + public bool ok { get; set; } + public int? error_code { get; set; } + public string description { get; set; } + public Message result { get; set; } + + public class Message + { + public long message_id { get; set; } + public int date { get; set; } + public Chat chat { get; set; } + + public class Chat + { + public long id { get; set; } + public string type { get; set; } + public string username { get; set; } + } + } +} diff --git a/api/modules/Sozsoft.Sender/Telegram/TelegramSettingNames.cs b/api/modules/Sozsoft.Sender/Telegram/TelegramSettingNames.cs new file mode 100644 index 00000000..6447fe2c --- /dev/null +++ b/api/modules/Sozsoft.Sender/Telegram/TelegramSettingNames.cs @@ -0,0 +1,9 @@ +namespace Sozsoft.Sender.Telegram; + +public static class TelegramSettingNames +{ + public const string Default = "App.Sender.Telegram."; + public const string Url = Default + "Url"; // https://api.telegram.org + public const string BotToken = Default + "BotToken"; // BotFather token: 123456:ABC-DEF... + public const string ParseMode = Default + "ParseMode"; // HTML | Markdown | MarkdownV2 | (bos) +} diff --git a/api/src/Sozsoft.Platform.Application.Contracts/Identity/Dto/UserInfoViewModel.cs b/api/src/Sozsoft.Platform.Application.Contracts/Identity/Dto/UserInfoViewModel.cs index 0692c649..7c30833f 100644 --- a/api/src/Sozsoft.Platform.Application.Contracts/Identity/Dto/UserInfoViewModel.cs +++ b/api/src/Sozsoft.Platform.Application.Contracts/Identity/Dto/UserInfoViewModel.cs @@ -38,6 +38,7 @@ public class UserInfoViewModel : ExtensibleObject public bool ShouldChangePasswordOnNextLogin { get; set; } public string Avatar { get; set; } public string RocketUsername { get; set; } + public string TelegramChatId { get; set; } public DateTimeOffset? CreationTime { get; set; } public DateTimeOffset? LastModificationTime { get; set; } public string WorkHour { get; set; } diff --git a/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs b/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs index b865ed15..b271b96f 100644 --- a/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Identity/PlatformIdentityAppService.cs @@ -260,6 +260,7 @@ public class PlatformIdentityAppService : ApplicationService user.Surname = UserInfo.Surname; user.SetPhoneNumber(UserInfo.PhoneNumber, user.PhoneNumberConfirmed); user.SetRocketUsername(UserInfo.RocketUsername); + user.SetTelegramChatId(UserInfo.TelegramChatId); user.SetWorkHour(UserInfo.WorkHour); user.SetDepartmentId(UserInfo.DepartmentId); user.SetJobPositionId(UserInfo.JobPositionId); diff --git a/api/src/Sozsoft.Platform.Application/Identity/UserInfoViewModelMappingExtensions.cs b/api/src/Sozsoft.Platform.Application/Identity/UserInfoViewModelMappingExtensions.cs index 683e6421..b22a78b5 100644 --- a/api/src/Sozsoft.Platform.Application/Identity/UserInfoViewModelMappingExtensions.cs +++ b/api/src/Sozsoft.Platform.Application/Identity/UserInfoViewModelMappingExtensions.cs @@ -16,6 +16,7 @@ public static class UserInfoViewModelMappingExtensions userInfoViewModel.LoginEndDate = user.GetLoginEndDate(); userInfoViewModel.Avatar = user.GetAvatar(); userInfoViewModel.RocketUsername = user.GetRocketUsername(); + userInfoViewModel.TelegramChatId = user.GetTelegramChatId(); userInfoViewModel.WorkHour = user.GetWorkHour(); userInfoViewModel.DepartmentId = user.GetDepartmentId(); userInfoViewModel.JobPositionId = user.GetJobPositionId(); diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/HostData.json b/api/src/Sozsoft.Platform.DbMigrator/Migrations/HostData.json index 8ebb70cd..f35dd83f 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/HostData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/HostData.json @@ -414,6 +414,58 @@ "selectOptions": {}, "order": 16 }, + { + "code": "App.Sender.Telegram.Url", + "nameKey": "App.Sender.Telegram.Url", + "descriptionKey": "App.SenderUrl.UrlDescription", + "defaultValue": "https://api.telegram.org", + "isVisibleToClients": false, + "providers": "T|G|D", + "isInherited": false, + "isEncrypted": false, + "mainGroupKey": "App.Sender", + "subGroupKey": "App.Sender.Telegram", + "requiredPermissionName": "App.Sender.Telegram", + "dataType": "Text", + "selectOptions": {}, + "order": 17 + }, + { + "code": "App.Sender.Telegram.BotToken", + "nameKey": "App.Sender.Telegram.BotToken", + "descriptionKey": "App.TelegramBotToken.BotTokenDescription", + "defaultValue": "8783091157:AAHlTHofhUmQtAd3JwPShkGBbL_JwPJjI7c", + "isVisibleToClients": false, + "providers": "T|G|D", + "isInherited": false, + "isEncrypted": true, + "mainGroupKey": "App.Sender", + "subGroupKey": "App.Sender.Telegram", + "requiredPermissionName": "App.Sender.Telegram", + "dataType": "Text", + "selectOptions": {}, + "order": 18 + }, + { + "code": "App.Sender.Telegram.ParseMode", + "nameKey": "App.Sender.Telegram.ParseMode", + "descriptionKey": "App.TelegramParseMode.ParseModeDescription", + "defaultValue": "HTML", + "isVisibleToClients": false, + "providers": "T|G|D", + "isInherited": false, + "isEncrypted": false, + "mainGroupKey": "App.Sender", + "subGroupKey": "App.Sender.Telegram", + "requiredPermissionName": "App.Sender.Telegram", + "dataType": "List", + "selectOptions": { + "HTML": "HTML", + "Markdown": "Markdown", + "MarkdownV2": "MarkdownV2" + }, + "order": 19 + }, { "code": "App.Sender.Rocket.Url", "nameKey": "App.Sender.Rocket.Url", @@ -428,7 +480,7 @@ "requiredPermissionName": "App.Sender.Rocket", "dataType": "Text", "selectOptions": {}, - "order": 17 + "order": 20 }, { "code": "App.Sender.Rocket.UserId", @@ -444,7 +496,7 @@ "requiredPermissionName": "App.Sender.Rocket", "dataType": "Text", "selectOptions": {}, - "order": 18 + "order": 21 }, { "code": "App.Sender.Rocket.Token", @@ -460,7 +512,7 @@ "requiredPermissionName": "App.Sender.Rocket", "dataType": "Text", "selectOptions": {}, - "order": 19 + "order": 22 }, { "code": "Abp.Mailing.DefaultFromDisplayName", @@ -476,7 +528,7 @@ "requiredPermissionName": "Abp.Mailing", "dataType": "Text", "selectOptions": {}, - "order": 20 + "order": 23 }, { "code": "Abp.Mailing.DefaultFromAddress", @@ -492,7 +544,7 @@ "requiredPermissionName": "Abp.Mailing", "dataType": "Text", "selectOptions": {}, - "order": 21 + "order": 24 }, { "code": "Abp.Mailing.Smtp.UserName", @@ -508,7 +560,7 @@ "requiredPermissionName": "Abp.Mailing.Smtp", "dataType": "Text", "selectOptions": {}, - "order": 22 + "order": 25 }, { "code": "Abp.Mailing.Smtp.Password", @@ -524,7 +576,7 @@ "requiredPermissionName": "Abp.Mailing.Smtp", "dataType": "Text", "selectOptions": {}, - "order": 23 + "order": 26 }, { "code": "Abp.Mailing.Smtp.Host", @@ -540,7 +592,7 @@ "requiredPermissionName": "Abp.Mailing.Smtp", "dataType": "Text", "selectOptions": {}, - "order": 24 + "order": 27 }, { "code": "Abp.Mailing.Smtp.Port", @@ -556,7 +608,7 @@ "requiredPermissionName": "Abp.Mailing.Smtp", "dataType": "Number", "selectOptions": {}, - "order": 25 + "order": 28 }, { "code": "Abp.Mailing.Smtp.Domain", @@ -572,7 +624,7 @@ "requiredPermissionName": "Abp.Mailing.Smtp", "dataType": "Text", "selectOptions": {}, - "order": 26 + "order": 29 }, { "code": "Abp.Mailing.Smtp.EnableSsl", @@ -588,7 +640,7 @@ "requiredPermissionName": "Abp.Mailing.Smtp", "dataType": "Bool", "selectOptions": {}, - "order": 27 + "order": 30 }, { "code": "Abp.Mailing.Smtp.UseDefaultCredentials", @@ -604,7 +656,7 @@ "requiredPermissionName": "Abp.Mailing.Smtp", "dataType": "Bool", "selectOptions": {}, - "order": 28 + "order": 31 }, { "code": "Abp.Mailing.AWS.Profile", @@ -620,7 +672,7 @@ "requiredPermissionName": "Abp.Mailing.AWS", "dataType": "Text", "selectOptions": {}, - "order": 29 + "order": 32 }, { "code": "Abp.Mailing.AWS.Region", @@ -636,7 +688,7 @@ "requiredPermissionName": "Abp.Mailing.AWS", "dataType": "Text", "selectOptions": {}, - "order": 30 + "order": 33 }, { "code": "Abp.Mailing.AWS.AccessKey", @@ -652,7 +704,7 @@ "requiredPermissionName": "Abp.Mailing.AWS", "dataType": "Text", "selectOptions": {}, - "order": 31 + "order": 34 }, { "code": "Abp.Mailing.AWS.AccessKeyId", @@ -668,7 +720,7 @@ "requiredPermissionName": "Abp.Mailing.AWS", "dataType": "Text", "selectOptions": {}, - "order": 32 + "order": 35 }, { "code": "Abp.Account.IsSelfRegistrationEnabled", @@ -684,7 +736,7 @@ "requiredPermissionName": "Abp.Account.General", "dataType": "Bool", "selectOptions": {}, - "order": 40 + "order": 43 }, { "code": "Abp.Account.EnableLocalLogin", @@ -700,7 +752,7 @@ "requiredPermissionName": "Abp.Account.General", "dataType": "Bool", "selectOptions": {}, - "order": 41 + "order": 44 }, { "code": "Abp.Account.TwoFactor.Enabled", @@ -716,7 +768,7 @@ "requiredPermissionName": "Abp.Account.TwoFactor", "dataType": "Bool", "selectOptions": {}, - "order": 42 + "order": 45 }, { "code": "Abp.Account.Captcha.MaxFailedAccessAttempts", @@ -732,7 +784,7 @@ "requiredPermissionName": "Abp.Account.Captcha", "dataType": "Number", "selectOptions": {}, - "order": 43 + "order": 46 }, { "code": "Abp.Account.Captcha.EndPoint", @@ -748,7 +800,7 @@ "requiredPermissionName": "Abp.Account.Captcha", "dataType": "Text", "selectOptions": {}, - "order": 44 + "order": 47 }, { "code": "Abp.Account.Captcha.SiteKey", @@ -764,7 +816,7 @@ "requiredPermissionName": "Abp.Account.Captcha", "dataType": "Text", "selectOptions": {}, - "order": 45 + "order": 48 }, { "code": "Abp.Account.Captcha.SecretKey", @@ -780,7 +832,7 @@ "requiredPermissionName": "Abp.Account.Captcha", "dataType": "Text", "selectOptions": {}, - "order": 46 + "order": 49 }, { "code": "Abp.Identity.Profile.General.RequireVerifiedAccount", @@ -796,7 +848,7 @@ "requiredPermissionName": "Abp.Identity.Profile", "dataType": "Bool", "selectOptions": {}, - "order": 50 + "order": 53 }, { "code": "Abp.Identity.Profile.General.BlacklistedEmailProviders", @@ -812,7 +864,7 @@ "requiredPermissionName": "Abp.Identity.Profile", "dataType": "Memo", "selectOptions": {}, - "order": 51 + "order": 54 }, { "code": "Abp.Identity.SignIn.RequireConfirmedEmail", @@ -828,7 +880,7 @@ "requiredPermissionName": "Abp.Identity.SignIn", "dataType": "Bool", "selectOptions": {}, - "order": 60 + "order": 63 }, { "code": "Abp.Identity.SignIn.RequireEmailVerificationToRegister", @@ -844,7 +896,7 @@ "requiredPermissionName": "Abp.Identity.SignIn", "dataType": "Bool", "selectOptions": {}, - "order": 61 + "order": 64 }, { "code": "Abp.Identity.SignIn.RequireConfirmedPhoneNumber", @@ -860,7 +912,7 @@ "requiredPermissionName": "Abp.Identity.SignIn", "dataType": "Bool", "selectOptions": {}, - "order": 62 + "order": 65 }, { "code": "Abp.Identity.SignIn.EnablePhoneNumberConfirmation", @@ -876,7 +928,7 @@ "requiredPermissionName": "Abp.Identity.SignIn", "dataType": "Bool", "selectOptions": {}, - "order": 63 + "order": 66 }, { "code": "Abp.Identity.User.IsUserNameUpdateEnabled", @@ -892,7 +944,7 @@ "requiredPermissionName": "Abp.Identity.User", "dataType": "Bool", "selectOptions": {}, - "order": 64 + "order": 67 }, { "code": "Abp.Identity.User.IsEmailUpdateEnabled", @@ -908,7 +960,7 @@ "requiredPermissionName": "Abp.Identity.User", "dataType": "Bool", "selectOptions": {}, - "order": 65 + "order": 68 }, @@ -926,7 +978,7 @@ "requiredPermissionName": "Abp.Identity.Password", "dataType": "Bool", "selectOptions": {}, - "order": 70 + "order": 73 }, { "code": "Abp.Identity.Password.PasswordChangePeriodDays", @@ -942,7 +994,7 @@ "requiredPermissionName": "Abp.Identity.Password", "dataType": "Number", "selectOptions": {}, - "order": 71 + "order": 74 }, { "code": "Abp.Identity.Password.RequiredLength", @@ -958,7 +1010,7 @@ "requiredPermissionName": "Abp.Identity.Password", "dataType": "Number", "selectOptions": {}, - "order": 72 + "order": 75 }, { "code": "Abp.Identity.Password.RequiredUniqueChars", @@ -974,7 +1026,7 @@ "requiredPermissionName": "Abp.Identity.Password", "dataType": "Number", "selectOptions": {}, - "order": 73 + "order": 76 }, { "code": "Abp.Identity.Password.RequireNonAlphanumeric", @@ -990,7 +1042,7 @@ "requiredPermissionName": "Abp.Identity.Password", "dataType": "Bool", "selectOptions": {}, - "order": 74 + "order": 77 }, { "code": "Abp.Identity.Password.RequireLowercase", @@ -1006,7 +1058,7 @@ "requiredPermissionName": "Abp.Identity.Password", "dataType": "Bool", "selectOptions": {}, - "order": 75 + "order": 78 }, { "code": "Abp.Identity.Password.RequireUppercase", @@ -1022,7 +1074,7 @@ "requiredPermissionName": "Abp.Identity.Password", "dataType": "Bool", "selectOptions": {}, - "order": 76 + "order": 79 }, { "code": "Abp.Identity.Password.RequireDigit", @@ -1038,7 +1090,7 @@ "requiredPermissionName": "Abp.Identity.Password", "dataType": "Bool", "selectOptions": {}, - "order": 77 + "order": 80 }, { "code": "Abp.Identity.Lockout.AllowedForNewUsers", @@ -1054,7 +1106,7 @@ "requiredPermissionName": "Abp.Identity.Lockout", "dataType": "Bool", "selectOptions": {}, - "order": 78 + "order": 81 }, { "code": "Abp.Identity.Lockout.LockoutDuration", @@ -1070,7 +1122,7 @@ "requiredPermissionName": "Abp.Identity.Lockout", "dataType": "Number", "selectOptions": {}, - "order": 79 + "order": 82 }, { "code": "Abp.Identity.Lockout.MaxFailedAccessAttempts", @@ -1086,7 +1138,7 @@ "requiredPermissionName": "Abp.Identity.Lockout", "dataType": "Number", "selectOptions": {}, - "order": 80 + "order": 83 }, { "code": "Abp.Identity.OrganizationUnit.MaxUserMembershipCount", @@ -1102,7 +1154,7 @@ "requiredPermissionName": "Abp.Identity.OrganizationUnits", "dataType": "Number", "selectOptions": {}, - "order": 90 + "order": 93 } ], "NotificationTypes": [], diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json index 78df8fb6..44f23f28 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json @@ -1986,6 +1986,42 @@ "en": "The token for the WhatsApp service.", "tr": "WhatsApp hizmeti için token." }, + { + "resourceName": "Platform", + "key": "App.Sender.Telegram", + "en": "Telegram", + "tr": "Telegram" + }, + { + "resourceName": "Platform", + "key": "App.Sender.Telegram.Url", + "en": "Url", + "tr": "Url" + }, + { + "resourceName": "Platform", + "key": "App.Sender.Telegram.BotToken", + "en": "Bot Token", + "tr": "Bot Token" + }, + { + "resourceName": "Platform", + "key": "App.TelegramBotToken.BotTokenDescription", + "en": "The BotFather token of the Telegram bot.", + "tr": "Telegram botunun BotFather token bilgisi." + }, + { + "resourceName": "Platform", + "key": "App.Sender.Telegram.ParseMode", + "en": "Parse Mode", + "tr": "Biçimlendirme Modu" + }, + { + "resourceName": "Platform", + "key": "App.TelegramParseMode.ParseModeDescription", + "en": "The formatting mode used for Telegram messages.", + "tr": "Telegram mesajlarında kullanılan biçimlendirme modu." + }, { "resourceName": "Platform", "key": "App.Sender.Rocket", @@ -3468,6 +3504,12 @@ "en": "Rocket User Name", "tr": "Rocket Kullanıcı Adı" }, + { + "resourceName": "Platform", + "key": "App.Platform.TelegramChatId", + "en": "Telegram Chat Id", + "tr": "Telegram Sohbet Kimliği" + }, { "resourceName": "Platform", "key": "Abp.Identity.UserLockedOutMessage", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs index 77857a14..7fa346ba 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/ListFormSeeder_Saas.cs @@ -5387,6 +5387,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency new () { Key="Sms", Name="App.Sender.Sms" }, new () { Key="Mail", Name="App.StaticLookup.Mail" }, new () { Key="WhatsApp", Name="App.Sender.WhatsApp" }, + new () { Key="Telegram", Name="App.Sender.Telegram" }, new () { Key="Rocket", Name="App.StaticLookup.Rocket" }, new () { Key="UiActivity", Name="App.StaticLookup.UiActivity" }, new () { Key="UiToast", Name="App.StaticLookup.UiToast" }, @@ -5608,6 +5609,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency new () { Key="UiActivity", Name="App.StaticLookup.UiActivity" }, new () { Key="UiToast", Name="App.StaticLookup.UiToast" }, new () { Key="WhatsApp", Name="App.Sender.WhatsApp" }, + new () { Key="Telegram", Name="App.Sender.Telegram" }, }), }), ColumnCustomizationJson = DefaultColumnCustomizationJson, diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/PermissionsData.json b/api/src/Sozsoft.Platform.DbMigrator/Migrations/PermissionsData.json index 58e234de..3baf3641 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/PermissionsData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/PermissionsData.json @@ -2714,6 +2714,15 @@ "MultiTenancySide": 3, "MenuGroup": "Erp|Kurs" }, + { + "GroupName": "App.Administration", + "Name": "App.Sender.Telegram", + "ParentName": "App.Sender", + "DisplayName": "App.Sender.Telegram", + "IsEnabled": true, + "MultiTenancySide": 3, + "MenuGroup": "Erp|Kurs" + }, { "GroupName": "App.Administration", "Name": "App.Sender.Rocket", diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs index 7bb16c4a..094ed6a1 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs @@ -105,6 +105,7 @@ public static class PlatformConsts public const string IsVerified = "IsVerified"; public const string Avatar = "Avatar"; public const string RocketUsername = "RocketUsername"; + public const string TelegramChatId = "TelegramChatId"; public const string HomeAddress = "HomeAddress"; public const string EducationLevel = "EducationLevel"; public const string GraduationSchool = "GraduationSchool"; @@ -342,6 +343,15 @@ public static class PlatformConsts public const string TemplateName = Default + ".TemplateName"; public const string Token = Default + ".Token"; } + + public static class Telegram + { + public const string Default = GroupName + ".Telegram"; + + public const string Url = Default + ".Url"; + public const string BotToken = Default + ".BotToken"; + public const string ParseMode = Default + ".ParseMode"; + } } } diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformModuleExtensionConfigurator.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformModuleExtensionConfigurator.cs index b3ffa029..41eb89b1 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformModuleExtensionConfigurator.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformModuleExtensionConfigurator.cs @@ -84,6 +84,13 @@ public static class PlatformModuleExtensionConfigurator property.DisplayName = new LocalizableString(typeof(PlatformResource), PlatformConsts.AbpIdentity.User.RocketUsername); }); + user.AddOrUpdateProperty( + PlatformConsts.AbpIdentity.User.TelegramChatId, + property => + { + property.DisplayName = new LocalizableString(typeof(PlatformResource), PlatformConsts.AbpIdentity.User.TelegramChatId); + }); + user.AddOrUpdateProperty( PlatformConsts.AbpIdentity.User.DepartmentId, property => diff --git a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/NotificationWorker.cs b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/NotificationWorker.cs index 9ab94eed..35e0d952 100644 --- a/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/NotificationWorker.cs +++ b/api/src/Sozsoft.Platform.Domain/BackgroundWorkers/NotificationWorker.cs @@ -10,6 +10,7 @@ using Sozsoft.Platform.Data.Seeds; using Sozsoft.Sender.Mail; using Sozsoft.Sender.Rocket; using Sozsoft.Sender.Sms; +using Sozsoft.Sender.Telegram; using Sozsoft.Sender.WhatsApp; using Volo.Abp.BackgroundWorkers; using Volo.Abp.Domain.Repositories; @@ -28,6 +29,7 @@ public class NotificationWorker : BackgroundWorkerBase protected ISozsoftEmailSender ErpEmailSender { get; } protected ISozsoftSmsSender ErpSmsSender { get; } protected ISozsoftWhatsAppSender ErpWhatsAppSender { get; } + protected ISozsoftTelegramSender ErpTelegramSender { get; } protected ISettingProvider SettingProvider { get; } public NotificationWorker( @@ -38,6 +40,7 @@ public class NotificationWorker : BackgroundWorkerBase ISozsoftRocketSender erpRocketSender, ISozsoftEmailSender erpEmailSender, ISozsoftWhatsAppSender erpWhatsAppSender, + ISozsoftTelegramSender erpTelegramSender, ISettingProvider settingProvider) { Repository = repository; @@ -47,6 +50,7 @@ public class NotificationWorker : BackgroundWorkerBase ErpRocketSender = erpRocketSender; ErpEmailSender = erpEmailSender; ErpWhatsAppSender = erpWhatsAppSender; + ErpTelegramSender = erpTelegramSender; SettingProvider = settingProvider; } @@ -140,6 +144,25 @@ public class NotificationWorker : BackgroundWorkerBase } } + var telegram = await Repository.GetListAsync(a => a.NotificationChannel == NotificationChannels.Telegram && !a.IsSent, cancellationToken: cancellationToken); + foreach (var notification in telegram) + { + try + { + var result = await ErpTelegramSender.SendAsync(notification.Identifier, notification.Message); + if (result.Success) + { + notification.IsSent = true; + notification.IsRead = true; + await Repository.UpdateAsync(notification, autoSave: true, cancellationToken: cancellationToken); + } + } + catch (Exception ex) + { + Logger.LogException(ex); + } + } + await uow.SaveChangesAsync(cancellationToken); } } diff --git a/api/src/Sozsoft.Platform.Domain/Data/SeedConsts.cs b/api/src/Sozsoft.Platform.Domain/Data/SeedConsts.cs index 4280f7d7..5ce44a84 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/SeedConsts.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/SeedConsts.cs @@ -305,6 +305,15 @@ public static class SeedConsts public const string TemplateName = Default + ".TemplateName"; public const string Token = Default + ".Token"; } + + public static class Telegram + { + public const string Default = GroupName + ".Telegram"; + + public const string Url = Default + ".Url"; + public const string BotToken = Default + ".BotToken"; + public const string ParseMode = Default + ".ParseMode"; + } } } diff --git a/api/src/Sozsoft.Platform.Domain/Extensions/AbpIdentityUserExtensions.cs b/api/src/Sozsoft.Platform.Domain/Extensions/AbpIdentityUserExtensions.cs index 73ce7e1c..23ef4161 100644 --- a/api/src/Sozsoft.Platform.Domain/Extensions/AbpIdentityUserExtensions.cs +++ b/api/src/Sozsoft.Platform.Domain/Extensions/AbpIdentityUserExtensions.cs @@ -61,6 +61,16 @@ public static class AbpIdentityUserExtensions return user.GetProperty(PlatformConsts.AbpIdentity.User.RocketUsername); } + //Telegram Chat Id + public static void SetTelegramChatId(this IdentityUser user, string telegramChatId) + { + user.SetProperty(PlatformConsts.AbpIdentity.User.TelegramChatId, telegramChatId); + } + public static string GetTelegramChatId(this IdentityUser user) + { + return user.GetProperty(PlatformConsts.AbpIdentity.User.TelegramChatId); + } + //Work Hour public static void SetWorkHour(this IdentityUser user, string workHour) { diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs index bb4e5600..ab7e2b93 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformEfCoreEntityExtensionMappings.cs @@ -65,6 +65,15 @@ public static class PlatformEfCoreEntityExtensionMappings } ); + ObjectExtensionManager.Instance + .MapEfCoreProperty( + PlatformConsts.AbpIdentity.User.TelegramChatId, + (entityBuilder, propertyBuilder) => + { + propertyBuilder.HasMaxLength(64).HasDefaultValue(null); + } + ); + ObjectExtensionManager.Instance .MapEfCoreProperty( PlatformConsts.AbpIdentity.User.WorkHour, diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260826070015_Initial.Designer.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260828094327_Initial.Designer.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260826070015_Initial.Designer.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260828094327_Initial.Designer.cs index 4452a68b..f0d1e99e 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260826070015_Initial.Designer.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260828094327_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Sozsoft.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20260826070015_Initial")] + [Migration("20260828094327_Initial")] partial class Initial { /// @@ -7854,6 +7854,10 @@ namespace Sozsoft.Platform.Migrations .HasColumnType("nvarchar(64)") .HasColumnName("Surname"); + b.Property("TelegramChatId") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + b.Property("TenantId") .HasColumnType("uniqueidentifier") .HasColumnName("TenantId"); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260826070015_Initial.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260828094327_Initial.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260826070015_Initial.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260828094327_Initial.cs index 24a07b25..de702d30 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260826070015_Initial.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260828094327_Initial.cs @@ -461,6 +461,7 @@ namespace Sozsoft.Platform.Migrations SequenceNo = table.Column(type: "nvarchar(32)", maxLength: 32, nullable: true), SerialNo = table.Column(type: "nvarchar(32)", maxLength: 32, nullable: true), SskNo = table.Column(type: "nvarchar(32)", maxLength: 32, nullable: true), + TelegramChatId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: true), TerminationDate = table.Column(type: "datetime2", nullable: true), UserType = table.Column(type: "int", nullable: false, defaultValue: 0), Village = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: true), diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index 86b55864..99b4aaaf 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -7851,6 +7851,10 @@ namespace Sozsoft.Platform.Migrations .HasColumnType("nvarchar(64)") .HasColumnName("Surname"); + b.Property("TelegramChatId") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + b.Property("TenantId") .HasColumnType("uniqueidentifier") .HasColumnName("TenantId"); diff --git a/ui/src/constants/notification-channel.enum.ts b/ui/src/constants/notification-channel.enum.ts index b3ceb9f1..527dcf5b 100644 --- a/ui/src/constants/notification-channel.enum.ts +++ b/ui/src/constants/notification-channel.enum.ts @@ -6,7 +6,7 @@ const NotificationChannels = { UiActivity: 'UiActivity', UiToast: 'UiToast', WhatsApp: 'WhatsApp', - // Telegram: 'Telegram', + Telegram: 'Telegram', } export const NotificationChannelColors: Record = { @@ -17,7 +17,7 @@ export const NotificationChannelColors: Record = { UiActivity: 'bg-lime-600', UiToast: 'bg-emerald-800', WhatsApp: 'bg-cyan-600', - // Telegram: 'bg-purple-900', + Telegram: 'bg-purple-900', } export default NotificationChannels diff --git a/ui/src/proxy/admin/models.ts b/ui/src/proxy/admin/models.ts index d23c093c..250b0aef 100644 --- a/ui/src/proxy/admin/models.ts +++ b/ui/src/proxy/admin/models.ts @@ -140,6 +140,7 @@ export interface UserInfoViewModel extends ExtensibleObject { shouldChangePasswordOnNextLogin: boolean avatar: string rocketUsername?: string + telegramChatId?: string creationTime: Date | string lastModificationTime: Date | string workHour?: string diff --git a/ui/src/views/admin/activityLog/ActivityLog.tsx b/ui/src/views/admin/activityLog/ActivityLog.tsx index 1e2c521f..63497507 100644 --- a/ui/src/views/admin/activityLog/ActivityLog.tsx +++ b/ui/src/views/admin/activityLog/ActivityLog.tsx @@ -35,6 +35,7 @@ const ActivityLog = () => { NotificationChannels.UiActivity, NotificationChannels.UiToast, NotificationChannels.WhatsApp, + NotificationChannels.Telegram, ]) const fetchData = useCallback(async (isLoadmore: boolean = false) => { diff --git a/ui/src/views/admin/activityLog/components/LogFilter.tsx b/ui/src/views/admin/activityLog/components/LogFilter.tsx index 9705277a..ed26cdf6 100644 --- a/ui/src/views/admin/activityLog/components/LogFilter.tsx +++ b/ui/src/views/admin/activityLog/components/LogFilter.tsx @@ -15,6 +15,7 @@ const ticketCheckboxes = [ { label: NotificationChannels.UiActivity, value: NotificationChannels.UiActivity }, { label: NotificationChannels.UiToast, value: NotificationChannels.UiToast }, { label: NotificationChannels.WhatsApp, value: NotificationChannels.WhatsApp }, + { label: NotificationChannels.Telegram, value: NotificationChannels.Telegram }, ] const CategoryTitle = ({ children, className }: CategoryTitleProps) => { diff --git a/ui/src/views/admin/profile/components/General.tsx b/ui/src/views/admin/profile/components/General.tsx index 979a12a1..63854317 100644 --- a/ui/src/views/admin/profile/components/General.tsx +++ b/ui/src/views/admin/profile/components/General.tsx @@ -20,6 +20,7 @@ import { import 'react-advanced-cropper/dist/style.css' import { FaFacebookMessenger, + FaTelegramPlane, FaEnvelope, FaTrashAlt, FaUserCircle, @@ -293,6 +294,16 @@ const General = () => { value={profileData?.extraProperties?.['RocketUsername'] as string | undefined} > + + } + value={ + profileData?.extraProperties?.['TelegramChatId'] as string | undefined + } + > +
diff --git a/ui/src/views/admin/user-management/Details.tsx b/ui/src/views/admin/user-management/Details.tsx index 3662f142..2e527ba2 100644 --- a/ui/src/views/admin/user-management/Details.tsx +++ b/ui/src/views/admin/user-management/Details.tsx @@ -49,6 +49,7 @@ import { FaPhone, FaUserCircle, FaFacebookMessenger, + FaTelegramPlane, FaHome, FaUniversity, FaCalendarAlt, @@ -828,6 +829,21 @@ function UserDetails() {
+
+ + } + /> + +
+