Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b98d28adb4 | |||
| 60ed7e1e79 | |||
| fb147888c5 | |||
| 4a73927e64 |
@@ -1,3 +1,5 @@
|
|||||||
# jobs-jobs-jobs
|
# Jobs, Jobs, Jobs <small>_(and Jobs - Let's Vote for Jobs!)_</small>
|
||||||
|
|
||||||
Repository for the development of No Agenda Jobs - currently parked [here](http://jobs.bitbadger.solutions)
|
Source repository for **Jobs, Jobs, Jobs**, the jobs and career site for No Agenda nation.
|
||||||
|
|
||||||
|
What is No Agenda? [So glad you asked!](https://noagendashow.net)
|
||||||
|
|||||||
2
src/.dockerignore
Normal file
2
src/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
**/bin
|
||||||
|
**/obj
|
||||||
10
src/Dockerfile
Normal file
10
src/Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
|
||||||
|
WORKDIR /jjj
|
||||||
|
COPY . ./
|
||||||
|
WORKDIR /jjj/JobsJobsJobs/Server
|
||||||
|
RUN dotnet publish JobsJobsJobs.Server.csproj -c Release /p:PublishProfile=Properties/PublishProfiles/FolderProfile.xml
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:5.0
|
||||||
|
WORKDIR /jjj
|
||||||
|
COPY --from=build /jjj/JobsJobsJobs/Server/bin/Release/net5.0/linux-x64/publish/ ./
|
||||||
|
ENTRYPOINT [ "/jjj/JobsJobsJobs.Server" ]
|
||||||
@@ -11,8 +11,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JobsJobsJobs.Shared", "Jobs
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{50B51580-9F09-41E2-BC78-DAD38C37B583}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{50B51580-9F09-41E2-BC78-DAD38C37B583}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
.dockerignore = .dockerignore
|
||||||
database\12-add-real-name.sql = database\12-add-real-name.sql
|
database\12-add-real-name.sql = database\12-add-real-name.sql
|
||||||
JobsJobsJobs\Directory.Build.props = JobsJobsJobs\Directory.Build.props
|
JobsJobsJobs\Directory.Build.props = JobsJobsJobs\Directory.Build.props
|
||||||
|
Dockerfile = Dockerfile
|
||||||
database\tables.sql = database\tables.sql
|
database\tables.sql = database\tables.sql
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace JobsJobsJobs.Client
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The application version, as a nice display string
|
/// The application version, as a nice display string
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Lazy<string> Version => new Lazy<string>(() =>
|
public static Lazy<string> Version => new(() =>
|
||||||
{
|
{
|
||||||
var version = Assembly.GetExecutingAssembly().GetName().Version!;
|
var version = Assembly.GetExecutingAssembly().GetName().Version!;
|
||||||
var display = $"v{version.Major}.{version.Minor}";
|
var display = $"v{version.Major}.{version.Minor}";
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
{
|
{
|
||||||
<p>
|
<p>
|
||||||
Your employment profile was last updated <FullDateTime TheDate=@Profile.LastUpdatedOn />. Your profile currently
|
Your employment profile was last updated <FullDateTime TheDate=@Profile.LastUpdatedOn />. Your profile currently
|
||||||
lists @Profile.Skills.Length skill@(Profile.Skills.Length != 1 ? "s" : "").
|
lists @Profile.Skills.Count skill@(Profile.Skills.Count != 1 ? "s" : "").
|
||||||
</p>
|
</p>
|
||||||
<p><a href="/profile/view/@state.User.Id">View Your Employment Profile</a></p>
|
<p><a href="/profile/view/@state.User.Id">View Your Employment Profile</a></p>
|
||||||
@if (Profile.SeekingEmployment)
|
@if (Profile.SeekingEmployment)
|
||||||
@@ -41,7 +41,6 @@
|
|||||||
</Loading>
|
</Loading>
|
||||||
<hr>
|
<hr>
|
||||||
<p>
|
<p>
|
||||||
To see what is currently done, and how this application works, check out “How It Works” in the sidebar.
|
To see how this application works, check out “How It Works” in the sidebar (last updated June
|
||||||
The application now has 4 of 5 phases complete towards version 1.0; the documentation was last updated January
|
14<sup>th</sup>, 2021).
|
||||||
31<sup>st</sup>, 2021.
|
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -4,18 +4,6 @@
|
|||||||
|
|
||||||
<h3>How It Works</h3>
|
<h3>How It Works</h3>
|
||||||
|
|
||||||
<p>
|
|
||||||
Phase 4 is complete, which means that the entire logged-in version of the application is now available. There are
|
|
||||||
GitHub issues for each one
|
|
||||||
(<a href="https://github.com/bit-badger/jobs-jobs-jobs/issues/1" target="_blank">Phase 1</a> •
|
|
||||||
<a href="https://github.com/bit-badger/jobs-jobs-jobs/issues/2" target="_blank">Phase 2</a> •
|
|
||||||
<a href="https://github.com/bit-badger/jobs-jobs-jobs/issues/3" target="_blank">Phase 3</a> •
|
|
||||||
<a href="https://github.com/bit-badger/jobs-jobs-jobs/issues/4" target="_blank">Phase 4</a>), and if you run into any
|
|
||||||
issues with it, feel free to
|
|
||||||
<a href="https://github.com/bit-badger/jobs-jobs-jobs/issues" target="_blank">let us know on GitHub</a>, or look up
|
|
||||||
@("@")danieljsummers on No Agenda Social.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h4>Completing Your Profile</h4>
|
<h4>Completing Your Profile</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
@@ -44,11 +32,10 @@
|
|||||||
would like it presented to fellow citizens.
|
would like it presented to fellow citizens.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://github.com/bit-badger/jobs-jobs-jobs/issues/5" target="_blank">Phase 5</a> includes allowing
|
If you check the “Allow my profile to be searched publicly” checkbox, <strong>and</strong> you are
|
||||||
public access to the continent, region, and skills fields of Gitmo Nation citizens who indicate that they are both
|
seeking employment, your continent, region, and skills fields will be searchable and displayed to public users of
|
||||||
seeking employment <strong>and</strong> want their information disclosed to public users. The “Allow my
|
the site. They will not be tied to your No Agenda Social handle or real name; they are there to let people peek
|
||||||
profile to be searched publicly” checkbox, at the bottom of the page where you edit your employment profile,
|
behind the curtain a bit, and hopefully inspire them to join us.
|
||||||
is how you opt your profile in to this list.
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@@ -77,19 +64,18 @@
|
|||||||
to read it; if you submitted the story, there will also be an “Edit” link.
|
to read it; if you submitted the story, there will also be an “Edit” link.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h4>
|
<h4>Publicly Available Information</h4>
|
||||||
Publicly Available Information
|
|
||||||
<em><small>(coming in <a href="https://github.com/bit-badger/jobs-jobs-jobs/issues/5" target="_blank">Phase 5</a>)</small></em>
|
|
||||||
</h4>
|
|
||||||
<p>
|
<p>
|
||||||
The “public” page for profile information will display the following information:
|
The “Job Seekers” page for profile information will allow users to search for and display the continent,
|
||||||
|
region, skills, and notes of users who are seeking employment <strong>and</strong> have opted in to their information
|
||||||
|
being publicly searchable. If you are a public user, this information is always the latest we have; check out the link
|
||||||
|
at the top of the search results for how you can learn more about these fine human resources!
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
|
||||||
<li>A count of profiles where the citizen is seeking employment</li>
|
<h4>Help / Suggestions</h4>
|
||||||
<li>The citizen’s continent and region</li>
|
|
||||||
<li>The citizen’s skills and notes</li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
<p>
|
||||||
This information will be pullled only from profiles where citizens have said can it be publicly available
|
This is open-source software
|
||||||
<strong>and</strong> are currently seeking employment.
|
<a href="https://github.com/bit-badger/jobs-jobs-jobs" _target="_blank">developed on Github</a>; feel free to
|
||||||
|
<a href="https://github.com/bit-badger/jobs-jobs-jobs/issues" target="_blank">create an issue there</a>, or look up
|
||||||
|
@("@")danieljsummers on No Agenda Social.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
<PageTitle Title="Welcome!" />
|
<PageTitle Title="Welcome!" />
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Future home of No Agenda Jobs, where citizens of Gitmo Nation can assist one another in finding or enhancing their
|
Welcome to Jobs, Jobs, Jobs (AKA No Agenda Careers), where citizens of Gitmo Nation can assist one another in finding
|
||||||
employment. This will enable them to continue providing value for value to Adam and John, as they continue their work
|
employment. This will enable them to continue providing value-for-value to Adam and John, as they continue their work
|
||||||
deconstructing the misinformation that passes for news on a day-to-day basis.
|
deconstructing the misinformation that passes for news on a day-to-day basis.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
62
src/JobsJobsJobs/Client/Pages/Profiles/Seeking.razor
Normal file
62
src/JobsJobsJobs/Client/Pages/Profiles/Seeking.razor
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
@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())
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
These profiles match your search criteria. To learn more about these people, join the merry band of human
|
||||||
|
resources in the <a href="https://noagendashow.net" target="_blank">No Agenda</a> tribe!
|
||||||
|
</p>
|
||||||
|
<table class="table table-sm table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<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>@profile.Continent</td>
|
||||||
|
<td>@profile.Region</td>
|
||||||
|
<td class="text-center">@YesOrNo(profile.RemoteWork)</td>
|
||||||
|
<td>
|
||||||
|
@foreach (var skill in profile.Skills)
|
||||||
|
{
|
||||||
|
@skill.Replace(" ()", "")<br>
|
||||||
|
}
|
||||||
|
</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<PublicSearchResult> SearchResults { get; set; } = Enumerable.Empty<PublicSearchResult>();
|
||||||
|
|
||||||
|
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/seeking", query));
|
||||||
|
await RetrieveProfiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retreive profiles matching the current search criteria
|
||||||
|
/// </summary>
|
||||||
|
private async Task RetrieveProfiles()
|
||||||
|
{
|
||||||
|
Searching = true;
|
||||||
|
|
||||||
|
var searchResult = await ServerApi.RetrieveMany<PublicSearchResult>(http,
|
||||||
|
QueryHelpers.AddQueryString("profile/public-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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@if (Profile.Skills.Length > 0)
|
@if (Profile.Skills.Count > 0)
|
||||||
{
|
{
|
||||||
<hr>
|
<hr>
|
||||||
<h4>Skills</h4>
|
<h4>Skills</h4>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
using JobsJobsJobs.Shared.Api;
|
using JobsJobsJobs.Shared.Api;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using NodaTime.Serialization.SystemTextJson;
|
using NodaTime.Serialization.SystemTextJson;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|||||||
@@ -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!;
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AssemblyVersion>0.9.1.0</AssemblyVersion>
|
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||||
<FileVersion>0.9.1.0</FileVersion>
|
<FileVersion>1.0.0.0</FileVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -97,10 +97,6 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers
|
|||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("skills")]
|
|
||||||
public async Task<IActionResult> GetSkills() =>
|
|
||||||
Ok(await _db.FindSkillsByCitizen(CurrentCitizenId));
|
|
||||||
|
|
||||||
[HttpGet("count")]
|
[HttpGet("count")]
|
||||||
public async Task<IActionResult> GetProfileCount() =>
|
public async Task<IActionResult> GetProfileCount() =>
|
||||||
Ok(new Count(await _db.CountProfiles()));
|
Ok(new Count(await _db.CountProfiles()));
|
||||||
@@ -120,6 +116,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,8 +83,12 @@ namespace JobsJobsJobs.Server.Data
|
|||||||
m.Property(e => e.LastUpdatedOn).HasColumnName("last_updated_on").IsRequired();
|
m.Property(e => e.LastUpdatedOn).HasColumnName("last_updated_on").IsRequired();
|
||||||
m.Property(e => e.Experience).HasColumnName("experience")
|
m.Property(e => e.Experience).HasColumnName("experience")
|
||||||
.HasConversion(Converters.OptionalMarkdownStringConverter);
|
.HasConversion(Converters.OptionalMarkdownStringConverter);
|
||||||
m.Ignore(e => e.Continent);
|
m.HasOne(e => e.Continent)
|
||||||
m.Ignore(e => e.Skills);
|
.WithMany()
|
||||||
|
.HasForeignKey(e => e.ContinentId);
|
||||||
|
m.HasMany(e => e.Skills)
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey(e => e.CitizenId);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity<Skill>(m =>
|
modelBuilder.Entity<Skill>(m =>
|
||||||
|
|||||||
@@ -18,24 +18,13 @@ namespace JobsJobsJobs.Server.Data
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="citizenId">The ID of the citizen whose profile should be retrieved</param>
|
/// <param name="citizenId">The ID of the citizen whose profile should be retrieved</param>
|
||||||
/// <returns>The profile, or null if it does not exist</returns>
|
/// <returns>The profile, or null if it does not exist</returns>
|
||||||
public static async Task<Profile?> FindProfileByCitizen(this JobsDbContext db, CitizenId citizenId)
|
public static async Task<Profile?> FindProfileByCitizen(this JobsDbContext db, CitizenId citizenId) =>
|
||||||
{
|
await db.Profiles.AsNoTracking()
|
||||||
var profile = await db.Profiles.AsNoTracking()
|
.Include(p => p.Continent)
|
||||||
|
.Include(p => p.Skills)
|
||||||
.SingleOrDefaultAsync(p => p.Id == citizenId)
|
.SingleOrDefaultAsync(p => p.Id == citizenId)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (profile != null)
|
|
||||||
{
|
|
||||||
return profile with
|
|
||||||
{
|
|
||||||
Continent = await db.FindContinentById(profile.ContinentId).ConfigureAwait(false),
|
|
||||||
Skills = (await db.FindSkillsByCitizen(citizenId).ConfigureAwait(false)).ToArray()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Save a profile
|
/// Save a profile
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -52,16 +41,6 @@ namespace JobsJobsJobs.Server.Data
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieve all skills for the given citizen
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="citizenId">The ID of the citizen whose skills should be retrieved</param>
|
|
||||||
/// <returns>The skills defined for this citizen</returns>
|
|
||||||
public static async Task<IEnumerable<Skill>> FindSkillsByCitizen(this JobsDbContext db, CitizenId citizenId) =>
|
|
||||||
await db.Skills.AsNoTracking()
|
|
||||||
.Where(s => s.CitizenId == citizenId)
|
|
||||||
.ToListAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Save a skill
|
/// Save a skill
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -111,7 +90,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)
|
||||||
@@ -161,6 +140,56 @@ namespace JobsJobsJobs.Server.Data
|
|||||||
.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<PublicSearchResult>> SearchPublicProfiles(this JobsDbContext db,
|
||||||
|
PublicSearch search)
|
||||||
|
{
|
||||||
|
var query = db.Profiles
|
||||||
|
.Include(it => it.Continent)
|
||||||
|
.Include(it => it.Skills)
|
||||||
|
.Where(it => it.IsPublic);
|
||||||
|
|
||||||
|
var useIds = false;
|
||||||
|
var citizenIds = new List<CitizenId>();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(search.ContinentId))
|
||||||
|
{
|
||||||
|
query = query.Where(it => it.ContinentId == ContinentId.Parse(search.ContinentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(search.Region))
|
||||||
|
{
|
||||||
|
query = query.Where(it => it.Region.ToLower().Contains(search.Region.ToLower()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(search.RemoteWork))
|
||||||
|
{
|
||||||
|
query = query.Where(it => it.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.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query.Select(x => new PublicSearchResult(x.Continent!.Name, x.Region, x.RemoteWork,
|
||||||
|
x.Skills.Select(sk => $"{sk.Description} ({sk.Notes})")))
|
||||||
|
.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>
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
|||||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<_PublishTargetUrl>C:\Users\danie\Documents\sandbox\jobs-jobs-jobs\src\JobsJobsJobs\Server\bin\Release\net5.0\publish\</_PublishTargetUrl>
|
<_PublishTargetUrl>C:\Users\danie\Documents\sandbox\jobs-jobs-jobs\src\JobsJobsJobs\Server\bin\Release\net5.0\publish\</_PublishTargetUrl>
|
||||||
<History>True|2021-03-16T23:34:57.2747439Z;</History>
|
<History>True|2021-06-15T02:08:33.4261507Z;True|2021-06-14T21:58:04.2622487-04:00;True|2021-03-16T19:34:57.2747439-04:00;</History>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/JobsJobsJobs/Shared/Api/PublicSearchResult.cs
Normal file
13
src/JobsJobsJobs/Shared/Api/PublicSearchResult.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace JobsJobsJobs.Shared.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A public profile search result
|
||||||
|
/// </summary>
|
||||||
|
public record PublicSearchResult(
|
||||||
|
string Continent,
|
||||||
|
string Region,
|
||||||
|
bool RemoteWork,
|
||||||
|
IEnumerable<string> Skills);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
using NodaTime;
|
using NodaTime;
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace JobsJobsJobs.Shared
|
namespace JobsJobsJobs.Shared
|
||||||
{
|
{
|
||||||
@@ -26,6 +26,6 @@ namespace JobsJobsJobs.Shared
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convenience property for skills associated with a profile
|
/// Convenience property for skills associated with a profile
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Skill[] Skills { get; set; } = Array.Empty<Skill>();
|
public ICollection<Skill> Skills { get; set; } = new List<Skill>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user