5b7952877e
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.
553 lines
24 KiB
C#
553 lines
24 KiB
C#
using System.Security.Claims;
|
|
using AutoMapper;
|
|
using PortBlog.API.Entities;
|
|
using PortBlog.API.Models;
|
|
using PortBlog.API.Repositories.Contracts;
|
|
using PortBlog.API.Services.Contracts;
|
|
|
|
namespace PortBlog.API.Services
|
|
{
|
|
/// <summary>
|
|
/// Service for administrative operations related to candidates, resumes, and their associated data.
|
|
/// </summary>
|
|
public class AdminService(ICandidateRepository candidateRepository, IResumeRepository resumeRepository, IMapper mapper) : IAdminService
|
|
{
|
|
public int GetCandidateIdFromClaims(ClaimsPrincipal user)
|
|
{
|
|
var candidateIdClaim = user.FindFirst("CandidateId")?.Value;
|
|
if (string.IsNullOrEmpty(candidateIdClaim) || !int.TryParse(candidateIdClaim, out var candidateId))
|
|
{
|
|
throw new UnauthorizedAccessException("CandidateId claim is missing or invalid.");
|
|
}
|
|
return candidateId;
|
|
}
|
|
|
|
public async Task<AboutDto?> GetHobbiesAsync(int candidateId)
|
|
{
|
|
var aboutDetails = await resumeRepository.GetHobbiesAsync(candidateId);
|
|
return aboutDetails != null ? mapper.Map<AboutDto>(aboutDetails) : null;
|
|
}
|
|
|
|
public async Task<CandidateSocialLinksDto?> GetContactAsync(int candidateId)
|
|
{
|
|
var contact = await resumeRepository.GetCandidateWithSocialLinksAsync(candidateId);
|
|
return contact != null ? mapper.Map<CandidateSocialLinksDto>(contact) : null;
|
|
}
|
|
|
|
public async Task<ResumeDto?> GetResumeAsync(int candidateId)
|
|
{
|
|
var resume = await resumeRepository.GetResumeAsync(candidateId);
|
|
return resume != null ? mapper.Map<ResumeDto>(resume) : null;
|
|
}
|
|
|
|
public async Task<ProjectsDto?> GetProjectsAsync(int candidateId)
|
|
{
|
|
var projects = await resumeRepository.GetProjectsAsync(candidateId);
|
|
return projects != null ? mapper.Map<ProjectsDto>(projects) : null;
|
|
}
|
|
|
|
public async Task<ProjectDto> UpsertProjectAsync(int candidateId, ProjectDto projectDto)
|
|
{
|
|
var resumeWithCollections = await GetOrCreateResumeWithCollectionsAsync(candidateId);
|
|
|
|
Project? projectEntity = null;
|
|
if (projectDto.ProjectId > 0)
|
|
{
|
|
projectEntity = await resumeRepository.GetProjectAsync(projectDto.ProjectId.Value);
|
|
}
|
|
|
|
if (projectEntity == null)
|
|
{
|
|
projectEntity = new Project
|
|
{
|
|
Name = projectDto.Name,
|
|
Description = projectDto.Description,
|
|
Categories = projectDto.Categories ?? Array.Empty<string>(),
|
|
Roles = projectDto.Roles != null ? string.Join(",", projectDto.Roles) : null,
|
|
Responsibilities = projectDto.Responsibilities != null ? string.Join(",", projectDto.Responsibilities) : null,
|
|
TechnologiesUsed = projectDto.TechnologiesUsed != null ? string.Join(",", projectDto.TechnologiesUsed) : null,
|
|
Challenges = projectDto.Challenges,
|
|
LessonsLearned = projectDto.LessonsLearned,
|
|
Impact = projectDto.Impact,
|
|
StartDate = projectDto.StartDate == default ? null : projectDto.StartDate,
|
|
EndDate = projectDto.EndDate == default ? null : projectDto.EndDate,
|
|
ImagePath = projectDto.ImagePath,
|
|
Status = projectDto.Status,
|
|
ResumeId = resumeWithCollections.ResumeId
|
|
};
|
|
|
|
resumeRepository.AddProject(projectEntity);
|
|
}
|
|
else
|
|
{
|
|
projectEntity.Name = projectDto.Name;
|
|
projectEntity.Description = projectDto.Description;
|
|
projectEntity.Categories = projectDto.Categories ?? Array.Empty<string>();
|
|
projectEntity.Roles = projectDto.Roles != null ? string.Join(",", projectDto.Roles) : projectEntity.Roles;
|
|
projectEntity.Responsibilities = projectDto.Responsibilities != null ? string.Join(",", projectDto.Responsibilities) : projectEntity.Responsibilities;
|
|
projectEntity.TechnologiesUsed = projectDto.TechnologiesUsed != null ? string.Join(",", projectDto.TechnologiesUsed) : projectEntity.TechnologiesUsed;
|
|
projectEntity.Challenges = projectDto.Challenges;
|
|
projectEntity.LessonsLearned = projectDto.LessonsLearned;
|
|
projectEntity.Impact = projectDto.Impact;
|
|
projectEntity.StartDate = projectDto.StartDate == default ? projectEntity.StartDate : projectDto.StartDate;
|
|
projectEntity.EndDate = projectDto.EndDate == default ? projectEntity.EndDate : projectDto.EndDate;
|
|
projectEntity.ImagePath = projectDto.ImagePath;
|
|
projectEntity.Status = projectDto.Status;
|
|
|
|
resumeRepository.UpdateProject(projectEntity);
|
|
}
|
|
|
|
await resumeRepository.SaveChangesAsync();
|
|
|
|
return mapper.Map<ProjectDto>(projectEntity);
|
|
}
|
|
|
|
public async Task<bool> DeleteProjectAsync(int candidateId, int projectId)
|
|
{
|
|
var resume = await resumeRepository.GetLatestResumeForCandidateAsync(candidateId, includeOtherData: false)
|
|
?? throw new KeyNotFoundException($"Resume for candidate {candidateId} not found.");
|
|
|
|
var project = await resumeRepository.GetProjectAsync(projectId);
|
|
if (project == null || project.ResumeId != resume.ResumeId)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
resumeRepository.RemoveProject(project);
|
|
await resumeRepository.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
public async Task<AboutDto> UpsertHobbiesAsync(int candidateId, AboutDto aboutDto)
|
|
{
|
|
var resume = await resumeRepository.GetLatestResumeForCandidateAsync(candidateId, includeOtherData: false)
|
|
?? throw new KeyNotFoundException($"Resume for candidate {candidateId} not found.");
|
|
|
|
// Update About
|
|
if (!string.IsNullOrWhiteSpace(aboutDto.About))
|
|
{
|
|
resume.About = aboutDto.About;
|
|
resumeRepository.UpdateResume(resume);
|
|
}
|
|
|
|
// Upsert Hobbies
|
|
var existingHobbies = (await resumeRepository.GetHobbiesByResumeIdAsync(resume.ResumeId)).ToList();
|
|
var incomingIds = aboutDto.Hobbies.Where(h => h.HobbyId > 0).Select(h => h.HobbyId).ToHashSet();
|
|
|
|
foreach (var existing in existingHobbies)
|
|
{
|
|
if (!incomingIds.Contains(existing.HobbyId))
|
|
{
|
|
resumeRepository.RemoveHobby(existing);
|
|
}
|
|
}
|
|
|
|
var order = 1;
|
|
var resultEntities = new List<Hobby>();
|
|
|
|
foreach (var hobbyDto in aboutDto.Hobbies)
|
|
{
|
|
Hobby? hobbyEntity = null;
|
|
|
|
if (hobbyDto.HobbyId > 0)
|
|
{
|
|
hobbyEntity = existingHobbies.FirstOrDefault(h => h.HobbyId == hobbyDto.HobbyId);
|
|
}
|
|
|
|
if (hobbyEntity == null)
|
|
{
|
|
hobbyEntity = new Hobby
|
|
{
|
|
Name = hobbyDto.Name,
|
|
Description = hobbyDto.Description,
|
|
Icon = hobbyDto.Icon,
|
|
ResumeId = resume.ResumeId,
|
|
Order = order
|
|
};
|
|
resumeRepository.AddHobby(hobbyEntity);
|
|
}
|
|
else
|
|
{
|
|
hobbyEntity.Name = hobbyDto.Name;
|
|
hobbyEntity.Description = hobbyDto.Description;
|
|
hobbyEntity.Icon = hobbyDto.Icon;
|
|
hobbyEntity.Order = order;
|
|
resumeRepository.UpdateHobby(hobbyEntity);
|
|
}
|
|
|
|
resultEntities.Add(hobbyEntity);
|
|
order++;
|
|
}
|
|
|
|
await resumeRepository.SaveChangesAsync();
|
|
|
|
return new AboutDto
|
|
{
|
|
About = resume.About,
|
|
Hobbies = mapper.Map<ICollection<HobbyDto>>(resultEntities)
|
|
};
|
|
}
|
|
|
|
public async Task<CandidateSocialLinksDto> UpsertContactAsync(int candidateId, CandidateSocialLinksDto contactDto)
|
|
{
|
|
var resume = await resumeRepository.GetCandidateWithSocialLinksAsync(candidateId)
|
|
?? throw new KeyNotFoundException($"Resume with candidate id {candidateId} not found.");
|
|
|
|
if (!string.IsNullOrWhiteSpace(contactDto.Title))
|
|
{
|
|
resume.Title = contactDto.Title;
|
|
resumeRepository.UpdateResume(resume);
|
|
}
|
|
|
|
if (contactDto.Candidate != null && resume.Candidate != null)
|
|
{
|
|
resume.Candidate.FirstName = contactDto.Candidate.FirstName;
|
|
resume.Candidate.LastName = contactDto.Candidate.LastName;
|
|
resume.Candidate.Email = contactDto.Candidate.Email;
|
|
resume.Candidate.Phone = contactDto.Candidate.Phone;
|
|
resume.Candidate.Address = contactDto.Candidate.Address;
|
|
|
|
resumeRepository.UpdateCandidate(resume.Candidate);
|
|
}
|
|
|
|
if (contactDto.SocialLinks != null)
|
|
{
|
|
if (resume.SocialLinks == null)
|
|
{
|
|
resume.SocialLinks = new SocialLinks
|
|
{
|
|
ResumeId = resume.ResumeId,
|
|
GitHub = contactDto.SocialLinks.GitHub,
|
|
Linkedin = contactDto.SocialLinks.Linkedin,
|
|
Instagram = contactDto.SocialLinks.Instagram,
|
|
Facebook = contactDto.SocialLinks.Facebook,
|
|
Twitter = contactDto.SocialLinks.Twitter,
|
|
PersonalWebsite = contactDto.SocialLinks.PersonalWebsite,
|
|
BlogUrl = contactDto.SocialLinks.BlogUrl
|
|
};
|
|
resumeRepository.AddSocialLink(resume.SocialLinks);
|
|
}
|
|
else
|
|
{
|
|
resume.SocialLinks.GitHub = contactDto.SocialLinks.GitHub;
|
|
resume.SocialLinks.Linkedin = contactDto.SocialLinks.Linkedin;
|
|
resume.SocialLinks.Instagram = contactDto.SocialLinks.Instagram;
|
|
resume.SocialLinks.Facebook = contactDto.SocialLinks.Facebook;
|
|
resume.SocialLinks.Twitter = contactDto.SocialLinks.Twitter;
|
|
resume.SocialLinks.PersonalWebsite = contactDto.SocialLinks.PersonalWebsite;
|
|
resume.SocialLinks.BlogUrl = contactDto.SocialLinks.BlogUrl;
|
|
|
|
resumeRepository.UpdateSocialLink(resume.SocialLinks);
|
|
}
|
|
}
|
|
|
|
await resumeRepository.SaveChangesAsync();
|
|
|
|
var updatedContact = await resumeRepository.GetCandidateWithSocialLinksAsync(candidateId);
|
|
return mapper.Map<CandidateSocialLinksDto>(updatedContact);
|
|
}
|
|
|
|
public async Task<IEnumerable<SkillDto>> UpsertSkillsAsync(int candidateId, IEnumerable<SkillDto> skillDtos)
|
|
{
|
|
var resumeWithCollections = await GetOrCreateResumeWithCollectionsAsync(candidateId);
|
|
|
|
UpsertSkills(resumeWithCollections, skillDtos.ToList());
|
|
|
|
await resumeRepository.SaveChangesAsync();
|
|
|
|
var updatedResume = await resumeRepository.GetByIdWithCollectionsAsync(resumeWithCollections.ResumeId);
|
|
return mapper.Map<IEnumerable<SkillDto>>(updatedResume?.Skills ?? []);
|
|
}
|
|
|
|
public async Task<IEnumerable<AcademicDto>> UpsertAcademicsAsync(int candidateId, IEnumerable<AcademicDto> academicDtos)
|
|
{
|
|
var resumeWithCollections = await GetOrCreateResumeWithCollectionsAsync(candidateId);
|
|
|
|
UpsertAcademics(resumeWithCollections, academicDtos.ToList());
|
|
|
|
await resumeRepository.SaveChangesAsync();
|
|
|
|
var updatedResume = await resumeRepository.GetByIdWithCollectionsAsync(resumeWithCollections.ResumeId);
|
|
return mapper.Map<IEnumerable<AcademicDto>>(updatedResume?.Academics ?? []);
|
|
}
|
|
|
|
public async Task<IEnumerable<ExperienceDto>> UpsertExperiencesAsync(int candidateId, IEnumerable<ExperienceDto> experienceDtos)
|
|
{
|
|
var resumeWithCollections = await GetOrCreateResumeWithCollectionsAsync(candidateId);
|
|
|
|
UpsertExperiences(resumeWithCollections, experienceDtos.ToList());
|
|
|
|
await resumeRepository.SaveChangesAsync();
|
|
|
|
var updatedResume = await resumeRepository.GetByIdWithCollectionsAsync(resumeWithCollections.ResumeId);
|
|
return mapper.Map<IEnumerable<ExperienceDto>>(updatedResume?.Experiences ?? []);
|
|
}
|
|
|
|
public async Task<IEnumerable<CertificationDto>> UpsertCertificationsAsync(int candidateId, IEnumerable<CertificationDto> certificationDtos)
|
|
{
|
|
var resumeWithCollections = await GetOrCreateResumeWithCollectionsAsync(candidateId);
|
|
|
|
UpsertCertifications(resumeWithCollections, certificationDtos.ToList());
|
|
|
|
await resumeRepository.SaveChangesAsync();
|
|
|
|
var updatedResume = await resumeRepository.GetByIdWithCollectionsAsync(resumeWithCollections.ResumeId);
|
|
return mapper.Map<IEnumerable<CertificationDto>>(updatedResume?.Certifications ?? []);
|
|
}
|
|
|
|
#region Private Helpers
|
|
|
|
private async Task<Resume> GetOrCreateResumeWithCollectionsAsync(int candidateId)
|
|
{
|
|
var resume = await resumeRepository.GetLatestResumeForCandidateAsync(candidateId, includeOtherData: false);
|
|
|
|
if (resume == null)
|
|
{
|
|
resume = new Resume
|
|
{
|
|
CandidateId = candidateId,
|
|
About = string.Empty,
|
|
Order = 1
|
|
};
|
|
|
|
resumeRepository.AddResume(resume);
|
|
await resumeRepository.SaveChangesAsync();
|
|
}
|
|
|
|
return await resumeRepository.GetByIdWithCollectionsAsync(resume.ResumeId)
|
|
?? throw new InvalidOperationException("Failed to reload resume with collections.");
|
|
}
|
|
|
|
private void UpsertSkills(Resume resume, ICollection<SkillDto> skillDtos)
|
|
{
|
|
var existingSkills = resume.Skills.ToList();
|
|
var incomingIds = skillDtos.Where(s => s.SkillId > 0).Select(s => s.SkillId).ToHashSet();
|
|
|
|
foreach (var existing in existingSkills)
|
|
{
|
|
if (!incomingIds.Contains(existing.SkillId))
|
|
{
|
|
resumeRepository.RemoveSkill(existing);
|
|
}
|
|
}
|
|
|
|
foreach (var skillDto in skillDtos)
|
|
{
|
|
var skillEntity = skillDto.SkillId > 0
|
|
? existingSkills.FirstOrDefault(s => s.SkillId == skillDto.SkillId)
|
|
: null;
|
|
|
|
if (skillEntity == null)
|
|
{
|
|
resumeRepository.AddSkill(new Skill
|
|
{
|
|
Name = skillDto.Name,
|
|
Description = skillDto.Description,
|
|
ProficiencyLevel = skillDto.ProficiencyLevel,
|
|
ResumeId = resume.ResumeId
|
|
});
|
|
}
|
|
else
|
|
{
|
|
skillEntity.Name = skillDto.Name;
|
|
skillEntity.Description = skillDto.Description;
|
|
skillEntity.ProficiencyLevel = skillDto.ProficiencyLevel;
|
|
resumeRepository.UpdateSkill(skillEntity);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpsertAcademics(Resume resume, ICollection<AcademicDto> academicDtos)
|
|
{
|
|
var existingAcademics = resume.Academics.ToList();
|
|
var incomingIds = academicDtos.Where(a => a.AcademicId > 0).Select(a => a.AcademicId).ToHashSet();
|
|
|
|
foreach (var existing in existingAcademics)
|
|
{
|
|
if (!incomingIds.Contains(existing.AcademicId))
|
|
{
|
|
resumeRepository.RemoveAcademic(existing);
|
|
}
|
|
}
|
|
|
|
foreach (var academicDto in academicDtos)
|
|
{
|
|
var academicEntity = academicDto.AcademicId > 0
|
|
? existingAcademics.FirstOrDefault(a => a.AcademicId == academicDto.AcademicId)
|
|
: null;
|
|
|
|
if (academicEntity == null)
|
|
{
|
|
resumeRepository.AddAcademic(new Academic
|
|
{
|
|
Institution = academicDto.Institution,
|
|
StartYear = academicDto.StartYear,
|
|
EndYear = academicDto.EndYear,
|
|
Degree = academicDto.Degree,
|
|
DegreeSpecialization = academicDto.DegreeSpecialization,
|
|
ResumeId = resume.ResumeId
|
|
});
|
|
}
|
|
else
|
|
{
|
|
academicEntity.Institution = academicDto.Institution;
|
|
academicEntity.StartYear = academicDto.StartYear;
|
|
academicEntity.EndYear = academicDto.EndYear;
|
|
academicEntity.Degree = academicDto.Degree;
|
|
academicEntity.DegreeSpecialization = academicDto.DegreeSpecialization;
|
|
resumeRepository.UpdateAcademic(academicEntity);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpsertExperiences(Resume resume, ICollection<ExperienceDto> experienceDtos)
|
|
{
|
|
var existingExperiences = resume.Experiences.ToList();
|
|
var incomingIds = experienceDtos.Where(e => e.ExperienceId > 0).Select(e => e.ExperienceId).ToHashSet();
|
|
|
|
foreach (var existing in existingExperiences)
|
|
{
|
|
if (!incomingIds.Contains(existing.ExperienceId))
|
|
{
|
|
foreach (var detail in existing.Details.ToList())
|
|
{
|
|
resumeRepository.RemoveExperienceDetails(detail);
|
|
}
|
|
resumeRepository.RemoveExperience(existing);
|
|
}
|
|
}
|
|
|
|
foreach (var experienceDto in experienceDtos)
|
|
{
|
|
var experienceEntity = experienceDto.ExperienceId > 0
|
|
? existingExperiences.FirstOrDefault(e => e.ExperienceId == experienceDto.ExperienceId)
|
|
: null;
|
|
|
|
if (experienceEntity == null)
|
|
{
|
|
experienceEntity = new Experience
|
|
{
|
|
Title = experienceDto.Title,
|
|
Description = experienceDto.Description,
|
|
Company = experienceDto.Company,
|
|
Location = experienceDto.Location ?? string.Empty,
|
|
StartDate = experienceDto.StartDate ?? DateTime.UtcNow,
|
|
EndDate = experienceDto.EndDate,
|
|
ResumeId = resume.ResumeId
|
|
};
|
|
resumeRepository.AddExperience(experienceEntity);
|
|
|
|
var detailOrder = 1;
|
|
foreach (var detailDto in experienceDto.Details)
|
|
{
|
|
resumeRepository.AddExperienceDetails(new ExperienceDetails
|
|
{
|
|
Details = detailDto.Details,
|
|
Order = detailOrder++,
|
|
Experience = experienceEntity
|
|
});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
experienceEntity.Title = experienceDto.Title;
|
|
experienceEntity.Description = experienceDto.Description;
|
|
experienceEntity.Company = experienceDto.Company;
|
|
if (!string.IsNullOrWhiteSpace(experienceDto.Location))
|
|
experienceEntity.Location = experienceDto.Location;
|
|
if (experienceDto.StartDate.HasValue)
|
|
experienceEntity.StartDate = experienceDto.StartDate.Value;
|
|
experienceEntity.EndDate = experienceDto.EndDate;
|
|
|
|
resumeRepository.UpdateExperience(experienceEntity);
|
|
UpsertExperienceDetails(experienceEntity, experienceDto.Details);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpsertExperienceDetails(Experience experience, ICollection<ExperienceDetailsDto> detailDtos)
|
|
{
|
|
var existingDetails = experience.Details.ToList();
|
|
var incomingIds = detailDtos.Where(d => d.Id > 0).Select(d => d.Id).ToHashSet();
|
|
|
|
foreach (var existing in existingDetails)
|
|
{
|
|
if (!incomingIds.Contains(existing.Id))
|
|
{
|
|
resumeRepository.RemoveExperienceDetails(existing);
|
|
}
|
|
}
|
|
|
|
var order = 1;
|
|
foreach (var detailDto in detailDtos)
|
|
{
|
|
var detailEntity = detailDto.Id > 0
|
|
? existingDetails.FirstOrDefault(d => d.Id == detailDto.Id)
|
|
: null;
|
|
|
|
if (detailEntity == null)
|
|
{
|
|
resumeRepository.AddExperienceDetails(new ExperienceDetails
|
|
{
|
|
Details = detailDto.Details,
|
|
Order = order,
|
|
ExperienceId = experience.ExperienceId
|
|
});
|
|
}
|
|
else
|
|
{
|
|
detailEntity.Details = detailDto.Details;
|
|
detailEntity.Order = order;
|
|
resumeRepository.UpdateExperienceDetails(detailEntity);
|
|
}
|
|
order++;
|
|
}
|
|
}
|
|
|
|
private void UpsertCertifications(Resume resume, ICollection<CertificationDto> certificationDtos)
|
|
{
|
|
var existingCertifications = resume.Certifications.ToList();
|
|
var incomingIds = certificationDtos.Where(c => c.CertificationId > 0).Select(c => c.CertificationId).ToHashSet();
|
|
|
|
foreach (var existing in existingCertifications)
|
|
{
|
|
if (!incomingIds.Contains(existing.CertificationId))
|
|
{
|
|
resumeRepository.RemoveCertification(existing);
|
|
}
|
|
}
|
|
|
|
foreach (var certDto in certificationDtos)
|
|
{
|
|
var existingCert = certDto.CertificationId > 0
|
|
? existingCertifications.FirstOrDefault(c => c.CertificationId == certDto.CertificationId)
|
|
: null;
|
|
|
|
if (existingCert == null)
|
|
{
|
|
resumeRepository.AddCertification(new Certification
|
|
{
|
|
CertificationName = certDto.CertificationName,
|
|
IssuingOrganization = certDto.IssuingOrganization,
|
|
CertificationLink = certDto.CertificationLink,
|
|
IssueDate = certDto.IssueDate,
|
|
ExpiryDate = certDto.ExpiryDate,
|
|
ResumeId = resume.ResumeId
|
|
});
|
|
}
|
|
else
|
|
{
|
|
existingCert.CertificationName = certDto.CertificationName;
|
|
existingCert.IssuingOrganization = certDto.IssuingOrganization;
|
|
existingCert.CertificationLink = certDto.CertificationLink;
|
|
existingCert.IssueDate = certDto.IssueDate;
|
|
existingCert.ExpiryDate = certDto.ExpiryDate;
|
|
resumeRepository.UpdateCertification(existingCert);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|