WIP on implementing NodaTime
This commit is contained in:
parent
404e314f97
commit
c782f5aac4
|
@ -9,6 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JobsJobsJobs.Client", "Jobs
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JobsJobsJobs.Shared", "JobsJobsJobs\Shared\JobsJobsJobs.Shared.csproj", "{AE329284-47DA-4E76-B542-47489B271130}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JobsJobsJobs.Shared", "JobsJobsJobs\Shared\JobsJobsJobs.Shared.csproj", "{AE329284-47DA-4E76-B542-47489B271130}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{50B51580-9F09-41E2-BC78-DAD38C37B583}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
database\tables.sql = database\tables.sql
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using JobsJobsJobs.Shared;
|
using JobsJobsJobs.Shared;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace JobsJobsJobs.Client
|
namespace JobsJobsJobs.Client
|
||||||
{
|
{
|
||||||
|
@ -12,16 +13,40 @@ namespace JobsJobsJobs.Client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AppState
|
public class AppState
|
||||||
{
|
{
|
||||||
|
public event Action OnChange = () => { };
|
||||||
|
|
||||||
|
private UserInfo? _user = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The information of the currently logged-in user
|
/// The information of the currently logged-in user
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public UserInfo? User { get; set; } = null;
|
public UserInfo? User
|
||||||
|
{
|
||||||
|
get => _user;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_user = value;
|
||||||
|
NotifyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _jwt = "";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The JSON Web Token (JWT) for the currently logged-on user
|
/// The JSON Web Token (JWT) for the currently logged-on user
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Jwt { get; set; } = "";
|
public string Jwt
|
||||||
|
{
|
||||||
|
get => _jwt;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_jwt = value;
|
||||||
|
NotifyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public AppState() { }
|
public AppState() { }
|
||||||
|
|
||||||
|
private void NotifyChanged() => OnChange.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,20 @@
|
||||||
@inject HttpClient http
|
@inject HttpClient http
|
||||||
@inject AppState state
|
@inject AppState state
|
||||||
|
|
||||||
<h3>Dashboard</h3>
|
<h3>Welcome, @state.User!.Name!</h3>
|
||||||
|
|
||||||
<p>Here's the dashboard, homie...</p>
|
@if (retrievingProfile)
|
||||||
|
|
||||||
@if (hasProfile != null)
|
|
||||||
{
|
{
|
||||||
<p>Has profile? @hasProfile</p>
|
<p>Retrieving your employment profile...</p>
|
||||||
|
}
|
||||||
|
else if (profile != null)
|
||||||
|
{
|
||||||
|
<p>Your employment profile was last updated @profile.LastUpdatedOn</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<p>You do not have an employment profile established; click “Profile”* in the menu to get started!</p>
|
||||||
|
<p><em>* Once it's there...</em></p>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (errorMessage != null)
|
@if (errorMessage != null)
|
||||||
|
@ -17,21 +24,22 @@
|
||||||
}
|
}
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
bool? hasProfile = null;
|
bool retrievingProfile = true;
|
||||||
|
Profile? profile = null;
|
||||||
string? errorMessage = null;
|
string? errorMessage = null;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
if (state.User != null)
|
if (state.User != null)
|
||||||
{
|
{
|
||||||
var profile = await ServerApi.RetrieveProfile(http, state);
|
var profileResult = await ServerApi.RetrieveProfile(http, state);
|
||||||
if (profile.IsOk)
|
if (profileResult.IsOk)
|
||||||
{
|
{
|
||||||
hasProfile = profile.Ok != null;
|
profile = profileResult.Ok;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
errorMessage = profile.Error;
|
errorMessage = profileResult.Error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,26 +26,32 @@
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="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">
|
||||||
|
<NavLink class="nav-link" href="/citizen/profile">
|
||||||
|
<span class="oi oi-spreadsheet" aria-hidden="true"></span> Profile
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
<li class="nav-item px-3">
|
<li class="nav-item px-3">
|
||||||
<NavLink class="nav-link" href="counter">
|
<NavLink class="nav-link" href="counter">
|
||||||
<span class="oi oi-plus" aria-hidden="true"></span> Log Off
|
<span class="oi oi-plus" aria-hidden="true"></span> Log Off
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
<li class="nav-item px-3">
|
|
||||||
<NavLink class="nav-link" href="fetchdata">
|
|
||||||
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
|
|
||||||
</NavLink>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
base.OnInitialized();
|
||||||
|
state.OnChange += StateHasChanged;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The client ID for Jobs, Jobs, Jobs at No Agenda Social
|
/// The client ID for Jobs, Jobs, Jobs at No Agenda Social
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -3,6 +3,7 @@ using JobsJobsJobs.Shared;
|
||||||
using JobsJobsJobs.Shared.Api;
|
using JobsJobsJobs.Shared.Api;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using NodaTime;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
@ -14,11 +15,14 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers
|
||||||
{
|
{
|
||||||
private readonly IConfigurationSection _config;
|
private readonly IConfigurationSection _config;
|
||||||
|
|
||||||
|
private readonly IClock _clock;
|
||||||
|
|
||||||
private readonly NpgsqlConnection _db;
|
private readonly NpgsqlConnection _db;
|
||||||
|
|
||||||
public CitizenController(IConfiguration config, NpgsqlConnection db)
|
public CitizenController(IConfiguration config, IClock clock, NpgsqlConnection db)
|
||||||
{
|
{
|
||||||
_config = config.GetSection("Auth");
|
_config = config.GetSection("Auth");
|
||||||
|
_clock = clock;
|
||||||
_db = db;
|
_db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +36,7 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers
|
||||||
|
|
||||||
// Step 2 - Find / establish Jobs, Jobs, Jobs account
|
// Step 2 - Find / establish Jobs, Jobs, Jobs account
|
||||||
var account = accountResult.Ok;
|
var account = accountResult.Ok;
|
||||||
var now = Milliseconds.Now();
|
var now = _clock.GetCurrentInstant();
|
||||||
|
|
||||||
await _db.OpenAsync();
|
await _db.OpenAsync();
|
||||||
var citizen = await _db.FindCitizenByNAUser(account.Username);
|
var citizen = await _db.FindCitizenByNAUser(account.Username);
|
||||||
|
@ -47,7 +51,7 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers
|
||||||
citizen = citizen with
|
citizen = citizen with
|
||||||
{
|
{
|
||||||
DisplayName = account.DisplayName,
|
DisplayName = account.DisplayName,
|
||||||
LastSeenOn = Milliseconds.Now()
|
LastSeenOn = now
|
||||||
};
|
};
|
||||||
await _db.UpdateCitizenOnLogOn(citizen);
|
await _db.UpdateCitizenOnLogOn(citizen);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace JobsJobsJobs.Server.Data
|
||||||
/// <returns>A populated citizen</returns>
|
/// <returns>A populated citizen</returns>
|
||||||
private static Citizen ToCitizen(NpgsqlDataReader rdr) =>
|
private static Citizen ToCitizen(NpgsqlDataReader rdr) =>
|
||||||
new Citizen(CitizenId.Parse(rdr.GetString("id")), rdr.GetString("na_user"), rdr.GetString("display_name"),
|
new Citizen(CitizenId.Parse(rdr.GetString("id")), rdr.GetString("na_user"), rdr.GetString("display_name"),
|
||||||
rdr.GetString("profile_url"), rdr.GetMilliseconds("joined_on"), rdr.GetMilliseconds("last_seen_on"));
|
rdr.GetString("profile_url"), rdr.GetInstant("joined_on"), rdr.GetInstant("last_seen_on"));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieve a citizen by their No Agenda Social user name
|
/// Retrieve a citizen by their No Agenda Social user name
|
||||||
|
@ -58,8 +58,8 @@ namespace JobsJobsJobs.Server.Data
|
||||||
cmd.Parameters.Add(new NpgsqlParameter("@na_user", citizen.NaUser));
|
cmd.Parameters.Add(new NpgsqlParameter("@na_user", citizen.NaUser));
|
||||||
cmd.Parameters.Add(new NpgsqlParameter("@display_name", citizen.DisplayName));
|
cmd.Parameters.Add(new NpgsqlParameter("@display_name", citizen.DisplayName));
|
||||||
cmd.Parameters.Add(new NpgsqlParameter("@profile_url", citizen.ProfileUrl));
|
cmd.Parameters.Add(new NpgsqlParameter("@profile_url", citizen.ProfileUrl));
|
||||||
cmd.Parameters.Add(new NpgsqlParameter("@joined_on", citizen.JoinedOn.Millis));
|
cmd.Parameters.Add(new NpgsqlParameter("@joined_on", citizen.JoinedOn));
|
||||||
cmd.Parameters.Add(new NpgsqlParameter("@last_seen_on", citizen.LastSeenOn.Millis));
|
cmd.Parameters.Add(new NpgsqlParameter("@last_seen_on", citizen.LastSeenOn));
|
||||||
|
|
||||||
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
|
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ namespace JobsJobsJobs.Server.Data
|
||||||
WHERE id = @id";
|
WHERE id = @id";
|
||||||
cmd.Parameters.Add(new NpgsqlParameter("@id", citizen.Id.ToString()));
|
cmd.Parameters.Add(new NpgsqlParameter("@id", citizen.Id.ToString()));
|
||||||
cmd.Parameters.Add(new NpgsqlParameter("@display_name", citizen.DisplayName));
|
cmd.Parameters.Add(new NpgsqlParameter("@display_name", citizen.DisplayName));
|
||||||
cmd.Parameters.Add(new NpgsqlParameter("@last_seen_on", citizen.LastSeenOn.Millis));
|
cmd.Parameters.Add(new NpgsqlParameter("@last_seen_on", citizen.LastSeenOn));
|
||||||
|
|
||||||
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
|
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using JobsJobsJobs.Shared;
|
using JobsJobsJobs.Shared;
|
||||||
|
using NodaTime;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -19,6 +20,14 @@ namespace JobsJobsJobs.Server.Data
|
||||||
/// <returns>The specified field as a boolean</returns>
|
/// <returns>The specified field as a boolean</returns>
|
||||||
public static bool GetBoolean(this NpgsqlDataReader rdr, string name) => rdr.GetBoolean(rdr.GetOrdinal(name));
|
public static bool GetBoolean(this NpgsqlDataReader rdr, string name) => rdr.GetBoolean(rdr.GetOrdinal(name));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get an Instant by its name
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the field to be retrieved as an Instant</param>
|
||||||
|
/// <returns>The specified field as an Instant</returns>
|
||||||
|
public static Instant GetInstant(this NpgsqlDataReader rdr, string name) =>
|
||||||
|
rdr.GetFieldValue<Instant>(rdr.GetOrdinal(name));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a 64-bit integer by its name
|
/// Get a 64-bit integer by its name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -26,14 +35,6 @@ namespace JobsJobsJobs.Server.Data
|
||||||
/// <returns>The specified field as a 64-bit integer</returns>
|
/// <returns>The specified field as a 64-bit integer</returns>
|
||||||
public static long GetInt64(this NpgsqlDataReader rdr, string name) => rdr.GetInt64(rdr.GetOrdinal(name));
|
public static long GetInt64(this NpgsqlDataReader rdr, string name) => rdr.GetInt64(rdr.GetOrdinal(name));
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get milliseconds by its name
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">The name of the field to be retrieved as milliseconds</param>
|
|
||||||
/// <returns>The specified field as milliseconds</returns>
|
|
||||||
public static Milliseconds GetMilliseconds(this NpgsqlDataReader rdr, string name) =>
|
|
||||||
new Milliseconds(rdr.GetInt64(name));
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a string by its name
|
/// Get a string by its name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace JobsJobsJobs.Server.Data
|
||||||
return new Profile(CitizenId.Parse(rdr.GetString("id")), rdr.GetBoolean("seeking_employment"),
|
return new Profile(CitizenId.Parse(rdr.GetString("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.GetMilliseconds("last_updated_on"),
|
rdr.GetInstant("last_updated_on"),
|
||||||
rdr.IsDBNull("experience") ? null : new MarkdownString(rdr.GetString("experience")))
|
rdr.IsDBNull("experience") ? null : new MarkdownString(rdr.GetString("experience")))
|
||||||
{
|
{
|
||||||
Continent = new Continent(continentId, rdr.GetString("continent_name"))
|
Continent = new Continent(continentId, rdr.GetString("continent_name"))
|
||||||
|
|
|
@ -7,8 +7,9 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.1" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="5.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="5.0.1" />
|
||||||
<PackageReference Include="Npgsql" Version="5.0.0" />
|
<PackageReference Include="Npgsql" Version="5.0.1.1" />
|
||||||
|
<PackageReference Include="Npgsql.NodaTime" Version="5.0.1.1" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.8.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.8.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
using JobsJobsJobs.Server.Data;
|
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
@ -8,8 +7,8 @@ using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using NodaTime;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace JobsJobsJobs.Server
|
namespace JobsJobsJobs.Server
|
||||||
|
@ -28,6 +27,7 @@ namespace JobsJobsJobs.Server
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddScoped(_ => new NpgsqlConnection(Configuration.GetConnectionString("JobsDb")));
|
services.AddScoped(_ => new NpgsqlConnection(Configuration.GetConnectionString("JobsDb")));
|
||||||
|
services.AddSingleton<IClock>(SystemClock.Instance);
|
||||||
services.AddLogging();
|
services.AddLogging();
|
||||||
services.AddControllersWithViews();
|
services.AddControllersWithViews();
|
||||||
services.AddRazorPages();
|
services.AddRazorPages();
|
||||||
|
@ -54,6 +54,8 @@ namespace JobsJobsJobs.Server
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
{
|
{
|
||||||
|
NpgsqlConnection.GlobalTypeMapper.UseNodaTime();
|
||||||
|
|
||||||
if (env.IsDevelopment())
|
if (env.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseDeveloperExceptionPage();
|
app.UseDeveloperExceptionPage();
|
||||||
|
@ -69,10 +71,6 @@ namespace JobsJobsJobs.Server
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
app.UseBlazorFrameworkFiles();
|
app.UseBlazorFrameworkFiles();
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
|
|
||||||
// TODO: middleware to extract user info from our custom JWT, and see if it will fill the standard stuff.
|
|
||||||
// Alternately, maybe we can even configure the default services and middleware so that it will work
|
|
||||||
// to give us the currently-logged-on user.
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
namespace JobsJobsJobs.Shared
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace JobsJobsJobs.Shared
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A user of Jobs, Jobs, Jobs
|
/// A user of Jobs, Jobs, Jobs
|
||||||
|
@ -8,6 +10,6 @@
|
||||||
string NaUser,
|
string NaUser,
|
||||||
string DisplayName,
|
string DisplayName,
|
||||||
string ProfileUrl,
|
string ProfileUrl,
|
||||||
Milliseconds JoinedOn,
|
Instant JoinedOn,
|
||||||
Milliseconds LastSeenOn);
|
Instant LastSeenOn);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace JobsJobsJobs.Shared
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Milliseconds past the epoch (JavaScript's date storage format)
|
|
||||||
/// </summary>
|
|
||||||
public record Milliseconds(long Millis)
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Get the milliseconds value for now
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A new milliseconds from the time now</returns>
|
|
||||||
public static Milliseconds Now() =>
|
|
||||||
new Milliseconds(
|
|
||||||
(DateTime.UtcNow.Ticks - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks) / 10000L);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,6 @@
|
||||||
namespace JobsJobsJobs.Shared
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace JobsJobsJobs.Shared
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A job seeker profile
|
/// A job seeker profile
|
||||||
|
@ -12,7 +14,7 @@
|
||||||
bool RemoteWork,
|
bool RemoteWork,
|
||||||
bool FullTime,
|
bool FullTime,
|
||||||
MarkdownString Biography,
|
MarkdownString Biography,
|
||||||
Milliseconds LastUpdatedOn,
|
Instant LastUpdatedOn,
|
||||||
MarkdownString? Experience)
|
MarkdownString? Experience)
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
namespace JobsJobsJobs.Shared
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace JobsJobsJobs.Shared
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A record of success finding employment
|
/// A record of success finding employment
|
||||||
|
@ -6,7 +8,7 @@
|
||||||
public record Success(
|
public record Success(
|
||||||
SuccessId Id,
|
SuccessId Id,
|
||||||
CitizenId CitizenId,
|
CitizenId CitizenId,
|
||||||
Milliseconds RecordedOn,
|
Instant RecordedOn,
|
||||||
bool FromHere,
|
bool FromHere,
|
||||||
MarkdownString? Story);
|
MarkdownString? Story);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Markdig" Version="0.22.0" />
|
<PackageReference Include="Markdig" Version="0.22.0" />
|
||||||
<PackageReference Include="Nanoid" Version="2.1.0" />
|
<PackageReference Include="Nanoid" Version="2.1.0" />
|
||||||
|
<PackageReference Include="NodaTime" Version="3.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -6,8 +6,8 @@ CREATE TABLE jjj.citizen (
|
||||||
na_user VARCHAR(50) NOT NULL,
|
na_user VARCHAR(50) NOT NULL,
|
||||||
display_name VARCHAR(255) NOT NULL,
|
display_name VARCHAR(255) NOT NULL,
|
||||||
profile_url VARCHAR(1024) NOT NULL,
|
profile_url VARCHAR(1024) NOT NULL,
|
||||||
joined_on BIGINT NOT NULL,
|
joined_on TIMESTAMP NOT NULL,
|
||||||
last_seen_on BIGINT NOT NULL,
|
last_seen_on TIMESTAMP NOT NULL,
|
||||||
CONSTRAINT pk_citizen PRIMARY KEY (id),
|
CONSTRAINT pk_citizen PRIMARY KEY (id),
|
||||||
CONSTRAINT uk_na_user UNIQUE (na_user)
|
CONSTRAINT uk_na_user UNIQUE (na_user)
|
||||||
);
|
);
|
||||||
|
@ -45,7 +45,7 @@ CREATE TABLE jjj.profile (
|
||||||
remote_work BOOLEAN NOT NULL,
|
remote_work BOOLEAN NOT NULL,
|
||||||
full_time BOOLEAN NOT NULL,
|
full_time BOOLEAN NOT NULL,
|
||||||
biography TEXT NOT NULL,
|
biography TEXT NOT NULL,
|
||||||
last_updated_on BIGINT NOT NULL,
|
last_updated_on TIMESTAMP NOT NULL,
|
||||||
experience TEXT,
|
experience TEXT,
|
||||||
CONSTRAINT pk_profile PRIMARY KEY (citizen_id),
|
CONSTRAINT pk_profile PRIMARY KEY (citizen_id),
|
||||||
CONSTRAINT fk_profile_citizen FOREIGN KEY (citizen_id) REFERENCES jjj.citizen (id),
|
CONSTRAINT fk_profile_citizen FOREIGN KEY (citizen_id) REFERENCES jjj.citizen (id),
|
||||||
|
@ -100,7 +100,7 @@ COMMENT ON INDEX jjj.idx_skill_citizen IS 'FK index';
|
||||||
CREATE TABLE jjj.success (
|
CREATE TABLE jjj.success (
|
||||||
id VARCHAR(12) NOT NULL,
|
id VARCHAR(12) NOT NULL,
|
||||||
citizen_id VARCHAR(12) NOT NULL,
|
citizen_id VARCHAR(12) NOT NULL,
|
||||||
recorded_on BIGINT NOT NULL,
|
recorded_on TIMESTAMP NOT NULL,
|
||||||
from_here BOOLEAN NOT NULL,
|
from_here BOOLEAN NOT NULL,
|
||||||
story TEXT,
|
story TEXT,
|
||||||
CONSTRAINT pk_success PRIMARY KEY (id),
|
CONSTRAINT pk_success PRIMARY KEY (id),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user