From dee7942f2008920924757818849faabeae4eb5e3 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Sat, 12 Jul 2025 19:51:34 +0300 Subject: [PATCH 01/20] feat: added implementation of HeadHunter hook for user registration --- .../Controllers/HeadHunterHookController.cs | 23 ++++ .../HeadHunterTokenResponseDto.cs | 14 +++ .../Infrastructure/Config/HeadHunterConfig.cs | 18 +++ JOBot.Backend/JOBot.Backend.csproj | 1 + JOBot.Backend/Services/HeadHunterService.cs | 106 ++++++++++++++++++ JOBot.Backend/Startup.cs | 18 +-- JOBot.Backend/appsettings.json | 11 ++ 7 files changed, 184 insertions(+), 7 deletions(-) create mode 100644 JOBot.Backend/Controllers/HeadHunterHookController.cs create mode 100644 JOBot.Backend/DTOs/HeadHunterHook/HeadHunterTokenResponseDto.cs create mode 100644 JOBot.Backend/Infrastructure/Config/HeadHunterConfig.cs create mode 100644 JOBot.Backend/Services/HeadHunterService.cs diff --git a/JOBot.Backend/Controllers/HeadHunterHookController.cs b/JOBot.Backend/Controllers/HeadHunterHookController.cs new file mode 100644 index 0000000..27e9310 --- /dev/null +++ b/JOBot.Backend/Controllers/HeadHunterHookController.cs @@ -0,0 +1,23 @@ +using JOBot.Backend.Services; +using Microsoft.AspNetCore.Mvc; + +namespace JOBot.Backend.Controllers; + +[ApiController] +[Route("auth")] +public class HeadHunterHookController(HeadHunterService hhService) + : ControllerBase +{ + + [HttpGet] + public async Task Get(int userId, string? error, string authorizationCode) + { + var res = await hhService.AuthUser(userId, authorizationCode, error); + return res switch + { + HeadHunterService.Status.Success => Ok("Авторизация завершена успешно. Вернитесь в Telegram для продолжения."), + HeadHunterService.Status.UserNotFoundError => NotFound("Пользователь не найден."), + _ => BadRequest("Авторизация завершена с ошибкой. Вернитесь в Telegram для продолжения.") //TODO: Add resource + }; + } +} \ No newline at end of file diff --git a/JOBot.Backend/DTOs/HeadHunterHook/HeadHunterTokenResponseDto.cs b/JOBot.Backend/DTOs/HeadHunterHook/HeadHunterTokenResponseDto.cs new file mode 100644 index 0000000..179cfb3 --- /dev/null +++ b/JOBot.Backend/DTOs/HeadHunterHook/HeadHunterTokenResponseDto.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace JOBot.Backend.DTOs.HeadHunterHook; + +public record HeadHunterTokenResponseDto( + [property:JsonPropertyName("access_token")] + string AccessToken, + [property:JsonPropertyName("expires_in")] + int ExpiresIn, + [property:JsonPropertyName("refresh_token")] + string RefreshToken, + [property:JsonPropertyName("token_type")] + string TokenType + ); \ No newline at end of file diff --git a/JOBot.Backend/Infrastructure/Config/HeadHunterConfig.cs b/JOBot.Backend/Infrastructure/Config/HeadHunterConfig.cs new file mode 100644 index 0000000..e4c1d15 --- /dev/null +++ b/JOBot.Backend/Infrastructure/Config/HeadHunterConfig.cs @@ -0,0 +1,18 @@ +namespace JOBot.Backend.Infrastructure.Config; + +public class HeadHunterConfig +{ + public const string SectionName = "HeadHunter"; + public required HeadHunterLinksConfig Links { get; init; } + public required string ClientId { get; init; } + public required string Secret { get; init; } + + public class HeadHunterLinksConfig + { + public required string AuthLink { get; init; } + public required string HookDomain { get; init; } + public required string HookRoute { get; init; } + public required string HeadHunterApiDomain { get; init; } + public required string HeadHunterTokenRoute { get; init; } + } +} \ No newline at end of file diff --git a/JOBot.Backend/JOBot.Backend.csproj b/JOBot.Backend/JOBot.Backend.csproj index 2c4b3c8..1f9a3c1 100644 --- a/JOBot.Backend/JOBot.Backend.csproj +++ b/JOBot.Backend/JOBot.Backend.csproj @@ -14,6 +14,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/JOBot.Backend/Services/HeadHunterService.cs b/JOBot.Backend/Services/HeadHunterService.cs new file mode 100644 index 0000000..80b3283 --- /dev/null +++ b/JOBot.Backend/Services/HeadHunterService.cs @@ -0,0 +1,106 @@ +using System.Text.Json; +using JOBot.Backend.DAL.Context; +using JOBot.Backend.DTOs.HeadHunterHook; +using JOBot.Backend.Infrastructure.Config; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; + +namespace JOBot.Backend.Services; + +public class HeadHunterService(ILogger logger, IOptions config, AppDbContext dbContext) +{ + private readonly HeadHunterConfig _config = config.Value; + + /// + /// Generate HeadHunter oauth authorization link + /// + /// Telegram UserId + /// Link for auth + public string GenerateAuthLink(int userId) + { + var redirectUri = new UriBuilder(_config.Links.HookDomain) + { + Port = -1, + Scheme = "https", + Path = _config.Links.HookRoute, + Query = $"?userId={userId}" + }.ToString(); + + return string.Format(_config.Links.AuthLink, [_config.ClientId, userId, redirectUri]); + } + + public async Task AuthUser(int userId, string authorizationCode, string? error) + { + logger.LogInformation($"Authorization for user {userId} in process..."); + + if (!string.IsNullOrEmpty(error)) + { + logger.LogWarning($"User {userId} auth completed with error {error}"); + return Status.UserAuthRejectedError; + } + + using var client = new HttpClient(); + var form = new Dictionary + { + { "client-id", _config.ClientId }, + { "client_secret", _config.Secret }, + { "code", authorizationCode }, + { "grant_type", "authorization_code" } + }; + 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) + { + Content = new FormUrlEncodedContent(form) + }); + + if (!res.IsSuccessStatusCode) + { + logger.LogWarning($"Response of HttpRequest ${_config.Links.HeadHunterApiDomain} " + + $"${_config.Links.HeadHunterTokenRoute} has unsuccessful status code {res.StatusCode}"); + return Status.HeadHunterAuthRejectedError; + } + + var 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 user = await dbContext.Users.FirstOrDefaultAsync(x => x.UserId == userId); + + if (user == null) + { + 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!"); + return Status.Success; + } + + public enum Status + { + UserAuthRejectedError, + HeadHunterAuthRejectedError, + UserNotFoundError, + HeadHunterResponseDeserializationFailedError, + Success + } +} \ No newline at end of file diff --git a/JOBot.Backend/Startup.cs b/JOBot.Backend/Startup.cs index 0186b3b..1295739 100644 --- a/JOBot.Backend/Startup.cs +++ b/JOBot.Backend/Startup.cs @@ -1,30 +1,34 @@ using JOBot.Backend.DAL.Context; +using JOBot.Backend.Infrastructure.Config; +using JOBot.Backend.Services; using JOBot.Backend.Services.gRPC; using Microsoft.EntityFrameworkCore; namespace JOBot.Backend; -public class Startup +public class Startup(IConfiguration configuration) { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } + private IConfiguration Configuration { get; } = configuration; public void ConfigureServices(IServiceCollection services) { services.AddGrpc(); services.AddGrpcReflection(); + services.AddControllers(); + services.AddLogging(); services.AddDbContext(options => options.UseNpgsql(Configuration.GetConnectionString("PostgreSQL"))); + + services.Configure(Configuration.GetSection(HeadHunterConfig.SectionName)); + + services.AddScoped(); } public void Configure(WebApplication app, IWebHostEnvironment env) { app.MapGrpcReflectionService().AllowAnonymous(); app.MapGrpcService(); + app.MapControllers(); } } \ No newline at end of file diff --git a/JOBot.Backend/appsettings.json b/JOBot.Backend/appsettings.json index 6bb04e6..6af15be 100644 --- a/JOBot.Backend/appsettings.json +++ b/JOBot.Backend/appsettings.json @@ -9,6 +9,17 @@ "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={client_id}&redirect_uri={redirect_uri}", + "HookDomain": "jobot.lisoveliy.su", + "HookRoute": "/auth", + "HeadHunterApiDomain": "api.hh.ru", + "HeadHunterTokenRoute": "/token" + }, + "ClientId": "", + "Secret": "" + }, "Kestrel": { "Endpoints": { "gRPC": { From f9a154be6fdcb71f6e3073fafc05e5fe8d168fe2 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Wed, 16 Jul 2025 19:48:17 +0300 Subject: [PATCH 02/20] feat: try to get appsettings.json for JOBot.TClient --- JOBot.TClient/JOBot.TClient.csproj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/JOBot.TClient/JOBot.TClient.csproj b/JOBot.TClient/JOBot.TClient.csproj index e8b6287..c5619d2 100644 --- a/JOBot.TClient/JOBot.TClient.csproj +++ b/JOBot.TClient/JOBot.TClient.csproj @@ -57,4 +57,11 @@ + + + + PreserveNewest + + + From 5b81ad2084f5890972649216cdc66e663b73a596 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Wed, 16 Jul 2025 19:48:30 +0300 Subject: [PATCH 03/20] chore: update csproj --- JOBot.TClient/JOBot.TClient.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/JOBot.TClient/JOBot.TClient.csproj b/JOBot.TClient/JOBot.TClient.csproj index c5619d2..1b2bd76 100644 --- a/JOBot.TClient/JOBot.TClient.csproj +++ b/JOBot.TClient/JOBot.TClient.csproj @@ -58,7 +58,6 @@ - PreserveNewest From 1d5b171479d4e41fb36f16fb44ba9ebfb5a4b2d5 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Wed, 16 Jul 2025 19:56:41 +0300 Subject: [PATCH 04/20] chore: fix csproj --- JOBot.TClient/JOBot.TClient.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/JOBot.TClient/JOBot.TClient.csproj b/JOBot.TClient/JOBot.TClient.csproj index 1b2bd76..987d35a 100644 --- a/JOBot.TClient/JOBot.TClient.csproj +++ b/JOBot.TClient/JOBot.TClient.csproj @@ -58,9 +58,9 @@ - + PreserveNewest - + From bae7a0267d093065cda121c0a356e8dd0a957595 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Wed, 16 Jul 2025 20:09:12 +0300 Subject: [PATCH 05/20] feat: updated Dockerfile --- JOBot.Backend/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/JOBot.Backend/Dockerfile b/JOBot.Backend/Dockerfile index 6073e57..da0d968 100644 --- a/JOBot.Backend/Dockerfile +++ b/JOBot.Backend/Dockerfile @@ -10,5 +10,4 @@ WORKDIR /app COPY --from=build /app . EXPOSE 5001 EXPOSE 5000 -ENV ASPNETCORE_ENVIRONMENT Staging ENTRYPOINT ["dotnet", "JOBot.Backend.dll"] \ No newline at end of file From fcdc4476df24c7073c01f70ae3401469f4070a38 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Wed, 16 Jul 2025 20:34:29 +0300 Subject: [PATCH 06/20] feat: added auth for telegram stage --- JOBot.Backend/Services/HeadHunterService.cs | 2 +- JOBot.Backend/Services/gRPC/UserService.cs | 13 ++++++++++++- .../Buttons/EulaAgreementButtonCommand.cs | 3 ++- JOBot.TClient/Services/MenuService.cs | 6 ++---- JOBot.TClient/Services/PrepareUserService.cs | 15 +++++++++++++++ JOBot.TClient/Statements/PrepareUserState.cs | 17 ++++++++++++----- JOBot.TClient/TextResource.Designer.cs | 9 +++++++++ JOBot.TClient/TextResource.resx | 3 +++ Proto/user.proto | 14 ++++++++++++-- 9 files changed, 68 insertions(+), 14 deletions(-) diff --git a/JOBot.Backend/Services/HeadHunterService.cs b/JOBot.Backend/Services/HeadHunterService.cs index 80b3283..2204a6c 100644 --- a/JOBot.Backend/Services/HeadHunterService.cs +++ b/JOBot.Backend/Services/HeadHunterService.cs @@ -16,7 +16,7 @@ public class HeadHunterService(ILogger logger, IOptions /// Telegram UserId /// Link for auth - public string GenerateAuthLink(int userId) + public string GenerateAuthLink(long userId) { var redirectUri = new UriBuilder(_config.Links.HookDomain) { diff --git a/JOBot.Backend/Services/gRPC/UserService.cs b/JOBot.Backend/Services/gRPC/UserService.cs index f694431..4a1cfe6 100644 --- a/JOBot.Backend/Services/gRPC/UserService.cs +++ b/JOBot.Backend/Services/gRPC/UserService.cs @@ -1,3 +1,4 @@ +using Google.Protobuf.WellKnownTypes; using Grpc.Core; using JOBot.Backend.DAL.Context; using JOBot.Backend.DAL.Models; @@ -7,7 +8,7 @@ using User = JOBot.Backend.DAL.Models.User; namespace JOBot.Backend.Services.gRPC; -public class UserService(AppDbContext dbContext) : Proto.User.UserBase +public class UserService(AppDbContext dbContext, HeadHunterService hhService) : Proto.User.UserBase { /// /// Create user @@ -62,6 +63,16 @@ public class UserService(AppDbContext dbContext) : Proto.User.UserBase return new AcceptEulaResponse { Success = true }; } + public override Task GetHeadHunterAuthHook( + GetHeadHunterAuthHookRequest request, + ServerCallContext context) + { + return Task.Run(() => new GetHeadHunterAuthHookResponse + { + RegistrationUrl = hhService.GenerateAuthLink(request.UserId) + }); + } + /// /// Throw RPCException if user not found /// diff --git a/JOBot.TClient/Commands/Buttons/EulaAgreementButtonCommand.cs b/JOBot.TClient/Commands/Buttons/EulaAgreementButtonCommand.cs index 87d6368..f28e9ab 100644 --- a/JOBot.TClient/Commands/Buttons/EulaAgreementButtonCommand.cs +++ b/JOBot.TClient/Commands/Buttons/EulaAgreementButtonCommand.cs @@ -12,6 +12,7 @@ public class EulaAgreementButtonCommand(PrepareUserState prepareUserState) : IAu { public async Task ExecuteAsync(Update update, GetUserResponse user, CancellationToken ct) { - await prepareUserState.AcceptEula(update, ct); + if (!user.Eula) + await prepareUserState.AcceptEula(user, update, ct); } } \ No newline at end of file diff --git a/JOBot.TClient/Services/MenuService.cs b/JOBot.TClient/Services/MenuService.cs index 9849490..e8ba02e 100644 --- a/JOBot.TClient/Services/MenuService.cs +++ b/JOBot.TClient/Services/MenuService.cs @@ -6,12 +6,10 @@ namespace JOBot.TClient.Services; public class MenuService(ITelegramBotClient bot) { - public Task RenderMenu(Update update, CancellationToken ct = default) + public async Task RenderMenu(Update update, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(update.Message?.From); - bot.SendMessage(update.Message.From.Id,"PrepareUser stage is done.", cancellationToken: ct); - - return Task.CompletedTask; + 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 4f2921e..941286d 100644 --- a/JOBot.TClient/Services/PrepareUserService.cs +++ b/JOBot.TClient/Services/PrepareUserService.cs @@ -83,4 +83,19 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie if (!result.Success) throw new FallbackException(TextResource.FallbackMessage, update.Message, _bot); } + + public async Task Auth(Update update, CancellationToken ct = default) + { + ArgumentNullException.ThrowIfNull(update.Message?.From); + + var url = await _userClient.GetHeadHunterAuthHookAsync(new GetHeadHunterAuthHookRequest() + { + UserId = update.Message.From.Id + }); + + await _bot.SendMessage( + update.Message.From.Id, + string.Format(TextResource.AskForAuth, [url]), + cancellationToken: ct); + } } \ No newline at end of file diff --git a/JOBot.TClient/Statements/PrepareUserState.cs b/JOBot.TClient/Statements/PrepareUserState.cs index 855ebe4..eb87901 100644 --- a/JOBot.TClient/Statements/PrepareUserState.cs +++ b/JOBot.TClient/Statements/PrepareUserState.cs @@ -1,3 +1,4 @@ +using JOBot.Proto; using JOBot.TClient.Services; using Telegram.Bot.Types; using User = JOBot.Proto.User; @@ -21,27 +22,33 @@ public class PrepareUserState(PrepareUserService prepareUserService, MenuService return; //interrupt while eula isn't accepted } - await OnUserEulaValidStage(update, ct); + await OnUserEulaValidStage(user, update, ct); } /// /// Signal for accepted eula /// + /// /// /// - public async Task AcceptEula(Update update, CancellationToken ct = default) + public async Task AcceptEula(GetUserResponse user, Update update, CancellationToken ct = default) { await prepareUserService.AcceptEula(update, ct: ct); - await OnUserEulaValidStage(update, ct); + await OnUserEulaValidStage(user, update, ct); } - + /// /// Continue prepare stage /// + /// /// /// - private async Task OnUserEulaValidStage(Update update, CancellationToken ct = default) + private async Task OnUserEulaValidStage(GetUserResponse user, Update update, CancellationToken ct = default) { + if (!user.IsLogged) + { + prepareUserService.Auth(update, ct); + } await menuService.RenderMenu(update, ct); //boilerplate } } \ No newline at end of file diff --git a/JOBot.TClient/TextResource.Designer.cs b/JOBot.TClient/TextResource.Designer.cs index 3104beb..088802e 100644 --- a/JOBot.TClient/TextResource.Designer.cs +++ b/JOBot.TClient/TextResource.Designer.cs @@ -59,6 +59,15 @@ namespace JOBot.TClient { } } + /// + /// Looks up a localized string similar to Авторизируйтесь на сайте HeadHunter для получения доступа к резюме и вакансиям {0}. + /// + public static string AskForAuth { + get { + return ResourceManager.GetString("AskForAuth", resourceCulture); + } + } + /// /// Looks up a localized string similar to Команда не найдена, попробуйте что-то другое. /// diff --git a/JOBot.TClient/TextResource.resx b/JOBot.TClient/TextResource.resx index 3d541c9..7bed097 100644 --- a/JOBot.TClient/TextResource.resx +++ b/JOBot.TClient/TextResource.resx @@ -34,4 +34,7 @@ https://hh.ru/account/agreement?backurl=%2Faccount%2Fsignup%3Fbackurl%3D%252F%26 Это бот для упрощения поиска работы на HH.ru + + Авторизируйтесь на сайте HeadHunter для получения доступа к резюме и вакансиям {0} + \ No newline at end of file diff --git a/Proto/user.proto b/Proto/user.proto index 98072c3..687316c 100644 --- a/Proto/user.proto +++ b/Proto/user.proto @@ -1,14 +1,16 @@ syntax = "proto3"; option csharp_namespace = "JOBot.Proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/empty.proto"; + service User { rpc Register (RegisterRequest) returns (RegisterResponse); rpc GetUser (GetUserRequest) returns (GetUserResponse); rpc AcceptEula (AcceptEulaRequest) returns (AcceptEulaResponse); + rpc GetHeadHunterAuthHook(GetHeadHunterAuthHookRequest) returns (GetHeadHunterAuthHookResponse); } -import "google/protobuf/wrappers.proto"; - message RegisterRequest{ int64 user_id = 1; google.protobuf.StringValue username = 2; @@ -37,4 +39,12 @@ message AcceptEulaRequest { message AcceptEulaResponse{ bool success = 1; +} + +message GetHeadHunterAuthHookRequest{ + int64 user_id = 1; +} + +message GetHeadHunterAuthHookResponse{ + string registration_url = 1; } \ No newline at end of file From 0826eb5898692f84a34c9eb01669917082184002 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Wed, 16 Jul 2025 20:36:30 +0300 Subject: [PATCH 07/20] fix: fixed stage --- JOBot.TClient/Statements/PrepareUserState.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/JOBot.TClient/Statements/PrepareUserState.cs b/JOBot.TClient/Statements/PrepareUserState.cs index eb87901..b989aec 100644 --- a/JOBot.TClient/Statements/PrepareUserState.cs +++ b/JOBot.TClient/Statements/PrepareUserState.cs @@ -47,7 +47,8 @@ public class PrepareUserState(PrepareUserService prepareUserService, MenuService { if (!user.IsLogged) { - prepareUserService.Auth(update, ct); + await prepareUserService.Auth(update, ct); + return; } await menuService.RenderMenu(update, ct); //boilerplate } From 60db714a3270bcc27ca306df14cb7eafb38da3fe Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Wed, 16 Jul 2025 20:37:33 +0300 Subject: [PATCH 08/20] fix: hotfix --- JOBot.TClient/Services/PrepareUserService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JOBot.TClient/Services/PrepareUserService.cs b/JOBot.TClient/Services/PrepareUserService.cs index 941286d..443f8b0 100644 --- a/JOBot.TClient/Services/PrepareUserService.cs +++ b/JOBot.TClient/Services/PrepareUserService.cs @@ -95,7 +95,7 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie await _bot.SendMessage( update.Message.From.Id, - string.Format(TextResource.AskForAuth, [url]), + string.Format(TextResource.AskForAuth, [url.RegistrationUrl]), cancellationToken: ct); } } \ No newline at end of file From 0dae5a2a134be36213a6ce6ccbfa08891c0d2070 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Wed, 16 Jul 2025 20:39:37 +0300 Subject: [PATCH 09/20] fix: fixed appsettings.json --- JOBot.Backend/appsettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JOBot.Backend/appsettings.json b/JOBot.Backend/appsettings.json index 6af15be..ae7b3b2 100644 --- a/JOBot.Backend/appsettings.json +++ b/JOBot.Backend/appsettings.json @@ -11,7 +11,7 @@ }, "HeadHunter": { "Links": { - "AuthLink": "https://hh.ru/oauth/authorize?response_type=code&client_id={client_id}&redirect_uri={redirect_uri}", + "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", From 362074faa401159f8c6dd47b3093aa19be46b621 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Wed, 16 Jul 2025 20:43:50 +0300 Subject: [PATCH 10/20] fix: fix of Uri construct for Auth link --- JOBot.Backend/Services/HeadHunterService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JOBot.Backend/Services/HeadHunterService.cs b/JOBot.Backend/Services/HeadHunterService.cs index 2204a6c..7812fc3 100644 --- a/JOBot.Backend/Services/HeadHunterService.cs +++ b/JOBot.Backend/Services/HeadHunterService.cs @@ -26,7 +26,7 @@ public class HeadHunterService(ILogger logger, IOptions AuthUser(int userId, string authorizationCode, string? error) From e82cc31b4ea89cf5ca32bf242a18a9daeefbfb2a Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Wed, 16 Jul 2025 20:48:44 +0300 Subject: [PATCH 11/20] fix: fix of docker container --- compose.dev.yml | 1 + compose.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/compose.dev.yml b/compose.dev.yml index 4bb2897..7770047 100644 --- a/compose.dev.yml +++ b/compose.dev.yml @@ -20,6 +20,7 @@ services: depends_on: - postgres ports: + - "5000:5000" - "5001:5001" networks: - jobot diff --git a/compose.yml b/compose.yml index da54fa4..5c87703 100644 --- a/compose.yml +++ b/compose.yml @@ -17,6 +17,8 @@ services: dockerfile: JOBot.Backend/Dockerfile depends_on: - postgres + ports: + - "5000:5000" networks: - jobot From 54393e2e0f82b06b5ed0547e2893c305fa68d638 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Wed, 16 Jul 2025 20:51:25 +0300 Subject: [PATCH 12/20] fix: fixed HeadHunter API Hook --- JOBot.Backend/Controllers/HeadHunterHookController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/JOBot.Backend/Controllers/HeadHunterHookController.cs b/JOBot.Backend/Controllers/HeadHunterHookController.cs index 27e9310..1ba1e24 100644 --- a/JOBot.Backend/Controllers/HeadHunterHookController.cs +++ b/JOBot.Backend/Controllers/HeadHunterHookController.cs @@ -10,9 +10,9 @@ public class HeadHunterHookController(HeadHunterService hhService) { [HttpGet] - public async Task Get(int userId, string? error, string authorizationCode) + public async Task Get(int userId, string? error, string code) { - var res = await hhService.AuthUser(userId, authorizationCode, error); + var res = await hhService.AuthUser(userId, code, error); return res switch { HeadHunterService.Status.Success => Ok("Авторизация завершена успешно. Вернитесь в Telegram для продолжения."), From e4c7df9714955b090d9f7007505ee5a973b9f806 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Wed, 16 Jul 2025 20:58:01 +0300 Subject: [PATCH 13/20] chore: added debug content stream --- JOBot.Backend/Services/HeadHunterService.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/JOBot.Backend/Services/HeadHunterService.cs b/JOBot.Backend/Services/HeadHunterService.cs index 7812fc3..c6c8aad 100644 --- a/JOBot.Backend/Services/HeadHunterService.cs +++ b/JOBot.Backend/Services/HeadHunterService.cs @@ -64,8 +64,9 @@ public class HeadHunterService(ILogger logger, IOptions Date: Wed, 16 Jul 2025 20:59:25 +0300 Subject: [PATCH 14/20] feat: fix of FormUrlEncoded params --- JOBot.Backend/Services/HeadHunterService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JOBot.Backend/Services/HeadHunterService.cs b/JOBot.Backend/Services/HeadHunterService.cs index c6c8aad..ec4a273 100644 --- a/JOBot.Backend/Services/HeadHunterService.cs +++ b/JOBot.Backend/Services/HeadHunterService.cs @@ -42,7 +42,7 @@ public class HeadHunterService(ILogger logger, IOptions { - { "client-id", _config.ClientId }, + { "client_id", _config.ClientId }, { "client_secret", _config.Secret }, { "code", authorizationCode }, { "grant_type", "authorization_code" } From a868cb3e5c927e10bd84d49c05a43095e367d669 Mon Sep 17 00:00:00 2001 From: Lisoveliy Date: Wed, 16 Jul 2025 21:01:50 +0300 Subject: [PATCH 15/20] fix: added redirect_uri for following stage HH rules --- JOBot.Backend/Services/HeadHunterService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/JOBot.Backend/Services/HeadHunterService.cs b/JOBot.Backend/Services/HeadHunterService.cs index ec4a273..363a84a 100644 --- a/JOBot.Backend/Services/HeadHunterService.cs +++ b/JOBot.Backend/Services/HeadHunterService.cs @@ -45,7 +45,8 @@ public class HeadHunterService(ILogger logger, IOptions Date: Wed, 16 Jul 2025 21:02:59 +0300 Subject: [PATCH 16/20] fix: try to fix HeadHunter hook --- JOBot.Backend/Services/HeadHunterService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JOBot.Backend/Services/HeadHunterService.cs b/JOBot.Backend/Services/HeadHunterService.cs index 363a84a..7cf9f6c 100644 --- a/JOBot.Backend/Services/HeadHunterService.cs +++ b/JOBot.Backend/Services/HeadHunterService.cs @@ -46,7 +46,7 @@ public class HeadHunterService(ILogger logger, IOptions Date: Wed, 16 Jul 2025 21:03:55 +0300 Subject: [PATCH 17/20] fix: #999 --- JOBot.Backend/Services/HeadHunterService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JOBot.Backend/Services/HeadHunterService.cs b/JOBot.Backend/Services/HeadHunterService.cs index 7cf9f6c..98e8dcc 100644 --- a/JOBot.Backend/Services/HeadHunterService.cs +++ b/JOBot.Backend/Services/HeadHunterService.cs @@ -46,7 +46,7 @@ public class HeadHunterService(ILogger logger, IOptions Date: Wed, 16 Jul 2025 21:05:47 +0300 Subject: [PATCH 18/20] fix: last fix --- JOBot.Backend/Services/HeadHunterService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/JOBot.Backend/Services/HeadHunterService.cs b/JOBot.Backend/Services/HeadHunterService.cs index 98e8dcc..944ff05 100644 --- a/JOBot.Backend/Services/HeadHunterService.cs +++ b/JOBot.Backend/Services/HeadHunterService.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using System.Web; using JOBot.Backend.DAL.Context; using JOBot.Backend.DTOs.HeadHunterHook; using JOBot.Backend.Infrastructure.Config; @@ -46,7 +47,7 @@ public class HeadHunterService(ILogger logger, IOptions Date: Wed, 16 Jul 2025 21:08:45 +0300 Subject: [PATCH 19/20] -- --- JOBot.Backend/Services/HeadHunterService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JOBot.Backend/Services/HeadHunterService.cs b/JOBot.Backend/Services/HeadHunterService.cs index 944ff05..02af0db 100644 --- a/JOBot.Backend/Services/HeadHunterService.cs +++ b/JOBot.Backend/Services/HeadHunterService.cs @@ -47,7 +47,7 @@ public class HeadHunterService(ILogger logger, IOptions Date: Wed, 16 Jul 2025 21:17:18 +0300 Subject: [PATCH 20/20] fix: fix of redirect uri form issue --- JOBot.Backend/Services/HeadHunterService.cs | 22 ++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/JOBot.Backend/Services/HeadHunterService.cs b/JOBot.Backend/Services/HeadHunterService.cs index 02af0db..e89ce38 100644 --- a/JOBot.Backend/Services/HeadHunterService.cs +++ b/JOBot.Backend/Services/HeadHunterService.cs @@ -19,15 +19,8 @@ public class HeadHunterService(ILogger logger, IOptionsLink for auth public string GenerateAuthLink(long userId) { - var redirectUri = new UriBuilder(_config.Links.HookDomain) - { - Port = -1, - Scheme = "https", - Path = _config.Links.HookRoute, - Query = $"?userId={userId}" - }.ToString(); - return string.Format(_config.Links.AuthLink, [_config.ClientId, redirectUri]); + return string.Format(_config.Links.AuthLink, [_config.ClientId, GetRedirectUrl(userId)]); } public async Task AuthUser(int userId, string authorizationCode, string? error) @@ -47,7 +40,7 @@ public class HeadHunterService(ILogger logger, IOptions logger, IOptions