Major refactor of AdminController and related services to support full CRUD for candidate resume, projects, hobbies, skills, academics, experiences, certifications, and contact info, all using access token claims for candidate identity. Introduced AdminService and expanded IResumeRepository for granular entity management. Updated DTOs for upsert operations and date support. Improved API versioning (Asp.Versioning.Mvc), Swagger integration, and middleware setup. Added unit test project. Enhanced error handling, documentation, and mapping.
423 lines
18 KiB
C#
423 lines
18 KiB
C#
using Asp.Versioning;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using PortBlog.API.Models;
|
|
using PortBlog.API.Services.Contracts;
|
|
|
|
namespace PortBlog.API.Controllers
|
|
{
|
|
|
|
/// <summary>
|
|
/// Controller for administrative actions related to the logged-in candidate and their resume.
|
|
/// All endpoints derive the candidate identity from the access token claims.
|
|
/// </summary>
|
|
[ApiController]
|
|
[ApiVersion("1.0")]
|
|
[Route("api/v{version:apiVersion}/admin")]
|
|
[Authorize]
|
|
public class AdminController(ILogger<CvController> logger, IAdminService adminService) : Controller
|
|
{
|
|
/// <summary>
|
|
/// Get hobbies of the logged-in candidate.
|
|
/// </summary>
|
|
/// <returns>Hobbies and about details of the candidate</returns>
|
|
/// <response code="200">Returns the requested hobbies of the candidate</response>
|
|
[HttpGet("GetHobbies")]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<AboutDto>> GetHobbies()
|
|
{
|
|
try
|
|
{
|
|
var candidateId = adminService.GetCandidateIdFromClaims(User);
|
|
var aboutDetails = await adminService.GetHobbiesAsync(candidateId);
|
|
return Ok(aboutDetails);
|
|
}
|
|
catch (UnauthorizedAccessException ex)
|
|
{
|
|
logger.LogInformation(ex, "Unauthorized access when fetching hobbies.");
|
|
return Unauthorized(ex.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogCritical(ex, "Exception while getting about details.");
|
|
return StatusCode(500, "A problem happened while handling your request.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get contact details (candidate with social links) for the logged-in candidate.
|
|
/// </summary>
|
|
/// <returns>Candidate details with social links</returns>
|
|
/// <response code="200">Returns the requested candidate details with social links</response>
|
|
[HttpGet("GetCandidateWithSocialLinks")]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<CandidateSocialLinksDto>> GetContact()
|
|
{
|
|
try
|
|
{
|
|
var candidateId = adminService.GetCandidateIdFromClaims(User);
|
|
var contact = await adminService.GetContactAsync(candidateId);
|
|
return Ok(contact);
|
|
}
|
|
catch (UnauthorizedAccessException ex)
|
|
{
|
|
logger.LogInformation(ex, "Unauthorized access when fetching contact.");
|
|
return Unauthorized(ex.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogCritical(ex, "Exception while getting contact details.");
|
|
return StatusCode(500, "A problem happened while handling your request.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get resume for the logged-in candidate.
|
|
/// </summary>
|
|
/// <returns>Candidate resume</returns>
|
|
/// <response code="200">Returns the requested candidate resume</response>
|
|
[HttpGet("GetResume")]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<ResumeDto>> GetResume()
|
|
{
|
|
try
|
|
{
|
|
var candidateId = adminService.GetCandidateIdFromClaims(User);
|
|
var resume = await adminService.GetResumeAsync(candidateId);
|
|
return Ok(resume);
|
|
}
|
|
catch (UnauthorizedAccessException ex)
|
|
{
|
|
logger.LogInformation(ex, "Unauthorized access when fetching resume.");
|
|
return Unauthorized(ex.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogCritical(ex, "Exception while getting resume.");
|
|
return StatusCode(500, "A problem happened while handling your request.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get projects for the logged-in candidate.
|
|
/// </summary>
|
|
/// <returns>Candidate projects</returns>
|
|
/// <response code="200">Returns the requested candidate projects</response>
|
|
[HttpGet("GetProjects")]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<ProjectsDto>> GetProjects()
|
|
{
|
|
try
|
|
{
|
|
var candidateId = adminService.GetCandidateIdFromClaims(User);
|
|
var projects = await adminService.GetProjectsAsync(candidateId);
|
|
return Ok(projects);
|
|
}
|
|
catch (UnauthorizedAccessException ex)
|
|
{
|
|
logger.LogInformation(ex, "Unauthorized access when fetching projects.");
|
|
return Unauthorized(ex.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogCritical(ex, "Exception while getting projects.");
|
|
return StatusCode(500, "A problem happened while handling your request.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create or update a project for the logged-in candidate's resume.
|
|
/// </summary>
|
|
[HttpPost("UpsertProject")]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<ProjectDto>> UpsertProject([FromBody] ProjectDto projectDto)
|
|
{
|
|
try
|
|
{
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return BadRequest(ModelState);
|
|
}
|
|
|
|
var candidateId = adminService.GetCandidateIdFromClaims(User);
|
|
var result = await adminService.UpsertProjectAsync(candidateId, projectDto);
|
|
return Ok(result);
|
|
}
|
|
catch (UnauthorizedAccessException ex)
|
|
{
|
|
logger.LogInformation(ex, "Unauthorized access when upserting project.");
|
|
return Unauthorized(ex.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogCritical(ex, "Exception while upserting project.");
|
|
return StatusCode(500, "A problem happened while handling your request.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delete a project from the logged-in candidate's resume.
|
|
/// </summary>
|
|
/// <param name="projectId">The id of the project to delete</param>
|
|
[HttpDelete("DeleteProject/{projectId}")]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
public async Task<ActionResult> DeleteProject(int projectId)
|
|
{
|
|
try
|
|
{
|
|
var candidateId = adminService.GetCandidateIdFromClaims(User);
|
|
var deleted = await adminService.DeleteProjectAsync(candidateId, projectId);
|
|
if (!deleted)
|
|
{
|
|
return NotFound();
|
|
}
|
|
return Ok();
|
|
}
|
|
catch (UnauthorizedAccessException ex)
|
|
{
|
|
logger.LogInformation(ex, "Unauthorized access when deleting project.");
|
|
return Unauthorized(ex.Message);
|
|
}
|
|
catch (KeyNotFoundException ex)
|
|
{
|
|
logger.LogInformation(ex, "Resume not found when deleting project.");
|
|
return NotFound();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogCritical(ex, "Exception while deleting project {ProjectId}.", projectId);
|
|
return StatusCode(500, "A problem happened while handling your request.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create, update, or remove hobbies and update the about section for the logged-in candidate.
|
|
/// Hobbies present in the list are added or updated; hobbies not in the list are removed.
|
|
/// </summary>
|
|
[HttpPost("UpsertHobbies")]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<AboutDto>> UpsertHobbies([FromBody] AboutDto aboutDto)
|
|
{
|
|
try
|
|
{
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return BadRequest(ModelState);
|
|
}
|
|
|
|
var candidateId = adminService.GetCandidateIdFromClaims(User);
|
|
var result = await adminService.UpsertHobbiesAsync(candidateId, aboutDto);
|
|
return Ok(result);
|
|
}
|
|
catch (UnauthorizedAccessException ex)
|
|
{
|
|
logger.LogInformation(ex, "Unauthorized access when upserting hobbies.");
|
|
return Unauthorized(ex.Message);
|
|
}
|
|
catch (KeyNotFoundException ex)
|
|
{
|
|
logger.LogInformation(ex, "Resume not found when upserting hobbies.");
|
|
return NotFound();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogCritical(ex, "Exception while upserting hobbies.");
|
|
return StatusCode(500, "A problem happened while handling your request.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create, update, or remove skills for the logged-in candidate's resume.
|
|
/// Skills present in the list are added or updated; skills not in the list are removed.
|
|
/// </summary>
|
|
[HttpPost("UpsertSkills")]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<IEnumerable<SkillDto>>> UpsertSkills([FromBody] IEnumerable<SkillDto> skillDtos)
|
|
{
|
|
try
|
|
{
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return BadRequest(ModelState);
|
|
}
|
|
|
|
var candidateId = adminService.GetCandidateIdFromClaims(User);
|
|
var result = await adminService.UpsertSkillsAsync(candidateId, skillDtos);
|
|
return Ok(result);
|
|
}
|
|
catch (UnauthorizedAccessException ex)
|
|
{
|
|
logger.LogInformation(ex, "Unauthorized access when upserting skills.");
|
|
return Unauthorized(ex.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogCritical(ex, "Exception while upserting skills.");
|
|
return StatusCode(500, "A problem happened while handling your request.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create, update, or remove academics for the logged-in candidate's resume.
|
|
/// Academics present in the list are added or updated; academics not in the list are removed.
|
|
/// </summary>
|
|
[HttpPost("UpsertAcademics")]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<IEnumerable<AcademicDto>>> UpsertAcademics([FromBody] IEnumerable<AcademicDto> academicDtos)
|
|
{
|
|
try
|
|
{
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return BadRequest(ModelState);
|
|
}
|
|
|
|
var candidateId = adminService.GetCandidateIdFromClaims(User);
|
|
var result = await adminService.UpsertAcademicsAsync(candidateId, academicDtos);
|
|
return Ok(result);
|
|
}
|
|
catch (UnauthorizedAccessException ex)
|
|
{
|
|
logger.LogInformation(ex, "Unauthorized access when upserting academics.");
|
|
return Unauthorized(ex.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogCritical(ex, "Exception while upserting academics.");
|
|
return StatusCode(500, "A problem happened while handling your request.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create, update, or remove experiences for the logged-in candidate's resume.
|
|
/// Experiences present in the list are added or updated; experiences not in the list are removed.
|
|
/// </summary>
|
|
[HttpPost("UpsertExperiences")]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<IEnumerable<ExperienceDto>>> UpsertExperiences([FromBody] IEnumerable<ExperienceDto> experienceDtos)
|
|
{
|
|
try
|
|
{
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return BadRequest(ModelState);
|
|
}
|
|
|
|
var candidateId = adminService.GetCandidateIdFromClaims(User);
|
|
var result = await adminService.UpsertExperiencesAsync(candidateId, experienceDtos);
|
|
return Ok(result);
|
|
}
|
|
catch (UnauthorizedAccessException ex)
|
|
{
|
|
logger.LogInformation(ex, "Unauthorized access when upserting experiences.");
|
|
return Unauthorized(ex.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogCritical(ex, "Exception while upserting experiences.");
|
|
return StatusCode(500, "A problem happened while handling your request.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create, update, or remove certifications for the logged-in candidate's resume.
|
|
/// Certifications present in the list are added or updated; certifications not in the list are removed.
|
|
/// </summary>
|
|
[HttpPost("UpsertCertifications")]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<IEnumerable<CertificationDto>>> UpsertCertifications([FromBody] IEnumerable<CertificationDto> certificationDtos)
|
|
{
|
|
try
|
|
{
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return BadRequest(ModelState);
|
|
}
|
|
|
|
var candidateId = adminService.GetCandidateIdFromClaims(User);
|
|
var result = await adminService.UpsertCertificationsAsync(candidateId, certificationDtos);
|
|
return Ok(result);
|
|
}
|
|
catch (UnauthorizedAccessException ex)
|
|
{
|
|
logger.LogInformation(ex, "Unauthorized access when upserting certifications.");
|
|
return Unauthorized(ex.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogCritical(ex, "Exception while upserting certifications.");
|
|
return StatusCode(500, "A problem happened while handling your request.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create or update contact information (candidate with social links) for the logged-in candidate.
|
|
/// </summary>
|
|
[HttpPost("UpsertContact")]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
public async Task<ActionResult<CandidateSocialLinksDto>> UpsertContact([FromBody] CandidateSocialLinksDto contactDto)
|
|
{
|
|
try
|
|
{
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return BadRequest(ModelState);
|
|
}
|
|
|
|
var candidateId = adminService.GetCandidateIdFromClaims(User);
|
|
var result = await adminService.UpsertContactAsync(candidateId, contactDto);
|
|
return Ok(result);
|
|
}
|
|
catch (UnauthorizedAccessException ex)
|
|
{
|
|
logger.LogInformation(ex, "Unauthorized access when upserting contact.");
|
|
return Unauthorized(ex.Message);
|
|
}
|
|
catch (KeyNotFoundException ex)
|
|
{
|
|
logger.LogInformation(ex, "Resume not found when upserting contact.");
|
|
return NotFound();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogCritical(ex, "Exception while upserting contact.");
|
|
return StatusCode(500, "A problem happened while handling your request.");
|
|
}
|
|
}
|
|
}
|
|
}
|