From cd01c11be795467915321fe1a942a0a7f51cfd17 Mon Sep 17 00:00:00 2001 From: Bangara Raju Kottedi Date: Sat, 22 Nov 2025 14:16:31 +0530 Subject: [PATCH] Refactor controllers and services; update logging Refactored AdminController and AuthService to use primary constructor syntax, removing explicit field declarations. Updated logging to use structured logging across controllers and services. Added XML documentation for methods in AdminController, AuthController, AuthService, and MailService. Modified Program.cs to improve connection string handling and Swagger setup. Removed deprecated service files. --- PortBlog.API/Controllers/AdminController.cs | 58 +++--- PortBlog.API/Controllers/AuthController.cs | 22 +- PortBlog.API/PortBlog.API.xml | 194 +++++++++++++++++- PortBlog.API/Program.cs | 45 ++-- PortBlog.API/Services/AuthService.cs | 80 +++++--- .../Services/Contracts/IAuthService.cs | 45 +++- .../Services/Contracts/IOtpService.cs | 6 - .../Services/Contracts/ITokenService.cs | 6 - .../Services/Contracts/JwtServicecs.cs | 6 - PortBlog.API/Services/JwtService.cs | 6 - PortBlog.API/Services/MailService.cs | 66 +++--- PortBlog.API/Services/OtpService.cs | 6 - PortBlog.API/Services/TemplateService.cs | 10 + PortBlog.API/Services/TokenService.cs | 6 - 14 files changed, 395 insertions(+), 161 deletions(-) delete mode 100644 PortBlog.API/Services/Contracts/IOtpService.cs delete mode 100644 PortBlog.API/Services/Contracts/ITokenService.cs delete mode 100644 PortBlog.API/Services/Contracts/JwtServicecs.cs delete mode 100644 PortBlog.API/Services/JwtService.cs delete mode 100644 PortBlog.API/Services/OtpService.cs delete mode 100644 PortBlog.API/Services/TokenService.cs 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 - { - } -}