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/.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 diff --git a/JOBot.Backend/Controllers/HeadHunterHookController.cs b/JOBot.Backend/Controllers/HeadHunterHookController.cs index 1ba1e24..60ef630 100644 --- a/JOBot.Backend/Controllers/HeadHunterHookController.cs +++ b/JOBot.Backend/Controllers/HeadHunterHookController.cs @@ -8,14 +8,13 @@ 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 }; 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 1f9a3c1..f814b31 100644 --- a/JOBot.Backend/JOBot.Backend.csproj +++ b/JOBot.Backend/JOBot.Backend.csproj @@ -1,33 +1,38 @@  - - 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 e89ce38..c155837 100644 --- a/JOBot.Backend/Services/HeadHunterService.cs +++ b/JOBot.Backend/Services/HeadHunterService.cs @@ -1,79 +1,103 @@ +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 JOBot.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, + IWebHostEnvironment env, + 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}"); 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; - } - - var responseDto = JsonSerializer.Deserialize(await res.Content.ReadAsStringAsync()); + using var res = await client.SendAsync( + new HttpRequestMessage( + HttpMethod.Post, + _config.Links.HeadHunterTokenRoute) + { + Content = new FormUrlEncodedContent(form) + }); - if (responseDto == null) - { - logger.LogWarning($"User {userId} auth completed with error " + - $"{nameof(Status.HeadHunterResponseDeserializationFailedError)}"); - return Status.HeadHunterResponseDeserializationFailedError; + 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; + } } - + else + { + responseDto = new HeadHunterTokenResponseDto("testtoken", 0, "testtoken", "--"); + } + var user = await dbContext.Users.FirstOrDefaultAsync(x => x.UserId == userId); if (user == null) @@ -81,13 +105,21 @@ public class HeadHunterService(ILogger logger, IOptions logger, IOptions - /// 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 1295739..38366f5 100644 --- a/JOBot.Backend/Startup.cs +++ b/JOBot.Backend/Startup.cs @@ -2,7 +2,9 @@ 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; namespace JOBot.Backend; @@ -12,7 +14,7 @@ public class Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { - services.AddGrpc(); + services.AddGrpc(); services.AddGrpcReflection(); services.AddControllers(); services.AddLogging(); @@ -20,6 +22,23 @@ public class Startup(IConfiguration configuration) services.AddDbContext(options => options.UseNpgsql(Configuration.GetConnectionString("PostgreSQL"))); + + services.AddSingleton(x => + { + 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)); services.AddScoped(); 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" - } -} 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.Infrastructure/Config/RabbitQueues.cs b/JOBot.Infrastructure/Config/RabbitQueues.cs new file mode 100644 index 0000000..0525bdc --- /dev/null +++ b/JOBot.Infrastructure/Config/RabbitQueues.cs @@ -0,0 +1,6 @@ +namespace JOBot.Infrastructure.Config; + +public static class RabbitQueues +{ + public const string AuthQueue = "auth"; +} \ No newline at end of file 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/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..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; @@ -8,7 +9,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); + + await bot.SendMessageRemK(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..a063b8f 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,12 +69,15 @@ public sealed class BotBackgroundService( await authorizedTelegramCommand.ExecuteAsync(update, user, ct); } - else await command.ExecuteAsync(update, ct); + else + { + await command.ExecuteAsync(update, ct); + } 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/DependencyInjection.cs b/JOBot.TClient/DependencyInjection.cs deleted file mode 100644 index 153be1b..0000000 --- a/JOBot.TClient/DependencyInjection.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Grpc.Core; -using Grpc.Net.Client; -using JOBot.Proto; -using JOBot.TClient.Commands.Buttons; -using JOBot.TClient.Commands.Commands; -using JOBot.TClient.Core.HostedServices; -using JOBot.TClient.Services; -using JOBot.TClient.Statements; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Telegram.Bot; - -namespace JOBot.TClient; - -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"))); - - #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") - ?? throw new MissingFieldException("TelegramToken is not set"))); - - services.AddLogging(builder => builder.AddConsole()); - return services; - } -} \ No newline at end of file diff --git a/JOBot.TClient/Infrastructure/Attributes/Authorization/AcceptNotPreparedAttribute.cs b/JOBot.TClient/Infrastructure/Attributes/Authorization/AcceptNotPreparedAttribute.cs index 7929497..bac3b51 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 diff --git a/JOBot.TClient/Infrastructure/Exceptions/FallbackException.cs b/JOBot.TClient/Infrastructure/Exceptions/FallbackException.cs index e624813..1a765e9 100644 --- a/JOBot.TClient/Infrastructure/Exceptions/FallbackException.cs +++ b/JOBot.TClient/Infrastructure/Exceptions/FallbackException.cs @@ -1,17 +1,18 @@ +using JOBot.TClient.Infrastructure.Extensions; using Telegram.Bot; 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.SendMessageRemK(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/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/JOBot.TClient.csproj b/JOBot.TClient/JOBot.TClient.csproj index 987d35a..9bce854 100644 --- a/JOBot.TClient/JOBot.TClient.csproj +++ b/JOBot.TClient/JOBot.TClient.csproj @@ -20,6 +20,7 @@ + @@ -58,9 +59,13 @@ - - 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/Queues/AuthQueue.cs b/JOBot.TClient/Queues/AuthQueue.cs new file mode 100644 index 0000000..d3e1fc1 --- /dev/null +++ b/JOBot.TClient/Queues/AuthQueue.cs @@ -0,0 +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(IChannel channel, PrepareUserService prepareUserService) : BackgroundService +{ + + private async Task OnDataReceivedAsync(object sender, BasicDeliverEventArgs eventArgs) + { + 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 e8ba02e..bdfd891 100644 --- a/JOBot.TClient/Services/MenuService.cs +++ b/JOBot.TClient/Services/MenuService.cs @@ -1,6 +1,6 @@ +using JOBot.TClient.Infrastructure.Extensions; using Telegram.Bot; using Telegram.Bot.Types; -using User = JOBot.Proto.User; namespace JOBot.TClient.Services; @@ -9,7 +9,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.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 443f8b0..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; @@ -12,13 +13,13 @@ 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 /// 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); @@ -26,21 +27,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.SendMessageRemK(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,23 +51,23 @@ public class PrepareUserService(ITelegramBotClient bot, User.UserClient userClie } /// - /// Get Eula Agreement from user + /// Get Eula Agreement from user /// /// 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); - await _bot.SendMessage( + await _bot.SendMessageRemK( update.Message.From.Id, TextResource.EULA, replyMarkup: new[] { ButtonResource.EULAAgrement }, cancellationToken: ct); } /// - /// Accept EULA for user + /// Accept EULA for user /// /// Telegram update object /// Cancellation Token @@ -74,10 +75,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,15 +88,34 @@ 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]), + await _bot.SendMessageRemK( + update.Message.From.Id, + string.Format(TextResource.AskForAuth, [url.RegistrationUrl]), cancellationToken: ct); } + + public async Task AuthHookReceived(long userId, CancellationToken ct = default) + { + await _bot.SendMessageRemK(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); + } + + public async Task SelectCv(long userId, CancellationToken ct = default) + { + 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/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/Startup.cs b/JOBot.TClient/Startup.cs new file mode 100644 index 0000000..fa3bdaa --- /dev/null +++ b/JOBot.TClient/Startup.cs @@ -0,0 +1,87 @@ +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 Startup +{ + public static IServiceCollection ConfigureServices(this IServiceCollection services, IConfiguration config) + { + services.AddSingleton(config); + + services.AddSingleton(_ => GrpcChannel.ForAddress(config.GetValue("BackendHost") + ?? throw new MissingFieldException("Host is not defined"))); + + #region Commands + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + //buttons + services.AddSingleton(); + + #endregion + + #region gRPC Clients + + services.AddGrpcClient(o => o.Address = new Uri("http://backend:5001")); + + #endregion + + #region Services + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + #endregion + + #region States + + services.AddSingleton(); + + #endregion + + #region RabbitMQ Clients + + var factory = new ConnectionFactory { HostName = "rabbitmq" }; + var connection = factory.CreateConnectionAsync().Result; + var channel = connection.CreateChannelAsync().Result; + + channel.QueueDeclareAsync( + RabbitQueues.AuthQueue, + false, + false, + false, + arguments: null).Wait(); + + services.AddSingleton(channel); + services.AddHostedService(); + + #endregion + + // Bot service + services.AddHostedService(); + services.AddSingleton(_ => + new TelegramBotClient(config.GetValue("TelegramToken") + ?? throw new MissingFieldException("TelegramToken is not set"))); + + services.AddLogging(builder => builder.AddConsole()); + return services; + } +} \ No newline at end of file diff --git a/JOBot.TClient/Statements/PrepareUserState.cs b/JOBot.TClient/Statements/PrepareUserState.cs index b989aec..a44efa0 100644 --- a/JOBot.TClient/Statements/PrepareUserState.cs +++ b/JOBot.TClient/Statements/PrepareUserState.cs @@ -1,14 +1,13 @@ using JOBot.Proto; using JOBot.TClient.Services; using Telegram.Bot.Types; -using User = JOBot.Proto.User; 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 @@ -21,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); } /// - /// Continue prepare stage + /// Check user logged /// /// /// @@ -50,6 +49,19 @@ 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 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/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 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 ea8011c..daa1ca1 100644 --- a/compose.dev.yml +++ b/compose.dev.yml @@ -1,6 +1,17 @@ version: '3.8' services: + rabbitmq: + hostname: jobot-rabbit + image: rabbitmq:4 + ports: + - "5672:5672" + - "15672:15672" + volumes: + - rabbitmq_data:/var/lib/rabbitmq/ + networks: + - jobot + postgres: image: postgres:15 environment: @@ -19,6 +30,7 @@ services: dockerfile: JOBot.Backend/Dockerfile depends_on: - postgres + - rabbitmq ports: - "5000:5000" - "5001:5001" @@ -36,6 +48,7 @@ services: 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..bb1fd90 100644 --- a/compose.yml +++ b/compose.yml @@ -1,11 +1,19 @@ version: '3.8' services: + rabbitmq: + hostname: jobot-rabbit + image: rabbitmq:4 + volumes: + - rabbitmq_data:/var/lib/rabbitmq/ + networks: + - jobot + postgres: image: postgres:15 environment: POSTGRES_PASSWORD: LocalDbPass - POSTGRES_DB: jobot + POSTGRES_DB: jobot volumes: - postgres_data:/var/lib/postgresql/data networks: @@ -17,6 +25,7 @@ services: dockerfile: JOBot.Backend/Dockerfile depends_on: - postgres + - rabbitmq ports: - "5000:5000" networks: @@ -33,7 +42,8 @@ services: networks: jobot: - + volumes: - postgres_data: \ No newline at end of file + postgres_data: + rabbitmq_data: \ No newline at end of file