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
- {
- }
-}