Profile save/load works
Still need to add skills, but #2 is getting there...
This commit is contained in:
parent
1e474395a9
commit
a48af190fa
|
@ -25,7 +25,7 @@ else
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
bool retrievingProfile = true;
|
bool retrievingProfile = true;
|
||||||
JobsJobsJobs.Shared.Profile? profile = null;
|
Profile? profile = null;
|
||||||
string? errorMessage = null;
|
string? errorMessage = null;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
|
|
89
src/JobsJobsJobs/Client/Pages/Citizen/EditProfile.razor
Normal file
89
src/JobsJobsJobs/Client/Pages/Citizen/EditProfile.razor
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
@page "/citizen/profile"
|
||||||
|
|
||||||
|
<h3>Employment Profile</h3>
|
||||||
|
|
||||||
|
@if (ErrorMessage != "")
|
||||||
|
{
|
||||||
|
<p>@ErrorMessage</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<EditForm Model="@ProfileForm" OnValidSubmit="@SaveProfile">
|
||||||
|
<DataAnnotationsValidator />
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-check">
|
||||||
|
<InputCheckbox id="seeking" class="form-check-input" @bind-Value="@ProfileForm.IsSeekingEmployment" />
|
||||||
|
<label for="seeking" class="form-check-label">I am currently seeking employment</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col col-xs-12 col-sm-6 col-md-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="continentId" class="jjj-required">Continent</label>
|
||||||
|
<InputSelect id="continentId" @bind-Value="@ProfileForm.ContinentId" class="form-control">
|
||||||
|
<option>– Select –</option>
|
||||||
|
@foreach (var (id, name) in Continents)
|
||||||
|
{
|
||||||
|
<option value="@id">@name</option>
|
||||||
|
}
|
||||||
|
</InputSelect>
|
||||||
|
<ValidationMessage For="@(() => ProfileForm.ContinentId)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col col-xs-12 col-sm-6 col-md-8">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="region" class="jjj-required">Region</label>
|
||||||
|
<InputText id="region" @bind-Value="@ProfileForm.Region" class="form-control" />
|
||||||
|
<ValidationMessage For="@(() => ProfileForm.Region)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="bio" class="jjj-required">Professional Biography</label>
|
||||||
|
<MarkdownEditor Id="bio" @bind-Text="@ProfileForm.Biography" />
|
||||||
|
<ValidationMessage For="@(() => ProfileForm.Biography)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col col-xs-12 col-sm-12 offset-md-2 col-md-4">
|
||||||
|
<div class="form-check">
|
||||||
|
<InputCheckbox id="isRemote" class="form-check-input" @bind-Value="@ProfileForm.RemoteWork" />
|
||||||
|
<label for="isRemote" class="form-check-label">I am looking for remote work</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col col-xs-12 col-sm-12 col-md-4">
|
||||||
|
<div class="form-check">
|
||||||
|
<InputCheckbox id="isFull" class="form-check-input" @bind-Value="@ProfileForm.FullTime" />
|
||||||
|
<label for="isFull" class="form-check-label">I am looking for full-time work</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col">
|
||||||
|
<label for="experience">Experience</label>
|
||||||
|
<MarkdownEditor Id="experience" @bind-Text="@ProfileForm.Experience" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-check">
|
||||||
|
<InputCheckbox id="isPublic" class="form-check-input" @bind-Value="@ProfileForm.IsPublic" />
|
||||||
|
<label for="isPublic" class="form-check-label">
|
||||||
|
Allow my profile to be searched publicly (outside NA Social)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="col">
|
||||||
|
<br>
|
||||||
|
<button type="submit" class="btn btn-outline-primary">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</EditForm>
|
||||||
|
}
|
84
src/JobsJobsJobs/Client/Pages/Citizen/EditProfile.razor.cs
Normal file
84
src/JobsJobsJobs/Client/Pages/Citizen/EditProfile.razor.cs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
using JobsJobsJobs.Shared;
|
||||||
|
using JobsJobsJobs.Shared.Api;
|
||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace JobsJobsJobs.Client.Pages.Citizen
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Profile edit page (called EditProfile so as not to create naming conflicts)
|
||||||
|
/// </summary>
|
||||||
|
public partial class EditProfile : ComponentBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The form for this page
|
||||||
|
/// </summary>
|
||||||
|
private ProfileForm ProfileForm { get; set; } = new ProfileForm();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All continents
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerable<Continent> Continents { get; set; } = Enumerable.Empty<Continent>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Error message from API access
|
||||||
|
/// </summary>
|
||||||
|
private string ErrorMessage { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// HTTP client instance to use for API access
|
||||||
|
/// </summary>
|
||||||
|
[Inject]
|
||||||
|
private HttpClient Http { get; set; } = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Application state
|
||||||
|
/// </summary>
|
||||||
|
[Inject]
|
||||||
|
private AppState State { get; set; } = default!;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
ServerApi.SetJwt(Http, State);
|
||||||
|
var continentResult = await ServerApi.AllContinents(Http, State);
|
||||||
|
if (continentResult.IsOk)
|
||||||
|
{
|
||||||
|
Continents = continentResult.Ok;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ErrorMessage = continentResult.Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await ServerApi.RetrieveProfile(Http, State);
|
||||||
|
if (result.IsOk)
|
||||||
|
{
|
||||||
|
System.Console.WriteLine($"Result is null? {result.Ok == null}");
|
||||||
|
ProfileForm = (result.Ok == null) ? new ProfileForm() : ProfileForm.FromProfile(result.Ok);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ErrorMessage = result.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveProfile()
|
||||||
|
{
|
||||||
|
var res = await Http.PostAsJsonAsync("/api/profile/save", ProfileForm);
|
||||||
|
if (res.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
// TODO: success notification
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: probably not the best way to handle this...
|
||||||
|
ErrorMessage = await res.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,130 +0,0 @@
|
||||||
@page "/citizen/profile"
|
|
||||||
@using JobsJobsJobs.Client.ViewModels
|
|
||||||
@inject HttpClient http
|
|
||||||
@inject AppState state
|
|
||||||
|
|
||||||
<h3>Employment Profile</h3>
|
|
||||||
|
|
||||||
@if (errorMessage != "")
|
|
||||||
{
|
|
||||||
<p>@errorMessage</p>
|
|
||||||
}
|
|
||||||
else if (profileForm != null)
|
|
||||||
{
|
|
||||||
<EditForm Model="@profileForm" OnValidSubmit="@SaveProfile">
|
|
||||||
<DataAnnotationsValidator />
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="col col-xs-12 col-sm-12 col-md-4">
|
|
||||||
<div class="form-check">
|
|
||||||
<InputCheckbox id="seeking" class="form-check-input" @bind-Value="@profileForm.IsSeekingEmployment" />
|
|
||||||
<label for="seeking" class="form-check-label">I am currently seeking employment</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col col-xs-12 col-sm-12 col-md-8">
|
|
||||||
<div class="form-check">
|
|
||||||
<InputCheckbox id="isPublic" class="form-check-input" @bind-Value="@profileForm.IsPublic" />
|
|
||||||
<label for="isPublic" class="form-check-label">
|
|
||||||
Allow my profile to be searched publicly (outside NA Social)
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="col col-xs-12 col-sm-12 col-md-4">
|
|
||||||
<div class="form-check">
|
|
||||||
<InputCheckbox id="isRemote" class="form-check-input" @bind-Value="@profileForm.RemoteWork" />
|
|
||||||
<label for="isRemote" class="form-check-label">I am looking for remote work</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col col-xs-12 col-sm-12 col-md-8">
|
|
||||||
<div class="form-check">
|
|
||||||
<InputCheckbox id="isFull" class="form-check-input" @bind-Value="@profileForm.FullTime" />
|
|
||||||
<label for="isFull" class="form-check-label">I am looking for full-time work</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="col col-xs-12 col-sm-6 col-md-4">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="continentId">Continent</label>
|
|
||||||
<InputSelect id="continentId" @bind-Value="@profileForm.ContinentId" class="form-control">
|
|
||||||
<option>– Select –</option>
|
|
||||||
@foreach (var (id, name) in continents)
|
|
||||||
{
|
|
||||||
<option value="@id">@name</option>
|
|
||||||
}
|
|
||||||
</InputSelect>
|
|
||||||
<ValidationMessage For="@(() => profileForm.ContinentId)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col col-xs-12 col-sm-6 col-md-8">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="region">Region</label>
|
|
||||||
<InputText id="region" @bind-Value="@profileForm.Region" class="form-control" />
|
|
||||||
<ValidationMessage For="@(() => profileForm.Region)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="col">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="bio">Professional Biography</label>
|
|
||||||
<MarkdownEditor Id="bio" @bind-Text="@profileForm.Biography" />
|
|
||||||
<ValidationMessage For="@(() => profileForm.Biography)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="col">
|
|
||||||
<label for="experience">Experience</label>
|
|
||||||
<MarkdownEditor Id="experience" @bind-Text="@profileForm.Experience" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="col">
|
|
||||||
<br>
|
|
||||||
<button type="submit" class="btn btn-outline-primary">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</EditForm>
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
|
||||||
|
|
||||||
public JobsJobsJobs.Shared.Profile? profile = null;
|
|
||||||
|
|
||||||
public ProfileForm? profileForm = null;
|
|
||||||
|
|
||||||
private IEnumerable<Continent> continents = Enumerable.Empty<Continent>();
|
|
||||||
|
|
||||||
public string errorMessage = "";
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
var continentResult = await ServerApi.AllContinents(http, state);
|
|
||||||
if (continentResult.IsOk)
|
|
||||||
{
|
|
||||||
continents = continentResult.Ok;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
errorMessage = continentResult.Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = await ServerApi.RetrieveProfile(http, state);
|
|
||||||
if (result.IsOk)
|
|
||||||
{
|
|
||||||
profile = result.Ok;
|
|
||||||
profileForm = (profile == null) ? new ProfileForm() : ProfileForm.FromProfile(profile);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
errorMessage = result.Error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveProfile()
|
|
||||||
{
|
|
||||||
// TODO: save profile
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,7 @@
|
||||||
using JobsJobsJobs.Shared;
|
using JobsJobsJobs.Shared;
|
||||||
using JobsJobsJobs.Shared.Api;
|
using JobsJobsJobs.Shared.Api;
|
||||||
|
using NodaTime;
|
||||||
|
using NodaTime.Serialization.SystemTextJson;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -7,6 +9,7 @@ using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace JobsJobsJobs.Client
|
namespace JobsJobsJobs.Client
|
||||||
|
@ -16,6 +19,25 @@ namespace JobsJobsJobs.Client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ServerApi
|
public static class ServerApi
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// System.Text.Json options configured for NodaTime
|
||||||
|
/// </summary>
|
||||||
|
private static readonly JsonSerializerOptions _serializerOptions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Static constructor
|
||||||
|
/// </summary>
|
||||||
|
static ServerApi()
|
||||||
|
{
|
||||||
|
|
||||||
|
var options = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
|
};
|
||||||
|
options.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||||
|
_serializerOptions = options;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create an API URL
|
/// Create an API URL
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -37,6 +59,14 @@ namespace JobsJobsJobs.Client
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the JSON Web Token (JWT) bearer header for the given HTTP client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="http">The HTTP client whose authentication header should be set</param>
|
||||||
|
/// <param name="state">The current application state</param>
|
||||||
|
public static void SetJwt(HttpClient http, AppState state) =>
|
||||||
|
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", state.Jwt);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Log on a user with the authorization code received from No Agenda Social
|
/// Log on a user with the authorization code received from No Agenda Social
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -73,7 +103,8 @@ namespace JobsJobsJobs.Client
|
||||||
return true switch
|
return true switch
|
||||||
{
|
{
|
||||||
_ when res.StatusCode == HttpStatusCode.NoContent => Result<Profile?>.AsOk(null),
|
_ when res.StatusCode == HttpStatusCode.NoContent => Result<Profile?>.AsOk(null),
|
||||||
_ when res.IsSuccessStatusCode => Result<Profile?>.AsOk(await res.Content.ReadFromJsonAsync<Profile>()),
|
_ when res.IsSuccessStatusCode => Result<Profile?>.AsOk(
|
||||||
|
await res.Content.ReadFromJsonAsync<Profile>(_serializerOptions)),
|
||||||
_ => Result<Profile?>.AsError(await res.Content.ReadAsStringAsync()),
|
_ => Result<Profile?>.AsError(await res.Content.ReadAsStringAsync()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
section.preview {
|
section.preview {
|
||||||
border: solid 1px darkgray;
|
border: solid 1px darkgray;
|
||||||
border-radius: 2rem;
|
border-radius: .5rem;
|
||||||
|
padding: .25rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,3 +56,12 @@ a.audio {
|
||||||
a.audio:hover {
|
a.audio:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label.jjj-required {
|
||||||
|
font-weight:bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.jjj-required::after {
|
||||||
|
color: red;
|
||||||
|
content: ' *';
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
using JobsJobsJobs.Server.Data;
|
using JobsJobsJobs.Server.Data;
|
||||||
using JobsJobsJobs.Shared;
|
using JobsJobsJobs.Shared;
|
||||||
|
using JobsJobsJobs.Shared.Api;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NodaTime;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -17,6 +19,7 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers
|
||||||
/// API controller for employment profile information
|
/// API controller for employment profile information
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class ProfileController : ControllerBase
|
public class ProfileController : ControllerBase
|
||||||
{
|
{
|
||||||
|
@ -25,16 +28,21 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly NpgsqlConnection _db;
|
private readonly NpgsqlConnection _db;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The NodaTime clock instance
|
||||||
|
/// </summary>
|
||||||
|
private readonly IClock _clock;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructor
|
/// Constructor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="db">The database connection to use for this request</param>
|
/// <param name="db">The database connection to use for this request</param>
|
||||||
public ProfileController(NpgsqlConnection db)
|
public ProfileController(NpgsqlConnection db, IClock clock)
|
||||||
{
|
{
|
||||||
_db = db;
|
_db = db;
|
||||||
|
_clock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
[HttpGet("")]
|
[HttpGet("")]
|
||||||
public async Task<IActionResult> Get()
|
public async Task<IActionResult> Get()
|
||||||
{
|
{
|
||||||
|
@ -43,5 +51,32 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers
|
||||||
CitizenId.Parse(User.FindFirst(ClaimTypes.NameIdentifier)!.Value));
|
CitizenId.Parse(User.FindFirst(ClaimTypes.NameIdentifier)!.Value));
|
||||||
return profile == null ? NoContent() : Ok(profile);
|
return profile == null ? NoContent() : Ok(profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("save")]
|
||||||
|
public async Task<IActionResult> Save([FromBody] ProfileForm form)
|
||||||
|
{
|
||||||
|
var citizenId = CitizenId.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier));
|
||||||
|
await _db.OpenAsync();
|
||||||
|
var existing = await _db.FindProfileByCitizen(citizenId);
|
||||||
|
var profile = existing == null
|
||||||
|
? new Profile(citizenId, form.IsSeekingEmployment, form.IsPublic, ContinentId.Parse(form.ContinentId),
|
||||||
|
form.Region, form.RemoteWork, form.FullTime, new MarkdownString(form.Biography),
|
||||||
|
_clock.GetCurrentInstant(),
|
||||||
|
string.IsNullOrEmpty(form.Experience) ? null : new MarkdownString(form.Experience))
|
||||||
|
: existing with
|
||||||
|
{
|
||||||
|
SeekingEmployment = form.IsSeekingEmployment,
|
||||||
|
IsPublic = form.IsPublic,
|
||||||
|
ContinentId = ContinentId.Parse(form.ContinentId),
|
||||||
|
Region = form.Region,
|
||||||
|
RemoteWork = form.RemoteWork,
|
||||||
|
FullTime = form.FullTime,
|
||||||
|
Biography = new MarkdownString(form.Biography),
|
||||||
|
LastUpdatedOn = _clock.GetCurrentInstant(),
|
||||||
|
Experience = string.IsNullOrEmpty(form.Experience) ? null : new MarkdownString(form.Experience)
|
||||||
|
};
|
||||||
|
await _db.SaveProfile(profile);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace JobsJobsJobs.Server.Data
|
||||||
private static Profile ToProfile(NpgsqlDataReader rdr)
|
private static Profile ToProfile(NpgsqlDataReader rdr)
|
||||||
{
|
{
|
||||||
var continentId = ContinentId.Parse(rdr.GetString("continent_id"));
|
var continentId = ContinentId.Parse(rdr.GetString("continent_id"));
|
||||||
return new Profile(CitizenId.Parse(rdr.GetString("id")), rdr.GetBoolean("seeking_employment"),
|
return new Profile(CitizenId.Parse(rdr.GetString("citizen_id")), rdr.GetBoolean("seeking_employment"),
|
||||||
rdr.GetBoolean("is_public"), continentId, rdr.GetString("region"), rdr.GetBoolean("remote_work"),
|
rdr.GetBoolean("is_public"), continentId, rdr.GetString("region"), rdr.GetBoolean("remote_work"),
|
||||||
rdr.GetBoolean("full_time"), new MarkdownString(rdr.GetString("biography")),
|
rdr.GetBoolean("full_time"), new MarkdownString(rdr.GetString("biography")),
|
||||||
rdr.GetInstant("last_updated_on"),
|
rdr.GetInstant("last_updated_on"),
|
||||||
|
@ -48,5 +48,45 @@ namespace JobsJobsJobs.Server.Data
|
||||||
using var rdr = await cmd.ExecuteReaderAsync().ConfigureAwait(false);
|
using var rdr = await cmd.ExecuteReaderAsync().ConfigureAwait(false);
|
||||||
return await rdr.ReadAsync().ConfigureAwait(false) ? ToProfile(rdr) : null;
|
return await rdr.ReadAsync().ConfigureAwait(false) ? ToProfile(rdr) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save a profile
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="profile">The profile to be saved</param>
|
||||||
|
public static async Task SaveProfile(this NpgsqlConnection conn, Profile profile)
|
||||||
|
{
|
||||||
|
using var cmd = conn.CreateCommand();
|
||||||
|
cmd.CommandText =
|
||||||
|
@"INSERT INTO profile (
|
||||||
|
citizen_id, seeking_employment, is_public, continent_id, region, remote_work, full_time,
|
||||||
|
biography, last_updated_on, experience
|
||||||
|
) VALUES (
|
||||||
|
@citizen_id, @seeking_employment, @is_public, @continent_id, @region, @remote_work, @full_time,
|
||||||
|
@biography, @last_updated_on, @experience
|
||||||
|
) ON CONFLICT (citizen_id) DO UPDATE
|
||||||
|
SET seeking_employment = @seeking_employment,
|
||||||
|
is_public = @is_public,
|
||||||
|
continent_id = @continent_id,
|
||||||
|
region = @region,
|
||||||
|
remote_work = @remote_work,
|
||||||
|
full_time = @full_time,
|
||||||
|
biography = @biography,
|
||||||
|
last_updated_on = @last_updated_on,
|
||||||
|
experience = @experience
|
||||||
|
WHERE profile.citizen_id = excluded.citizen_id";
|
||||||
|
cmd.Parameters.Add(new NpgsqlParameter("@citizen_id", profile.Id.ToString()));
|
||||||
|
cmd.Parameters.Add(new NpgsqlParameter("@seeking_employment", profile.SeekingEmployment));
|
||||||
|
cmd.Parameters.Add(new NpgsqlParameter("@is_public", profile.IsPublic));
|
||||||
|
cmd.Parameters.Add(new NpgsqlParameter("@continent_id", profile.ContinentId.ToString()));
|
||||||
|
cmd.Parameters.Add(new NpgsqlParameter("@region", profile.Region));
|
||||||
|
cmd.Parameters.Add(new NpgsqlParameter("@remote_work", profile.RemoteWork));
|
||||||
|
cmd.Parameters.Add(new NpgsqlParameter("@full_time", profile.FullTime));
|
||||||
|
cmd.Parameters.Add(new NpgsqlParameter("@biography", profile.Biography.Text));
|
||||||
|
cmd.Parameters.Add(new NpgsqlParameter("@last_updated_on", profile.LastUpdatedOn));
|
||||||
|
cmd.Parameters.Add(new NpgsqlParameter("@experience",
|
||||||
|
profile.Experience == null ? DBNull.Value : profile.Experience.Text));
|
||||||
|
|
||||||
|
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using JobsJobsJobs.Shared;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace JobsJobsJobs.Client.ViewModels
|
namespace JobsJobsJobs.Shared.Api
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The data required to update a profile
|
/// The data required to update a profile
|
Loading…
Reference in New Issue
Block a user