From 15c1a3ff2c9d02f185422bdc368d4384dcfca94c Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sun, 10 Jan 2021 22:06:26 -0500 Subject: [PATCH] Return all users with profiles (#3) Also fixed server pre-rendering and added log off functionality --- src/JobsJobsJobs.sln | 1 + .../Client/JobsJobsJobs.Client.csproj | 7 -- .../Client/Pages/Citizen/Dashboard.razor | 6 ++ .../Client/Pages/Citizen/LogOff.razor | 13 +++ .../Client/Pages/Profile/Search.razor | 44 +++++++++ .../Client/Pages/Profile/Search.razor.cs | 89 +++++++++++++++++++ src/JobsJobsJobs/Client/Shared/FullDate.razor | 23 +++++ .../Client/Shared/MainLayout.razor | 2 +- src/JobsJobsJobs/Client/Shared/NavMenu.razor | 45 +++++----- .../Client/Shared/PageTitle.razor | 4 +- src/JobsJobsJobs/Directory.Build.props | 8 ++ .../Api/Controllers/ProfileController.cs | 6 ++ .../Server/Data/ProfileExtensions.cs | 15 ++++ .../Server/JobsJobsJobs.Server.csproj | 5 -- src/JobsJobsJobs/Server/Pages/_Host.cshtml | 24 +++-- src/JobsJobsJobs/Server/Startup.cs | 8 +- .../Shared/Api/ProfileSearchResult.cs | 15 ++++ .../Shared/JobsJobsJobs.Shared.csproj | 7 -- 18 files changed, 270 insertions(+), 52 deletions(-) create mode 100644 src/JobsJobsJobs/Client/Pages/Citizen/LogOff.razor create mode 100644 src/JobsJobsJobs/Client/Pages/Profile/Search.razor create mode 100644 src/JobsJobsJobs/Client/Pages/Profile/Search.razor.cs create mode 100644 src/JobsJobsJobs/Client/Shared/FullDate.razor create mode 100644 src/JobsJobsJobs/Directory.Build.props create mode 100644 src/JobsJobsJobs/Shared/Api/ProfileSearchResult.cs diff --git a/src/JobsJobsJobs.sln b/src/JobsJobsJobs.sln index 77b6417..20e3996 100644 --- a/src/JobsJobsJobs.sln +++ b/src/JobsJobsJobs.sln @@ -11,6 +11,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JobsJobsJobs.Shared", "Jobs EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{50B51580-9F09-41E2-BC78-DAD38C37B583}" ProjectSection(SolutionItems) = preProject + JobsJobsJobs\Directory.Build.props = JobsJobsJobs\Directory.Build.props database\tables.sql = database\tables.sql EndProjectSection EndProject diff --git a/src/JobsJobsJobs/Client/JobsJobsJobs.Client.csproj b/src/JobsJobsJobs/Client/JobsJobsJobs.Client.csproj index 6e841de..f70aace 100644 --- a/src/JobsJobsJobs/Client/JobsJobsJobs.Client.csproj +++ b/src/JobsJobsJobs/Client/JobsJobsJobs.Client.csproj @@ -1,12 +1,5 @@  - - net5.0 - enable - 0.7.0.0 - 0.7.0.0 - - diff --git a/src/JobsJobsJobs/Client/Pages/Citizen/Dashboard.razor b/src/JobsJobsJobs/Client/Pages/Citizen/Dashboard.razor index 52fdc8b..0e5e79d 100644 --- a/src/JobsJobsJobs/Client/Pages/Citizen/Dashboard.razor +++ b/src/JobsJobsJobs/Client/Pages/Citizen/Dashboard.razor @@ -44,6 +44,12 @@ else }
+

Phase 3 – What Works (In Progress ~~ Last Updated January 10th, 2021)

+

+ The “View Profiles” link at the side does not have any search capabilities, but it does provide a list of + citizens who have filled out profiles, along with a way to view those profiles. +

+

Phase 2 – What Works (Last Updated January 8th, 2021)

If you’ve gotten this far, you’ve already passed diff --git a/src/JobsJobsJobs/Client/Pages/Citizen/LogOff.razor b/src/JobsJobsJobs/Client/Pages/Citizen/LogOff.razor new file mode 100644 index 0000000..adc183e --- /dev/null +++ b/src/JobsJobsJobs/Client/Pages/Citizen/LogOff.razor @@ -0,0 +1,13 @@ +@page "/citizen/log-off" +@inject NavigationManager nav +@inject AppState state +@inject IToastService toast +@code { + protected override void OnInitialized() + { + state.Jwt = ""; + state.User = null; + toast.ShowSuccess("Have a Nice Day!", "Log Off Successful"); + nav.NavigateTo("/"); + } +} diff --git a/src/JobsJobsJobs/Client/Pages/Profile/Search.razor b/src/JobsJobsJobs/Client/Pages/Profile/Search.razor new file mode 100644 index 0000000..30d935b --- /dev/null +++ b/src/JobsJobsJobs/Client/Pages/Profile/Search.razor @@ -0,0 +1,44 @@ +@page "/profile/search" +@inject HttpClient http +@inject AppState state + + +

Search Profiles

+ + + @if (Searching) + { +

Searching profiles...

+ } + else + { + @if (SearchResults.Any()) + { + + + + + + + + + + + + + @foreach (var profile in SearchResults) + { + + + + + + + + + } + +
ProfileNameSeeking?Remote?Full-Time?Last Updated
View@profile.DisplayName@YesOrNo(profile.SeekingEmployment)@YesOrNo(profile.RemoteWork)@YesOrNo(profile.FullTime)
+ } + } +
diff --git a/src/JobsJobsJobs/Client/Pages/Profile/Search.razor.cs b/src/JobsJobsJobs/Client/Pages/Profile/Search.razor.cs new file mode 100644 index 0000000..b0fc88b --- /dev/null +++ b/src/JobsJobsJobs/Client/Pages/Profile/Search.razor.cs @@ -0,0 +1,89 @@ +using JobsJobsJobs.Shared; +using JobsJobsJobs.Shared.Api; +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace JobsJobsJobs.Client.Pages.Profile +{ + public partial class Search : ComponentBase + { + /// + /// Whether a search has been performed + /// + private bool Searched { get; set; } = false; + + /// + /// Indicates whether a request for matching profiles is in progress + /// + private bool Searching { get; set; } = false; + + /// + /// Error messages encountered while searching for profiles + /// + private IList ErrorMessages { get; } = new List(); + + /// + /// All continents + /// + private IEnumerable Continents { get; set; } = Enumerable.Empty(); + + /// + /// The search results + /// + private IEnumerable SearchResults { get; set; } = Enumerable.Empty(); + + protected override async Task OnInitializedAsync() + { + ServerApi.SetJwt(http, state); + var continentResult = await ServerApi.RetrieveMany(http, "continent/all"); + + if (continentResult.IsOk) + { + Continents = continentResult.Ok; + } + else + { + ErrorMessages.Add(continentResult.Error); + } + + // TODO: remove this call once the filter is ready + await RetrieveProfiles(); + } + + /// + /// Retreive profiles matching the current search criteria + /// + private async Task RetrieveProfiles() + { + Searching = true; + + // TODO: send a filter with this request + var searchResult = await ServerApi.RetrieveMany(http, "profile/search"); + + 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; + + /// + /// Return "Yes" for true and "No" for false + /// + /// The condition in question + /// "Yes" for true, "No" for false + private static string YesOrNo(bool condition) => condition ? "Yes" : "No"; + } +} diff --git a/src/JobsJobsJobs/Client/Shared/FullDate.razor b/src/JobsJobsJobs/Client/Shared/FullDate.razor new file mode 100644 index 0000000..cdb1cc6 --- /dev/null +++ b/src/JobsJobsJobs/Client/Shared/FullDate.razor @@ -0,0 +1,23 @@ +@using NodaTime +@using NodaTime.Text +@using System.Globalization + +@Translated + +@code { + /// + /// The pattern with which dates will be formatted + /// + private static InstantPattern pattern = InstantPattern.Create("ld", CultureInfo.CurrentCulture); + + /// + /// The date to be formatted + /// + [Parameter] + public Instant TheDate { get; set; } + + /// + /// The formatted date + /// + private string Translated => pattern.Format(TheDate); +} diff --git a/src/JobsJobsJobs/Client/Shared/MainLayout.razor b/src/JobsJobsJobs/Client/Shared/MainLayout.razor index b37c8c1..e9b1b78 100644 --- a/src/JobsJobsJobs/Client/Shared/MainLayout.razor +++ b/src/JobsJobsJobs/Client/Shared/MainLayout.razor @@ -35,7 +35,7 @@ { var version = Assembly.GetExecutingAssembly().GetName().Version!; Version = $"v{version.Major}.{version.Minor}"; - if (version.Revision > 0) Version += $".{version.Revision}"; + if (version.Build > 0) Version += $".{version.Build}"; base.OnInitialized(); } } diff --git a/src/JobsJobsJobs/Client/Shared/NavMenu.razor b/src/JobsJobsJobs/Client/Shared/NavMenu.razor index 4fdf956..6de9041 100644 --- a/src/JobsJobsJobs/Client/Shared/NavMenu.razor +++ b/src/JobsJobsJobs/Client/Shared/NavMenu.razor @@ -18,29 +18,34 @@ @if (state.User == null) { - + } else { - - - + + + + } diff --git a/src/JobsJobsJobs/Client/Shared/PageTitle.razor b/src/JobsJobsJobs/Client/Shared/PageTitle.razor index f7f8d08..e6da7b9 100644 --- a/src/JobsJobsJobs/Client/Shared/PageTitle.razor +++ b/src/JobsJobsJobs/Client/Shared/PageTitle.razor @@ -3,9 +3,9 @@ [Parameter] public string Title { get; set; } = default!; - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { - await base.OnInitializedAsync(); + await base.OnAfterRenderAsync(firstRender); await js.InvokeVoidAsync("setPageTitle", $"{Title} ~ Jobs, Jobs, Jobs"); } } diff --git a/src/JobsJobsJobs/Directory.Build.props b/src/JobsJobsJobs/Directory.Build.props new file mode 100644 index 0000000..4441614 --- /dev/null +++ b/src/JobsJobsJobs/Directory.Build.props @@ -0,0 +1,8 @@ + + + net5.0 + enable + 0.7.1.0 + 0.7.1.0 + + diff --git a/src/JobsJobsJobs/Server/Areas/Api/Controllers/ProfileController.cs b/src/JobsJobsJobs/Server/Areas/Api/Controllers/ProfileController.cs index d4c534e..b1e1095 100644 --- a/src/JobsJobsJobs/Server/Areas/Api/Controllers/ProfileController.cs +++ b/src/JobsJobsJobs/Server/Areas/Api/Controllers/ProfileController.cs @@ -110,5 +110,11 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers var profile = await _db.FindProfileByCitizen(CitizenId.Parse(id)); return profile == null ? NotFound() : Ok(profile); } + + [HttpGet("search")] + public async Task Search() + { + return Ok(await _db.SearchProfiles()); + } } } diff --git a/src/JobsJobsJobs/Server/Data/ProfileExtensions.cs b/src/JobsJobsJobs/Server/Data/ProfileExtensions.cs index 97a59e0..65f318e 100644 --- a/src/JobsJobsJobs/Server/Data/ProfileExtensions.cs +++ b/src/JobsJobsJobs/Server/Data/ProfileExtensions.cs @@ -1,4 +1,5 @@ using JobsJobsJobs.Shared; +using JobsJobsJobs.Shared.Api; using Microsoft.EntityFrameworkCore; using Npgsql; using System; @@ -108,5 +109,19 @@ namespace JobsJobsJobs.Server.Data /// The count of skills for the given citizen public static async Task CountSkillsByCitizen(this JobsDbContext db, CitizenId citizenId) => await db.Skills.CountAsync(s => s.CitizenId == citizenId).ConfigureAwait(false); + + /// + /// Search profiles by the given criteria + /// + // TODO: A criteria parameter! + /// The information for profiles matching the criteria + public static async Task> SearchProfiles(this JobsDbContext db) + { + return await db.Profiles + .Join(db.Citizens, p => p.Id, c => c.Id, (p, c) => new { Profile = p, Citizen = c }) + .Select(x => new ProfileSearchResult(x.Citizen.Id, x.Citizen.DisplayName, x.Profile.SeekingEmployment, + x.Profile.RemoteWork, x.Profile.FullTime, x.Profile.LastUpdatedOn)) + .ToListAsync().ConfigureAwait(false); + } } } diff --git a/src/JobsJobsJobs/Server/JobsJobsJobs.Server.csproj b/src/JobsJobsJobs/Server/JobsJobsJobs.Server.csproj index 2f2c7ec..b7e6ab9 100644 --- a/src/JobsJobsJobs/Server/JobsJobsJobs.Server.csproj +++ b/src/JobsJobsJobs/Server/JobsJobsJobs.Server.csproj @@ -1,11 +1,7 @@  - net5.0 - enable 553960ef-0c79-47d4-98d8-9ca1708e558f - 0.7.0.0 - 0.7.0.0 @@ -28,5 +24,4 @@ - diff --git a/src/JobsJobsJobs/Server/Pages/_Host.cshtml b/src/JobsJobsJobs/Server/Pages/_Host.cshtml index 169bd45..e37e190 100644 --- a/src/JobsJobsJobs/Server/Pages/_Host.cshtml +++ b/src/JobsJobsJobs/Server/Pages/_Host.cshtml @@ -16,14 +16,26 @@ +
+
-
- An unhandled error has occurred. - Reload - 🗙 -
- +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + diff --git a/src/JobsJobsJobs/Server/Startup.cs b/src/JobsJobsJobs/Server/Startup.cs index 824d4ad..ed06bab 100644 --- a/src/JobsJobsJobs/Server/Startup.cs +++ b/src/JobsJobsJobs/Server/Startup.cs @@ -3,8 +3,6 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.AspNetCore.ResponseCompression; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -35,7 +33,9 @@ namespace JobsJobsJobs.Server services.AddDbContext(options => { options.UseNpgsql(Configuration.GetConnectionString("JobsDb"), o => o.UseNodaTime()); - // options.LogTo(System.Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information); +#if DEBUG + options.LogTo(System.Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information); +#endif }); services.AddSingleton(SystemClock.Instance); services.AddLogging(); @@ -98,7 +98,7 @@ namespace JobsJobsJobs.Server endpoints.MapRazorPages(); endpoints.MapControllers(); endpoints.MapFallback("api/{**slug}", send404); - endpoints.MapFallbackToFile("{**slug}", "index.html"); + endpoints.MapFallbackToPage("{**slug}", "/_Host"); }); } } diff --git a/src/JobsJobsJobs/Shared/Api/ProfileSearchResult.cs b/src/JobsJobsJobs/Shared/Api/ProfileSearchResult.cs new file mode 100644 index 0000000..390bc96 --- /dev/null +++ b/src/JobsJobsJobs/Shared/Api/ProfileSearchResult.cs @@ -0,0 +1,15 @@ +using NodaTime; + +namespace JobsJobsJobs.Shared.Api +{ + /// + /// A user matching the profile search + /// + public record ProfileSearchResult( + CitizenId CitizenId, + string DisplayName, + bool SeekingEmployment, + bool RemoteWork, + bool FullTime, + Instant LastUpdated); +} diff --git a/src/JobsJobsJobs/Shared/JobsJobsJobs.Shared.csproj b/src/JobsJobsJobs/Shared/JobsJobsJobs.Shared.csproj index 3dab347..c422640 100644 --- a/src/JobsJobsJobs/Shared/JobsJobsJobs.Shared.csproj +++ b/src/JobsJobsJobs/Shared/JobsJobsJobs.Shared.csproj @@ -1,12 +1,5 @@ - - net5.0 - enable - 0.7.0.0 - 0.7.0.0 - -