parent
340b93c6d7
commit
7f7eb191fb
|
@ -20,6 +20,13 @@ else
|
||||||
lists @Profile.Skills.Length skill@(Profile.Skills.Length != 1 ? "s" : "").
|
lists @Profile.Skills.Length skill@(Profile.Skills.Length != 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)
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
Your profile indicates that you are seeking employment. Once you find it,
|
||||||
|
<a href="/success-story/add">tell your fellow citizens about it!</a>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,6 +18,12 @@
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<InputCheckbox id="seeking" class="form-check-input" @bind-Value=@ProfileForm.IsSeekingEmployment />
|
<InputCheckbox id="seeking" class="form-check-input" @bind-Value=@ProfileForm.IsSeekingEmployment />
|
||||||
<label for="seeking" class="form-check-label">I am currently seeking employment</label>
|
<label for="seeking" class="form-check-label">I am currently seeking employment</label>
|
||||||
|
@if (IsSeeking)
|
||||||
|
{
|
||||||
|
<em> If you have found employment, consider
|
||||||
|
<a href="/success-story/add">telling your fellow citizens about it</a>
|
||||||
|
</em>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -23,6 +23,12 @@ namespace JobsJobsJobs.Client.Pages.Citizen
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool AllLoaded { get; set; } = false;
|
private bool AllLoaded { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the citizen is seeking employment at the time the profile is loaded (used to show success story
|
||||||
|
/// link)
|
||||||
|
/// </summary>
|
||||||
|
private bool IsSeeking { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The form for this page
|
/// The form for this page
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -63,6 +69,7 @@ namespace JobsJobsJobs.Client.Pages.Citizen
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ProfileForm = ProfileForm.FromProfile(profileTask.Result.Ok);
|
ProfileForm = ProfileForm.FromProfile(profileTask.Result.Ok);
|
||||||
|
IsSeeking = profileTask.Result.Ok.SeekingEmployment;
|
||||||
}
|
}
|
||||||
if (ProfileForm.Skills.Count == 0) AddNewSkill();
|
if (ProfileForm.Skills.Count == 0) AddNewSkill();
|
||||||
}
|
}
|
||||||
|
|
57
src/JobsJobsJobs/Client/Pages/SuccessStory/EditStory.razor
Normal file
57
src/JobsJobsJobs/Client/Pages/SuccessStory/EditStory.razor
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
@page "/success-story/add"
|
||||||
|
@page "/success-story/edit/{Id}"
|
||||||
|
@inject HttpClient http
|
||||||
|
@inject AppState state
|
||||||
|
@inject NavigationManager nav
|
||||||
|
@inject IToastService toast
|
||||||
|
|
||||||
|
<PageTitle Title=@Title />
|
||||||
|
|
||||||
|
<h3>@Title</h3>
|
||||||
|
|
||||||
|
<ErrorList Errors=@ErrorMessages>
|
||||||
|
@if (Loading)
|
||||||
|
{
|
||||||
|
<p>Loading...</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@if (IsNew)
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
Congratulations on your employment! Your fellow citizens would enjoy hearing how it all came about; tell us
|
||||||
|
about it below! <em>(These will be visible to other users, but not to the general public.)</em>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
<EditForm Model=@Form OnValidSubmit=@SaveStory>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-check">
|
||||||
|
<InputCheckbox id="fromHere" class="form-check-input" @bind-Value=@Form.FromHere />
|
||||||
|
<label for="fromHere" class="form-check-label">I found my employment here</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="story" class="jjj-label">The Success Story</label>
|
||||||
|
<MarkdownEditor Id="story" @bind-Text=@Form.Story />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col">
|
||||||
|
<br>
|
||||||
|
<button type="submit" class="btn btn-outline-primary">Save</button>
|
||||||
|
@if (IsNew)
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
<em>(Saving this will set “Seeking Employment” to “No” on your profile.)</em>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</EditForm>
|
||||||
|
}
|
||||||
|
</ErrorList>
|
124
src/JobsJobsJobs/Client/Pages/SuccessStory/EditStory.razor.cs
Normal file
124
src/JobsJobsJobs/Client/Pages/SuccessStory/EditStory.razor.cs
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
using JobsJobsJobs.Shared;
|
||||||
|
using JobsJobsJobs.Shared.Api;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace JobsJobsJobs.Client.Pages.SuccessStory
|
||||||
|
{
|
||||||
|
public partial class EditStory : ComponentBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the success story being edited
|
||||||
|
/// </summary>
|
||||||
|
[Parameter]
|
||||||
|
public string? Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether we are loading information
|
||||||
|
/// </summary>
|
||||||
|
private bool Loading { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The page title / header
|
||||||
|
/// </summary>
|
||||||
|
public string Title => IsNew ? "Tell Your Success Story" : "Edit Success Story";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The form with information for the success story
|
||||||
|
/// </summary>
|
||||||
|
private StoryForm Form { get; set; } = new StoryForm();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convenience property for showing new
|
||||||
|
/// </summary>
|
||||||
|
private bool IsNew => Form.Id == "new";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Error messages from API access
|
||||||
|
/// </summary>
|
||||||
|
private IList<string> ErrorMessages { get; } = new List<string>();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (Id != null)
|
||||||
|
{
|
||||||
|
ServerApi.SetJwt(http, state);
|
||||||
|
var story = await ServerApi.RetrieveOne<Success>(http, $"success/{Id}");
|
||||||
|
if (story.IsOk && story.Ok != null)
|
||||||
|
{
|
||||||
|
Form = new StoryForm
|
||||||
|
{
|
||||||
|
Id = story.Ok.Id.ToString(),
|
||||||
|
FromHere = story.Ok.FromHere,
|
||||||
|
Story = story.Ok.Story?.Text ?? ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (story.IsOk)
|
||||||
|
{
|
||||||
|
ErrorMessages.Add($"The success story {Id} does not exist");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ErrorMessages.Add(story.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save the success story
|
||||||
|
/// </summary>
|
||||||
|
private async Task SaveStory()
|
||||||
|
{
|
||||||
|
ServerApi.SetJwt(http, state);
|
||||||
|
var res = await http.PostAsJsonAsync("/api/success/save", Form);
|
||||||
|
|
||||||
|
if (res.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
if (IsNew)
|
||||||
|
{
|
||||||
|
res = await http.PatchAsync("/api/profile/employment-found", new StringContent(""));
|
||||||
|
if (res.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
SaveSuccessful();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await SaveFailed(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SaveSuccessful();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await SaveFailed(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle success notifications if saving succeeded
|
||||||
|
/// </summary>
|
||||||
|
private void SaveSuccessful()
|
||||||
|
{
|
||||||
|
toast.ShowSuccess("Story Saved Successfully");
|
||||||
|
nav.NavigateTo("/success-story/list");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handle failure notifications is saving was not successful
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="res">The HTTP response</param>
|
||||||
|
private async Task SaveFailed(HttpResponseMessage res)
|
||||||
|
{
|
||||||
|
var error = await res.Content.ReadAsStringAsync();
|
||||||
|
if (!string.IsNullOrEmpty(error)) error = $"- {error}";
|
||||||
|
toast.ShowError($"{(int)res.StatusCode} {error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/JobsJobsJobs/Client/Pages/SuccessStory/ListStories.razor
Normal file
46
src/JobsJobsJobs/Client/Pages/SuccessStory/ListStories.razor
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
@page "/success-story/list"
|
||||||
|
@inject HttpClient http
|
||||||
|
@inject AppState state
|
||||||
|
|
||||||
|
<PageTitle Title="Success Stories" />
|
||||||
|
|
||||||
|
<h3>Success Stories</h3>
|
||||||
|
|
||||||
|
<ErrorList Errors=@ErrorMessages>
|
||||||
|
@if (Loading)
|
||||||
|
{
|
||||||
|
<p>Loading...</p>
|
||||||
|
}
|
||||||
|
else if (Stories.Any())
|
||||||
|
{
|
||||||
|
<table class="table table-sm table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Story</th>
|
||||||
|
<th scope="col">From</th>
|
||||||
|
<th scope="col">Recorded On</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var story in Stories)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="/success-story/view/@story.Id">View</a>
|
||||||
|
@if (story.CitizenId == state.User!.Id)
|
||||||
|
{
|
||||||
|
<text> ~ <a href="/success-story/edit/@story.Id">Edit</a></text>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>@story.CitizenName</td>
|
||||||
|
<td><FullDate TheDate=@story.RecordedOn /></td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>There are no success stories recorded <em>(yet)</em></p>
|
||||||
|
}
|
||||||
|
</ErrorList>
|
|
@ -0,0 +1,44 @@
|
||||||
|
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.SuccessStory
|
||||||
|
{
|
||||||
|
public partial class ListStories : ComponentBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether we are still loading data
|
||||||
|
/// </summary>
|
||||||
|
private bool Loading { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The story entries
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerable<StoryEntry> Stories { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Error messages encountered
|
||||||
|
/// </summary>
|
||||||
|
private IList<string> ErrorMessages { get; set; } = new List<string>();
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
ServerApi.SetJwt(http, state);
|
||||||
|
var stories = await ServerApi.RetrieveMany<StoryEntry>(http, "success/list");
|
||||||
|
|
||||||
|
if (stories.IsOk)
|
||||||
|
{
|
||||||
|
Stories = stories.Ok;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ErrorMessages.Add(stories.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,34 +18,39 @@
|
||||||
</li>
|
</li>
|
||||||
@if (state.User == null)
|
@if (state.User == null)
|
||||||
{
|
{
|
||||||
<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
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="/citizen/dashboard">
|
<NavLink class="nav-link" href="/citizen/dashboard">
|
||||||
<span class="oi oi-dashboard" aria-hidden="true"></span> Dashboard
|
<span class="oi oi-dashboard" aria-hidden="true"></span> Dashboard
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="/citizen/profile">
|
<NavLink class="nav-link" href="/citizen/profile">
|
||||||
<span class="oi oi-pencil" aria-hidden="true"></span> Edit Your Profile
|
<span class="oi oi-pencil" aria-hidden="true"></span> Edit Your Profile
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="/profile/search">
|
<NavLink class="nav-link" href="/profile/search">
|
||||||
<span class="oi oi-spreadsheet" aria-hidden="true"></span> View Profiles
|
<span class="oi oi-spreadsheet" aria-hidden="true"></span> View Profiles
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="/citizen/log-off">
|
<NavLink class="nav-link" href="/success-story/list">
|
||||||
<span class="oi oi-plus" aria-hidden="true"></span> Log Off
|
<span class="oi oi-graph" aria-hidden="true"></span> Success Stories
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item px-3">
|
||||||
|
<NavLink class="nav-link" href="/citizen/log-off">
|
||||||
|
<span class="oi oi-plus" aria-hidden="true"></span> Log Off
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AssemblyVersion>0.8.0.0</AssemblyVersion>
|
<AssemblyVersion>0.9.0.0</AssemblyVersion>
|
||||||
<FileVersion>0.8.0.0</FileVersion>
|
<FileVersion>0.9.0.0</FileVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -33,6 +33,7 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers
|
||||||
/// Constructor
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="db">The data context to use for this request</param>
|
/// <param name="db">The data context to use for this request</param>
|
||||||
|
/// <param name="clock">The clock instance to use for this request</param>
|
||||||
public ProfileController(JobsDbContext db, IClock clock)
|
public ProfileController(JobsDbContext db, IClock clock)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
|
@ -114,5 +115,20 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers
|
||||||
[HttpGet("search")]
|
[HttpGet("search")]
|
||||||
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));
|
||||||
|
|
||||||
|
[HttpPatch("employment-found")]
|
||||||
|
public async Task<IActionResult> EmploymentFound()
|
||||||
|
{
|
||||||
|
var profile = await _db.FindProfileByCitizen(CurrentCitizenId);
|
||||||
|
if (profile == null) return NotFound();
|
||||||
|
|
||||||
|
var updated = profile with { SeekingEmployment = false };
|
||||||
|
_db.Update(updated);
|
||||||
|
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
using JobsJobsJobs.Server.Data;
|
||||||
|
using JobsJobsJobs.Shared;
|
||||||
|
using JobsJobsJobs.Shared.Api;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using NodaTime;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace JobsJobsJobs.Server.Areas.Api.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// API controller for success stories
|
||||||
|
/// </summary>
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
[ApiController]
|
||||||
|
public class SuccessController : Controller
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The data context
|
||||||
|
/// </summary>
|
||||||
|
private readonly JobsDbContext _db;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The NodaTime clock instance
|
||||||
|
/// </summary>
|
||||||
|
private readonly IClock _clock;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructor
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="db">The data context to use for this request</param>
|
||||||
|
/// <param name="clock">The clock instance to use for this request</param>
|
||||||
|
public SuccessController(JobsDbContext db, IClock clock)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
_clock = clock;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current citizen ID
|
||||||
|
/// </summary>
|
||||||
|
private CitizenId CurrentCitizenId => CitizenId.Parse(User.FindFirst(ClaimTypes.NameIdentifier)!.Value);
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<IActionResult> Retrieve(string id) =>
|
||||||
|
Ok(await _db.FindSuccessById(SuccessId.Parse(id)));
|
||||||
|
|
||||||
|
[HttpPost("save")]
|
||||||
|
public async Task<IActionResult> Save([FromBody] StoryForm form)
|
||||||
|
{
|
||||||
|
if (form.Id == "new")
|
||||||
|
{
|
||||||
|
var story = new Success(await SuccessId.Create(), CurrentCitizenId, _clock.GetCurrentInstant(),
|
||||||
|
form.FromHere, string.IsNullOrWhiteSpace(form.Story) ? null : new MarkdownString(form.Story));
|
||||||
|
await _db.AddAsync(story);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var story = await _db.FindSuccessById(SuccessId.Parse(form.Id));
|
||||||
|
if (story == null) return NotFound();
|
||||||
|
var updated = story with
|
||||||
|
{
|
||||||
|
FromHere = form.FromHere,
|
||||||
|
Story = string.IsNullOrWhiteSpace(form.Story) ? null : new MarkdownString(form.Story)
|
||||||
|
};
|
||||||
|
_db.Update(updated);
|
||||||
|
}
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("list")]
|
||||||
|
public async Task<IActionResult> List() =>
|
||||||
|
Ok(await _db.AllStories());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,9 @@
|
||||||
using JobsJobsJobs.Shared;
|
using JobsJobsJobs.Shared;
|
||||||
using JobsJobsJobs.Shared.Api;
|
using JobsJobsJobs.Shared.Api;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Npgsql;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace JobsJobsJobs.Server.Data
|
namespace JobsJobsJobs.Server.Data
|
||||||
|
|
34
src/JobsJobsJobs/Server/Data/SuccessExtensions.cs
Normal file
34
src/JobsJobsJobs/Server/Data/SuccessExtensions.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using JobsJobsJobs.Shared;
|
||||||
|
using JobsJobsJobs.Shared.Api;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace JobsJobsJobs.Server.Data
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extensions to JobsDbContext to support manipulation of success stories
|
||||||
|
/// </summary>
|
||||||
|
public static class SuccessExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get a success story by its ID
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The ID of the story to retrieve</param>
|
||||||
|
/// <returns>The success story, if found</returns>
|
||||||
|
public static async Task<Success?> FindSuccessById(this JobsDbContext db, SuccessId id) =>
|
||||||
|
await db.Successes.AsNoTracking().SingleOrDefaultAsync(s => s.Id == id).ConfigureAwait(false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a list of success stories, with the information needed for the list page
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A list of success stories, citizen names, and dates</returns>
|
||||||
|
public static async Task<IEnumerable<StoryEntry>> AllStories(this JobsDbContext db) =>
|
||||||
|
await db.Successes
|
||||||
|
.Join(db.Citizens, s => s.CitizenId, c => c.Id, (s, c) => new { Success = s, Citizen = c })
|
||||||
|
.OrderByDescending(it => it.Success.RecordedOn)
|
||||||
|
.Select(it => new StoryEntry(it.Success.Id, it.Citizen.Id, it.Citizen.DisplayName, it.Success.RecordedOn))
|
||||||
|
.ToListAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
9
src/JobsJobsJobs/Shared/Api/StoryEntry.cs
Normal file
9
src/JobsJobsJobs/Shared/Api/StoryEntry.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace JobsJobsJobs.Shared.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An entry in the list of success stories
|
||||||
|
/// </summary>
|
||||||
|
public record StoryEntry(SuccessId Id, CitizenId CitizenId, string CitizenName, Instant RecordedOn);
|
||||||
|
}
|
23
src/JobsJobsJobs/Shared/Api/StoryForm.cs
Normal file
23
src/JobsJobsJobs/Shared/Api/StoryForm.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
namespace JobsJobsJobs.Shared.Api
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The data required to provide a success story
|
||||||
|
/// </summary>
|
||||||
|
public class StoryForm
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of this story
|
||||||
|
/// </summary>
|
||||||
|
public string Id { get; set; } = "new";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the employment was obtained from Jobs, Jobs, Jobs
|
||||||
|
/// </summary>
|
||||||
|
public bool FromHere { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The success story
|
||||||
|
/// </summary>
|
||||||
|
public string Story { get; set; } = "";
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user