diff --git a/PortBlog.API/Controllers/AdminController.cs b/PortBlog.API/Controllers/AdminController.cs index 56905bf..b6cb275 100644 --- a/PortBlog.API/Controllers/AdminController.cs +++ b/PortBlog.API/Controllers/AdminController.cs @@ -4,27 +4,19 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using PortBlog.API.Models; using PortBlog.API.Repositories.Contracts; -using PortBlog.API.Services.Contracts; namespace PortBlog.API.Controllers { + + /// + /// Controller for administrative actions related to candidates and their resumes. + /// [Route("api/v{version:apiVersion}/admin")] [ApiController] [ApiVersion(1)] [Authorize] - public class AdminController : Controller + public class AdminController(ILogger logger, ICandidateRepository candidateRepository, IResumeRepository resumeRepository, IMapper mapper) : Controller { - private readonly ILogger _logger; - private readonly ICandidateRepository _candidateRepository; - private readonly IResumeRepository _resumeRepository; - private readonly IMapper _mapper; - public AdminController(ILogger logger, ICandidateRepository candidateRepository, IResumeRepository resumeRepository, IMailService mailService, IMapper mapper, IMailRepository mailRepository) - { - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _candidateRepository = candidateRepository ?? throw new ArgumentNullException(nameof(candidateRepository)); - _resumeRepository = resumeRepository ?? throw new ArgumentNullException(nameof(resumeRepository)); - _mapper = mapper; - } /// /// Get hobbies of the candidate by candidateid /// @@ -40,20 +32,20 @@ namespace PortBlog.API.Controllers { try { - if (!await _candidateRepository.CandidateExistAsync(candidateId)) + if (!await candidateRepository.CandidateExistAsync(candidateId)) { - _logger.LogInformation($"Candidate with id {candidateId} wasn't found when fetching about details."); + logger.LogInformation("Candidate with id {CandidateId} wasn't found when fetching about details.", candidateId); return NotFound(); } - var aboutDetails = await _resumeRepository.GetHobbiesAsync(candidateId); + var aboutDetails = await resumeRepository.GetHobbiesAsync(candidateId); - return Ok(_mapper.Map(aboutDetails)); + return Ok(mapper.Map(aboutDetails)); } catch (Exception ex) { - _logger.LogCritical($"Exception while getting about details for the candidate with id {candidateId}.", ex); + logger.LogCritical(ex, "Exception while getting about details for the candidate with id {CandidateId}.", candidateId); return StatusCode(500, "A problem happened while handling your request."); } } @@ -73,20 +65,20 @@ namespace PortBlog.API.Controllers { try { - if (!await _candidateRepository.CandidateExistAsync(candidateId)) + if (!await candidateRepository.CandidateExistAsync(candidateId)) { - _logger.LogInformation($"Candidate with id {candidateId} wasn't found when fetching candidate with social links."); + logger.LogInformation("Candidate with id {CandidateId} wasn't found when fetching candidate with social links.", candidateId); return NotFound(); } - var contact = await _resumeRepository.GetCandidateWithSocialLinksAsync(candidateId); + var contact = await resumeRepository.GetCandidateWithSocialLinksAsync(candidateId); - return Ok(_mapper.Map(contact)); + return Ok(mapper.Map(contact)); } catch (Exception ex) { - _logger.LogCritical($"Exception while getting contact for the candidate with social links with id {candidateId}.", ex); + logger.LogCritical(ex, "Exception while getting contact for the candidate with social links with id {CandidateId}.", candidateId); return StatusCode(500, "A problem happened while handling your request."); } } @@ -106,20 +98,20 @@ namespace PortBlog.API.Controllers { try { - if (!await _candidateRepository.CandidateExistAsync(candidateId)) + if (!await candidateRepository.CandidateExistAsync(candidateId)) { - _logger.LogInformation($"Candidate with id {candidateId} wasn't found when fetching resume."); + logger.LogInformation("Candidate with id {CandidateId} wasn't found when fetching resume.", candidateId); return NotFound(); } - var resume = await _resumeRepository.GetResumeAsync(candidateId); + var resume = await resumeRepository.GetResumeAsync(candidateId); - return Ok(_mapper.Map(resume)); + return Ok(mapper.Map(resume)); } catch (Exception ex) { - _logger.LogCritical($"Exception while getting resume for the candidate with id {candidateId}.", ex); + logger.LogCritical(ex, "Exception while getting resume for the candidate with id {CandidateId}.", candidateId); return StatusCode(500, "A problem happened while handling your request."); } } @@ -139,20 +131,20 @@ namespace PortBlog.API.Controllers { try { - if (!await _candidateRepository.CandidateExistAsync(candidateId)) + if (!await candidateRepository.CandidateExistAsync(candidateId)) { - _logger.LogInformation($"Candidate with id {candidateId} wasn't found when fetching projects."); + logger.LogInformation("Candidate with id {CandidateId} wasn't found when fetching projects.", candidateId); return NotFound(); } - var projects = await _resumeRepository.GetProjectsAsync(candidateId); + var projects = await resumeRepository.GetProjectsAsync(candidateId); - return Ok(_mapper.Map(projects)); + return Ok(mapper.Map(projects)); } catch (Exception ex) { - _logger.LogCritical($"Exception while getting projects for the candidate with id {candidateId}.", ex); + logger.LogCritical(ex, "Exception while getting projects for the candidate with id {CandidateId}.", candidateId); return StatusCode(500, "A problem happened while handling your request."); } } diff --git a/PortBlog.API/Controllers/AuthController.cs b/PortBlog.API/Controllers/AuthController.cs index f2d5983..d1bf7dd 100644 --- a/PortBlog.API/Controllers/AuthController.cs +++ b/PortBlog.API/Controllers/AuthController.cs @@ -1,15 +1,11 @@ using Asp.Versioning; using AutoMapper; -using Azure.Core; using KBR.Cache; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; using PortBlog.API.Entities; using PortBlog.API.Models; using PortBlog.API.Repositories.Contracts; -using PortBlog.API.Services; using PortBlog.API.Services.Contracts; -using System.Threading.Tasks; namespace PortBlog.API.Controllers { @@ -24,7 +20,7 @@ namespace PortBlog.API.Controllers /// /// Generates a One-Time Password (OTP) for the specified candidate and sends it via email. /// - /// The ID of the candidate for whom the OTP is generated. + /// The email of the candidate for whom the OTP is generated. /// An ActionResult indicating the result of the operation. [HttpPost("GenerateOtp")] [ProducesResponseType(StatusCodes.Status404NotFound)] @@ -38,7 +34,7 @@ namespace PortBlog.API.Controllers var candidate = await candidateRepository.GetCandidateAsync(email); if (candidate == null) { - logger.LogInformation($"Candidate with email ({email}) wasn't found."); + logger.LogInformation("Candidate with email ({Email}) wasn't found.", email); return NotFound(); } @@ -62,7 +58,7 @@ namespace PortBlog.API.Controllers } catch (Exception ex) { - logger.LogCritical($"Exception while sending OTP for {messageSendDto.ToEmail}.", ex); + logger.LogCritical(ex, "Exception while sending OTP for {ToEmail}.", messageSendDto.ToEmail); return StatusCode(500, "A problem happened while handling your request."); } } @@ -108,11 +104,15 @@ namespace PortBlog.API.Controllers } catch (Exception ex) { - logger.LogCritical($"Exception while validating OTP for {request.UserId}.", ex); + logger.LogCritical(ex, "Exception while validating OTP for {UserId}.", request?.UserId); return StatusCode(500, "A problem happened while handling your request."); } } + /// + /// Refreshes the access token using a valid refresh token from the HttpOnly cookie. + /// + /// An IActionResult containing the new access token if the refresh token is valid; otherwise, an appropriate error response. [HttpPost("RefreshToken")] public async Task Refresh() { @@ -120,7 +120,7 @@ namespace PortBlog.API.Controllers if (!Request.Cookies.TryGetValue("refreshToken", out var refreshToken)) return Unauthorized(); - var matchedToken = await authService.GetRefreshTokenAsync(refreshToken); + var matchedToken = await authService.GetRefreshToken(refreshToken); if (matchedToken == null) return Forbid(); // Rotate refresh token @@ -147,6 +147,10 @@ namespace PortBlog.API.Controllers return Ok(new { accessToken = newAccessToken }); } + /// + /// Logs out the current user by removing the refresh token cookie and invalidating the refresh token. + /// + /// An IActionResult indicating the result of the logout operation. [HttpPost("logout")] public async Task Logout() { diff --git a/PortBlog.API/PortBlog.API.xml b/PortBlog.API/PortBlog.API.xml index 0bd7480..274a32b 100644 --- a/PortBlog.API/PortBlog.API.xml +++ b/PortBlog.API/PortBlog.API.xml @@ -4,6 +4,16 @@ PortBlog.API + + + Controller for administrative actions related to candidates and their resumes. + + + + + Controller for administrative actions related to candidates and their resumes. + + Get hobbies of the candidate by candidateid @@ -50,7 +60,7 @@ Generates a One-Time Password (OTP) for the specified candidate and sends it via email. - The ID of the candidate for whom the OTP is generated. + The email of the candidate for whom the OTP is generated. An ActionResult indicating the result of the operation. @@ -60,6 +70,18 @@ The request containing the user ID and OTP code to verify. An ActionResult indicating the result of the verification. + + + Refreshes the access token using a valid refresh token from the HttpOnly cookie. + + An IActionResult containing the new access token if the refresh token is valid; otherwise, an appropriate error response. + + + + Logs out the current user by removing the refresh token cookie and invalidating the refresh token. + + An IActionResult indicating the result of the logout operation. + Get CV details of the candidate by candidateid. @@ -399,6 +421,24 @@ The work experiences of the candidate + + + Provides authentication services such as OTP generation, validation, and JWT token management. + + + + + Provides authentication services such as OTP generation, validation, and JWT token management. + + + + + Generates a one-time password (OTP) for the specified email address and stores the secret key in the cache. + + The email address for which to generate the OTP. + A cancellation token that can be used to cancel the operation. + A task that represents the asynchronous operation. The task result contains the generated OTP as a string. + Validates the provided OTP against the secret key. @@ -407,5 +447,157 @@ The OTP to validate. True if the OTP is valid; otherwise, false. + + + Generates a JWT access token for the specified username. + + The username for which to generate the access token. + The generated JWT access token as a string. + + + + Generates a secure random refresh token as a Base64-encoded string. + + The generated refresh token. + + + + Saves a refresh token for the specified user email and associates it with the current HTTP context. + + The email address of the user. + The refresh token to be saved. + The current HTTP context containing request information. + A task that represents the asynchronous save operation. + + + + Saves a refresh token entity to the database. + + The refresh token entity to save. + A task that represents the asynchronous save operation. + + + + Retrieves a valid, non-revoked, and non-expired refresh token entity matching the provided refresh token string. + + The refresh token string to validate and retrieve. + + A task that represents the asynchronous operation. The task result contains the matching entity if found and valid; otherwise, throws . + + Thrown if no valid refresh token is found. + + + + Revokes (removes) a refresh token by marking it as revoked in the database if it is valid and not expired. + + The refresh token to revoke. + A task that represents the asynchronous revoke operation. + + + + Provides authentication-related services such as OTP generation, token management, and refresh token handling. + + + + + Generates a one-time password (OTP) for the specified email. + + The email address to generate the OTP for. + A cancellation token. + The generated OTP as a string. + + + + Validates the provided OTP against the secret key. + + The secret key used for validation. + The OTP to validate. + True if the OTP is valid; otherwise, false. + + + + Generates an access token for the specified username. + + The username for which to generate the access token. + The generated access token as a string. + + + + Generates a new refresh token. + + The generated refresh token as a string. + + + + Saves the refresh token for the specified user and HTTP context. + + The user ID. + The refresh token to save. + The HTTP context. + + + + Saves the specified refresh token. + + The refresh token entity to save. + + + + Retrieves the refresh token entity for the specified token string. + + The refresh token string. + The corresponding entity. + + + + Removes the specified refresh token. + + The refresh token to remove. + + + + Provides functionality for sending emails and logging message activity. + + + Initializes a new instance of the class. + + The application configuration. + The logger instance. + The mail repository for storing messages. + The AutoMapper instance. + + + + Provides functionality for sending emails and logging message activity. + + + Initializes a new instance of the class. + + The application configuration. + The logger instance. + The mail repository for storing messages. + The AutoMapper instance. + + + + Sends an email message asynchronously using the provided message details. + + The message details to send. + A task representing the asynchronous operation. + + + + Provides functionality to render Razor view templates with a specified model. + + + + + Renders a Razor view template at the specified path using the provided model. + + The type of the model to pass to the view. + The path to the Razor view template. + The model to use when rendering the view. + A task that represents the asynchronous operation. The task result contains the rendered view as a string. + diff --git a/PortBlog.API/Program.cs b/PortBlog.API/Program.cs index ac46b28..9d9e7d0 100644 --- a/PortBlog.API/Program.cs +++ b/PortBlog.API/Program.cs @@ -43,7 +43,7 @@ if (!string.IsNullOrEmpty(urls.Value)) } var allowedCorsOrigins = builder.Configuration.GetSection("AllowedCorsOrigins"); -string[] origins = Array.Empty(); +string[] origins = []; if (!String.IsNullOrEmpty(allowedCorsOrigins.Value)) { @@ -78,16 +78,16 @@ builder.Services.AddEndpointsApiExplorer(); var connectionString = builder.Configuration.GetConnectionString("PortBlogDBConnectionString"); -if (builder.Configuration.GetValue("ConnectionStrings:Encryption")) -{ - connectionString = builder.Configuration.DecryptConnectionString(connectionString); -} - if (string.IsNullOrEmpty(connectionString)) { throw new Exception("Connection string cannot be empty"); } +if (builder.Configuration.GetValue("ConnectionStrings:Encryption")) +{ + connectionString = builder.Configuration.DecryptConnectionString(connectionString); +} + builder.Services .AddDbContext(dbContextOptions => dbContextOptions.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))); @@ -111,23 +111,8 @@ builder.Services.AddApiVersioning(setupAction => setupAction.SubstituteApiVersionInUrl = true; }); -var apiVersionDescriptionProvider = builder.Services.BuildServiceProvider() - .GetRequiredService(); - builder.Services.AddSwaggerGen(c => { - foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions) - { - c.SwaggerDoc( - $"{description.GroupName}", - new() - { - Title = "Portfolio Blog API", - Version = description.ApiVersion.ToString(), - Description = "Through this API you can access candidate cv details and along with other details." - }); - } - // XML Comments file for API Documentation var xmlCommentsFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlCommentsFullPath = $"{Path.Combine(AppContext.BaseDirectory, xmlCommentsFile)}"; @@ -174,16 +159,21 @@ builder.Services.AddSwaggerGen(c => } }; - var requirement = new OpenApiSecurityRequirement() + var requirement = new OpenApiSecurityRequirement { - {key, new List {} }, - {bearerScheme, new List{} } + { key, new List() }, + { bearerScheme, new List() } }; c.AddSecurityRequirement(requirement); }); -var key = Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]); +var jwtKey = builder.Configuration["Jwt:Key"]; +if (string.IsNullOrEmpty(jwtKey)) +{ + throw new Exception("JWT key cannot be null or empty. Please check your configuration."); +} +var key = Encoding.UTF8.GetBytes(jwtKey); builder.Services.AddAuthentication(options => { @@ -219,8 +209,9 @@ if (app.Environment.IsDevelopment()) app.UseSwagger(); app.UseSwaggerUI(setupAction => { - var descriptions = app.DescribeApiVersions(); - foreach (var description in descriptions) + // Get the IApiVersionDescriptionProvider from the app's service provider + var apiVersionDescriptionProvider = app.Services.GetRequiredService(); + foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions) { setupAction.SwaggerEndpoint( $"/swagger/{description.GroupName}/swagger.json", diff --git a/PortBlog.API/Services/AuthService.cs b/PortBlog.API/Services/AuthService.cs index 998c57d..ae97e58 100644 --- a/PortBlog.API/Services/AuthService.cs +++ b/PortBlog.API/Services/AuthService.cs @@ -12,19 +12,17 @@ using System.Text; namespace PortBlog.API.Services { - public class AuthService : IAuthService + /// + /// Provides authentication services such as OTP generation, validation, and JWT token management. + /// + public class AuthService(IConfiguration configuration, IAppDistributedCache cache, CvBlogContext context) : IAuthService { - private readonly IConfiguration configuration; - private readonly IAppDistributedCache cache; - private readonly CvBlogContext _context; - - public AuthService(IConfiguration configuration, IAppDistributedCache cache, CvBlogContext context) - { - this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); - this.cache = cache ?? throw new ArgumentNullException(nameof(cache)); - this._context = context ?? throw new ArgumentNullException(nameof(context)); - } - + /// + /// Generates a one-time password (OTP) for the specified email address and stores the secret key in the cache. + /// + /// The email address for which to generate the OTP. + /// A cancellation token that can be used to cancel the operation. + /// A task that represents the asynchronous operation. The task result contains the generated OTP as a string. public async Task GenerateOtp(string email, CancellationToken ct = default) { var secretKey = KeyGeneration.GenerateRandomKey(20); @@ -51,6 +49,11 @@ namespace PortBlog.API.Services return totp.VerifyTotp(DateTime.UtcNow, otp, out long _, VerificationWindow.RfcSpecifiedNetworkDelay); } + /// + /// Generates a JWT access token for the specified username. + /// + /// The username for which to generate the access token. + /// The generated JWT access token as a string. public string GenerateAccessToken(string username) { var claims = new[] @@ -80,19 +83,29 @@ namespace PortBlog.API.Services return new JwtSecurityTokenHandler().WriteToken(token); } + /// + /// Generates a secure random refresh token as a Base64-encoded string. + /// + /// The generated refresh token. public string GenerateRefreshToken() { return Convert.ToBase64String(RandomNumberGenerator.GetBytes(64)); } + /// + /// Saves a refresh token for the specified user email and associates it with the current HTTP context. + /// + /// The email address of the user. + /// The refresh token to be saved. + /// The current HTTP context containing request information. + /// A task that represents the asynchronous save operation. public async Task SaveRefreshToken(string userEmail, string refreshToken, HttpContext httpContext) { - var user = await GetUser(userEmail); - if (user == null) throw new InvalidOperationException("User not found."); + var user = await GetUser(userEmail) ?? throw new InvalidOperationException("User not found."); // Store hashed refresh token in DB var hashedToken = BCrypt.Net.BCrypt.HashPassword(refreshToken); - _context.RefreshTokens.Add(new RefreshToken + context.RefreshTokens.Add(new RefreshToken { UserId = user.UserId, Token = hashedToken, @@ -101,29 +114,48 @@ namespace PortBlog.API.Services DeviceInfo = httpContext.Request.Headers.UserAgent.ToString() }); - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } + /// + /// Saves a refresh token entity to the database. + /// + /// The refresh token entity to save. + /// A task that represents the asynchronous save operation. public async Task SaveRefreshToken(RefreshToken refreshToken) { - _context.RefreshTokens.Add(refreshToken); - await _context.SaveChangesAsync(); + context.RefreshTokens.Add(refreshToken); + await context.SaveChangesAsync(); } - public async Task GetRefreshTokenAsync(string refreshToken) + /// + /// Retrieves a valid, non-revoked, and non-expired refresh token entity matching the provided refresh token string. + /// + /// The refresh token string to validate and retrieve. + /// + /// A task that represents the asynchronous operation. The task result contains the matching entity if found and valid; otherwise, throws . + /// + /// Thrown if no valid refresh token is found. + public async Task GetRefreshToken(string refreshToken) { // Find user and validate refresh token - var refreshEntity = await _context.RefreshTokens + var refreshEntity = await context.RefreshTokens .Include(rt => rt.User) .Where(rt => !rt.Revoked && rt.ExpiryDate > DateTime.UtcNow) .ToListAsync(); - return refreshEntity.FirstOrDefault(rt => BCrypt.Net.BCrypt.Verify(refreshToken, rt.Token)); + var token = refreshEntity.FirstOrDefault(rt => BCrypt.Net.BCrypt.Verify(refreshToken, rt.Token)) ?? throw new InvalidOperationException("Refresh token not found or invalid."); + return token; } + /// + /// Revokes (removes) a refresh token by marking it as revoked in the database if it is valid and not expired. + /// + /// The refresh token to revoke. + /// A task that represents the asynchronous revoke operation. public async Task RemoveRefreshToken(string refreshToken) { - var tokens = await _context.RefreshTokens + var tokens = await context.RefreshTokens .Where(rt => rt.Revoked == false && rt.ExpiryDate > DateTime.UtcNow) .ToListAsync(); @@ -133,13 +165,13 @@ namespace PortBlog.API.Services if (refreshTokenEntity != null) { refreshTokenEntity.Revoked = true; - await _context.SaveChangesAsync(); + await context.SaveChangesAsync(); } } private async Task GetUser(string userEmail) { - return await _context.Users.FirstOrDefaultAsync(u => u.Email == userEmail); + return await context.Users.FirstOrDefaultAsync(u => u.Email == userEmail); } private string GenerateOtp(string secretKey) diff --git a/PortBlog.API/Services/Contracts/IAuthService.cs b/PortBlog.API/Services/Contracts/IAuthService.cs index acb9e53..f18645e 100644 --- a/PortBlog.API/Services/Contracts/IAuthService.cs +++ b/PortBlog.API/Services/Contracts/IAuthService.cs @@ -2,22 +2,65 @@ namespace PortBlog.API.Services.Contracts { + /// + /// Provides authentication-related services such as OTP generation, token management, and refresh token handling. + /// public interface IAuthService { + /// + /// Generates a one-time password (OTP) for the specified email. + /// + /// The email address to generate the OTP for. + /// A cancellation token. + /// The generated OTP as a string. Task GenerateOtp(string email, CancellationToken ct = default); + /// + /// Validates the provided OTP against the secret key. + /// + /// The secret key used for validation. + /// The OTP to validate. + /// True if the OTP is valid; otherwise, false. bool ValidateOtp(string secretKey, string otp); + /// + /// Generates an access token for the specified username. + /// + /// The username for which to generate the access token. + /// The generated access token as a string. string GenerateAccessToken(string username); + /// + /// Generates a new refresh token. + /// + /// The generated refresh token as a string. string GenerateRefreshToken(); + /// + /// Saves the refresh token for the specified user and HTTP context. + /// + /// The user ID. + /// The refresh token to save. + /// The HTTP context. Task SaveRefreshToken(string userId, string refreshToken, HttpContext httpContext); + /// + /// Saves the specified refresh token. + /// + /// The refresh token entity to save. Task SaveRefreshToken(RefreshToken refreshToken); - Task GetRefreshTokenAsync(string refreshToken); + /// + /// Retrieves the refresh token entity for the specified token string. + /// + /// The refresh token string. + /// The corresponding entity. + Task GetRefreshToken(string refreshToken); + /// + /// Removes the specified refresh token. + /// + /// The refresh token to remove. Task RemoveRefreshToken(string refreshToken); } } diff --git a/PortBlog.API/Services/Contracts/IOtpService.cs b/PortBlog.API/Services/Contracts/IOtpService.cs deleted file mode 100644 index 816bc88..0000000 --- a/PortBlog.API/Services/Contracts/IOtpService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace PortBlog.API.Services.Contracts -{ - public interface IOtpService - { - } -} diff --git a/PortBlog.API/Services/Contracts/ITokenService.cs b/PortBlog.API/Services/Contracts/ITokenService.cs deleted file mode 100644 index 2b9eb42..0000000 --- a/PortBlog.API/Services/Contracts/ITokenService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace PortBlog.API.Services.Contracts -{ - public interface ITokenService - { - } -} diff --git a/PortBlog.API/Services/Contracts/JwtServicecs.cs b/PortBlog.API/Services/Contracts/JwtServicecs.cs deleted file mode 100644 index 93da49e..0000000 --- a/PortBlog.API/Services/Contracts/JwtServicecs.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace PortBlog.API.Services.Contracts -{ - public interface JwtServicecs - { - } -} diff --git a/PortBlog.API/Services/JwtService.cs b/PortBlog.API/Services/JwtService.cs deleted file mode 100644 index c8e5e16..0000000 --- a/PortBlog.API/Services/JwtService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace PortBlog.API.Services -{ - public class JwtService - { - } -} diff --git a/PortBlog.API/Services/MailService.cs b/PortBlog.API/Services/MailService.cs index 906ef50..3cf85db 100644 --- a/PortBlog.API/Services/MailService.cs +++ b/PortBlog.API/Services/MailService.cs @@ -10,29 +10,32 @@ using System.Net.Mail; namespace PortBlog.API.Services { - public class MailService : IMailService + /// + /// Provides functionality for sending emails and logging message activity. + /// + /// + /// Initializes a new instance of the class. + /// + /// The application configuration. + /// The logger instance. + /// The mail repository for storing messages. + /// The AutoMapper instance. + public class MailService(IConfiguration configuration, ILogger logger, IMailRepository mailRepository, IMapper mapper) : IMailService { - private readonly ILogger _logger; - private readonly IConfiguration _configuration; - private readonly IMailRepository _mailRepository; - private readonly IMapper _mapper; - - public MailService(IConfiguration configuration, ILogger logger, IMailRepository mailRepository, IMapper mapper) - { - _logger = logger; - _configuration = configuration; - _mailRepository = mailRepository; - _mapper = mapper; - } + /// + /// Sends an email message asynchronously using the provided message details. + /// + /// The message details to send. + /// A task representing the asynchronous operation. public async Task SendAsync(MessageSendDto messageSendDto) { - _logger.LogInformation($"Sending message from {messageSendDto.Name} ({messageSendDto.FromEmail})."); + logger.LogInformation("Sending message from {Name} ({FromEmail}).", messageSendDto.Name, messageSendDto.FromEmail); messageSendDto.Subject = $"Message from {messageSendDto.Name}: Portfolio"; - var messageEntity = _mapper.Map(messageSendDto); + var messageEntity = mapper.Map(messageSendDto); try { - var mailSettings = _configuration.GetSection("MailSettings").Get(); + var mailSettings = configuration.GetSection("MailSettings").Get(); if (mailSettings != null && mailSettings.Enable) { @@ -45,31 +48,34 @@ namespace PortBlog.API.Services client.EnableSsl = true; client.Credentials = new NetworkCredential(mailSettings.Email, mailSettings.Password); - using (var mailMessage = new MailMessage( + using var mailMessage = new MailMessage( from: new MailAddress(mailSettings.Email), to: new MailAddress(MailHelpers.ReplaceEmailDomain(messageSendDto.ToEmail, mailSettings.Domain), messageSendDto.Name) - )) - { + ); - mailMessage.Subject = messageSendDto.Subject; - mailMessage.Body = messageSendDto.Content; - mailMessage.IsBodyHtml = true; + mailMessage.Subject = messageSendDto.Subject; + mailMessage.Body = messageSendDto.Content; + mailMessage.IsBodyHtml = true; - client.Send(mailMessage); - messageSendDto.SentStatus = (int)MailConstants.MailStatus.Success; - } + client.Send(mailMessage); + messageSendDto.SentStatus = (int)MailConstants.MailStatus.Success; } - _mailRepository.AddMessage(messageEntity); - await _mailRepository.SaveChangesAsync(); + mailRepository.AddMessage(messageEntity); + await mailRepository.SaveChangesAsync(); } } catch (Exception ex) { - _logger.LogCritical($"Exception while sending mail from {new MailAddress(messageSendDto.FromEmail, messageSendDto.Name)}", ex); + logger.LogCritical( + "Exception while sending mail from {FromEmail} ({Name}): {Exception}", + messageSendDto.FromEmail, + messageSendDto.Name, + ex.Message + ); messageSendDto.SentStatus = (int)MailConstants.MailStatus.Failed; - _mailRepository.AddMessage(messageEntity); - await _mailRepository.SaveChangesAsync(); + mailRepository.AddMessage(messageEntity); + await mailRepository.SaveChangesAsync(); throw new Exception(); } } diff --git a/PortBlog.API/Services/OtpService.cs b/PortBlog.API/Services/OtpService.cs deleted file mode 100644 index 97fcf69..0000000 --- a/PortBlog.API/Services/OtpService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace PortBlog.API.Services -{ - public class OtpService - { - } -} diff --git a/PortBlog.API/Services/TemplateService.cs b/PortBlog.API/Services/TemplateService.cs index bb4b03b..60e8cd8 100644 --- a/PortBlog.API/Services/TemplateService.cs +++ b/PortBlog.API/Services/TemplateService.cs @@ -3,8 +3,18 @@ using Razor.Templating.Core; namespace PortBlog.API.Services { + /// + /// Provides functionality to render Razor view templates with a specified model. + /// public class TemplateService : ITemplateService { + /// + /// Renders a Razor view template at the specified path using the provided model. + /// + /// The type of the model to pass to the view. + /// The path to the Razor view template. + /// The model to use when rendering the view. + /// A task that represents the asynchronous operation. The task result contains the rendered view as a string. public async Task GetViewTemplate(string viewPath, T model) { return await RazorTemplateEngine.RenderAsync(viewPath, model); diff --git a/PortBlog.API/Services/TokenService.cs b/PortBlog.API/Services/TokenService.cs deleted file mode 100644 index 1b9d103..0000000 --- a/PortBlog.API/Services/TokenService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace PortBlog.API.Services -{ - public class TokenService - { - } -}