11 Commits

Author SHA1 Message Date
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
dee7942f20 feat: added implementation of HeadHunter hook for user registration 2025-07-12 19:51:34 +03:00
6ffbe67d0d Merge pull request 'Обновить README.md' (#19) from lisoveliy-patch-2 into main
Reviewed-on: http://git.lisoveliy.su/Lisoveliy/JOBot/pulls/19
2025-07-12 16:43:31 +02:00
408af57f5f Обновить README.md 2025-07-12 16:43:19 +02:00
f28c6fdbe5 Merge pull request 'chore: removed trash file' (#18) from dev into main
Reviewed-on: http://git.lisoveliy.su/Lisoveliy/JOBot/pulls/18
2025-07-12 01:16:39 +02:00
d433b6a30b chore: removed trash file 2025-07-12 02:16:28 +03:00
fe9d660a93 Merge pull request 'hotfix: fix of render EULA agreement' (#17) from dev into main
Reviewed-on: http://git.lisoveliy.su/Lisoveliy/JOBot/pulls/17
2025-07-12 01:15:37 +02:00
db1717ef22 Merge pull request 'Обновить README.md' (#16) from lisoveliy-patch-1 into main
Reviewed-on: http://git.lisoveliy.su/Lisoveliy/JOBot/pulls/16
2025-07-12 01:15:25 +02:00
e2a2c0ac1c Обновить README.md 2025-07-12 01:15:16 +02:00
399364f5fc hotfix: fix of render EULA agreement 2025-07-12 02:14:58 +03:00
4bc1e63da7 Merge pull request '#14 feat: implemented registration stage of preparation on PrepareUserState' (#15) from 14 into main
Reviewed-on: http://git.lisoveliy.su/Lisoveliy/JOBot/pulls/15
2025-07-12 01:02:26 +02:00
11 changed files with 212 additions and 31 deletions

View File

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

View File

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

View File

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

View File

@@ -14,6 +14,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>

View File

@@ -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<HeadHunterService> logger, IOptions<HeadHunterConfig> config, AppDbContext dbContext)
{
private readonly HeadHunterConfig _config = config.Value;
/// <summary>
/// Generate HeadHunter oauth authorization link
/// </summary>
/// <param name="userId">Telegram UserId</param>
/// <returns>Link for auth</returns>
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<Status> 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<string, string>
{
{ "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<HeadHunterTokenResponseDto>(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
}
}

View File

@@ -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<AppDbContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("PostgreSQL")));
services.Configure<HeadHunterConfig>(Configuration.GetSection(HeadHunterConfig.SectionName));
services.AddScoped<HeadHunterService>();
}
public void Configure(WebApplication app, IWebHostEnvironment env)
{
app.MapGrpcReflectionService().AllowAnonymous();
app.MapGrpcService<UserService>();
app.MapControllers();
}
}

View File

@@ -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": {

View File

@@ -69,8 +69,11 @@ namespace JOBot.TClient {
}
/// <summary>
/// Looks up a localized string similar to Продолжая, вы принимаете политику конфиденциальности и правила сервиса \nhttps://hh.ru/article/personal_data?backurl=%2F&amp;role=applicant
///\nhttps://hh.ru/account/agreement?backurl=%2Faccount%2Fsignup%3Fbackurl%3D%252F%26role%3Dapplicant&amp;role=applicant&quot;.
/// Looks up a localized string similar to Продолжая, вы принимаете политику конфиденциальности и правила сервиса
///
///https://hh.ru/article/personal_data?backurl=%2F&amp;role=applicant
///
///https://hh.ru/account/agreement?backurl=%2Faccount%2Fsignup%3Fbackurl%3D%252F%26role%3Dapplicant&amp;role=applicant&quot;.
/// </summary>
public static string EULA {
get {

View File

@@ -19,8 +19,11 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="EULA" xml:space="preserve">
<value>Продолжая, вы принимаете политику конфиденциальности и правила сервиса \nhttps://hh.ru/article/personal_data?backurl=%2F&amp;role=applicant
\nhttps://hh.ru/account/agreement?backurl=%2Faccount%2Fsignup%3Fbackurl%3D%252F%26role%3Dapplicant&amp;role=applicant"</value>
<value>Продолжая, вы принимаете политику конфиденциальности и правила сервиса
https://hh.ru/article/personal_data?backurl=%2F&amp;role=applicant
https://hh.ru/account/agreement?backurl=%2Faccount%2Fsignup%3Fbackurl%3D%252F%26role%3Dapplicant&amp;role=applicant"</value>
</data>
<data name="FallbackMessage" xml:space="preserve">
<value>Something went REALLY wrong. Service can't continue process.</value>

View File

@@ -5,21 +5,28 @@ Telegram-бот для автоматизации поиска работы че
## 🔥 Возможности
- **Авторизация через HH**
Привяжите аккаунт HeadHunter для доступа к резюме и вакансиям.
- **Отклик по предпочтениям**
Выбор откликаемых вакансий по предпочтениям.
- **Умные отклики**
Автоматическая отправка откликов на подходящие вакансии.
- **Проверка резюме**
Уведомления, если резюме требует обновления.
Правка резюме, под ваши приоритеты.
- **Аналитика**
Статистика по откликам и приглашениям.
## 🛠 Технологии
- **Backend**: .NET 8 + gRPC
- **База данных**: PostgreSQL
- **Авторизация**: OAuth 2.0 (HH API)
- **Инфраструктура**: Docker + Kubernetes (опционально)
- **Frontend** Telegram клиент через Telegram.Bot
- **БД**: PostgreSQL
- **Авторизация**: OAuth 2.0 (HH API), Telegram API
- **Инфраструктура**: Docker / Docker compose
## 🚀 Запуск
@@ -33,24 +40,15 @@ Telegram-бот для автоматизации поиска работы че
### 2. Запуск через Docker
```
docker-compose up -d
dotnet run --project JoBot.Backend
dotnet run --project JoBot.Client
docker-compose up -d --build
```
## 📌 Команды бота
|Команда|Описание|
|-------|--------|
|/start |Начало работы|
|/connect_hh|Привязать аккаунт HH|
|/jobs |Поиск вакансий|
|/profile|Проверить резюме|
|/stats |Статистика откликов|
## 🔒 Безопасность
Токены пользователей шифруются (AES-256)
Все данные передаются через HTTPS
|/menu|Настройка откликов и резюме по предпочтениям|
|/info|Информация о боте|
📄 Лицензия
MIT License. Подробнее в файле LICENSE.

View File