| @@ -0,0 +1,25 @@ | |||
| **/.classpath | |||
| **/.dockerignore | |||
| **/.env | |||
| **/.git | |||
| **/.gitignore | |||
| **/.project | |||
| **/.settings | |||
| **/.toolstarget | |||
| **/.vs | |||
| **/.vscode | |||
| **/*.*proj.user | |||
| **/*.dbmdl | |||
| **/*.jfm | |||
| **/azds.yaml | |||
| **/bin | |||
| **/charts | |||
| **/docker-compose* | |||
| **/Dockerfile* | |||
| **/node_modules | |||
| **/npm-debug.log | |||
| **/obj | |||
| **/secrets.dev.yaml | |||
| **/values.dev.yaml | |||
| LICENSE | |||
| README.md | |||
| @@ -0,0 +1,4 @@ | |||
| [*.cs] | |||
| # CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. | |||
| dotnet_diagnostic.CS8618.severity = none | |||
| @@ -0,0 +1,21 @@ | |||
| ################################################################################ | |||
| # This .gitignore file was automatically created by Microsoft(R) Visual Studio. | |||
| ################################################################################ | |||
| /.vs | |||
| /Diligent.WebAPI.Data/bin/Debug/net6.0 | |||
| /Diligent.WebAPI.Data/obj | |||
| /Diligent.WebAPI.Host/.vs/Diligent.WebAPI/v17 | |||
| /Diligent.WebAPI.Host/bin/Debug/net6.0 | |||
| /Diligent.WebAPI.Host/obj | |||
| /Diligent.WebAPI.Host/Logs | |||
| /Diligent.WebAPI.Business/bin/Debug/net6.0 | |||
| /Diligent.WebAPI.Business/obj/Debug/net6.0 | |||
| /Diligent.WebAPI.Business/obj | |||
| /Diligent.WebAPI.Contracts/bin/Debug/net6.0 | |||
| /Diligent.WebAPI.Contracts/obj/Debug/net6.0 | |||
| /Diligent.WebAPI.Contracts/obj | |||
| /Diligent.WebAPI.Tests/bin/Debug/net6.0 | |||
| /Diligent.WebAPI.Tests/obj/Debug/net6.0 | |||
| /Diligent.WebAPI.Tests/obj | |||
| /Diligent.WebAPI.Tests/coverageresults | |||
| @@ -0,0 +1,28 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk"> | |||
| <PropertyGroup> | |||
| <TargetFramework>net6.0</TargetFramework> | |||
| <ImplicitUsings>enable</ImplicitUsings> | |||
| <Nullable>enable</Nullable> | |||
| </PropertyGroup> | |||
| <ItemGroup> | |||
| <PackageReference Include="AutoMapper" Version="11.0.1" /> | |||
| <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" /> | |||
| <PackageReference Include="Azure.Storage.Blobs" Version="12.14.1" /> | |||
| <PackageReference Include="Bytescout.Spreadsheet" Version="4.6.0.2025" /> | |||
| <PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" /> | |||
| <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.10" /> | |||
| <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="6.0.10" /> | |||
| <PackageReference Include="Microsoft.Extensions.Identity.Core" Version="6.0.10" /> | |||
| <PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="6.0.10" /> | |||
| <PackageReference Include="RestSharp" Version="108.0.2-alpha.0.6" /> | |||
| <PackageReference Include="WindowsAzure.Storage" Version="9.3.3" /> | |||
| </ItemGroup> | |||
| <ItemGroup> | |||
| <ProjectReference Include="..\Diligent.WebAPI.Contracts\Diligent.WebAPI.Contracts.csproj" /> | |||
| <ProjectReference Include="..\Diligent.WebAPI.Data\Diligent.WebAPI.Data.csproj" /> | |||
| </ItemGroup> | |||
| </Project> | |||
| @@ -0,0 +1,58 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Business.Extensions | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public static class AdExtensions | |||
| { | |||
| public static List<Ad> Filter(this List<Ad> query, AdFilterDto filters) => | |||
| query.FilterByExperience(filters.MinExperience, filters.MaxExperience).FilterByWorkType(filters.WorkHour).FilterByEmploymentType(filters.EmploymentType).ToList().FilterByTechnologies(filters.Technologies); | |||
| public static List<Ad> FilterByExperience(this List<Ad> query, int minExperience, int maxExperience) => | |||
| minExperience >= maxExperience ? query.Where(x => x.MinimumExperience >= minExperience && x.MinimumExperience <= maxExperience).ToList() | |||
| : query.Where(x => x.MinimumExperience >= minExperience && x.MinimumExperience <= maxExperience).ToList(); | |||
| public static List<Ad> FilterByWorkType(this List<Ad> query, string workHour) => | |||
| workHour.ToLower() == "parttime" ? query.Where(x => x.WorkHour == WorkHours.PartTime).ToList() : query.Where(x => x.WorkHour == WorkHours.FullTime).ToList(); | |||
| public static List<Ad> FilterByEmploymentType(this List<Ad> query, string employmentType) => | |||
| employmentType.ToLower() == "intership" ? query.Where(x => x.EmploymentType == EmploymentTypes.Intership).ToList() : query.Where(x => x.EmploymentType == EmploymentTypes.Work).ToList(); | |||
| public static List<Ad> FilterByTechnologies(this List<Ad> query, string[] technologies) | |||
| { | |||
| if (technologies == null || technologies.Length == 0) | |||
| { | |||
| return query; | |||
| } | |||
| List<Ad> filteredAds = new List<Ad>(); | |||
| for (int i = 0; i < query.Count(); i++) | |||
| { | |||
| for (int j = 0; j < query[i].Technologies.Count(); j++) | |||
| { | |||
| var s = 0; | |||
| for (int k = 0; k < technologies.Length; k++) | |||
| { | |||
| if (query[i].Technologies[j].Name.ToLower() == technologies[k].ToLower()) | |||
| { | |||
| s = 1; | |||
| } | |||
| } | |||
| if (s == 1) | |||
| { | |||
| filteredAds.Add(query[i]); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| return filteredAds; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,88 @@ | |||
| using System.Diagnostics.CodeAnalysis; | |||
| using static Diligent.WebAPI.Data.Entities.Applicant; | |||
| namespace Diligent.WebAPI.Business.Extensions | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public static class ApplicantExtensions | |||
| { | |||
| public static List<Applicant> FilterApplicants(this List<Applicant> query,ApplicantFilterDto applicantFilterDto) | |||
| { | |||
| return query.FilterByExperience(applicantFilterDto.MinExperience, applicantFilterDto.MaxExperience) | |||
| .FilterByEmploymentType(applicantFilterDto.EmploymentType) | |||
| .FilterByDateOfApplication(applicantFilterDto.MinDateOfApplication, applicantFilterDto.MaxDateOfApplication) | |||
| .FilterByTechnologies(applicantFilterDto.Technologies).ToList(); | |||
| } | |||
| public static List<Ad> FilterAdApplicants(this List<Ad> query, ApplicantFilterDto applicantFilterDto) | |||
| { | |||
| List<Ad> filteredAds = new(); | |||
| List<List<Applicant>> applicants = new(); | |||
| for (int i = 0; i < query.Count; i++) | |||
| { | |||
| var app = query[i].Applicants.FilterApplicants(applicantFilterDto); | |||
| applicants.Add(app); | |||
| var k = query[i]; | |||
| k.Applicants = applicants[i]; | |||
| filteredAds.Add(k); | |||
| } | |||
| return filteredAds; | |||
| } | |||
| private static List<Applicant> FilterByExperience(this List<Applicant> query, int minExperience, int maxExperience) | |||
| { | |||
| if ((minExperience == 0 && maxExperience == 0) || minExperience > maxExperience) return query; | |||
| return query.Where(x => x.Experience >= minExperience && x.Experience <= maxExperience).ToList(); | |||
| } | |||
| private static List<Applicant> FilterByEmploymentType(this List<Applicant> query, string? employmentType) | |||
| { | |||
| if (employmentType == null) return query; | |||
| return query.Where(x => x.TypeOfEmployment == Enum.Parse<TypesOfEmployment>(employmentType)).ToList(); | |||
| } | |||
| private static List<Applicant> FilterByDateOfApplication(this List<Applicant> query, DateTime? minDateOfApplication, DateTime? maxDateOfApplication) | |||
| { | |||
| if (minDateOfApplication == null) return query; | |||
| if (minDateOfApplication > maxDateOfApplication) return query; | |||
| if (maxDateOfApplication == null) return query.Where(x => x.DateOfApplication >= minDateOfApplication && x.DateOfApplication <= DateTime.Now).ToList(); | |||
| return query.Where(x => x.DateOfApplication >= minDateOfApplication && x.DateOfApplication < maxDateOfApplication).ToList(); | |||
| } | |||
| private static List<Applicant> FilterByTechnologies(this List<Applicant> query, string[]? technologies) | |||
| { | |||
| if (technologies is null) | |||
| { | |||
| return query; | |||
| } | |||
| List<Applicant> filteredApplicants = new(); | |||
| for (int i = 0; i < query.Count; i++) | |||
| { | |||
| for (int j = 0; j < query[i].TechnologyApplicants.Count; j++) | |||
| { | |||
| bool s = false; | |||
| for (int n = 0; n < technologies.Length; n++) | |||
| { | |||
| if (query[i].TechnologyApplicants[j].Technology.Name.ToLower() == technologies[n].ToLower()) | |||
| { | |||
| s = true; | |||
| break; | |||
| } | |||
| } | |||
| if (s) | |||
| { | |||
| filteredApplicants.Add(query[i]); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| return filteredApplicants; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| using System.Diagnostics.CodeAnalysis; | |||
| namespace Diligent.WebAPI.Business.Extensions | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public static class PaginationExtension | |||
| { | |||
| public static List<T> ApplyPagging<T>(this List<T> query, Pagination pagination) | |||
| { | |||
| return query.Skip((pagination.CurrentPage - 1) * pagination.PageSize) | |||
| .Take(pagination.PageSize).ToList(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,52 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Pattern; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Business.Extensions | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public static class PatternExtension | |||
| { | |||
| public static List<Pattern> FilterApplicants(this List<Pattern> query, FilterPatternDto filterPatternDto) | |||
| { | |||
| return query.FilterByDate(filterPatternDto.FromDate, filterPatternDto.ToDate) | |||
| .FilterBySelectionLevels(filterPatternDto.SelectionLevels) | |||
| .ToList(); | |||
| } | |||
| private static List<Pattern> FilterByDate(this List<Pattern> query, DateTime? fromDate, DateTime? toDate) | |||
| { | |||
| if(fromDate == null && toDate == null) return query; | |||
| if(fromDate == null && toDate != null) return query.Where(x => x.CreatedAt <= toDate).ToList(); | |||
| if ((fromDate != null && toDate == null) || (fromDate > toDate)) return query.Where(x => x.CreatedAt >= fromDate).ToList(); | |||
| return query.Where(x => x.CreatedAt >= fromDate && x.CreatedAt < toDate).ToList(); | |||
| } | |||
| private static List<Pattern> FilterBySelectionLevels(this List<Pattern> query, int[]? selectionLevels) | |||
| { | |||
| if (selectionLevels is null) | |||
| { | |||
| return query; | |||
| } | |||
| List<Pattern> filteredPatterns = new(); | |||
| for (int i = 0; i < query.Count; i++) | |||
| { | |||
| for(int j = 0; j < selectionLevels.Length; j++) | |||
| { | |||
| if (query[i].SelectionLevelId == selectionLevels[j]) | |||
| { | |||
| filteredPatterns.Add(query[i]); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| return filteredPatterns; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,38 @@ | |||
| namespace Diligent.WebAPI.Business.Extensions | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public static class SelectionProcessExtensions | |||
| { | |||
| public static List<SelectionLevel> FilterLevels(this List<SelectionLevel> query, SelectionProcessFilterDto filter) | |||
| { | |||
| var filteredLevels = new List<SelectionLevel>(); | |||
| // If filters are empty | |||
| if ((filter == null) || (!filter.DateStart.HasValue && !filter.DateEnd.HasValue && filter.Statuses != null && filter.Statuses.Length == 0)) | |||
| return query; | |||
| foreach (var level in query) | |||
| { | |||
| List<SelectionProcess> selectionProcesses = level.SelectionProcesses; | |||
| if (filter.DateStart.HasValue) | |||
| { | |||
| selectionProcesses = level.SelectionProcesses.Where(sp => sp.Date >= filter.DateStart.Value).ToList(); | |||
| } | |||
| if (filter.DateEnd.HasValue) | |||
| { | |||
| selectionProcesses = selectionProcesses.Where(sp => sp.Date <= filter.DateEnd.Value).ToList(); | |||
| } | |||
| var filteredLevel = new SelectionLevel { Id = level.Id, Name = level.Name, SelectionProcesses = level.SelectionProcesses}; | |||
| if(filter.Statuses != null && filter.Statuses.Length > 0) { | |||
| filteredLevel.SelectionProcesses = selectionProcesses.Where(f => filter.Statuses.Contains(f.Status)).ToList(); | |||
| } | |||
| filteredLevels.Add(filteredLevel); | |||
| } | |||
| return filteredLevels; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,86 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Business.Helper | |||
| { | |||
| public static class HTMLHelper | |||
| { | |||
| public static string RenderForgotPasswordPage(string url) | |||
| { | |||
| return "<div style=\"font-family: sans-serif\">" + | |||
| "<div style=\"font-family: sans-serif;text-align: center;\">" + | |||
| "<h2 style=\"color: #017397;\">HR Center Password Reset</h2>" + | |||
| "<p style=\"font-size: 20px\">" + | |||
| "To reset your HR Center password, please click on the button below." + | |||
| "</p>" + | |||
| "<a style = \"color: white;text-decoration:none;background-color: #017397;cursor: pointer;font-size: 20px;width: 220px;text-align: center;border-radius: 5px;padding: 5px 15px;height: 25px;\" " + | |||
| $"href=\"{url}\">" + | |||
| " RESET PASSWORD" + | |||
| "</a>" + | |||
| "<p style = \"font-size: 12px; margin-top: 25px;\" >" + | |||
| "Please do not reply to this email.This message was sent from a notification-only address that is not monitored." + | |||
| "</p>" + | |||
| "</div>" + | |||
| "</div>"; | |||
| } | |||
| public static string RenderRegisterPage(string url) | |||
| { | |||
| return "<div style=\"font-family: sans-serif\">" + | |||
| "<div style=\"font-family: sans-serif;text-align: center;\">" + | |||
| "<h2 style=\"color: #017397;\">Welcome to HR Center</h2>" + | |||
| "<p style=\"font-size: 20px\">" + | |||
| "To register, please click on the button below." + | |||
| "</p>" + | |||
| "<a style = \"color: white;text-decoration:none;background-color: #017397;cursor: pointer;font-size: 20px;width: 220px;text-align: center;border-radius: 5px;padding: 5px 15px;height: 25px;\" " + | |||
| $"href=\"{url}\">" + | |||
| " Click here to register" + | |||
| "</a>" + | |||
| "<p style = \"font-size: 12px; margin-top: 25px;\" >" + | |||
| "Please do not reply to this email.This message was sent from a notification-only address that is not monitored." + | |||
| "</p>" + | |||
| "</div>" + | |||
| "</div>"; | |||
| } | |||
| public static string RenderTagPage(string url) | |||
| { | |||
| return "<div>" + | |||
| "<a style = \"color: white;text-decoration:none;background-color: #017397;cursor: pointer;font-size: 20px;border-radius: 5px;padding: 5px 15px;height: 25px;margin-top:10px;\" " + | |||
| $"href=\"{url}\">" + | |||
| "Click here to see the comment" + | |||
| "</a>" + | |||
| "</div>"; | |||
| } | |||
| public static string SuccessfulStep(string message, string pattern, string date) | |||
| { | |||
| return "<div style=\"font-family: sans-serif\">" + | |||
| "<div style=\"font-family: sans-serif; text-align: center; \">" + | |||
| "<h2 style=\"color: #017397;\">" + pattern + "</h2>" + | |||
| "</div>" + | |||
| "<div style=\"padding: 0.25rem 2rem 0 2rem\">" + | |||
| "<p style=\"color: #017397;\">Poštovani,</p >" + | |||
| "</div>" + | |||
| "<div style=\"padding: 0 13rem 0 4rem; \">" + | |||
| "<p style=\"color: #017397;\">" + message + " " + date + "</p>" + | |||
| "</div>" + "<div style=\"padding: 0 2rem; \">" + | |||
| "<p style=\"color: #017397;\">Srdačan pozdrav,<br>Diligent HR Team</p>" + | |||
| "</div>" + | |||
| "</div>"; | |||
| } | |||
| //public static string ScheduleInterview(string pattern, string message) | |||
| //{ | |||
| // Dictionary<string, string> Patterns = new Dictionary<string, string> | |||
| // { | |||
| // { "Neuspesan korak", $"<div><p>Neuspesan korak: {message}</p></div>" }, | |||
| // { "Uspesan korak", SuccessfulStep(message, pattern) } | |||
| // }; | |||
| // return Patterns[pattern]; | |||
| //} | |||
| } | |||
| } | |||
| @@ -0,0 +1,57 @@ | |||
| namespace Diligent.WebAPI.Business.Helper | |||
| { | |||
| public static class StringGenerator | |||
| { | |||
| public static string GenerateRandomPassword(PasswordOptions opts = null) | |||
| { | |||
| if (opts == null) opts = new PasswordOptions() | |||
| { | |||
| RequiredLength = 8, | |||
| RequiredUniqueChars = 4, | |||
| RequireDigit = true, | |||
| RequireLowercase = true, | |||
| RequireNonAlphanumeric = true, | |||
| RequireUppercase = true | |||
| }; | |||
| if(opts.RequiredLength < 4) | |||
| opts.RequiredLength = 4; | |||
| string[] randomChars = new[] { | |||
| "ABCDEFGHJKLMNOPQRSTUVWXYZ", // uppercase | |||
| "abcdefghijkmnopqrstuvwxyz", // lowercase | |||
| "0123456789", // digits | |||
| "!@$?_-" // non-alphanumeric | |||
| }; | |||
| Random rand = new(Environment.TickCount); | |||
| List<char> chars = new List<char>(); | |||
| if (opts.RequireUppercase) | |||
| chars.Insert(rand.Next(0, chars.Count), | |||
| randomChars[0][rand.Next(0, randomChars[0].Length)]); | |||
| if (opts.RequireLowercase) | |||
| chars.Insert(rand.Next(0, chars.Count), | |||
| randomChars[1][rand.Next(0, randomChars[1].Length)]); | |||
| if (opts.RequireDigit) | |||
| chars.Insert(rand.Next(0, chars.Count), | |||
| randomChars[2][rand.Next(0, randomChars[2].Length)]); | |||
| if (opts.RequireNonAlphanumeric) | |||
| chars.Insert(rand.Next(0, chars.Count), | |||
| randomChars[3][rand.Next(0, randomChars[3].Length)]); | |||
| for (int i = chars.Count; i < opts.RequiredLength | |||
| || chars.Distinct().Count() < opts.RequiredUniqueChars; i++) | |||
| { | |||
| string rcs = randomChars[rand.Next(0, randomChars.Length)]; | |||
| chars.Insert(rand.Next(0, chars.Count), | |||
| rcs[rand.Next(0, rcs.Length)]); | |||
| } | |||
| return new string(chars.ToArray()); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| | |||
| using System.Diagnostics.CodeAnalysis; | |||
| namespace Diligent.WebAPI.Business.MappingProfiles | |||
| { | |||
| public class AdMappingProfile : Profile | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public AdMappingProfile() | |||
| { | |||
| #region DTO to Model | |||
| CreateMap<AdCreateDto, Ad>(); | |||
| CreateMap<AdUpdateDto, Ad>(); | |||
| #endregion | |||
| #region Model to DTO | |||
| CreateMap<Ad, AdResponseDto>(); | |||
| CreateMap<Ad, AdDetailsResponseDto>(); | |||
| CreateMap<Ad, AdApplicantsViewDto>(); | |||
| CreateMap<Ad, AdResponseWithCountDto>() | |||
| .ForMember(dest => dest.Count, opt => opt.MapFrom(x => x.Applicants.Count)); | |||
| #endregion | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Applicant; | |||
| namespace Diligent.WebAPI.Business.MappingProfiles | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class ApplicantMappingProfile:Profile | |||
| { | |||
| public ApplicantMappingProfile() | |||
| { | |||
| #region Models to DTOs | |||
| CreateMap<Applicant, ApplicantViewDto>(); | |||
| CreateMap<Applicant, AdApplicantViewDto>(); | |||
| CreateMap<Applicant, ApplicantScheduleViewDto>(); | |||
| CreateMap<Applicant, PatternApplicantViewDto>(); | |||
| CreateMap<Applicant, ApplicantOptionsDTO>(); | |||
| #endregion | |||
| #region DTOs to Models | |||
| CreateMap<ApplicantImportDto, Applicant>(); | |||
| #endregion | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Comment; | |||
| namespace Diligent.WebAPI.Business.MappingProfiles | |||
| { | |||
| public class CommentMappingProfile:Profile | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public CommentMappingProfile() | |||
| { | |||
| #region Models to DTO | |||
| CreateMap<Comment, CommentViewDto>(); | |||
| #endregion | |||
| #region DTO to Model | |||
| CreateMap<CommentCreateDto, Comment>(); | |||
| #endregion | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| namespace Diligent.WebAPI.Business.MappingProfiles | |||
| { | |||
| public class CompanyMappingProfile : Profile | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public CompanyMappingProfile() | |||
| { | |||
| #region Models to DTOs | |||
| CreateMap<InsuranceCompany, InsuranceCompanyViewDto>(); | |||
| #endregion | |||
| #region DTOs to Models | |||
| CreateMap<InsuranceCompanyCreateDto, InsuranceCompany>(); | |||
| CreateMap<InsuranceCompanyUpdateDto, InsuranceCompany>(); | |||
| #endregion | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| namespace Diligent.WebAPI.Business.MappingProfiles | |||
| { | |||
| public class InsurerMappingProfile : Profile | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public InsurerMappingProfile() | |||
| { | |||
| #region DTO to Model | |||
| CreateMap<InsurerCreateDto, Insurer>(); | |||
| CreateMap<InsurerUpdateDto, Insurer>(); | |||
| #endregion | |||
| #region Model to DTO | |||
| CreateMap<Insurer, InsurerViewDto>(); | |||
| #endregion | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Pattern; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Business.MappingProfiles | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class PatternMappingProfile : Profile | |||
| { | |||
| public PatternMappingProfile() | |||
| { | |||
| #region DTO to Model | |||
| CreateMap<PatternCreateDto, Pattern>(); | |||
| CreateMap<PatternUpdateDto, Pattern>(); | |||
| #endregion | |||
| #region Model to DTO | |||
| CreateMap<Pattern, PatternResponseDto>(); | |||
| #endregion | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| namespace Diligent.WebAPI.Business.MappingProfiles | |||
| { | |||
| public class PolicyMappingProfiles : Profile | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public PolicyMappingProfiles() | |||
| { | |||
| CreateMap<InsurancePolicy, InsurancePolicyViewDto>(); | |||
| CreateMap<InsurancePolicyCreateDto, InsurancePolicy>(); | |||
| CreateMap<InsurancePolicyUpdateDto, InsurancePolicy>(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| | |||
| using Diligent.WebAPI.Contracts.DTOs.SelectionLevel; | |||
| namespace Diligent.WebAPI.Business.MappingProfiles | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class SelectionLevelMappingProfile : Profile | |||
| { | |||
| public SelectionLevelMappingProfile() | |||
| { | |||
| #region Model to DTO | |||
| CreateMap<SelectionLevel, SelectionLevelResposneDto>(); | |||
| CreateMap<SelectionLevel, SelectionLevelResponseWithDataDto>(); | |||
| #endregion | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Schedule; | |||
| using Diligent.WebAPI.Contracts.DTOs.SelectionProcess; | |||
| namespace Diligent.WebAPI.Business.MappingProfiles | |||
| { | |||
| public class SelectionProcessMappingProfile : Profile | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public SelectionProcessMappingProfile() | |||
| { | |||
| #region DTO to Model | |||
| CreateMap<SelectionProcessCreateDto, SelectionProcess>(); | |||
| CreateMap<SelectionProcessUpdateStatusDto, SelectionProcess>(); | |||
| #endregion | |||
| #region Model to DTO | |||
| CreateMap<SelectionProcess, SelectionProcessResposneDto>(); | |||
| CreateMap<SelectionProcess, SelectionProcessResposneWithoutApplicantDto>(); | |||
| CreateMap<SelectionProcess, ScheduleViewDto>(); | |||
| #endregion | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| | |||
| namespace Diligent.WebAPI.Business.MappingProfiles | |||
| { | |||
| public class TechnologyMappingProfile : Profile | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public TechnologyMappingProfile() | |||
| { | |||
| #region Model to DTO | |||
| CreateMap<Technology, TechnologyResponseDto>(); | |||
| //CreateMap<TechnologyApplicant, TechnologyResponseDto>(); -- ermin | |||
| CreateMap<TechnologyApplicant, TechnologyViewDto>(); | |||
| #endregion | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.User; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Business.MappingProfiles | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class UserMappingProfile : Profile | |||
| { | |||
| public UserMappingProfile() | |||
| { | |||
| #region DTO to Model | |||
| CreateMap<CreateUserRequestDto, User>(); | |||
| CreateMap<RegisterDTO, User>().ForMember(n => n.PhoneNumber, opt => opt.MapFrom(n => n.Phone)); | |||
| #endregion | |||
| #region Model to DTO | |||
| CreateMap<User, UserResponseDTO>(); | |||
| CreateMap<User, UserDetailsResponseDTO>() | |||
| .ForMember(dest => dest.PhoneNumber, opt => opt.NullSubstitute("User has no phone number saved.")) | |||
| .ForMember(dest => dest.Position, opt => opt.NullSubstitute("Position has not been declared yet.")) | |||
| .ForMember(dest => dest.LinkedIn, opt => opt.NullSubstitute("User takes no part in any social media.")); | |||
| #endregion | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| namespace Diligent.WebAPI.Business.MappingProfiles | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class WebhookMappingProfile : Profile | |||
| { | |||
| public WebhookMappingProfile() | |||
| { | |||
| #region DTO to Model | |||
| CreateMap<WebhookSubscriptionCreateDto, WebhookSubscription>(); | |||
| #endregion | |||
| #region Model to DTO | |||
| CreateMap<WebhookDefinition, WebhookDefinitionViewDto>(); | |||
| #endregion | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,215 @@ | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| public class AdService : IAdService | |||
| { | |||
| private readonly ILogger<AdService> _logger; | |||
| private readonly DatabaseContext _context; | |||
| private readonly IMapper _mapper; | |||
| private readonly ITechnologyService _technologyService; | |||
| public AdService(DatabaseContext context, IMapper mapper, ITechnologyService technologyService, ILogger<AdService> logger) | |||
| { | |||
| _logger = logger; | |||
| _context = context; | |||
| _mapper = mapper; | |||
| _technologyService = technologyService; | |||
| } | |||
| public async Task<List<AdResponseDto>> GetAllAsync() | |||
| { | |||
| _logger.LogInformation("Start getting all Ads"); | |||
| var today = DateTime.Now; | |||
| _logger.LogInformation("Getting data from DB"); | |||
| var fromDb = await _context.Ads.Include(x => x.Technologies).Where(x => x.ExpiredAt > today).ToListAsync(); | |||
| _logger.LogInformation($"Received {fromDb.Count} ads from db."); | |||
| _logger.LogInformation($"Mapping received ads to AdResponseDto"); | |||
| var result = _mapper.Map<List<AdResponseDto>>(fromDb); | |||
| _logger.LogInformation($"Ads has been mapped and received to client: {result.Count} mapped ads"); | |||
| return result; | |||
| } | |||
| public async Task<List<AdResponseWithCountDto>> GetAllWithCountAsync() | |||
| { | |||
| _logger.LogInformation("Start getting all Ads with applicants count"); | |||
| var today = DateTime.Now; | |||
| _logger.LogInformation("Getting data from database"); | |||
| var res = _mapper.Map<List<AdResponseWithCountDto>>(await _context.Ads.Include(x => x.Applicants).Where(x => x.ExpiredAt > today).ToListAsync()); | |||
| _logger.LogInformation($"Received {res.Count} ads with their counts"); | |||
| return res; | |||
| } | |||
| public async Task<AdResponseDto> GetByIdAsync(int id) | |||
| { | |||
| _logger.LogInformation($"Start searching Ad with id = {id}"); | |||
| var ad = await _context.Ads.FindAsync(id); | |||
| if (ad is null) | |||
| { | |||
| _logger.LogError($"Ad with id = {id} not found"); | |||
| throw new EntityNotFoundException("Ad not found"); | |||
| } | |||
| _logger.LogInformation($"Mapping Ad with id = {id}"); | |||
| AdResponseDto result = _mapper.Map<AdResponseDto>(ad); | |||
| _logger.LogInformation($"Ad with id = {id} mapped successfully"); | |||
| return result; | |||
| } | |||
| public async Task<AdDetailsResponseDto> GetAdDetailsByIdAsync(int id) | |||
| { | |||
| _logger.LogInformation($"Start finding Ad with id = {id} with applicants"); | |||
| var ad = await _context.Ads.Include(x => x.Applicants).Where(x => x.Id == id).FirstOrDefaultAsync(); | |||
| if (ad is null) | |||
| { | |||
| _logger.LogError($"Ad with id = {id} not found"); | |||
| throw new EntityNotFoundException("Ad not found"); | |||
| } | |||
| _logger.LogInformation($"Mapping Ad with id = {id}"); | |||
| AdDetailsResponseDto result = _mapper.Map<AdDetailsResponseDto>(ad); | |||
| _logger.LogInformation($"Ad with id = {id} mapped successfully"); | |||
| return result; | |||
| } | |||
| public async Task<List<AdResponseDto>> GetArchiveAds() | |||
| { | |||
| _logger.LogInformation("Start getting all Archived Ads"); | |||
| var today = DateTime.Now; | |||
| _logger.LogInformation("Getting data from DB"); | |||
| var archiveAds = await _context.Ads.Where(x => x.ExpiredAt < today).ToListAsync(); | |||
| _logger.LogInformation($"Received {archiveAds.Count} ads from db."); | |||
| _logger.LogInformation($"Mapping received ads to AdResponseDto"); | |||
| List<AdResponseDto> result = _mapper.Map<List<AdResponseDto>>(archiveAds); | |||
| _logger.LogInformation($"Ads has been mapped and received to client: {result.Count} mapped ads"); | |||
| return result; | |||
| } | |||
| public async Task<List<AdResponseDto>> GetFilteredAdsAsync(AdFilterDto filters) | |||
| { | |||
| _logger.LogInformation($"Start getting all filtered Ads"); | |||
| var today = DateTime.Now; | |||
| _logger.LogInformation("Getting data from DB"); | |||
| var filteredAds = await _context.Ads.Include(x => x.Technologies).Where(x => x.ExpiredAt > today).ToListAsync(); | |||
| _logger.LogInformation($"Received {filteredAds.Count} ads from db."); | |||
| _logger.LogInformation($"Mapping received ads to AdResponseDto"); | |||
| List<AdResponseDto> result = _mapper.Map<List<AdResponseDto>>(filteredAds.Filter(filters)); | |||
| _logger.LogInformation($"Ads has been mapped and received to client: {result.Count} mapped ads"); | |||
| return result; | |||
| } | |||
| public async Task CreateAsync(AdCreateDto adCreateDto) | |||
| { | |||
| _logger.LogInformation($"Start creating Ad"); | |||
| var ad = _mapper.Map<Ad>(adCreateDto); | |||
| _logger.LogInformation($"Ad created successfully"); | |||
| _logger.LogInformation($"Start adding technologies to Ad"); | |||
| for (int i = 0; i < adCreateDto.TechnologiesIds.Count; i++) | |||
| { | |||
| var technology = await _technologyService.GetEntityByIdAsync(adCreateDto.TechnologiesIds[i]); | |||
| ad.Technologies.Add(technology); | |||
| _logger.LogInformation($"Technology with id {technology.TechnologyId} added to Ad"); | |||
| } | |||
| _logger.LogInformation($"Finished adding techonologies"); | |||
| await _context.Ads.AddAsync(ad); | |||
| _logger.LogInformation($"Saving Ad to db..."); | |||
| var result = _context.SaveChangesAsync(); | |||
| _logger.LogInformation($"Ad saved to DB"); | |||
| await result; | |||
| } | |||
| public async Task UpdateAsync(int id, AdUpdateDto adUpdateDto) | |||
| { | |||
| _logger.LogInformation($"Start searching Ad with id = {id}"); | |||
| var ad = await _context.Ads.FindAsync(id); | |||
| if (ad is null) | |||
| { | |||
| _logger.LogError($"Ad with id = {id} not found"); | |||
| throw new EntityNotFoundException("Ad not found"); | |||
| } | |||
| _logger.LogInformation($"Mapping Ad with id = {id}"); | |||
| _mapper.Map(adUpdateDto, ad); | |||
| _logger.LogInformation($"Ad with id = {id} mapped successfully"); | |||
| _context.Entry(ad).State = EntityState.Modified; | |||
| var result = _context.SaveChangesAsync(); | |||
| _logger.LogInformation($"Ad saved to DB"); | |||
| await result; | |||
| } | |||
| public async Task ArchiveAdAsync(int id) | |||
| { | |||
| _logger.LogInformation($"Start searching Ad with id = {id}"); | |||
| var ad = await _context.Ads.FindAsync(id); | |||
| if (ad is null) | |||
| { | |||
| _logger.LogError($"Ad with id = {id} not found"); | |||
| throw new EntityNotFoundException("Ad not found"); | |||
| } | |||
| _logger.LogInformation($"Change ad expired time"); | |||
| ad.ExpiredAt = DateTime.Now; | |||
| _logger.LogInformation($"Ad expired time changed successfully"); | |||
| _context.Entry(ad).State = EntityState.Modified; | |||
| var result = _context.SaveChangesAsync(); | |||
| _logger.LogInformation($"Ad saved to DB"); | |||
| await result; | |||
| } | |||
| public async Task DeleteAsync(int id) | |||
| { | |||
| _logger.LogInformation($"Start searching Ad with id = {id}"); | |||
| var ad = await _context.Ads.FindAsync(id); | |||
| if (ad is null) | |||
| { | |||
| _logger.LogError($"Ad with id = {id} not found"); | |||
| throw new EntityNotFoundException("Ad not found"); | |||
| } | |||
| _context.Ads.Remove(ad); | |||
| var result = _context.SaveChangesAsync(); | |||
| _logger.LogInformation($"Ad saved to DB"); | |||
| await result; | |||
| } | |||
| public async Task<Ad> ImportAsync(AdCreateDto adCreateDto) | |||
| { | |||
| _logger.LogInformation($"Start importing Ad"); | |||
| var ad = _mapper.Map<Ad>(adCreateDto); | |||
| _logger.LogInformation($"Ad imported successfully"); | |||
| await _context.Ads.AddAsync(ad); | |||
| _logger.LogInformation($"Saving Ad to db..."); | |||
| await _context.SaveChangesAsync(); | |||
| _logger.LogInformation($"Ad saved to DB"); | |||
| return ad; | |||
| } | |||
| public async Task<Ad> GetByIdEntityAsync(int id) | |||
| { | |||
| _logger.LogInformation($"Start searching Ad with id = {id}"); | |||
| var ad = await _context.Ads.FindAsync(id); | |||
| if (ad is null) | |||
| { | |||
| _logger.LogError($"Ad with id = {id} not found"); | |||
| throw new EntityNotFoundException("Ad not found"); | |||
| } | |||
| return ad; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,287 @@ | |||
| using static Diligent.WebAPI.Data.Entities.Applicant; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| public class ApplicantService : IApplicantService | |||
| { | |||
| private readonly DatabaseContext _context; | |||
| private readonly IMapper _mapper; | |||
| private readonly ILogger<ApplicantService> _logger; | |||
| private readonly IUserService _userService; | |||
| private readonly IFileService _fileService; | |||
| private readonly IAdService _adService; | |||
| private readonly ITechnologyService _technologyService; | |||
| public ApplicantService(DatabaseContext context, IMapper mapper, ILogger<ApplicantService> logger, | |||
| IUserService userService,IFileService fileService,IAdService adService,ITechnologyService technologyService) | |||
| { | |||
| _context = context; | |||
| _mapper = mapper; | |||
| _logger = logger; | |||
| _userService = userService; | |||
| _fileService = fileService; | |||
| _adService = adService; | |||
| _technologyService = technologyService; | |||
| } | |||
| public async Task<QueryResultDto<ApplicantViewDto>> GetFilteredApplicants(ApplicantFilterDto applicantFilterDto) | |||
| { | |||
| _logger.LogInformation("Start getting filtered applicants"); | |||
| _logger.LogInformation("Getting data from DB and filter"); | |||
| var filteredApplicants = (await _context.Applicants | |||
| .Include(c => c.Ads) | |||
| .Include(x => x.TechnologyApplicants) | |||
| .ThenInclude(x => x.Technology).ToListAsync()) | |||
| .FilterApplicants(applicantFilterDto); | |||
| int totalNumberOfItems = filteredApplicants.Count; | |||
| _logger.LogInformation($"Got {totalNumberOfItems} applicants"); | |||
| filteredApplicants = PaginationExtension.ApplyPagging(filteredApplicants, new Pagination | |||
| { | |||
| CurrentPage = applicantFilterDto.CurrentPage, | |||
| PageSize = applicantFilterDto.PageSize | |||
| }); | |||
| _logger.LogInformation($"Return list of applicants"); | |||
| return new QueryResultDto<ApplicantViewDto> | |||
| { | |||
| Items = _mapper.Map<List<ApplicantViewDto>>(filteredApplicants), | |||
| Total = totalNumberOfItems | |||
| }; | |||
| } | |||
| public async Task<ApplicantViewDto> GetById(int id) | |||
| { | |||
| _logger.LogInformation($"Start searching Applicant with id = {id}"); | |||
| var applicant = await _context.Applicants | |||
| .Include(x => x.Ads) | |||
| .ThenInclude(x => x.Technologies) | |||
| .Include(x => x.TechnologyApplicants) | |||
| .ThenInclude(x => x.Technology) | |||
| .Include(x => x.Comments) | |||
| .ThenInclude(t => t.User) | |||
| .FirstOrDefaultAsync(x => x.ApplicantId == id); | |||
| if (applicant is null) | |||
| { | |||
| _logger.LogError($"Applicant with id = {id} not found"); | |||
| throw new EntityNotFoundException("Applicant not found"); | |||
| } | |||
| _logger.LogInformation($"Mapping Applicant with id = {id}"); | |||
| var result = _mapper.Map<ApplicantViewDto>(applicant); | |||
| result.CV = await _fileService.GetCV("638077305621281656.pdf"); | |||
| _logger.LogInformation($"Applicant with id = {id} mapped successfully"); | |||
| return result; | |||
| } | |||
| public async Task<ApplicantViewDto> GetApplicantWithSelectionProcessesById(int id) | |||
| { | |||
| _logger.LogInformation($"Start searching Applicant with id = {id}"); | |||
| var applicant = await _context.Applicants | |||
| .Include(a => a.SelectionProcesses).ThenInclude(sp => sp.SelectionLevel) | |||
| .Include(a => a.SelectionProcesses).ThenInclude(sp => sp.Scheduler) | |||
| .FirstOrDefaultAsync(a => a.ApplicantId == id); | |||
| if (applicant is null) | |||
| { | |||
| _logger.LogError($"Applicant with id = {id} not found"); | |||
| throw new EntityNotFoundException("Applicant not found"); | |||
| } | |||
| _logger.LogInformation($"Applicant with id = {id} mapped successfully"); | |||
| return _mapper.Map<ApplicantViewDto>(applicant); | |||
| } | |||
| public async Task DeleteApplicant(int id) | |||
| { | |||
| _logger.LogInformation($"Start searching Applicant with id = {id}"); | |||
| var applicant = await _context.Applicants.FindAsync(id); | |||
| if (applicant is null) | |||
| { | |||
| _logger.LogError($"Applicant with id = {id} not found"); | |||
| throw new EntityNotFoundException("Applicant not found"); | |||
| } | |||
| _logger.LogInformation($"Removing Applicant with id = {id}"); | |||
| _context.Applicants.Remove(applicant); | |||
| var result = _context.SaveChangesAsync(); | |||
| _logger.LogInformation($"Applicant with id = {id} is removed successfully"); | |||
| await result; | |||
| } | |||
| public async Task<List<AdApplicantsViewDto>> GetAllAdsApplicants(ApplicantFilterDto applicantFilterDto) | |||
| { | |||
| _logger.LogInformation("Start getting filtered applicants"); | |||
| _logger.LogInformation("Getting data from DB and filter"); | |||
| var adsApplicants = (await _context.Ads | |||
| .Include(a => a.Applicants) | |||
| .ThenInclude(a => a.TechnologyApplicants) | |||
| .ThenInclude(a => a.Technology) | |||
| .ToListAsync()) | |||
| .FilterAdApplicants(applicantFilterDto); | |||
| _logger.LogInformation($"Got {adsApplicants.Count} ads"); | |||
| _logger.LogInformation("Mapping received Ads to AdApplicantsViewDto"); | |||
| var result = _mapper.Map<List<AdApplicantsViewDto>>(adsApplicants); | |||
| _logger.LogInformation($"Ads mapped successfully"); | |||
| return result; | |||
| } | |||
| public async Task ApplyForAd(ApplyForAdRequestDto request) | |||
| { | |||
| string fileName = string.Format(@"{0}.pdf", DateTime.Now.Ticks); | |||
| _logger.LogInformation($"Start uploading CV of applicant on Azure Blob storage"); | |||
| await _fileService.UploadCV(fileName, request.PdfFile); | |||
| _logger.LogInformation($"CV uploaded on Azure Blob storage"); | |||
| _logger.LogInformation("Start applying for ad"); | |||
| _logger.LogInformation("Find ad by id"); | |||
| var ad = await _adService.GetByIdEntityAsync(request.AdId); | |||
| if (ad == null) | |||
| { | |||
| _logger.LogError($"Ad with {request.AdId} not found"); | |||
| throw new EntityNotFoundException("Ad not found in database"); | |||
| } | |||
| _logger.LogInformation($"Find sent technologies from FE in database"); | |||
| //var technologies = await _context.Technologies.Where(x => request.TechnologiesIds.Contains(x.TechnologyId)).ToListAsync(); | |||
| var technologies = await _technologyService.GetEntitiesAsync(request.TechnologiesIds); | |||
| _logger.LogInformation($"Create applicant instance with sent data"); | |||
| Applicant applicant = new() | |||
| { | |||
| FirstName = request.FirstName, | |||
| LastName = request.LastName, | |||
| Position = ad.Title, | |||
| DateOfApplication = DateTime.Now, | |||
| CV = fileName, | |||
| Email = request.Email, | |||
| PhoneNumber = request.PhoneNumber, | |||
| GithubLink = request.GithubLink, | |||
| LinkedlnLink = request.LinkedinLink, | |||
| BitBucketLink = request.BitBucketLink, | |||
| Experience = request.Experience, | |||
| //TypeOfEmployment = (EmploymentTypes)Enum.Parse(typeof(EmploymentTypes), ad.EmploymentType, true), | |||
| TypeOfEmployment = ad.EmploymentType == EmploymentTypes.Intership ? TypesOfEmployment.Intership : TypesOfEmployment.Posao, | |||
| Comments = new(), | |||
| Ads = new List<Ad> { ad }, | |||
| SelectionProcesses = new(), | |||
| TechnologyApplicants = new(), | |||
| ApplicationChannel = "Putem sajta", | |||
| Gender = request.Gender == "Muski" ? Genders.M : Genders.Z, | |||
| ProfessionalQualification = request.ProfessionalQualification | |||
| }; | |||
| _logger.LogInformation($"Saving applicant in database"); | |||
| await _context.Applicants.AddAsync(applicant); | |||
| var res = await _context.SaveChangesAsync(); | |||
| _logger.LogInformation($"Applicant saved in database"); | |||
| _logger.LogInformation($"Saving TechnologyApplicants in database"); | |||
| for (int i = 0; i < technologies.Count; i++) | |||
| { | |||
| await _context.ApplicantTechnologies.AddAsync(new TechnologyApplicant { Applicant = applicant, ApplicantId = applicant.ApplicantId, Technology = technologies[i], TechnologyId = technologies[i].TechnologyId }); | |||
| } | |||
| await _context.SaveChangesAsync(); | |||
| _logger.LogInformation($"TechnologyApplicants saved in database"); | |||
| } | |||
| public async Task ImportApplicant(List<ApplicantImportDto> requests) | |||
| { | |||
| _logger.LogInformation($"Create applicant instance with sent data"); | |||
| var res = new List<Applicant>(); | |||
| _logger.LogInformation($"Get first user from database"); | |||
| var user = await _userService.GetFirst(); | |||
| _logger.LogInformation($"User succesufully fetched from database"); | |||
| foreach (var request in requests) { | |||
| Applicant applicant = new Applicant | |||
| { | |||
| FirstName = request.FirstName ?? "", | |||
| LastName = request.LastName ?? "", | |||
| CV = request.CV ?? "", | |||
| Email = request.Email ?? "", | |||
| PhoneNumber = request.PhoneNumber ?? "", | |||
| GithubLink = request.GithubLink ?? "", | |||
| LinkedlnLink = request.LinkedlnLink ?? "", | |||
| BitBucketLink = request.BitBucketLink ?? "", | |||
| Position = "", | |||
| DateOfApplication = request.DateOfApplication, | |||
| TypeOfEmployment = request.TypeOfEmployment == "Praksa" ? TypesOfEmployment.Intership : TypesOfEmployment.Posao, | |||
| Experience = request.Experience, | |||
| ApplicationChannel = request.ApplicationChannel ?? "Putem sajta", | |||
| // MORA DA SE UVEDE KO JE DAO KOMENTAR DA LI DA STAVIMO DA JE DANIJELA SVIMA STAVILA KOMENTARE ILI ?? | |||
| Comments = new List<Comment> | |||
| { | |||
| new Comment | |||
| { | |||
| User = user, | |||
| Content = request.Comment | |||
| } | |||
| }, | |||
| Ads = new List<Ad> { request.Ad }, | |||
| SelectionProcesses = new(), | |||
| TechnologyApplicants = new(), | |||
| Gender = Genders.M, | |||
| ProfessionalQualification = "Elektrotehnicki fakultet", | |||
| }; | |||
| res.Add(applicant); | |||
| } | |||
| _logger.LogInformation($"Saving applicants in database"); | |||
| await _context.AddRangeAsync(res); | |||
| await _context.SaveChangesAsync(); | |||
| _logger.LogInformation($"Applicants saved in database"); | |||
| } | |||
| public async Task<List<ApplicantOptionsDTO>> GetOptions() | |||
| { | |||
| _logger.LogInformation($"Start getting all applicants from database"); | |||
| var res = await _context.Applicants.ToListAsync(); | |||
| _logger.LogInformation($"Got {res.Count} applicants"); | |||
| return _mapper.Map<List<ApplicantOptionsDTO>>(res); | |||
| } | |||
| public async Task<ServiceResponseDTO<object>> InitializeProcess(ApplicantProcessRequestDTO model) | |||
| { | |||
| _logger.LogInformation($"Start searching Applicant with id = {model.ApplicantId}"); | |||
| var applicant = await _context.Applicants.Include(n => n.SelectionProcesses).Where(n=> n.ApplicantId ==model.ApplicantId).FirstOrDefaultAsync(); | |||
| if (applicant == null) | |||
| { | |||
| _logger.LogError($"Applicant with id = {model.ApplicantId} not found"); | |||
| return new ServiceResponseDTO<object> | |||
| { | |||
| IsError = true, | |||
| ErrorMessage = "Applicant does not exist." | |||
| }; | |||
| } | |||
| applicant.SelectionProcesses.Add(new SelectionProcess | |||
| { | |||
| Name = StringGenerator.GenerateRandomPassword(), | |||
| SchedulerId = model.SchedulerId, | |||
| SelectionLevelId = 1, | |||
| Status = model.Appointment != null ? "Zakazan" : "Čeka na zakazivanje", | |||
| Date = model.Appointment | |||
| }); | |||
| _logger.LogInformation($"Saving selection processes in database"); | |||
| await _context.SaveChangesAsync(); | |||
| _logger.LogInformation($"Selecetion processes saved in database"); | |||
| return new ServiceResponseDTO<object> | |||
| { | |||
| Data = true | |||
| }; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,464 @@ | |||
| using Microsoft.AspNetCore.WebUtilities; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| public class AuthenticationService : IAuthenticationService | |||
| { | |||
| private readonly AuthorizationSettings _authSettings; | |||
| private readonly FrontEndSettings _frontEndSettings; | |||
| private readonly UserManager<User> _userManager; | |||
| private readonly DatabaseContext _databaseContext; | |||
| private readonly IEmailer _emailer; | |||
| private readonly IMapper _mapper; | |||
| private readonly ILogger<AuthenticationService> _logger; | |||
| private readonly IHttpClientService _httpClient; | |||
| public AuthenticationService(IOptions<AuthorizationSettings> authSettings, | |||
| IOptions<FrontEndSettings> frontEndSettings, | |||
| UserManager<User> userManager, | |||
| DatabaseContext databaseContext, | |||
| IEmailer emailer, | |||
| ILogger<AuthenticationService> logger, | |||
| IHttpClientService httpClient, | |||
| IMapper mapper) | |||
| { | |||
| _authSettings = authSettings.Value; | |||
| _frontEndSettings = frontEndSettings.Value; | |||
| _userManager = userManager; | |||
| _databaseContext = databaseContext; | |||
| _httpClient = httpClient; | |||
| _emailer = emailer; | |||
| _logger = logger; | |||
| _mapper = mapper; | |||
| } | |||
| public async Task<ServiceResponseDTO<AuthenticateResponseDto>> Authenticate(AuthenticateRequestDto model) | |||
| { | |||
| _logger.LogInformation($"Checking credentials for user: {model.Username}"); | |||
| var user = await _userManager.FindByNameAsync(model.Username); | |||
| // return null if user not found | |||
| if (user == null) | |||
| { | |||
| _logger.LogError($"User with username = {model.Username} not found"); | |||
| return new ServiceResponseDTO<AuthenticateResponseDto> | |||
| { | |||
| IsError = true, | |||
| ErrorMessage = "Username is not valid" | |||
| }; | |||
| } | |||
| var result = await _userManager.CheckPasswordAsync(user, model.Password); | |||
| // return null if user is disabled | |||
| if (user.IsEnabled == false) | |||
| { | |||
| _logger.LogError($"User: {model.Username} is not enabled"); | |||
| return new ServiceResponseDTO<AuthenticateResponseDto> | |||
| { | |||
| IsError = true, | |||
| ErrorMessage = $"User with email {model.Username} has no permission to log in." | |||
| }; | |||
| } | |||
| // password is not correct | |||
| if (!result) | |||
| { | |||
| _logger.LogError($"Password for user: {model.Username} is not correct"); | |||
| await _userManager.AccessFailedAsync(user); | |||
| return new ServiceResponseDTO<AuthenticateResponseDto> | |||
| { | |||
| IsError = true, | |||
| ErrorMessage = "Password is not correct" | |||
| }; | |||
| } | |||
| var token = await GenerateToken(user); | |||
| _logger.LogInformation($"Successfull login token: {token}"); | |||
| return token; | |||
| } | |||
| public async Task<ServiceResponseDTO<AuthenticateResponseDto>> Authenticate(GoogleApiModel model) | |||
| { | |||
| _logger.LogInformation($"Checking token for google login {model.Token}"); | |||
| if (!(await _httpClient.IsTokenValid(model.Token))) | |||
| { | |||
| _logger.LogError($"Token is not valid"); | |||
| return new ServiceResponseDTO<AuthenticateResponseDto> | |||
| { | |||
| IsError = true, | |||
| ErrorMessage = "Invalid Google Api Token" | |||
| }; | |||
| } | |||
| _logger.LogInformation($"Checking if user exists in Db with email : {model.User.email}"); | |||
| var user = await _userManager.FindByEmailAsync(model.User.email); | |||
| // return null if user not found | |||
| if (user == null) | |||
| { | |||
| _logger.LogError($"User does not exist in Db"); | |||
| return new ServiceResponseDTO<AuthenticateResponseDto> | |||
| { | |||
| IsError = true, | |||
| ErrorMessage = $"User with email {model.User.email} does not exist in database" | |||
| }; | |||
| } | |||
| if (user.IsEnabled == false) | |||
| { | |||
| _logger.LogError($"User is not enabled"); | |||
| return new ServiceResponseDTO<AuthenticateResponseDto> | |||
| { | |||
| IsError = true, | |||
| ErrorMessage = $"User with email {model.User.email} has no permission to log in." | |||
| }; | |||
| } | |||
| var token = await GenerateToken(user); | |||
| _logger.LogInformation($"Successfull login. Token :{token}"); | |||
| return token; | |||
| } | |||
| private async Task<ServiceResponseDTO<AuthenticateResponseDto>> GenerateToken(User user) | |||
| { | |||
| var isLocked = await _userManager.IsLockedOutAsync(user); | |||
| if (isLocked) | |||
| return new ServiceResponseDTO<AuthenticateResponseDto> | |||
| { | |||
| IsError = true, | |||
| ErrorMessage = "The account is locked out" | |||
| }; | |||
| // authentication successful so generate jwt token | |||
| var token = await GenerateJwtToken(user, true); | |||
| var data = new AuthenticateResponseDto | |||
| { | |||
| Id = user.Id, | |||
| Username = user.UserName, | |||
| FirstName = user.FirstName, | |||
| LastName = user.LastName, | |||
| Token = token, | |||
| RefreshToken = token | |||
| }; | |||
| return new ServiceResponseDTO<AuthenticateResponseDto> | |||
| { | |||
| Data = data | |||
| }; | |||
| } | |||
| private async Task<string> GenerateJwtToken(User user, bool authenticate = false) | |||
| { | |||
| // generate token that is valid for 7 days | |||
| var tokenHandler = new JwtSecurityTokenHandler(); | |||
| var key = Encoding.ASCII.GetBytes(_authSettings.Secret); | |||
| var tokenDescriptor = new SecurityTokenDescriptor | |||
| { | |||
| Subject = new ClaimsIdentity(new[] { | |||
| new Claim(JwtRegisteredClaimNames.Jti, user.Id.ToString()), | |||
| new Claim("id", user.Id.ToString()) | |||
| }), | |||
| Expires = DateTime.Now.AddMinutes(_authSettings.JwtExpiredTime), | |||
| SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) | |||
| }; | |||
| var token = tokenHandler.CreateToken(tokenDescriptor); | |||
| var writedToken = tokenHandler.WriteToken(token); | |||
| var refreshToken = new RefreshToken | |||
| { | |||
| Token = writedToken, | |||
| JwtId = user.Id.ToString(), | |||
| UserId = user.Id, | |||
| User = user, | |||
| CreationDate = DateTime.Now, | |||
| ExpiryDate = DateTime.Now.AddMinutes(_authSettings.JwtRefreshExpiredTime) | |||
| }; | |||
| var existRefreshToken = await _databaseContext.RefreshTokens.Where(x => x.UserId == user.Id).FirstOrDefaultAsync(); | |||
| if (existRefreshToken != null) | |||
| { | |||
| existRefreshToken.Token = writedToken; | |||
| existRefreshToken.JwtId = token.Id; | |||
| existRefreshToken.CreationDate = DateTime.Now; | |||
| existRefreshToken.ExpiryDate = DateTime.Now.AddMinutes(_authSettings.JwtRefreshExpiredTime); | |||
| if (authenticate) | |||
| { | |||
| existRefreshToken.Used = false; | |||
| existRefreshToken.Invalidated = false; | |||
| } | |||
| _databaseContext.RefreshTokens.Update(existRefreshToken); | |||
| await UpdateRefreshToken(existRefreshToken); | |||
| } | |||
| else | |||
| { | |||
| await _databaseContext.RefreshTokens.AddAsync(refreshToken); | |||
| } | |||
| await _databaseContext.SaveChangesAsync(); | |||
| _logger.LogInformation($"JWTToken : {writedToken}"); | |||
| return writedToken; | |||
| } | |||
| public async Task<RefreshTokenResultDto> RefreshTokenAsync(RefreshTokenRequestDto model) | |||
| { | |||
| var validatedToken = GetPrincipalFromToken(model.Token, false); | |||
| if (validatedToken == null) | |||
| { | |||
| return new RefreshTokenResultDto { Error = "Invalid token" }; | |||
| } | |||
| var jti = validatedToken.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Jti).Value; | |||
| var storedRefreshToken = await _databaseContext.RefreshTokens.SingleOrDefaultAsync(x => x.JwtId == jti); | |||
| if (storedRefreshToken == null) | |||
| { | |||
| return new RefreshTokenResultDto { Error = "This refresh token does not exist" }; | |||
| } | |||
| var userk = await _databaseContext.Users.Where(u => u.Id == storedRefreshToken.UserId).FirstOrDefaultAsync(); | |||
| if (userk == null) | |||
| { | |||
| return new RefreshTokenResultDto { Error = "There is no user which is associated with refresh token" }; | |||
| } | |||
| var expiryDateUnix = long.Parse(validatedToken.Claims.Single(x => x.Type == JwtRegisteredClaimNames.Exp).Value); | |||
| var expiryDateTimeUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) | |||
| .AddSeconds(expiryDateUnix); | |||
| if (expiryDateTimeUtc > DateTime.UtcNow) | |||
| { | |||
| return new RefreshTokenResultDto | |||
| { | |||
| Data = new AuthenticateResponseDto | |||
| { | |||
| Id = userk.Id, | |||
| FirstName = userk.FirstName, | |||
| LastName = userk.LastName, | |||
| Username = userk.UserName, | |||
| Token = model.Token, | |||
| RefreshToken = model.RefreshToken | |||
| } | |||
| }; | |||
| } | |||
| if (DateTime.Now > storedRefreshToken.ExpiryDate) | |||
| { | |||
| return new RefreshTokenResultDto { Error = "This refresh token has expired" }; | |||
| } | |||
| if (storedRefreshToken.Invalidated) | |||
| { | |||
| return new RefreshTokenResultDto { Error = "This refresh token has been invalidated" }; | |||
| } | |||
| if (storedRefreshToken.JwtId != jti) | |||
| { | |||
| return new RefreshTokenResultDto { Error = "This refresh token does not match this JWT" }; | |||
| } | |||
| storedRefreshToken.ExpiryDate = DateTime.Now.AddMinutes(_authSettings.JwtRefreshExpiredTime); | |||
| await _databaseContext.SaveChangesAsync(); | |||
| var user = await _userManager.FindByIdAsync(validatedToken.Claims.Single(x => x.Type == "id").Value); | |||
| var token = await GenerateJwtToken(user); | |||
| _logger.LogInformation($"Refresh token : {model.Token}"); | |||
| return new RefreshTokenResultDto | |||
| { | |||
| Data = new AuthenticateResponseDto | |||
| { | |||
| Id = userk.Id, | |||
| FirstName = userk.FirstName, | |||
| LastName = userk.LastName, | |||
| Username = userk.UserName, | |||
| Token = token, | |||
| RefreshToken = token | |||
| } | |||
| }; | |||
| } | |||
| public async Task<ServiceResponseDTO<string>> DeleteRefreshToken(int userId) | |||
| { | |||
| var refreshToken = await _databaseContext.RefreshTokens.Where(r => r.UserId == userId).FirstOrDefaultAsync(); | |||
| if (refreshToken is null) | |||
| return new ServiceResponseDTO<string> | |||
| { | |||
| IsError = true, | |||
| ErrorMessage = "There is no refresh token for user" | |||
| }; | |||
| _databaseContext.RefreshTokens.Remove(refreshToken); | |||
| var result = await _databaseContext.SaveChangesAsync() > 0; | |||
| if (!result) | |||
| return new ServiceResponseDTO<string> | |||
| { | |||
| IsError = true, | |||
| ErrorMessage = "Problem with saving changes into database" | |||
| }; | |||
| _logger.LogInformation($"Delted refresh token : {refreshToken}"); | |||
| return new ServiceResponseDTO<string> | |||
| { | |||
| Data = null | |||
| }; | |||
| } | |||
| private ClaimsPrincipal? GetPrincipalFromToken(string token, bool validateLifetime) | |||
| { | |||
| var tokenHandler = new JwtSecurityTokenHandler(); | |||
| var key = Encoding.ASCII.GetBytes(_authSettings.Secret); | |||
| var tokenValidationParameters = new TokenValidationParameters | |||
| { | |||
| ValidateIssuerSigningKey = true, | |||
| IssuerSigningKey = new SymmetricSecurityKey(key), | |||
| ValidateIssuer = false, | |||
| ValidateAudience = false, | |||
| RequireExpirationTime = false, | |||
| ValidateLifetime = validateLifetime, | |||
| // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later) | |||
| //ClockSkew = TimeSpan.Zero | |||
| }; | |||
| try | |||
| { | |||
| var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var validatedToken); | |||
| if (!IsJwtWithValidSecurityAlgorithm(validatedToken)) | |||
| { | |||
| return null; | |||
| } | |||
| return principal; | |||
| } | |||
| catch (Exception) | |||
| { | |||
| return null; | |||
| } | |||
| } | |||
| private static bool IsJwtWithValidSecurityAlgorithm(SecurityToken validatedToken) | |||
| { | |||
| return (validatedToken is JwtSecurityToken jwtSecurityToken) && | |||
| jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, | |||
| StringComparison.InvariantCultureIgnoreCase); | |||
| } | |||
| public async Task<RefreshToken?> GetRefreshTokenByUserId(int userId) | |||
| { | |||
| return await _databaseContext.RefreshTokens.Where(x => x.UserId == userId).FirstOrDefaultAsync(); | |||
| } | |||
| public async Task UpdateRefreshToken(RefreshToken refreshToken) | |||
| { | |||
| _databaseContext.RefreshTokens.Update(refreshToken); | |||
| await _databaseContext.SaveChangesAsync(); | |||
| } | |||
| public async Task<ServiceResponseDTO<object>> GetForgotPasswordUrlAsync(string email) | |||
| { | |||
| _logger.LogInformation($"Sending forgot password email for : {email}"); | |||
| var user = await _userManager.FindByEmailAsync(email); | |||
| if (user == null) | |||
| { | |||
| return new ServiceResponseDTO<object> | |||
| { | |||
| IsError = true, | |||
| ErrorMessage = "Email did not find." | |||
| }; | |||
| } | |||
| var token = await _userManager.GeneratePasswordResetTokenAsync(user); | |||
| token = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(token)); | |||
| await _emailer.SendEmailAndWriteToDbAsync(email, "Reset password", HTMLHelper.RenderForgotPasswordPage($"{_frontEndSettings.BaseUrl}/reset-password?token={token}&email={email}"), isHtml: true); | |||
| _logger.LogInformation($"Reset password email is sent"); | |||
| return new ServiceResponseDTO<object> | |||
| { | |||
| Data = new { code = token, email = email } | |||
| }; | |||
| } | |||
| public async Task<ServiceResponseDTO<object>> PasswordResetAsync(string email, string code, string password) | |||
| { | |||
| _logger.LogInformation($"User with email : {email} changes password"); | |||
| var user = await _userManager.FindByEmailAsync(email); | |||
| if (user == null) | |||
| { | |||
| return new ServiceResponseDTO<object> | |||
| { | |||
| IsError = true, | |||
| ErrorMessage = "Email did not find." | |||
| }; | |||
| } | |||
| var passwordResetToken = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code)); | |||
| IdentityResult resetResult = await _userManager.ResetPasswordAsync(user, passwordResetToken, password); | |||
| if (resetResult.Succeeded) | |||
| { | |||
| _logger.LogInformation($"Password for user : {email} changed successfully"); | |||
| return new ServiceResponseDTO<object> { Data = true }; | |||
| } | |||
| var errors = resetResult.Errors.Select(x => x.Description); | |||
| return new ServiceResponseDTO<object> | |||
| { | |||
| IsError = true, | |||
| ErrorMessage = errors.First() | |||
| }; | |||
| } | |||
| public async Task<ServiceResponseDTO<object>> Register(RegisterDTO model) | |||
| { | |||
| _logger.LogInformation($"User with email: {model.Email} is going to register."); | |||
| var user = await _userManager.FindByEmailAsync(model.Email); | |||
| if (user == null) | |||
| { | |||
| _logger.LogInformation($"User with email: {model.Email} not found."); | |||
| return new ServiceResponseDTO<object> | |||
| { | |||
| IsError = true, | |||
| ErrorMessage = "User not invited." | |||
| }; | |||
| } | |||
| _logger.LogInformation($"Found user: {user.FirstName} {user.LastName}"); | |||
| _mapper.Map<RegisterDTO, User>(model, user); | |||
| _logger.LogInformation($"Enabled login for user: {user.FirstName} {user.LastName}"); | |||
| user.IsEnabled = true; | |||
| IdentityResult resetResult = await _userManager.ResetPasswordAsync(user, HttpUtility.UrlDecode(model.Token), model.Password); | |||
| if (resetResult.Succeeded) | |||
| { | |||
| _logger.LogInformation($"Succesfuly registered user: {user.FirstName} {user.LastName}"); | |||
| await _databaseContext.SaveChangesAsync(); | |||
| //_logger.LogInformation($"Password for user : {model.Email} changed successfully"); | |||
| return new ServiceResponseDTO<object> { Data = true }; | |||
| } | |||
| var errors = resetResult.Errors.Select(x => x.Description); | |||
| return new ServiceResponseDTO<object> | |||
| { | |||
| IsError = true, | |||
| ErrorMessage = errors.First() | |||
| }; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,42 @@ | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| public class CommentService : ICommentService | |||
| { | |||
| private readonly FrontEndSettings _frontEndSettings; | |||
| private readonly DatabaseContext _context; | |||
| private readonly IMapper _mapper; | |||
| private readonly ILogger<CommentService> _logger; | |||
| private readonly IEmailer _emailer; | |||
| public CommentService(IOptions<FrontEndSettings> frontEndSettings,DatabaseContext context, IMapper mapper, ILogger<CommentService> logger,IEmailer emailer) | |||
| { | |||
| _frontEndSettings = frontEndSettings.Value; | |||
| _context = context; | |||
| _mapper = mapper; | |||
| _logger = logger; | |||
| _emailer = emailer; | |||
| } | |||
| public async Task CreateComment(CommentCreateDto commentCreateDto) | |||
| { | |||
| _logger.LogInformation("Start creating comment"); | |||
| var comment = _mapper.Map<Comment>(commentCreateDto); | |||
| if(commentCreateDto.UsersToNotify.Count > 0) | |||
| { | |||
| _logger.LogInformation("Start sending emails"); | |||
| await _emailer.SendEmailAsync(commentCreateDto.UsersToNotify, "You're tagged in comment by another user", | |||
| HTMLHelper.RenderTagPage($"{_frontEndSettings.BaseUrl}/candidates/{commentCreateDto.ApplicantId}"), isHtml: true); | |||
| _logger.LogInformation("Emails send successfully"); | |||
| } | |||
| comment.DateOfSending = DateTime.Now; | |||
| _logger.LogInformation($"Comment created successfully in {comment.DateOfSending}"); | |||
| _logger.LogInformation($"Saving comment in Db"); | |||
| await _context.Comments.AddAsync(comment); | |||
| var result = _context.SaveChangesAsync(); | |||
| _logger.LogInformation($"Comment saved in Db"); | |||
| await result; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,389 @@ | |||
| using System.Net.Mail; | |||
| using System.Net; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| /// <summary> | |||
| /// Provieds an API for sending emails in both sync & async fashion, as well as sending emails with delays. | |||
| /// </summary> | |||
| public class Emailer : IEmailer | |||
| { | |||
| private readonly MailSettings _settings; | |||
| private readonly ILogger<Emailer> _logger; | |||
| public Emailer(IOptions<MailSettings> mailSettings, ILogger<Emailer> logger) | |||
| { | |||
| _settings = mailSettings.Value; | |||
| _logger = logger; | |||
| } | |||
| /// <summary> | |||
| /// Sends an email asynchronously and inserts a new <see cref="DiligEmail"/> record to the underlying database. | |||
| /// </summary> | |||
| /// <param name="to"></param> | |||
| /// <param name="subject"></param> | |||
| /// <param name="body"></param> | |||
| /// <param name="isHtml"></param> | |||
| /// <param name="attachments"></param> | |||
| /// <param name="cc"></param> | |||
| /// <exception cref="ArgumentException"></exception> | |||
| /// <see cref="ArgumentNullException"/> | |||
| /// <exception cref="Exception"></exception> | |||
| /// <returns></returns> | |||
| public async Task<bool> SendEmailAndWriteToDbAsync(List<string> to, string subject, string body, bool isHtml = false, List<string> cc = null) | |||
| { | |||
| _logger.LogInformation($"Start sending email to {to}"); | |||
| var emailResult = await SendEmailAsync(to, subject, body, isHtml, cc); | |||
| //_logger.LogInformation("Create email entity for save in db"); | |||
| var email = CreateEmail(to, subject, body, isHtml); | |||
| if (emailResult) | |||
| { | |||
| email.SentTime = DateTime.Now; | |||
| } | |||
| //_logger.LogInformation("Save email in db"); | |||
| //var dbResult = await WriteEmailToDbAsync(email); | |||
| //_logger.LogInformation("Email is saved in db."); | |||
| return emailResult; // && dbResult > 0; | |||
| } | |||
| public async Task<bool> SendEmailAndWriteToDbAsync(string to, string subject, string body, bool isHtml = false, List<string> cc = null) | |||
| { | |||
| return await SendEmailAndWriteToDbAsync(new List<string> { to }, subject, body, isHtml, cc); | |||
| } | |||
| /// <summary> | |||
| /// Sends an email synchronously and inserts a new <see cref="DiligEmail"/> record to the underlying database. | |||
| /// </summary> | |||
| /// <param name="to"></param> | |||
| /// <param name="subject"></param> | |||
| /// <param name="body"></param> | |||
| /// <param name="isHtml"></param> | |||
| /// <param name="attachments"></param> | |||
| /// <param name="cc"></param> | |||
| /// <exception cref="ArgumentException"></exception> | |||
| /// <see cref="ArgumentNullException"/> | |||
| /// <exception cref="Exception"></exception> | |||
| /// <returns></returns> | |||
| public bool SendEmailAndWriteToDb(List<string> to, string subject, string body, bool isHtml = false, | |||
| List<string> cc = null) | |||
| { | |||
| var emailResult = SendEmail(to, subject, body, isHtml, cc); | |||
| var email = CreateEmail(to, subject, body, isHtml); | |||
| if (emailResult) | |||
| { | |||
| email.SentTime = DateTime.Now; | |||
| } | |||
| //var dbResult = WriteEmailToDb(email); | |||
| return emailResult; // && dbResult > 0; | |||
| } | |||
| public bool SendEmailAndWriteToDb(string to, string subject, string body, bool isHtml = false, | |||
| List<string> cc = null) | |||
| { | |||
| return SendEmailAndWriteToDb(new List<string> { to }, subject, body, isHtml, cc); | |||
| } | |||
| /// <summary> | |||
| /// Sends an email synchronously. | |||
| /// </summary> | |||
| /// <param name="to"></param> | |||
| /// <param name="subject"></param> | |||
| /// <param name="body"></param> | |||
| /// <param name="isHtml"></param> | |||
| /// <param name="attachments"></param> | |||
| /// <param name="cc"></param> | |||
| /// <exception cref="ArgumentException"></exception> | |||
| /// <see cref="ArgumentNullException"/> | |||
| /// <exception cref="Exception"></exception> | |||
| /// <returns></returns> | |||
| public bool SendEmail(List<string> to, string subject, string body, bool isHtml = false, | |||
| List<string> cc = null) | |||
| { | |||
| try | |||
| { | |||
| using (var smtp = GetSmtpClient()) | |||
| { | |||
| var message = GetMailMessage(to, subject, body, isHtml, cc); | |||
| smtp.Send(message); | |||
| return true; | |||
| } | |||
| } | |||
| catch (ArgumentException) | |||
| { | |||
| throw; | |||
| } | |||
| catch (Exception e) | |||
| { | |||
| throw new Exception("Failed to send email message.", e); | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// Sends an email asynchronously. | |||
| /// </summary> | |||
| /// <param name="to"></param> | |||
| /// <param name="subject"></param> | |||
| /// <param name="body"></param> | |||
| /// <param name="isHtml"></param> | |||
| /// <param name="attachments"></param> | |||
| /// <param name="cc"></param> | |||
| /// <exception cref="ArgumentException"></exception> | |||
| /// <see cref="ArgumentNullException"/> | |||
| /// <exception cref="Exception"></exception> | |||
| /// <returns></returns> | |||
| public async Task<bool> SendEmailAsync(List<string> to, string subject, string body, bool isHtml = false, List<string> cc = null) | |||
| { | |||
| try | |||
| { | |||
| _logger.LogInformation("Getting SMTP client settings from appsettings file"); | |||
| using (var smtp = GetSmtpClient()) | |||
| { | |||
| _logger.LogInformation("Create mail message"); | |||
| var message = GetMailMessage(to, subject, body, isHtml, cc); | |||
| _logger.LogInformation("Message created."); | |||
| _logger.LogInformation("Sending message to client"); | |||
| await smtp.SendMailAsync(message); | |||
| _logger.LogInformation("Email message sent."); | |||
| return true; | |||
| } | |||
| } | |||
| catch (ArgumentException ex) | |||
| { | |||
| _logger.LogInformation($"Error in arguments {ex}"); | |||
| throw; | |||
| } | |||
| catch (Exception e) | |||
| { | |||
| _logger.LogInformation($"Error {e}"); | |||
| throw new Exception("Failed to send email message.", e); | |||
| } | |||
| } | |||
| /// <summary> | |||
| /// Creates a <see cref="SmtpClient"/> object and configures it. | |||
| /// </summary> | |||
| /// <returns></returns> | |||
| public SmtpClient GetSmtpClient() | |||
| { | |||
| var smtp = new SmtpClient(_settings.SmtpServer) { Timeout = 1000000 }; | |||
| if (!string.IsNullOrWhiteSpace(_settings.SmtpUsername)) | |||
| { | |||
| smtp.UseDefaultCredentials = false; | |||
| smtp.Credentials = new NetworkCredential( | |||
| _settings.SmtpUsername, | |||
| _settings.SmtpPassword); | |||
| smtp.EnableSsl = _settings.SmtpUseSSL; | |||
| smtp.Port = _settings.SmtpPort; | |||
| } | |||
| return smtp; | |||
| } | |||
| /// <summary> | |||
| /// Creates a new <see cref="MailMessage"/> from the specified arguments. | |||
| /// </summary> | |||
| /// <param name="to"></param> | |||
| /// <param name="subject"></param> | |||
| /// <param name="body"></param> | |||
| /// <param name="isHtml"></param> | |||
| /// <param name="attachments"></param> | |||
| /// <param name="cc"></param> | |||
| /// <exception cref="ArgumentException"></exception> | |||
| /// <returns></returns> | |||
| public MailMessage GetMailMessage(List<string> to, string subject, string body, bool isHtml = false, List<string> cc = null) | |||
| { | |||
| var message = new MailMessage | |||
| { | |||
| Sender = new MailAddress(_settings.SmtpFrom, _settings.SmtpFromName), | |||
| From = new MailAddress(_settings.SmtpFrom), | |||
| Subject = subject, | |||
| Body = body, | |||
| IsBodyHtml = isHtml | |||
| }; | |||
| if (to.Any()) | |||
| { | |||
| message.To.Add(string.Join(",", to.Where(email => !string.IsNullOrWhiteSpace(email)))); | |||
| } | |||
| else | |||
| { | |||
| throw new ArgumentException("The list of recipient emails can not be empty"); | |||
| } | |||
| if (cc != null && cc.Any()) | |||
| { | |||
| message.CC.Add(string.Join(",", cc.Where(email => !string.IsNullOrWhiteSpace(email)))); | |||
| } | |||
| return message; | |||
| } | |||
| /// <summary> | |||
| /// Sends an email aysnchronously. If the "dont send before" argument is specified, then the email will be sent with a delay - after the specified time. | |||
| /// </summary> | |||
| /// <param name="to"></param> | |||
| /// <param name="subject"></param> | |||
| /// <param name="body"></param> | |||
| /// <param name="isHtml"></param> | |||
| /// <param name="attachments"></param> | |||
| /// <param name="dontSendBefore"></param> | |||
| /// <exception cref="ArgumentException"></exception> | |||
| /// <exception cref="Exception"></exception> | |||
| /// <returns></returns> | |||
| //public async Task<bool> SendEmailWithDelayAsync(List<string> to, string subject, string body, bool isHtml = false, DateTime? dontSendBefore = null) | |||
| //{ | |||
| // try | |||
| // { | |||
| // var email = CreateEmail(to, subject, body, isHtml, dontSendBefore); | |||
| // //var result = await WriteEmailToDbAsync(email); | |||
| // return true; | |||
| // } | |||
| // catch (ArgumentException) | |||
| // { | |||
| // throw; | |||
| // } | |||
| // catch (Exception e) | |||
| // { | |||
| // throw new Exception("Error while attempting to send an email with delay.", e); | |||
| // } | |||
| //} | |||
| /// <summary> | |||
| /// Creates a <see cref="DiligEmail"/> object with specified arguments. | |||
| /// </summary> | |||
| /// <param name="to"></param> | |||
| /// <param name="subject"></param> | |||
| /// <param name="body"></param> | |||
| /// <param name="isHtml"></param> | |||
| /// <param name="attachments"></param> | |||
| /// <param name="dontSendBefore"></param> | |||
| /// <exception cref="ArgumentException"></exception> | |||
| /// <returns></returns> | |||
| public DiligEmail CreateEmail(List<string> to, string subject, string body, bool isHtml = false, | |||
| DateTime? dontSendBefore = null) | |||
| { | |||
| if (!to.Any()) | |||
| { | |||
| throw new ArgumentException("The list of recipient emails can not be empty"); | |||
| } | |||
| var email = new DiligEmail | |||
| { | |||
| To = to.Aggregate((previous, next) => previous + ";" + next), | |||
| Subject = subject, | |||
| Body = body, | |||
| IsHtml = isHtml, | |||
| DontSendBefore = dontSendBefore, | |||
| CreateTime = DateTime.Now | |||
| }; | |||
| return email; | |||
| } | |||
| /// <summary> | |||
| /// Fills the specified <see cref="DiligEmail"/> object with the specified <see cref="EmailAttachment"/> list. | |||
| /// </summary> | |||
| /// <param name="email"></param> | |||
| /// <param name="attachments"></param> | |||
| /// <exception cref="ArgumentException"></exception> | |||
| /// <returns></returns> | |||
| //public DiligEmail FillEmailAttachments(DiligEmail email) | |||
| //{ | |||
| // if (email == null) | |||
| // { | |||
| // throw new ArgumentNullException(nameof(email), "Email can not be null"); | |||
| // } | |||
| // if (attachments != null && attachments.Any()) | |||
| // { | |||
| // attachments.ForEach(attachment => | |||
| // { | |||
| // email.DiligEmailAttachments.Add(new DiligEmailAttachment | |||
| // { | |||
| // FileName = attachment.FileName, | |||
| // SourceFileName = attachment.SourceFileName, | |||
| // Type = attachment.Type, | |||
| // Disposition = attachment.Disposition | |||
| // }); | |||
| // }); | |||
| // } | |||
| // return email; | |||
| //} | |||
| /// <summary> | |||
| /// Writes the specified <see cref="DiligEmail"/> object to the underlying database. | |||
| /// </summary> | |||
| /// <param name="email"></param> | |||
| /// <exception cref="ArgumentException"></exception> | |||
| /// <exception cref="Exception"></exception> | |||
| /// <returns></returns> | |||
| //public async Task<int> WriteEmailToDbAsync(DiligEmail email) | |||
| //{ | |||
| // try | |||
| // { | |||
| // if (email == null) | |||
| // { | |||
| // throw new ArgumentNullException(nameof(email), "Email can not be null"); | |||
| // } | |||
| // _entities.DiligEmails.Add(email); | |||
| // var result = await _entities.SaveChangesAsync(); | |||
| // return result; | |||
| // } | |||
| // catch (Exception e) | |||
| // { | |||
| // throw new Exception("Failed to write entry into the database", e); | |||
| // } | |||
| //} | |||
| /// <summary> | |||
| /// Writes the specified <see cref="DiligEmail"/> object to the underlying database. | |||
| /// </summary> | |||
| /// <param name="email"></param> | |||
| /// <exception cref="ArgumentNullException"></exception> | |||
| /// <exception cref="Exception"></exception> | |||
| /// <returns></returns> | |||
| //public int WriteEmailToDb(DiligEmail email) | |||
| //{ | |||
| // try | |||
| // { | |||
| // if (email == null) | |||
| // { | |||
| // throw new ArgumentNullException(nameof(email), "Email can not be null"); | |||
| // } | |||
| // _entities.DiligEmails.Add(email); | |||
| // var result = _entities.SaveChanges(); | |||
| // return result; | |||
| // } | |||
| // catch (ArgumentException) | |||
| // { | |||
| // throw; | |||
| // } | |||
| // catch (Exception e) | |||
| // { | |||
| // throw new Exception("Failed to write entry into the database", e); | |||
| // } | |||
| //} | |||
| } | |||
| } | |||
| @@ -0,0 +1,53 @@ | |||
| using Microsoft.AspNetCore.Http; | |||
| using Microsoft.Extensions.Configuration; | |||
| using Microsoft.WindowsAzure.Storage; | |||
| using Microsoft.WindowsAzure.Storage.Blob; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| public class FileService : IFileService | |||
| { | |||
| private readonly IConfiguration _configuration; | |||
| public FileService(IConfiguration configuration) | |||
| { | |||
| _configuration = configuration; | |||
| } | |||
| public async Task<string> GetCV(string fileName) | |||
| { | |||
| await using MemoryStream memoryStream = new(); | |||
| var cloudBlockBlob = GetCloudBlockBlob(fileName); | |||
| await cloudBlockBlob.DownloadToStreamAsync(memoryStream); | |||
| Stream blobStream = cloudBlockBlob.OpenReadAsync().Result; | |||
| return ConvertToBase64(blobStream); | |||
| } | |||
| public async Task UploadCV(string fileName,IFormFile file) | |||
| { | |||
| var cloudBlockBlob = GetCloudBlockBlob(fileName); | |||
| await using var data = file.OpenReadStream(); | |||
| await cloudBlockBlob.UploadFromStreamAsync(data); | |||
| } | |||
| private CloudBlockBlob GetCloudBlockBlob(string fileName) | |||
| { | |||
| string blobstorageconnection = _configuration.GetValue<string>("BlobConnectionString"); | |||
| CloudStorageAccount cloudStorageAccount = CloudStorageAccount.Parse(blobstorageconnection); | |||
| CloudBlobClient blobClient = cloudStorageAccount.CreateCloudBlobClient(); | |||
| CloudBlobContainer container = blobClient.GetContainerReference( | |||
| _configuration.GetValue<string>("BlobContainerName")); | |||
| return container.GetBlockBlobReference(fileName); | |||
| } | |||
| private static string ConvertToBase64(Stream stream) | |||
| { | |||
| byte[] bytes; | |||
| using (var memoryStream = new MemoryStream()) | |||
| { | |||
| stream.CopyTo(memoryStream); | |||
| bytes = memoryStream.ToArray(); | |||
| } | |||
| string base64 = Convert.ToBase64String(bytes); | |||
| return base64; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,53 @@ | |||
| using System.Net; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| public class HttpClientService : IHttpClientService | |||
| { | |||
| private const string GoogleApiTokenInfoUrl = "https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={0}"; | |||
| private string[] SupportedClientsIds = { "" }; | |||
| private readonly AuthorizationSettings _authSettings; | |||
| private readonly ILogger<HttpClientService> _logger; | |||
| public HttpClientService(IOptions<AuthorizationSettings> authSettings, ILogger<HttpClientService> logger) | |||
| { | |||
| _authSettings = authSettings.Value; | |||
| _logger = logger; | |||
| } | |||
| public async Task<bool> IsTokenValid(string providerToken) | |||
| { | |||
| _logger.LogInformation($"Start checking is token valid: {providerToken}"); | |||
| var httpClient = new HttpClient(); | |||
| var requestUri = new Uri(string.Format(GoogleApiTokenInfoUrl, providerToken)); | |||
| _logger.LogInformation("Initilazing http call to googleapi"); | |||
| HttpResponseMessage httpResponseMessage; | |||
| try | |||
| { | |||
| _logger.LogInformation("Calling googleapi HTTPGet method"); | |||
| httpResponseMessage = httpClient.GetAsync(requestUri).Result; | |||
| } | |||
| catch(Exception ex) | |||
| { | |||
| _logger.LogInformation($"Error in call: {ex.Message}"); | |||
| return false; | |||
| } | |||
| if (httpResponseMessage.StatusCode != HttpStatusCode.OK) | |||
| { | |||
| return false; | |||
| } | |||
| var response = httpResponseMessage.Content.ReadAsStringAsync().Result; | |||
| var googleApiTokenInfo = JsonConvert.DeserializeObject<GoogleApiTokenInfo>(response); | |||
| _logger.LogInformation($"Call pass and it received: {googleApiTokenInfo}"); | |||
| //if (!SupportedClientsIds.Contains(googleApiTokenInfo.aud)) | |||
| if (googleApiTokenInfo.aud != _authSettings.GoogleClientId) | |||
| { | |||
| return false; | |||
| } | |||
| return true; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,55 @@ | |||
| using Microsoft.AspNetCore.Http; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| public class ImportService : IImportService | |||
| { | |||
| private readonly ILogger<ImportService> _logger; | |||
| private readonly ISaveImportedDataService _service; | |||
| public ImportService(ILogger<ImportService> logger, ISaveImportedDataService service) | |||
| { | |||
| _logger = logger; | |||
| _service = service; | |||
| } | |||
| public async Task<List<ApplicantImportDto>> Import(IFormFile fileData) | |||
| { | |||
| try | |||
| { | |||
| using (var stream = new MemoryStream()) | |||
| { | |||
| fileData.CopyTo(stream); | |||
| var FileData = stream.ToArray(); | |||
| CopyFileToDirectory(FileData); | |||
| } | |||
| return await _service.Save(); | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| throw new Exception(ex.Message); | |||
| } | |||
| return new List<ApplicantImportDto>(); | |||
| } | |||
| private async Task CopyFileToDirectory(byte[] FileData) | |||
| { | |||
| try | |||
| { | |||
| var content = new System.IO.MemoryStream(FileData); | |||
| var path = Path.Combine(Directory.GetCurrentDirectory(), "Files", "s.xlsx"); | |||
| await CopyStream(content, path); | |||
| } | |||
| catch (Exception) | |||
| { | |||
| throw; | |||
| } | |||
| } | |||
| private async Task CopyStream(Stream stream, string downloadPath) | |||
| { | |||
| using (var fileStream = new FileStream(downloadPath, FileMode.Create, FileAccess.Write)) | |||
| { | |||
| await stream.CopyToAsync(fileStream); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,64 @@ | |||
| using System.Diagnostics.CodeAnalysis; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class InsuranceCompaniesService : IInsuranceCompaniesService | |||
| { | |||
| private readonly DatabaseContext _context; | |||
| private readonly IMapper _mapper; | |||
| public InsuranceCompaniesService(DatabaseContext context, IMapper mapper) | |||
| { | |||
| _context = context; | |||
| _mapper = mapper; | |||
| } | |||
| public async Task<List<InsuranceCompanyViewDto>> GetInsuranceCompanies() | |||
| { | |||
| var insuranceCompanies = await _context.InsuranceCompanies.ToListAsync(); | |||
| var insuranceCompaniesDto = _mapper.Map<List<InsuranceCompanyViewDto>>(insuranceCompanies); | |||
| return insuranceCompaniesDto; | |||
| } | |||
| public async Task<InsuranceCompanyViewDto?> GetInsuranceCompany(long id) | |||
| { | |||
| var insuranceCompany = await _context.InsuranceCompanies.FindAsync(id); | |||
| if (insuranceCompany == null) | |||
| throw new EntityNotFoundException("Insurance company not found"); | |||
| var insuranceCompanyDto = _mapper.Map<InsuranceCompanyViewDto?>(insuranceCompany); | |||
| return insuranceCompanyDto; | |||
| } | |||
| public async Task CreateInsuranceCompany(InsuranceCompanyCreateDto insuranceCompanyCreateDto) | |||
| { | |||
| var insuranceCompany = _mapper.Map<InsuranceCompany>(insuranceCompanyCreateDto); | |||
| insuranceCompany.CreatedAtUtc = DateTime.Now; | |||
| await _context.InsuranceCompanies.AddAsync(insuranceCompany); | |||
| await _context.SaveChangesAsync(); | |||
| } | |||
| public async Task UpdateInsuranceCompany(long insuranceCompanyId, InsuranceCompanyUpdateDto insuranceCompanyUpdateDto) | |||
| { | |||
| var insuranceCompany = _context.InsuranceCompanies.Find(insuranceCompanyId); | |||
| if (insuranceCompany == null) | |||
| throw new EntityNotFoundException($"Insurance company not found"); | |||
| _mapper.Map(insuranceCompanyUpdateDto, insuranceCompany); | |||
| insuranceCompany.UpdatedAtUtc = DateTime.Now; | |||
| _context.Entry(insuranceCompany).State = EntityState.Modified; | |||
| await _context.SaveChangesAsync(); | |||
| } | |||
| public async Task DeleteInsuranceCompany(long insuranceCompanyId) | |||
| { | |||
| var insuranceCompany = _context.InsuranceCompanies.Find(insuranceCompanyId); | |||
| if (insuranceCompany == null) | |||
| throw new EntityNotFoundException("Insurance company not found"); | |||
| _context.Remove(insuranceCompany); | |||
| await _context.SaveChangesAsync(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,76 @@ | |||
| using System.Diagnostics.CodeAnalysis; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class InsurancePoliciesService : IInsurancePoliciesService | |||
| { | |||
| private readonly DatabaseContext _context; | |||
| private readonly IWebhookPublisherService _webhookPublisher; | |||
| private readonly IMapper _mapper; | |||
| public InsurancePoliciesService(DatabaseContext context, IWebhookPublisherService webhookPublisher, IMapper mapper) | |||
| { | |||
| _context = context; | |||
| _webhookPublisher = webhookPublisher; | |||
| _mapper = mapper; | |||
| } | |||
| public async Task<List<InsurancePolicyViewDto>> GetInsurancePolicies() | |||
| { | |||
| var insurancePolicies = await _context.InsurancePolicies | |||
| .Include(i => i.Insurer) | |||
| .ThenInclude(k => k.InsuranceCompany) | |||
| .ToListAsync(); | |||
| var insurancePoliciesDto = _mapper.Map<List<InsurancePolicyViewDto>>(insurancePolicies); | |||
| return insurancePoliciesDto; | |||
| } | |||
| public async Task<InsurancePolicyViewDto?> GetInsurancePolicy(long id) | |||
| { | |||
| var insurancePolicy = await _context.InsurancePolicies | |||
| .Include(i => i.Insurer) | |||
| .ThenInclude(k => k.InsuranceCompany) | |||
| .FirstOrDefaultAsync(i => i.Id == id); | |||
| if (insurancePolicy == null) | |||
| throw new EntityNotFoundException("Insurance policy not found"); | |||
| var insurancePolicyDto = _mapper.Map<InsurancePolicyViewDto>(insurancePolicy); | |||
| return insurancePolicyDto; | |||
| } | |||
| public async Task CreateInsurancePolicy(InsurancePolicyCreateDto insurancePolicyCreateDto) | |||
| { | |||
| var insurancePolicy = _mapper.Map<InsurancePolicy>(insurancePolicyCreateDto); | |||
| var result = await _context.InsurancePolicies.AddAsync(insurancePolicy); | |||
| await _webhookPublisher.PublishAsync("insurancePolicy.created", result); | |||
| await _context.SaveChangesAsync(); | |||
| } | |||
| public async Task UpdateInsurancePolicy(long insurancePolicyId, InsurancePolicyUpdateDto insurancePolicyUpdateDto) | |||
| { | |||
| var insurancePolicy = _context.InsurancePolicies.Find(insurancePolicyId); | |||
| if (insurancePolicy == null) | |||
| throw new EntityNotFoundException("Insurance policy not found"); | |||
| _mapper.Map(insurancePolicyUpdateDto, insurancePolicy); | |||
| insurancePolicy.UpdatedAtUtc = DateTime.Now; | |||
| _context.Entry(insurancePolicy).State = EntityState.Modified; | |||
| await _context.SaveChangesAsync(); | |||
| } | |||
| public async Task DeleteInsurancePolicy(long insurancePolicyId) | |||
| { | |||
| var insurancePolicy = _context.InsurancePolicies.Find(insurancePolicyId); | |||
| if (insurancePolicy == null) | |||
| throw new EntityNotFoundException("Insurance policy not found"); | |||
| _context.Remove(insurancePolicy); | |||
| await _context.SaveChangesAsync(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,66 @@ | |||
| using System.Diagnostics.CodeAnalysis; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class InsurersService : IInsurersService | |||
| { | |||
| private readonly DatabaseContext _context; | |||
| private readonly IMapper _mapper; | |||
| public InsurersService(DatabaseContext context, IMapper mapper) | |||
| { | |||
| _context = context; | |||
| _mapper = mapper; | |||
| } | |||
| public async Task<List<InsurerViewDto>> GetInsurers() | |||
| { | |||
| var insurers = await _context.Insurers.Include(x => x.InsuranceCompany).ToListAsync(); | |||
| return _mapper.Map<List<InsurerViewDto>>(insurers); | |||
| } | |||
| public async Task<InsurerViewDto?> GetInsurer(long id) | |||
| { | |||
| var insurer = await _context.Insurers | |||
| .Include(x => x.InsuranceCompany) | |||
| .FirstOrDefaultAsync(x => x.Id == id); | |||
| if (insurer == null) | |||
| throw new EntityNotFoundException("Insurer not found"); | |||
| return _mapper.Map<InsurerViewDto>(insurer); | |||
| } | |||
| public async Task CreateInsurer(InsurerCreateDto insurerCreateDto) | |||
| { | |||
| var insurer = _mapper.Map<Insurer>(insurerCreateDto); | |||
| await _context.Insurers.AddAsync(insurer); | |||
| await _context.SaveChangesAsync(); | |||
| } | |||
| public async Task UpdateInsurer(long insurerId, InsurerUpdateDto insurerUpdateDto) | |||
| { | |||
| var insurer = _context.InsurancePolicies.Find(insurerId); | |||
| if (insurer == null) | |||
| throw new EntityNotFoundException("Insurer not found"); | |||
| _mapper.Map(insurerUpdateDto, insurer); | |||
| insurer.UpdatedAtUtc = DateTime.Now; | |||
| _context.Entry(insurer).State = EntityState.Modified; | |||
| await _context.SaveChangesAsync(); | |||
| } | |||
| public async Task DeleteInsurerAsync(long insurerId) | |||
| { | |||
| var insurer = _context.InsurancePolicies.Find(insurerId); | |||
| if (insurer == null) | |||
| throw new EntityNotFoundException("Insurer not found"); | |||
| _context.Remove(insurer); | |||
| await _context.SaveChangesAsync(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,27 @@ | |||
| | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IAdService | |||
| { | |||
| Task<List<AdResponseDto>> GetAllAsync(); | |||
| Task<List<AdResponseWithCountDto>> GetAllWithCountAsync(); | |||
| Task<AdResponseDto> GetByIdAsync(int id); | |||
| Task<AdDetailsResponseDto> GetAdDetailsByIdAsync(int id); | |||
| Task<List<AdResponseDto>> GetArchiveAds(); | |||
| Task<List<AdResponseDto>> GetFilteredAdsAsync(AdFilterDto filters); | |||
| Task CreateAsync(AdCreateDto adCreateDto); | |||
| Task<Ad> ImportAsync(AdCreateDto adCreateDto); | |||
| Task UpdateAsync(int id, AdUpdateDto adUpdateDto); | |||
| Task ArchiveAdAsync(int id); | |||
| Task DeleteAsync(int id); | |||
| Task<Ad> GetByIdEntityAsync(int id); | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| | |||
| using Diligent.WebAPI.Contracts.DTOs.Applicant; | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IApplicantService | |||
| { | |||
| Task<QueryResultDto<ApplicantViewDto>> GetFilteredApplicants(ApplicantFilterDto applicantFilterDto); | |||
| Task<List<AdApplicantsViewDto>> GetAllAdsApplicants(ApplicantFilterDto applicantFilterDto); | |||
| Task<ApplicantViewDto> GetById(int id); | |||
| Task<ApplicantViewDto> GetApplicantWithSelectionProcessesById(int id); | |||
| Task ApplyForAd(ApplyForAdRequestDto request); | |||
| Task DeleteApplicant(int id); | |||
| Task<List<ApplicantOptionsDTO>> GetOptions(); | |||
| Task<ServiceResponseDTO<object>> InitializeProcess(ApplicantProcessRequestDTO model); | |||
| Task ImportApplicant(List<ApplicantImportDto> request); | |||
| } | |||
| } | |||
| @@ -0,0 +1,22 @@ | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IAuthenticationService | |||
| { | |||
| Task<ServiceResponseDTO<AuthenticateResponseDto>> Authenticate(AuthenticateRequestDto model); | |||
| Task<ServiceResponseDTO<AuthenticateResponseDto>> Authenticate(GoogleApiModel model); | |||
| Task<RefreshTokenResultDto> RefreshTokenAsync(RefreshTokenRequestDto model); | |||
| Task<RefreshToken?> GetRefreshTokenByUserId(int userId); | |||
| Task UpdateRefreshToken(RefreshToken refreshToken); | |||
| Task<ServiceResponseDTO<string>> DeleteRefreshToken(int userId); | |||
| Task<ServiceResponseDTO<object>> GetForgotPasswordUrlAsync(string email); | |||
| Task<ServiceResponseDTO<object>> PasswordResetAsync(string email, string code, string password); | |||
| Task<ServiceResponseDTO<object>> Register(RegisterDTO model); | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Comment; | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface ICommentService | |||
| { | |||
| Task CreateComment(CommentCreateDto commentCreateDto); | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| using Diligent.WebAPI.Contracts.Models; | |||
| using System.Net.Mail; | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IEmailer | |||
| { | |||
| DiligEmail CreateEmail(List<string> to, string subject, string body, bool isHtml = false, DateTime? dontSendBefore = null); | |||
| MailMessage GetMailMessage(List<string> to, string subject, string body, bool isHtml = false, List<string> cc = null); | |||
| SmtpClient GetSmtpClient(); | |||
| bool SendEmail(List<string> to, string subject, string body, bool isHtml = false, List<string> cc = null); | |||
| bool SendEmailAndWriteToDb(List<string> to, string subject, string body, bool isHtml = false, List<string> cc = null); | |||
| bool SendEmailAndWriteToDb(string to, string subject, string body, bool isHtml = false, List<string> cc = null); | |||
| Task<bool> SendEmailAndWriteToDbAsync(List<string> to, string subject, string body, bool isHtml = false, List<string> cc = null); | |||
| Task<bool> SendEmailAndWriteToDbAsync(string to, string subject, string body, bool isHtml = false, List<string> cc = null); | |||
| Task<bool> SendEmailAsync(List<string> to, string subject, string body, bool isHtml = false, List<string> cc = null); | |||
| } | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| using Microsoft.AspNetCore.Http; | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IFileService | |||
| { | |||
| Task<string> GetCV(string fileName); | |||
| Task UploadCV(string fileName,IFormFile file); | |||
| } | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IHttpClientService | |||
| { | |||
| Task<bool> IsTokenValid(string providedToken); | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| using Microsoft.AspNetCore.Http; | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IImportService | |||
| { | |||
| Task<List<ApplicantImportDto>> Import(IFormFile fileData); | |||
| } | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IInsuranceCompaniesService | |||
| { | |||
| Task<List<InsuranceCompanyViewDto>> GetInsuranceCompanies(); | |||
| Task<InsuranceCompanyViewDto?> GetInsuranceCompany(long id); | |||
| Task CreateInsuranceCompany(InsuranceCompanyCreateDto insuranceCompanyCreateDto); | |||
| Task UpdateInsuranceCompany(long insuranceCompanyId, InsuranceCompanyUpdateDto insuranceCompanyUpdateDto); | |||
| Task DeleteInsuranceCompany(long insuranceCompanyId); | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IInsurancePoliciesService | |||
| { | |||
| Task<List<InsurancePolicyViewDto>> GetInsurancePolicies(); | |||
| Task<InsurancePolicyViewDto?> GetInsurancePolicy(long id); | |||
| Task CreateInsurancePolicy(InsurancePolicyCreateDto insurancePolicyCreateDto); | |||
| Task UpdateInsurancePolicy(long insurancePolicyId, InsurancePolicyUpdateDto insurancePolicyUpdateDto); | |||
| Task DeleteInsurancePolicy(long insurancePolicyId); | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IInsurersService | |||
| { | |||
| Task<List<InsurerViewDto>> GetInsurers(); | |||
| Task<InsurerViewDto?> GetInsurer(long id); | |||
| Task CreateInsurer(InsurerCreateDto insurerCreateDto); | |||
| Task UpdateInsurer(long insurerId, InsurerUpdateDto insurerUpdateDto); | |||
| Task DeleteInsurerAsync(long insurerId); | |||
| } | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Pattern; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IPatternService | |||
| { | |||
| Task<List<PatternResponseDto>> GetAllAsync(); | |||
| Task<PatternResponseDto> GetByIdAsync(int id); | |||
| Task<List<PatternResponseDto>> GetFilteredPatternsAsync(FilterPatternDto filterPatternDto); | |||
| Task<List<PatternApplicantViewDto>> GetCorrespondingPatternApplicants(int id); | |||
| Task CreateAsync(PatternCreateDto patternCreateDto); | |||
| Task<ScheduleInterviewResponseDto?> ScheduleIntrviewAsync(ScheduleInterviewDto scheduleInterviewDto); | |||
| Task UpdateAsync(PatternUpdateDto patternUpdateDto, int id); | |||
| Task DeleteAsync(int id); | |||
| } | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface ISaveImportedDataService | |||
| { | |||
| Task<List<ApplicantImportDto>> Save(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Schedule; | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IScheduleService | |||
| { | |||
| Task<List<ScheduleViewDto>> GetScheduleForCertainPeriod(int month, int year); | |||
| } | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IScreeningTestClientService | |||
| { | |||
| Task<AuthSuccessResponse> LoginToScreening(AuthenticateRequestDto model); | |||
| Task GetScreening(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IScreeningTestService | |||
| { | |||
| Task<BaseResult<IEnumerable<TestMicroserviceRequest>>> GetScreening(); | |||
| Task<bool> SendTest(TestMicroserviceInviteRequest test); | |||
| Task<AuthSuccessResponse> LoginToScreening(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| | |||
| using Diligent.WebAPI.Contracts.DTOs.SelectionLevel; | |||
| using Diligent.WebAPI.Contracts.DTOs.SelectionProcess; | |||
| using Diligent.WebAPI.Contracts.DTOs.Stats; | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface ISelectionLevelService | |||
| { | |||
| Task<List<SelectionLevelResponseWithDataDto>> GetAllAsync(); | |||
| Task<SelectionLevelResposneDto> GetByIdAsync(int id); | |||
| Task<SelectionLevel> GetByIdEntity(int id); | |||
| List<SelectionLevelResponseWithDataDto> GetFilteredLevelsAsync(SelectionProcessFilterDto filters); | |||
| Task<List<SelectionLevelInfoDto>> GetCountByLevels(List<string> statuses); | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| | |||
| using Diligent.WebAPI.Contracts.DTOs.SelectionProcess; | |||
| using Diligent.WebAPI.Contracts.DTOs.Stats; | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface ISelectionProcessService | |||
| { | |||
| Task<List<SelectionProcessResposneDto>> GetAllAsync(); | |||
| Task<bool> FinishSelectionProcess(SelectionProcessCreateDto model); | |||
| Task UpdateSelectionProcessStatusAsync(int id, SelectionProcessUpdateStatusDto selectionProcessUpdateStatusDto); | |||
| Task StatusUpdate(StatusChangeDTO model); | |||
| Task InterviewerUpdate(InterviewerUpdateDTO model); | |||
| } | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface ITechnologyService | |||
| { | |||
| Task<List<TechnologyResponseDto>> GetAllAsync(); | |||
| Task<TechnologyResponseDto> GetByIdAsync(int id); | |||
| Task<Technology> GetEntityByIdAsync(int id); | |||
| Task<List<Technology>> GetEntitiesAsync(int [] technologiesIds); | |||
| } | |||
| } | |||
| @@ -0,0 +1,16 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.User; | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IUserService | |||
| { | |||
| Task<IEnumerable<User?>> GetAll(); | |||
| Task<User> GetById(int id); | |||
| Task<User> GetByEmail(string email); | |||
| Task CreateUser(CreateUserRequestDto model); | |||
| Task<bool?> ToggleEnable(User user); | |||
| Task RemoveUser(User user); | |||
| Task<ServiceResponseDTO<object>> SendRegistrationLink(InviteDTO invite); | |||
| Task<User> GetFirst(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IWebhookDefinitionService | |||
| { | |||
| /// <summary> | |||
| /// Get all webhook definitions | |||
| /// </summary> | |||
| Task<List<WebhookDefinitionViewDto>> GetAll(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,7 @@ | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IWebhookPublisherService | |||
| { | |||
| Task PublishAsync(string webhookName, object data); | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| namespace Diligent.WebAPI.Business.Services.Interfaces | |||
| { | |||
| public interface IWebhookSubscriptionService | |||
| { | |||
| /// <summary> | |||
| /// Create new webhook subscription | |||
| /// </summary> | |||
| Task<WebhookSubscription> CreateWebhookSubscription(WebhookSubscriptionCreateDto dto); | |||
| /// <summary> | |||
| /// Get subscriptions by webhook name | |||
| /// </summary> | |||
| Task<List<WebhookSubscription>> GetAllSubscriptionsAsync(string webhookName); | |||
| } | |||
| } | |||
| @@ -0,0 +1,241 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Pattern; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| public class PatternService : IPatternService | |||
| { | |||
| private readonly DatabaseContext _context; | |||
| private readonly IMapper _mapper; | |||
| private readonly ISelectionLevelService _selectionLevelService; | |||
| private readonly ILogger<PatternService> _logger; | |||
| private readonly IEmailer _emailer; | |||
| private readonly ISelectionProcessService _selectionProcessService; | |||
| public PatternService(DatabaseContext context, IMapper mapper, ISelectionLevelService selectionLevelService, ILogger<PatternService> logger, IEmailer emailer, ISelectionProcessService selectionProcessService) | |||
| { | |||
| _context = context; | |||
| _mapper = mapper; | |||
| _selectionLevelService = selectionLevelService; | |||
| _logger = logger; | |||
| _emailer = emailer; | |||
| _selectionProcessService = selectionProcessService; | |||
| } | |||
| public async Task<List<PatternResponseDto>> GetAllAsync() | |||
| { | |||
| _logger.LogInformation("Start getting all Patterns"); | |||
| _logger.LogInformation("Getting data from DB"); | |||
| var fromDb = await _context.Patterns.Include(x => x.SelectionLevel).ToListAsync(); | |||
| _logger.LogInformation($"Received {fromDb.Count} patterns from db."); | |||
| _logger.LogInformation($"Mapping received patterns to PatternResponseDto"); | |||
| var result = _mapper.Map<List<PatternResponseDto>>(fromDb); | |||
| _logger.LogInformation($"Patterns has been mapped and received to client: {result.Count} mapped patterns"); | |||
| return result; | |||
| } | |||
| public async Task<PatternResponseDto> GetByIdAsync(int id) | |||
| { | |||
| _logger.LogInformation($"Start searching Pattern with id = {id}"); | |||
| var pattern = await _context.Patterns.Include(x => x.SelectionLevel).Where(x => x.Id == id).FirstOrDefaultAsync(); | |||
| if (pattern is null) | |||
| { | |||
| _logger.LogError($"Pattern with id = {id} not found"); | |||
| throw new EntityNotFoundException("Pattern not found"); | |||
| } | |||
| _logger.LogInformation($"Mapping Pattern with id = {id}"); | |||
| PatternResponseDto result = _mapper.Map<PatternResponseDto>(pattern); | |||
| _logger.LogInformation($"Pattern with id = {id} mapped successfully"); | |||
| return result; | |||
| } | |||
| public async Task<List<PatternResponseDto>> GetFilteredPatternsAsync(FilterPatternDto filterPatternDto) | |||
| { | |||
| _logger.LogInformation($"Start getting all filtered Patterns"); | |||
| _logger.LogInformation("Getting data from DB"); | |||
| var filteredPatterns = await _context.Patterns.Include(x => x.SelectionLevel).ToListAsync(); | |||
| _logger.LogInformation($"Received {filteredPatterns.Count} patterns from db."); | |||
| _logger.LogInformation($"Mapping received patterns to PatternResponseDto"); | |||
| List<PatternResponseDto> result = _mapper.Map<List<PatternResponseDto>>(filteredPatterns.FilterApplicants(filterPatternDto)); | |||
| _logger.LogInformation($"Patterns has been mapped and received to client: {result.Count} mapped patterns"); | |||
| return result; | |||
| } | |||
| public async Task<List<PatternApplicantViewDto>> GetCorrespondingPatternApplicants(int id) | |||
| { | |||
| _logger.LogInformation($"Start getting corresponding pattern applicants"); | |||
| _logger.LogInformation($"Getting pattern from database by id"); | |||
| var pattern = await _context.Patterns.Include(x => x.SelectionLevel).ThenInclude(y => y.SelectionProcesses).ThenInclude(z => z.Applicant).Where(x => x.Id == id).FirstOrDefaultAsync(); | |||
| if(pattern == null) | |||
| { | |||
| _logger.LogError($"Pattern with id = {id} not found"); | |||
| throw new EntityNotFoundException("Pattern not found"); | |||
| } | |||
| _logger.LogInformation($"Select applicants from selection processes with status \"Čeka na zakazivanje\""); | |||
| var selectionProcessesApplicants = pattern.SelectionLevel.SelectionProcesses.Where(x => x.Status == "Čeka na zakazivanje").Select(x => x.Applicant).ToList(); | |||
| _logger.LogInformation($"Mapping Pattern applicants"); | |||
| var applicants = _mapper.Map<List<PatternApplicantViewDto>>(selectionProcessesApplicants); | |||
| _logger.LogInformation($"Pattern applicants mapped successfully"); | |||
| return applicants; | |||
| } | |||
| public async Task CreateAsync(PatternCreateDto patternCreateDto) | |||
| { | |||
| _logger.LogInformation($"Start creating Pattern"); | |||
| _logger.LogInformation($"Check is Pattern in database"); | |||
| var patternExists = await _context.Patterns.Include(x => x.SelectionLevel).Where(x => x.Title == patternCreateDto.Title && x.SelectionLevelId == patternCreateDto.SelectionLevelId).FirstOrDefaultAsync(); | |||
| if (patternExists is not null) | |||
| { | |||
| _logger.LogError($"Pattern already exists in database"); | |||
| throw new EntityNotFoundException("Pattern already exists in database"); | |||
| } | |||
| _logger.LogInformation($"Pattern is not in database"); | |||
| _logger.LogInformation($"Mapping PatternCreateDto to model"); | |||
| var pattern = _mapper.Map<Pattern>(patternCreateDto); | |||
| _logger.LogInformation($"Pattern mapped successfully"); | |||
| _logger.LogInformation($"Start searching SelectionLevel with id = {patternCreateDto.SelectionLevelId}"); | |||
| var selectionLevel = await _selectionLevelService.GetByIdEntity(patternCreateDto.SelectionLevelId); | |||
| if(selectionLevel == null) | |||
| { | |||
| _logger.LogError($"SelectionLevel with id = {patternCreateDto.SelectionLevelId} not found"); | |||
| throw new EntityNotFoundException("Selection level not found"); | |||
| } | |||
| _logger.LogInformation($"Add founded SelectionLevel to pattern"); | |||
| pattern.SelectionLevel = selectionLevel; | |||
| await _context.AddAsync(pattern); | |||
| _logger.LogInformation($"Saving Ad to db..."); | |||
| var result = _context.SaveChangesAsync(); | |||
| _logger.LogInformation($"Saved Ad to db..."); | |||
| await result; | |||
| } | |||
| public async Task<ScheduleInterviewResponseDto?> ScheduleIntrviewAsync(ScheduleInterviewDto scheduleInterviewDto) | |||
| { | |||
| _logger.LogInformation($"Start scheduling interview"); | |||
| List<string> NotSentEmails = new(); | |||
| _logger.LogInformation("Getting pattern from DB by id"); | |||
| var pattern = await _context.Patterns.Include(x => x.SelectionLevel).ThenInclude(y => y.SelectionProcesses).ThenInclude(z => z.Applicant).Where(x => x.Id == scheduleInterviewDto.PatternId).FirstOrDefaultAsync(); | |||
| if (pattern == null) | |||
| { | |||
| _logger.LogError($"Pattern with id = {scheduleInterviewDto.PatternId} not found"); | |||
| throw new EntityNotFoundException("Pattern not found"); | |||
| } | |||
| _logger.LogInformation("Pattern found in DB"); | |||
| for (int i = 0; i < scheduleInterviewDto.Emails.Count; i++) | |||
| { | |||
| var to = new List<string> { scheduleInterviewDto.Emails[i] }; | |||
| _logger.LogInformation("Select process where status is \"Čeka na zakazivanje\""); | |||
| var selectionProcesses = pattern.SelectionLevel.SelectionProcesses.Where(x => x.Status == "Čeka na zakazivanje" && x.Applicant.Email == scheduleInterviewDto.Emails[i]).FirstOrDefault(); | |||
| if(selectionProcesses != null) | |||
| { | |||
| _logger.LogInformation("Selection process is not null"); | |||
| _logger.LogInformation("Selection process status changing to \"Zakazan\""); | |||
| await _selectionProcessService.UpdateSelectionProcessStatusAsync(selectionProcesses.Id, new SelectionProcessUpdateStatusDto | |||
| { | |||
| Status = "Zakazan" | |||
| }); | |||
| _logger.LogInformation("Sending pattern to selected emails on frontend"); | |||
| await _emailer.SendEmailAsync(to, "Schedule interview", | |||
| HTMLHelper.SuccessfulStep(pattern.Message, pattern.Title, String.Format("{0:M/d/yyyy}", selectionProcesses.Date)), isHtml: true); | |||
| } | |||
| else | |||
| { | |||
| _logger.LogInformation("Selection process not found in database"); | |||
| NotSentEmails.Add(scheduleInterviewDto.Emails[i] + " ne postoji u bazi sa statusom \"Čeka na zakazivanje\" "); | |||
| continue; | |||
| } | |||
| } | |||
| if(NotSentEmails.Count() > 0) | |||
| { | |||
| _logger.LogInformation("List of not set emails are not empty"); | |||
| _logger.LogInformation("Returning list of not sent emails"); | |||
| return new ScheduleInterviewResponseDto { NotSentEmails = NotSentEmails }; | |||
| } | |||
| _logger.LogInformation("List of not sent email is empty. Return null..."); | |||
| return null; | |||
| } | |||
| public async Task UpdateAsync(PatternUpdateDto patternUpdateDto, int id) | |||
| { | |||
| _logger.LogInformation($"Start updating Pattern"); | |||
| _logger.LogInformation($"Check is Pattern in database"); | |||
| var patternExists = await _context.Patterns.Include(x => x.SelectionLevel).Where(x => x.Title == patternUpdateDto.Title && x.SelectionLevelId == patternUpdateDto.SelectionLevelId).FirstOrDefaultAsync(); | |||
| if (patternExists is not null) | |||
| { | |||
| _logger.LogError($"Pattern already exists in database"); | |||
| throw new EntityNotFoundException("Pattern already exists in database"); | |||
| } | |||
| _logger.LogInformation($"Start searching Pattern with id = {id}"); | |||
| var pattern = await _context.Patterns.Where(x => x.Id == id).FirstOrDefaultAsync(); | |||
| if (pattern is null) | |||
| { | |||
| _logger.LogError($"Pattern with id = {id} not found"); | |||
| throw new EntityNotFoundException("Pattern not found"); | |||
| } | |||
| _logger.LogInformation($"Mapping Pattern with id = {id}"); | |||
| _mapper.Map(patternUpdateDto, pattern); | |||
| _logger.LogInformation($"Pattern with id = {id} mapped successfully"); | |||
| _logger.LogInformation($"Start searching SelectionLevel with id = {patternUpdateDto.SelectionLevelId}"); | |||
| var selectionLevel = await _selectionLevelService.GetByIdEntity(patternUpdateDto.SelectionLevelId); | |||
| if (selectionLevel == null) | |||
| { | |||
| _logger.LogError($"SelectionLevel with id = {patternUpdateDto.SelectionLevelId} not found"); | |||
| throw new EntityNotFoundException("Selection level not found"); | |||
| } | |||
| _logger.LogInformation($"Add founded SelectionLevel to pattern"); | |||
| pattern.SelectionLevel = selectionLevel; | |||
| _context.Entry(pattern).State = EntityState.Modified; | |||
| var result = _context.SaveChangesAsync(); | |||
| _logger.LogInformation($"Pattern saved to DB"); | |||
| await result; | |||
| } | |||
| public async Task DeleteAsync(int id) | |||
| { | |||
| _logger.LogInformation($"Start searching Pattern with id = {id}"); | |||
| var pattern = await _context.Patterns.FindAsync(id); | |||
| if (pattern is null) | |||
| { | |||
| _logger.LogError($"Pattern with id = {id} not found"); | |||
| throw new EntityNotFoundException("Pattern not found"); | |||
| } | |||
| _context.Patterns.Remove(pattern); | |||
| var result = _context.SaveChangesAsync(); | |||
| _logger.LogInformation($"Ad saved to DB"); | |||
| await result; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,91 @@ | |||
| using Bytescout.Spreadsheet; | |||
| using System.Globalization; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| public class SaveImportedDataService : ISaveImportedDataService | |||
| { | |||
| private readonly ILogger<SaveImportedDataService> _logger; | |||
| private readonly IAdService _adService; | |||
| public SaveImportedDataService(ILogger<SaveImportedDataService> logger, IAdService adService) | |||
| { | |||
| _logger = logger; | |||
| _adService = adService; | |||
| } | |||
| public async Task<List<ApplicantImportDto>> Save() | |||
| { | |||
| List<ApplicantImportDto> applicants = new List<ApplicantImportDto>(); | |||
| try | |||
| { | |||
| _logger.LogInformation("Unoirtubg data from file..."); | |||
| var path = Path.Combine(Directory.GetCurrentDirectory(), "Files", "s.xlsx"); | |||
| Spreadsheet document = new Spreadsheet(); | |||
| document.LoadFromFile(path); | |||
| _logger.LogInformation("File is opened successfully"); | |||
| var worksheetsNumber = document.Workbook.Worksheets.Count; | |||
| _logger.LogInformation($"File contains {worksheetsNumber} sheets"); | |||
| for (int k = 0; k < worksheetsNumber; k++) | |||
| { | |||
| var worksheets = document.Workbook.Worksheets[k]; | |||
| var position = worksheets.Name; | |||
| _logger.LogInformation($"Import data for Ad {position}"); | |||
| var ad = await _adService.ImportAsync(new AdCreateDto | |||
| { | |||
| Title = position, | |||
| TechnologiesIds = new(), | |||
| MinimumExperience = 0, | |||
| WorkHour = "FullTime", | |||
| Requirements = "", | |||
| Offer = "", | |||
| KeyResponsibilities = "", | |||
| EmploymentType = position.Contains("praksa") ? "Intership" : "Work", | |||
| CreatedAt = DateTime.Now, | |||
| ExpiredAt = DateTime.Now | |||
| }); | |||
| int i = 2; | |||
| while (true) | |||
| { | |||
| if (String.IsNullOrEmpty(worksheets.Cell(i, 0).ToString())) | |||
| break; | |||
| var name = worksheets.Cell(i, 0).ToString().Split(' '); | |||
| var a = new ApplicantImportDto | |||
| { | |||
| FirstName = name[0], | |||
| LastName = name[1], | |||
| Email = worksheets.Cell(i, 1).ToString(), | |||
| CV = worksheets.Cell(i, 2).ToString(), | |||
| ApplicationChannel = worksheets.Cell(i, 6).ToString(), | |||
| Comment = worksheets.Cell(i, 7).ToString(), | |||
| Position = position, | |||
| Ad = ad, | |||
| TypeOfEmployment = position.Contains("praksa") ? "Praksa" : "Posao" | |||
| }; | |||
| _logger.LogInformation($"Loaded user {a.FirstName} {a.LastName}"); | |||
| try | |||
| { | |||
| string str = worksheets.Cell(i, 3).ToString(); | |||
| if(!string.IsNullOrEmpty(str)) | |||
| a.DateOfApplication = DateTime.ParseExact(str, "dd.MM.yyyy.", CultureInfo.InvariantCulture); | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| _logger.LogError("Incorect date time for this candidate"); | |||
| } | |||
| applicants.Add(a); | |||
| _logger.LogInformation("Candidate added successfully in the list"); | |||
| i++; | |||
| } | |||
| } | |||
| } | |||
| catch (Exception e) | |||
| { | |||
| _logger.LogError(e.Message); | |||
| throw new FileNotFoundException("File is not uploaded!"); | |||
| } | |||
| return applicants; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,33 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Schedule; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| public class ScheduleService : IScheduleService | |||
| { | |||
| private readonly DatabaseContext _context; | |||
| private readonly IMapper _mapper; | |||
| private readonly ILogger<ScheduleService> _logger; | |||
| public ScheduleService(DatabaseContext context, IMapper mapper,ILogger<ScheduleService> logger) | |||
| { | |||
| _context = context; | |||
| _mapper = mapper; | |||
| _logger = logger; | |||
| } | |||
| public async Task<List<ScheduleViewDto>> GetScheduleForCertainPeriod(int month, int year) | |||
| { | |||
| _logger.LogInformation("Start getting schedule for certain period"); | |||
| _logger.LogInformation("Getting data from DB and filter"); | |||
| var selectionProcessess = await _context.SelectionProcesses | |||
| .Include(c => c.Applicant) | |||
| .Include(c => c.SelectionLevel) | |||
| .Where(k => k.Date != null && k.Date.Value.Month == month && k.Date.Value.Year == year) | |||
| .ToListAsync(); | |||
| _logger.LogInformation($"Got {selectionProcessess.Count} selection processes"); | |||
| _logger.LogInformation($"Return schedule for certain period"); | |||
| return _mapper.Map<List<ScheduleViewDto>>(selectionProcessess); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,174 @@ | |||
| using Microsoft.AspNetCore.Mvc; | |||
| using Microsoft.AspNetCore.Mvc.Diagnostics; | |||
| using Microsoft.Extensions.Caching.Memory; | |||
| using System.Net; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| public class ScreeningTestService : IScreeningTestService | |||
| { | |||
| private readonly ScreeningTestSettings _settings; | |||
| private readonly ILogger<ScreeningTestService> _logger; | |||
| private readonly IMemoryCache _memoryCache; | |||
| public ScreeningTestService(IOptions<ScreeningTestSettings> settings, ILogger<ScreeningTestService> logger, IMemoryCache memoryCache) | |||
| { | |||
| _settings = settings.Value; | |||
| _logger = logger; | |||
| _memoryCache = memoryCache; | |||
| } | |||
| public async Task<BaseResult<IEnumerable<TestMicroserviceRequest>>> GetScreening() | |||
| { | |||
| string token = await GetToken(); | |||
| _logger.LogInformation($"Start calling microservice to get tests request"); | |||
| var httpClient = new HttpClient(); | |||
| var request = new HttpRequestMessage(HttpMethod.Get, _settings.Url + "tests"); | |||
| request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); | |||
| _logger.LogInformation("Initilazing http call to microservice"); | |||
| HttpResponseMessage httpResponseMessage; | |||
| try | |||
| { | |||
| _logger.LogInformation("Calling microservis to get test"); | |||
| httpResponseMessage = httpClient.SendAsync(request).Result; | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| _logger.LogError($"Error in call: {ex.Message}"); | |||
| return new BaseResult<IEnumerable<TestMicroserviceRequest>> | |||
| { | |||
| IsSuccess = false, | |||
| DataObject = new List<TestMicroserviceRequest>() | |||
| }; | |||
| } | |||
| if (httpResponseMessage.StatusCode == HttpStatusCode.Unauthorized) | |||
| { | |||
| _logger.LogError("Error: Unauthorized"); | |||
| return new BaseResult<IEnumerable<TestMicroserviceRequest>> | |||
| { | |||
| IsSuccess = false, | |||
| DataObject = new List<TestMicroserviceRequest>() | |||
| }; | |||
| } | |||
| if (httpResponseMessage.StatusCode != HttpStatusCode.OK) | |||
| { | |||
| _logger.LogError("Error"); | |||
| return new BaseResult<IEnumerable<TestMicroserviceRequest>> | |||
| { | |||
| IsSuccess = false, | |||
| DataObject = new List<TestMicroserviceRequest>() | |||
| }; | |||
| } | |||
| var response = httpResponseMessage.Content.ReadAsStringAsync().Result; | |||
| var resultData = JsonConvert.DeserializeObject<IEnumerable<TestMicroserviceRequest>>(response); | |||
| _logger.LogInformation($"Call pass and it received: {resultData.Count()} records"); | |||
| return new BaseResult<IEnumerable<TestMicroserviceRequest>> | |||
| { | |||
| DataObject = resultData | |||
| }; | |||
| } | |||
| private async Task<string> GetToken() | |||
| { | |||
| string token = ""; | |||
| if (_memoryCache.TryGetValue("JWT", out string t)) | |||
| { | |||
| token = t; | |||
| } | |||
| else | |||
| { | |||
| var result = await LoginToScreening(); | |||
| var cacheEntryOptions = new MemoryCacheEntryOptions() | |||
| .SetSlidingExpiration(TimeSpan.FromSeconds(60)) | |||
| .SetAbsoluteExpiration(TimeSpan.FromSeconds(3600)) | |||
| .SetPriority(CacheItemPriority.Normal) | |||
| .SetSize(1024); | |||
| _memoryCache.Set("JWT", result.Token, cacheEntryOptions); | |||
| token = result.Token; | |||
| } | |||
| return token; | |||
| } | |||
| public async Task<AuthSuccessResponse> LoginToScreening() | |||
| { | |||
| _logger.LogInformation($"Start calling microservice to login"); | |||
| var httpClient = new HttpClient(); | |||
| var requestUri = new Uri(string.Format(_settings.Url + "auth")); | |||
| var httpContent = new StringContent(System.Text.Json.JsonSerializer.Serialize(new AuthMicroserviceRequest | |||
| { | |||
| Email = _settings.Email, | |||
| Password = _settings.Password | |||
| }), | |||
| Encoding.UTF8, | |||
| "application/json"); | |||
| var response = await httpClient.PostAsync(requestUri, httpContent); | |||
| var content = await response.Content.ReadAsStringAsync(); | |||
| try | |||
| { | |||
| var result = JsonConvert.DeserializeObject<AuthSuccessResponse>(content); | |||
| var expires = result.Expires.Value - DateTime.Now; | |||
| var cacheEntryOptions = new MemoryCacheEntryOptions() | |||
| .SetSlidingExpiration(TimeSpan.FromSeconds(60)) | |||
| .SetAbsoluteExpiration(expires) | |||
| .SetPriority(CacheItemPriority.Normal) | |||
| .SetSize(1024); | |||
| _memoryCache.Set("JWT", result.Token, cacheEntryOptions); | |||
| _logger.LogInformation($"Call pass and it received: {result}"); | |||
| return result; | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| _logger.LogInformation($"Error in call: "); | |||
| return new AuthSuccessResponse { Token = "" }; | |||
| } | |||
| } | |||
| public async Task<bool> SendTest(TestMicroserviceInviteRequest test) | |||
| { | |||
| string token = await GetToken(); | |||
| _logger.LogInformation($"Start calling microservice to send test request"); | |||
| var httpClient = new HttpClient(); | |||
| var request = new HttpRequestMessage(HttpMethod.Post, _settings.Url + "tests"); | |||
| request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); | |||
| var httpContent = new StringContent(System.Text.Json.JsonSerializer.Serialize(test), | |||
| Encoding.UTF8, | |||
| "application/json"); | |||
| request.Content = httpContent; | |||
| _logger.LogInformation("Initilazing http call to microservice"); | |||
| HttpResponseMessage httpResponseMessage; | |||
| try | |||
| { | |||
| _logger.LogInformation("Calling microservis to send test"); | |||
| httpResponseMessage = httpClient.SendAsync(request).Result; | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| _logger.LogError($"Error in call: {ex.Message}"); | |||
| return false; | |||
| } | |||
| if (httpResponseMessage.StatusCode == HttpStatusCode.Unauthorized) | |||
| { | |||
| _logger.LogError("Error: Unauthorized"); | |||
| return false; | |||
| } | |||
| if (httpResponseMessage.StatusCode != HttpStatusCode.OK) | |||
| { | |||
| _logger.LogError("Error"); | |||
| return false; | |||
| } | |||
| _logger.LogInformation($"Call pass"); | |||
| return true; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,103 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Stats; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| public class SelectionLevelService : ISelectionLevelService | |||
| { | |||
| private readonly DatabaseContext _context; | |||
| private readonly IMapper _mapper; | |||
| private readonly ILogger<SelectionLevelService> _logger; | |||
| public SelectionLevelService(DatabaseContext context, IMapper mapper, ILogger<SelectionLevelService> logger) | |||
| { | |||
| _context = context; | |||
| _mapper = mapper; | |||
| _logger = logger; | |||
| } | |||
| public async Task<List<SelectionLevelResponseWithDataDto>> GetAllAsync() | |||
| { | |||
| _logger.LogInformation("Start getting all selection levels"); | |||
| _logger.LogInformation("Getting data from DB"); | |||
| var fromDb = await _context.SelectionLevels | |||
| .Include(sl => sl.SelectionProcesses) | |||
| .ThenInclude(sp => sp.Applicant) | |||
| .Include(sl => sl.SelectionProcesses) | |||
| .ThenInclude(sp => sp.Scheduler) | |||
| .ToListAsync(); | |||
| _logger.LogInformation($"Received {fromDb.Count} levels from db."); | |||
| _logger.LogInformation($"Mapping received ads to SelectionLevelResponseWithDataDto"); | |||
| var result = _mapper.Map<List<SelectionLevelResponseWithDataDto>>(fromDb); | |||
| _logger.LogInformation($"Levels has been mapped and received to client: {result.Count} mapped levels"); | |||
| return result; | |||
| } | |||
| public async Task<SelectionLevelResposneDto> GetByIdAsync(int id) | |||
| { | |||
| _logger.LogInformation($"Start searching Level with id = {id}"); | |||
| var sl = await _context.SelectionLevels.FindAsync(id); | |||
| if (sl is null) | |||
| { | |||
| _logger.LogError($"Level with id = {id} not found"); | |||
| throw new EntityNotFoundException("Selection level not found"); | |||
| } | |||
| _logger.LogInformation($"Mapping Level with id = {id} to SelectionLevelResposneDto"); | |||
| var result = _mapper.Map<SelectionLevelResposneDto>(sl); | |||
| _logger.LogInformation($"Level with id = {id} mapped successfully"); | |||
| return result; | |||
| } | |||
| public async Task<SelectionLevel> GetByIdEntity(int id) | |||
| { | |||
| _logger.LogInformation($"Start searching Level with id = {id}"); | |||
| var sl = await _context.SelectionLevels.FindAsync(id); | |||
| if (sl is null) | |||
| { | |||
| _logger.LogError($"Level with id = {id} not found"); | |||
| throw new EntityNotFoundException("Selection level not found"); | |||
| } | |||
| _logger.LogInformation($"Level with id = {id} found and returned to client"); | |||
| return sl; | |||
| } | |||
| public List<SelectionLevelResponseWithDataDto> GetFilteredLevelsAsync(SelectionProcessFilterDto filters) | |||
| { | |||
| _logger.LogInformation("Start getting filtered selection levels"); | |||
| _logger.LogInformation("Getting data from DB and filter it"); | |||
| var filteredLevels = _context.SelectionLevels | |||
| .Include(x => x.SelectionProcesses) | |||
| .ThenInclude(sp => sp.Applicant).ToList() | |||
| .FilterLevels(filters); | |||
| _logger.LogInformation($"Received {filteredLevels.Count} levels from db."); | |||
| _logger.LogInformation($"Mapping received ads to SelectionLevelResponseWithDataDto"); | |||
| var result = _mapper.Map<List<SelectionLevelResponseWithDataDto>>(filteredLevels); | |||
| _logger.LogInformation($"Levels has been mapped and received to client: {result.Count} mapped levels"); | |||
| return result; | |||
| } | |||
| public async Task<List<SelectionLevelInfoDto>> GetCountByLevels(List<string> statuses) | |||
| { | |||
| _logger.LogInformation("Start getting all Selection levels"); | |||
| var res = await _context.SelectionLevels.Include(n => n.SelectionProcesses).ToListAsync(); | |||
| _logger.LogInformation($"Received {res.Count} selection levels"); | |||
| _logger.LogInformation("Mapping levels with process counts to SelectionLevelInfo"); | |||
| var resMapped = res.Select(n => new SelectionLevelInfoDto | |||
| { | |||
| Level = n.Name, | |||
| CountAll = n.SelectionProcesses.Count, | |||
| CountDone = n.SelectionProcesses.Where(n => statuses.Contains(n.Status)).Count() | |||
| }).ToList(); | |||
| return resMapped; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,144 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Stats; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| public class SelectionProcessService : ISelectionProcessService | |||
| { | |||
| private readonly DatabaseContext _context; | |||
| private readonly IMapper _mapper; | |||
| private readonly UserManager<User> _userManager; | |||
| private readonly ILogger<SelectionProcessService> _logger; | |||
| public SelectionProcessService(UserManager<User> userManager,DatabaseContext context, IMapper mapper, ILogger<SelectionProcessService> logger) | |||
| { | |||
| _context = context; | |||
| _mapper = mapper; | |||
| _logger = logger; | |||
| _userManager = userManager; | |||
| } | |||
| public async Task<List<SelectionProcessResposneDto>> GetAllAsync() | |||
| { | |||
| _logger.LogInformation("Start getting all Selection Processes"); | |||
| _logger.LogInformation("Getting data from DB"); | |||
| var fromDB = await _context.SelectionProcesses.ToListAsync(); | |||
| _logger.LogInformation($"Received {fromDB.Count} processes from db."); | |||
| _logger.LogInformation($"Mapping received ads to SelectionProcessResposneDto"); | |||
| var result = _mapper.Map<List<SelectionProcessResposneDto>>(fromDB); | |||
| _logger.LogInformation($"Processes has been mapped and received to client: {result.Count} mapped processes"); | |||
| return result; | |||
| } | |||
| public async Task<bool> FinishSelectionProcess(SelectionProcessCreateDto model) | |||
| { | |||
| _logger.LogInformation($"Start finishing selection process with {model.Id}"); | |||
| var sp = await _context.SelectionProcesses.FindAsync(model.Id); | |||
| if (sp is null) | |||
| { | |||
| _logger.LogError($"Process with id = {model.Id} not found"); | |||
| throw new EntityNotFoundException("Selection process not found"); | |||
| } | |||
| _logger.LogInformation($"Changing status for {model.Id}"); | |||
| sp.Status = "Odrađen"; | |||
| _logger.LogInformation($"Skipping throught levels to come to next level"); | |||
| if (sp.SelectionLevelId == _context.SelectionLevels.OrderBy(n => n.Id).Last().Id) | |||
| { | |||
| _logger.LogError($"Applicant is in the last selection level"); | |||
| throw new EntityNotFoundException("Candidate came to the last selection level"); | |||
| } | |||
| var nextLevel = _context.SelectionLevels.AsEnumerable() | |||
| .SkipWhile(obj => obj.Id != sp.SelectionLevelId) | |||
| .Skip(1).First(); | |||
| SelectionProcess newProcess = new SelectionProcess | |||
| { | |||
| Name = model.Name, | |||
| SelectionLevelId = nextLevel.Id, | |||
| Status = "Čeka na zakazivanje", | |||
| ApplicantId = sp.ApplicantId | |||
| }; | |||
| _context.SelectionProcesses.Add(newProcess); | |||
| _logger.LogInformation($"Create and add new selection process"); | |||
| var result = await _context.SaveChangesAsync() > 0; | |||
| _logger.LogInformation($"Saved changes to db"); | |||
| return result; | |||
| } | |||
| public async Task UpdateSelectionProcessStatusAsync(int id, SelectionProcessUpdateStatusDto selectionProcessUpdateStatusDto) | |||
| { | |||
| _logger.LogInformation($"Start searching Ad with id = {id}"); | |||
| var selectionProcess = await _context.SelectionProcesses.FindAsync(id); | |||
| if (selectionProcess is null) | |||
| { | |||
| _logger.LogError($"Selection process with id = {id} not found"); | |||
| throw new EntityNotFoundException("Selection process not found"); | |||
| } | |||
| _logger.LogInformation($"Mapping Selection process with id = {id}"); | |||
| _mapper.Map(selectionProcessUpdateStatusDto, selectionProcess); | |||
| _logger.LogInformation($"Selection process with id = {id} mapped successfully"); | |||
| _context.Entry(selectionProcess).State = EntityState.Modified; | |||
| var result = _context.SaveChangesAsync(); | |||
| _logger.LogInformation($"Selection process saved to DB"); | |||
| await result; | |||
| } | |||
| public async Task StatusUpdate(StatusChangeDTO model) | |||
| { | |||
| _logger.LogInformation($"Start searching process with id = {model.ProcessId}"); | |||
| var process = await _context.SelectionProcesses.FindAsync(model.ProcessId); | |||
| if (process is null) | |||
| { | |||
| _logger.LogError($"Process with id = {model.ProcessId} not found"); | |||
| throw new EntityNotFoundException("Process does not exist."); | |||
| } | |||
| _logger.LogInformation($"Check which status is going to be set."); | |||
| if (model.NewStatus == "Zakazan" && model.SchedulerId is not null) | |||
| { | |||
| _logger.LogInformation($"Start searching user with id = {model.SchedulerId}"); | |||
| var user = await _userManager.FindByIdAsync(model.SchedulerId.ToString()); | |||
| if (user is null) | |||
| throw new EntityNotFoundException("Scheduler does not exist."); | |||
| _logger.LogInformation($"Setting user {user.FirstName} {user.LastName} as scheduler for process with id = {model.ProcessId}"); | |||
| process.SchedulerId = user.Id; | |||
| } | |||
| process.Status = model.NewStatus; | |||
| process.Date = model.Appointment; | |||
| process.Comment = model.Comment; | |||
| _logger.LogInformation($"Processing changes."); | |||
| await _context.SaveChangesAsync(); | |||
| } | |||
| public async Task InterviewerUpdate(InterviewerUpdateDTO model) | |||
| { | |||
| _logger.LogInformation($"Start searching process with id = {model.ProcessId}"); | |||
| var process = await _context.SelectionProcesses.FindAsync(model.ProcessId); | |||
| if (process is null) | |||
| throw new EntityNotFoundException("Process does not exist."); | |||
| _logger.LogInformation($"Start searching user with id = {model.SchedulerId}"); | |||
| var user = await _userManager.FindByIdAsync(model.SchedulerId.ToString()); | |||
| if (user is null) | |||
| throw new EntityNotFoundException("Scheduler does not exist."); | |||
| _logger.LogInformation($"Setting user {user.FirstName} {user.LastName} as scheduler for process with id = {model.ProcessId}"); | |||
| process.SchedulerId = user.Id; | |||
| _logger.LogInformation("Processing changes..."); | |||
| await _context.SaveChangesAsync(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,66 @@ | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| public class TechnologyService : ITechnologyService | |||
| { | |||
| private readonly DatabaseContext _context; | |||
| private readonly ILogger<TechnologyService> _logger; | |||
| private readonly IMapper _mapper; | |||
| public TechnologyService(IMapper mapper, DatabaseContext context, ILogger<TechnologyService> logger) | |||
| { | |||
| _mapper = mapper; | |||
| _context = context; | |||
| _logger = logger; | |||
| } | |||
| public async Task<List<TechnologyResponseDto>> GetAllAsync() | |||
| { | |||
| _logger.LogInformation("Start getting all technologies"); | |||
| var technologies = await _context.Technologies.ToListAsync(); | |||
| _logger.LogInformation($"Received {technologies.Count} technologies from database"); | |||
| _logger.LogInformation("Mapping received technologies to TechnologyResponseDto"); | |||
| var technologiesDto = _mapper.Map<List<TechnologyResponseDto>>(technologies); | |||
| _logger.LogInformation($"Technologies mapped successfully"); | |||
| return technologiesDto; | |||
| } | |||
| public async Task<TechnologyResponseDto> GetByIdAsync(int id) | |||
| { | |||
| _logger.LogInformation($"Start searching Techology with id = {id}"); | |||
| var technology = await _context.Technologies.FindAsync(id); | |||
| if (technology is null) | |||
| { | |||
| _logger.LogError($"Technology with id = {id} not found"); | |||
| throw new EntityNotFoundException("Technology not found"); | |||
| } | |||
| _logger.LogInformation($"Mapping Technology with id = {id}"); | |||
| var result = _mapper.Map<TechnologyResponseDto>(technology); | |||
| _logger.LogInformation($"Technology with id = {id} mapped successfully"); | |||
| return result; | |||
| } | |||
| public async Task<Technology> GetEntityByIdAsync(int id) | |||
| { | |||
| _logger.LogInformation($"Start searching Ad with id = {id}"); | |||
| var technology = await _context.Technologies.FindAsync(id); | |||
| if (technology is null) | |||
| { | |||
| _logger.LogError($"Technology with id = {id} not found"); | |||
| throw new EntityNotFoundException("Technology not found"); | |||
| } | |||
| _logger.LogInformation($"Technology with id = {id} found successfully"); | |||
| return technology; | |||
| } | |||
| public async Task<List<Technology>> GetEntitiesAsync(int[] technologiesIds) | |||
| { | |||
| _logger.LogInformation("Start getting all technologies"); | |||
| var technologies = await _context.Technologies.Where(x => technologiesIds.Contains(x.TechnologyId)).ToListAsync(); | |||
| _logger.LogInformation($"Received {technologies.Count} technologies from database"); | |||
| return technologies; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,130 @@ | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| public class UserService : IUserService | |||
| { | |||
| private readonly FrontEndSettings _frontEndSettings; | |||
| private readonly UserManager<User> _userManager; | |||
| private readonly IMapper _mapper; | |||
| private readonly DatabaseContext _databaseContext; | |||
| private readonly IEmailer _emailer; | |||
| private readonly ILogger<UserService> _logger; | |||
| public UserService(IOptions<FrontEndSettings> frontEndSettings, UserManager<User> userManager, IMapper mapper, DatabaseContext databaseContext, IEmailer emailer, ILogger<UserService> logger) | |||
| { | |||
| _frontEndSettings = frontEndSettings.Value; | |||
| _userManager = userManager; | |||
| _mapper = mapper; | |||
| _databaseContext = databaseContext; | |||
| _emailer = emailer; | |||
| _logger = logger; | |||
| } | |||
| public async Task<IEnumerable<User?>> GetAll() | |||
| { | |||
| _logger.LogInformation("Start getting all users"); | |||
| _logger.LogInformation("Getting data from DB"); | |||
| var fromDb = await _userManager.Users.ToListAsync(); | |||
| _logger.LogInformation($"Received {fromDb.Count} ads from db."); | |||
| return fromDb; | |||
| } | |||
| public async Task<User> GetFirst() | |||
| { | |||
| var result = await _userManager.Users.FirstOrDefaultAsync(); | |||
| if (result == null) | |||
| throw new EntityNotFoundException("No users in database"); | |||
| return result; | |||
| } | |||
| #region REFACTORING CODE HERE TO CHECK IF USER IS NULL | |||
| public async Task<User> GetById(int id) | |||
| { | |||
| _logger.LogInformation($"Start searching user with id = {id}"); | |||
| var result = await _userManager.FindByIdAsync(id.ToString()); | |||
| if (result == null) | |||
| { | |||
| throw new EntityNotFoundException("User not found"); | |||
| } | |||
| return result; | |||
| } | |||
| public async Task<User> GetByEmail(string email) | |||
| { | |||
| _logger.LogInformation($"Start searching user with mail = {email}"); | |||
| var result = await _userManager.FindByEmailAsync(email); | |||
| if (result == null) | |||
| { | |||
| throw new EntityNotFoundException("User not found"); | |||
| } | |||
| return result; | |||
| } | |||
| #endregion | |||
| public async Task CreateUser(CreateUserRequestDto model) | |||
| { | |||
| _logger.LogInformation($"Start creating user"); | |||
| var user = _mapper.Map<User>(model); | |||
| _logger.LogInformation($"User created successfully"); | |||
| _logger.LogInformation($"Saving user to db..."); | |||
| await _userManager.CreateAsync(user, model.Password); | |||
| _logger.LogInformation($"User saved to DB"); | |||
| } | |||
| public async Task RemoveUser(User user) | |||
| { | |||
| await _userManager.DeleteAsync(user); | |||
| await _databaseContext.SaveChangesAsync(); | |||
| } | |||
| public async Task<bool?> ToggleEnable(User user) | |||
| { | |||
| user.IsEnabled = !user.IsEnabled; | |||
| await _databaseContext.SaveChangesAsync(); | |||
| return user.IsEnabled; | |||
| } | |||
| public async Task<ServiceResponseDTO<object>> SendRegistrationLink(InviteDTO invite) | |||
| { | |||
| // check if user exists | |||
| var check = await _userManager.FindByEmailAsync(invite.Email); | |||
| if (check != null) | |||
| return new ServiceResponseDTO<object>() | |||
| { | |||
| IsError = true, | |||
| ErrorMessage = "User already registered." | |||
| }; | |||
| // create template user | |||
| // this user is disabled to log in until confirming invitation | |||
| var user = new User | |||
| { | |||
| UserName = invite.Email, | |||
| Email = invite.Email, | |||
| FirstName = invite.FirstName, | |||
| LastName = invite.LastName, | |||
| IsEnabled = false | |||
| }; | |||
| await _userManager.CreateAsync(user, StringGenerator.GenerateRandomPassword()); | |||
| // generate invitation token for user | |||
| // encoded for URLs | |||
| var token = await _userManager.GeneratePasswordResetTokenAsync(user); | |||
| token = HttpUtility.UrlEncode(token); | |||
| // send link | |||
| await _emailer.SendEmailAndWriteToDbAsync(invite.Email, "Welcome", HTMLHelper.RenderRegisterPage($"{_frontEndSettings.BaseUrl}/register?token={token}&email={invite.Email}"), isHtml: true); | |||
| await _databaseContext.SaveChangesAsync(); | |||
| return new ServiceResponseDTO<object> | |||
| { | |||
| Data = new { Message = "Link has been sent!" } | |||
| }; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| using System.Diagnostics.CodeAnalysis; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class WebhookDefinitionService : IWebhookDefinitionService | |||
| { | |||
| private readonly DatabaseContext _context; | |||
| private readonly IMapper _mapper; | |||
| public WebhookDefinitionService(DatabaseContext context, IMapper mapper) | |||
| { | |||
| _context = context; | |||
| _mapper = mapper; | |||
| } | |||
| public async Task<List<WebhookDefinitionViewDto>> GetAll() | |||
| { | |||
| var webhookDefinitions = await _context.WebhookDefinitions.ToListAsync(); | |||
| return _mapper.Map<List<WebhookDefinitionViewDto>>(webhookDefinitions); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,49 @@ | |||
| using System.Diagnostics.CodeAnalysis; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class WebhookPublisherService : IWebhookPublisherService | |||
| { | |||
| private readonly IWebhookSubscriptionService _subscriptionService; | |||
| public WebhookPublisherService(IWebhookSubscriptionService subscriptionService) | |||
| { | |||
| _subscriptionService = subscriptionService; | |||
| } | |||
| public async Task PublishAsync(string webhookName, object data) | |||
| { | |||
| // Get all subscriptions | |||
| var subscriptions = await _subscriptionService.GetAllSubscriptionsAsync(webhookName); | |||
| if (subscriptions == null || subscriptions.Count == 0) | |||
| return; | |||
| // Send POST request to subscriptions | |||
| // 1: define POST request | |||
| var request = new RestRequest("", Method.Post); | |||
| // 2: define request body | |||
| var serializedOrder = JsonConvert.SerializeObject(data, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); | |||
| request.AddJsonBody(serializedOrder); | |||
| // 3: define request headers | |||
| request.AddHeader("Content-Type", "application/json"); | |||
| foreach (var subscription in subscriptions) | |||
| { | |||
| try | |||
| { | |||
| // 4: define client endpoint | |||
| var client = new RestClient(subscription.WebhookURL); | |||
| // 5: send | |||
| await client.ExecuteAsync(request); | |||
| } | |||
| catch (Exception) | |||
| { | |||
| // if one subscription fail continue | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| using System.Diagnostics.CodeAnalysis; | |||
| namespace Diligent.WebAPI.Business.Services | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class WebhookSubscriptionService : IWebhookSubscriptionService | |||
| { | |||
| private readonly DatabaseContext _context; | |||
| private readonly IMapper _mapper; | |||
| public WebhookSubscriptionService(DatabaseContext context, IMapper mapper) | |||
| { | |||
| _context = context; | |||
| _mapper = mapper; | |||
| } | |||
| public async Task<WebhookSubscription> CreateWebhookSubscription(WebhookSubscriptionCreateDto dto) | |||
| { | |||
| // map dto to db model | |||
| WebhookSubscription subscription = _mapper.Map<WebhookSubscription>(dto); | |||
| subscription.CreatedAtUtc = DateTime.Now; | |||
| subscription.IsActive = true; | |||
| // add to db | |||
| await _context.WebhookSubscriptions.AddAsync(subscription); | |||
| await _context.SaveChangesAsync(); | |||
| return subscription; | |||
| } | |||
| public async Task<List<WebhookSubscription>> GetAllSubscriptionsAsync(string webhookName) | |||
| { | |||
| return await _context.WebhookSubscriptions | |||
| .Where(x => x.WebhookDefinition.Name == webhookName) | |||
| .Include(x => x.WebhookDefinition) | |||
| .ToListAsync(); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal; | |||
| namespace Diligent.WebAPI.Business.Settings | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class AuthorizationSettings | |||
| { | |||
| public string Secret { get; set; } | |||
| public int JwtExpiredTime { get; set; } | |||
| public int JwtRefreshExpiredTime { get; set; } | |||
| public string GoogleClientId { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| namespace Diligent.WebAPI.Business.Settings | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class FrontEndSettings | |||
| { | |||
| public string BaseUrl { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| namespace Diligent.WebAPI.Business.Settings | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class MailSettings | |||
| { | |||
| public string SmtpFrom { get; set; } | |||
| public string SmtpFromName { get; set; } | |||
| public string SmtpServer { get; set; } | |||
| public int SmtpPort { get; set; } | |||
| public bool SmtpUseSSL { get; set; } | |||
| public string SmtpUsername { get; set; } | |||
| public string SmtpPassword { get; set; } | |||
| public string ResetPasswordUrl { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| namespace Diligent.WebAPI.Business.Settings | |||
| { | |||
| [ExcludeFromCodeCoverage] | |||
| public class ScreeningTestSettings | |||
| { | |||
| public string Url { get; set; } | |||
| public string Email { get; set; } | |||
| public string Password { get; set; } | |||
| public string Link { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,41 @@ | |||
| global using Diligent.WebAPI.Business.Services.Interfaces; | |||
| global using Diligent.WebAPI.Business.Settings; | |||
| global using Diligent.WebAPI.Business.Helper; | |||
| global using Diligent.WebAPI.Data; | |||
| global using Diligent.WebAPI.Data.Entities; | |||
| global using Diligent.WebAPI.Contracts.DTOs.InsuranceCompany; | |||
| global using Diligent.WebAPI.Contracts.DTOs.InsurancePolicy; | |||
| global using Diligent.WebAPI.Contracts.DTOs.Insurer; | |||
| global using Diligent.WebAPI.Contracts.DTOs.WebhookDefinition; | |||
| global using Diligent.WebAPI.Contracts.DTOs.WebhookSubscription; | |||
| global using Diligent.WebAPI.Contracts.DTOs.SelectionProcess; | |||
| global using Diligent.WebAPI.Contracts.DTOs.Ad; | |||
| global using Diligent.WebAPI.Contracts.DTOs.Auth; | |||
| global using Diligent.WebAPI.Contracts.DTOs.User; | |||
| global using Diligent.WebAPI.Contracts.DTOs; | |||
| global using Diligent.WebAPI.Contracts.Exceptions; | |||
| global using Diligent.WebAPI.Contracts.Models; | |||
| global using Diligent.WebAPI.Contracts.DTOs.Applicant; | |||
| global using Diligent.WebAPI.Contracts.DTOs.Technology; | |||
| global using Diligent.WebAPI.Contracts.DTOs.SelectionLevel; | |||
| global using Diligent.WebAPI.Contracts.DTOs.Comment; | |||
| global using Diligent.WebAPI.Business.Extensions; | |||
| global using Microsoft.EntityFrameworkCore; | |||
| global using Microsoft.Extensions.Options; | |||
| global using Microsoft.IdentityModel.Tokens; | |||
| global using System.IdentityModel.Tokens.Jwt; | |||
| global using System.Security.Claims; | |||
| global using System.Text; | |||
| global using Newtonsoft.Json; | |||
| global using RestSharp; | |||
| global using AutoMapper; | |||
| global using System.Web; | |||
| global using Microsoft.AspNetCore.Identity; | |||
| global using Microsoft.Extensions.Logging; | |||
| global using System.Diagnostics.CodeAnalysis; | |||
| @@ -0,0 +1,12 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Applicant; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Ad | |||
| { | |||
| public class AdApplicantsViewDto | |||
| { | |||
| public int Id { get; set; } | |||
| public string Title { get; set; } | |||
| public List<AdApplicantViewDto> Applicants { get; set; } | |||
| public int NubmerOfApplicants { get { return Applicants.Count; } } | |||
| } | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Ad | |||
| { | |||
| public class AdCreateDto | |||
| { | |||
| public string Title { get; set; } | |||
| public int MinimumExperience { get; set; } | |||
| public DateTime CreatedAt { get; set; } | |||
| public DateTime ExpiredAt { get; set; } | |||
| public string KeyResponsibilities { get; set; } | |||
| public string Requirements { get; set; } | |||
| public string Offer { get; set; } | |||
| public string WorkHour { get; set; } | |||
| public string EmploymentType { get; set; } | |||
| public List<int> TechnologiesIds { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,40 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Applicant; | |||
| using Diligent.WebAPI.Contracts.DTOs.Technology; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Ad | |||
| { | |||
| public class AdDetailsResponseDto | |||
| { | |||
| public int Id { get; set; } | |||
| public string Title { get; set; } | |||
| public int MinimumExperience { get; set; } | |||
| public DateTime CreatedAt { get; set; } | |||
| public DateTime ExpiredAt { get; set; } | |||
| public string KeyResponsibilities { get; set; } | |||
| public string Requirements { get; set; } | |||
| public string Offer { get; set; } | |||
| public int TotalApplicants { get { return CalculateTotalApplicants(); } } | |||
| public List<TechnologyResponseDto> Technologies { get; set; } | |||
| public List<ApplicantViewDto> Applicants { get; set; } | |||
| private int CalculateTotalApplicants() | |||
| { | |||
| return Applicants.Count; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Ad | |||
| { | |||
| public class AdFilterDto | |||
| { | |||
| public int MinExperience { get; set; } | |||
| public int MaxExperience { get; set; } | |||
| public string[]? Technologies { get; set; } | |||
| public string WorkHour { get; set; } | |||
| public string EmploymentType { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,34 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Technology; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Ad | |||
| { | |||
| public class AdResponseDto | |||
| { | |||
| public int Id { get; set; } | |||
| public string Title { get; set; } | |||
| public int MinimumExperience { get; set; } | |||
| public DateTime CreatedAt { get; set; } | |||
| public DateTime ExpiredAt { get; set; } | |||
| public string KeyResponsibilities { get; set; } | |||
| public string Requirements { get; set; } | |||
| public string Offer { get; set; } | |||
| public List<TechnologyResponseDto> Technologies { get; set; } | |||
| public string WorkHour { get; set; } | |||
| public string EmploymentType { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Technology; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Ad | |||
| { | |||
| public class AdResponseWithCountDto | |||
| { | |||
| public int Id { get; set; } | |||
| public string Title { get; set; } | |||
| public int MinimumExperience { get; set; } | |||
| public DateTime CreatedAt { get; set; } | |||
| public DateTime ExpiredAt { get; set; } | |||
| public int Count { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Ad | |||
| { | |||
| public class AdUpdateDto | |||
| { | |||
| public string Title { get; set; } | |||
| public int MinimumExperience { get; set; } | |||
| public DateTime CreatedAt { get; set; } | |||
| public DateTime ExpiredAt { get; set; } | |||
| public string KeyResponsibilities { get; set; } | |||
| public string Requirements { get; set; } | |||
| public string Offer { get; set; } | |||
| public string WorkHour { get; set; } | |||
| public string EmploymentType { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Ad | |||
| { | |||
| public class SelectionProcessFilterDto | |||
| { | |||
| public string[]? Statuses { get; set; } | |||
| public DateTime? DateStart { get; set; } | |||
| public DateTime? DateEnd { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Technology; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Applicant | |||
| { | |||
| public class AdApplicantViewDto | |||
| { | |||
| public int ApplicantId { get; set; } | |||
| public string FirstName { get; set; } | |||
| public string LastName { get; set; } | |||
| public DateTime DateOfApplication { get; set; } | |||
| public string CV { get; set; } | |||
| public int Experience { get; set; } | |||
| public List<TechnologyViewDto> TechnologyApplicants { get; set; } = new(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Applicant | |||
| { | |||
| public class ApplicantOptionsDTO | |||
| { | |||
| public int ApplicantId { get; set; } | |||
| public string FirstName { get; set; } | |||
| public string LastName { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| using Diligent.WebAPI.Contracts.Models; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Applicant | |||
| { | |||
| public class ApplicantFilterDto : Pagination | |||
| { | |||
| public int MinExperience { get; set; } | |||
| public int MaxExperience { get; set; } | |||
| public string[]? Technologies { get; set; } | |||
| public string? EmploymentType { get; set; } | |||
| public DateTime? MinDateOfApplication { get; set; } | |||
| public DateTime? MaxDateOfApplication { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Applicant | |||
| { | |||
| public class ApplicantImportDto | |||
| { | |||
| public string FirstName { get; set; } | |||
| public string LastName { get; set; } | |||
| public string Position { get; set; } | |||
| public string CV { get; set; } | |||
| public DateTime DateOfApplication { get; set; } | |||
| public string Email { get; set; } | |||
| public string PhoneNumber { get; set; } | |||
| public string LinkedlnLink { get; set; } | |||
| public string GithubLink { get; set; } | |||
| public string BitBucketLink { get; set; } | |||
| public int Experience { get; set; } | |||
| public string ApplicationChannel { get; set; } | |||
| public string TypeOfEmployment { get; set; } | |||
| public string Comment { get; set; } | |||
| public Diligent.WebAPI.Data.Entities.Ad Ad { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Applicant | |||
| { | |||
| public class ApplicantProcessRequestDTO | |||
| { | |||
| [Required] | |||
| public int ApplicantId { get; set; } | |||
| public int? SchedulerId { get; set; } | |||
| public DateTime? Appointment { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Applicant | |||
| { | |||
| public class ApplicantScheduleViewDto | |||
| { | |||
| public int ApplicantId { get; set; } | |||
| public string FirstName { get; set; } | |||
| public string LastName { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,31 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.Ad; | |||
| using Diligent.WebAPI.Contracts.DTOs.Comment; | |||
| using Diligent.WebAPI.Contracts.DTOs.SelectionProcess; | |||
| using Diligent.WebAPI.Contracts.DTOs.Technology; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Applicant | |||
| { | |||
| public class ApplicantViewDto | |||
| { | |||
| public int ApplicantId { get; set; } | |||
| public string FirstName { get; set; } | |||
| public string LastName { get; set; } | |||
| public string Position { get; set; } | |||
| public DateTime DateOfApplication { get; set; } | |||
| public string CV { get; set; } | |||
| public string Email { get; set; } | |||
| public string PhoneNumber { get; set; } | |||
| public string LinkedlnLink { get; set; } | |||
| public string GithubLink { get; set; } | |||
| public string BitBucketLink { get; set; } | |||
| public int Experience { get; set; } | |||
| public string ApplicationChannel { get; set; } | |||
| public string TypeOfEmployment { get; set; } | |||
| public string Gender { get; set; } | |||
| public string ProfessionalQualification { get; set; } | |||
| public List<TechnologyViewDto> TechnologyApplicants { get; set; } = new(); | |||
| public List<CommentViewDto> Comments { get; set; } | |||
| public List<AdResponseDto> Ads { get; set; } | |||
| public List<SelectionProcessResposneWithoutApplicantDto> SelectionProcesses { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,40 @@ | |||
| using Microsoft.AspNetCore.Http; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Applicant | |||
| { | |||
| public class ApplyForAdRequestDto | |||
| { | |||
| public int AdId { get; set; } | |||
| public string FirstName { get; set; } | |||
| public string LastName { get; set; } | |||
| public DateTime DateOfBirth { get; set; } | |||
| public string PhoneNumber { get; set; } | |||
| public int[] TechnologiesIds { get; set; } | |||
| public int Experience { get; set; } | |||
| public string LinkedinLink { get; set; } | |||
| public string GithubLink { get; set; } | |||
| public string BitBucketLink { get; set; } | |||
| public string Email { get; set; } | |||
| public string CoverLetter { get; set; } | |||
| public IFormFile PdfFile { get; set; } | |||
| public string Gender { get; set; } | |||
| public string ProfessionalQualification { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Applicant | |||
| { | |||
| public class PatternApplicantViewDto | |||
| { | |||
| public int ApplicantId { get; set; } | |||
| public string FirstName { get; set; } | |||
| public string LastName { get; set; } | |||
| public string Email { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Auth | |||
| { | |||
| public class AuthFailedResponse | |||
| { | |||
| public string Error { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Auth | |||
| { | |||
| public class AuthenticateRequestDto | |||
| { | |||
| [Required] | |||
| public string Username { get; set; } | |||
| [Required] | |||
| public string Password { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Auth | |||
| { | |||
| public class AuthenticateResponseDto | |||
| { | |||
| public long Id { get; set; } | |||
| public string FirstName { get; set; } | |||
| public string LastName { get; set; } | |||
| public string Username { get; set; } | |||
| public string Token { get; set; } | |||
| public string RefreshToken { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Auth | |||
| { | |||
| public class CreateUserRequestDto | |||
| { | |||
| public string FirstName { get; set; } | |||
| public string LastName { get; set; } | |||
| public string UserName { get; set; } | |||
| public string Email { get; set; } | |||
| public string Password { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Auth | |||
| { | |||
| public class RefreshTokenRequestDto | |||
| { | |||
| public string Token { get; set; } | |||
| public string RefreshToken { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using System.Text; | |||
| using System.Threading.Tasks; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Auth | |||
| { | |||
| public class RefreshTokenResultDto | |||
| { | |||
| public AuthenticateResponseDto? Data { get; set; } | |||
| public string? Error { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,10 @@ | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Comment | |||
| { | |||
| public class CommentCreateDto | |||
| { | |||
| public string Content { get; set; } | |||
| public int UserId { get; set; } | |||
| public int ApplicantId { get; set; } | |||
| public List<string> UsersToNotify { get; set; } //email | |||
| } | |||
| } | |||
| @@ -0,0 +1,11 @@ | |||
| using Diligent.WebAPI.Contracts.DTOs.User; | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Comment | |||
| { | |||
| public class CommentViewDto | |||
| { | |||
| public string Content { get; set; } | |||
| public DateTime DateOfSending { get; set; } | |||
| public UserResponseDTO User { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| namespace Diligent.WebAPI.Contracts.DTOs.Error | |||
| { | |||
| public class ErrorResponseDto | |||
| { | |||
| public string Message { get; set; } | |||
| public List<ValidationItemDto> ValidationItems { get; set; } = new List<ValidationItemDto>(); | |||
| public ErrorResponseDto(string message) | |||
| { | |||
| Message = message; | |||
| } | |||
| public ErrorResponseDto(string message, List<ValidationItemDto> validationItems) | |||
| { | |||
| Message = message; | |||
| ValidationItems = validationItems; | |||
| } | |||
| } | |||
| } | |||