38 Commits

Author SHA1 Message Date
0e9084ce2e Merge pull request 'fix: fix of pipeline' (#24) from dev into main
All checks were successful
Publish JOBot on dev / Build and deploy (push) Successful in 25s
Reviewed-on: http://git.lisoveliy.su/Lisoveliy/JOBot/pulls/24
2025-07-18 16:03:33 +02:00
f2aadf43a6 fix: fix of pipeline
All checks were successful
Publish JOBot on dev / Build and deploy (push) Successful in 25s
2025-07-18 17:02:35 +03:00
48b79ce6ea Merge pull request 'Configured auto-deploy' (#23) from dev into main
All checks were successful
Publish JOBot on dev / Build and deploy (push) Successful in 26s
Reviewed-on: http://git.lisoveliy.su/Lisoveliy/JOBot/pulls/23
2025-07-18 16:00:48 +02:00
81bf05e3fc fix: fix of compose files
All checks were successful
Publish JOBot on dev / Build and deploy (push) Successful in 21s
2025-07-18 16:28:10 +03:00
445a1f7fa4 fix: fix of docker compose file
Some checks failed
Publish JOBot on dev / Build and deploy (push) Failing after 1s
2025-07-18 16:23:23 +03:00
2f79bd336a chore: update pipeline
Some checks failed
Publish JOBot on dev / Build and deploy (push) Failing after 15s
2025-07-18 16:07:29 +03:00
cf463a5d94 chore: update pipeline
Some checks failed
Publish JOBot on dev / Build and deploy (push) Failing after 0s
2025-07-18 16:02:20 +03:00
f6500670c0 chore: update pipeline
Some checks failed
Publish JOBot on dev / Build and deploy (push) Failing after 0s
2025-07-18 16:01:37 +03:00
1f0bc01dc6 chore: update pipeline
Some checks failed
Publish JOBot on dev / Build and deploy (push) Failing after 14s
2025-07-18 15:59:40 +03:00
18e6fcc883 chore: update pipeline
Some checks failed
Publish JOBot on dev / Build and deploy (push) Failing after 13s
2025-07-18 15:58:11 +03:00
10d0a83e15 test: test pipeline
Some checks failed
Publish JOBot on dev / Build and deploy (push) Failing after 12s
2025-07-18 15:55:30 +03:00
3b8278ba66 chore: test deploy
Some checks failed
Publish JOBot on dev / Build and deploy (push) Failing after 18s
2025-07-18 15:51:16 +03:00
86cad2beec fix: fix pipeline
Some checks failed
Publish JOBot on dev / Build and deploy (push) Failing after 4s
2025-07-16 23:07:47 +03:00
4b223ecf98 chore: debug pipeline
Some checks failed
Publish JOBot on dev / Build and deploy (push) Failing after 5s
2025-07-16 22:57:50 +03:00
d041fd870b fix: fix conf
Some checks failed
Publish JOBot on dev / Build and deploy (push) Failing after 2s
2025-07-16 22:57:32 +03:00
6d5c327747 fix: fixed workflow
Some checks failed
Publish JOBot on dev / Build and deploy (push) Failing after 3s
2025-07-16 22:47:01 +03:00
7e24a7444e feat: added deploy script 2025-07-16 22:46:00 +03:00
cb481e6af6 Merge pull request 'Fixed deploy, Added HH Auth on back-end' (#22) from dev into main
Reviewed-on: http://git.lisoveliy.su/Lisoveliy/JOBot/pulls/22
2025-07-16 20:21:01 +02:00
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
17 changed files with 137 additions and 47 deletions

View File

@@ -0,0 +1,25 @@
name: Publish JOBot on dev
on:
push:
branches:
- dev
jobs:
build and deploy:
runs-on: host
name: Build and deploy
volumes:
steps:
- name: Update repo
run: |
pwd
cd /home/dockeruser/jobot-stack
git checkout dev
git pull
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

View File

@@ -10,9 +10,9 @@ public class HeadHunterHookController(HeadHunterService hhService)
{ {
[HttpGet] [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 return res switch
{ {
HeadHunterService.Status.Success => Ok("Авторизация завершена успешно. Вернитесь в Telegram для продолжения."), HeadHunterService.Status.Success => Ok("Авторизация завершена успешно. Вернитесь в Telegram для продолжения."),

View File

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

View File

@@ -1,4 +1,5 @@
using System.Text.Json; using System.Text.Json;
using System.Web;
using JOBot.Backend.DAL.Context; using JOBot.Backend.DAL.Context;
using JOBot.Backend.DTOs.HeadHunterHook; using JOBot.Backend.DTOs.HeadHunterHook;
using JOBot.Backend.Infrastructure.Config; using JOBot.Backend.Infrastructure.Config;
@@ -16,17 +17,10 @@ public class HeadHunterService(ILogger<HeadHunterService> logger, IOptions<HeadH
/// </summary> /// </summary>
/// <param name="userId">Telegram UserId</param> /// <param name="userId">Telegram UserId</param>
/// <returns>Link for auth</returns> /// <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) 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(); using var client = new HttpClient();
var form = new Dictionary<string, string> var form = new Dictionary<string, string>
{ {
{ "client-id", _config.ClientId }, { "client_id", _config.ClientId },
{ "client_secret", _config.Secret }, { "client_secret", _config.Secret },
{ "code", authorizationCode }, { "code", authorizationCode },
{ "grant_type", "authorization_code" } { "grant_type", "authorization_code" },
{ "redirect_uri", GetRedirectUrl(userId) }
}; };
client.BaseAddress = new UriBuilder(_config.Links.HeadHunterApiDomain) client.BaseAddress = new UriBuilder(_config.Links.HeadHunterApiDomain)
{ {
@@ -64,8 +59,9 @@ public class HeadHunterService(ILogger<HeadHunterService> logger, IOptions<HeadH
if (!res.IsSuccessStatusCode) if (!res.IsSuccessStatusCode)
{ {
logger.LogWarning($"Response of HttpRequest ${_config.Links.HeadHunterApiDomain} " + logger.LogWarning($"Response of HttpRequest {_config.Links.HeadHunterApiDomain}" +
$"${_config.Links.HeadHunterTokenRoute} has unsuccessful status code {res.StatusCode}"); $"{_config.Links.HeadHunterTokenRoute} has unsuccessful status code {res.StatusCode}");
logger.LogWarning($"{res.Content.ReadAsStringAsync().Result}");
return Status.HeadHunterAuthRejectedError; return Status.HeadHunterAuthRejectedError;
} }
@@ -95,6 +91,17 @@ public class HeadHunterService(ILogger<HeadHunterService> logger, IOptions<HeadH
return Status.Success; 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 public enum Status
{ {
UserAuthRejectedError, UserAuthRejectedError,

View File

@@ -1,3 +1,4 @@
using Google.Protobuf.WellKnownTypes;
using Grpc.Core; using Grpc.Core;
using JOBot.Backend.DAL.Context; using JOBot.Backend.DAL.Context;
using JOBot.Backend.DAL.Models; using JOBot.Backend.DAL.Models;
@@ -7,7 +8,7 @@ using User = JOBot.Backend.DAL.Models.User;
namespace JOBot.Backend.Services.gRPC; namespace JOBot.Backend.Services.gRPC;
public class UserService(AppDbContext dbContext) : Proto.User.UserBase public class UserService(AppDbContext dbContext, HeadHunterService hhService) : Proto.User.UserBase
{ {
/// <summary> /// <summary>
/// Create user /// Create user
@@ -62,6 +63,16 @@ public class UserService(AppDbContext dbContext) : Proto.User.UserBase
return new AcceptEulaResponse { Success = true }; 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> /// <summary>
/// Throw RPCException if user not found /// Throw RPCException if user not found
/// </summary> /// </summary>

View File

@@ -1,12 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"PostgreSQL": "Host=postgres;Port=5432;Database=jobot_test;Username=postgres;Password=LocalDbPass"
}
}

View File

@@ -11,7 +11,7 @@
}, },
"HeadHunter": { "HeadHunter": {
"Links": { "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", "HookDomain": "jobot.lisoveliy.su",
"HookRoute": "/auth", "HookRoute": "/auth",
"HeadHunterApiDomain": "api.hh.ru", "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) 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> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project> </Project>

View File

@@ -6,12 +6,10 @@ namespace JOBot.TClient.Services;
public class MenuService(ITelegramBotClient bot) 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); ArgumentNullException.ThrowIfNull(update.Message?.From);
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);
return Task.CompletedTask;
} }
} }

View File

@@ -83,4 +83,19 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie
if (!result.Success) if (!result.Success)
throw new FallbackException(TextResource.FallbackMessage, update.Message, _bot); 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 JOBot.TClient.Services;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using User = JOBot.Proto.User; using User = JOBot.Proto.User;
@@ -21,27 +22,34 @@ public class PrepareUserState(PrepareUserService prepareUserService, MenuService
return; //interrupt while eula isn't accepted return; //interrupt while eula isn't accepted
} }
await OnUserEulaValidStage(update, ct); await OnUserEulaValidStage(user, update, ct);
} }
/// <summary> /// <summary>
/// Signal for accepted eula /// Signal for accepted eula
/// </summary> /// </summary>
/// <param name="user"></param>
/// <param name="update"></param> /// <param name="update"></param>
/// <param name="ct"></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 prepareUserService.AcceptEula(update, ct: ct);
await OnUserEulaValidStage(update, ct); await OnUserEulaValidStage(user, update, ct);
} }
/// <summary> /// <summary>
/// Continue prepare stage /// Continue prepare stage
/// </summary> /// </summary>
/// <param name="user"></param>
/// <param name="update"></param> /// <param name="update"></param>
/// <param name="ct"></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 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> /// <summary>
/// Looks up a localized string similar to Команда не найдена, попробуйте что-то другое. /// Looks up a localized string similar to Команда не найдена, попробуйте что-то другое.
/// </summary> /// </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"> <data name="Info" xml:space="preserve">
<value>Это бот для упрощения поиска работы на HH.ru</value> <value>Это бот для упрощения поиска работы на HH.ru</value>
</data> </data>
<data name="AskForAuth" xml:space="preserve">
<value>Авторизируйтесь на сайте HeadHunter для получения доступа к резюме и вакансиям {0}</value>
</data>
</root> </root>

View File

@@ -1,14 +1,16 @@
syntax = "proto3"; syntax = "proto3";
option csharp_namespace = "JOBot.Proto"; option csharp_namespace = "JOBot.Proto";
import "google/protobuf/wrappers.proto";
import "google/protobuf/empty.proto";
service User { service User {
rpc Register (RegisterRequest) returns (RegisterResponse); rpc Register (RegisterRequest) returns (RegisterResponse);
rpc GetUser (GetUserRequest) returns (GetUserResponse); rpc GetUser (GetUserRequest) returns (GetUserResponse);
rpc AcceptEula (AcceptEulaRequest) returns (AcceptEulaResponse); rpc AcceptEula (AcceptEulaRequest) returns (AcceptEulaResponse);
rpc GetHeadHunterAuthHook(GetHeadHunterAuthHookRequest) returns (GetHeadHunterAuthHookResponse);
} }
import "google/protobuf/wrappers.proto";
message RegisterRequest{ message RegisterRequest{
int64 user_id = 1; int64 user_id = 1;
google.protobuf.StringValue username = 2; google.protobuf.StringValue username = 2;
@@ -37,4 +39,12 @@ message AcceptEulaRequest {
message AcceptEulaResponse{ message AcceptEulaResponse{
bool success = 1; bool success = 1;
}
message GetHeadHunterAuthHookRequest{
int64 user_id = 1;
}
message GetHeadHunterAuthHookResponse{
string registration_url = 1;
} }

View File

@@ -9,7 +9,7 @@ services:
ports: ports:
- "5432:5432" - "5432:5432"
volumes: volumes:
- ./.docker/postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
networks: networks:
- jobot - jobot
@@ -20,6 +20,7 @@ services:
depends_on: depends_on:
- postgres - postgres
ports: ports:
- "5000:5000"
- "5001:5001" - "5001:5001"
networks: networks:
- jobot - jobot
@@ -34,4 +35,7 @@ services:
- jobot - jobot
networks: networks:
jobot: jobot:
volumes:
postgres_data:

View File

@@ -5,9 +5,9 @@ services:
image: postgres:15 image: postgres:15
environment: environment:
POSTGRES_PASSWORD: LocalDbPass POSTGRES_PASSWORD: LocalDbPass
POSTGRES_DB: jobot POSTGRES_DB: jobot
volumes: volumes:
- ./.docker/postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
networks: networks:
- jobot - jobot
@@ -17,6 +17,8 @@ services:
dockerfile: JOBot.Backend/Dockerfile dockerfile: JOBot.Backend/Dockerfile
depends_on: depends_on:
- postgres - postgres
ports:
- "5000:5000"
networks: networks:
- jobot - jobot
@@ -30,4 +32,8 @@ services:
- jobot - jobot
networks: networks:
jobot: jobot:
volumes:
postgres_data: