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