20 Commits

Author SHA1 Message Date
cc559a4aa3 fix: fix of redirect uri form issue 2025-07-16 21:17:18 +03:00
904fa634c6 -- 2025-07-16 21:08:45 +03:00
9fc1fadff5 fix: last fix 2025-07-16 21:05:47 +03:00
333eeab756 fix: #999 2025-07-16 21:03:55 +03:00
0ba1f4e736 fix: try to fix HeadHunter hook 2025-07-16 21:02:59 +03:00
a868cb3e5c fix: added redirect_uri for following stage HH rules 2025-07-16 21:01:50 +03:00
a32bfb6e99 feat: fix of FormUrlEncoded params 2025-07-16 20:59:25 +03:00
e4c7df9714 chore: added debug content stream 2025-07-16 20:58:01 +03:00
54393e2e0f fix: fixed HeadHunter API Hook 2025-07-16 20:51:25 +03:00
e82cc31b4e fix: fix of docker container 2025-07-16 20:48:44 +03:00
362074faa4 fix: fix of Uri construct for Auth link 2025-07-16 20:43:50 +03:00
0dae5a2a13 fix: fixed appsettings.json 2025-07-16 20:39:37 +03:00
60db714a32 fix: hotfix 2025-07-16 20:37:33 +03:00
0826eb5898 fix: fixed stage 2025-07-16 20:36:30 +03:00
fcdc4476df feat: added auth for telegram stage 2025-07-16 20:34:29 +03:00
bae7a0267d feat: updated Dockerfile 2025-07-16 20:09:12 +03:00
1d5b171479 chore: fix csproj 2025-07-16 19:56:41 +03:00
5b81ad2084 chore: update csproj 2025-07-16 19:48:30 +03:00
f9a154be6f feat: try to get appsettings.json for JOBot.TClient 2025-07-16 19:48:17 +03:00
20c24c1fdb Merge pull request 'Test of HeadHunter hook' (#20) from 1 into dev
Reviewed-on: http://git.lisoveliy.su/Lisoveliy/JOBot/pulls/20
2025-07-12 18:52:31 +02:00
15 changed files with 100 additions and 30 deletions

View File

@@ -10,9 +10,9 @@ public class HeadHunterHookController(HeadHunterService hhService)
{
[HttpGet]
public async Task<IActionResult> Get(int userId, string? error, string authorizationCode)
public async Task<IActionResult> 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 для продолжения."),

View File

@@ -10,5 +10,4 @@ WORKDIR /app
COPY --from=build /app .
EXPOSE 5001
EXPOSE 5000
ENV ASPNETCORE_ENVIRONMENT Staging
ENTRYPOINT ["dotnet", "JOBot.Backend.dll"]

View File

@@ -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;
@@ -16,17 +17,10 @@ public class HeadHunterService(ILogger<HeadHunterService> logger, IOptions<HeadH
/// </summary>
/// <param name="userId">Telegram UserId</param>
/// <returns>Link for auth</returns>
public string GenerateAuthLink(int userId)
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, userId, redirectUri]);
return string.Format(_config.Links.AuthLink, [_config.ClientId, GetRedirectUrl(userId)]);
}
public async Task<Status> AuthUser(int userId, string authorizationCode, string? error)
@@ -42,10 +36,11 @@ public class HeadHunterService(ILogger<HeadHunterService> logger, IOptions<HeadH
using var client = new HttpClient();
var form = new Dictionary<string, string>
{
{ "client-id", _config.ClientId },
{ "client_id", _config.ClientId },
{ "client_secret", _config.Secret },
{ "code", authorizationCode },
{ "grant_type", "authorization_code" }
{ "grant_type", "authorization_code" },
{ "redirect_uri", GetRedirectUrl(userId) }
};
client.BaseAddress = new UriBuilder(_config.Links.HeadHunterApiDomain)
{
@@ -64,8 +59,9 @@ public class HeadHunterService(ILogger<HeadHunterService> logger, IOptions<HeadH
if (!res.IsSuccessStatusCode)
{
logger.LogWarning($"Response of HttpRequest ${_config.Links.HeadHunterApiDomain} " +
$"${_config.Links.HeadHunterTokenRoute} has unsuccessful status code {res.StatusCode}");
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;
}
@@ -95,6 +91,17 @@ public class HeadHunterService(ILogger<HeadHunterService> logger, IOptions<HeadH
return Status.Success;
}
private string GetRedirectUrl(long userId)
{
return new UriBuilder(_config.Links.HookDomain)
{
Port = -1,
Scheme = "https",
Path = _config.Links.HookRoute,
Query = $"?userId={userId}"
}.ToString();
}
public enum Status
{
UserAuthRejectedError,

View File

@@ -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
{
/// <summary>
/// Create user
@@ -62,6 +63,16 @@ public class UserService(AppDbContext dbContext) : Proto.User.UserBase
return new AcceptEulaResponse { Success = true };
}
public override Task<GetHeadHunterAuthHookResponse> GetHeadHunterAuthHook(
GetHeadHunterAuthHookRequest request,
ServerCallContext context)
{
return Task.Run(() => new GetHeadHunterAuthHookResponse
{
RegistrationUrl = hhService.GenerateAuthLink(request.UserId)
});
}
/// <summary>
/// Throw RPCException if user not found
/// </summary>

View File

@@ -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",

View File

@@ -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);
}
}

View File

@@ -57,4 +57,10 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -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);
}
}

View File

@@ -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.RegistrationUrl]),
cancellationToken: ct);
}
}

View File

@@ -1,3 +1,4 @@
using JOBot.Proto;
using JOBot.TClient.Services;
using Telegram.Bot.Types;
using User = JOBot.Proto.User;
@@ -21,27 +22,34 @@ public class PrepareUserState(PrepareUserService prepareUserService, MenuService
return; //interrupt while eula isn't accepted
}
await OnUserEulaValidStage(update, ct);
await OnUserEulaValidStage(user, update, ct);
}
/// <summary>
/// Signal for accepted eula
/// </summary>
/// <param name="user"></param>
/// <param name="update"></param>
/// <param name="ct"></param>
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);
}
/// <summary>
/// Continue prepare stage
/// </summary>
/// <param name="user"></param>
/// <param name="update"></param>
/// <param name="ct"></param>
private async Task OnUserEulaValidStage(Update update, CancellationToken ct = default)
private async Task OnUserEulaValidStage(GetUserResponse user, Update update, CancellationToken ct = default)
{
if (!user.IsLogged)
{
await prepareUserService.Auth(update, ct);
return;
}
await menuService.RenderMenu(update, ct); //boilerplate
}
}

View File

@@ -59,6 +59,15 @@ namespace JOBot.TClient {
}
}
/// <summary>
/// Looks up a localized string similar to Авторизируйтесь на сайте HeadHunter для получения доступа к резюме и вакансиям {0}.
/// </summary>
public static string AskForAuth {
get {
return ResourceManager.GetString("AskForAuth", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Команда не найдена, попробуйте что-то другое.
/// </summary>

View File

@@ -34,4 +34,7 @@ https://hh.ru/account/agreement?backurl=%2Faccount%2Fsignup%3Fbackurl%3D%252F%26
<data name="Info" xml:space="preserve">
<value>Это бот для упрощения поиска работы на HH.ru</value>
</data>
<data name="AskForAuth" xml:space="preserve">
<value>Авторизируйтесь на сайте HeadHunter для получения доступа к резюме и вакансиям {0}</value>
</data>
</root>

View File

@@ -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;
@@ -38,3 +40,11 @@ message AcceptEulaRequest {
message AcceptEulaResponse{
bool success = 1;
}
message GetHeadHunterAuthHookRequest{
int64 user_id = 1;
}
message GetHeadHunterAuthHookResponse{
string registration_url = 1;
}

View File

@@ -20,6 +20,7 @@ services:
depends_on:
- postgres
ports:
- "5000:5000"
- "5001:5001"
networks:
- jobot

View File

@@ -17,6 +17,8 @@ services:
dockerfile: JOBot.Backend/Dockerfile
depends_on:
- postgres
ports:
- "5000:5000"
networks:
- jobot