forked from SteveSandersonMS/CarChecker
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 5897e8b
Showing
93 changed files
with
7,288 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
node_modules/ | ||
bin/ | ||
obj/ | ||
.vs/ | ||
*.csproj.user | ||
Server/app.db |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 16 | ||
VisualStudioVersion = 16.0.0.0 | ||
MinimumVisualStudioVersion = 16.0.0.0 | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CarChecker.Server", "Server\CarChecker.Server.csproj", "{28148247-170D-49AE-A367-7984410446CF}" | ||
EndProject | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CarChecker.Client", "Client\CarChecker.Client.csproj", "{985FD988-9FA6-4305-A7BB-70E31DF1D0B8}" | ||
EndProject | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CarChecker.Shared", "Shared\CarChecker.Shared.csproj", "{C5DD5CF5-8462-4433-B463-E3F3EA6F0FA1}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Debug|x64 = Debug|x64 | ||
Debug|x86 = Debug|x86 | ||
Release|Any CPU = Release|Any CPU | ||
Release|x64 = Release|x64 | ||
Release|x86 = Release|x86 | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{28148247-170D-49AE-A367-7984410446CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{28148247-170D-49AE-A367-7984410446CF}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{28148247-170D-49AE-A367-7984410446CF}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
{28148247-170D-49AE-A367-7984410446CF}.Debug|x64.Build.0 = Debug|Any CPU | ||
{28148247-170D-49AE-A367-7984410446CF}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
{28148247-170D-49AE-A367-7984410446CF}.Debug|x86.Build.0 = Debug|Any CPU | ||
{28148247-170D-49AE-A367-7984410446CF}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{28148247-170D-49AE-A367-7984410446CF}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{28148247-170D-49AE-A367-7984410446CF}.Release|x64.ActiveCfg = Release|Any CPU | ||
{28148247-170D-49AE-A367-7984410446CF}.Release|x64.Build.0 = Release|Any CPU | ||
{28148247-170D-49AE-A367-7984410446CF}.Release|x86.ActiveCfg = Release|Any CPU | ||
{28148247-170D-49AE-A367-7984410446CF}.Release|x86.Build.0 = Release|Any CPU | ||
{985FD988-9FA6-4305-A7BB-70E31DF1D0B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{985FD988-9FA6-4305-A7BB-70E31DF1D0B8}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{985FD988-9FA6-4305-A7BB-70E31DF1D0B8}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
{985FD988-9FA6-4305-A7BB-70E31DF1D0B8}.Debug|x64.Build.0 = Debug|Any CPU | ||
{985FD988-9FA6-4305-A7BB-70E31DF1D0B8}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
{985FD988-9FA6-4305-A7BB-70E31DF1D0B8}.Debug|x86.Build.0 = Debug|Any CPU | ||
{985FD988-9FA6-4305-A7BB-70E31DF1D0B8}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{985FD988-9FA6-4305-A7BB-70E31DF1D0B8}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{985FD988-9FA6-4305-A7BB-70E31DF1D0B8}.Release|x64.ActiveCfg = Release|Any CPU | ||
{985FD988-9FA6-4305-A7BB-70E31DF1D0B8}.Release|x64.Build.0 = Release|Any CPU | ||
{985FD988-9FA6-4305-A7BB-70E31DF1D0B8}.Release|x86.ActiveCfg = Release|Any CPU | ||
{985FD988-9FA6-4305-A7BB-70E31DF1D0B8}.Release|x86.Build.0 = Release|Any CPU | ||
{C5DD5CF5-8462-4433-B463-E3F3EA6F0FA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{C5DD5CF5-8462-4433-B463-E3F3EA6F0FA1}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{C5DD5CF5-8462-4433-B463-E3F3EA6F0FA1}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
{C5DD5CF5-8462-4433-B463-E3F3EA6F0FA1}.Debug|x64.Build.0 = Debug|Any CPU | ||
{C5DD5CF5-8462-4433-B463-E3F3EA6F0FA1}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
{C5DD5CF5-8462-4433-B463-E3F3EA6F0FA1}.Debug|x86.Build.0 = Debug|Any CPU | ||
{C5DD5CF5-8462-4433-B463-E3F3EA6F0FA1}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{C5DD5CF5-8462-4433-B463-E3F3EA6F0FA1}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{C5DD5CF5-8462-4433-B463-E3F3EA6F0FA1}.Release|x64.ActiveCfg = Release|Any CPU | ||
{C5DD5CF5-8462-4433-B463-E3F3EA6F0FA1}.Release|x64.Build.0 = Release|Any CPU | ||
{C5DD5CF5-8462-4433-B463-E3F3EA6F0FA1}.Release|x86.ActiveCfg = Release|Any CPU | ||
{C5DD5CF5-8462-4433-B463-E3F3EA6F0FA1}.Release|x86.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {C511E458-9B4D-4D16-AB90-69DC35B0F57B} | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<CascadingAuthenticationState> | ||
<Router AppAssembly="@typeof(Program).Assembly"> | ||
<Found Context="routeData"> | ||
<AuthorizeRouteView RouteData="@routeData"> | ||
<Authorizing> | ||
<LayoutView Layout="@typeof(HeaderLayout)"> | ||
<div class="loader"></div> | ||
</LayoutView> | ||
</Authorizing> | ||
<NotAuthorized> | ||
@if (!context.User.Identity.IsAuthenticated) | ||
{ | ||
<NotLoggedIn /> | ||
} | ||
else | ||
{ | ||
<LayoutView Layout="@typeof(HeaderLayout)"> | ||
<p>You are not authorized to access this resource.</p> | ||
</LayoutView> | ||
} | ||
</NotAuthorized> | ||
</AuthorizeRouteView> | ||
</Found> | ||
<NotFound> | ||
<LayoutView Layout="@typeof(HeaderLayout)"> | ||
<p>Sorry, there's nothing at this address.</p> | ||
</LayoutView> | ||
</NotFound> | ||
</Router> | ||
</CascadingAuthenticationState> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netstandard2.1</TargetFramework> | ||
<RazorLangVersion>3.0</RazorLangVersion> | ||
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="BlazorInputFile" Version="0.2.0" /> | ||
<PackageReference Include="SceneViewer" Version="0.1.0" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Components.DataAnnotations.Validation" Version="3.2.0" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="3.2.0" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.0" PrivateAssets="all" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="3.2.0" PrivateAssets="all" /> | ||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="3.2.0" /> | ||
<PackageReference Include="Microsoft.Extensions.Localization" Version="3.1.3" /> | ||
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.3" /> | ||
<PackageReference Include="System.Net.Http.Json" Version="3.2.0" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Shared\CarChecker.Shared.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<Compile Update="Resources\App.Designer.cs" DesignTime="True" AutoGen="True" DependentUpon="App.resx" /> | ||
<EmbeddedResource Update="Resources\App.resx"> | ||
<Generator>PublicResXFileCodeGenerator</Generator> | ||
<LastGenOutput>App.Designer.cs</LastGenOutput> | ||
</EmbeddedResource> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
using System; | ||
using System.IO; | ||
|
||
namespace CarChecker.Client.Data | ||
{ | ||
public static class DataUrl | ||
{ | ||
public static string ToDataUrl(this MemoryStream data, string format) | ||
{ | ||
var span = new Span<byte>(data.GetBuffer()).Slice(0, (int)data.Length); | ||
return $"data:{format};base64,{Convert.ToBase64String(span)}"; | ||
} | ||
|
||
public static byte[] ToBytes(string url) | ||
{ | ||
var commaPos = url.IndexOf(','); | ||
if (commaPos >= 0) | ||
{ | ||
var base64 = url.Substring(commaPos + 1); | ||
return Convert.FromBase64String(base64); | ||
} | ||
|
||
return null; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
using System; | ||
using System.Linq; | ||
using System.Net.Http; | ||
using System.Net.Http.Json; | ||
using System.Security.Claims; | ||
using System.Threading.Tasks; | ||
using CarChecker.Shared; | ||
using Microsoft.JSInterop; | ||
|
||
namespace CarChecker.Client.Data | ||
{ | ||
// To support offline use, we use this simple local data repository | ||
// instead of performing data access directly against the server. | ||
// This would not be needed if we assumed that network access was always | ||
// available. | ||
|
||
public class LocalVehiclesStore | ||
{ | ||
private readonly HttpClient httpClient; | ||
private readonly IJSRuntime js; | ||
|
||
public LocalVehiclesStore(HttpClient httpClient, IJSRuntime js) | ||
{ | ||
this.httpClient = httpClient; | ||
this.js = js; | ||
} | ||
|
||
public ValueTask<Vehicle[]> GetOutstandingLocalEditsAsync() | ||
{ | ||
return js.InvokeAsync<Vehicle[]>( | ||
"localVehicleStore.getAll", "localedits"); | ||
} | ||
|
||
public async Task SynchronizeAsync() | ||
{ | ||
// If there are local edits, always send them first | ||
foreach (var editedVehicle in await GetOutstandingLocalEditsAsync()) | ||
{ | ||
(await httpClient.PutAsJsonAsync("api/vehicle/details", editedVehicle)).EnsureSuccessStatusCode(); | ||
await DeleteAsync("localedits", editedVehicle.LicenseNumber); | ||
} | ||
|
||
await FetchChangesAsync(); | ||
} | ||
|
||
public ValueTask SaveUserAccountAsync(ClaimsPrincipal user) | ||
{ | ||
return user != null | ||
? PutAsync("metadata", "userAccount", user.Claims.Select(c => new ClaimData { Type = c.Type, Value = c.Value })) | ||
: DeleteAsync("metadata", "userAccount"); | ||
} | ||
|
||
public async Task<ClaimsPrincipal> LoadUserAccountAsync() | ||
{ | ||
var storedClaims = await GetAsync<ClaimData[]>("metadata", "userAccount"); | ||
return storedClaims != null | ||
? new ClaimsPrincipal(new ClaimsIdentity(storedClaims.Select(c => new Claim(c.Type, c.Value)), "appAuth")) | ||
: new ClaimsPrincipal(new ClaimsIdentity()); | ||
} | ||
|
||
public ValueTask<string[]> Autocomplete(string prefix) | ||
=> js.InvokeAsync<string[]>("localVehicleStore.autocompleteKeys", "serverdata", prefix, 5); | ||
|
||
// If there's an outstanding local edit, use that. If not, use the server data. | ||
public async Task<Vehicle> GetVehicle(string licenseNumber) | ||
=> await GetAsync<Vehicle>("localedits", licenseNumber) | ||
?? await GetAsync<Vehicle>("serverdata", licenseNumber); | ||
|
||
public async ValueTask<DateTime?> GetLastUpdateDateAsync() | ||
{ | ||
var value = await GetAsync<string>("metadata", "lastUpdateDate"); | ||
return value == null ? (DateTime?)null : DateTime.Parse(value); | ||
} | ||
|
||
public ValueTask SaveVehicleAsync(Vehicle vehicle) | ||
=> PutAsync("localedits", null, vehicle); | ||
|
||
async Task FetchChangesAsync() | ||
{ | ||
var mostRecentlyUpdated = await js.InvokeAsync<Vehicle>("localVehicleStore.getFirstFromIndex", "serverdata", "lastUpdated", "prev"); | ||
var since = mostRecentlyUpdated?.LastUpdated ?? DateTime.MinValue; | ||
var json = await httpClient.GetStringAsync($"api/vehicle/changedvehicles?since={since:o}"); | ||
await js.InvokeVoidAsync("localVehicleStore.putAllFromJson", "serverdata", json); | ||
await PutAsync("metadata", "lastUpdateDate", DateTime.Now.ToString("o")); | ||
} | ||
|
||
ValueTask<T> GetAsync<T>(string storeName, object key) | ||
=> js.InvokeAsync<T>("localVehicleStore.get", storeName, key); | ||
|
||
ValueTask PutAsync<T>(string storeName, object key, T value) | ||
=> js.InvokeVoidAsync("localVehicleStore.put", storeName, key, value); | ||
|
||
ValueTask DeleteAsync(string storeName, object key) | ||
=> js.InvokeVoidAsync("localVehicleStore.delete", storeName, key); | ||
|
||
class ClaimData | ||
{ | ||
public string Type { get; set; } | ||
public string Value { get; set; } | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
using Microsoft.AspNetCore.Components.WebAssembly.Authentication; | ||
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using System; | ||
using System.Security.Claims; | ||
using System.Threading.Tasks; | ||
|
||
namespace CarChecker.Client.Data | ||
{ | ||
public class OfflineAccountClaimsPrincipalFactory : AccountClaimsPrincipalFactory<RemoteUserAccount> | ||
{ | ||
private readonly IServiceProvider services; | ||
|
||
public OfflineAccountClaimsPrincipalFactory(IServiceProvider services, IAccessTokenProviderAccessor accessor) : base(accessor) | ||
{ | ||
this.services = services; | ||
} | ||
|
||
public override async ValueTask<ClaimsPrincipal> CreateUserAsync(RemoteUserAccount account, RemoteAuthenticationUserOptions options) | ||
{ | ||
var localVehiclesStore = services.GetRequiredService<LocalVehiclesStore>(); | ||
|
||
var result = await base.CreateUserAsync(account, options); | ||
if (result.Identity.IsAuthenticated) | ||
{ | ||
await localVehiclesStore.SaveUserAccountAsync(result); | ||
} | ||
else | ||
{ | ||
result = await localVehiclesStore.LoadUserAccountAsync(); | ||
} | ||
|
||
return result; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using System.Security.Claims; | ||
|
||
namespace CarChecker.Client.Data | ||
{ | ||
public static class UserExtensions | ||
{ | ||
public static string FirstName(this ClaimsPrincipal user) | ||
=> user.FindFirst("firstname").Value; | ||
|
||
public static string LastName(this ClaimsPrincipal user) | ||
=> user.FindFirst("lastname").Value; | ||
|
||
public static string Email(this ClaimsPrincipal user) | ||
=> user.Identity.Name; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
@page "/authentication/{action}" | ||
@layout HeaderLayout | ||
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication | ||
|
||
<RemoteAuthenticatorView Action="@Action"> | ||
<LoggingIn> | ||
<div class="loader"></div> | ||
</LoggingIn> | ||
<LogOutSucceeded> | ||
<Redirect Url="" /> | ||
</LogOutSucceeded> | ||
</RemoteAuthenticatorView> | ||
|
||
@code{ | ||
[Parameter] public string Action { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
@page "/" | ||
@attribute [Authorize] | ||
@inject NavigationManager Navigation | ||
@inject LocalVehiclesStore LocalVehiclesStore | ||
@inject IStringLocalizer<App> Localize | ||
|
||
<AuthorizeView> | ||
<header> | ||
<div class="toolbar"> | ||
<button class="toolbar-item" @onclick="() => loginStatusOverlay.Show()">☰</button> | ||
</div> | ||
<div class="title">@Localize["Welcome, {0}!", context.User.FirstName()]</div> | ||
</header> | ||
|
||
<Overlay @ref="loginStatusOverlay" OverlayStyle="Overlay.Style.Top" CssClass="home-overlay"> | ||
<LoginStatus /> | ||
</Overlay> | ||
</AuthorizeView> | ||
|
||
<main class="container"> | ||
<EditForm class="home-options" Model="this" OnValidSubmit="FindVehicle"> | ||
<DataAnnotationsValidator /> | ||
|
||
<p>@Localize["Enter license number:"]</p> | ||
<Autocomplete @bind-Value="@LicenseNumber" Choices="@GetLicenseAutocompleteItems" | ||
OnItemChosen="FindVehicle" class="find-by-license-plate" placeholder="@Localize["License"]" /> | ||
<ValidationMessage For="() => LicenseNumber" /> | ||
</EditForm> | ||
</main> | ||
|
||
<footer> | ||
<SyncStatus /> | ||
</footer> | ||
|
||
@code { | ||
Overlay loginStatusOverlay; | ||
|
||
[RegularExpression("[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*\\-{0,1}", ErrorMessageResourceType = typeof(Resources.App), ErrorMessageResourceName = nameof(Resources.App.LicenseNumberIncorrectFormat))] | ||
public string LicenseNumber { get; set; } | ||
|
||
async Task<IEnumerable<string>> GetLicenseAutocompleteItems(string prefix) | ||
{ | ||
return await LocalVehiclesStore.Autocomplete(prefix); | ||
} | ||
|
||
void FindVehicle() | ||
{ | ||
if (!string.IsNullOrEmpty(LicenseNumber)) | ||
{ | ||
Navigation.NavigateTo($"vehicle/{Uri.EscapeDataString(LicenseNumber)}"); | ||
} | ||
} | ||
} |
Oops, something went wrong.