Add public search page / initial API (#5)
This commit is contained in:
parent
4a73927e64
commit
fb147888c5
|
@ -5,7 +5,6 @@ using Microsoft.AspNetCore.WebUtilities;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace JobsJobsJobs.Client.Pages.Profiles
|
namespace JobsJobsJobs.Client.Pages.Profiles
|
||||||
|
@ -56,10 +55,10 @@ namespace JobsJobsJobs.Client.Pages.Profiles
|
||||||
{
|
{
|
||||||
if (query.TryGetValue(part, out var partValue)) func(partValue);
|
if (query.TryGetValue(part, out var partValue)) func(partValue);
|
||||||
}
|
}
|
||||||
setPart("ContinentId", x => Criteria.ContinentId = x);
|
setPart(nameof(Criteria.ContinentId), x => Criteria.ContinentId = x);
|
||||||
setPart("Skill", x => Criteria.Skill = x);
|
setPart(nameof(Criteria.Skill), x => Criteria.Skill = x);
|
||||||
setPart("BioExperience", x => Criteria.BioExperience = x);
|
setPart(nameof(Criteria.BioExperience), x => Criteria.BioExperience = x);
|
||||||
setPart("RemoteWork", x => Criteria.RemoteWork = x);
|
setPart(nameof(Criteria.RemoteWork), x => Criteria.RemoteWork = x);
|
||||||
|
|
||||||
await RetrieveProfiles();
|
await RetrieveProfiles();
|
||||||
}
|
}
|
||||||
|
@ -100,6 +99,11 @@ namespace JobsJobsJobs.Client.Pages.Profiles
|
||||||
Searching = false;
|
Searching = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return a CSS class if the user is actively seeking work
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="profile">The result in question</param>
|
||||||
|
/// <returns>A string with the appropriate CSS class, if actively seeking work</returns>
|
||||||
private static string? IsSeeking(ProfileSearchResult profile) =>
|
private static string? IsSeeking(ProfileSearchResult profile) =>
|
||||||
profile.SeekingEmployment ? "font-weight-bold" : null;
|
profile.SeekingEmployment ? "font-weight-bold" : null;
|
||||||
|
|
||||||
|
@ -124,10 +128,10 @@ namespace JobsJobsJobs.Client.Pages.Profiles
|
||||||
if (!string.IsNullOrEmpty(func(Criteria))) dict.Add(name, func(Criteria));
|
if (!string.IsNullOrEmpty(func(Criteria))) dict.Add(name, func(Criteria));
|
||||||
}
|
}
|
||||||
|
|
||||||
part("ContinentId", it => it.ContinentId);
|
part(nameof(Criteria.ContinentId), it => it.ContinentId);
|
||||||
part("Skill", it => it.Skill);
|
part(nameof(Criteria.Skill), it => it.Skill);
|
||||||
part("BioExperience", it => it.BioExperience);
|
part(nameof(Criteria.BioExperience), it => it.BioExperience);
|
||||||
part("RemoteWork", it => it.RemoteWork);
|
part(nameof(Criteria.RemoteWork), it => it.RemoteWork);
|
||||||
|
|
||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
|
|
56
src/JobsJobsJobs/Client/Pages/Profiles/Seeking.razor
Normal file
56
src/JobsJobsJobs/Client/Pages/Profiles/Seeking.razor
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
@page "/profile/seeking"
|
||||||
|
@inject HttpClient http
|
||||||
|
@inject NavigationManager nav
|
||||||
|
@inject AppState state
|
||||||
|
|
||||||
|
<PageTitle Title="People Seeking Work" />
|
||||||
|
<h3>People Seeking Work</h3>
|
||||||
|
|
||||||
|
<ErrorList Errors=@ErrorMessages>
|
||||||
|
@if (Searching)
|
||||||
|
{
|
||||||
|
<p>Searching profiles...</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!Searched)
|
||||||
|
{
|
||||||
|
<p>Enter one or more criteria to filter results, or just click “Search” to list all profiles.</p>
|
||||||
|
}
|
||||||
|
<Collapsible HeaderText="Search Criteria" Collapsed=@(Searched && SearchResults.Any())>
|
||||||
|
<PublicSearchForm Criteria=@Criteria OnSearch=@DoSearch Continents=@Continents />
|
||||||
|
</Collapsible>
|
||||||
|
<br>
|
||||||
|
@if (SearchResults.Any())
|
||||||
|
{
|
||||||
|
<table class="table table-sm table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Profile</th>
|
||||||
|
<th scope="col">Continent</th>
|
||||||
|
<th scope="col" class="text-center">Region</th>
|
||||||
|
<th scope="col" class="text-center">Remote?</th>
|
||||||
|
<th scope="col" class="text-center">Skills</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var profile in SearchResults)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td><a href="/profile/bio/@profile.CitizenId">View</a></td>
|
||||||
|
<td class=@IsSeeking(profile)>@profile.DisplayName</td>
|
||||||
|
<td class="text-center">@YesOrNo(profile.SeekingEmployment)</td>
|
||||||
|
<td class="text-center">@YesOrNo(profile.RemoteWork)</td>
|
||||||
|
<td class="text-center">@YesOrNo(profile.FullTime)</td>
|
||||||
|
<td><FullDate TheDate=@profile.LastUpdated /></td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
else if (Searched)
|
||||||
|
{
|
||||||
|
<p>No results found for the specified criteria</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</ErrorList>
|
134
src/JobsJobsJobs/Client/Pages/Profiles/Seeking.razor.cs
Normal file
134
src/JobsJobsJobs/Client/Pages/Profiles/Seeking.razor.cs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
using JobsJobsJobs.Shared;
|
||||||
|
using JobsJobsJobs.Shared.Api;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.WebUtilities;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace JobsJobsJobs.Client.Pages.Profiles
|
||||||
|
{
|
||||||
|
public partial class Seeking : ComponentBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether a search has been performed
|
||||||
|
/// </summary>
|
||||||
|
private bool Searched { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether a request for matching profiles is in progress
|
||||||
|
/// </summary>
|
||||||
|
private bool Searching { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The search criteria
|
||||||
|
/// </summary>
|
||||||
|
private PublicSearch Criteria { get; set; } = new PublicSearch();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Error messages encountered while searching for profiles
|
||||||
|
/// </summary>
|
||||||
|
private IList<string> ErrorMessages { get; } = new List<string>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All continents
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerable<Continent> Continents { get; set; } = Enumerable.Empty<Continent>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The search results
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerable<ProfileSearchResult> SearchResults { get; set; } = Enumerable.Empty<ProfileSearchResult>();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
Continents = await state.GetContinents(http);
|
||||||
|
|
||||||
|
// Determine if we have searched before
|
||||||
|
var query = QueryHelpers.ParseQuery(nav.ToAbsoluteUri(nav.Uri).Query);
|
||||||
|
|
||||||
|
if (query.TryGetValue("Searched", out var searched))
|
||||||
|
{
|
||||||
|
Searched = Convert.ToBoolean(searched);
|
||||||
|
void setPart(string part, Action<string> func)
|
||||||
|
{
|
||||||
|
if (query.TryGetValue(part, out var partValue)) func(partValue);
|
||||||
|
}
|
||||||
|
setPart(nameof(Criteria.ContinentId), x => Criteria.ContinentId = x);
|
||||||
|
setPart(nameof(Criteria.Region), x => Criteria.Region = x);
|
||||||
|
setPart(nameof(Criteria.Skill), x => Criteria.Skill = x);
|
||||||
|
setPart(nameof(Criteria.RemoteWork), x => Criteria.RemoteWork = x);
|
||||||
|
|
||||||
|
await RetrieveProfiles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Do a search
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This navigates with the parameters in the URL; this should trigger a search</remarks>
|
||||||
|
private async Task DoSearch()
|
||||||
|
{
|
||||||
|
var query = SearchQuery();
|
||||||
|
query.Add("Searched", "True");
|
||||||
|
nav.NavigateTo(QueryHelpers.AddQueryString("/profile/search", query));
|
||||||
|
await RetrieveProfiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retreive profiles matching the current search criteria
|
||||||
|
/// </summary>
|
||||||
|
private async Task RetrieveProfiles()
|
||||||
|
{
|
||||||
|
Searching = true;
|
||||||
|
|
||||||
|
var searchResult = await ServerApi.RetrieveMany<ProfileSearchResult>(http,
|
||||||
|
QueryHelpers.AddQueryString("profile/search", SearchQuery()));
|
||||||
|
|
||||||
|
if (searchResult.IsOk)
|
||||||
|
{
|
||||||
|
SearchResults = searchResult.Ok;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ErrorMessages.Add(searchResult.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Searched = true;
|
||||||
|
Searching = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? IsSeeking(ProfileSearchResult profile) =>
|
||||||
|
profile.SeekingEmployment ? "font-weight-bold" : null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return "Yes" for true and "No" for false
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="condition">The condition in question</param>
|
||||||
|
/// <returns>"Yes" for true, "No" for false</returns>
|
||||||
|
private static string YesOrNo(bool condition) => condition ? "Yes" : "No";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a search query string from the currently-entered criteria
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The query string for the currently-entered criteria</returns>
|
||||||
|
private IDictionary<string, string?> SearchQuery()
|
||||||
|
{
|
||||||
|
var dict = new Dictionary<string, string?>();
|
||||||
|
if (Criteria.IsEmptySearch) return dict;
|
||||||
|
|
||||||
|
void part(string name, Func<PublicSearch, string?> func)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(func(Criteria))) dict.Add(name, func(Criteria));
|
||||||
|
}
|
||||||
|
|
||||||
|
part(nameof(Criteria.ContinentId), it => it.ContinentId);
|
||||||
|
part(nameof(Criteria.Region), it => it.Region);
|
||||||
|
part(nameof(Criteria.Skill), it => it.Skill);
|
||||||
|
part(nameof(Criteria.RemoteWork), it => it.RemoteWork);
|
||||||
|
|
||||||
|
return dict;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,11 @@
|
||||||
<span class="oi oi-home" aria-hidden="true"></span> Home
|
<span class="oi oi-home" aria-hidden="true"></span> Home
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item px-3">
|
||||||
|
<NavLink class="nav-link" href="/profile/seeking">
|
||||||
|
<span class="oi oi-spreadsheet" aria-hidden="true"></span> Job Seekers
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item px-3">
|
||||||
<a class="nav-link" href="@AuthUrl">
|
<a class="nav-link" href="@AuthUrl">
|
||||||
<span class="oi oi-account-login" aria-hidden="true"></span> Log On
|
<span class="oi oi-account-login" aria-hidden="true"></span> Log On
|
||||||
|
|
60
src/JobsJobsJobs/Client/Shared/PublicSearchForm.razor
Normal file
60
src/JobsJobsJobs/Client/Shared/PublicSearchForm.razor
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
@using JobsJobsJobs.Shared.Api
|
||||||
|
|
||||||
|
<EditForm Model=@Criteria OnValidSubmit=@OnSearch>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col col-12 col-sm-6 col-md-4 col-lg-3">
|
||||||
|
<label for="continentId" class="jjj-label">Continent</label>
|
||||||
|
<InputSelect id="continentId" @bind-Value=@Criteria.ContinentId class="form-control form-control-sm">
|
||||||
|
<option value="">– Any –</option>
|
||||||
|
@foreach (var (id, name) in Continents)
|
||||||
|
{
|
||||||
|
<option value="@id">@name</option>
|
||||||
|
}
|
||||||
|
</InputSelect>
|
||||||
|
</div>
|
||||||
|
<div class="col col-12 col-sm-6 col-md-4 col-lg-3">
|
||||||
|
<label for="region" class="jjj-label">Region</label>
|
||||||
|
<InputText id="region" @bind-Value=@Criteria.Region class="form-control form-control-sm"
|
||||||
|
placeholder="(free-form text)" />
|
||||||
|
</div>
|
||||||
|
<div class="col col-12 col-sm-6 offset-md-2 col-lg-3 offset-lg-0">
|
||||||
|
<label class="jjj-label">Seeking Remote Work?</label><br>
|
||||||
|
<InputRadioGroup @bind-Value=@Criteria.RemoteWork>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<InputRadio id="remoteNull" Value=@("") class="form-check-input" />
|
||||||
|
<label for="remoteNull" class="form-check-label">No Selection</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<InputRadio id="remoteYes" Value=@("yes") class="form-check-input" />
|
||||||
|
<label for="remoteYes" class="form-check-label">Yes</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<InputRadio id="remoteNo" Value=@("no") class="form-check-input" />
|
||||||
|
<label for="remoteNo" class="form-check-label">No</label>
|
||||||
|
</div>
|
||||||
|
</InputRadioGroup>
|
||||||
|
</div>
|
||||||
|
<div class="col col-12 col-sm-6 col-lg-3">
|
||||||
|
<label for="skillSearch" class="jjj-label">Skill</label>
|
||||||
|
<InputText id="skillSearch" @bind-Value=@Criteria.Skill class="form-control form-control-sm"
|
||||||
|
placeholder="(free-form text)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col">
|
||||||
|
<br>
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-primary">Search</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</EditForm>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter]
|
||||||
|
public PublicSearch Criteria { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public EventCallback OnSearch { get; set; } = default!;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public IEnumerable<Continent> Continents { get; set; } = default!;
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
using JobsJobsJobs.Server.Data;
|
using JobsJobsJobs.Server.Data;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
@ -9,7 +8,6 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers
|
||||||
/// API endpoint for continent information
|
/// API endpoint for continent information
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
[Authorize]
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class ContinentController : ControllerBase
|
public class ContinentController : ControllerBase
|
||||||
{
|
{
|
||||||
|
|
|
@ -120,6 +120,11 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers
|
||||||
public async Task<IActionResult> Search([FromQuery] ProfileSearch search) =>
|
public async Task<IActionResult> Search([FromQuery] ProfileSearch search) =>
|
||||||
Ok(await _db.SearchProfiles(search));
|
Ok(await _db.SearchProfiles(search));
|
||||||
|
|
||||||
|
[HttpGet("public-search")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<IActionResult> SearchPublic([FromQuery] PublicSearch search) =>
|
||||||
|
Ok(await _db.SearchPublicProfiles(search));
|
||||||
|
|
||||||
[HttpPatch("employment-found")]
|
[HttpPatch("employment-found")]
|
||||||
public async Task<IActionResult> EmploymentFound()
|
public async Task<IActionResult> EmploymentFound()
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,38 +12,36 @@ namespace JobsJobsJobs.Server.Data
|
||||||
/// Citizen ID converter
|
/// Citizen ID converter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly ValueConverter<CitizenId, string> CitizenIdConverter =
|
public static readonly ValueConverter<CitizenId, string> CitizenIdConverter =
|
||||||
new ValueConverter<CitizenId, string>(v => v.ToString(), v => CitizenId.Parse(v));
|
new(v => v.ToString(), v => CitizenId.Parse(v));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Continent ID converter
|
/// Continent ID converter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly ValueConverter<ContinentId, string> ContinentIdConverter =
|
public static readonly ValueConverter<ContinentId, string> ContinentIdConverter =
|
||||||
new ValueConverter<ContinentId, string>(v => v.ToString(), v => ContinentId.Parse(v));
|
new(v => v.ToString(), v => ContinentId.Parse(v));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Markdown converter
|
/// Markdown converter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly ValueConverter<MarkdownString, string> MarkdownStringConverter =
|
public static readonly ValueConverter<MarkdownString, string> MarkdownStringConverter =
|
||||||
new ValueConverter<MarkdownString, string>(v => v.Text, v => new MarkdownString(v));
|
new(v => v.Text, v => new MarkdownString(v));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Markdown converter for possibly-null values
|
/// Markdown converter for possibly-null values
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly ValueConverter<MarkdownString?, string?> OptionalMarkdownStringConverter =
|
public static readonly ValueConverter<MarkdownString?, string?> OptionalMarkdownStringConverter =
|
||||||
new ValueConverter<MarkdownString?, string?>(
|
new(v => v == null ? null : v.Text, v => v == null ? null : new MarkdownString(v));
|
||||||
v => v == null ? null : v.Text,
|
|
||||||
v => v == null ? null : new MarkdownString(v));
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Skill ID converter
|
/// Skill ID converter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly ValueConverter<SkillId, string> SkillIdConverter =
|
public static readonly ValueConverter<SkillId, string> SkillIdConverter =
|
||||||
new ValueConverter<SkillId, string>(v => v.ToString(), v => SkillId.Parse(v));
|
new(v => v.ToString(), v => SkillId.Parse(v));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Success ID converter
|
/// Success ID converter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly ValueConverter<SuccessId, string> SuccessIdConverter =
|
public static readonly ValueConverter<SuccessId, string> SuccessIdConverter =
|
||||||
new ValueConverter<SuccessId, string>(v => v.ToString(), v => SuccessId.Parse(v));
|
new(v => v.ToString(), v => SuccessId.Parse(v));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,7 +111,7 @@ namespace JobsJobsJobs.Server.Data
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Search profiles by the given criteria
|
/// Search profiles by the given criteria
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// TODO: A criteria parameter!
|
/// <param name="search">The search parameters</param>
|
||||||
/// <returns>The information for profiles matching the criteria</returns>
|
/// <returns>The information for profiles matching the criteria</returns>
|
||||||
public static async Task<IEnumerable<ProfileSearchResult>> SearchProfiles(this JobsDbContext db,
|
public static async Task<IEnumerable<ProfileSearchResult>> SearchProfiles(this JobsDbContext db,
|
||||||
ProfileSearch search)
|
ProfileSearch search)
|
||||||
|
@ -160,7 +160,56 @@ namespace JobsJobsJobs.Server.Data
|
||||||
x.Profile.SeekingEmployment, x.Profile.RemoteWork, x.Profile.FullTime, x.Profile.LastUpdatedOn))
|
x.Profile.SeekingEmployment, x.Profile.RemoteWork, x.Profile.FullTime, x.Profile.LastUpdatedOn))
|
||||||
.ToListAsync().ConfigureAwait(false);
|
.ToListAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Search public profiles by the given criteria
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="search">The search parameters</param>
|
||||||
|
/// <returns>The information for profiles matching the criteria</returns>
|
||||||
|
public static async Task<IEnumerable<ProfileSearchResult>> SearchPublicProfiles(this JobsDbContext db,
|
||||||
|
PublicSearch search)
|
||||||
|
{
|
||||||
|
var query = db.Profiles
|
||||||
|
.Join(db.Citizens, p => p.Id, c => c.Id, (p, c) => new { Profile = p, Citizen = c })
|
||||||
|
.Where(it => it.Profile.IsPublic);
|
||||||
|
|
||||||
|
var useIds = false;
|
||||||
|
var citizenIds = new List<CitizenId>();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(search.ContinentId))
|
||||||
|
{
|
||||||
|
query = query.Where(it => it.Profile.ContinentId == ContinentId.Parse(search.ContinentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(search.Region))
|
||||||
|
{
|
||||||
|
query = query.Where(it => it.Profile.Region.ToLower().Contains(search.Region.ToLower()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(search.RemoteWork))
|
||||||
|
{
|
||||||
|
query = query.Where(it => it.Profile.RemoteWork == (search.RemoteWork == "yes"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(search.Skill))
|
||||||
|
{
|
||||||
|
useIds = true;
|
||||||
|
citizenIds.AddRange(await db.Skills
|
||||||
|
.Where(s => s.Description.ToLower().Contains(search.Skill.ToLower()))
|
||||||
|
.Select(s => s.CitizenId)
|
||||||
|
.ToListAsync().ConfigureAwait(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useIds)
|
||||||
|
{
|
||||||
|
query = query.Where(it => citizenIds.Contains(it.Citizen.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query.Select(x => new ProfileSearchResult(x.Citizen.Id, x.Citizen.CitizenName,
|
||||||
|
x.Profile.SeekingEmployment, x.Profile.RemoteWork, x.Profile.FullTime, x.Profile.LastUpdatedOn))
|
||||||
|
.ToListAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delete skills and profile for the given citizen
|
/// Delete skills and profile for the given citizen
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<ActiveDebugProfile>JobsJobsJobs.Server</ActiveDebugProfile>
|
<ActiveDebugProfile>JobsJobsJobs.Server</ActiveDebugProfile>
|
||||||
<Controller_SelectedScaffolderID>MvcControllerEmptyScaffolder</Controller_SelectedScaffolderID>
|
<Controller_SelectedScaffolderID>MvcControllerEmptyScaffolder</Controller_SelectedScaffolderID>
|
||||||
<Controller_SelectedScaffolderCategoryPath>root/Common/MVC/Controller</Controller_SelectedScaffolderCategoryPath>
|
<Controller_SelectedScaffolderCategoryPath>root/Common/MVC/Controller</Controller_SelectedScaffolderCategoryPath>
|
||||||
<NameOfLastUsedPublishProfile>FolderProfile</NameOfLastUsedPublishProfile>
|
<NameOfLastUsedPublishProfile>C:\Users\danie\Documents\sandbox\jobs-jobs-jobs\src\JobsJobsJobs\Server\Properties\PublishProfiles\FolderProfile.pubxml</NameOfLastUsedPublishProfile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
||||||
|
|
37
src/JobsJobsJobs/Shared/Api/PublicSearch.cs
Normal file
37
src/JobsJobsJobs/Shared/Api/PublicSearch.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
namespace JobsJobsJobs.Shared.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The parameters for a public job search
|
||||||
|
/// </summary>
|
||||||
|
public class PublicSearch
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve citizens from this continent
|
||||||
|
/// </summary>
|
||||||
|
public string? ContinentId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve citizens from this region
|
||||||
|
/// </summary>
|
||||||
|
public string? Region { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Text for a search within a citizen's skills
|
||||||
|
/// </summary>
|
||||||
|
public string? Skill { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to retrieve citizens who do or do not want remote work
|
||||||
|
/// </summary>
|
||||||
|
public string RemoteWork { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is the search empty?
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEmptySearch =>
|
||||||
|
string.IsNullOrEmpty(ContinentId)
|
||||||
|
&& string.IsNullOrEmpty(Region)
|
||||||
|
&& string.IsNullOrEmpty(Skill)
|
||||||
|
&& string.IsNullOrEmpty(RemoteWork);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user