From 8de65abea553989a099dc935fc3a8ef5936a9f10 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Fri, 18 Jul 2025 17:21:45 +0300 Subject: [PATCH 01/14] fix: fix of run on development env --- .gitea/workflows/deploy-dev.yml | 5 ++--- JOBot.Backend/appsettings.Development.json | 11 ----------- 2 files changed, 2 insertions(+), 14 deletions(-) delete mode 100644 JOBot.Backend/appsettings.Development.json diff --git a/.gitea/workflows/deploy-dev.yml b/.gitea/workflows/deploy-dev.yml index 7980920..8342b35 100644 --- a/.gitea/workflows/deploy-dev.yml +++ b/.gitea/workflows/deploy-dev.yml @@ -20,6 +20,5 @@ jobs: docker compose -f compose.dev.yml up -d postgres cd /home/dockeruser/jobot-stack/JOBot.Backend - dotnet ef database update -- --environment Development - docker compose up --build -d - \ No newline at end of file + dotnet ef database update --connection "Host=localhost;Port=5432;Database=jobot;Username=postgres;Password=LocalDbPass" + docker compose up --build -d \ No newline at end of file diff --git a/JOBot.Backend/appsettings.Development.json b/JOBot.Backend/appsettings.Development.json deleted file mode 100644 index 955448b..0000000 --- a/JOBot.Backend/appsettings.Development.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "ConnectionStrings": { - "PostgreSQL": "Host=localhost;Port=5432;Database=jobot;Username=postgres;Password=LocalDbPass" - } -} From 003d2201e5a18f5216509c83cf6063985ffad85e Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Fri, 18 Jul 2025 17:25:33 +0300 Subject: [PATCH 02/14] chore: .gitignore updated --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0c4ec6d..671798f 100644 --- a/.gitignore +++ b/.gitignore @@ -404,6 +404,7 @@ FodyWeavers.xsd .docker # Secrets +JOBot.Backend/appsettings.Development.json JOBot.TClient/appsettings.json /.idea/.idea.JOBot/Docker/compose.generated.override.yml /.idea/.idea.JOBot/Docker/compose.dev.generated.override.yml From 8018cb6496a7310e29d475a6865d7d24c3f36a76 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Thu, 24 Jul 2025 20:19:38 +0300 Subject: [PATCH 03/14] feat: added boilerplate cap for next stage of user prepare --- JOBot.TClient/Services/PrepareUserService.cs | 7 +++++++ JOBot.TClient/Statements/PrepareUserState.cs | 18 ++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/JOBot.TClient/Services/PrepareUserService.cs b/JOBot.TClient/Services/PrepareUserService.cs index 443f8b0..d83b666 100644 --- a/JOBot.TClient/Services/PrepareUserService.cs +++ b/JOBot.TClient/Services/PrepareUserService.cs @@ -98,4 +98,11 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie string.Format(TextResource.AskForAuth, [url.RegistrationUrl]), cancellationToken: ct); } + + public async Task SelectCv(Update update, CancellationToken ct = default) + { + ArgumentNullException.ThrowIfNull(update.Message?.From); + + await _bot.SendMessage(update.Message.From.Id, "Давайте выберем одно из доступных резюме:", cancellationToken: ct); //TODO: https://git.lisoveliy.su/Lisoveliy/JOBot/issues/9 + } } \ No newline at end of file diff --git a/JOBot.TClient/Statements/PrepareUserState.cs b/JOBot.TClient/Statements/PrepareUserState.cs index b989aec..338c40b 100644 --- a/JOBot.TClient/Statements/PrepareUserState.cs +++ b/JOBot.TClient/Statements/PrepareUserState.cs @@ -1,7 +1,6 @@ using JOBot.Proto; using JOBot.TClient.Services; using Telegram.Bot.Types; -using User = JOBot.Proto.User; namespace JOBot.TClient.Statements; @@ -38,7 +37,7 @@ public class PrepareUserState(PrepareUserService prepareUserService, MenuService } /// - /// Continue prepare stage + /// Check user logged /// /// /// @@ -50,6 +49,21 @@ public class PrepareUserState(PrepareUserService prepareUserService, MenuService await prepareUserService.Auth(update, ct); return; } + await OnAuthStage(user, update, ct); + } + + /// + /// Check user selected CV + /// + /// + /// + /// + private async Task OnAuthStage(GetUserResponse user, Update update, CancellationToken ct = default) + { + if (string.IsNullOrEmpty(user.CVUrl)) + { + await prepareUserService.SelectCv(update, ct); + } await menuService.RenderMenu(update, ct); //boilerplate } } \ No newline at end of file From 1f3d4525d8a8af14d184461307b1501b90ab0193 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Thu, 24 Jul 2025 20:40:23 +0300 Subject: [PATCH 04/14] chore: added RabbitMQ package to docker and csproj --- JOBot.Backend/JOBot.Backend.csproj | 1 + JOBot.TClient/JOBot.TClient.csproj | 1 + compose.dev.yml | 12 +++++++++++- compose.yml | 7 +++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/JOBot.Backend/JOBot.Backend.csproj b/JOBot.Backend/JOBot.Backend.csproj index 1f9a3c1..d137fc6 100644 --- a/JOBot.Backend/JOBot.Backend.csproj +++ b/JOBot.Backend/JOBot.Backend.csproj @@ -20,6 +20,7 @@ all + diff --git a/JOBot.TClient/JOBot.TClient.csproj b/JOBot.TClient/JOBot.TClient.csproj index 987d35a..6489bda 100644 --- a/JOBot.TClient/JOBot.TClient.csproj +++ b/JOBot.TClient/JOBot.TClient.csproj @@ -20,6 +20,7 @@ + diff --git a/compose.dev.yml b/compose.dev.yml index ea8011c..3ff879e 100644 --- a/compose.dev.yml +++ b/compose.dev.yml @@ -1,6 +1,15 @@ version: '3.8' services: + rabbitmq: + hostname: jobot-rabbit + image: rabbitmq:4 + ports: + - "5672:5672" + - "15672:15672" + volumes: + - rabbitmq_data:/var/lib/rabbitmq/ + postgres: image: postgres:15 environment: @@ -38,4 +47,5 @@ networks: jobot: volumes: - postgres_data: \ No newline at end of file + postgres_data: + rabbitmq_data: \ No newline at end of file diff --git a/compose.yml b/compose.yml index 6ebc653..c6f70c9 100644 --- a/compose.yml +++ b/compose.yml @@ -1,6 +1,12 @@ version: '3.8' services: + rabbitmq: + hostname: jobot-rabbit + image: rabbitmq:4 + volumes: + - rabbitmq_data:/var/lib/rabbitmq/ + postgres: image: postgres:15 environment: @@ -17,6 +23,7 @@ services: dockerfile: JOBot.Backend/Dockerfile depends_on: - postgres + - image ports: - "5000:5000" networks: From 06367a3ccdafa5737f38d14454aa96c36f983818 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Thu, 24 Jul 2025 20:40:58 +0300 Subject: [PATCH 05/14] chore: cleanup --- JOBot.TClient/Services/PrepareUserService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/JOBot.TClient/Services/PrepareUserService.cs b/JOBot.TClient/Services/PrepareUserService.cs index d83b666..db0fba7 100644 --- a/JOBot.TClient/Services/PrepareUserService.cs +++ b/JOBot.TClient/Services/PrepareUserService.cs @@ -18,7 +18,7 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie /// Cancellation Token /// RPC User Response /// If something in server logic went wrong - /// If update.Message is null + /// update.Message is null public async Task RegisterUser(Update update, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(update.Message?.From); @@ -54,7 +54,7 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie /// /// Telegram Update object /// Cancellation Token - /// If update.Message is null + /// update.Message is null public async Task AskForEulaAgreement(Update update, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(update.Message?.From); From 8c9973e55deafafe9f95b2b9b42be72934e912a3 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Fri, 25 Jul 2025 16:27:27 +0300 Subject: [PATCH 06/14] feat: added RabbitMQ queue from back-end to hook auth for client --- .../Infrastructure/Config/RabbitQueues.cs | 6 ++++++ JOBot.Backend/Program.cs | 2 +- JOBot.Backend/Services/HeadHunterService.cs | 17 +++++++++++++++-- JOBot.Backend/Startup.cs | 11 ++++++++++- 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 JOBot.Backend/Infrastructure/Config/RabbitQueues.cs diff --git a/JOBot.Backend/Infrastructure/Config/RabbitQueues.cs b/JOBot.Backend/Infrastructure/Config/RabbitQueues.cs new file mode 100644 index 0000000..cc8de04 --- /dev/null +++ b/JOBot.Backend/Infrastructure/Config/RabbitQueues.cs @@ -0,0 +1,6 @@ +namespace JOBot.Backend.Infrastructure.Config; + +public static class RabbitQueues +{ + public const string AuthQueue = "auth"; +} \ No newline at end of file diff --git a/JOBot.Backend/Program.cs b/JOBot.Backend/Program.cs index 95467a3..6e070ab 100644 --- a/JOBot.Backend/Program.cs +++ b/JOBot.Backend/Program.cs @@ -4,7 +4,7 @@ var builder = WebApplication.CreateBuilder(args); var startup = new Startup(builder.Configuration); -startup.ConfigureServices(builder.Services); +await startup.ConfigureServices(builder.Services); var app = builder.Build(); startup.Configure(app, app.Environment); diff --git a/JOBot.Backend/Services/HeadHunterService.cs b/JOBot.Backend/Services/HeadHunterService.cs index e89ce38..3e67879 100644 --- a/JOBot.Backend/Services/HeadHunterService.cs +++ b/JOBot.Backend/Services/HeadHunterService.cs @@ -1,14 +1,19 @@ +using System.Text; using System.Text.Json; -using System.Web; using JOBot.Backend.DAL.Context; using JOBot.Backend.DTOs.HeadHunterHook; using JOBot.Backend.Infrastructure.Config; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; +using RabbitMQ.Client; namespace JOBot.Backend.Services; -public class HeadHunterService(ILogger logger, IOptions config, AppDbContext dbContext) +public class HeadHunterService( + IChannel channel, + ILogger logger, + IOptions config, + AppDbContext dbContext) { private readonly HeadHunterConfig _config = config.Value; @@ -88,6 +93,14 @@ public class HeadHunterService(ILogger logger, IOptions(options => options.UseNpgsql(Configuration.GetConnectionString("PostgreSQL"))); From 7ceb8f656effa6c63dd132ebda79e71892aeebf9 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Fri, 25 Jul 2025 16:31:57 +0300 Subject: [PATCH 07/14] chore: added doc for attribute --- .../Attributes/Authorization/AcceptNotPreparedAttribute.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/JOBot.TClient/Infrastructure/Attributes/Authorization/AcceptNotPreparedAttribute.cs b/JOBot.TClient/Infrastructure/Attributes/Authorization/AcceptNotPreparedAttribute.cs index 7929497..bb9bf0d 100644 --- a/JOBot.TClient/Infrastructure/Attributes/Authorization/AcceptNotPreparedAttribute.cs +++ b/JOBot.TClient/Infrastructure/Attributes/Authorization/AcceptNotPreparedAttribute.cs @@ -1,4 +1,7 @@ namespace JOBot.TClient.Infrastructure.Attributes.Authorization; +/// +///Атрибут допуска к команде пользователя не завершившего стадию PrepareUser +/// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class AcceptNotPreparedAttribute : Attribute; \ No newline at end of file From b15a77557e90b3a9bd464816e828fa200802837b Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Fri, 25 Jul 2025 16:37:42 +0300 Subject: [PATCH 08/14] chore: cleanup --- .../Controllers/HeadHunterHookController.cs | 7 +- JOBot.Backend/DAL/Context/AppDbContext.cs | 8 +-- JOBot.Backend/DAL/Models/User.cs | 10 +-- .../HeadHunterTokenResponseDto.cs | 10 +-- JOBot.Backend/JOBot.Backend.csproj | 54 ++++++++-------- JOBot.Backend/Services/HeadHunterService.cs | 51 ++++++++------- JOBot.Backend/Services/gRPC/UserService.cs | 11 ++-- JOBot.Backend/Startup.cs | 6 +- JOBot.Backend/appsettings.json | 64 +++++++++---------- JOBot.TClient/ButtonResource.resx | 13 ++-- .../Buttons/EulaAgreementButtonCommand.cs | 2 - .../Commands/Commands/InfoCommand.cs | 2 +- .../Commands/IAuthorizedTelegramCommand.cs | 11 ++-- .../HostedServices/BotBackgroundService.cs | 13 ++-- JOBot.TClient/DependencyInjection.cs | 32 ++++++---- .../AcceptNotPreparedAttribute.cs | 2 +- .../Exceptions/FallbackException.cs | 6 +- .../Extensions/GRpcModelsExtensions.cs | 7 +- JOBot.TClient/JOBot.TClient.csproj | 8 +-- JOBot.TClient/Program.cs | 1 - JOBot.TClient/Services/MenuService.cs | 5 +- JOBot.TClient/Services/PrepareUserService.cs | 31 ++++----- JOBot.TClient/Services/UserService.cs | 5 +- JOBot.TClient/Statements/PrepareUserState.cs | 20 +++--- JOBot.TClient/TextResource.resx | 13 ++-- README.md | 24 ++++--- compose.dev.yml | 2 +- compose.yml | 4 +- 28 files changed, 224 insertions(+), 198 deletions(-) diff --git a/JOBot.Backend/Controllers/HeadHunterHookController.cs b/JOBot.Backend/Controllers/HeadHunterHookController.cs index 1ba1e24..4b630ae 100644 --- a/JOBot.Backend/Controllers/HeadHunterHookController.cs +++ b/JOBot.Backend/Controllers/HeadHunterHookController.cs @@ -8,16 +8,17 @@ namespace JOBot.Backend.Controllers; public class HeadHunterHookController(HeadHunterService hhService) : ControllerBase { - [HttpGet] public async Task Get(int userId, string? error, string code) { var res = await hhService.AuthUser(userId, code, error); return res switch { - HeadHunterService.Status.Success => Ok("Авторизация завершена успешно. Вернитесь в Telegram для продолжения."), + HeadHunterService.Status.Success => Ok( + "Авторизация завершена успешно. Вернитесь в Telegram для продолжения."), HeadHunterService.Status.UserNotFoundError => NotFound("Пользователь не найден."), - _ => BadRequest("Авторизация завершена с ошибкой. Вернитесь в Telegram для продолжения.") //TODO: Add resource + _ => BadRequest( + "Авторизация завершена с ошибкой. Вернитесь в Telegram для продолжения.") //TODO: Add resource }; } } \ No newline at end of file diff --git a/JOBot.Backend/DAL/Context/AppDbContext.cs b/JOBot.Backend/DAL/Context/AppDbContext.cs index 9e91d4e..12ef6af 100644 --- a/JOBot.Backend/DAL/Context/AppDbContext.cs +++ b/JOBot.Backend/DAL/Context/AppDbContext.cs @@ -1,14 +1,14 @@ -namespace JOBot.Backend.DAL.Context; - -using Models; +using JOBot.Backend.DAL.Models; using Microsoft.EntityFrameworkCore; +namespace JOBot.Backend.DAL.Context; + public class AppDbContext(DbContextOptions options) : DbContext(options) { public DbSet Users { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) - { + { base.OnModelCreating(modelBuilder); modelBuilder.Entity() diff --git a/JOBot.Backend/DAL/Models/User.cs b/JOBot.Backend/DAL/Models/User.cs index 82ec05c..0009df7 100644 --- a/JOBot.Backend/DAL/Models/User.cs +++ b/JOBot.Backend/DAL/Models/User.cs @@ -9,12 +9,12 @@ public class User { public Guid Id { get; set; } - [Key] - public required long UserId { get; set; } - [MaxLength(255)] - public string? Username { get; set; } + [Key] public required long UserId { get; set; } + + [MaxLength(255)] public string? Username { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - + [MaxLength(255)] public string? AccessToken { get; set; } = null; [MaxLength(255)] public string? RefreshToken { get; set; } = null; diff --git a/JOBot.Backend/DTOs/HeadHunterHook/HeadHunterTokenResponseDto.cs b/JOBot.Backend/DTOs/HeadHunterHook/HeadHunterTokenResponseDto.cs index 179cfb3..a970955 100644 --- a/JOBot.Backend/DTOs/HeadHunterHook/HeadHunterTokenResponseDto.cs +++ b/JOBot.Backend/DTOs/HeadHunterHook/HeadHunterTokenResponseDto.cs @@ -3,12 +3,12 @@ using System.Text.Json.Serialization; namespace JOBot.Backend.DTOs.HeadHunterHook; public record HeadHunterTokenResponseDto( - [property:JsonPropertyName("access_token")] + [property: JsonPropertyName("access_token")] string AccessToken, - [property:JsonPropertyName("expires_in")] + [property: JsonPropertyName("expires_in")] int ExpiresIn, - [property:JsonPropertyName("refresh_token")] + [property: JsonPropertyName("refresh_token")] string RefreshToken, - [property:JsonPropertyName("token_type")] + [property: JsonPropertyName("token_type")] string TokenType - ); \ No newline at end of file +); \ No newline at end of file diff --git a/JOBot.Backend/JOBot.Backend.csproj b/JOBot.Backend/JOBot.Backend.csproj index d137fc6..6526b76 100644 --- a/JOBot.Backend/JOBot.Backend.csproj +++ b/JOBot.Backend/JOBot.Backend.csproj @@ -1,34 +1,34 @@  - - net9.0 - enable - enable - + + net9.0 + enable + enable + - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + - - - + + + - - - + + + \ No newline at end of file diff --git a/JOBot.Backend/Services/HeadHunterService.cs b/JOBot.Backend/Services/HeadHunterService.cs index 3e67879..8ff1787 100644 --- a/JOBot.Backend/Services/HeadHunterService.cs +++ b/JOBot.Backend/Services/HeadHunterService.cs @@ -10,28 +10,36 @@ using RabbitMQ.Client; namespace JOBot.Backend.Services; public class HeadHunterService( - IChannel channel, - ILogger logger, - IOptions config, + IChannel channel, + ILogger logger, + IOptions config, AppDbContext dbContext) { + public enum Status + { + UserAuthRejectedError, + HeadHunterAuthRejectedError, + UserNotFoundError, + HeadHunterResponseDeserializationFailedError, + Success + } + private readonly HeadHunterConfig _config = config.Value; /// - /// Generate HeadHunter oauth authorization link + /// Generate HeadHunter oauth authorization link /// /// Telegram UserId /// Link for auth public string GenerateAuthLink(long userId) { - return string.Format(_config.Links.AuthLink, [_config.ClientId, GetRedirectUrl(userId)]); } - public async Task AuthUser(int userId, string authorizationCode, string? error) + public async Task AuthUser(int userId, string authorizationCode, string? error) //TODO: Разбить этот метод { logger.LogInformation($"Authorization for user {userId} in process..."); - + if (!string.IsNullOrEmpty(error)) { logger.LogWarning($"User {userId} auth completed with error {error}"); @@ -69,7 +77,7 @@ public class HeadHunterService( logger.LogWarning($"{res.Content.ReadAsStringAsync().Result}"); return Status.HeadHunterAuthRejectedError; } - + var responseDto = JsonSerializer.Deserialize(await res.Content.ReadAsStringAsync()); if (responseDto == null) @@ -78,7 +86,7 @@ public class HeadHunterService( $"{nameof(Status.HeadHunterResponseDeserializationFailedError)}"); return Status.HeadHunterResponseDeserializationFailedError; } - + var user = await dbContext.Users.FirstOrDefaultAsync(x => x.UserId == userId); if (user == null) @@ -86,20 +94,20 @@ public class HeadHunterService( logger.LogWarning($"User {userId} search completed with error {nameof(Status.UserNotFoundError)}"); return Status.UserNotFoundError; } - + user.AccessToken = responseDto.AccessToken; user.RefreshToken = responseDto.RefreshToken; - + await dbContext.SaveChangesAsync(); - + logger.LogInformation($"User {userId} auth completed!"); - + await channel.BasicPublishAsync( - string.Empty, - RabbitQueues.AuthQueue, + string.Empty, + RabbitQueues.AuthQueue, Encoding.UTF8.GetBytes(userId.ToString())); - - logger.LogInformation($"RabbitMQ event was created"); + + logger.LogInformation("RabbitMQ event was created"); return Status.Success; } @@ -114,13 +122,4 @@ public class HeadHunterService( Query = $"?userId={userId}" }.ToString(); } - - public enum Status - { - UserAuthRejectedError, - HeadHunterAuthRejectedError, - UserNotFoundError, - HeadHunterResponseDeserializationFailedError, - Success - } } \ No newline at end of file diff --git a/JOBot.Backend/Services/gRPC/UserService.cs b/JOBot.Backend/Services/gRPC/UserService.cs index 4a1cfe6..fdbbbec 100644 --- a/JOBot.Backend/Services/gRPC/UserService.cs +++ b/JOBot.Backend/Services/gRPC/UserService.cs @@ -1,4 +1,3 @@ -using Google.Protobuf.WellKnownTypes; using Grpc.Core; using JOBot.Backend.DAL.Context; using JOBot.Backend.DAL.Models; @@ -11,7 +10,7 @@ namespace JOBot.Backend.Services.gRPC; public class UserService(AppDbContext dbContext, HeadHunterService hhService) : Proto.User.UserBase { /// - /// Create user + /// Create user /// /// /// @@ -32,7 +31,7 @@ public class UserService(AppDbContext dbContext, HeadHunterService hhService) : } /// - /// Get user for client + /// Get user for client /// /// /// @@ -46,7 +45,7 @@ public class UserService(AppDbContext dbContext, HeadHunterService hhService) : } /// - /// Accept EULA for user + /// Accept EULA for user /// /// /// @@ -64,7 +63,7 @@ public class UserService(AppDbContext dbContext, HeadHunterService hhService) : } public override Task GetHeadHunterAuthHook( - GetHeadHunterAuthHookRequest request, + GetHeadHunterAuthHookRequest request, ServerCallContext context) { return Task.Run(() => new GetHeadHunterAuthHookResponse @@ -74,7 +73,7 @@ public class UserService(AppDbContext dbContext, HeadHunterService hhService) : } /// - /// Throw RPCException if user not found + /// Throw RPCException if user not found /// /// /// diff --git a/JOBot.Backend/Startup.cs b/JOBot.Backend/Startup.cs index d08caed..6890d1e 100644 --- a/JOBot.Backend/Startup.cs +++ b/JOBot.Backend/Startup.cs @@ -13,7 +13,7 @@ public class Startup(IConfiguration configuration) public async Task ConfigureServices(IServiceCollection services) { - services.AddGrpc(); + services.AddGrpc(); services.AddGrpcReflection(); services.AddControllers(); services.AddLogging(); @@ -23,9 +23,9 @@ public class Startup(IConfiguration configuration) HostName = "jobot-rabbitmq" }.CreateConnectionAsync(); await using var channel = await rabbitMqConnection.CreateChannelAsync(); - await channel.QueueDeclareAsync(RabbitQueues.AuthQueue, false, false, autoDelete: false); + await channel.QueueDeclareAsync(RabbitQueues.AuthQueue, false, false, false); services.AddSingleton(channel); - + services.AddDbContext(options => options.UseNpgsql(Configuration.GetConnectionString("PostgreSQL"))); diff --git a/JOBot.Backend/appsettings.json b/JOBot.Backend/appsettings.json index ae7b3b2..0605775 100644 --- a/JOBot.Backend/appsettings.json +++ b/JOBot.Backend/appsettings.json @@ -1,35 +1,35 @@ { - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "PostgreSQL": "Host=postgres;Port=5432;Database=jobot;Username=postgres;Password=LocalDbPass" - }, - "HeadHunter": { - "Links": { - "AuthLink": "https://hh.ru/oauth/authorize?response_type=code&client_id={0}&redirect_uri={1}", - "HookDomain": "jobot.lisoveliy.su", - "HookRoute": "/auth", - "HeadHunterApiDomain": "api.hh.ru", - "HeadHunterTokenRoute": "/token" - }, - "ClientId": "", - "Secret": "" - }, - "Kestrel": { - "Endpoints": { - "gRPC": { - "Url": "http://*:5001", - "Protocols": "Http2" - }, - "REST": { - "Url": "http://*:5000", - "Protocols": "Http1" - } - } + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "PostgreSQL": "Host=postgres;Port=5432;Database=jobot;Username=postgres;Password=LocalDbPass" + }, + "HeadHunter": { + "Links": { + "AuthLink": "https://hh.ru/oauth/authorize?response_type=code&client_id={0}&redirect_uri={1}", + "HookDomain": "jobot.lisoveliy.su", + "HookRoute": "/auth", + "HeadHunterApiDomain": "api.hh.ru", + "HeadHunterTokenRoute": "/token" + }, + "ClientId": "", + "Secret": "" + }, + "Kestrel": { + "Endpoints": { + "gRPC": { + "Url": "http://*:5001", + "Protocols": "Http2" + }, + "REST": { + "Url": "http://*:5000", + "Protocols": "Http1" + } + } + } } \ No newline at end of file diff --git a/JOBot.TClient/ButtonResource.resx b/JOBot.TClient/ButtonResource.resx index a799cfc..91aa97c 100644 --- a/JOBot.TClient/ButtonResource.resx +++ b/JOBot.TClient/ButtonResource.resx @@ -1,9 +1,10 @@ - + - + @@ -13,10 +14,14 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + Соглашаюсь с условиями использования ✅ diff --git a/JOBot.TClient/Commands/Buttons/EulaAgreementButtonCommand.cs b/JOBot.TClient/Commands/Buttons/EulaAgreementButtonCommand.cs index f28e9ab..b9ff97c 100644 --- a/JOBot.TClient/Commands/Buttons/EulaAgreementButtonCommand.cs +++ b/JOBot.TClient/Commands/Buttons/EulaAgreementButtonCommand.cs @@ -1,9 +1,7 @@ using JOBot.Proto; using JOBot.TClient.Infrastructure.Attributes.Authorization; -using JOBot.TClient.Services; using JOBot.TClient.Statements; using Telegram.Bot.Types; -using Telegram.Bot.Types.Enums; namespace JOBot.TClient.Commands.Buttons; diff --git a/JOBot.TClient/Commands/Commands/InfoCommand.cs b/JOBot.TClient/Commands/Commands/InfoCommand.cs index 45bb655..cc2a653 100644 --- a/JOBot.TClient/Commands/Commands/InfoCommand.cs +++ b/JOBot.TClient/Commands/Commands/InfoCommand.cs @@ -8,7 +8,7 @@ public class InfoCommand(ITelegramBotClient bot) : ITelegramCommand public async Task ExecuteAsync(Update update, CancellationToken ct) { ArgumentNullException.ThrowIfNull(update.Message?.From); - + await bot.SendMessage(update.Message.From.Id, TextResource.Info, cancellationToken: ct); } } \ No newline at end of file diff --git a/JOBot.TClient/Commands/IAuthorizedTelegramCommand.cs b/JOBot.TClient/Commands/IAuthorizedTelegramCommand.cs index 7d0d035..b91b18d 100644 --- a/JOBot.TClient/Commands/IAuthorizedTelegramCommand.cs +++ b/JOBot.TClient/Commands/IAuthorizedTelegramCommand.cs @@ -1,17 +1,18 @@ using JOBot.Proto; -using Telegram.Bot; using Telegram.Bot.Types; namespace JOBot.TClient.Commands; public interface IAuthorizedTelegramCommand : ITelegramCommand { - public Task ExecuteAsync(Update update, GetUserResponse user, CancellationToken ct); - - /// Throws if you try to use ITelegramCommand.ExecuteAsync - /// instead of IAuthorizedTelegramCommand.ExecuteAsync + /// + /// Throws if you try to use ITelegramCommand.ExecuteAsync + /// instead of IAuthorizedTelegramCommand.ExecuteAsync + /// Task ITelegramCommand.ExecuteAsync(Update update, CancellationToken ct) { throw new UnauthorizedAccessException("You do not have permission to access this command."); } + + public Task ExecuteAsync(Update update, GetUserResponse user, CancellationToken ct); } \ No newline at end of file diff --git a/JOBot.TClient/Core/HostedServices/BotBackgroundService.cs b/JOBot.TClient/Core/HostedServices/BotBackgroundService.cs index 87e63ff..3006f7e 100644 --- a/JOBot.TClient/Core/HostedServices/BotBackgroundService.cs +++ b/JOBot.TClient/Core/HostedServices/BotBackgroundService.cs @@ -5,10 +5,10 @@ using JOBot.TClient.Infrastructure.Attributes.Authorization; using JOBot.TClient.Infrastructure.Extensions; using JOBot.TClient.Services; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Telegram.Bot; using Telegram.Bot.Types; -using Microsoft.Extensions.Hosting; namespace JOBot.TClient.Core.HostedServices; @@ -22,8 +22,8 @@ public sealed class BotBackgroundService( protected override Task ExecuteAsync(CancellationToken stoppingToken) { botClient.StartReceiving( - updateHandler: HandleUpdateAsync, - errorHandler: HandleErrorAsync, + HandleUpdateAsync, + HandleErrorAsync, cancellationToken: stoppingToken); return Task.CompletedTask; @@ -40,7 +40,7 @@ public sealed class BotBackgroundService( ["/info"] = scope.ServiceProvider.GetRequiredService(), //Buttons - [ButtonResource.EULAAgrement] = scope.ServiceProvider.GetRequiredService(), + [ButtonResource.EULAAgrement] = scope.ServiceProvider.GetRequiredService() }; if (update.Message is { Text: { } text, From: not null }) @@ -69,7 +69,10 @@ public sealed class BotBackgroundService( await authorizedTelegramCommand.ExecuteAsync(update, user, ct); } - else await command.ExecuteAsync(update, ct); + else + { + await command.ExecuteAsync(update, ct); + } return; } diff --git a/JOBot.TClient/DependencyInjection.cs b/JOBot.TClient/DependencyInjection.cs index 153be1b..6594372 100644 --- a/JOBot.TClient/DependencyInjection.cs +++ b/JOBot.TClient/DependencyInjection.cs @@ -18,39 +18,47 @@ public static class DependencyInjection public static IServiceCollection ConfigureServices(this IServiceCollection services, IConfiguration config) { services.AddSingleton(config); - - services.AddScoped(_ => GrpcChannel.ForAddress(config.GetValue("BackendHost") - ?? throw new MissingFieldException("Host is not defined"))); - + + services.AddScoped(_ => GrpcChannel.ForAddress(config.GetValue("BackendHost") + ?? throw new MissingFieldException("Host is not defined"))); + #region Commands + services.AddScoped(); services.AddScoped(); services.AddScoped(); - + //buttons services.AddScoped(); + #endregion - + #region gRPC Clients + services.AddGrpcClient(o => o.Address = new Uri("http://backend:5001")); + #endregion - + #region Services + services.AddSingleton(); services.AddScoped(); services.AddScoped(); + #endregion - + #region States + services.AddScoped(); + #endregion - + // Bot service services.AddHostedService(); - services.AddSingleton(_ => - new TelegramBotClient(config.GetValue("TelegramToken") + services.AddSingleton(_ => + new TelegramBotClient(config.GetValue("TelegramToken") ?? throw new MissingFieldException("TelegramToken is not set"))); - + services.AddLogging(builder => builder.AddConsole()); return services; } diff --git a/JOBot.TClient/Infrastructure/Attributes/Authorization/AcceptNotPreparedAttribute.cs b/JOBot.TClient/Infrastructure/Attributes/Authorization/AcceptNotPreparedAttribute.cs index bb9bf0d..bac3b51 100644 --- a/JOBot.TClient/Infrastructure/Attributes/Authorization/AcceptNotPreparedAttribute.cs +++ b/JOBot.TClient/Infrastructure/Attributes/Authorization/AcceptNotPreparedAttribute.cs @@ -1,7 +1,7 @@ namespace JOBot.TClient.Infrastructure.Attributes.Authorization; /// -///Атрибут допуска к команде пользователя не завершившего стадию PrepareUser +/// Атрибут допуска к команде пользователя не завершившего стадию PrepareUser /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class AcceptNotPreparedAttribute : Attribute; \ No newline at end of file diff --git a/JOBot.TClient/Infrastructure/Exceptions/FallbackException.cs b/JOBot.TClient/Infrastructure/Exceptions/FallbackException.cs index e624813..afe8459 100644 --- a/JOBot.TClient/Infrastructure/Exceptions/FallbackException.cs +++ b/JOBot.TClient/Infrastructure/Exceptions/FallbackException.cs @@ -4,14 +4,14 @@ using Telegram.Bot.Types; namespace JOBot.TClient.Infrastructure.Exceptions; /// -/// Exception for fallback -/// WARNING: Don't create new exception without throwing it +/// Exception for fallback +/// WARNING: Don't create new exception without throwing it /// public class FallbackException : Exception { public FallbackException(string message, Message botMessage, ITelegramBotClient botClient) : base(message) { - botClient.SendMessage(chatId: botMessage.Chat.Id, + botClient.SendMessage(botMessage.Chat.Id, TextResource.FallbackMessage); } } \ No newline at end of file diff --git a/JOBot.TClient/Infrastructure/Extensions/GRpcModelsExtensions.cs b/JOBot.TClient/Infrastructure/Extensions/GRpcModelsExtensions.cs index ba10820..0a81265 100644 --- a/JOBot.TClient/Infrastructure/Extensions/GRpcModelsExtensions.cs +++ b/JOBot.TClient/Infrastructure/Extensions/GRpcModelsExtensions.cs @@ -4,6 +4,9 @@ namespace JOBot.TClient.Infrastructure.Extensions; public static class GRpcModelsExtensions { - public static bool IsPrepared(this GetUserResponse user) => user is { Eula: true, IsLogged: true } && - !string.IsNullOrEmpty(user.CVUrl); + public static bool IsPrepared(this GetUserResponse user) + { + return user is { Eula: true, IsLogged: true } && + !string.IsNullOrEmpty(user.CVUrl); + } } \ No newline at end of file diff --git a/JOBot.TClient/JOBot.TClient.csproj b/JOBot.TClient/JOBot.TClient.csproj index 6489bda..5bcfbc3 100644 --- a/JOBot.TClient/JOBot.TClient.csproj +++ b/JOBot.TClient/JOBot.TClient.csproj @@ -20,7 +20,7 @@ - + @@ -59,9 +59,9 @@ - - PreserveNewest - + + PreserveNewest + diff --git a/JOBot.TClient/Program.cs b/JOBot.TClient/Program.cs index 5c14977..563d136 100644 --- a/JOBot.TClient/Program.cs +++ b/JOBot.TClient/Program.cs @@ -6,7 +6,6 @@ var host = Host.CreateDefaultBuilder(args) { // Настройка DI services.ConfigureServices(context.Configuration); - }) .Build(); diff --git a/JOBot.TClient/Services/MenuService.cs b/JOBot.TClient/Services/MenuService.cs index e8ba02e..4f2e4d1 100644 --- a/JOBot.TClient/Services/MenuService.cs +++ b/JOBot.TClient/Services/MenuService.cs @@ -1,6 +1,5 @@ using Telegram.Bot; using Telegram.Bot.Types; -using User = JOBot.Proto.User; namespace JOBot.TClient.Services; @@ -9,7 +8,7 @@ public class MenuService(ITelegramBotClient bot) public async Task RenderMenu(Update update, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(update.Message?.From); - - await bot.SendMessage(update.Message.From.Id,"PrepareUser stage is done.", cancellationToken: ct); + + await bot.SendMessage(update.Message.From.Id, "PrepareUser stage is done.", cancellationToken: ct); } } \ No newline at end of file diff --git a/JOBot.TClient/Services/PrepareUserService.cs b/JOBot.TClient/Services/PrepareUserService.cs index db0fba7..4c6a71b 100644 --- a/JOBot.TClient/Services/PrepareUserService.cs +++ b/JOBot.TClient/Services/PrepareUserService.cs @@ -12,7 +12,7 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie private readonly User.UserClient _userClient = userClient; /// - /// Get or register user on system + /// Get or register user on system /// /// Telegram Update object /// Cancellation Token @@ -26,21 +26,21 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie var result = await _userClient.RegisterAsync(new RegisterRequest { UserId = update.Message.From.Id, - Username = update.Message.From.Username, + Username = update.Message.From.Username }); if (!result.Success) { - await _bot.SendMessage(chatId: update.Message.Chat.Id, + await _bot.SendMessage(update.Message.Chat.Id, TextResource.FallbackMessage, cancellationToken: ct); throw new FallbackException(TextResource.FallbackMessage, update.Message, _bot); } - var user = await _userClient.GetUserAsync(new GetUserRequest() + var user = await _userClient.GetUserAsync(new GetUserRequest { - UserId = update.Message.From.Id, + UserId = update.Message.From.Id }); if (user == null) @@ -50,7 +50,7 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie } /// - /// Get Eula Agreement from user + /// Get Eula Agreement from user /// /// Telegram Update object /// Cancellation Token @@ -66,7 +66,7 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie } /// - /// Accept EULA for user + /// Accept EULA for user /// /// Telegram update object /// Cancellation Token @@ -74,10 +74,10 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie public async Task AcceptEula(Update update, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(update.Message?.From); - var result = await _userClient.AcceptEulaAsync(new() + var result = await _userClient.AcceptEulaAsync(new AcceptEulaRequest { EulaAccepted = true, - UserId = update.Message.From.Id, + UserId = update.Message.From.Id }); if (!result.Success) @@ -87,22 +87,23 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie public async Task Auth(Update update, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(update.Message?.From); - - var url = await _userClient.GetHeadHunterAuthHookAsync(new GetHeadHunterAuthHookRequest() + + var url = await _userClient.GetHeadHunterAuthHookAsync(new GetHeadHunterAuthHookRequest { UserId = update.Message.From.Id }); await _bot.SendMessage( - update.Message.From.Id, - string.Format(TextResource.AskForAuth, [url.RegistrationUrl]), + update.Message.From.Id, + string.Format(TextResource.AskForAuth, [url.RegistrationUrl]), cancellationToken: ct); } public async Task SelectCv(Update update, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(update.Message?.From); - - await _bot.SendMessage(update.Message.From.Id, "Давайте выберем одно из доступных резюме:", cancellationToken: ct); //TODO: https://git.lisoveliy.su/Lisoveliy/JOBot/issues/9 + + await _bot.SendMessage(update.Message.From.Id, "Давайте выберем одно из доступных резюме:", + cancellationToken: ct); //TODO: https://git.lisoveliy.su/Lisoveliy/JOBot/issues/9 } } \ No newline at end of file diff --git a/JOBot.TClient/Services/UserService.cs b/JOBot.TClient/Services/UserService.cs index 51f4c7b..f097bdd 100644 --- a/JOBot.TClient/Services/UserService.cs +++ b/JOBot.TClient/Services/UserService.cs @@ -10,7 +10,7 @@ namespace JOBot.TClient.Services; public class UserService(ITelegramBotClient bot, User.UserClient userClient) { /// - /// Get user + /// Get user /// /// Telegram Update object /// Cancellation Token @@ -26,7 +26,7 @@ public class UserService(ITelegramBotClient bot, User.UserClient userClient) { user = await userClient.GetUserAsync(new GetUserRequest() //Получаем пользователя { - UserId = update.Message.From.Id, + UserId = update.Message.From.Id }); } catch (RpcException e) //Пользователь не найден? @@ -36,6 +36,7 @@ public class UserService(ITelegramBotClient bot, User.UserClient userClient) return null; } + return user; } } \ No newline at end of file diff --git a/JOBot.TClient/Statements/PrepareUserState.cs b/JOBot.TClient/Statements/PrepareUserState.cs index 338c40b..a44efa0 100644 --- a/JOBot.TClient/Statements/PrepareUserState.cs +++ b/JOBot.TClient/Statements/PrepareUserState.cs @@ -7,7 +7,7 @@ namespace JOBot.TClient.Statements; public class PrepareUserState(PrepareUserService prepareUserService, MenuService menuService) { /// - /// Try to prepare user if is not registered + /// Try to prepare user if is not registered /// /// Update telegram object /// Cancellation token @@ -20,24 +20,24 @@ public class PrepareUserState(PrepareUserService prepareUserService, MenuService await prepareUserService.AskForEulaAgreement(update, ct); return; //interrupt while eula isn't accepted } - + await OnUserEulaValidStage(user, update, ct); } /// - /// Signal for accepted eula + /// Signal for accepted eula /// /// /// /// public async Task AcceptEula(GetUserResponse user, Update update, CancellationToken ct = default) { - await prepareUserService.AcceptEula(update, ct: ct); + await prepareUserService.AcceptEula(update, ct); await OnUserEulaValidStage(user, update, ct); } /// - /// Check user logged + /// Check user logged /// /// /// @@ -49,21 +49,19 @@ public class PrepareUserState(PrepareUserService prepareUserService, MenuService await prepareUserService.Auth(update, ct); return; } + await OnAuthStage(user, update, ct); } - + /// - /// Check user selected CV + /// Check user selected CV /// /// /// /// private async Task OnAuthStage(GetUserResponse user, Update update, CancellationToken ct = default) { - if (string.IsNullOrEmpty(user.CVUrl)) - { - await prepareUserService.SelectCv(update, ct); - } + if (string.IsNullOrEmpty(user.CVUrl)) await prepareUserService.SelectCv(update, ct); await menuService.RenderMenu(update, ct); //boilerplate } } \ No newline at end of file diff --git a/JOBot.TClient/TextResource.resx b/JOBot.TClient/TextResource.resx index 7bed097..f1fede4 100644 --- a/JOBot.TClient/TextResource.resx +++ b/JOBot.TClient/TextResource.resx @@ -1,9 +1,10 @@ - + - + @@ -13,10 +14,14 @@ 1.3 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + Продолжая, вы принимаете политику конфиденциальности и правила сервиса diff --git a/README.md b/README.md index 8ae4af9..e9e3cc2 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Telegram-бот для автоматизации поиска работы че ## 🔥 Возможности - Привяжите аккаунт HeadHunter для доступа к резюме и вакансиям. +Привяжите аккаунт HeadHunter для доступа к резюме и вакансиям. - **Отклик по предпочтениям** @@ -16,7 +16,7 @@ Telegram-бот для автоматизации поиска работы че - **Проверка резюме** Правка резюме, под ваши приоритеты. -- **Аналитика** +- **Аналитика** Статистика по откликам и приглашениям. @@ -25,36 +25,42 @@ Telegram-бот для автоматизации поиска работы че - **Backend**: .NET 8 + gRPC - **Frontend** Telegram клиент через Telegram.Bot - **БД**: PostgreSQL -- **Авторизация**: OAuth 2.0 (HH API), Telegram API +- **Авторизация**: OAuth 2.0 (HH API), Telegram API - **Инфраструктура**: Docker / Docker compose ## 🚀 Запуск ### Требования + - .NET 8 SDK - Docker (для работы с БД) - Аккаунт разработчика на [dev.hh.ru](https://dev.hh.ru) ### 1. Настройка конфигурации + Создайте `appsettings.json` в `JOBot.TClient` на основе `appsettings.Example.json` ### 2. Запуск через Docker + ``` docker-compose up -d --build ``` ## 📌 Команды бота -|Команда|Описание| -|-------|--------| -|/start |Начало работы| -|/menu|Настройка откликов и резюме по предпочтениям| -|/info|Информация о боте| + +| Команда | Описание | +|---------|----------------------------------------------| +| /start | Начало работы | +| /menu | Настройка откликов и резюме по предпочтениям | +| /info | Информация о боте | 📄 Лицензия MIT License. Подробнее в файле LICENSE. ## Отказ от ответственности -Продолжая, вы принимаете [политику конфиденциальности](https://hh.ru/article/personal_data?backurl=%2F&role=applicant) и [правила сервиса](https://hh.ru/account/agreement?backurl=%2Faccount%2Fsignup%3Fbackurl%3D%252F%26role%3Dapplicant&role=applicant) hh.ru +Продолжая, вы принимаете [политику конфиденциальности](https://hh.ru/article/personal_data?backurl=%2F&role=applicant) +и [правила сервиса](https://hh.ru/account/agreement?backurl=%2Faccount%2Fsignup%3Fbackurl%3D%252F%26role%3Dapplicant&role=applicant) +hh.ru Этот сервис никак не связан с HeadHunter™ (далее hh.ru), не выдаёт себя за hh.ru и не является hh.ru \ No newline at end of file diff --git a/compose.dev.yml b/compose.dev.yml index 3ff879e..d21dad1 100644 --- a/compose.dev.yml +++ b/compose.dev.yml @@ -45,7 +45,7 @@ services: networks: jobot: - + volumes: postgres_data: rabbitmq_data: \ No newline at end of file diff --git a/compose.yml b/compose.yml index c6f70c9..dbb502e 100644 --- a/compose.yml +++ b/compose.yml @@ -11,7 +11,7 @@ services: image: postgres:15 environment: POSTGRES_PASSWORD: LocalDbPass - POSTGRES_DB: jobot + POSTGRES_DB: jobot volumes: - postgres_data:/var/lib/postgresql/data networks: @@ -40,7 +40,7 @@ services: networks: jobot: - + volumes: postgres_data: \ No newline at end of file From d2280cdc5bfebd3522b9260e9f9c38bc1d9c4909 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Fri, 25 Jul 2025 16:39:22 +0300 Subject: [PATCH 09/14] chore: cleanup --- JOBot.Backend/Controllers/HeadHunterHookController.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/JOBot.Backend/Controllers/HeadHunterHookController.cs b/JOBot.Backend/Controllers/HeadHunterHookController.cs index 4b630ae..60ef630 100644 --- a/JOBot.Backend/Controllers/HeadHunterHookController.cs +++ b/JOBot.Backend/Controllers/HeadHunterHookController.cs @@ -14,11 +14,9 @@ public class HeadHunterHookController(HeadHunterService hhService) var res = await hhService.AuthUser(userId, code, error); return res switch { - HeadHunterService.Status.Success => Ok( - "Авторизация завершена успешно. Вернитесь в Telegram для продолжения."), + HeadHunterService.Status.Success => Ok("Авторизация завершена успешно. Вернитесь в Telegram для продолжения."), HeadHunterService.Status.UserNotFoundError => NotFound("Пользователь не найден."), - _ => BadRequest( - "Авторизация завершена с ошибкой. Вернитесь в Telegram для продолжения.") //TODO: Add resource + _ => BadRequest("Авторизация завершена с ошибкой. Вернитесь в Telegram для продолжения.") //TODO: Add resource }; } } \ No newline at end of file From f1157607131716736605a127e1b1da4d9d84379b Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Fri, 25 Jul 2025 17:12:10 +0300 Subject: [PATCH 10/14] feat: added auth hook using RabbitMQ on client for login continue --- JOBot.Backend/JOBot.Backend.csproj | 4 +++ JOBot.Backend/Services/HeadHunterService.cs | 1 + JOBot.Backend/Startup.cs | 10 +++++-- .../Config/RabbitQueues.cs | 2 +- .../JOBot.Infrastructure.csproj | 9 +++++++ JOBot.TClient/JOBot.TClient.csproj | 4 +++ JOBot.TClient/Queues/AuthQueue.cs | 26 +++++++++++++++++++ JOBot.TClient/Services/PrepareUserService.cs | 13 +++++++++- .../{DependencyInjection.cs => Startup.cs} | 23 +++++++++++++++- JOBot.sln | 14 ++++++++++ 10 files changed, 101 insertions(+), 5 deletions(-) rename {JOBot.Backend/Infrastructure => JOBot.Infrastructure}/Config/RabbitQueues.cs (63%) create mode 100644 JOBot.Infrastructure/JOBot.Infrastructure.csproj create mode 100644 JOBot.TClient/Queues/AuthQueue.cs rename JOBot.TClient/{DependencyInjection.cs => Startup.cs} (75%) diff --git a/JOBot.Backend/JOBot.Backend.csproj b/JOBot.Backend/JOBot.Backend.csproj index 6526b76..f814b31 100644 --- a/JOBot.Backend/JOBot.Backend.csproj +++ b/JOBot.Backend/JOBot.Backend.csproj @@ -31,4 +31,8 @@ + + + + \ No newline at end of file diff --git a/JOBot.Backend/Services/HeadHunterService.cs b/JOBot.Backend/Services/HeadHunterService.cs index 8ff1787..3395a22 100644 --- a/JOBot.Backend/Services/HeadHunterService.cs +++ b/JOBot.Backend/Services/HeadHunterService.cs @@ -3,6 +3,7 @@ using System.Text.Json; using JOBot.Backend.DAL.Context; using JOBot.Backend.DTOs.HeadHunterHook; using JOBot.Backend.Infrastructure.Config; +using JOBot.Infrastructure.Config; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using RabbitMQ.Client; diff --git a/JOBot.Backend/Startup.cs b/JOBot.Backend/Startup.cs index 6890d1e..957d184 100644 --- a/JOBot.Backend/Startup.cs +++ b/JOBot.Backend/Startup.cs @@ -2,6 +2,7 @@ using JOBot.Backend.DAL.Context; using JOBot.Backend.Infrastructure.Config; using JOBot.Backend.Services; using JOBot.Backend.Services.gRPC; +using JOBot.Infrastructure.Config; using Microsoft.EntityFrameworkCore; using RabbitMQ.Client; @@ -22,8 +23,13 @@ public class Startup(IConfiguration configuration) { HostName = "jobot-rabbitmq" }.CreateConnectionAsync(); - await using var channel = await rabbitMqConnection.CreateChannelAsync(); - await channel.QueueDeclareAsync(RabbitQueues.AuthQueue, false, false, false); + var channel = await rabbitMqConnection.CreateChannelAsync(); + await channel.QueueDeclareAsync( + RabbitQueues.AuthQueue, + false, + false, + false, + arguments: null); services.AddSingleton(channel); services.AddDbContext(options => diff --git a/JOBot.Backend/Infrastructure/Config/RabbitQueues.cs b/JOBot.Infrastructure/Config/RabbitQueues.cs similarity index 63% rename from JOBot.Backend/Infrastructure/Config/RabbitQueues.cs rename to JOBot.Infrastructure/Config/RabbitQueues.cs index cc8de04..0525bdc 100644 --- a/JOBot.Backend/Infrastructure/Config/RabbitQueues.cs +++ b/JOBot.Infrastructure/Config/RabbitQueues.cs @@ -1,4 +1,4 @@ -namespace JOBot.Backend.Infrastructure.Config; +namespace JOBot.Infrastructure.Config; public static class RabbitQueues { diff --git a/JOBot.Infrastructure/JOBot.Infrastructure.csproj b/JOBot.Infrastructure/JOBot.Infrastructure.csproj new file mode 100644 index 0000000..17b910f --- /dev/null +++ b/JOBot.Infrastructure/JOBot.Infrastructure.csproj @@ -0,0 +1,9 @@ + + + + net9.0 + enable + enable + + + diff --git a/JOBot.TClient/JOBot.TClient.csproj b/JOBot.TClient/JOBot.TClient.csproj index 5bcfbc3..9bce854 100644 --- a/JOBot.TClient/JOBot.TClient.csproj +++ b/JOBot.TClient/JOBot.TClient.csproj @@ -64,4 +64,8 @@ + + + + diff --git a/JOBot.TClient/Queues/AuthQueue.cs b/JOBot.TClient/Queues/AuthQueue.cs new file mode 100644 index 0000000..e5b9498 --- /dev/null +++ b/JOBot.TClient/Queues/AuthQueue.cs @@ -0,0 +1,26 @@ +using System.Text; +using JOBot.Infrastructure.Config; +using JOBot.TClient.Services; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; + +namespace JOBot.TClient.Queues; + +public class AuthQueue +{ + private readonly PrepareUserService _prepareUserService; + public AuthQueue( + IChannel channel, PrepareUserService prepareUserService) + { + _prepareUserService = prepareUserService; + + var consumer = new AsyncEventingBasicConsumer(channel); + consumer.ReceivedAsync += OnDataReceivedAsync; + channel.BasicConsumeAsync(RabbitQueues.AuthQueue, autoAck: true, consumer: consumer); + } + + private async Task OnDataReceivedAsync(object sender, BasicDeliverEventArgs eventArgs) + { + await _prepareUserService.SelectCv(Convert.ToInt64(Encoding.UTF8.GetString(eventArgs.Body.ToArray()))); + } +} \ No newline at end of file diff --git a/JOBot.TClient/Services/PrepareUserService.cs b/JOBot.TClient/Services/PrepareUserService.cs index 4c6a71b..df61817 100644 --- a/JOBot.TClient/Services/PrepareUserService.cs +++ b/JOBot.TClient/Services/PrepareUserService.cs @@ -99,11 +99,22 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie cancellationToken: ct); } + public async Task AuthHookReceived(long userId, CancellationToken ct = default) + { + await _bot.SendMessage(userId, "Авторизация завершена успешно!", cancellationToken: ct); + await SelectCv(userId, ct); + } + public async Task SelectCv(Update update, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(update.Message?.From); + + await SelectCv(update.Message.From.Id, ct); + } - await _bot.SendMessage(update.Message.From.Id, "Давайте выберем одно из доступных резюме:", + public async Task SelectCv(long userId, CancellationToken ct = default) + { + await _bot.SendMessage(userId, "Давайте выберем одно из доступных резюме:", cancellationToken: ct); //TODO: https://git.lisoveliy.su/Lisoveliy/JOBot/issues/9 } } \ No newline at end of file diff --git a/JOBot.TClient/DependencyInjection.cs b/JOBot.TClient/Startup.cs similarity index 75% rename from JOBot.TClient/DependencyInjection.cs rename to JOBot.TClient/Startup.cs index 6594372..23630fd 100644 --- a/JOBot.TClient/DependencyInjection.cs +++ b/JOBot.TClient/Startup.cs @@ -1,19 +1,23 @@ using Grpc.Core; using Grpc.Net.Client; +using JOBot.Infrastructure.Config; using JOBot.Proto; using JOBot.TClient.Commands.Buttons; using JOBot.TClient.Commands.Commands; using JOBot.TClient.Core.HostedServices; +using JOBot.TClient.Queues; using JOBot.TClient.Services; using JOBot.TClient.Statements; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; using Telegram.Bot; namespace JOBot.TClient; -public static class DependencyInjection +public static class Startup { public static IServiceCollection ConfigureServices(this IServiceCollection services, IConfiguration config) { @@ -53,6 +57,23 @@ public static class DependencyInjection #endregion + #region RabbitMQ Clients + + var factory = new ConnectionFactory { HostName = "localhost" }; + using var connection = factory.CreateConnectionAsync().Result; + var channel = connection.CreateChannelAsync().Result; + + channel.QueueDeclareAsync( + RabbitQueues.AuthQueue, + false, + false, + false, + arguments: null).Wait(); + + services.AddSingleton(); + + #endregion + // Bot service services.AddHostedService(); services.AddSingleton(_ => diff --git a/JOBot.sln b/JOBot.sln index e1513fe..0539a75 100644 --- a/JOBot.sln +++ b/JOBot.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JOBot.Backend", "JOBot.Back EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JOBot.TClient", "JOBot.TClient\JOBot.TClient.csproj", "{4526BCB1-DAD3-430C-BD7C-9C114DFE9A2A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JOBot.Infrastructure", "JOBot.Infrastructure\JOBot.Infrastructure.csproj", "{32006B71-E6F7-4264-A8B4-AC3A6B77CC54}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,6 +43,18 @@ Global {4526BCB1-DAD3-430C-BD7C-9C114DFE9A2A}.Release|x64.Build.0 = Release|Any CPU {4526BCB1-DAD3-430C-BD7C-9C114DFE9A2A}.Release|x86.ActiveCfg = Release|Any CPU {4526BCB1-DAD3-430C-BD7C-9C114DFE9A2A}.Release|x86.Build.0 = Release|Any CPU + {32006B71-E6F7-4264-A8B4-AC3A6B77CC54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32006B71-E6F7-4264-A8B4-AC3A6B77CC54}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32006B71-E6F7-4264-A8B4-AC3A6B77CC54}.Debug|x64.ActiveCfg = Debug|Any CPU + {32006B71-E6F7-4264-A8B4-AC3A6B77CC54}.Debug|x64.Build.0 = Debug|Any CPU + {32006B71-E6F7-4264-A8B4-AC3A6B77CC54}.Debug|x86.ActiveCfg = Debug|Any CPU + {32006B71-E6F7-4264-A8B4-AC3A6B77CC54}.Debug|x86.Build.0 = Debug|Any CPU + {32006B71-E6F7-4264-A8B4-AC3A6B77CC54}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32006B71-E6F7-4264-A8B4-AC3A6B77CC54}.Release|Any CPU.Build.0 = Release|Any CPU + {32006B71-E6F7-4264-A8B4-AC3A6B77CC54}.Release|x64.ActiveCfg = Release|Any CPU + {32006B71-E6F7-4264-A8B4-AC3A6B77CC54}.Release|x64.Build.0 = Release|Any CPU + {32006B71-E6F7-4264-A8B4-AC3A6B77CC54}.Release|x86.ActiveCfg = Release|Any CPU + {32006B71-E6F7-4264-A8B4-AC3A6B77CC54}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 970526d84e36bad352387301b89a8cdc8e709873 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Fri, 25 Jul 2025 18:35:59 +0300 Subject: [PATCH 11/14] feat: created auth hook on TClient --- JOBot.Backend/Services/HeadHunterService.cs | 78 +++++++++++-------- JOBot.Backend/Startup.cs | 4 +- .../Commands/Commands/InfoCommand.cs | 3 +- .../HostedServices/BotBackgroundService.cs | 2 +- .../Exceptions/FallbackException.cs | 3 +- .../Extensions/TelegramBotClientExtensions.cs | 52 +++++++++++++ JOBot.TClient/Queues/AuthQueue.cs | 22 +++--- JOBot.TClient/Services/MenuService.cs | 3 +- JOBot.TClient/Services/PrepareUserService.cs | 11 +-- JOBot.TClient/Startup.cs | 27 +++---- compose.dev.yml | 3 + compose.yml | 4 +- 12 files changed, 141 insertions(+), 71 deletions(-) create mode 100644 JOBot.TClient/Infrastructure/Extensions/TelegramBotClientExtensions.cs diff --git a/JOBot.Backend/Services/HeadHunterService.cs b/JOBot.Backend/Services/HeadHunterService.cs index 3395a22..c155837 100644 --- a/JOBot.Backend/Services/HeadHunterService.cs +++ b/JOBot.Backend/Services/HeadHunterService.cs @@ -14,6 +14,7 @@ public class HeadHunterService( IChannel channel, ILogger logger, IOptions config, + IWebHostEnvironment env, AppDbContext dbContext) { public enum Status @@ -47,45 +48,54 @@ public class HeadHunterService( return Status.UserAuthRejectedError; } - using var client = new HttpClient(); - var form = new Dictionary + HeadHunterTokenResponseDto? responseDto; + if (!env.IsDevelopment()) //Production server { - { "client_id", _config.ClientId }, - { "client_secret", _config.Secret }, - { "code", authorizationCode }, - { "grant_type", "authorization_code" }, - { "redirect_uri", GetRedirectUrl(userId) } - }; - client.BaseAddress = new UriBuilder(_config.Links.HeadHunterApiDomain) - { - Port = -1, - Scheme = "https" - }.Uri; - client.DefaultRequestHeaders.UserAgent.ParseAdd("Jobot BackEnd Service"); - - using var res = await client.SendAsync( - new HttpRequestMessage( - HttpMethod.Post, - _config.Links.HeadHunterTokenRoute) + using var client = new HttpClient(); + var form = new Dictionary { - Content = new FormUrlEncodedContent(form) - }); + { "client_id", _config.ClientId }, + { "client_secret", _config.Secret }, + { "code", authorizationCode }, + { "grant_type", "authorization_code" }, + { "redirect_uri", GetRedirectUrl(userId) } + }; + client.BaseAddress = new UriBuilder(_config.Links.HeadHunterApiDomain) + { + Port = -1, + Scheme = "https" + }.Uri; + client.DefaultRequestHeaders.UserAgent.ParseAdd("Jobot BackEnd Service"); - if (!res.IsSuccessStatusCode) - { - logger.LogWarning($"Response of HttpRequest {_config.Links.HeadHunterApiDomain}" + - $"{_config.Links.HeadHunterTokenRoute} has unsuccessful status code {res.StatusCode}"); - logger.LogWarning($"{res.Content.ReadAsStringAsync().Result}"); - return Status.HeadHunterAuthRejectedError; + using var res = await client.SendAsync( + new HttpRequestMessage( + HttpMethod.Post, + _config.Links.HeadHunterTokenRoute) + { + Content = new FormUrlEncodedContent(form) + }); + + if (!res.IsSuccessStatusCode) + { + logger.LogWarning($"Response of HttpRequest {_config.Links.HeadHunterApiDomain}" + + $"{_config.Links.HeadHunterTokenRoute} has unsuccessful status code {res.StatusCode}"); + logger.LogWarning($"{res.Content.ReadAsStringAsync().Result}"); + return Status.HeadHunterAuthRejectedError; + } + + responseDto = + JsonSerializer.Deserialize(await res.Content.ReadAsStringAsync()); + + if (responseDto == null) + { + logger.LogWarning($"User {userId} auth completed with error " + + $"{nameof(Status.HeadHunterResponseDeserializationFailedError)}"); + return Status.HeadHunterResponseDeserializationFailedError; + } } - - var responseDto = JsonSerializer.Deserialize(await res.Content.ReadAsStringAsync()); - - if (responseDto == null) + else { - logger.LogWarning($"User {userId} auth completed with error " + - $"{nameof(Status.HeadHunterResponseDeserializationFailedError)}"); - return Status.HeadHunterResponseDeserializationFailedError; + responseDto = new HeadHunterTokenResponseDto("testtoken", 0, "testtoken", "--"); } var user = await dbContext.Users.FirstOrDefaultAsync(x => x.UserId == userId); diff --git a/JOBot.Backend/Startup.cs b/JOBot.Backend/Startup.cs index 957d184..9cb2137 100644 --- a/JOBot.Backend/Startup.cs +++ b/JOBot.Backend/Startup.cs @@ -19,9 +19,9 @@ public class Startup(IConfiguration configuration) services.AddControllers(); services.AddLogging(); - await using var rabbitMqConnection = await new ConnectionFactory + var rabbitMqConnection = await new ConnectionFactory { - HostName = "jobot-rabbitmq" + HostName = "rabbitmq" }.CreateConnectionAsync(); var channel = await rabbitMqConnection.CreateChannelAsync(); await channel.QueueDeclareAsync( diff --git a/JOBot.TClient/Commands/Commands/InfoCommand.cs b/JOBot.TClient/Commands/Commands/InfoCommand.cs index cc2a653..2375337 100644 --- a/JOBot.TClient/Commands/Commands/InfoCommand.cs +++ b/JOBot.TClient/Commands/Commands/InfoCommand.cs @@ -1,3 +1,4 @@ +using JOBot.TClient.Infrastructure.Extensions; using Telegram.Bot; using Telegram.Bot.Types; @@ -9,6 +10,6 @@ public class InfoCommand(ITelegramBotClient bot) : ITelegramCommand { ArgumentNullException.ThrowIfNull(update.Message?.From); - await bot.SendMessage(update.Message.From.Id, TextResource.Info, cancellationToken: ct); + await bot.SendMessageRemK(update.Message.From.Id, TextResource.Info, cancellationToken: ct); } } \ No newline at end of file diff --git a/JOBot.TClient/Core/HostedServices/BotBackgroundService.cs b/JOBot.TClient/Core/HostedServices/BotBackgroundService.cs index 3006f7e..a063b8f 100644 --- a/JOBot.TClient/Core/HostedServices/BotBackgroundService.cs +++ b/JOBot.TClient/Core/HostedServices/BotBackgroundService.cs @@ -77,7 +77,7 @@ public sealed class BotBackgroundService( return; } - await bot.SendMessage(update.Message.From.Id, TextResource.CommandNotFound, cancellationToken: ct); + await bot.SendMessageRemK(update.Message.From.Id, TextResource.CommandNotFound, cancellationToken: ct); } } diff --git a/JOBot.TClient/Infrastructure/Exceptions/FallbackException.cs b/JOBot.TClient/Infrastructure/Exceptions/FallbackException.cs index afe8459..1a765e9 100644 --- a/JOBot.TClient/Infrastructure/Exceptions/FallbackException.cs +++ b/JOBot.TClient/Infrastructure/Exceptions/FallbackException.cs @@ -1,3 +1,4 @@ +using JOBot.TClient.Infrastructure.Extensions; using Telegram.Bot; using Telegram.Bot.Types; @@ -11,7 +12,7 @@ public class FallbackException : Exception { public FallbackException(string message, Message botMessage, ITelegramBotClient botClient) : base(message) { - botClient.SendMessage(botMessage.Chat.Id, + botClient.SendMessageRemK(botMessage.Chat.Id, TextResource.FallbackMessage); } } \ No newline at end of file diff --git a/JOBot.TClient/Infrastructure/Extensions/TelegramBotClientExtensions.cs b/JOBot.TClient/Infrastructure/Extensions/TelegramBotClientExtensions.cs new file mode 100644 index 0000000..b713e49 --- /dev/null +++ b/JOBot.TClient/Infrastructure/Extensions/TelegramBotClientExtensions.cs @@ -0,0 +1,52 @@ +using Telegram.Bot; +using Telegram.Bot.Types; +using Telegram.Bot.Types.Enums; +using Telegram.Bot.Types.ReplyMarkups; + +namespace JOBot.TClient.Infrastructure.Extensions; + +public static class TelegramBotClientExtensions +{ + /// + /// Extension method for auto-remove of reply keyboard if is null + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static Task SendMessageRemK( + this ITelegramBotClient bot, + ChatId chatId, + string text, + ParseMode parseMode = default, + ReplyParameters? replyParameters = null, + ReplyMarkup? replyMarkup = null, + LinkPreviewOptions? linkPreviewOptions = null, + int? messageThreadId = null, + IEnumerable? entities = null, + bool disableNotification = false, + bool protectContent = false, + string? messageEffectId = null, + string? businessConnectionId = null, + bool allowPaidBroadcast = false, + CancellationToken cancellationToken = default) + { + replyMarkup ??= new ReplyKeyboardRemove(); + + return bot.SendMessage(chatId, text, parseMode, replyParameters, replyMarkup, linkPreviewOptions, messageThreadId, + entities, disableNotification, protectContent, messageEffectId, businessConnectionId, allowPaidBroadcast, + cancellationToken); + } +} \ No newline at end of file diff --git a/JOBot.TClient/Queues/AuthQueue.cs b/JOBot.TClient/Queues/AuthQueue.cs index e5b9498..d3e1fc1 100644 --- a/JOBot.TClient/Queues/AuthQueue.cs +++ b/JOBot.TClient/Queues/AuthQueue.cs @@ -1,26 +1,24 @@ using System.Text; using JOBot.Infrastructure.Config; using JOBot.TClient.Services; +using Microsoft.Extensions.Hosting; using RabbitMQ.Client; using RabbitMQ.Client.Events; namespace JOBot.TClient.Queues; -public class AuthQueue +public class AuthQueue(IChannel channel, PrepareUserService prepareUserService) : BackgroundService { - private readonly PrepareUserService _prepareUserService; - public AuthQueue( - IChannel channel, PrepareUserService prepareUserService) - { - _prepareUserService = prepareUserService; - - var consumer = new AsyncEventingBasicConsumer(channel); - consumer.ReceivedAsync += OnDataReceivedAsync; - channel.BasicConsumeAsync(RabbitQueues.AuthQueue, autoAck: true, consumer: consumer); - } private async Task OnDataReceivedAsync(object sender, BasicDeliverEventArgs eventArgs) { - await _prepareUserService.SelectCv(Convert.ToInt64(Encoding.UTF8.GetString(eventArgs.Body.ToArray()))); + await prepareUserService.AuthHookReceived(Convert.ToInt64(Encoding.UTF8.GetString(eventArgs.Body.ToArray()))); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + var consumer = new AsyncEventingBasicConsumer(channel); + consumer.ReceivedAsync += OnDataReceivedAsync; + await channel.BasicConsumeAsync(RabbitQueues.AuthQueue, autoAck: true, consumer: consumer, cancellationToken: stoppingToken); } } \ No newline at end of file diff --git a/JOBot.TClient/Services/MenuService.cs b/JOBot.TClient/Services/MenuService.cs index 4f2e4d1..bdfd891 100644 --- a/JOBot.TClient/Services/MenuService.cs +++ b/JOBot.TClient/Services/MenuService.cs @@ -1,3 +1,4 @@ +using JOBot.TClient.Infrastructure.Extensions; using Telegram.Bot; using Telegram.Bot.Types; @@ -9,6 +10,6 @@ public class MenuService(ITelegramBotClient bot) { ArgumentNullException.ThrowIfNull(update.Message?.From); - await bot.SendMessage(update.Message.From.Id, "PrepareUser stage is done.", cancellationToken: ct); + await bot.SendMessageRemK(update.Message.From.Id, "PrepareUser stage is done.", cancellationToken: ct); } } \ No newline at end of file diff --git a/JOBot.TClient/Services/PrepareUserService.cs b/JOBot.TClient/Services/PrepareUserService.cs index df61817..55898cd 100644 --- a/JOBot.TClient/Services/PrepareUserService.cs +++ b/JOBot.TClient/Services/PrepareUserService.cs @@ -1,5 +1,6 @@ using JOBot.Proto; using JOBot.TClient.Infrastructure.Exceptions; +using JOBot.TClient.Infrastructure.Extensions; using Telegram.Bot; using Telegram.Bot.Types; using User = JOBot.Proto.User; @@ -31,7 +32,7 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie if (!result.Success) { - await _bot.SendMessage(update.Message.Chat.Id, + await _bot.SendMessageRemK(update.Message.Chat.Id, TextResource.FallbackMessage, cancellationToken: ct); @@ -59,7 +60,7 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie { ArgumentNullException.ThrowIfNull(update.Message?.From); - await _bot.SendMessage( + await _bot.SendMessageRemK( update.Message.From.Id, TextResource.EULA, replyMarkup: new[] { ButtonResource.EULAAgrement }, cancellationToken: ct); @@ -93,7 +94,7 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie UserId = update.Message.From.Id }); - await _bot.SendMessage( + await _bot.SendMessageRemK( update.Message.From.Id, string.Format(TextResource.AskForAuth, [url.RegistrationUrl]), cancellationToken: ct); @@ -101,7 +102,7 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie public async Task AuthHookReceived(long userId, CancellationToken ct = default) { - await _bot.SendMessage(userId, "Авторизация завершена успешно!", cancellationToken: ct); + await _bot.SendMessageRemK(userId, "✅ Авторизация завершена успешно!", cancellationToken: ct); await SelectCv(userId, ct); } @@ -114,7 +115,7 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie public async Task SelectCv(long userId, CancellationToken ct = default) { - await _bot.SendMessage(userId, "Давайте выберем одно из доступных резюме:", + await _bot.SendMessageRemK(userId, "Давайте выберем одно из доступных резюме:", cancellationToken: ct); //TODO: https://git.lisoveliy.su/Lisoveliy/JOBot/issues/9 } } \ No newline at end of file diff --git a/JOBot.TClient/Startup.cs b/JOBot.TClient/Startup.cs index 23630fd..fa3bdaa 100644 --- a/JOBot.TClient/Startup.cs +++ b/JOBot.TClient/Startup.cs @@ -23,17 +23,17 @@ public static class Startup { services.AddSingleton(config); - services.AddScoped(_ => GrpcChannel.ForAddress(config.GetValue("BackendHost") - ?? throw new MissingFieldException("Host is not defined"))); + services.AddSingleton(_ => GrpcChannel.ForAddress(config.GetValue("BackendHost") + ?? throw new MissingFieldException("Host is not defined"))); #region Commands - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); //buttons - services.AddScoped(); + services.AddSingleton(); #endregion @@ -46,21 +46,21 @@ public static class Startup #region Services services.AddSingleton(); - services.AddScoped(); - services.AddScoped(); + services.AddSingleton(); + services.AddSingleton(); #endregion #region States - services.AddScoped(); + services.AddSingleton(); #endregion #region RabbitMQ Clients - var factory = new ConnectionFactory { HostName = "localhost" }; - using var connection = factory.CreateConnectionAsync().Result; + var factory = new ConnectionFactory { HostName = "rabbitmq" }; + var connection = factory.CreateConnectionAsync().Result; var channel = connection.CreateChannelAsync().Result; channel.QueueDeclareAsync( @@ -69,8 +69,9 @@ public static class Startup false, false, arguments: null).Wait(); - - services.AddSingleton(); + + services.AddSingleton(channel); + services.AddHostedService(); #endregion diff --git a/compose.dev.yml b/compose.dev.yml index d21dad1..daa1ca1 100644 --- a/compose.dev.yml +++ b/compose.dev.yml @@ -9,6 +9,8 @@ services: - "15672:15672" volumes: - rabbitmq_data:/var/lib/rabbitmq/ + networks: + - jobot postgres: image: postgres:15 @@ -28,6 +30,7 @@ services: dockerfile: JOBot.Backend/Dockerfile depends_on: - postgres + - rabbitmq ports: - "5000:5000" - "5001:5001" diff --git a/compose.yml b/compose.yml index dbb502e..d737d73 100644 --- a/compose.yml +++ b/compose.yml @@ -6,6 +6,8 @@ services: image: rabbitmq:4 volumes: - rabbitmq_data:/var/lib/rabbitmq/ + networks: + - jobot postgres: image: postgres:15 @@ -23,7 +25,7 @@ services: dockerfile: JOBot.Backend/Dockerfile depends_on: - postgres - - image + - rabbitmq ports: - "5000:5000" networks: From 17cec6dd643071dadd11688cd16513c7ffff9fa7 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Fri, 25 Jul 2025 19:04:38 +0300 Subject: [PATCH 12/14] fix: removed async in lock tasks --- JOBot.Backend/Program.cs | 2 +- JOBot.Backend/Startup.cs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/JOBot.Backend/Program.cs b/JOBot.Backend/Program.cs index 6e070ab..95467a3 100644 --- a/JOBot.Backend/Program.cs +++ b/JOBot.Backend/Program.cs @@ -4,7 +4,7 @@ var builder = WebApplication.CreateBuilder(args); var startup = new Startup(builder.Configuration); -await startup.ConfigureServices(builder.Services); +startup.ConfigureServices(builder.Services); var app = builder.Build(); startup.Configure(app, app.Environment); diff --git a/JOBot.Backend/Startup.cs b/JOBot.Backend/Startup.cs index 9cb2137..3c6c2ab 100644 --- a/JOBot.Backend/Startup.cs +++ b/JOBot.Backend/Startup.cs @@ -12,29 +12,29 @@ public class Startup(IConfiguration configuration) { private IConfiguration Configuration { get; } = configuration; - public async Task ConfigureServices(IServiceCollection services) + public void ConfigureServices(IServiceCollection services) { services.AddGrpc(); services.AddGrpcReflection(); services.AddControllers(); services.AddLogging(); + + services.AddDbContext(options => + options.UseNpgsql(Configuration.GetConnectionString("PostgreSQL"))); - var rabbitMqConnection = await new ConnectionFactory + var rabbitMqConnection = new ConnectionFactory { HostName = "rabbitmq" - }.CreateConnectionAsync(); - var channel = await rabbitMqConnection.CreateChannelAsync(); - await channel.QueueDeclareAsync( + }.CreateConnectionAsync().Result; + var channel = rabbitMqConnection.CreateChannelAsync().Result; + channel.QueueDeclareAsync( RabbitQueues.AuthQueue, false, false, false, - arguments: null); + arguments: null).Wait(); services.AddSingleton(channel); - services.AddDbContext(options => - options.UseNpgsql(Configuration.GetConnectionString("PostgreSQL"))); - services.Configure(Configuration.GetSection(HeadHunterConfig.SectionName)); services.AddScoped(); From 176cf194cda88e58958da5850f45ee5262af4777 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Fri, 25 Jul 2025 19:43:14 +0300 Subject: [PATCH 13/14] fix: fixed migration by lazy load of rabbitmq --- JOBot.Backend/Startup.cs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/JOBot.Backend/Startup.cs b/JOBot.Backend/Startup.cs index 3c6c2ab..38366f5 100644 --- a/JOBot.Backend/Startup.cs +++ b/JOBot.Backend/Startup.cs @@ -22,18 +22,22 @@ public class Startup(IConfiguration configuration) services.AddDbContext(options => options.UseNpgsql(Configuration.GetConnectionString("PostgreSQL"))); - var rabbitMqConnection = new ConnectionFactory + + services.AddSingleton(x => { - HostName = "rabbitmq" - }.CreateConnectionAsync().Result; - var channel = rabbitMqConnection.CreateChannelAsync().Result; - channel.QueueDeclareAsync( - RabbitQueues.AuthQueue, - false, - false, - false, - arguments: null).Wait(); - services.AddSingleton(channel); + var rabbitMqConnection = new ConnectionFactory + { + HostName = "rabbitmq" + }.CreateConnectionAsync().Result; + var channel = rabbitMqConnection.CreateChannelAsync().Result; + channel.QueueDeclareAsync( + RabbitQueues.AuthQueue, + false, + false, + false, + arguments: null).Wait(); + return channel; + }); services.Configure(Configuration.GetSection(HeadHunterConfig.SectionName)); From 0c6b8705eecef74883fa732e16180f21c41f3f41 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Fri, 25 Jul 2025 19:44:51 +0300 Subject: [PATCH 14/14] fix: fixed production compose file --- compose.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compose.yml b/compose.yml index d737d73..bb1fd90 100644 --- a/compose.yml +++ b/compose.yml @@ -45,4 +45,5 @@ networks: volumes: - postgres_data: \ No newline at end of file + postgres_data: + rabbitmq_data: \ No newline at end of file