From 9d005be51e3eadfdbb4e85f31bea778d09a5f10e Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 23 Feb 2026 13:10:55 +0100 Subject: [PATCH 01/31] feat: create microservice --- Streetcode/Streetcode.Email.BLL/Class1.cs | 7 ++++ .../Streetcode.Email.BLL.csproj | 9 ++++ Streetcode/Streetcode.Email.DAL/Class1.cs | 7 ++++ .../Streetcode.Email.DAL.csproj | 9 ++++ Streetcode/Streetcode.Email.WebAPI/Program.cs | 25 +++++++++++ .../Properties/launchSettings.json | 41 +++++++++++++++++++ .../Streetcode.Email.WebAPI.csproj | 17 ++++++++ .../Streetcode.Email.WebAPI/appsettings.json | 9 ++++ .../Streetcode.Email.XUnitTest.csproj | 23 +++++++++++ .../Streetcode.Email.XUnitTest/UnitTest1.cs | 11 +++++ Streetcode/Streetcode.sln | 35 +++++++++++++++- 11 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 Streetcode/Streetcode.Email.BLL/Class1.cs create mode 100644 Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj create mode 100644 Streetcode/Streetcode.Email.DAL/Class1.cs create mode 100644 Streetcode/Streetcode.Email.DAL/Streetcode.Email.DAL.csproj create mode 100644 Streetcode/Streetcode.Email.WebAPI/Program.cs create mode 100644 Streetcode/Streetcode.Email.WebAPI/Properties/launchSettings.json create mode 100644 Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj create mode 100644 Streetcode/Streetcode.Email.WebAPI/appsettings.json create mode 100644 Streetcode/Streetcode.Email.XUnitTest/Streetcode.Email.XUnitTest.csproj create mode 100644 Streetcode/Streetcode.Email.XUnitTest/UnitTest1.cs diff --git a/Streetcode/Streetcode.Email.BLL/Class1.cs b/Streetcode/Streetcode.Email.BLL/Class1.cs new file mode 100644 index 0000000..675b809 --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/Class1.cs @@ -0,0 +1,7 @@ +namespace Streetcode.Email.BLL +{ + public class Class1 + { + + } +} diff --git a/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj b/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/Streetcode/Streetcode.Email.DAL/Class1.cs b/Streetcode/Streetcode.Email.DAL/Class1.cs new file mode 100644 index 0000000..4009b00 --- /dev/null +++ b/Streetcode/Streetcode.Email.DAL/Class1.cs @@ -0,0 +1,7 @@ +namespace Streetcode.Email.DAL +{ + public class Class1 + { + + } +} diff --git a/Streetcode/Streetcode.Email.DAL/Streetcode.Email.DAL.csproj b/Streetcode/Streetcode.Email.DAL/Streetcode.Email.DAL.csproj new file mode 100644 index 0000000..fa71b7a --- /dev/null +++ b/Streetcode/Streetcode.Email.DAL/Streetcode.Email.DAL.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/Streetcode/Streetcode.Email.WebAPI/Program.cs b/Streetcode/Streetcode.Email.WebAPI/Program.cs new file mode 100644 index 0000000..48863a6 --- /dev/null +++ b/Streetcode/Streetcode.Email.WebAPI/Program.cs @@ -0,0 +1,25 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/Streetcode/Streetcode.Email.WebAPI/Properties/launchSettings.json b/Streetcode/Streetcode.Email.WebAPI/Properties/launchSettings.json new file mode 100644 index 0000000..5d976c2 --- /dev/null +++ b/Streetcode/Streetcode.Email.WebAPI/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:48956", + "sslPort": 44318 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5179", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7094;http://localhost:5179", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj b/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj new file mode 100644 index 0000000..0c56541 --- /dev/null +++ b/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Streetcode/Streetcode.Email.WebAPI/appsettings.json b/Streetcode/Streetcode.Email.WebAPI/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/Streetcode/Streetcode.Email.WebAPI/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Streetcode/Streetcode.Email.XUnitTest/Streetcode.Email.XUnitTest.csproj b/Streetcode/Streetcode.Email.XUnitTest/Streetcode.Email.XUnitTest.csproj new file mode 100644 index 0000000..9c5b30a --- /dev/null +++ b/Streetcode/Streetcode.Email.XUnitTest/Streetcode.Email.XUnitTest.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + diff --git a/Streetcode/Streetcode.Email.XUnitTest/UnitTest1.cs b/Streetcode/Streetcode.Email.XUnitTest/UnitTest1.cs new file mode 100644 index 0000000..6383bfb --- /dev/null +++ b/Streetcode/Streetcode.Email.XUnitTest/UnitTest1.cs @@ -0,0 +1,11 @@ +namespace Streetcode.Email.XUnitTest +{ + public class UnitTest1 + { + [Fact] + public void Test1() + { + + } + } +} \ No newline at end of file diff --git a/Streetcode/Streetcode.sln b/Streetcode/Streetcode.sln index 17164d8..84d7f02 100644 --- a/Streetcode/Streetcode.sln +++ b/Streetcode/Streetcode.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.14.36717.8 d17.14 +# Visual Studio Version 18 +VisualStudioVersion = 18.3.11512.155 d18.3 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Streetcode.WebApi", "Streetcode.WebApi\Streetcode.WebApi.csproj", "{CAA32FB4-F481-4748-BFCE-33B0DBF433E8}" EndProject @@ -40,6 +40,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Streetcode.Auth.XUnitTest", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Streetcode.Resources", "Streetcode.Resources\Streetcode.Resources.csproj", "{42EC9582-F9D7-9F18-42AB-C0D335E17C5C}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Streetcode.Email", "Streetcode.Email", "{722B38C8-AA92-4391-8320-9CCBFCD6BBEF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Streetcode.Email.WebAPI", "Streetcode.Email.WebAPI\Streetcode.Email.WebAPI.csproj", "{F3953CBD-E8CE-40AA-A219-DED4518B6334}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Streetcode.Email.BLL", "Streetcode.Email.BLL\Streetcode.Email.BLL.csproj", "{CC733440-BA59-431D-B9CC-BB2B7EC0091B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Streetcode.Email.DAL", "Streetcode.Email.DAL\Streetcode.Email.DAL.csproj", "{072870A2-8064-40E2-B5C5-BF90B33F36BC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Streetcode.Email.XUnitTest", "Streetcode.Email.XUnitTest\Streetcode.Email.XUnitTest.csproj", "{360844AB-D2B0-49B1-A500-EAFC0F8A7659}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -96,6 +106,22 @@ Global {42EC9582-F9D7-9F18-42AB-C0D335E17C5C}.Debug|Any CPU.Build.0 = Debug|Any CPU {42EC9582-F9D7-9F18-42AB-C0D335E17C5C}.Release|Any CPU.ActiveCfg = Release|Any CPU {42EC9582-F9D7-9F18-42AB-C0D335E17C5C}.Release|Any CPU.Build.0 = Release|Any CPU + {F3953CBD-E8CE-40AA-A219-DED4518B6334}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3953CBD-E8CE-40AA-A219-DED4518B6334}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3953CBD-E8CE-40AA-A219-DED4518B6334}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3953CBD-E8CE-40AA-A219-DED4518B6334}.Release|Any CPU.Build.0 = Release|Any CPU + {CC733440-BA59-431D-B9CC-BB2B7EC0091B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC733440-BA59-431D-B9CC-BB2B7EC0091B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC733440-BA59-431D-B9CC-BB2B7EC0091B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC733440-BA59-431D-B9CC-BB2B7EC0091B}.Release|Any CPU.Build.0 = Release|Any CPU + {072870A2-8064-40E2-B5C5-BF90B33F36BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {072870A2-8064-40E2-B5C5-BF90B33F36BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {072870A2-8064-40E2-B5C5-BF90B33F36BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {072870A2-8064-40E2-B5C5-BF90B33F36BC}.Release|Any CPU.Build.0 = Release|Any CPU + {360844AB-D2B0-49B1-A500-EAFC0F8A7659}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {360844AB-D2B0-49B1-A500-EAFC0F8A7659}.Debug|Any CPU.Build.0 = Debug|Any CPU + {360844AB-D2B0-49B1-A500-EAFC0F8A7659}.Release|Any CPU.ActiveCfg = Release|Any CPU + {360844AB-D2B0-49B1-A500-EAFC0F8A7659}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -107,6 +133,11 @@ Global {D89E738A-A455-4D17-9C9D-581AE177E5AC} = {F69E76E4-84F5-4D70-A52B-E588CCEE716B} {00D85447-2B32-4B68-A4C2-1B467C42FD16} = {7F5CBCE1-AD14-4C06-8939-B595C1B25B47} {DF36FE52-8788-4490-8406-62132A7E6756} = {F69E76E4-84F5-4D70-A52B-E588CCEE716B} + {722B38C8-AA92-4391-8320-9CCBFCD6BBEF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {F3953CBD-E8CE-40AA-A219-DED4518B6334} = {722B38C8-AA92-4391-8320-9CCBFCD6BBEF} + {CC733440-BA59-431D-B9CC-BB2B7EC0091B} = {722B38C8-AA92-4391-8320-9CCBFCD6BBEF} + {072870A2-8064-40E2-B5C5-BF90B33F36BC} = {722B38C8-AA92-4391-8320-9CCBFCD6BBEF} + {360844AB-D2B0-49B1-A500-EAFC0F8A7659} = {722B38C8-AA92-4391-8320-9CCBFCD6BBEF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7D3D1FEF-DB8F-4A51-AD6E-0EE327AB534A} From 956639912f9d3565e4f0910e60bc8d41c6ab2ab9 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Wed, 25 Feb 2026 14:17:10 +0100 Subject: [PATCH 02/31] feat: create entity, hadlers, dbcontext, mapping, dtos, validation, services, interfaces --- Streetcode/Streetcode.Email.BLL/Class1.cs | 7 --- .../Configs/EmailConfiguration.cs | 14 ++++++ .../DTO/CreateFeedback.cs | 8 ++++ .../Streetcode.Email.BLL/DTO/FeedbackDTO.cs | 9 ++++ .../Interfaces/IEmailService.cs | 9 ++++ .../Mapping/FeedbackProfile.cs | 15 +++++++ .../MediatR/Feedback/FeedbackDTOValidator.cs | 20 +++++++++ .../MediatR/Feedback/SendFeedbackCommand.cs | 8 ++++ .../Feedback/SendFeedbackCommandValidator.cs | 16 +++++++ .../MediatR/Feedback/SendFeedbackHandler.cs | 43 +++++++++++++++++++ .../Services/EmailService.cs | 40 +++++++++++++++++ .../Streetcode.Email.BLL.csproj | 15 +++++++ Streetcode/Streetcode.Email.DAL/Class1.cs | 7 --- .../Streetcode.Email.DAL/Entities/Feedback.cs | 18 ++++++++ .../Persistence/EmailDbContext.cs | 19 ++++++++ .../Streetcode.Email.DAL.csproj | 13 ++++++ Streetcode/Streetcode.sln | 2 +- 17 files changed, 248 insertions(+), 15 deletions(-) delete mode 100644 Streetcode/Streetcode.Email.BLL/Class1.cs create mode 100644 Streetcode/Streetcode.Email.BLL/Configs/EmailConfiguration.cs create mode 100644 Streetcode/Streetcode.Email.BLL/DTO/CreateFeedback.cs create mode 100644 Streetcode/Streetcode.Email.BLL/DTO/FeedbackDTO.cs create mode 100644 Streetcode/Streetcode.Email.BLL/Interfaces/IEmailService.cs create mode 100644 Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs create mode 100644 Streetcode/Streetcode.Email.BLL/MediatR/Feedback/FeedbackDTOValidator.cs create mode 100644 Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommand.cs create mode 100644 Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommandValidator.cs create mode 100644 Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackHandler.cs create mode 100644 Streetcode/Streetcode.Email.BLL/Services/EmailService.cs delete mode 100644 Streetcode/Streetcode.Email.DAL/Class1.cs create mode 100644 Streetcode/Streetcode.Email.DAL/Entities/Feedback.cs create mode 100644 Streetcode/Streetcode.Email.DAL/Persistence/EmailDbContext.cs diff --git a/Streetcode/Streetcode.Email.BLL/Class1.cs b/Streetcode/Streetcode.Email.BLL/Class1.cs deleted file mode 100644 index 675b809..0000000 --- a/Streetcode/Streetcode.Email.BLL/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Streetcode.Email.BLL -{ - public class Class1 - { - - } -} diff --git a/Streetcode/Streetcode.Email.BLL/Configs/EmailConfiguration.cs b/Streetcode/Streetcode.Email.BLL/Configs/EmailConfiguration.cs new file mode 100644 index 0000000..de289d4 --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/Configs/EmailConfiguration.cs @@ -0,0 +1,14 @@ +namespace Streetcode.Email.BLL.Configs +{ + public class EmailConfiguration + { + public const string SectionName = "EmailConfiguration"; + + public string FromAddress { get; set; } + public string AdminAddress { get; set; } + public string SmtpServer { get; set; } + public int Port { get; set; } + public string SmtpUser { get; set; } + public string SmtpPassword { get; set; } + } +} diff --git a/Streetcode/Streetcode.Email.BLL/DTO/CreateFeedback.cs b/Streetcode/Streetcode.Email.BLL/DTO/CreateFeedback.cs new file mode 100644 index 0000000..a2f882c --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/DTO/CreateFeedback.cs @@ -0,0 +1,8 @@ +namespace Streetcode.Email.BLL.DTO +{ + public class CreateFeedback + { + public string Email { get; set; } + public string Message { get; set; } + } +} diff --git a/Streetcode/Streetcode.Email.BLL/DTO/FeedbackDTO.cs b/Streetcode/Streetcode.Email.BLL/DTO/FeedbackDTO.cs new file mode 100644 index 0000000..3077d37 --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/DTO/FeedbackDTO.cs @@ -0,0 +1,9 @@ +namespace Streetcode.Email.BLL.DTO +{ + public class FeedbackDTO + { + public int Id { get; set; } + public string Email { get; set; } + public string Message { get; set; } + } +} diff --git a/Streetcode/Streetcode.Email.BLL/Interfaces/IEmailService.cs b/Streetcode/Streetcode.Email.BLL/Interfaces/IEmailService.cs new file mode 100644 index 0000000..e669f5e --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/Interfaces/IEmailService.cs @@ -0,0 +1,9 @@ +using Streetcode.Email.BLL.DTO; + +namespace Streetcode.Email.BLL.Interfaces +{ + public interface IEmailService + { + Task SendEmailAsync(FeedbackDTO feedback); + } +} \ No newline at end of file diff --git a/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs b/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs new file mode 100644 index 0000000..e04772e --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using Streetcode.Email.BLL.DTO; +using Streetcode.Email.DAL.Entities; + +namespace Streetcode.Email.BLL.Mapping +{ + public class FeedbackProfile : Profile + { + public FeedbackProfile() + { + CreateMap().ReverseMap(); + CreateMap(); + } + } +} diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/FeedbackDTOValidator.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/FeedbackDTOValidator.cs new file mode 100644 index 0000000..ef539d1 --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/FeedbackDTOValidator.cs @@ -0,0 +1,20 @@ +using FluentValidation; +using Streetcode.Email.BLL.DTO; + +namespace Streetcode.Email.BLL.MediatR.Feedback +{ + public class FeedbackDTOValidator : AbstractValidator + { + public FeedbackDTOValidator() + { + RuleFor(x => x.Email) + .NotEmpty() + .EmailAddress(); + + RuleFor(x => x.Message) + .NotEmpty() + .MinimumLength(5) + .MaximumLength(100); + } + } +} diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommand.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommand.cs new file mode 100644 index 0000000..896a587 --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommand.cs @@ -0,0 +1,8 @@ +using FluentResults; +using MediatR; +using Streetcode.Email.BLL.DTO; + +namespace Streetcode.Email.BLL.MediatR.Feedback +{ + public record SendFeedbackCommand(FeedbackDTO Feedback) : IRequest>; +} diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommandValidator.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommandValidator.cs new file mode 100644 index 0000000..30dc6bc --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommandValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; +using Streetcode.Resources; + +namespace Streetcode.Email.BLL.MediatR.Feedback +{ + public class SendFeedbackCommandValidator : AbstractValidator + { + public SendFeedbackCommandValidator() + { + RuleFor(x => x.Feedback) + .NotNull() + .WithMessage(Messages.Error_CommandDataRequired) + .SetValidator(new FeedbackDTOValidator()); + } + } +} diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackHandler.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackHandler.cs new file mode 100644 index 0000000..cc5e2fe --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackHandler.cs @@ -0,0 +1,43 @@ +using AutoMapper; +using FluentResults; +using MediatR; +using Microsoft.Extensions.Logging; +using Streetcode.Email.BLL.DTO; +using Streetcode.Email.DAL.Persistence; +using Streetcode.Resources; +using FeedbackEntity = Streetcode.Email.DAL.Entities.Feedback; + +namespace Streetcode.Email.BLL.MediatR.Feedback +{ + public class SendFeedbackHandler : IRequestHandler> + { + private readonly EmailDbContext _context; + private readonly IMapper _mapper; + private readonly ILogger _logger; + + public SendFeedbackHandler(EmailDbContext context, IMapper mapper, ILogger logger) + { + _context = context; + _mapper = mapper; + _logger = logger; + } + + public async Task> Handle(SendFeedbackCommand request, CancellationToken cancellationToken) + { + var feedbackEntity = _mapper.Map(request.Feedback); + + _context.Feedbacks.Add(feedbackEntity); + + var rowsAffected = await _context.SaveChangesAsync(cancellationToken); + + if (rowsAffected <= 0) + { + var errorMsg = Messages.Error_FailedToCreateEntity; + _logger.LogError(errorMsg); + return Result.Fail(errorMsg); + } + + return Result.Ok(Unit.Value); + } + } +} diff --git a/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs b/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs new file mode 100644 index 0000000..b955113 --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs @@ -0,0 +1,40 @@ +using MailKit.Net.Smtp; +using MimeKit; +using MailKit.Security; +using Microsoft.Extensions.Options; +using Streetcode.Email.BLL.Configs; +using Streetcode.Email.BLL.DTO; +using Streetcode.Email.BLL.Interfaces; + +namespace Streetcode.Email.BLL.Services +{ + public class EmailService : IEmailService + { + private readonly EmailConfiguration _emailConfig; + + public EmailService(IOptions emailConfig) + { + _emailConfig = emailConfig; + } + + public async Task SendEmailAsync(FeedbackDTO feedback) + { + var message = new MimeMessage(); + message.From.Add(new MailboxAddress("Streetcode", _emailConfig.FromAddress)); + message.To.Add(new MailboxAddress("Streetcode Admin", _emailConfig.AdminAddress)); + message.Subject = $"New feedback from Streetcode User"; + message.Body = new TextPart("plain") + { + Text = $"Користувач {feedback.Email} залишив повідомлення:\n\n{feedback.Message}" + }; + + using var client = new SmtpClient(); + + await client.ConnectAsync(_emailConfig.SmtpServer, _emailConfig.Port, SecureSocketOptions.Auto); + await client.AuthenticateAsync(_emailConfig.SmtpUser, _emailConfig.SmtpPassword); + + await client.SendAsync(message); + await client.DisconnectAsync(true); + } + } +} diff --git a/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj b/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj index fa71b7a..4fc22ec 100644 --- a/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj +++ b/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj @@ -6,4 +6,19 @@ enable + + + + + + + + + + + + + + + diff --git a/Streetcode/Streetcode.Email.DAL/Class1.cs b/Streetcode/Streetcode.Email.DAL/Class1.cs deleted file mode 100644 index 4009b00..0000000 --- a/Streetcode/Streetcode.Email.DAL/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Streetcode.Email.DAL -{ - public class Class1 - { - - } -} diff --git a/Streetcode/Streetcode.Email.DAL/Entities/Feedback.cs b/Streetcode/Streetcode.Email.DAL/Entities/Feedback.cs new file mode 100644 index 0000000..7262364 --- /dev/null +++ b/Streetcode/Streetcode.Email.DAL/Entities/Feedback.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Streetcode.Email.DAL.Entities; + +[Table("feedback", Schema = "email")] +public class Feedback + { + [Key] + public int Id { get; set; } + [Required] + public string? Email { get; set; } + [Required] + [MinLength(5)] + [MaxLength(100)] + public string? Message { get; set; } + } + diff --git a/Streetcode/Streetcode.Email.DAL/Persistence/EmailDbContext.cs b/Streetcode/Streetcode.Email.DAL/Persistence/EmailDbContext.cs new file mode 100644 index 0000000..4655b10 --- /dev/null +++ b/Streetcode/Streetcode.Email.DAL/Persistence/EmailDbContext.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Streetcode.Email.DAL.Entities; + +namespace Streetcode.Email.DAL.Persistence +{ + public class EmailDbContext : DbContext + { + public EmailDbContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Feedbacks { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + } + } +} diff --git a/Streetcode/Streetcode.Email.DAL/Streetcode.Email.DAL.csproj b/Streetcode/Streetcode.Email.DAL/Streetcode.Email.DAL.csproj index fa71b7a..51b57ae 100644 --- a/Streetcode/Streetcode.Email.DAL/Streetcode.Email.DAL.csproj +++ b/Streetcode/Streetcode.Email.DAL/Streetcode.Email.DAL.csproj @@ -6,4 +6,17 @@ enable + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/Streetcode/Streetcode.sln b/Streetcode/Streetcode.sln index 84d7f02..51fc40f 100644 --- a/Streetcode/Streetcode.sln +++ b/Streetcode/Streetcode.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.3.11512.155 d18.3 +VisualStudioVersion = 18.3.11512.155 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Streetcode.WebApi", "Streetcode.WebApi\Streetcode.WebApi.csproj", "{CAA32FB4-F481-4748-BFCE-33B0DBF433E8}" EndProject From 1218d736a1937bd2f04b1918ef0f138d33db8af8 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Wed, 25 Feb 2026 17:45:19 +0100 Subject: [PATCH 03/31] feat: finish emailservice, created docker-compose --- .../MediatR/Feedback/SendFeedbackHandler.cs | 11 +++++++++- .../Services/EmailService.cs | 4 ++-- .../Streetcode.Email.BLL.csproj | 2 ++ .../Extensions/ServiceCollectionExtensions.cs | 18 +++++++++++++++++ .../Streetcode.Email.WebAPI.csproj | 4 ++++ .../Streetcode.Email.WebAPI/appsettings.json | 11 +++++++++- Streetcode/Streetcode.sln | 1 + Streetcode/docker-compose.yml | 20 +++++++++++++++++++ 8 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs create mode 100644 Streetcode/docker-compose.yml diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackHandler.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackHandler.cs index cc5e2fe..62c9f7c 100644 --- a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackHandler.cs +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackHandler.cs @@ -1,8 +1,10 @@ using AutoMapper; using FluentResults; +using Hangfire; using MediatR; using Microsoft.Extensions.Logging; using Streetcode.Email.BLL.DTO; +using Streetcode.Email.BLL.Interfaces; using Streetcode.Email.DAL.Persistence; using Streetcode.Resources; using FeedbackEntity = Streetcode.Email.DAL.Entities.Feedback; @@ -14,12 +16,14 @@ public class SendFeedbackHandler : IRequestHandler _logger; + private readonly IBackgroundJobClient _backgroundJob; - public SendFeedbackHandler(EmailDbContext context, IMapper mapper, ILogger logger) + public SendFeedbackHandler(EmailDbContext context, IMapper mapper, ILogger logger, IBackgroundJobClient backgroundJob) { _context = context; _mapper = mapper; _logger = logger; + _backgroundJob = backgroundJob; } public async Task> Handle(SendFeedbackCommand request, CancellationToken cancellationToken) @@ -37,6 +41,11 @@ public async Task> Handle(SendFeedbackCommand request, Cancellation return Result.Fail(errorMsg); } + _backgroundJob.Enqueue(emailService => + emailService.SendEmailAsync(request.Feedback)); + + _logger.LogInformation("Feedback saved to DB and email task enqueued for {Email}", request.Feedback.Email); + return Result.Ok(Unit.Value); } } diff --git a/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs b/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs index b955113..da29164 100644 --- a/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs +++ b/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs @@ -12,9 +12,9 @@ public class EmailService : IEmailService { private readonly EmailConfiguration _emailConfig; - public EmailService(IOptions emailConfig) + public EmailService(IOptions options) { - _emailConfig = emailConfig; + _emailConfig = options.Value; } public async Task SendEmailAsync(FeedbackDTO feedback) diff --git a/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj b/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj index 4fc22ec..fe7144f 100644 --- a/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj +++ b/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj @@ -11,9 +11,11 @@ + + diff --git a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..f81055b --- /dev/null +++ b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,18 @@ +using Streetcode.Email.BLL.Configs; +using Streetcode.Email.BLL.Interfaces; +using Streetcode.Email.BLL.Services; + +namespace Streetcode.Email.WebAPI.Extensions +{ + public static class ServiceCollectionExtensions + { + public static void AddCustomServices(this IServiceCollection services, IConfiguration configuration) + { + services.Configure( + configuration.GetSection("EmailConfiguration")); + + services.AddScoped(); + + } + } +} diff --git a/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj b/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj index 0c56541..11ae0a2 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj +++ b/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj @@ -14,4 +14,8 @@ + + + + diff --git a/Streetcode/Streetcode.Email.WebAPI/appsettings.json b/Streetcode/Streetcode.Email.WebAPI/appsettings.json index 10f68b8..7236305 100644 --- a/Streetcode/Streetcode.Email.WebAPI/appsettings.json +++ b/Streetcode/Streetcode.Email.WebAPI/appsettings.json @@ -5,5 +5,14 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + + "EmailConfiguration": { + "FromAddress": "system@streetcode.ua", + "AdminAddress": "admin@streetcode.ua", + "SmtpServer": "smtp.gmail.com", + "Port": 587, + "SmtpUser": "your_login@gmail.com", + "SmtpPassword": "your_app_password" + } } diff --git a/Streetcode/Streetcode.sln b/Streetcode/Streetcode.sln index 51fc40f..19b9f30 100644 --- a/Streetcode/Streetcode.sln +++ b/Streetcode/Streetcode.sln @@ -20,6 +20,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{95E863CE-C3B4-4DBC-8C15-957E63BA45B6}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + docker-compose.yml = docker-compose.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" diff --git a/Streetcode/docker-compose.yml b/Streetcode/docker-compose.yml new file mode 100644 index 0000000..b602120 --- /dev/null +++ b/Streetcode/docker-compose.yml @@ -0,0 +1,20 @@ +version: '3.8' + +services: + rabbitmq: + image: rabbitmq:3-management + container_name: streetcode-rabbitmq + restart: always + ports: + - "5672:5672" + - "15672:15672" + environment: + - RABBITMQ_DEFAULT_USER=guest + - RABBITMQ_DEFAULT_PASS=guest + + redis: + image: redis:latest + container_name: streetcode-redis + restart: always + ports: + - "6379:6379" \ No newline at end of file From 2f46d9c3d5695f1ff798a3c7d4cb13837d9b2edc Mon Sep 17 00:00:00 2001 From: Darsicl Date: Wed, 25 Feb 2026 17:55:57 +0100 Subject: [PATCH 04/31] feat: create appsetings --- .../Extensions/ServiceCollectionExtensions.cs | 56 ++++++++++++++++++- .../Streetcode.Email.WebAPI.csproj | 2 + .../Streetcode.Email.WebAPI/appsettings.json | 19 ++++++- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs index f81055b..104927e 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs +++ b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs @@ -1,11 +1,65 @@ -using Streetcode.Email.BLL.Configs; +using Hangfire; +using Microsoft.EntityFrameworkCore; +using Streetcode.Email.BLL.Configs; using Streetcode.Email.BLL.Interfaces; using Streetcode.Email.BLL.Services; +using Streetcode.Email.DAL.Persistence; namespace Streetcode.Email.WebAPI.Extensions { public static class ServiceCollectionExtensions { + public static void AddApplicationServices(this IServiceCollection services, ConfigurationManager configuration) + { + var connectionString = configuration.GetConnectionString("DefaultConnection"); + var emailConfig = configuration.GetSection("EmailConfiguration").Get(); + services.AddSingleton(emailConfig); + + services.AddDbContext(options => + { + options.UseSqlServer(connectionString, opt => + { + opt.MigrationsAssembly(typeof(EmailDbContext).Assembly.GetName().Name); + opt.MigrationsHistoryTable("__EFMigrationsHistory", schema: "entity_framework"); + }); + }); + + services.AddHangfire(config => + { + config.UseSqlServerStorage(connectionString); + }); + + services.AddHangfireServer(); + + var corsConfig = configuration.GetSection("CORS").Get(); + services.AddCors(opt => + { + opt.AddDefaultPolicy(policy => + { + if (corsConfig?.AllowedOrigins?.Any() == true && !corsConfig.AllowedOrigins.Contains("*")) + { + policy.WithOrigins(corsConfig.AllowedOrigins.ToArray()); + } + else + { + policy.SetIsOriginAllowed(origin => true); + } + + policy.AllowAnyHeader() + .AllowAnyMethod(); + }); + }); + + services.AddHsts(opt => + { + opt.Preload = true; + opt.IncludeSubDomains = true; + opt.MaxAge = TimeSpan.FromDays(30); + }); + + services.AddLogging(); + services.AddControllers(); + } public static void AddCustomServices(this IServiceCollection services, IConfiguration configuration) { services.Configure( diff --git a/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj b/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj index 11ae0a2..2c42fd0 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj +++ b/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj @@ -7,6 +7,8 @@ + + diff --git a/Streetcode/Streetcode.Email.WebAPI/appsettings.json b/Streetcode/Streetcode.Email.WebAPI/appsettings.json index 7236305..f67708b 100644 --- a/Streetcode/Streetcode.Email.WebAPI/appsettings.json +++ b/Streetcode/Streetcode.Email.WebAPI/appsettings.json @@ -14,5 +14,22 @@ "Port": 587, "SmtpUser": "your_login@gmail.com", "SmtpPassword": "your_app_password" - } + }, + + "CORS": { + "AllowedOrigins": [], + "AllowedHeaders": [], + "AllowedMethods": [], + "PreflightMaxAge": 1 + }, + "Jwt": { + "Key": "NiceSecretKey", + "Issuer": "StreetcodeAuth", + "Audience": "StreetcodeClient" + }, + "RabbitMQ": { + "Host": "localhost", + "Username": "SomeUsername", + "Password": "SomePassword" + } } From 88efed73784742409a4e24178c186cfdb8191efc Mon Sep 17 00:00:00 2001 From: Darsicl Date: Thu, 26 Feb 2026 12:31:19 +0100 Subject: [PATCH 05/31] feat: crate rabbitmq consumer --- .../Exceptions/ValidationException.cs | 29 +++++++ .../MediatR/Behavior/ValidatorBehavior.cs | 33 ++++++++ .../Services/EmailConsumer.cs | 78 +++++++++++++++++++ .../Streetcode.Email.BLL.csproj | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 26 ++++++- .../Extensions/WebApplicationExtensions.cs | 22 ++++++ 6 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 Streetcode/Streetcode.Email.BLL/Exceptions/ValidationException.cs create mode 100644 Streetcode/Streetcode.Email.BLL/MediatR/Behavior/ValidatorBehavior.cs create mode 100644 Streetcode/Streetcode.Email.BLL/Services/EmailConsumer.cs create mode 100644 Streetcode/Streetcode.Email.WebAPI/Extensions/WebApplicationExtensions.cs diff --git a/Streetcode/Streetcode.Email.BLL/Exceptions/ValidationException.cs b/Streetcode/Streetcode.Email.BLL/Exceptions/ValidationException.cs new file mode 100644 index 0000000..d2c6dd1 --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/Exceptions/ValidationException.cs @@ -0,0 +1,29 @@ +using FluentValidation.Results; + +namespace Streetcode.Email.BLL.Exceptions +{ + public class ValidationException : Exception + { + public ValidationException() : base("One or more validation failures have occurred.") + { + Errors = new Dictionary(); + } + + public ValidationException(IEnumerable failures) : this() + { + Errors = failures + .GroupBy(e => e.PropertyName, e => e.ErrorMessage) + .ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray()); + } + + public ValidationException(string propertyName, string errorMessage) : this() + { + Errors = new Dictionary + { + { propertyName, new[] { errorMessage } } + }; + } + + public IDictionary Errors { get; } + } +} \ No newline at end of file diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Behavior/ValidatorBehavior.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Behavior/ValidatorBehavior.cs new file mode 100644 index 0000000..36213eb --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Behavior/ValidatorBehavior.cs @@ -0,0 +1,33 @@ +using FluentValidation; +using MediatR; + +namespace Streetcode.Email.BLL.MediatR.Behavior +{ + public class ValidatorBehavior : IPipelineBehavior + where TRequest : IRequest + { + private readonly IEnumerable> _validators; + + public ValidatorBehavior(IEnumerable> validators) + { + _validators = validators; + } + + public Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) + { + var context = new ValidationContext(request); + var failures = _validators + .Select(v => v.Validate(context)) + .SelectMany(result => result.Errors) + .Where(f => f != null) + .ToList(); + + if (failures.Count != 0) + { + throw new Exceptions.ValidationException(failures); + } + + return next(); + } + } +} diff --git a/Streetcode/Streetcode.Email.BLL/Services/EmailConsumer.cs b/Streetcode/Streetcode.Email.BLL/Services/EmailConsumer.cs new file mode 100644 index 0000000..90102ba --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/Services/EmailConsumer.cs @@ -0,0 +1,78 @@ +using System.Text; +using System.Text.Json; +using MediatR; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; +using Streetcode.Email.BLL.DTO; +using Streetcode.Email.BLL.MediatR.Feedback; + +namespace Streetcode.Email.BLL.Services +{ + public class EmailConsumer : BackgroundService + { + private readonly IServiceProvider _serviceProvider; + private readonly IConnection _connection; + private readonly IModel _model; + private const string QueueName = "email_queue"; + public EmailConsumer(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + + var factory = new ConnectionFactory() { HostName = "localhost" }; + _connection = factory.CreateConnection(); + _model = _connection.CreateModel(); + + _model.QueueDeclare(queue: QueueName, + durable: true, + exclusive: false, + autoDelete: false, + arguments: null); + } + + protected override Task ExecuteAsync(CancellationToken stoppingToken) + { + var consumer = new EventingBasicConsumer(_model); + + consumer.Received += async (model, ea) => + { + var body = ea.Body.ToArray(); + var message = Encoding.UTF8.GetString(body); + + try + { + var feedback = JsonSerializer.Deserialize(message, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + if (feedback != null) + { + using var scope = _serviceProvider.CreateScope(); + var mediator = scope.ServiceProvider.GetRequiredService(); + + await mediator.Send(new SendFeedbackCommand(feedback)); + } + + _model.BasicAck(ea.DeliveryTag, false); + } + catch (Exception ex) + { + _model.BasicNack(ea.DeliveryTag, false, true); + } + }; + + _model.BasicConsume(queue: QueueName, autoAck: false, consumer: consumer); + return Task.CompletedTask; + } + + public override void Dispose() + { + _model.Close(); + _connection.Close(); + base.Dispose(); + } + } +} + diff --git a/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj b/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj index fe7144f..16efa1f 100644 --- a/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj +++ b/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj @@ -15,7 +15,7 @@ - + diff --git a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs index 104927e..00e3721 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs +++ b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ using Hangfire; +using Hangfire.Redis.StackExchange; using Microsoft.EntityFrameworkCore; using Streetcode.Email.BLL.Configs; using Streetcode.Email.BLL.Interfaces; @@ -12,8 +13,7 @@ public static class ServiceCollectionExtensions public static void AddApplicationServices(this IServiceCollection services, ConfigurationManager configuration) { var connectionString = configuration.GetConnectionString("DefaultConnection"); - var emailConfig = configuration.GetSection("EmailConfiguration").Get(); - services.AddSingleton(emailConfig); + var redisConnection = configuration.GetConnectionString("Redis"); services.AddDbContext(options => { @@ -26,7 +26,7 @@ public static void AddApplicationServices(this IServiceCollection services, Conf services.AddHangfire(config => { - config.UseSqlServerStorage(connectionString); + config.UseRedisStorage(redisConnection); }); services.AddHangfireServer(); @@ -68,5 +68,25 @@ public static void AddCustomServices(this IServiceCollection services, IConfigur services.AddScoped(); } + + public static void AddRabbitMQConsumer(this IServiceCollection services) + { + services.AddHostedService(); + } + + public class CorsConfiguration + { + public List AllowedOrigins { get; set; } + public List AllowedHeaders { get; set; } + public List AllowedMethods { get; set; } + public int PreflightMaxAge { get; set; } + } + + public class JwtOptions + { + public string Key { get; set; } + public string Issuer { get; set; } + public string Audience { get; set; } + } } } diff --git a/Streetcode/Streetcode.Email.WebAPI/Extensions/WebApplicationExtensions.cs b/Streetcode/Streetcode.Email.WebAPI/Extensions/WebApplicationExtensions.cs new file mode 100644 index 0000000..a3a435c --- /dev/null +++ b/Streetcode/Streetcode.Email.WebAPI/Extensions/WebApplicationExtensions.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; +using Streetcode.Email.DAL.Persistence; + +namespace Streetcode.Email.WebAPI.Extensions +{ + public static class WebApplicationExtensions + { + public static async Task ApplyMigrations(this WebApplication app) + { + var logger = app.Services.GetRequiredService>(); + try + { + var emailContext = app.Services.GetRequiredService(); + await emailContext.Database.MigrateAsync(); + } + catch (Exception ex) + { + logger.LogError(ex, "An error occured during startup migration"); + } + } + } +} From 037d6b58da05964959f7cb9e97799e8fc29db02e Mon Sep 17 00:00:00 2001 From: Darsicl Date: Thu, 26 Feb 2026 12:45:23 +0100 Subject: [PATCH 06/31] fix: prepared for migration --- .../DTO/CreateFeedback.cs | 8 ------ .../Streetcode.Email.BLL/DTO/FeedbackDTO.cs | 1 - .../Persistence/EmailDbContext.cs | 11 +++++++- .../Extensions/ServiceCollectionExtensions.cs | 26 ------------------- .../Extensions/WebApplicationExtensions.cs | 11 +++++--- Streetcode/Streetcode.Email.WebAPI/Program.cs | 3 --- .../Streetcode.Email.WebAPI/appsettings.json | 12 --------- 7 files changed, 18 insertions(+), 54 deletions(-) delete mode 100644 Streetcode/Streetcode.Email.BLL/DTO/CreateFeedback.cs diff --git a/Streetcode/Streetcode.Email.BLL/DTO/CreateFeedback.cs b/Streetcode/Streetcode.Email.BLL/DTO/CreateFeedback.cs deleted file mode 100644 index a2f882c..0000000 --- a/Streetcode/Streetcode.Email.BLL/DTO/CreateFeedback.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Streetcode.Email.BLL.DTO -{ - public class CreateFeedback - { - public string Email { get; set; } - public string Message { get; set; } - } -} diff --git a/Streetcode/Streetcode.Email.BLL/DTO/FeedbackDTO.cs b/Streetcode/Streetcode.Email.BLL/DTO/FeedbackDTO.cs index 3077d37..0a7c374 100644 --- a/Streetcode/Streetcode.Email.BLL/DTO/FeedbackDTO.cs +++ b/Streetcode/Streetcode.Email.BLL/DTO/FeedbackDTO.cs @@ -2,7 +2,6 @@ { public class FeedbackDTO { - public int Id { get; set; } public string Email { get; set; } public string Message { get; set; } } diff --git a/Streetcode/Streetcode.Email.DAL/Persistence/EmailDbContext.cs b/Streetcode/Streetcode.Email.DAL/Persistence/EmailDbContext.cs index 4655b10..3dfec32 100644 --- a/Streetcode/Streetcode.Email.DAL/Persistence/EmailDbContext.cs +++ b/Streetcode/Streetcode.Email.DAL/Persistence/EmailDbContext.cs @@ -12,8 +12,17 @@ public EmailDbContext(DbContextOptions options) public DbSet Feedbacks { get; set; } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + protected override void OnModelCreating(ModelBuilder modelBuilder) { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(entity => + { + entity.ToTable("Feedbacks"); + entity.HasKey(e => e.Id); + entity.Property(e => e.Email).IsRequired().HasMaxLength(256); + entity.Property(e => e.Message).IsRequired(); + }); } } } diff --git a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs index 00e3721..710fb93 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs +++ b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs @@ -31,32 +31,6 @@ public static void AddApplicationServices(this IServiceCollection services, Conf services.AddHangfireServer(); - var corsConfig = configuration.GetSection("CORS").Get(); - services.AddCors(opt => - { - opt.AddDefaultPolicy(policy => - { - if (corsConfig?.AllowedOrigins?.Any() == true && !corsConfig.AllowedOrigins.Contains("*")) - { - policy.WithOrigins(corsConfig.AllowedOrigins.ToArray()); - } - else - { - policy.SetIsOriginAllowed(origin => true); - } - - policy.AllowAnyHeader() - .AllowAnyMethod(); - }); - }); - - services.AddHsts(opt => - { - opt.Preload = true; - opt.IncludeSubDomains = true; - opt.MaxAge = TimeSpan.FromDays(30); - }); - services.AddLogging(); services.AddControllers(); } diff --git a/Streetcode/Streetcode.Email.WebAPI/Extensions/WebApplicationExtensions.cs b/Streetcode/Streetcode.Email.WebAPI/Extensions/WebApplicationExtensions.cs index a3a435c..c47d63c 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Extensions/WebApplicationExtensions.cs +++ b/Streetcode/Streetcode.Email.WebAPI/Extensions/WebApplicationExtensions.cs @@ -7,15 +7,20 @@ public static class WebApplicationExtensions { public static async Task ApplyMigrations(this WebApplication app) { - var logger = app.Services.GetRequiredService>(); + using var scope = app.Services.CreateScope(); + var services = scope.ServiceProvider; + var logger = services.GetRequiredService>(); + try { - var emailContext = app.Services.GetRequiredService(); + var emailContext = services.GetRequiredService(); await emailContext.Database.MigrateAsync(); + logger.LogInformation("Database migrated successfully."); } catch (Exception ex) { - logger.LogError(ex, "An error occured during startup migration"); + logger.LogError(ex, "An error occurred during startup migration"); + throw; } } } diff --git a/Streetcode/Streetcode.Email.WebAPI/Program.cs b/Streetcode/Streetcode.Email.WebAPI/Program.cs index 48863a6..f86b1dd 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Program.cs +++ b/Streetcode/Streetcode.Email.WebAPI/Program.cs @@ -1,15 +1,12 @@ var builder = WebApplication.CreateBuilder(args); -// Add services to the container. builder.Services.AddControllers(); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); -// Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); diff --git a/Streetcode/Streetcode.Email.WebAPI/appsettings.json b/Streetcode/Streetcode.Email.WebAPI/appsettings.json index f67708b..9c25a3b 100644 --- a/Streetcode/Streetcode.Email.WebAPI/appsettings.json +++ b/Streetcode/Streetcode.Email.WebAPI/appsettings.json @@ -15,18 +15,6 @@ "SmtpUser": "your_login@gmail.com", "SmtpPassword": "your_app_password" }, - - "CORS": { - "AllowedOrigins": [], - "AllowedHeaders": [], - "AllowedMethods": [], - "PreflightMaxAge": 1 - }, - "Jwt": { - "Key": "NiceSecretKey", - "Issuer": "StreetcodeAuth", - "Audience": "StreetcodeClient" - }, "RabbitMQ": { "Host": "localhost", "Username": "SomeUsername", From 636ddf37923c596f0ede045d2d9c9c29248c3ad8 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Thu, 26 Feb 2026 13:42:57 +0100 Subject: [PATCH 07/31] fix: fix appsettings --- .../Streetcode.Email.WebAPI/appsettings.json | 9 +++++++-- Streetcode/Streetcode.sln | 1 + Streetcode/docker-compose.yml | 16 +++++++++++++++- Streetcode/dockerpassword.env | 1 + 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 Streetcode/dockerpassword.env diff --git a/Streetcode/Streetcode.Email.WebAPI/appsettings.json b/Streetcode/Streetcode.Email.WebAPI/appsettings.json index 9c25a3b..5b58afa 100644 --- a/Streetcode/Streetcode.Email.WebAPI/appsettings.json +++ b/Streetcode/Streetcode.Email.WebAPI/appsettings.json @@ -13,11 +13,16 @@ "SmtpServer": "smtp.gmail.com", "Port": 587, "SmtpUser": "your_login@gmail.com", - "SmtpPassword": "your_app_password" + "SmtpPassword": "password" }, "RabbitMQ": { "Host": "localhost", "Username": "SomeUsername", "Password": "SomePassword" - } + }, + + "ConnectionStrings": { +"DefaultConnection": "ConnectionString", + "Redis": "Redis" + } } diff --git a/Streetcode/Streetcode.sln b/Streetcode/Streetcode.sln index 19b9f30..8dd48f9 100644 --- a/Streetcode/Streetcode.sln +++ b/Streetcode/Streetcode.sln @@ -21,6 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig docker-compose.yml = docker-compose.yml + dockerpassword.env = dockerpassword.env EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" diff --git a/Streetcode/docker-compose.yml b/Streetcode/docker-compose.yml index b602120..7c8ed0c 100644 --- a/Streetcode/docker-compose.yml +++ b/Streetcode/docker-compose.yml @@ -17,4 +17,18 @@ services: container_name: streetcode-redis restart: always ports: - - "6379:6379" \ No newline at end of file + - "6379:6379" + + sqlserver: + image: mcr.microsoft.com/mssql/server:2022-latest + container_name: streetcode-email-db + ports: + - "1433:1433" + environment: + - ACCEPT_EULA=Y + - MSSQL_SA_PASSWORD=${DB_PASSWORD} + healthcheck: + test: ["CMD", "/opt/mssql-tools/bin/sqlcmd", "-U", "sa", "-P", "${DB_PASSWORD}", "-Q", "SELECT 1"] + interval: 10s + timeout: 3s + retries: 10 \ No newline at end of file diff --git a/Streetcode/dockerpassword.env b/Streetcode/dockerpassword.env new file mode 100644 index 0000000..e5577c2 --- /dev/null +++ b/Streetcode/dockerpassword.env @@ -0,0 +1 @@ +DB_PASSWORD=5D71981AA0674A1295109C5995D6779F \ No newline at end of file From ba87c7632e13e2e47878c7556874d36f35ee24fa Mon Sep 17 00:00:00 2001 From: Darsicl Date: Fri, 27 Feb 2026 17:41:31 +0100 Subject: [PATCH 08/31] fix: fix problem with migration --- .../Mapping/FeedbackProfile.cs | 1 - .../Services/EmailConsumer.cs | 72 ++----------------- .../Streetcode.Email.BLL.csproj | 1 + .../20260227122516_Name.Designer.cs | 52 ++++++++++++++ .../Migrations/20260227122516_Name.cs | 35 +++++++++ .../Migrations/EmailDbContextModelSnapshot.cs | 49 +++++++++++++ .../Extensions/ServiceCollectionExtensions.cs | 50 +++++++------ Streetcode/Streetcode.Email.WebAPI/Program.cs | 25 ++++--- .../Properties/launchSettings.json | 8 +-- .../Streetcode.Email.WebAPI.csproj | 8 +++ .../Streetcode.Email.WebAPI/appsettings.json | 14 ---- Streetcode/docker-compose.yml | 4 +- Streetcode/dockerpassword.env | 2 +- 13 files changed, 204 insertions(+), 117 deletions(-) create mode 100644 Streetcode/Streetcode.Email.DAL/Migrations/20260227122516_Name.Designer.cs create mode 100644 Streetcode/Streetcode.Email.DAL/Migrations/20260227122516_Name.cs create mode 100644 Streetcode/Streetcode.Email.DAL/Migrations/EmailDbContextModelSnapshot.cs diff --git a/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs b/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs index e04772e..9e21717 100644 --- a/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs +++ b/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs @@ -9,7 +9,6 @@ public class FeedbackProfile : Profile public FeedbackProfile() { CreateMap().ReverseMap(); - CreateMap(); } } } diff --git a/Streetcode/Streetcode.Email.BLL/Services/EmailConsumer.cs b/Streetcode/Streetcode.Email.BLL/Services/EmailConsumer.cs index 90102ba..c185415 100644 --- a/Streetcode/Streetcode.Email.BLL/Services/EmailConsumer.cs +++ b/Streetcode/Streetcode.Email.BLL/Services/EmailConsumer.cs @@ -1,77 +1,19 @@ -using System.Text; -using System.Text.Json; +using MassTransit; using MediatR; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.DependencyInjection; -using RabbitMQ.Client; -using RabbitMQ.Client.Events; -using Streetcode.Email.BLL.DTO; using Streetcode.Email.BLL.MediatR.Feedback; namespace Streetcode.Email.BLL.Services { - public class EmailConsumer : BackgroundService + public class EmailConsumer : IConsumer { - private readonly IServiceProvider _serviceProvider; - private readonly IConnection _connection; - private readonly IModel _model; - private const string QueueName = "email_queue"; - public EmailConsumer(IServiceProvider serviceProvider) + private readonly IMediator _mediator; + public EmailConsumer(IMediator mediator) { - _serviceProvider = serviceProvider; - - var factory = new ConnectionFactory() { HostName = "localhost" }; - _connection = factory.CreateConnection(); - _model = _connection.CreateModel(); - - _model.QueueDeclare(queue: QueueName, - durable: true, - exclusive: false, - autoDelete: false, - arguments: null); - } - - protected override Task ExecuteAsync(CancellationToken stoppingToken) - { - var consumer = new EventingBasicConsumer(_model); - - consumer.Received += async (model, ea) => - { - var body = ea.Body.ToArray(); - var message = Encoding.UTF8.GetString(body); - - try - { - var feedback = JsonSerializer.Deserialize(message, new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }); - - if (feedback != null) - { - using var scope = _serviceProvider.CreateScope(); - var mediator = scope.ServiceProvider.GetRequiredService(); - - await mediator.Send(new SendFeedbackCommand(feedback)); - } - - _model.BasicAck(ea.DeliveryTag, false); - } - catch (Exception ex) - { - _model.BasicNack(ea.DeliveryTag, false, true); - } - }; - - _model.BasicConsume(queue: QueueName, autoAck: false, consumer: consumer); - return Task.CompletedTask; + _mediator = mediator; } - - public override void Dispose() + public async Task Consume(ConsumeContext context) { - _model.Close(); - _connection.Close(); - base.Dispose(); + await _mediator.Send(context.Message); } } } diff --git a/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj b/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj index 16efa1f..9800002 100644 --- a/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj +++ b/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj @@ -13,6 +13,7 @@ + diff --git a/Streetcode/Streetcode.Email.DAL/Migrations/20260227122516_Name.Designer.cs b/Streetcode/Streetcode.Email.DAL/Migrations/20260227122516_Name.Designer.cs new file mode 100644 index 0000000..ede2f6a --- /dev/null +++ b/Streetcode/Streetcode.Email.DAL/Migrations/20260227122516_Name.Designer.cs @@ -0,0 +1,52 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Streetcode.Email.DAL.Persistence; + +#nullable disable + +namespace Streetcode.Email.DAL.Migrations +{ + [DbContext(typeof(EmailDbContext))] + [Migration("20260227122516_Name")] + partial class Name + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.23") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Streetcode.Email.DAL.Entities.Feedback", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("Feedbacks", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Streetcode/Streetcode.Email.DAL/Migrations/20260227122516_Name.cs b/Streetcode/Streetcode.Email.DAL/Migrations/20260227122516_Name.cs new file mode 100644 index 0000000..2ec9e22 --- /dev/null +++ b/Streetcode/Streetcode.Email.DAL/Migrations/20260227122516_Name.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Streetcode.Email.DAL.Migrations +{ + /// + public partial class Name : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Feedbacks", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), + Message = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Feedbacks", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Feedbacks"); + } + } +} diff --git a/Streetcode/Streetcode.Email.DAL/Migrations/EmailDbContextModelSnapshot.cs b/Streetcode/Streetcode.Email.DAL/Migrations/EmailDbContextModelSnapshot.cs new file mode 100644 index 0000000..aa0b094 --- /dev/null +++ b/Streetcode/Streetcode.Email.DAL/Migrations/EmailDbContextModelSnapshot.cs @@ -0,0 +1,49 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Streetcode.Email.DAL.Persistence; + +#nullable disable + +namespace Streetcode.Email.DAL.Migrations +{ + [DbContext(typeof(EmailDbContext))] + partial class EmailDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.23") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Streetcode.Email.DAL.Entities.Feedback", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Message") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.ToTable("Feedbacks", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs index 710fb93..cee5016 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs +++ b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs @@ -1,10 +1,14 @@ -using Hangfire; -using Hangfire.Redis.StackExchange; +using FluentValidation; +using Hangfire; +using MassTransit; +using MediatR; using Microsoft.EntityFrameworkCore; using Streetcode.Email.BLL.Configs; using Streetcode.Email.BLL.Interfaces; +using Streetcode.Email.BLL.MediatR.Behavior; using Streetcode.Email.BLL.Services; using Streetcode.Email.DAL.Persistence; +using System.Reflection; namespace Streetcode.Email.WebAPI.Extensions { @@ -13,7 +17,6 @@ public static class ServiceCollectionExtensions public static void AddApplicationServices(this IServiceCollection services, ConfigurationManager configuration) { var connectionString = configuration.GetConnectionString("DefaultConnection"); - var redisConnection = configuration.GetConnectionString("Redis"); services.AddDbContext(options => { @@ -26,7 +29,7 @@ public static void AddApplicationServices(this IServiceCollection services, Conf services.AddHangfire(config => { - config.UseRedisStorage(redisConnection); + config.UseSqlServerStorage(connectionString); }); services.AddHangfireServer(); @@ -36,31 +39,36 @@ public static void AddApplicationServices(this IServiceCollection services, Conf } public static void AddCustomServices(this IServiceCollection services, IConfiguration configuration) { + var bllAssembly = Assembly.Load("Streetcode.Email.BLL"); + + services.AddAutoMapper(bllAssembly); + services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies(bllAssembly)); services.Configure( configuration.GetSection("EmailConfiguration")); services.AddScoped(); - } + services.AddValidatorsFromAssembly(bllAssembly); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidatorBehavior<,>)); - public static void AddRabbitMQConsumer(this IServiceCollection services) - { - services.AddHostedService(); - } + services.AddMassTransit(x => + { + x.AddConsumer(); - public class CorsConfiguration - { - public List AllowedOrigins { get; set; } - public List AllowedHeaders { get; set; } - public List AllowedMethods { get; set; } - public int PreflightMaxAge { get; set; } - } + x.UsingRabbitMq((context, cfg) => + { + var rabbitSection = configuration.GetSection("RabbitMQ"); + + cfg.Host(rabbitSection["Host"], "/", h => + { + h.Username(rabbitSection["Username"]); + h.Password(rabbitSection["Password"]); + }); + + cfg.ConfigureEndpoints(context); + }); + }); - public class JwtOptions - { - public string Key { get; set; } - public string Issuer { get; set; } - public string Audience { get; set; } } } } diff --git a/Streetcode/Streetcode.Email.WebAPI/Program.cs b/Streetcode/Streetcode.Email.WebAPI/Program.cs index f86b1dd..760a1b1 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Program.cs +++ b/Streetcode/Streetcode.Email.WebAPI/Program.cs @@ -1,21 +1,28 @@ -var builder = WebApplication.CreateBuilder(args); +using Hangfire; +using Streetcode.Email.BLL.Interfaces; +using Streetcode.Email.WebAPI.Extensions; +var builder = WebApplication.CreateBuilder(args); -builder.Services.AddControllers(); -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +builder.Services.AddApplicationServices(builder.Configuration); +builder.Services.AddCustomServices(builder.Configuration); var app = builder.Build(); -if (app.Environment.IsDevelopment()) +if (app.Environment.EnvironmentName == "Local") { - app.UseSwagger(); - app.UseSwaggerUI(); + app.UseHangfireDashboard("/dash"); } +else +{ + app.UseHsts(); +} + +await app.ApplyMigrations(); -app.UseHttpsRedirection(); +// app.SeedDataAsync(); // uncomment for seeding data in local -app.UseAuthorization(); +app.UseHangfireDashboard("/dash"); app.MapControllers(); diff --git a/Streetcode/Streetcode.Email.WebAPI/Properties/launchSettings.json b/Streetcode/Streetcode.Email.WebAPI/Properties/launchSettings.json index 5d976c2..879fee2 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Properties/launchSettings.json +++ b/Streetcode/Streetcode.Email.WebAPI/Properties/launchSettings.json @@ -13,20 +13,20 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "dash", "applicationUrl": "http://localhost:5179", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Local" } }, "https": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "dash", "applicationUrl": "https://localhost:7094;http://localhost:5179", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Local" } }, "IIS Express": { diff --git a/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj b/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj index 2c42fd0..b65acdf 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj +++ b/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj @@ -7,8 +7,16 @@ + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Streetcode/Streetcode.Email.WebAPI/appsettings.json b/Streetcode/Streetcode.Email.WebAPI/appsettings.json index 5b58afa..ad3c0e3 100644 --- a/Streetcode/Streetcode.Email.WebAPI/appsettings.json +++ b/Streetcode/Streetcode.Email.WebAPI/appsettings.json @@ -7,20 +7,6 @@ }, "AllowedHosts": "*", - "EmailConfiguration": { - "FromAddress": "system@streetcode.ua", - "AdminAddress": "admin@streetcode.ua", - "SmtpServer": "smtp.gmail.com", - "Port": 587, - "SmtpUser": "your_login@gmail.com", - "SmtpPassword": "password" - }, - "RabbitMQ": { - "Host": "localhost", - "Username": "SomeUsername", - "Password": "SomePassword" - }, - "ConnectionStrings": { "DefaultConnection": "ConnectionString", "Redis": "Redis" diff --git a/Streetcode/docker-compose.yml b/Streetcode/docker-compose.yml index 7c8ed0c..515bf18 100644 --- a/Streetcode/docker-compose.yml +++ b/Streetcode/docker-compose.yml @@ -26,9 +26,9 @@ services: - "1433:1433" environment: - ACCEPT_EULA=Y - - MSSQL_SA_PASSWORD=${DB_PASSWORD} + - MSSQL_SA_PASSWORD=Strong_Pass_123!D32@ healthcheck: - test: ["CMD", "/opt/mssql-tools/bin/sqlcmd", "-U", "sa", "-P", "${DB_PASSWORD}", "-Q", "SELECT 1"] + test: ["CMD", "/opt/mssql-tools/bin/sqlcmd", "-U", "sa", "-P", "Strong_Pass_123!D32@", "-Q", "SELECT 1"] interval: 10s timeout: 3s retries: 10 \ No newline at end of file diff --git a/Streetcode/dockerpassword.env b/Streetcode/dockerpassword.env index e5577c2..4266b09 100644 --- a/Streetcode/dockerpassword.env +++ b/Streetcode/dockerpassword.env @@ -1 +1 @@ -DB_PASSWORD=5D71981AA0674A1295109C5995D6779F \ No newline at end of file +DB_PASSWORD=Strong_Pass_123!D32@ \ No newline at end of file From e9491a3ec2680469deed4195c44895bbc27933b7 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Fri, 27 Feb 2026 17:42:25 +0100 Subject: [PATCH 09/31] refactor: removed unusing files --- Streetcode/Streetcode.sln | 1 - 1 file changed, 1 deletion(-) diff --git a/Streetcode/Streetcode.sln b/Streetcode/Streetcode.sln index 8dd48f9..19b9f30 100644 --- a/Streetcode/Streetcode.sln +++ b/Streetcode/Streetcode.sln @@ -21,7 +21,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig docker-compose.yml = docker-compose.yml - dockerpassword.env = dockerpassword.env EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" From d3ddcc525d5ec7b85a84f2f513336d4af4d71f04 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Fri, 27 Feb 2026 17:51:15 +0100 Subject: [PATCH 10/31] feat: create endpoint for email --- Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs | 4 ++++ .../Controllers/Email/EmailController.cs | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs b/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs index 8635ee1..64b6f85 100644 --- a/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs +++ b/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs @@ -4,6 +4,10 @@ namespace Streetcode.BLL.DTO.Email { public class EmailDTO { + public EmailDTO() + { + } + [MaxLength(80)] public string From { get; set; } diff --git a/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs b/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs index e85773a..7b8f28f 100644 --- a/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs +++ b/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs @@ -1,3 +1,4 @@ +using MassTransit; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Streetcode.BLL.DTO.Email; @@ -7,11 +8,20 @@ namespace Streetcode.WebApi.Controllers.Email { public class EmailController : BaseApiController { + private readonly IPublishEndpoint _publishEndpoint; + + public EmailController(IPublishEndpoint publishEndpoint) + { + _publishEndpoint = publishEndpoint; + } + [HttpPost] [AllowAnonymous] public async Task Send([FromBody] EmailDTO email) { - return HandleResult(await Mediator.Send(new SendEmailCommand(email))); + await _publishEndpoint.Publish(email); + + return Ok(); } } } From 1df68e0afb74c03de84ad7056bcd8e4c1c2aa588 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Sun, 1 Mar 2026 19:00:05 +0100 Subject: [PATCH 11/31] fix: change email endpoint, on masstransit publisher --- Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs | 4 ++-- .../Streetcode.BLL/MediatR/Email/SendEmailHandler.cs | 4 ++-- .../DTO/{FeedbackDTO.cs => EmailDTO.cs} | 5 ++++- .../Streetcode.Email.BLL/Interfaces/IEmailService.cs | 2 +- .../Streetcode.Email.BLL/Mapping/FeedbackProfile.cs | 2 +- .../MediatR/Feedback/FeedbackDTOValidator.cs | 2 +- .../MediatR/Feedback/SendFeedbackCommand.cs | 2 +- .../Streetcode.Email.BLL/Services/EmailService.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 11 +++++------ .../Streetcode.Shared/Contracts/IEmailMessage.cs | 8 ++++++++ .../Controllers/Email/EmailController.cs | 8 ++++++-- 11 files changed, 32 insertions(+), 18 deletions(-) rename Streetcode/Streetcode.Email.BLL/DTO/{FeedbackDTO.cs => EmailDTO.cs} (66%) create mode 100644 Streetcode/Streetcode.Shared/Contracts/IEmailMessage.cs diff --git a/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs b/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs index 64b6f85..cafd4e2 100644 --- a/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs +++ b/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs @@ -9,10 +9,10 @@ public EmailDTO() } [MaxLength(80)] - public string From { get; set; } + public string Email { get; set; } [Required] [StringLength(500, MinimumLength = 1)] - public string Content { get; set; } + public string Message { get; set; } } } diff --git a/Streetcode/Streetcode.BLL/MediatR/Email/SendEmailHandler.cs b/Streetcode/Streetcode.BLL/MediatR/Email/SendEmailHandler.cs index c16e438..cb40114 100644 --- a/Streetcode/Streetcode.BLL/MediatR/Email/SendEmailHandler.cs +++ b/Streetcode/Streetcode.BLL/MediatR/Email/SendEmailHandler.cs @@ -23,9 +23,9 @@ public async Task> Handle(SendEmailCommand request, CancellationTok { var message = new Message( [Constants.StreetcodeContacts.Email], - request.Email.From, + request.Email.Email, "FeedBack", - request.Email.Content); + request.Email.Message); var isResultSuccess = await _emailService.SendEmailAsync(message); diff --git a/Streetcode/Streetcode.Email.BLL/DTO/FeedbackDTO.cs b/Streetcode/Streetcode.Email.BLL/DTO/EmailDTO.cs similarity index 66% rename from Streetcode/Streetcode.Email.BLL/DTO/FeedbackDTO.cs rename to Streetcode/Streetcode.Email.BLL/DTO/EmailDTO.cs index 0a7c374..35092d6 100644 --- a/Streetcode/Streetcode.Email.BLL/DTO/FeedbackDTO.cs +++ b/Streetcode/Streetcode.Email.BLL/DTO/EmailDTO.cs @@ -1,7 +1,10 @@ namespace Streetcode.Email.BLL.DTO { - public class FeedbackDTO + public class EmailDTO { + public EmailDTO() + { + } public string Email { get; set; } public string Message { get; set; } } diff --git a/Streetcode/Streetcode.Email.BLL/Interfaces/IEmailService.cs b/Streetcode/Streetcode.Email.BLL/Interfaces/IEmailService.cs index e669f5e..04286b2 100644 --- a/Streetcode/Streetcode.Email.BLL/Interfaces/IEmailService.cs +++ b/Streetcode/Streetcode.Email.BLL/Interfaces/IEmailService.cs @@ -4,6 +4,6 @@ namespace Streetcode.Email.BLL.Interfaces { public interface IEmailService { - Task SendEmailAsync(FeedbackDTO feedback); + Task SendEmailAsync(EmailDTO feedback); } } \ No newline at end of file diff --git a/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs b/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs index 9e21717..7aeb47b 100644 --- a/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs +++ b/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs @@ -8,7 +8,7 @@ public class FeedbackProfile : Profile { public FeedbackProfile() { - CreateMap().ReverseMap(); + CreateMap().ReverseMap(); } } } diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/FeedbackDTOValidator.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/FeedbackDTOValidator.cs index ef539d1..e0b4fbe 100644 --- a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/FeedbackDTOValidator.cs +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/FeedbackDTOValidator.cs @@ -3,7 +3,7 @@ namespace Streetcode.Email.BLL.MediatR.Feedback { - public class FeedbackDTOValidator : AbstractValidator + public class FeedbackDTOValidator : AbstractValidator { public FeedbackDTOValidator() { diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommand.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommand.cs index 896a587..e513620 100644 --- a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommand.cs +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommand.cs @@ -4,5 +4,5 @@ namespace Streetcode.Email.BLL.MediatR.Feedback { - public record SendFeedbackCommand(FeedbackDTO Feedback) : IRequest>; + public record SendFeedbackCommand(EmailDTO Feedback) : IRequest>; } diff --git a/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs b/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs index da29164..3f51515 100644 --- a/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs +++ b/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs @@ -17,7 +17,7 @@ public EmailService(IOptions options) _emailConfig = options.Value; } - public async Task SendEmailAsync(FeedbackDTO feedback) + public async Task SendEmailAsync(EmailDTO feedback) { var message = new MimeMessage(); message.From.Add(new MailboxAddress("Streetcode", _emailConfig.FromAddress)); diff --git a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs index cee5016..21235ea 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs +++ b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs @@ -57,15 +57,14 @@ public static void AddCustomServices(this IServiceCollection services, IConfigur x.UsingRabbitMq((context, cfg) => { - var rabbitSection = configuration.GetSection("RabbitMQ"); + cfg.Host("localhost", "/"); - cfg.Host(rabbitSection["Host"], "/", h => + cfg.ReceiveEndpoint("Email", e => { - h.Username(rabbitSection["Username"]); - h.Password(rabbitSection["Password"]); - }); + e.ConfigureConsumer(context); - cfg.ConfigureEndpoints(context); + e.Bind("Email"); + }); }); }); diff --git a/Streetcode/Streetcode.Shared/Contracts/IEmailMessage.cs b/Streetcode/Streetcode.Shared/Contracts/IEmailMessage.cs new file mode 100644 index 0000000..8c57144 --- /dev/null +++ b/Streetcode/Streetcode.Shared/Contracts/IEmailMessage.cs @@ -0,0 +1,8 @@ +namespace Streetcode.Shared.Contracts +{ + public interface IEmailMessage + { + public string Email { get; } + public string Message { get; } + } +} diff --git a/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs b/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs index 7b8f28f..d170d93 100644 --- a/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs +++ b/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Streetcode.BLL.DTO.Email; -using Streetcode.BLL.MediatR.Email; +using Streetcode.Shared.Contracts; namespace Streetcode.WebApi.Controllers.Email { @@ -19,7 +19,11 @@ public EmailController(IPublishEndpoint publishEndpoint) [AllowAnonymous] public async Task Send([FromBody] EmailDTO email) { - await _publishEndpoint.Publish(email); + await _publishEndpoint.Publish(new + { + email.Email, + email.Message + }); return Ok(); } From d4cbea50cddb9611073a5efa7932a6fecab82717 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 00:13:07 +0100 Subject: [PATCH 12/31] fix: fix consumer setting for masstransit --- .../Services/EmailConsumer.cs | 19 ++++++++++++++++--- .../Streetcode.Email.BLL.csproj | 1 + .../Extensions/ServiceCollectionExtensions.cs | 9 +++------ .../Streetcode.Email.WebAPI.csproj | 1 + 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Streetcode/Streetcode.Email.BLL/Services/EmailConsumer.cs b/Streetcode/Streetcode.Email.BLL/Services/EmailConsumer.cs index c185415..1f92a6e 100644 --- a/Streetcode/Streetcode.Email.BLL/Services/EmailConsumer.cs +++ b/Streetcode/Streetcode.Email.BLL/Services/EmailConsumer.cs @@ -1,19 +1,32 @@ using MassTransit; using MediatR; +using Streetcode.Email.BLL.DTO; using Streetcode.Email.BLL.MediatR.Feedback; +using Streetcode.Shared.Contracts; namespace Streetcode.Email.BLL.Services { - public class EmailConsumer : IConsumer + public class EmailConsumer : IConsumer { private readonly IMediator _mediator; public EmailConsumer(IMediator mediator) { _mediator = mediator; } - public async Task Consume(ConsumeContext context) + + public async Task Consume(ConsumeContext context) { - await _mediator.Send(context.Message); + var message = context.Message; + + var feedback = new EmailDTO + { + Email = message.Email, + Message = message.Message + }; + + var command = new SendFeedbackCommand(feedback); + + await _mediator.Send(command); } } } diff --git a/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj b/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj index 9800002..10da8bc 100644 --- a/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj +++ b/Streetcode/Streetcode.Email.BLL/Streetcode.Email.BLL.csproj @@ -22,6 +22,7 @@ + diff --git a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs index 21235ea..925e43c 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs +++ b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs @@ -53,18 +53,15 @@ public static void AddCustomServices(this IServiceCollection services, IConfigur services.AddMassTransit(x => { + x.SetKebabCaseEndpointNameFormatter(); + x.AddConsumer(); x.UsingRabbitMq((context, cfg) => { cfg.Host("localhost", "/"); - cfg.ReceiveEndpoint("Email", e => - { - e.ConfigureConsumer(context); - - e.Bind("Email"); - }); + cfg.ConfigureEndpoints(context); }); }); diff --git a/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj b/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj index b65acdf..a29f5f4 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj +++ b/Streetcode/Streetcode.Email.WebAPI/Streetcode.Email.WebAPI.csproj @@ -26,6 +26,7 @@ + From 94906588c060b6147392106d671886e12e007f2c Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 00:24:58 +0100 Subject: [PATCH 13/31] feat: remove old logic for email --- .../Streetcode.BLL/DTO/Email/EmailDTO.cs | 18 ----- .../MediatR/Email/SendEmailCommand.cs | 6 -- .../MediatR/Email/SendEmailHandler.cs | 42 ----------- .../Services/Email/EmailService.cs | 70 ------------------- .../Streetcode.Email.DAL/Entities/Feedback.cs | 3 +- .../Controllers/Email/EmailController.cs | 31 -------- Streetcode/Streetcode.sln | 1 + Streetcode/docker-compose.yml | 8 +-- 8 files changed, 7 insertions(+), 172 deletions(-) delete mode 100644 Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs delete mode 100644 Streetcode/Streetcode.BLL/MediatR/Email/SendEmailCommand.cs delete mode 100644 Streetcode/Streetcode.BLL/MediatR/Email/SendEmailHandler.cs delete mode 100644 Streetcode/Streetcode.BLL/Services/Email/EmailService.cs delete mode 100644 Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs diff --git a/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs b/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs deleted file mode 100644 index cafd4e2..0000000 --- a/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Streetcode.BLL.DTO.Email -{ - public class EmailDTO - { - public EmailDTO() - { - } - - [MaxLength(80)] - public string Email { get; set; } - - [Required] - [StringLength(500, MinimumLength = 1)] - public string Message { get; set; } - } -} diff --git a/Streetcode/Streetcode.BLL/MediatR/Email/SendEmailCommand.cs b/Streetcode/Streetcode.BLL/MediatR/Email/SendEmailCommand.cs deleted file mode 100644 index 7a7d1da..0000000 --- a/Streetcode/Streetcode.BLL/MediatR/Email/SendEmailCommand.cs +++ /dev/null @@ -1,6 +0,0 @@ -using FluentResults; -using MediatR; -using Streetcode.BLL.DTO.Email; - -namespace Streetcode.BLL.MediatR.Email; -public record SendEmailCommand(EmailDTO Email) : IRequest>; diff --git a/Streetcode/Streetcode.BLL/MediatR/Email/SendEmailHandler.cs b/Streetcode/Streetcode.BLL/MediatR/Email/SendEmailHandler.cs deleted file mode 100644 index cb40114..0000000 --- a/Streetcode/Streetcode.BLL/MediatR/Email/SendEmailHandler.cs +++ /dev/null @@ -1,42 +0,0 @@ -using FluentResults; -using MediatR; -using Streetcode.BLL.Interfaces.Email; -using Streetcode.BLL.Interfaces.Logging; -using Streetcode.DAL.Entities.AdditionalContent.Email; -using Streetcode.Resources; -using Streetcode.Shared; - -namespace Streetcode.BLL.MediatR.Email -{ - public class SendEmailHandler : IRequestHandler> - { - private readonly IEmailService _emailService; - private readonly ILoggerService _logger; - - public SendEmailHandler(IEmailService emailService, ILoggerService logger) - { - _emailService = emailService; - _logger = logger; - } - - public async Task> Handle(SendEmailCommand request, CancellationToken cancellationToken) - { - var message = new Message( - [Constants.StreetcodeContacts.Email], - request.Email.Email, - "FeedBack", - request.Email.Message); - - var isResultSuccess = await _emailService.SendEmailAsync(message); - - if (isResultSuccess) - { - return Result.Ok(Unit.Value); - } - - var errorMsg = Messages.Error_FailedToSendEmail; - _logger.LogError(request, errorMsg); - return Result.Fail(new Error(errorMsg)); - } - } -} diff --git a/Streetcode/Streetcode.BLL/Services/Email/EmailService.cs b/Streetcode/Streetcode.BLL/Services/Email/EmailService.cs deleted file mode 100644 index 0c3d093..0000000 --- a/Streetcode/Streetcode.BLL/Services/Email/EmailService.cs +++ /dev/null @@ -1,70 +0,0 @@ -using MailKit.Net.Smtp; -using MimeKit; -using Streetcode.BLL.Interfaces.Email; -using Streetcode.DAL.Entities.AdditionalContent.Email; - -namespace Streetcode.BLL.Services.Email -{ - public class EmailService : IEmailService - { - private readonly EmailConfiguration _emailConfig; - - public EmailService(EmailConfiguration emailConfig) - { - _emailConfig = emailConfig; - } - - public async Task SendEmailAsync(Message message) - { - var mailMessage = CreateEmailMessage(message); - - return await SendAsync(mailMessage); - } - - private MimeMessage CreateEmailMessage(Message message) - { - var emailMessage = new MimeMessage(); - emailMessage.From.Add(new MailboxAddress("", _emailConfig.From)); - emailMessage.To.AddRange(message.To); - emailMessage.Subject = message.Subject; - - var bodyBuilder = new BodyBuilder - { - HtmlBody = - "

" + - $"Від: {message.From}
" + - $"Текст: {message.Content}" + - "

" - }; - - emailMessage.Body = bodyBuilder.ToMessageBody(); - return emailMessage; - } - - private async Task SendAsync(MimeMessage mailMessage) - { - using (var client = new SmtpClient()) - { - try - { - await client.ConnectAsync(_emailConfig.SmtpServer, _emailConfig.Port, true); - client.AuthenticationMechanisms.Remove("XOAUTH2"); - await client.AuthenticateAsync(_emailConfig.UserName, _emailConfig.Password); - - await client.SendAsync(mailMessage); - return true; - } - catch - { - // Logger - return false; - } - finally - { - await client.DisconnectAsync(true); - client.Dispose(); - } - } - } - } -} diff --git a/Streetcode/Streetcode.Email.DAL/Entities/Feedback.cs b/Streetcode/Streetcode.Email.DAL/Entities/Feedback.cs index 7262364..80b2e44 100644 --- a/Streetcode/Streetcode.Email.DAL/Entities/Feedback.cs +++ b/Streetcode/Streetcode.Email.DAL/Entities/Feedback.cs @@ -9,10 +9,11 @@ public class Feedback [Key] public int Id { get; set; } [Required] + [EmailAddress] public string? Email { get; set; } [Required] [MinLength(5)] - [MaxLength(100)] + [MaxLength(1000)] public string? Message { get; set; } } diff --git a/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs b/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs deleted file mode 100644 index d170d93..0000000 --- a/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MassTransit; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Streetcode.BLL.DTO.Email; -using Streetcode.Shared.Contracts; - -namespace Streetcode.WebApi.Controllers.Email -{ - public class EmailController : BaseApiController - { - private readonly IPublishEndpoint _publishEndpoint; - - public EmailController(IPublishEndpoint publishEndpoint) - { - _publishEndpoint = publishEndpoint; - } - - [HttpPost] - [AllowAnonymous] - public async Task Send([FromBody] EmailDTO email) - { - await _publishEndpoint.Publish(new - { - email.Email, - email.Message - }); - - return Ok(); - } - } -} diff --git a/Streetcode/Streetcode.sln b/Streetcode/Streetcode.sln index 19b9f30..fa08c0a 100644 --- a/Streetcode/Streetcode.sln +++ b/Streetcode/Streetcode.sln @@ -20,6 +20,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{95E863CE-C3B4-4DBC-8C15-957E63BA45B6}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + .env = .env docker-compose.yml = docker-compose.yml EndProjectSection EndProject diff --git a/Streetcode/docker-compose.yml b/Streetcode/docker-compose.yml index 515bf18..40b9ff9 100644 --- a/Streetcode/docker-compose.yml +++ b/Streetcode/docker-compose.yml @@ -9,8 +9,8 @@ services: - "5672:5672" - "15672:15672" environment: - - RABBITMQ_DEFAULT_USER=guest - - RABBITMQ_DEFAULT_PASS=guest + - RABBITMQ_DEFAULT_USER=${RABBITMQ_DEFAULT_USER} + - RABBITMQ_DEFAULT_PASS=${RABBITMQ_DEFAULT_PASS} redis: image: redis:latest @@ -26,9 +26,9 @@ services: - "1433:1433" environment: - ACCEPT_EULA=Y - - MSSQL_SA_PASSWORD=Strong_Pass_123!D32@ + - MSSQL_SA_PASSWORD=${DB_PASSWORD} healthcheck: - test: ["CMD", "/opt/mssql-tools/bin/sqlcmd", "-U", "sa", "-P", "Strong_Pass_123!D32@", "-Q", "SELECT 1"] + test: ["CMD", "/opt/mssql-tools/bin/sqlcmd", "-U", "sa", "-P", "${DB_PASSWORD}", "-Q", "SELECT 1"] interval: 10s timeout: 3s retries: 10 \ No newline at end of file From 8f449317a2480da69b93885b8c3a9fd91997ec93 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 01:06:50 +0100 Subject: [PATCH 14/31] fix: fix handfire configuration with sql --- .../Streetcode.BLL/DTO/Email/EmailDTO.cs | 18 +++++++++++ .../Interfaces/Email/IEmailService.cs | 9 ------ .../Extensions/ServiceCollectionExtensions.cs | 17 ++++++++-- .../Properties/launchSettings.json | 26 ++++++++-------- .../Controllers/Email/EmailController.cs | 31 +++++++++++++++++++ .../Extensions/ServiceCollectionExtensions.cs | 3 -- 6 files changed, 76 insertions(+), 28 deletions(-) create mode 100644 Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs delete mode 100644 Streetcode/Streetcode.BLL/Interfaces/Email/IEmailService.cs create mode 100644 Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs diff --git a/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs b/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs new file mode 100644 index 0000000..c341dfd --- /dev/null +++ b/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; + +namespace Streetcode.BLL.DTO.Email +{ + public class EmailDTO + { + public EmailDTO() + { + } + + [MaxLength(80)] + public string Email { get; set; } + + [Required] + [StringLength(500, MinimumLength = 1)] + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/Streetcode/Streetcode.BLL/Interfaces/Email/IEmailService.cs b/Streetcode/Streetcode.BLL/Interfaces/Email/IEmailService.cs deleted file mode 100644 index 7817456..0000000 --- a/Streetcode/Streetcode.BLL/Interfaces/Email/IEmailService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Streetcode.DAL.Entities.AdditionalContent.Email; - -namespace Streetcode.BLL.Interfaces.Email -{ - public interface IEmailService - { - Task SendEmailAsync(Message message); - } -} diff --git a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs index 925e43c..d9ebff4 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs +++ b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs @@ -1,5 +1,6 @@ using FluentValidation; using Hangfire; +using Hangfire.SqlServer; using MassTransit; using MediatR; using Microsoft.EntityFrameworkCore; @@ -27,10 +28,20 @@ public static void AddApplicationServices(this IServiceCollection services, Conf }); }); - services.AddHangfire(config => + services.AddHangfire(config => config + .SetDataCompatibilityLevel(CompatibilityLevel.Version_180) + .UseSimpleAssemblyNameTypeSerializer() + .UseRecommendedSerializerSettings() + .UseSqlServerStorage(configuration.GetConnectionString("DefaultConnection"), new SqlServerStorageOptions { - config.UseSqlServerStorage(connectionString); - }); + PrepareSchemaIfNecessary = true, + + CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), + SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5), + QueuePollInterval = TimeSpan.Zero, + UseRecommendedIsolationLevel = true, + DisableGlobalLocks = true + })); services.AddHangfireServer(); diff --git a/Streetcode/Streetcode.Email.WebAPI/Properties/launchSettings.json b/Streetcode/Streetcode.Email.WebAPI/Properties/launchSettings.json index 879fee2..d554bda 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Properties/launchSettings.json +++ b/Streetcode/Streetcode.Email.WebAPI/Properties/launchSettings.json @@ -9,25 +9,25 @@ } }, "profiles": { - "http": { + "Streetcode_Email_Local": { "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "dash", - "applicationUrl": "http://localhost:5179", + "launchBrowser": false, "environmentVariables": { + "STREETCODE_ENVIRONMENT": "Local", "ASPNETCORE_ENVIRONMENT": "Local" - } + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7094;http://localhost:5179" }, - "https": { + "Streetcode_Email_Dev": { "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "dash", - "applicationUrl": "https://localhost:7094;http://localhost:5179", + "launchBrowser": false, + "launchUrl": "swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Local" - } + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7094;http://localhost:5179" }, "IIS Express": { "commandName": "IISExpress", diff --git a/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs b/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs new file mode 100644 index 0000000..7146bd7 --- /dev/null +++ b/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs @@ -0,0 +1,31 @@ +using MassTransit; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Streetcode.BLL.DTO.Email; +using Streetcode.Shared.Contracts; + +namespace Streetcode.WebApi.Controllers.Email +{ + public class EmailController : BaseApiController + { + private readonly IPublishEndpoint _publishEndpoint; + + public EmailController(IPublishEndpoint publishEndpoint) + { + _publishEndpoint = publishEndpoint; + } + + [HttpPost] + [AllowAnonymous] + public async Task Send([FromBody] EmailDTO email) + { + await _publishEndpoint.Publish(new + { + email.Email, + email.Message + }); + + return Ok(); + } + } +} diff --git a/Streetcode/Streetcode.WebApi/Extensions/ServiceCollectionExtensions.cs b/Streetcode/Streetcode.WebApi/Extensions/ServiceCollectionExtensions.cs index d48a9ae..6e75186 100644 --- a/Streetcode/Streetcode.WebApi/Extensions/ServiceCollectionExtensions.cs +++ b/Streetcode/Streetcode.WebApi/Extensions/ServiceCollectionExtensions.cs @@ -10,7 +10,6 @@ using Microsoft.OpenApi.Models; using Streetcode.BLL.Interfaces.BlobStorage; using Streetcode.BLL.Interfaces.Cache; -using Streetcode.BLL.Interfaces.Email; using Streetcode.BLL.Interfaces.Instagram; using Streetcode.BLL.Interfaces.Logging; using Streetcode.BLL.Interfaces.Payment; @@ -18,7 +17,6 @@ using Streetcode.BLL.MediatR.PipelineBehavior; using Streetcode.BLL.Services.BlobStorageService; using Streetcode.BLL.Services.Cache; -using Streetcode.BLL.Services.Email; using Streetcode.BLL.Services.Instagram; using Streetcode.BLL.Services.Logging; using Streetcode.BLL.Services.Payment; @@ -51,7 +49,6 @@ public static void AddCustomServices(this IServiceCollection services, IConfigur services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); From 1c7c4964aa76135f1b9ed65dd274d76c671e7fc0 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 12:02:28 +0100 Subject: [PATCH 15/31] test: implement test for mediatr email --- .../Extensions/ServiceCollectionExtensions.cs | 2 +- .../Email/FeedbackDTOValidatorTests.cs | 95 ++++++++++++++++ .../SendFeedbackCommandValidatorTests.cs | 70 ++++++++++++ .../Email/SendFeedbackHandlerTests.cs | 105 ++++++++++++++++++ .../Streetcode.Email.XUnitTest.csproj | 8 ++ .../Streetcode.Email.XUnitTest/UnitTest1.cs | 11 -- 6 files changed, 279 insertions(+), 12 deletions(-) create mode 100644 Streetcode/Streetcode.Email.XUnitTest/Email/FeedbackDTOValidatorTests.cs create mode 100644 Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackCommandValidatorTests.cs create mode 100644 Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackHandlerTests.cs delete mode 100644 Streetcode/Streetcode.Email.XUnitTest/UnitTest1.cs diff --git a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs index d9ebff4..79d978b 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs +++ b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs @@ -32,7 +32,7 @@ public static void AddApplicationServices(this IServiceCollection services, Conf .SetDataCompatibilityLevel(CompatibilityLevel.Version_180) .UseSimpleAssemblyNameTypeSerializer() .UseRecommendedSerializerSettings() - .UseSqlServerStorage(configuration.GetConnectionString("DefaultConnection"), new SqlServerStorageOptions + .UseSqlServerStorage(connectionString, new SqlServerStorageOptions { PrepareSchemaIfNecessary = true, diff --git a/Streetcode/Streetcode.Email.XUnitTest/Email/FeedbackDTOValidatorTests.cs b/Streetcode/Streetcode.Email.XUnitTest/Email/FeedbackDTOValidatorTests.cs new file mode 100644 index 0000000..2c9758d --- /dev/null +++ b/Streetcode/Streetcode.Email.XUnitTest/Email/FeedbackDTOValidatorTests.cs @@ -0,0 +1,95 @@ +namespace Streetcode.Email.XUnitTest.MediatR.Feedback +{ + using FluentValidation.TestHelper; + using Streetcode.Email.BLL.DTO; + using Streetcode.Email.BLL.MediatR.Feedback; + using Xunit; + + public class FeedbackDTOValidatorTests + { + private readonly FeedbackDTOValidator validator; + + public FeedbackDTOValidatorTests() + { + this.validator = new FeedbackDTOValidator(); + } + + [Fact] + public void ShouldHaveError_WhenEmailIsEmpty() + { + // Arrange + var model = new EmailDTO { Email = string.Empty }; + + // Act + var result = this.validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Email); + } + + [Fact] + public void ShouldHaveError_WhenEmailIsInvalid() + { + // Arrange + var model = new EmailDTO { Email = "not-an-email" }; + + // Act + var result = this.validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Email) + .WithErrorCode("EmailValidator"); + } + + [Fact] + public void ShouldHaveError_WhenMessageIsTooShort() + { + // Arrange + var model = new EmailDTO + { + Email = "test@gmail.com", + Message = "123" + }; + + // Act + var result = this.validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Message); + } + + [Fact] + public void ShouldHaveError_WhenMessageExceedsMaxLength() + { + // Arrange + var model = new EmailDTO + { + Email = "test@gmail.com", + Message = new string('a', 101) + }; + + // Act + var result = this.validator.TestValidate(model); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Message); + } + + [Fact] + public void ShouldNotHaveAnyValidationErrors_WhenDTOIsValid() + { + // Arrange + var model = new EmailDTO + { + Email = "test@gmail.com", + Message = "Hello World!" + }; + + // Act + var result = this.validator.TestValidate(model); + + // Assert + result.ShouldNotHaveAnyValidationErrors(); + } + } +} \ No newline at end of file diff --git a/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackCommandValidatorTests.cs b/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackCommandValidatorTests.cs new file mode 100644 index 0000000..f48a4a5 --- /dev/null +++ b/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackCommandValidatorTests.cs @@ -0,0 +1,70 @@ +namespace Streetcode.Email.XUnitTest.MediatR.Feedback +{ + using FluentAssertions; + using FluentValidation.TestHelper; + using Streetcode.Email.BLL.DTO; + using Streetcode.Email.BLL.MediatR.Feedback; + using Streetcode.Resources; + using Xunit; + + public class SendFeedbackCommandValidatorTests + { + private readonly SendFeedbackCommandValidator validator; + + public SendFeedbackCommandValidatorTests() + { + this.validator = new SendFeedbackCommandValidator(); + } + + [Fact] + public void ShouldReturnError_IfFeedbackIsNull() + { + // Arrange + var command = new SendFeedbackCommand(null!); + + // Act + var result = this.validator.TestValidate(command); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Feedback) + .WithErrorMessage(Messages.Error_CommandDataRequired); + } + + [Fact] + public void ShouldHaveError_WhenFeedbackDTOIsInvalid() + { + // Arrange + var invalidDto = new EmailDTO + { + Email = "invalid-email", + Message = "123" + }; + var command = new SendFeedbackCommand(invalidDto); + + // Act + var result = this.validator.TestValidate(command); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Feedback.Email); + result.ShouldHaveValidationErrorFor(x => x.Feedback.Message); + } + + [Fact] + public void ShouldNotHaveErrors_WhenCommandIsValid() + { + // Arrange + var validDto = new EmailDTO + { + Email = "test@gmail.com", + Message = "Valid message content" + }; + var command = new SendFeedbackCommand(validDto); + + // Act + var result = this.validator.TestValidate(command); + + // Assert + result.ShouldNotHaveAnyValidationErrors(); + } + } +} \ No newline at end of file diff --git a/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackHandlerTests.cs b/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackHandlerTests.cs new file mode 100644 index 0000000..dacaf73 --- /dev/null +++ b/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackHandlerTests.cs @@ -0,0 +1,105 @@ + + +namespace Streetcode.Email.XUnitTest.MediatR.Feedback +{ + using AutoMapper; + using FluentAssertions; + using Hangfire; + using Hangfire.Common; + using Hangfire.States; + using Microsoft.EntityFrameworkCore; + using Microsoft.Extensions.Logging; + using Moq; + using Streetcode.Email.BLL.DTO; + using Streetcode.Email.BLL.Interfaces; + using Streetcode.Email.BLL.MediatR.Feedback; + using Streetcode.Email.DAL.Persistence; + using Streetcode.Email.BLL.Mapping; + using Streetcode.Resources; + public class SendFeedbackHandlerTests : IDisposable + { + private readonly EmailDbContext dbContext; + private readonly Mock> mockLogger; + private readonly Mock mockBackgroundJob; + private readonly IMapper mapper; + private readonly SendFeedbackHandler handler; + + public SendFeedbackHandlerTests() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + + this.dbContext = new EmailDbContext(options); + this.mockLogger = new Mock>(); + this.mockBackgroundJob = new Mock(); + + var configuration = new MapperConfiguration(cfg => + { + cfg.AddProfile(new FeedbackProfile()); + }); + this.mapper = new Mapper(configuration); + + this.handler = new SendFeedbackHandler( + this.dbContext, + this.mapper, + this.mockLogger.Object, + this.mockBackgroundJob.Object); + } + + [Fact] + public async Task Handle_ShouldReturnOk_WhenFeedbackIsSavedSuccessfully() + { + // Arrange + var feedbackDto = new EmailDTO { Email = "test@gmail.com", Message = "Valid message" }; + var command = new SendFeedbackCommand(feedbackDto); + + // Act + var result = await this.handler.Handle(command, CancellationToken.None); + + // Assert + result.IsSuccess.Should().BeTrue(); + this.mockBackgroundJob.Verify(x => x.Create( + It.Is(j => j.Method.Name == nameof(IEmailService.SendEmailAsync)), + It.IsAny()), Times.Once); + } + + [Fact] + public async Task Handle_ShouldReturnFail_IfDatabaseSaveFails() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: "FailDatabase") + .Options; + + var mockContext = new Mock(options) { CallBase = true }; + + mockContext + .Setup(c => c.SaveChangesAsync(It.IsAny())) + .ReturnsAsync(0); + + var failHandler = new SendFeedbackHandler( + mockContext.Object, + this.mapper, + this.mockLogger.Object, + this.mockBackgroundJob.Object); + + var command = new SendFeedbackCommand(new EmailDTO { Email = "fail@test.com", Message = "fail message" }); + + // Act + var result = await failHandler.Handle(command, CancellationToken.None); + + // Assert + result.IsFailed.Should().BeTrue(); + result.Errors.Should().ContainSingle(e => e.Message == Messages.Error_FailedToCreateEntity); + + this.mockBackgroundJob.Verify(x => x.Create(It.IsAny(), It.IsAny()), Times.Never); + } + + public void Dispose() + { + this.dbContext.Database.EnsureDeleted(); + this.dbContext.Dispose(); + } + } +} \ No newline at end of file diff --git a/Streetcode/Streetcode.Email.XUnitTest/Streetcode.Email.XUnitTest.csproj b/Streetcode/Streetcode.Email.XUnitTest/Streetcode.Email.XUnitTest.csproj index 9c5b30a..ec78860 100644 --- a/Streetcode/Streetcode.Email.XUnitTest/Streetcode.Email.XUnitTest.csproj +++ b/Streetcode/Streetcode.Email.XUnitTest/Streetcode.Email.XUnitTest.csproj @@ -11,11 +11,19 @@ + + + + + + + + diff --git a/Streetcode/Streetcode.Email.XUnitTest/UnitTest1.cs b/Streetcode/Streetcode.Email.XUnitTest/UnitTest1.cs deleted file mode 100644 index 6383bfb..0000000 --- a/Streetcode/Streetcode.Email.XUnitTest/UnitTest1.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Streetcode.Email.XUnitTest -{ - public class UnitTest1 - { - [Fact] - public void Test1() - { - - } - } -} \ No newline at end of file From 55303d2d718afa6dfc2b3a18d8f412b7f7d68cd9 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 12:07:30 +0100 Subject: [PATCH 16/31] refactor: rename feedbackEntity in Email Entity --- .../Streetcode.Email.BLL/Mapping/FeedbackProfile.cs | 2 +- .../MediatR/Feedback/SendFeedbackHandler.cs | 2 +- .../Entities/{Feedback.cs => Email.cs} | 8 ++++---- .../Streetcode.Email.DAL/Persistence/EmailDbContext.cs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename Streetcode/Streetcode.Email.DAL/Entities/{Feedback.cs => Email.cs} (68%) diff --git a/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs b/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs index 7aeb47b..02d0023 100644 --- a/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs +++ b/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs @@ -8,7 +8,7 @@ public class FeedbackProfile : Profile { public FeedbackProfile() { - CreateMap().ReverseMap(); + CreateMap().ReverseMap(); } } } diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackHandler.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackHandler.cs index 62c9f7c..b9efb67 100644 --- a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackHandler.cs +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackHandler.cs @@ -7,7 +7,7 @@ using Streetcode.Email.BLL.Interfaces; using Streetcode.Email.DAL.Persistence; using Streetcode.Resources; -using FeedbackEntity = Streetcode.Email.DAL.Entities.Feedback; +using FeedbackEntity = Streetcode.Email.DAL.Entities.Email; namespace Streetcode.Email.BLL.MediatR.Feedback { diff --git a/Streetcode/Streetcode.Email.DAL/Entities/Feedback.cs b/Streetcode/Streetcode.Email.DAL/Entities/Email.cs similarity index 68% rename from Streetcode/Streetcode.Email.DAL/Entities/Feedback.cs rename to Streetcode/Streetcode.Email.DAL/Entities/Email.cs index 80b2e44..3f6920f 100644 --- a/Streetcode/Streetcode.Email.DAL/Entities/Feedback.cs +++ b/Streetcode/Streetcode.Email.DAL/Entities/Email.cs @@ -3,17 +3,17 @@ namespace Streetcode.Email.DAL.Entities; -[Table("feedback", Schema = "email")] -public class Feedback +[Table("emails", Schema = "email")] +public class Email { [Key] public int Id { get; set; } [Required] [EmailAddress] - public string? Email { get; set; } + public string? From { get; set; } [Required] [MinLength(5)] [MaxLength(1000)] - public string? Message { get; set; } + public string? Content { get; set; } } diff --git a/Streetcode/Streetcode.Email.DAL/Persistence/EmailDbContext.cs b/Streetcode/Streetcode.Email.DAL/Persistence/EmailDbContext.cs index 3dfec32..6cf7b23 100644 --- a/Streetcode/Streetcode.Email.DAL/Persistence/EmailDbContext.cs +++ b/Streetcode/Streetcode.Email.DAL/Persistence/EmailDbContext.cs @@ -10,13 +10,13 @@ public EmailDbContext(DbContextOptions options) { } - public DbSet Feedbacks { get; set; } + public DbSet Emails { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - modelBuilder.Entity(entity => + modelBuilder.Entity(entity => { entity.ToTable("Feedbacks"); entity.HasKey(e => e.Id); From 21c2a4b3d14c6ddcd39d7dffef32cd2a7eb33c88 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 12:16:41 +0100 Subject: [PATCH 17/31] refactor: rename feedbackMethods into Email --- .../Streetcode.Email.BLL/DTO/EmailDTO.cs | 4 ++-- .../Interfaces/IEmailService.cs | 2 +- .../Mapping/EmailProfile.cs | 14 +++++++++++++ .../Mapping/FeedbackProfile.cs | 14 ------------- .../EmailDTOValidator.cs} | 6 +++--- .../MediatR/Email/SendEmailCommand.cs | 8 ++++++++ .../Email/SendEmailCommandValidator.cs | 16 +++++++++++++++ .../SendEmailHandler.cs} | 20 +++++++++---------- .../MediatR/Feedback/SendFeedbackCommand.cs | 8 -------- .../Feedback/SendFeedbackCommandValidator.cs | 16 --------------- .../Services/EmailConsumer.cs | 10 +++++----- .../Services/EmailService.cs | 4 ++-- .../Persistence/EmailDbContext.cs | 12 +++++------ .../Email/FeedbackDTOValidatorTests.cs | 6 +++--- .../SendFeedbackCommandValidatorTests.cs | 18 ++++++++--------- .../Email/SendFeedbackHandlerTests.cs | 18 ++++++++--------- .../Contracts/IEmailMessage.cs | 4 ++-- 17 files changed, 90 insertions(+), 90 deletions(-) create mode 100644 Streetcode/Streetcode.Email.BLL/Mapping/EmailProfile.cs delete mode 100644 Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs rename Streetcode/Streetcode.Email.BLL/MediatR/{Feedback/FeedbackDTOValidator.cs => Email/EmailDTOValidator.cs} (67%) create mode 100644 Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailCommand.cs create mode 100644 Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailCommandValidator.cs rename Streetcode/Streetcode.Email.BLL/MediatR/{Feedback/SendFeedbackHandler.cs => Email/SendEmailHandler.cs} (54%) delete mode 100644 Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommand.cs delete mode 100644 Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommandValidator.cs diff --git a/Streetcode/Streetcode.Email.BLL/DTO/EmailDTO.cs b/Streetcode/Streetcode.Email.BLL/DTO/EmailDTO.cs index 35092d6..dfd14e3 100644 --- a/Streetcode/Streetcode.Email.BLL/DTO/EmailDTO.cs +++ b/Streetcode/Streetcode.Email.BLL/DTO/EmailDTO.cs @@ -5,7 +5,7 @@ public class EmailDTO public EmailDTO() { } - public string Email { get; set; } - public string Message { get; set; } + public string From { get; set; } + public string Content { get; set; } } } diff --git a/Streetcode/Streetcode.Email.BLL/Interfaces/IEmailService.cs b/Streetcode/Streetcode.Email.BLL/Interfaces/IEmailService.cs index 04286b2..9582664 100644 --- a/Streetcode/Streetcode.Email.BLL/Interfaces/IEmailService.cs +++ b/Streetcode/Streetcode.Email.BLL/Interfaces/IEmailService.cs @@ -4,6 +4,6 @@ namespace Streetcode.Email.BLL.Interfaces { public interface IEmailService { - Task SendEmailAsync(EmailDTO feedback); + Task SendEmailAsync(EmailDTO email); } } \ No newline at end of file diff --git a/Streetcode/Streetcode.Email.BLL/Mapping/EmailProfile.cs b/Streetcode/Streetcode.Email.BLL/Mapping/EmailProfile.cs new file mode 100644 index 0000000..7bc2a7f --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/Mapping/EmailProfile.cs @@ -0,0 +1,14 @@ +using AutoMapper; +using Streetcode.Email.BLL.DTO; +using EmailEntity = Streetcode.Email.DAL.Entities.Email; + +namespace Streetcode.Email.BLL.Mapping +{ + public class EmailProfile : Profile + { + public EmailProfile() + { + CreateMap().ReverseMap(); + } + } +} diff --git a/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs b/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs deleted file mode 100644 index 02d0023..0000000 --- a/Streetcode/Streetcode.Email.BLL/Mapping/FeedbackProfile.cs +++ /dev/null @@ -1,14 +0,0 @@ -using AutoMapper; -using Streetcode.Email.BLL.DTO; -using Streetcode.Email.DAL.Entities; - -namespace Streetcode.Email.BLL.Mapping -{ - public class FeedbackProfile : Profile - { - public FeedbackProfile() - { - CreateMap().ReverseMap(); - } - } -} diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/FeedbackDTOValidator.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Email/EmailDTOValidator.cs similarity index 67% rename from Streetcode/Streetcode.Email.BLL/MediatR/Feedback/FeedbackDTOValidator.cs rename to Streetcode/Streetcode.Email.BLL/MediatR/Email/EmailDTOValidator.cs index e0b4fbe..5f8a504 100644 --- a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/FeedbackDTOValidator.cs +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Email/EmailDTOValidator.cs @@ -1,11 +1,11 @@ using FluentValidation; using Streetcode.Email.BLL.DTO; -namespace Streetcode.Email.BLL.MediatR.Feedback +namespace Streetcode.Email.BLL.MediatR.Email { - public class FeedbackDTOValidator : AbstractValidator + public class EmailDTOValidator : AbstractValidator { - public FeedbackDTOValidator() + public EmailDTOValidator() { RuleFor(x => x.Email) .NotEmpty() diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailCommand.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailCommand.cs new file mode 100644 index 0000000..98a028a --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailCommand.cs @@ -0,0 +1,8 @@ +using FluentResults; +using MediatR; +using Streetcode.Email.BLL.DTO; + +namespace Streetcode.Email.BLL.MediatR.Email +{ + public record SendEmailCommand(EmailDTO email) : IRequest>; +} diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailCommandValidator.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailCommandValidator.cs new file mode 100644 index 0000000..596df14 --- /dev/null +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailCommandValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; +using Streetcode.Resources; + +namespace Streetcode.Email.BLL.MediatR.Email +{ + public class SendEmailCommandValidator : AbstractValidator + { + public SendEmailCommandValidator() + { + RuleFor(x => x.email) + .NotNull() + .WithMessage(Messages.Error_CommandDataRequired) + .SetValidator(new EmailDTOValidator()); + } + } +} diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackHandler.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailHandler.cs similarity index 54% rename from Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackHandler.cs rename to Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailHandler.cs index b9efb67..1bd550e 100644 --- a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackHandler.cs +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailHandler.cs @@ -7,18 +7,18 @@ using Streetcode.Email.BLL.Interfaces; using Streetcode.Email.DAL.Persistence; using Streetcode.Resources; -using FeedbackEntity = Streetcode.Email.DAL.Entities.Email; +using EmailEntity = Streetcode.Email.DAL.Entities.Email; -namespace Streetcode.Email.BLL.MediatR.Feedback +namespace Streetcode.Email.BLL.MediatR.Email { - public class SendFeedbackHandler : IRequestHandler> + public class SendEmailHandler : IRequestHandler> { private readonly EmailDbContext _context; private readonly IMapper _mapper; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IBackgroundJobClient _backgroundJob; - public SendFeedbackHandler(EmailDbContext context, IMapper mapper, ILogger logger, IBackgroundJobClient backgroundJob) + public SendEmailHandler(EmailDbContext context, IMapper mapper, ILogger logger, IBackgroundJobClient backgroundJob) { _context = context; _mapper = mapper; @@ -26,11 +26,11 @@ public SendFeedbackHandler(EmailDbContext context, IMapper mapper, ILogger> Handle(SendFeedbackCommand request, CancellationToken cancellationToken) + public async Task> Handle(SendEmailCommand request, CancellationToken cancellationToken) { - var feedbackEntity = _mapper.Map(request.Feedback); + var feedbackEntity = _mapper.Map(request.email); - _context.Feedbacks.Add(feedbackEntity); + _context.Emails.Add(feedbackEntity); var rowsAffected = await _context.SaveChangesAsync(cancellationToken); @@ -42,9 +42,9 @@ public async Task> Handle(SendFeedbackCommand request, Cancellation } _backgroundJob.Enqueue(emailService => - emailService.SendEmailAsync(request.Feedback)); + emailService.SendEmailAsync(request.email)); - _logger.LogInformation("Feedback saved to DB and email task enqueued for {Email}", request.Feedback.Email); + _logger.LogInformation("Email saved to DB and email task enqueued for {Email}", request.email.From); return Result.Ok(Unit.Value); } diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommand.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommand.cs deleted file mode 100644 index e513620..0000000 --- a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommand.cs +++ /dev/null @@ -1,8 +0,0 @@ -using FluentResults; -using MediatR; -using Streetcode.Email.BLL.DTO; - -namespace Streetcode.Email.BLL.MediatR.Feedback -{ - public record SendFeedbackCommand(EmailDTO Feedback) : IRequest>; -} diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommandValidator.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommandValidator.cs deleted file mode 100644 index 30dc6bc..0000000 --- a/Streetcode/Streetcode.Email.BLL/MediatR/Feedback/SendFeedbackCommandValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -using FluentValidation; -using Streetcode.Resources; - -namespace Streetcode.Email.BLL.MediatR.Feedback -{ - public class SendFeedbackCommandValidator : AbstractValidator - { - public SendFeedbackCommandValidator() - { - RuleFor(x => x.Feedback) - .NotNull() - .WithMessage(Messages.Error_CommandDataRequired) - .SetValidator(new FeedbackDTOValidator()); - } - } -} diff --git a/Streetcode/Streetcode.Email.BLL/Services/EmailConsumer.cs b/Streetcode/Streetcode.Email.BLL/Services/EmailConsumer.cs index 1f92a6e..7fe53db 100644 --- a/Streetcode/Streetcode.Email.BLL/Services/EmailConsumer.cs +++ b/Streetcode/Streetcode.Email.BLL/Services/EmailConsumer.cs @@ -1,7 +1,7 @@ using MassTransit; using MediatR; using Streetcode.Email.BLL.DTO; -using Streetcode.Email.BLL.MediatR.Feedback; +using Streetcode.Email.BLL.MediatR.Email; using Streetcode.Shared.Contracts; namespace Streetcode.Email.BLL.Services @@ -18,13 +18,13 @@ public async Task Consume(ConsumeContext context) { var message = context.Message; - var feedback = new EmailDTO + var email = new EmailDTO { - Email = message.Email, - Message = message.Message + From = message.From, + Content = message.Content }; - var command = new SendFeedbackCommand(feedback); + var command = new SendEmailCommand(email); await _mediator.Send(command); } diff --git a/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs b/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs index 3f51515..575043a 100644 --- a/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs +++ b/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs @@ -17,7 +17,7 @@ public EmailService(IOptions options) _emailConfig = options.Value; } - public async Task SendEmailAsync(EmailDTO feedback) + public async Task SendEmailAsync(EmailDTO email) { var message = new MimeMessage(); message.From.Add(new MailboxAddress("Streetcode", _emailConfig.FromAddress)); @@ -25,7 +25,7 @@ public async Task SendEmailAsync(EmailDTO feedback) message.Subject = $"New feedback from Streetcode User"; message.Body = new TextPart("plain") { - Text = $"Користувач {feedback.Email} залишив повідомлення:\n\n{feedback.Message}" + Text = $"Користувач {email.From} залишив повідомлення:\n\n{email.Content}" }; using var client = new SmtpClient(); diff --git a/Streetcode/Streetcode.Email.DAL/Persistence/EmailDbContext.cs b/Streetcode/Streetcode.Email.DAL/Persistence/EmailDbContext.cs index 6cf7b23..bbc7001 100644 --- a/Streetcode/Streetcode.Email.DAL/Persistence/EmailDbContext.cs +++ b/Streetcode/Streetcode.Email.DAL/Persistence/EmailDbContext.cs @@ -1,5 +1,5 @@ using Microsoft.EntityFrameworkCore; -using Streetcode.Email.DAL.Entities; +using EmailEntity = Streetcode.Email.DAL.Entities.Email; namespace Streetcode.Email.DAL.Persistence { @@ -10,18 +10,18 @@ public EmailDbContext(DbContextOptions options) { } - public DbSet Emails { get; set; } + public DbSet Emails { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - modelBuilder.Entity(entity => + modelBuilder.Entity(entity => { - entity.ToTable("Feedbacks"); + entity.ToTable("Emails"); entity.HasKey(e => e.Id); - entity.Property(e => e.Email).IsRequired().HasMaxLength(256); - entity.Property(e => e.Message).IsRequired(); + entity.Property(e => e.From).IsRequired().HasMaxLength(256); + entity.Property(e => e.Content).IsRequired(); }); } } diff --git a/Streetcode/Streetcode.Email.XUnitTest/Email/FeedbackDTOValidatorTests.cs b/Streetcode/Streetcode.Email.XUnitTest/Email/FeedbackDTOValidatorTests.cs index 2c9758d..c702e53 100644 --- a/Streetcode/Streetcode.Email.XUnitTest/Email/FeedbackDTOValidatorTests.cs +++ b/Streetcode/Streetcode.Email.XUnitTest/Email/FeedbackDTOValidatorTests.cs @@ -2,16 +2,16 @@ { using FluentValidation.TestHelper; using Streetcode.Email.BLL.DTO; - using Streetcode.Email.BLL.MediatR.Feedback; + using Streetcode.Email.BLL.MediatR.Email; using Xunit; public class FeedbackDTOValidatorTests { - private readonly FeedbackDTOValidator validator; + private readonly EmailDTOValidator validator; public FeedbackDTOValidatorTests() { - this.validator = new FeedbackDTOValidator(); + this.validator = new EmailDTOValidator(); } [Fact] diff --git a/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackCommandValidatorTests.cs b/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackCommandValidatorTests.cs index f48a4a5..8834512 100644 --- a/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackCommandValidatorTests.cs +++ b/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackCommandValidatorTests.cs @@ -3,30 +3,30 @@ using FluentAssertions; using FluentValidation.TestHelper; using Streetcode.Email.BLL.DTO; - using Streetcode.Email.BLL.MediatR.Feedback; + using Streetcode.Email.BLL.MediatR.Email; using Streetcode.Resources; using Xunit; public class SendFeedbackCommandValidatorTests { - private readonly SendFeedbackCommandValidator validator; + private readonly SendEmailCommandValidator validator; public SendFeedbackCommandValidatorTests() { - this.validator = new SendFeedbackCommandValidator(); + this.validator = new SendEmailCommandValidator(); } [Fact] public void ShouldReturnError_IfFeedbackIsNull() { // Arrange - var command = new SendFeedbackCommand(null!); + var command = new SendEmailCommand(null!); // Act var result = this.validator.TestValidate(command); // Assert - result.ShouldHaveValidationErrorFor(x => x.Feedback) + result.ShouldHaveValidationErrorFor(x => x.email) .WithErrorMessage(Messages.Error_CommandDataRequired); } @@ -39,14 +39,14 @@ public void ShouldHaveError_WhenFeedbackDTOIsInvalid() Email = "invalid-email", Message = "123" }; - var command = new SendFeedbackCommand(invalidDto); + var command = new SendEmailCommand(invalidDto); // Act var result = this.validator.TestValidate(command); // Assert - result.ShouldHaveValidationErrorFor(x => x.Feedback.Email); - result.ShouldHaveValidationErrorFor(x => x.Feedback.Message); + result.ShouldHaveValidationErrorFor(x => x.email.Email); + result.ShouldHaveValidationErrorFor(x => x.email.Message); } [Fact] @@ -58,7 +58,7 @@ public void ShouldNotHaveErrors_WhenCommandIsValid() Email = "test@gmail.com", Message = "Valid message content" }; - var command = new SendFeedbackCommand(validDto); + var command = new SendEmailCommand(validDto); // Act var result = this.validator.TestValidate(command); diff --git a/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackHandlerTests.cs b/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackHandlerTests.cs index dacaf73..dd0f718 100644 --- a/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackHandlerTests.cs +++ b/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackHandlerTests.cs @@ -12,17 +12,17 @@ namespace Streetcode.Email.XUnitTest.MediatR.Feedback using Moq; using Streetcode.Email.BLL.DTO; using Streetcode.Email.BLL.Interfaces; - using Streetcode.Email.BLL.MediatR.Feedback; + using Streetcode.Email.BLL.MediatR.Email; using Streetcode.Email.DAL.Persistence; using Streetcode.Email.BLL.Mapping; using Streetcode.Resources; public class SendFeedbackHandlerTests : IDisposable { private readonly EmailDbContext dbContext; - private readonly Mock> mockLogger; + private readonly Mock> mockLogger; private readonly Mock mockBackgroundJob; private readonly IMapper mapper; - private readonly SendFeedbackHandler handler; + private readonly SendEmailHandler handler; public SendFeedbackHandlerTests() { @@ -31,16 +31,16 @@ public SendFeedbackHandlerTests() .Options; this.dbContext = new EmailDbContext(options); - this.mockLogger = new Mock>(); + this.mockLogger = new Mock>(); this.mockBackgroundJob = new Mock(); var configuration = new MapperConfiguration(cfg => { - cfg.AddProfile(new FeedbackProfile()); + cfg.AddProfile(new EmailProfile()); }); this.mapper = new Mapper(configuration); - this.handler = new SendFeedbackHandler( + this.handler = new SendEmailHandler( this.dbContext, this.mapper, this.mockLogger.Object, @@ -52,7 +52,7 @@ public async Task Handle_ShouldReturnOk_WhenFeedbackIsSavedSuccessfully() { // Arrange var feedbackDto = new EmailDTO { Email = "test@gmail.com", Message = "Valid message" }; - var command = new SendFeedbackCommand(feedbackDto); + var command = new SendEmailCommand(feedbackDto); // Act var result = await this.handler.Handle(command, CancellationToken.None); @@ -78,13 +78,13 @@ public async Task Handle_ShouldReturnFail_IfDatabaseSaveFails() .Setup(c => c.SaveChangesAsync(It.IsAny())) .ReturnsAsync(0); - var failHandler = new SendFeedbackHandler( + var failHandler = new SendEmailHandler( mockContext.Object, this.mapper, this.mockLogger.Object, this.mockBackgroundJob.Object); - var command = new SendFeedbackCommand(new EmailDTO { Email = "fail@test.com", Message = "fail message" }); + var command = new SendEmailCommand(new EmailDTO { Email = "fail@test.com", Message = "fail message" }); // Act var result = await failHandler.Handle(command, CancellationToken.None); diff --git a/Streetcode/Streetcode.Shared/Contracts/IEmailMessage.cs b/Streetcode/Streetcode.Shared/Contracts/IEmailMessage.cs index 8c57144..a13df68 100644 --- a/Streetcode/Streetcode.Shared/Contracts/IEmailMessage.cs +++ b/Streetcode/Streetcode.Shared/Contracts/IEmailMessage.cs @@ -2,7 +2,7 @@ { public interface IEmailMessage { - public string Email { get; } - public string Message { get; } + public string From { get; } + public string Content { get; } } } From 508396632425aedd0ab4eeb7c445cdf031c18335 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 12:35:43 +0100 Subject: [PATCH 18/31] refactor: preparing for db update --- .../MediatR/Email/EmailDTOValidator.cs | 4 +- ...112047_RenameFeedbackIntoEmail.Designer.cs | 52 +++++++++++++++ .../20260302112047_RenameFeedbackIntoEmail.cs | 66 +++++++++++++++++++ .../Migrations/EmailDbContextModelSnapshot.cs | 16 ++--- .../Email/FeedbackDTOValidatorTests.cs | 24 +++---- .../SendFeedbackCommandValidatorTests.cs | 12 ++-- .../Email/SendFeedbackHandlerTests.cs | 4 +- 7 files changed, 148 insertions(+), 30 deletions(-) create mode 100644 Streetcode/Streetcode.Email.DAL/Migrations/20260302112047_RenameFeedbackIntoEmail.Designer.cs create mode 100644 Streetcode/Streetcode.Email.DAL/Migrations/20260302112047_RenameFeedbackIntoEmail.cs diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Email/EmailDTOValidator.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Email/EmailDTOValidator.cs index 5f8a504..8f741cb 100644 --- a/Streetcode/Streetcode.Email.BLL/MediatR/Email/EmailDTOValidator.cs +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Email/EmailDTOValidator.cs @@ -7,11 +7,11 @@ public class EmailDTOValidator : AbstractValidator { public EmailDTOValidator() { - RuleFor(x => x.Email) + RuleFor(x => x.From) .NotEmpty() .EmailAddress(); - RuleFor(x => x.Message) + RuleFor(x => x.Content) .NotEmpty() .MinimumLength(5) .MaximumLength(100); diff --git a/Streetcode/Streetcode.Email.DAL/Migrations/20260302112047_RenameFeedbackIntoEmail.Designer.cs b/Streetcode/Streetcode.Email.DAL/Migrations/20260302112047_RenameFeedbackIntoEmail.Designer.cs new file mode 100644 index 0000000..9d2b1b6 --- /dev/null +++ b/Streetcode/Streetcode.Email.DAL/Migrations/20260302112047_RenameFeedbackIntoEmail.Designer.cs @@ -0,0 +1,52 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Streetcode.Email.DAL.Persistence; + +#nullable disable + +namespace Streetcode.Email.DAL.Migrations +{ + [DbContext(typeof(EmailDbContext))] + [Migration("20260302112047_RenameFeedbackIntoEmail")] + partial class RenameFeedbackIntoEmail + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.23") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Streetcode.Email.DAL.Entities.Email", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("From") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.ToTable("Emails", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Streetcode/Streetcode.Email.DAL/Migrations/20260302112047_RenameFeedbackIntoEmail.cs b/Streetcode/Streetcode.Email.DAL/Migrations/20260302112047_RenameFeedbackIntoEmail.cs new file mode 100644 index 0000000..fca7f70 --- /dev/null +++ b/Streetcode/Streetcode.Email.DAL/Migrations/20260302112047_RenameFeedbackIntoEmail.cs @@ -0,0 +1,66 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Streetcode.Email.DAL.Migrations +{ + /// + public partial class RenameFeedbackIntoEmail : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameTable( + name: "Feedbacks", + newName: "Emails"); + + migrationBuilder.RenameColumn( + name: "Email", + table: "Emails", + newName: "From"); + + migrationBuilder.RenameColumn( + name: "Message", + table: "Emails", + newName: "Content"); + + migrationBuilder.AlterColumn( + name: "Content", + table: "Emails", + type: "nvarchar(1000)", + maxLength: 1000, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(100)", + oldMaxLength: 100); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameTable( + name: "Emails", + newName: "Feedbacks"); + + migrationBuilder.RenameColumn( + name: "From", + table: "Feedbacks", + newName: "Email"); + + migrationBuilder.RenameColumn( + name: "Content", + table: "Feedbacks", + newName: "Message"); + + migrationBuilder.AlterColumn( + name: "Content", + table: "Emails", + type: "nvarchar(1000)", + maxLength: 1000, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(100)", + oldMaxLength: 100); + } + } +} diff --git a/Streetcode/Streetcode.Email.DAL/Migrations/EmailDbContextModelSnapshot.cs b/Streetcode/Streetcode.Email.DAL/Migrations/EmailDbContextModelSnapshot.cs index aa0b094..c8de553 100644 --- a/Streetcode/Streetcode.Email.DAL/Migrations/EmailDbContextModelSnapshot.cs +++ b/Streetcode/Streetcode.Email.DAL/Migrations/EmailDbContextModelSnapshot.cs @@ -21,7 +21,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - modelBuilder.Entity("Streetcode.Email.DAL.Entities.Feedback", b => + modelBuilder.Entity("Streetcode.Email.DAL.Entities.Email", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -29,19 +29,19 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); - b.Property("Email") + b.Property("Content") .IsRequired() - .HasMaxLength(256) - .HasColumnType("nvarchar(256)"); + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); - b.Property("Message") + b.Property("From") .IsRequired() - .HasMaxLength(100) - .HasColumnType("nvarchar(100)"); + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); b.HasKey("Id"); - b.ToTable("Feedbacks", (string)null); + b.ToTable("Emails", (string)null); }); #pragma warning restore 612, 618 } diff --git a/Streetcode/Streetcode.Email.XUnitTest/Email/FeedbackDTOValidatorTests.cs b/Streetcode/Streetcode.Email.XUnitTest/Email/FeedbackDTOValidatorTests.cs index c702e53..07b2491 100644 --- a/Streetcode/Streetcode.Email.XUnitTest/Email/FeedbackDTOValidatorTests.cs +++ b/Streetcode/Streetcode.Email.XUnitTest/Email/FeedbackDTOValidatorTests.cs @@ -18,26 +18,26 @@ public FeedbackDTOValidatorTests() public void ShouldHaveError_WhenEmailIsEmpty() { // Arrange - var model = new EmailDTO { Email = string.Empty }; + var model = new EmailDTO { From = string.Empty }; // Act var result = this.validator.TestValidate(model); // Assert - result.ShouldHaveValidationErrorFor(x => x.Email); + result.ShouldHaveValidationErrorFor(x => x.From); } [Fact] public void ShouldHaveError_WhenEmailIsInvalid() { // Arrange - var model = new EmailDTO { Email = "not-an-email" }; + var model = new EmailDTO { From = "not-an-email" }; // Act var result = this.validator.TestValidate(model); // Assert - result.ShouldHaveValidationErrorFor(x => x.Email) + result.ShouldHaveValidationErrorFor(x => x.From) .WithErrorCode("EmailValidator"); } @@ -47,15 +47,15 @@ public void ShouldHaveError_WhenMessageIsTooShort() // Arrange var model = new EmailDTO { - Email = "test@gmail.com", - Message = "123" + From = "test@gmail.com", + Content = "123" }; // Act var result = this.validator.TestValidate(model); // Assert - result.ShouldHaveValidationErrorFor(x => x.Message); + result.ShouldHaveValidationErrorFor(x => x.Content); } [Fact] @@ -64,15 +64,15 @@ public void ShouldHaveError_WhenMessageExceedsMaxLength() // Arrange var model = new EmailDTO { - Email = "test@gmail.com", - Message = new string('a', 101) + From = "test@gmail.com", + Content = new string('a', 101) }; // Act var result = this.validator.TestValidate(model); // Assert - result.ShouldHaveValidationErrorFor(x => x.Message); + result.ShouldHaveValidationErrorFor(x => x.Content); } [Fact] @@ -81,8 +81,8 @@ public void ShouldNotHaveAnyValidationErrors_WhenDTOIsValid() // Arrange var model = new EmailDTO { - Email = "test@gmail.com", - Message = "Hello World!" + From = "test@gmail.com", + Content = "Hello World!" }; // Act diff --git a/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackCommandValidatorTests.cs b/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackCommandValidatorTests.cs index 8834512..bcad90a 100644 --- a/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackCommandValidatorTests.cs +++ b/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackCommandValidatorTests.cs @@ -36,8 +36,8 @@ public void ShouldHaveError_WhenFeedbackDTOIsInvalid() // Arrange var invalidDto = new EmailDTO { - Email = "invalid-email", - Message = "123" + From = "invalid-email", + Content = "123" }; var command = new SendEmailCommand(invalidDto); @@ -45,8 +45,8 @@ public void ShouldHaveError_WhenFeedbackDTOIsInvalid() var result = this.validator.TestValidate(command); // Assert - result.ShouldHaveValidationErrorFor(x => x.email.Email); - result.ShouldHaveValidationErrorFor(x => x.email.Message); + result.ShouldHaveValidationErrorFor(x => x.email.From); + result.ShouldHaveValidationErrorFor(x => x.email.Content); } [Fact] @@ -55,8 +55,8 @@ public void ShouldNotHaveErrors_WhenCommandIsValid() // Arrange var validDto = new EmailDTO { - Email = "test@gmail.com", - Message = "Valid message content" + From = "test@gmail.com", + Content = "Valid message content" }; var command = new SendEmailCommand(validDto); diff --git a/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackHandlerTests.cs b/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackHandlerTests.cs index dd0f718..59cabd1 100644 --- a/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackHandlerTests.cs +++ b/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackHandlerTests.cs @@ -51,7 +51,7 @@ public SendFeedbackHandlerTests() public async Task Handle_ShouldReturnOk_WhenFeedbackIsSavedSuccessfully() { // Arrange - var feedbackDto = new EmailDTO { Email = "test@gmail.com", Message = "Valid message" }; + var feedbackDto = new EmailDTO { From = "test@gmail.com", Content = "Valid message" }; var command = new SendEmailCommand(feedbackDto); // Act @@ -84,7 +84,7 @@ public async Task Handle_ShouldReturnFail_IfDatabaseSaveFails() this.mockLogger.Object, this.mockBackgroundJob.Object); - var command = new SendEmailCommand(new EmailDTO { Email = "fail@test.com", Message = "fail message" }); + var command = new SendEmailCommand(new EmailDTO { From = "fail@test.com", Content = "fail message" }); // Act var result = await failHandler.Handle(command, CancellationToken.None); From 06470cc96f0346ba279ff669f5c47e7e7fbbc6a5 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 12:44:19 +0100 Subject: [PATCH 19/31] refactor: renamed test from feedback to email --- ...CommandValidatorTests.cs => EmailCommandValidatorTests.cs} | 4 ++-- ...FeedbackDTOValidatorTests.cs => EmailDTOValidatorTests.cs} | 4 ++-- .../{SendFeedbackHandlerTests.cs => SendEmailHandlerTests.cs} | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) rename Streetcode/Streetcode.Email.XUnitTest/Email/{SendFeedbackCommandValidatorTests.cs => EmailCommandValidatorTests.cs} (94%) rename Streetcode/Streetcode.Email.XUnitTest/Email/{FeedbackDTOValidatorTests.cs => EmailDTOValidatorTests.cs} (96%) rename Streetcode/Streetcode.Email.XUnitTest/Email/{SendFeedbackHandlerTests.cs => SendEmailHandlerTests.cs} (97%) diff --git a/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackCommandValidatorTests.cs b/Streetcode/Streetcode.Email.XUnitTest/Email/EmailCommandValidatorTests.cs similarity index 94% rename from Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackCommandValidatorTests.cs rename to Streetcode/Streetcode.Email.XUnitTest/Email/EmailCommandValidatorTests.cs index bcad90a..e874842 100644 --- a/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackCommandValidatorTests.cs +++ b/Streetcode/Streetcode.Email.XUnitTest/Email/EmailCommandValidatorTests.cs @@ -7,11 +7,11 @@ using Streetcode.Resources; using Xunit; - public class SendFeedbackCommandValidatorTests + public class EmailCommandValidatorTests { private readonly SendEmailCommandValidator validator; - public SendFeedbackCommandValidatorTests() + public EmailCommandValidatorTests() { this.validator = new SendEmailCommandValidator(); } diff --git a/Streetcode/Streetcode.Email.XUnitTest/Email/FeedbackDTOValidatorTests.cs b/Streetcode/Streetcode.Email.XUnitTest/Email/EmailDTOValidatorTests.cs similarity index 96% rename from Streetcode/Streetcode.Email.XUnitTest/Email/FeedbackDTOValidatorTests.cs rename to Streetcode/Streetcode.Email.XUnitTest/Email/EmailDTOValidatorTests.cs index 07b2491..873dc7d 100644 --- a/Streetcode/Streetcode.Email.XUnitTest/Email/FeedbackDTOValidatorTests.cs +++ b/Streetcode/Streetcode.Email.XUnitTest/Email/EmailDTOValidatorTests.cs @@ -5,11 +5,11 @@ using Streetcode.Email.BLL.MediatR.Email; using Xunit; - public class FeedbackDTOValidatorTests + public class EmailDTOValidatorTests { private readonly EmailDTOValidator validator; - public FeedbackDTOValidatorTests() + public EmailDTOValidatorTests() { this.validator = new EmailDTOValidator(); } diff --git a/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackHandlerTests.cs b/Streetcode/Streetcode.Email.XUnitTest/Email/SendEmailHandlerTests.cs similarity index 97% rename from Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackHandlerTests.cs rename to Streetcode/Streetcode.Email.XUnitTest/Email/SendEmailHandlerTests.cs index 59cabd1..74f7e20 100644 --- a/Streetcode/Streetcode.Email.XUnitTest/Email/SendFeedbackHandlerTests.cs +++ b/Streetcode/Streetcode.Email.XUnitTest/Email/SendEmailHandlerTests.cs @@ -16,7 +16,7 @@ namespace Streetcode.Email.XUnitTest.MediatR.Feedback using Streetcode.Email.DAL.Persistence; using Streetcode.Email.BLL.Mapping; using Streetcode.Resources; - public class SendFeedbackHandlerTests : IDisposable + public class SendEmailHandlerTests : IDisposable { private readonly EmailDbContext dbContext; private readonly Mock> mockLogger; @@ -24,7 +24,7 @@ public class SendFeedbackHandlerTests : IDisposable private readonly IMapper mapper; private readonly SendEmailHandler handler; - public SendFeedbackHandlerTests() + public SendEmailHandlerTests() { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) From 1653f03c1c6926fd0adb6c1579da2e529634cb08 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 12:49:19 +0100 Subject: [PATCH 20/31] refactor: renamed main api endpoint from feedback to email --- Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs | 4 ++-- .../Streetcode.WebApi/Controllers/Email/EmailController.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs b/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs index c341dfd..137074b 100644 --- a/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs +++ b/Streetcode/Streetcode.BLL/DTO/Email/EmailDTO.cs @@ -9,10 +9,10 @@ public EmailDTO() } [MaxLength(80)] - public string Email { get; set; } + public string From { get; set; } [Required] [StringLength(500, MinimumLength = 1)] - public string Message { get; set; } + public string Content { get; set; } } } \ No newline at end of file diff --git a/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs b/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs index 7146bd7..f0d2ba9 100644 --- a/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs +++ b/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs @@ -21,11 +21,11 @@ public async Task Send([FromBody] EmailDTO email) { await _publishEndpoint.Publish(new { - email.Email, - email.Message + email.From, + email.Content }); - return Ok(); + return Accepted(); } } } From eb121c8d52b1fa3929556a93d6ff7bec0a997521 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 13:26:28 +0100 Subject: [PATCH 21/31] refactor: fix spacing --- .../MediatR/Email/EmailDTOValidator.cs | 4 +-- .../MediatR/Email/SendEmailHandler.cs | 2 +- .../Extensions/ServiceCollectionExtensions.cs | 27 ++++++++++--------- .../Controllers/Email/EmailController.cs | 11 ++++---- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Email/EmailDTOValidator.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Email/EmailDTOValidator.cs index 8f741cb..0b5378c 100644 --- a/Streetcode/Streetcode.Email.BLL/MediatR/Email/EmailDTOValidator.cs +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Email/EmailDTOValidator.cs @@ -7,11 +7,11 @@ public class EmailDTOValidator : AbstractValidator { public EmailDTOValidator() { - RuleFor(x => x.From) + RuleFor(x => x.From) .NotEmpty() .EmailAddress(); - RuleFor(x => x.Content) + RuleFor(x => x.Content) .NotEmpty() .MinimumLength(5) .MaximumLength(100); diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailHandler.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailHandler.cs index 1bd550e..58a0dbf 100644 --- a/Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailHandler.cs +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailHandler.cs @@ -42,7 +42,7 @@ public async Task> Handle(SendEmailCommand request, CancellationTok } _backgroundJob.Enqueue(emailService => - emailService.SendEmailAsync(request.email)); + emailService.SendEmailAsync(request.email)); _logger.LogInformation("Email saved to DB and email task enqueued for {Email}", request.email.From); diff --git a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs index 79d978b..1368ce8 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs +++ b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs @@ -29,23 +29,24 @@ public static void AddApplicationServices(this IServiceCollection services, Conf }); services.AddHangfire(config => config - .SetDataCompatibilityLevel(CompatibilityLevel.Version_180) - .UseSimpleAssemblyNameTypeSerializer() - .UseRecommendedSerializerSettings() - .UseSqlServerStorage(connectionString, new SqlServerStorageOptions - { - PrepareSchemaIfNecessary = true, + .SetDataCompatibilityLevel(CompatibilityLevel.Version_180) + .UseSimpleAssemblyNameTypeSerializer() + .UseRecommendedSerializerSettings() + .UseSqlServerStorage(connectionString, new SqlServerStorageOptions + { + PrepareSchemaIfNecessary = true, - CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), - SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5), - QueuePollInterval = TimeSpan.Zero, - UseRecommendedIsolationLevel = true, - DisableGlobalLocks = true - })); + CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), + SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5), + QueuePollInterval = TimeSpan.Zero, + UseRecommendedIsolationLevel = true, + DisableGlobalLocks = true + })); services.AddHangfireServer(); services.AddLogging(); + services.AddControllers(); } public static void AddCustomServices(this IServiceCollection services, IConfiguration configuration) @@ -78,4 +79,4 @@ public static void AddCustomServices(this IServiceCollection services, IConfigur } } -} +} \ No newline at end of file diff --git a/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs b/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs index f0d2ba9..e6c1192 100644 --- a/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs +++ b/Streetcode/Streetcode.WebApi/Controllers/Email/EmailController.cs @@ -19,11 +19,12 @@ public EmailController(IPublishEndpoint publishEndpoint) [AllowAnonymous] public async Task Send([FromBody] EmailDTO email) { - await _publishEndpoint.Publish(new - { - email.From, - email.Content - }); + await _publishEndpoint.Publish( + new + { + email.From, + email.Content + }); return Accepted(); } From 40d650ea52034858cb39bff4af99d0069a58f954 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 15:18:02 +0100 Subject: [PATCH 22/31] chore: stop tracking dockerpassword.env --- Streetcode/dockerpassword.env | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Streetcode/dockerpassword.env diff --git a/Streetcode/dockerpassword.env b/Streetcode/dockerpassword.env deleted file mode 100644 index 4266b09..0000000 --- a/Streetcode/dockerpassword.env +++ /dev/null @@ -1 +0,0 @@ -DB_PASSWORD=Strong_Pass_123!D32@ \ No newline at end of file From 17c9520c051946bc046c5bffe337cf434253eaff Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 15:59:18 +0100 Subject: [PATCH 23/31] fix: change order of migration down build --- .../20260302112047_RenameFeedbackIntoEmail.cs | 20 +++++++++---------- Streetcode/dockerpassword.env | 1 + 2 files changed, 11 insertions(+), 10 deletions(-) create mode 100644 Streetcode/dockerpassword.env diff --git a/Streetcode/Streetcode.Email.DAL/Migrations/20260302112047_RenameFeedbackIntoEmail.cs b/Streetcode/Streetcode.Email.DAL/Migrations/20260302112047_RenameFeedbackIntoEmail.cs index fca7f70..331f7ce 100644 --- a/Streetcode/Streetcode.Email.DAL/Migrations/20260302112047_RenameFeedbackIntoEmail.cs +++ b/Streetcode/Streetcode.Email.DAL/Migrations/20260302112047_RenameFeedbackIntoEmail.cs @@ -38,6 +38,16 @@ protected override void Up(MigrationBuilder migrationBuilder) /// protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.AlterColumn( + name: "Content", + table: "Emails", + type: "nvarchar(100)", + maxLength: 100, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(1000)", + oldMaxLength: 1000); + migrationBuilder.RenameTable( name: "Emails", newName: "Feedbacks"); @@ -51,16 +61,6 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "Content", table: "Feedbacks", newName: "Message"); - - migrationBuilder.AlterColumn( - name: "Content", - table: "Emails", - type: "nvarchar(1000)", - maxLength: 1000, - nullable: false, - oldClrType: typeof(string), - oldType: "nvarchar(100)", - oldMaxLength: 100); } } } diff --git a/Streetcode/dockerpassword.env b/Streetcode/dockerpassword.env new file mode 100644 index 0000000..4266b09 --- /dev/null +++ b/Streetcode/dockerpassword.env @@ -0,0 +1 @@ +DB_PASSWORD=Strong_Pass_123!D32@ \ No newline at end of file From 9f155a27bfc27df36346120806b93e9e5e15dd2a Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 16:00:30 +0100 Subject: [PATCH 24/31] chore: stop tracking dockerpassword.env --- Streetcode/dockerpassword.env | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Streetcode/dockerpassword.env diff --git a/Streetcode/dockerpassword.env b/Streetcode/dockerpassword.env deleted file mode 100644 index 4266b09..0000000 --- a/Streetcode/dockerpassword.env +++ /dev/null @@ -1 +0,0 @@ -DB_PASSWORD=Strong_Pass_123!D32@ \ No newline at end of file From ffa66c5ae53324b17fd3ae09623523a755d49611 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 16:08:24 +0100 Subject: [PATCH 25/31] fix: replace hardcoder RabbitMQ configuration --- .../Extensions/ServiceCollectionExtensions.cs | 17 +++++++++++++---- .../Streetcode.Email.WebAPI/appsettings.json | 6 ++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs index 1368ce8..356e31b 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs +++ b/Streetcode/Streetcode.Email.WebAPI/Extensions/ServiceCollectionExtensions.cs @@ -65,18 +65,27 @@ public static void AddCustomServices(this IServiceCollection services, IConfigur services.AddMassTransit(x => { - x.SetKebabCaseEndpointNameFormatter(); - x.AddConsumer(); x.UsingRabbitMq((context, cfg) => { - cfg.Host("localhost", "/"); + var rabbitSection = configuration.GetSection("RabbitMQ"); + + var host = rabbitSection["Host"] + ?? throw new InvalidOperationException("RabbitMQ Host is missing"); + var username = rabbitSection["Username"] + ?? throw new InvalidOperationException("RabbitMQ Username is missing"); + var password = rabbitSection["Password"] + ?? throw new InvalidOperationException("RabbitMQ Password is missing"); + cfg.Host(host, "/", h => + { + h.Username(username); + h.Password(password); + }); cfg.ConfigureEndpoints(context); }); }); - } } } \ No newline at end of file diff --git a/Streetcode/Streetcode.Email.WebAPI/appsettings.json b/Streetcode/Streetcode.Email.WebAPI/appsettings.json index ad3c0e3..c451cd8 100644 --- a/Streetcode/Streetcode.Email.WebAPI/appsettings.json +++ b/Streetcode/Streetcode.Email.WebAPI/appsettings.json @@ -8,7 +8,9 @@ "AllowedHosts": "*", "ConnectionStrings": { -"DefaultConnection": "ConnectionString", + "DefaultConnection": "ConnectionString", "Redis": "Redis" - } + }, + + } From 77e7b8871def73082988b78f4979e10880db02b4 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 16:14:27 +0100 Subject: [PATCH 26/31] fix: add construction try-catch for emailService --- .../Services/EmailService.cs | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs b/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs index 575043a..92ed4d1 100644 --- a/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs +++ b/Streetcode/Streetcode.Email.BLL/Services/EmailService.cs @@ -1,40 +1,55 @@ using MailKit.Net.Smtp; -using MimeKit; using MailKit.Security; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using MimeKit; using Streetcode.Email.BLL.Configs; using Streetcode.Email.BLL.DTO; using Streetcode.Email.BLL.Interfaces; +using System.Data; namespace Streetcode.Email.BLL.Services { public class EmailService : IEmailService { private readonly EmailConfiguration _emailConfig; + private readonly ILogger _logger; - public EmailService(IOptions options) + public EmailService(IOptions options, ILogger logger) { _emailConfig = options.Value; + _logger = logger; } public async Task SendEmailAsync(EmailDTO email) { - var message = new MimeMessage(); - message.From.Add(new MailboxAddress("Streetcode", _emailConfig.FromAddress)); - message.To.Add(new MailboxAddress("Streetcode Admin", _emailConfig.AdminAddress)); - message.Subject = $"New feedback from Streetcode User"; - message.Body = new TextPart("plain") + try { - Text = $"Користувач {email.From} залишив повідомлення:\n\n{email.Content}" - }; + var message = new MimeMessage(); + message.From.Add(new MailboxAddress("Streetcode", _emailConfig.FromAddress)); + message.To.Add(new MailboxAddress("Streetcode Admin", _emailConfig.AdminAddress)); + message.Subject = $"New feedback from Streetcode User"; + message.Body = new TextPart("plain") + { + Text = $"Користувач {email.From} залишив повідомлення:\n\n{email.Content}" + }; + + using var client = new SmtpClient(); - using var client = new SmtpClient(); + await client.ConnectAsync(_emailConfig.SmtpServer, _emailConfig.Port, SecureSocketOptions.Auto); + await client.AuthenticateAsync(_emailConfig.SmtpUser, _emailConfig.SmtpPassword); - await client.ConnectAsync(_emailConfig.SmtpServer, _emailConfig.Port, SecureSocketOptions.Auto); - await client.AuthenticateAsync(_emailConfig.SmtpUser, _emailConfig.SmtpPassword); + await client.SendAsync(message); + await client.DisconnectAsync(true); + + _logger.LogInformation("Email sent successfully to {Recipient} from {Sender}.", _emailConfig.AdminAddress, email.From); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to send email to {Recipient} due to an error.", _emailConfig.AdminAddress); - await client.SendAsync(message); - await client.DisconnectAsync(true); + throw; + } } } } From 1e75cb94e8af44dd4efa4a72163ca24ed1ad2d1e Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 16:17:07 +0100 Subject: [PATCH 27/31] fix: changes access to hangfire, for differnt enviroment --- Streetcode/Streetcode.Email.WebAPI/Program.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Streetcode/Streetcode.Email.WebAPI/Program.cs b/Streetcode/Streetcode.Email.WebAPI/Program.cs index 760a1b1..9a9684b 100644 --- a/Streetcode/Streetcode.Email.WebAPI/Program.cs +++ b/Streetcode/Streetcode.Email.WebAPI/Program.cs @@ -9,7 +9,7 @@ var app = builder.Build(); -if (app.Environment.EnvironmentName == "Local") +if (app.Environment.IsDevelopment() || app.Environment.EnvironmentName == "Local") { app.UseHangfireDashboard("/dash"); } @@ -22,8 +22,6 @@ // app.SeedDataAsync(); // uncomment for seeding data in local -app.UseHangfireDashboard("/dash"); - app.MapControllers(); app.Run(); From 0af405b1b2873a81dcf19ebb3995c0aef8f11d89 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 16:18:41 +0100 Subject: [PATCH 28/31] fix: change attribute validation on 1000 from 100 like entity allow --- .../Streetcode.Email.BLL/MediatR/Email/EmailDTOValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Email/EmailDTOValidator.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Email/EmailDTOValidator.cs index 0b5378c..5ccf163 100644 --- a/Streetcode/Streetcode.Email.BLL/MediatR/Email/EmailDTOValidator.cs +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Email/EmailDTOValidator.cs @@ -14,7 +14,7 @@ public EmailDTOValidator() RuleFor(x => x.Content) .NotEmpty() .MinimumLength(5) - .MaximumLength(100); + .MaximumLength(1000); } } } From 898a44cd321d57a3a10bba2b055e9a0cadb862b0 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 16:22:40 +0100 Subject: [PATCH 29/31] fix: finish rename in bll and test --- .../MediatR/Email/SendEmailHandler.cs | 4 ++-- .../Email/EmailCommandValidatorTests.cs | 6 +++--- .../Email/EmailDTOValidatorTests.cs | 2 +- .../Email/SendEmailHandlerTests.cs | 10 ++++------ 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailHandler.cs b/Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailHandler.cs index 58a0dbf..76ae843 100644 --- a/Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailHandler.cs +++ b/Streetcode/Streetcode.Email.BLL/MediatR/Email/SendEmailHandler.cs @@ -28,9 +28,9 @@ public SendEmailHandler(EmailDbContext context, IMapper mapper, ILogger> Handle(SendEmailCommand request, CancellationToken cancellationToken) { - var feedbackEntity = _mapper.Map(request.email); + var EmailEntity = _mapper.Map(request.email); - _context.Emails.Add(feedbackEntity); + _context.Emails.Add(EmailEntity); var rowsAffected = await _context.SaveChangesAsync(cancellationToken); diff --git a/Streetcode/Streetcode.Email.XUnitTest/Email/EmailCommandValidatorTests.cs b/Streetcode/Streetcode.Email.XUnitTest/Email/EmailCommandValidatorTests.cs index e874842..b8d6c6c 100644 --- a/Streetcode/Streetcode.Email.XUnitTest/Email/EmailCommandValidatorTests.cs +++ b/Streetcode/Streetcode.Email.XUnitTest/Email/EmailCommandValidatorTests.cs @@ -1,4 +1,4 @@ -namespace Streetcode.Email.XUnitTest.MediatR.Feedback +namespace Streetcode.Email.XUnitTest.MediatR.Email { using FluentAssertions; using FluentValidation.TestHelper; @@ -17,7 +17,7 @@ public EmailCommandValidatorTests() } [Fact] - public void ShouldReturnError_IfFeedbackIsNull() + public void ShouldReturnError_IfEmailIsNull() { // Arrange var command = new SendEmailCommand(null!); @@ -31,7 +31,7 @@ public void ShouldReturnError_IfFeedbackIsNull() } [Fact] - public void ShouldHaveError_WhenFeedbackDTOIsInvalid() + public void ShouldHaveError_WhenEmailDTOIsInvalid() { // Arrange var invalidDto = new EmailDTO diff --git a/Streetcode/Streetcode.Email.XUnitTest/Email/EmailDTOValidatorTests.cs b/Streetcode/Streetcode.Email.XUnitTest/Email/EmailDTOValidatorTests.cs index 873dc7d..07da1d2 100644 --- a/Streetcode/Streetcode.Email.XUnitTest/Email/EmailDTOValidatorTests.cs +++ b/Streetcode/Streetcode.Email.XUnitTest/Email/EmailDTOValidatorTests.cs @@ -1,4 +1,4 @@ -namespace Streetcode.Email.XUnitTest.MediatR.Feedback +namespace Streetcode.Email.XUnitTest.MediatR.Email { using FluentValidation.TestHelper; using Streetcode.Email.BLL.DTO; diff --git a/Streetcode/Streetcode.Email.XUnitTest/Email/SendEmailHandlerTests.cs b/Streetcode/Streetcode.Email.XUnitTest/Email/SendEmailHandlerTests.cs index 74f7e20..8c18411 100644 --- a/Streetcode/Streetcode.Email.XUnitTest/Email/SendEmailHandlerTests.cs +++ b/Streetcode/Streetcode.Email.XUnitTest/Email/SendEmailHandlerTests.cs @@ -1,6 +1,4 @@ - - -namespace Streetcode.Email.XUnitTest.MediatR.Feedback +namespace Streetcode.Email.XUnitTest.MediatR.Email { using AutoMapper; using FluentAssertions; @@ -48,11 +46,11 @@ public SendEmailHandlerTests() } [Fact] - public async Task Handle_ShouldReturnOk_WhenFeedbackIsSavedSuccessfully() + public async Task Handle_ShouldReturnOk_WhenEmailIsSavedSuccessfully() { // Arrange - var feedbackDto = new EmailDTO { From = "test@gmail.com", Content = "Valid message" }; - var command = new SendEmailCommand(feedbackDto); + var EmailDto = new EmailDTO { From = "test@gmail.com", Content = "Valid message" }; + var command = new SendEmailCommand(EmailDto); // Act var result = await this.handler.Handle(command, CancellationToken.None); From 65f673631682f85f26ed079872d34ca665065ed9 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 16:24:02 +0100 Subject: [PATCH 30/31] fix: fix inmemory version different --- .../Streetcode.Email.XUnitTest.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streetcode/Streetcode.Email.XUnitTest/Streetcode.Email.XUnitTest.csproj b/Streetcode/Streetcode.Email.XUnitTest/Streetcode.Email.XUnitTest.csproj index ec78860..623a4a8 100644 --- a/Streetcode/Streetcode.Email.XUnitTest/Streetcode.Email.XUnitTest.csproj +++ b/Streetcode/Streetcode.Email.XUnitTest/Streetcode.Email.XUnitTest.csproj @@ -13,7 +13,7 @@ - + From 80f11a2cc1e9c3421f33ba6042ecfd0beb963979 Mon Sep 17 00:00:00 2001 From: Darsicl Date: Mon, 2 Mar 2026 16:59:37 +0100 Subject: [PATCH 31/31] fix: fix validator settings into test --- .../Streetcode.Email.XUnitTest/Email/EmailDTOValidatorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Streetcode/Streetcode.Email.XUnitTest/Email/EmailDTOValidatorTests.cs b/Streetcode/Streetcode.Email.XUnitTest/Email/EmailDTOValidatorTests.cs index 07da1d2..dbd42f9 100644 --- a/Streetcode/Streetcode.Email.XUnitTest/Email/EmailDTOValidatorTests.cs +++ b/Streetcode/Streetcode.Email.XUnitTest/Email/EmailDTOValidatorTests.cs @@ -65,7 +65,7 @@ public void ShouldHaveError_WhenMessageExceedsMaxLength() var model = new EmailDTO { From = "test@gmail.com", - Content = new string('a', 101) + Content = new string('a', 1001) }; // Act