diff --git a/.dockerignore b/.dockerignore index 3729ff0c..2588c273 100644 --- a/.dockerignore +++ b/.dockerignore @@ -21,5 +21,7 @@ **/obj **/secrets.dev.yaml **/values.dev.yaml +**/.angular +**/config LICENSE README.md \ No newline at end of file diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 5ea631ae..c036425d 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -34,7 +34,7 @@ jobs: - name: Setup Buildx uses: actions/setup-dotnet@v1 with: - dotnet-version: '6.0.x' + dotnet-version: '7.x' include-prerelease: true - name: Build Artifact diff --git a/.gitignore b/.gitignore index 0611cf78..7713f570 100644 --- a/.gitignore +++ b/.gitignore @@ -356,4 +356,4 @@ dist/ **/log*.txt *db-shm *db-wal -OpenAlprWebhookProcessor/ClientApp/.angular/* \ No newline at end of file +OpenAlprWebhookProcessor/angularapp/.angular/* \ No newline at end of file diff --git a/OpenAlprWebhookProcessor/Alerts/AlertService.cs b/OpenAlprWebhookProcessor/Alerts/AlertService.cs index c0bd9f82..1f2ff071 100644 --- a/OpenAlprWebhookProcessor/Alerts/AlertService.cs +++ b/OpenAlprWebhookProcessor/Alerts/AlertService.cs @@ -6,6 +6,7 @@ using OpenAlprWebhookProcessor.Data; using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -24,20 +25,20 @@ public class AlertService : IHostedService private readonly IHubContext _processorHub; - private readonly IAlertClient _alertClient; + private readonly IEnumerable _alertClients; public AlertService( IServiceProvider serviceProvider, ILogger logger, IHubContext processorHub, - IAlertClient alertClient) + IEnumerable alertClients) { _logger = logger; _serviceProvider = serviceProvider; _cancellationTokenSource = new CancellationTokenSource(); _alertsToProcess = new BlockingCollection(); _processorHub = processorHub; - _alertClient = alertClient; + _alertClients = alertClients; } public void AddJob(AlertUpdateRequest request) @@ -81,27 +82,32 @@ private async Task ProcessAlertsAsync() plateGroups = plateGroups.Where(x => x.Id == job.LicensePlateId); } - var result = await plateGroups.FirstOrDefaultAsync(_cancellationTokenSource.Token); + var result = await plateGroups + .Include(x => x.PlateImage) + .FirstOrDefaultAsync(_cancellationTokenSource.Token); if (result != null) { _logger.LogInformation("alerting for: {alertId}", result.Id); await _processorHub.Clients.All.LicensePlateAlerted(result.Id.ToString()); - try + foreach (var alertClient in _alertClients) { - await _alertClient.SendAlertAsync(new Alert() + try { - Description = job.Description, - Id = result.Id, - PlateNumber = result.BestNumber, - }, - result.PlatePreviewJpeg, - _cancellationTokenSource.Token); - } - catch (Exception ex) - { - _logger.LogError(ex, $"failed to send alert to {nameof(_alertClient)}"); + await alertClient.SendAlertAsync(new Alert() + { + Description = job.Description, + Id = result.Id, + PlateNumber = result.BestNumber, + }, + result.PlateImage.Jpeg, + _cancellationTokenSource.Token); + } + catch (Exception ex) + { + _logger.LogError(ex, $"failed to send alert to {nameof(alertClient)}"); + } } } } diff --git a/OpenAlprWebhookProcessor/Alerts/AlertsController.cs b/OpenAlprWebhookProcessor/Alerts/AlertsController.cs index 96437dad..fecce19b 100644 --- a/OpenAlprWebhookProcessor/Alerts/AlertsController.cs +++ b/OpenAlprWebhookProcessor/Alerts/AlertsController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using OpenAlprWebhookProcessor.Alerts.Pushover; +using OpenAlprWebhookProcessor.Alerts.WebPush; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -22,18 +23,30 @@ public class AlertsController : Controller private readonly TestPushoverClientRequestHandler _testPushoverClientRequestHandler; + private readonly UpsertWebPushClientRequestHandler _upsertWebPushRequestHandler; + + private readonly GetWebPushClientRequestHandler _getWebPushClientRequestHandler; + + private readonly TestWebPushClientRequestHandler _testWebPushClientRequestHandler; + public AlertsController( GetAlertsRequestHandler getAlertsRequestHandler, UpsertAlertsRequestHandler upsertAlertsRequestHandler, UpsertPushoverClientRequestHandler upsertPushoverRequestHandler, GetPushoverClientRequestHandler getPushoverClientRequestHandler, - TestPushoverClientRequestHandler testPushoverClientRequestHandler) + TestPushoverClientRequestHandler testPushoverClientRequestHandler, + UpsertWebPushClientRequestHandler upsertWebPushRequestHandler, + GetWebPushClientRequestHandler getWebPushClientRequestHandler, + TestWebPushClientRequestHandler testWebPushClientRequestHandler) { _getAlertsRequestHandler = getAlertsRequestHandler; _upsertAlertsRequestHandler = upsertAlertsRequestHandler; _upsertPushoverRequestHandler = upsertPushoverRequestHandler; _getPushoverClientRequestHandler = getPushoverClientRequestHandler; _testPushoverClientRequestHandler = testPushoverClientRequestHandler; + _upsertWebPushRequestHandler = upsertWebPushRequestHandler; + _getWebPushClientRequestHandler = getWebPushClientRequestHandler; + _testWebPushClientRequestHandler = testWebPushClientRequestHandler; } [HttpPost("add")] @@ -71,5 +84,23 @@ public async Task GetPushover(CancellationToken cancellationTok { return await _getPushoverClientRequestHandler.HandleAsync(cancellationToken); } + + [HttpPost("webpush")] + public async Task UpsertWebpush([FromBody] WebPushRequest request) + { + await _upsertWebPushRequestHandler.HandleAsync(request); + } + + [HttpPost("webpush/test")] + public async Task UpsertWebpush(CancellationToken cancellationToken) + { + await _testWebPushClientRequestHandler.HandleAsync(cancellationToken); + } + + [HttpGet("webpush")] + public async Task GetWebpush(CancellationToken cancellationToken) + { + return await _getWebPushClientRequestHandler.HandleAsync(cancellationToken); + } } } diff --git a/OpenAlprWebhookProcessor/Alerts/IAlertClient.cs b/OpenAlprWebhookProcessor/Alerts/IAlertClient.cs index 92a5fe62..b508ebb4 100644 --- a/OpenAlprWebhookProcessor/Alerts/IAlertClient.cs +++ b/OpenAlprWebhookProcessor/Alerts/IAlertClient.cs @@ -1,4 +1,5 @@ -using System.Threading; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; +using System.Threading; using System.Threading.Tasks; namespace OpenAlprWebhookProcessor.Alerts @@ -7,7 +8,7 @@ public interface IAlertClient { Task SendAlertAsync( Alert alert, - string base64PreviewJpeg, + byte[] plateJpeg, CancellationToken cancellationToken); Task VerifyCredentialsAsync( diff --git a/OpenAlprWebhookProcessor/Alerts/Pushover/PushoverClient.cs b/OpenAlprWebhookProcessor/Alerts/Pushover/PushoverClient.cs index 3ebd507a..32f5f141 100644 --- a/OpenAlprWebhookProcessor/Alerts/Pushover/PushoverClient.cs +++ b/OpenAlprWebhookProcessor/Alerts/Pushover/PushoverClient.cs @@ -28,7 +28,7 @@ public PushoverClient(IServiceProvider serviceProvider) public async Task SendAlertAsync( Alert alert, - string base64PreviewJpeg, + byte[] plateJpeg, CancellationToken cancellationToken) { using (var scope = _serviceProvider.CreateScope()) @@ -39,7 +39,9 @@ public async Task SendAlertAsync( var processorContext = scope.ServiceProvider.GetRequiredService(); - var clientSettings = await processorContext.PushoverAlertClients.FirstOrDefaultAsync(cancellationToken); + var clientSettings = await processorContext.PushoverAlertClients + .AsNoTracking() + .FirstOrDefaultAsync(cancellationToken); var boundary = Guid.NewGuid().ToString(); using (var content = new MultipartFormDataContent(boundary)) @@ -51,9 +53,9 @@ public async Task SendAlertAsync( content.Add(new StringContent(alert.PlateNumber + " " + alert.Description), "message"); content.Add(new StringContent("openalpr alert"), "title"); - if (clientSettings.SendPlatePreview) + if (clientSettings.SendPlatePreview && plateJpeg != null) { - content.Add(new ByteArrayContent(Convert.FromBase64String(base64PreviewJpeg)), "attachment", "attachment.jpg"); + content.Add(new ByteArrayContent(plateJpeg), "attachment", "attachment.jpg"); } try @@ -67,7 +69,7 @@ public async Task SendAlertAsync( { var result = await response.Content.ReadAsStringAsync(cancellationToken); - logger.LogError("Failed to send alert via Pushover: " + result); + logger.LogError("Failed to send alert via Pushover: {result}", result); throw new InvalidOperationException("failed"); } @@ -75,7 +77,7 @@ public async Task SendAlertAsync( } catch (Exception ex) { - logger.LogError(ex, "Failed to send alert via Pushover: " + ex.Message); + logger.LogError(ex, "Failed to send alert via Pushover: {exception}", ex.Message); throw new InvalidOperationException("failed"); } } @@ -92,27 +94,29 @@ public async Task VerifyCredentialsAsync(CancellationToken cancellationToken) var processorContext = scope.ServiceProvider.GetRequiredService(); - var clientSettings = await processorContext.PushoverAlertClients.FirstOrDefaultAsync(cancellationToken); + var clientSettings = await processorContext.PushoverAlertClients + .AsNoTracking() + .FirstOrDefaultAsync(cancellationToken); try { var result = await _httpClient.PostAsync(VerifyCredentialsUrl - .Replace("{0}", clientSettings.ApiToken) - .Replace("{1}", clientSettings.UserKey), + .Replace("{0}", clientSettings.ApiToken) + .Replace("{1}", clientSettings.UserKey), null, cancellationToken); if (!result.IsSuccessStatusCode) { var message = await result.Content.ReadAsStringAsync(cancellationToken); - logger.LogError("Pushover credential check failed: " + message); + logger.LogError("Pushover credential check failed: {message}", message); } logger.LogInformation("Pushover credentials are valid."); } catch (Exception ex) { - logger.LogError(ex, "Pushover credential check failed: " + ex.Message); + logger.LogError(ex, "Pushover credential check failed: {exception}", ex.Message); } } } diff --git a/OpenAlprWebhookProcessor/Alerts/Pushover/TestPushoverClientRequestHandler.cs b/OpenAlprWebhookProcessor/Alerts/Pushover/TestPushoverClientRequestHandler.cs index e3c7204a..98e5fc48 100644 --- a/OpenAlprWebhookProcessor/Alerts/Pushover/TestPushoverClientRequestHandler.cs +++ b/OpenAlprWebhookProcessor/Alerts/Pushover/TestPushoverClientRequestHandler.cs @@ -1,6 +1,8 @@ using Microsoft.EntityFrameworkCore; using OpenAlprWebhookProcessor.Data; +using OpenAlprWebhookProcessor.WebPushSubscriptions; using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -14,16 +16,17 @@ public class TestPushoverClientRequestHandler private readonly IAlertClient _alertClient; public TestPushoverClientRequestHandler(ProcessorContext processorContext, - IAlertClient alertClient) + IEnumerable alertClients) { _processorContext = processorContext; - _alertClient = alertClient; + _alertClient = alertClients.First(x => x is WebPushNotificationProducer); } public async Task HandleAsync(CancellationToken cancellationToken) { var testPlateGroup = await _processorContext.PlateGroups - .Where(x => x.PlatePreviewJpeg != null) + .Include(x => x.PlateImage) + .Where(x => x.PlateImage != null) .FirstOrDefaultAsync(cancellationToken); await _alertClient.VerifyCredentialsAsync(cancellationToken); @@ -35,7 +38,7 @@ await _alertClient.SendAlertAsync(new Alert() PlateNumber = testPlateGroup.BestNumber, StrictMatch = false, }, - testPlateGroup.PlatePreviewJpeg, + testPlateGroup.PlateImage.Jpeg, cancellationToken); } } diff --git a/OpenAlprWebhookProcessor/Alerts/WebPush/GetWebPushClientRequestHandler.cs b/OpenAlprWebhookProcessor/Alerts/WebPush/GetWebPushClientRequestHandler.cs new file mode 100644 index 00000000..964ecd22 --- /dev/null +++ b/OpenAlprWebhookProcessor/Alerts/WebPush/GetWebPushClientRequestHandler.cs @@ -0,0 +1,36 @@ +using OpenAlprWebhookProcessor.Data; +using System.Threading.Tasks; +using System.Threading; +using Microsoft.EntityFrameworkCore; +using OpenAlprWebhookProcessor.WebPushSubscriptions.VapidKeys; + +namespace OpenAlprWebhookProcessor.Alerts.WebPush +{ + public class GetWebPushClientRequestHandler + { + private readonly ProcessorContext _processorContext; + + public GetWebPushClientRequestHandler(ProcessorContext processorContext) + { + _processorContext = processorContext; + } + + public async Task HandleAsync(CancellationToken cancellationToken) + { + var client = await _processorContext.WebPushSettings.FirstOrDefaultAsync(cancellationToken); + + if (client == null) + { + client = VapidKeyHelper.AddVapidKeys(_processorContext); + } + + return new WebPushRequest() + { + IsEnabled = client.IsEnabled, + EmailAddress = client.Subject, + PublicKey = client.PublicKey, + PrivateKey = client.PrivateKey, + }; + } + } +} diff --git a/OpenAlprWebhookProcessor/Alerts/WebPush/TestPushoverClientRequestHandler.cs b/OpenAlprWebhookProcessor/Alerts/WebPush/TestPushoverClientRequestHandler.cs new file mode 100644 index 00000000..614f9a82 --- /dev/null +++ b/OpenAlprWebhookProcessor/Alerts/WebPush/TestPushoverClientRequestHandler.cs @@ -0,0 +1,45 @@ +using Microsoft.EntityFrameworkCore; +using OpenAlprWebhookProcessor.Data; +using OpenAlprWebhookProcessor.WebPushSubscriptions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace OpenAlprWebhookProcessor.Alerts.WebPush +{ + public class TestWebPushClientRequestHandler + { + private readonly ProcessorContext _processorContext; + + private readonly IAlertClient _alertClient; + + public TestWebPushClientRequestHandler(ProcessorContext processorContext, + IEnumerable alertClients) + { + _processorContext = processorContext; + _alertClient = alertClients.First(x => x is WebPushNotificationProducer); + } + + public async Task HandleAsync(CancellationToken cancellationToken) + { + var testPlateGroup = await _processorContext.PlateGroups + .Include(x => x.PlateImage) + .Where(x => x.PlateImage != null) + .FirstOrDefaultAsync(cancellationToken); + + await _alertClient.VerifyCredentialsAsync(cancellationToken); + + await _alertClient.SendAlertAsync(new Alert() + { + Description = "was seen on " + DateTimeOffset.Now.ToString("g"), + Id = testPlateGroup.Id, + PlateNumber = testPlateGroup.BestNumber, + StrictMatch = false, + }, + testPlateGroup.PlateImage.Jpeg, + cancellationToken); + } + } +} diff --git a/OpenAlprWebhookProcessor/Alerts/WebPush/UpsertWebPushClientRequestHandler.cs b/OpenAlprWebhookProcessor/Alerts/WebPush/UpsertWebPushClientRequestHandler.cs new file mode 100644 index 00000000..dbc2a56b --- /dev/null +++ b/OpenAlprWebhookProcessor/Alerts/WebPush/UpsertWebPushClientRequestHandler.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using OpenAlprWebhookProcessor.Data; +using OpenAlprWebhookProcessor.WebPushSubscriptions.VapidKeys; +using System.Threading.Tasks; + +namespace OpenAlprWebhookProcessor.Alerts.WebPush +{ + public class UpsertWebPushClientRequestHandler + { + private readonly ProcessorContext _processorContext; + + public UpsertWebPushClientRequestHandler(ProcessorContext processorContext) + { + _processorContext = processorContext; + } + + public async Task HandleAsync(WebPushRequest request) + { + var webPushClient = await _processorContext.WebPushSettings.FirstOrDefaultAsync(); + + webPushClient.IsEnabled = request.IsEnabled; + webPushClient.Subject = request.EmailAddress; + + await _processorContext.SaveChangesAsync(); + } + } +} diff --git a/OpenAlprWebhookProcessor/Alerts/WebPush/WebPushRequest.cs b/OpenAlprWebhookProcessor/Alerts/WebPush/WebPushRequest.cs new file mode 100644 index 00000000..67a6cafa --- /dev/null +++ b/OpenAlprWebhookProcessor/Alerts/WebPush/WebPushRequest.cs @@ -0,0 +1,13 @@ +namespace OpenAlprWebhookProcessor.Alerts.WebPush +{ + public class WebPushRequest + { + public bool IsEnabled { get; set; } + + public string PublicKey { get; set; } + + public string PrivateKey { get; set; } + + public string EmailAddress { get; set; } + } +} diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/app.component.html b/OpenAlprWebhookProcessor/ClientApp/src/app/app.component.html deleted file mode 100644 index b28b636b..00000000 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/app.component.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/app.component.ts b/OpenAlprWebhookProcessor/ClientApp/src/app/app.component.ts deleted file mode 100644 index e021db72..00000000 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/app.component.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; - -import { AccountService } from './_services'; -import { User } from './_models'; -import { SignalrService } from './signalr/signalr.service'; - -@Component({ - selector: 'app', - templateUrl: 'app.component.html', - styleUrls: ['app.component.css'] -}) -export class AppComponent implements OnInit, OnDestroy { - user: User; - appPlatesVisible: boolean; - appSettingsVisible: boolean; - - constructor( - private signalRService: SignalrService, - private accountService: AccountService) { - this.accountService.user.subscribe(x => this.user = x); - } - - public ngOnInit() { - this.signalRService.startConnection(); - } - - public ngOnDestroy() { - this.signalRService.stopConnection(); - } - - public logout() { - this.accountService.logout(); - } -} \ No newline at end of file diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/home/home.component.html b/OpenAlprWebhookProcessor/ClientApp/src/app/home/home.component.html deleted file mode 100644 index 584c6e65..00000000 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/home/home.component.html +++ /dev/null @@ -1,27 +0,0 @@ -
-
- - - Plates Seen - Last 7 days - - -
- -
-
-
-
-
\ No newline at end of file diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/home/plateCountResponse.ts b/OpenAlprWebhookProcessor/ClientApp/src/app/home/plateCountResponse.ts deleted file mode 100644 index 715f9c29..00000000 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/home/plateCountResponse.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface DayCounts { - counts: DayCount[]; -} - -export interface DayCount { - date: Date; - count: number; -} \ No newline at end of file diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/snackbar/snackbar.component.html b/OpenAlprWebhookProcessor/ClientApp/src/app/snackbar/snackbar.component.html deleted file mode 100644 index 3bb93e6d..00000000 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/snackbar/snackbar.component.html +++ /dev/null @@ -1,8 +0,0 @@ -
-
- {{getIcon}} -
-
- {{data.message}} -
-
\ No newline at end of file diff --git a/OpenAlprWebhookProcessor/ClientApp/src/environments/environment.ts b/OpenAlprWebhookProcessor/ClientApp/src/environments/environment.ts deleted file mode 100644 index 7b4f817a..00000000 --- a/OpenAlprWebhookProcessor/ClientApp/src/environments/environment.ts +++ /dev/null @@ -1,16 +0,0 @@ -// This file can be replaced during build by using the `fileReplacements` array. -// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. -// The list of file replacements can be found in `angular.json`. - -export const environment = { - production: false -}; - -/* - * For easier debugging in development mode, you can import the following file - * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. - * - * This import should be commented out in production mode because it will have a negative impact - * on performance if an error is thrown. - */ -// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/OpenAlprWebhookProcessor/ClientApp/src/favicon.ico b/OpenAlprWebhookProcessor/ClientApp/src/favicon.ico deleted file mode 100644 index 997406ad..00000000 Binary files a/OpenAlprWebhookProcessor/ClientApp/src/favicon.ico and /dev/null differ diff --git a/OpenAlprWebhookProcessor/ClientApp/src/polyfills.ts b/OpenAlprWebhookProcessor/ClientApp/src/polyfills.ts deleted file mode 100644 index 03711e5d..00000000 --- a/OpenAlprWebhookProcessor/ClientApp/src/polyfills.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * This file includes polyfills needed by Angular and is loaded before the app. - * You can add your own extra polyfills to this file. - * - * This file is divided into 2 sections: - * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. - * 2. Application imports. Files imported after ZoneJS that should be loaded before your main - * file. - * - * The current setup is for so-called "evergreen" browsers; the last versions of browsers that - * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), - * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. - * - * Learn more in https://angular.io/guide/browser-support - */ - -/*************************************************************************************************** - * BROWSER POLYFILLS - */ - -/** IE10 and IE11 requires the following for NgClass support on SVG elements */ -// import 'classlist.js'; // Run `npm install --save classlist.js`. - -/** - * Web Animations `@angular/platform-browser/animations` - * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. - * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). - */ -// import 'web-animations-js'; // Run `npm install --save web-animations-js`. - -/** - * By default, zone.js will patch all possible macroTask and DomEvents - * user can disable parts of macroTask/DomEvents patch by setting following flags - * because those flags need to be set before `zone.js` being loaded, and webpack - * will put import in the top of bundle, so user need to create a separate file - * in this directory (for example: zone-flags.ts), and put the following flags - * into that file, and then add the following code before importing zone.js. - * import './zone-flags'; - * - * The flags allowed in zone-flags.ts are listed here. - * - * The following flags will work for all browsers. - * - * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame - * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick - * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames - * - * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js - * with the following flag, it will bypass `zone.js` patch for IE/Edge - * - * (window as any).__Zone_enable_cross_context_check = true; - * - */ - -/*************************************************************************************************** - * Zone JS is required by default for Angular itself. - */ -import 'zone.js/dist/zone'; // Included with Angular CLI. - - -/*************************************************************************************************** - * APPLICATION IMPORTS - */ diff --git a/OpenAlprWebhookProcessor/Data/Agent.cs b/OpenAlprWebhookProcessor/Data/Agent.cs index 75e1014f..130709b2 100644 --- a/OpenAlprWebhookProcessor/Data/Agent.cs +++ b/OpenAlprWebhookProcessor/Data/Agent.cs @@ -31,5 +31,7 @@ public class Agent public long LastSuccessfulScrapeEpoch { get; set; } public bool IsDebugEnabled { get; set; } + + public bool IsImageCompressionEnabled { get; set; } } } diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231030144402_possiblenumberindex.Designer.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231030144402_possiblenumberindex.Designer.cs new file mode 100644 index 00000000..2749b846 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231030144402_possiblenumberindex.Designer.cs @@ -0,0 +1,416 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OpenAlprWebhookProcessor.Data; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + [DbContext(typeof(ProcessorContext))] + [Migration("20231030144402_possiblenumberindex")] + partial class possiblenumberindex + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.24"); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Agent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("EndpointUrl") + .HasColumnType("TEXT"); + + b.Property("Hostname") + .HasColumnType("TEXT"); + + b.Property("IsDebugEnabled") + .HasColumnType("INTEGER"); + + b.Property("LastSuccessfulScrapeEpoch") + .HasColumnType("INTEGER"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("OpenAlprWebServerApiKey") + .HasColumnType("TEXT"); + + b.Property("OpenAlprWebServerUrl") + .HasColumnType("TEXT"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimeZoneOffset") + .HasColumnType("REAL"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Agents"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Alert", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Alerts"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Camera", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CameraPassword") + .HasColumnType("TEXT"); + + b.Property("CameraUsername") + .HasColumnType("TEXT"); + + b.Property("DayFocus") + .HasColumnType("TEXT"); + + b.Property("DayZoom") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasColumnType("TEXT"); + + b.Property("LatestProcessedPlateUuid") + .HasColumnType("TEXT"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("Manufacturer") + .HasColumnType("INTEGER"); + + b.Property("ModelNumber") + .HasColumnType("TEXT"); + + b.Property("NextClearOverlayScheduleId") + .HasColumnType("TEXT"); + + b.Property("NextDayNightScheduleId") + .HasColumnType("TEXT"); + + b.Property("NightFocus") + .HasColumnType("TEXT"); + + b.Property("NightZoom") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprEnabled") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprName") + .HasColumnType("TEXT"); + + b.Property("PlatesSeen") + .HasColumnType("INTEGER"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimezoneOffset") + .HasColumnType("REAL"); + + b.Property("UpdateDayNightModeEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateDayNightModeUrl") + .HasColumnType("TEXT"); + + b.Property("UpdateOverlayEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateOverlayTextUrl") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Cameras"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Enricher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("EnricherType") + .HasColumnType("INTEGER"); + + b.Property("EnrichmentType") + .HasColumnType("INTEGER"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Enrichers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Ignore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Ignores"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AgentImageScrapeOccurredOn") + .HasColumnType("REAL"); + + b.Property("AlertDescription") + .HasColumnType("TEXT"); + + b.Property("BestNumber") + .HasColumnType("TEXT"); + + b.Property("Confidence") + .HasColumnType("REAL"); + + b.Property("Direction") + .HasColumnType("REAL"); + + b.Property("IsAlert") + .HasColumnType("INTEGER"); + + b.Property("IsEnriched") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprProcessingTimeMs") + .HasColumnType("REAL"); + + b.Property("OpenAlprUuid") + .HasColumnType("TEXT"); + + b.Property("PlateCoordinates") + .HasColumnType("TEXT"); + + b.Property("PlateJpeg") + .HasColumnType("BLOB"); + + b.Property("PlatePreviewJpeg") + .HasColumnType("TEXT"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("VehicleColor") + .HasColumnType("TEXT"); + + b.Property("VehicleJpeg") + .HasColumnType("BLOB"); + + b.Property("VehicleMake") + .HasColumnType("TEXT"); + + b.Property("VehicleMakeModel") + .HasColumnType("TEXT"); + + b.Property("VehiclePreviewJpeg") + .HasColumnType("TEXT"); + + b.Property("VehicleRegion") + .HasColumnType("TEXT"); + + b.Property("VehicleType") + .HasColumnType("TEXT"); + + b.Property("VehicleYear") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OpenAlprUuid"); + + b.HasIndex("ReceivedOnEpoch"); + + b.HasIndex("BestNumber", "ReceivedOnEpoch"); + + b.ToTable("PlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlateGroupId", "Number"); + + b.ToTable("PlateGroupPossibleNumbers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupRaw", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.Property("RawPlateGroup") + .HasColumnType("TEXT"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("WasProcessedCorrectly") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RawPlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Pushover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiToken") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("SendPlatePreview") + .HasColumnType("INTEGER"); + + b.Property("UserKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PushoverAlertClients"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebhookForward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ForwardGroupPreviews") + .HasColumnType("INTEGER"); + + b.Property("ForwardGroups") + .HasColumnType("INTEGER"); + + b.Property("ForwardSinglePlates") + .HasColumnType("INTEGER"); + + b.Property("FowardingDestination") + .HasColumnType("TEXT"); + + b.Property("IgnoreSslErrors") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("WebhookForwards"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithMany("PossibleNumbers") + .HasForeignKey("PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Navigation("PossibleNumbers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231030144402_possiblenumberindex.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231030144402_possiblenumberindex.cs new file mode 100644 index 00000000..f8656f7d --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231030144402_possiblenumberindex.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + public partial class possiblenumberindex : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_PlateGroupPossibleNumbers_PlateGroupId", + table: "PlateGroupPossibleNumbers"); + + migrationBuilder.CreateIndex( + name: "IX_PlateGroupPossibleNumbers_PlateGroupId_Number", + table: "PlateGroupPossibleNumbers", + columns: new[] { "PlateGroupId", "Number" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_PlateGroupPossibleNumbers_PlateGroupId_Number", + table: "PlateGroupPossibleNumbers"); + + migrationBuilder.CreateIndex( + name: "IX_PlateGroupPossibleNumbers_PlateGroupId", + table: "PlateGroupPossibleNumbers", + column: "PlateGroupId"); + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231101133429_removePreviewJpegs.Designer.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231101133429_removePreviewJpegs.Designer.cs new file mode 100644 index 00000000..675b66a2 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231101133429_removePreviewJpegs.Designer.cs @@ -0,0 +1,410 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OpenAlprWebhookProcessor.Data; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + [DbContext(typeof(ProcessorContext))] + [Migration("20231101133429_removePreviewJpegs")] + partial class removePreviewJpegs + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.12"); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Agent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("EndpointUrl") + .HasColumnType("TEXT"); + + b.Property("Hostname") + .HasColumnType("TEXT"); + + b.Property("IsDebugEnabled") + .HasColumnType("INTEGER"); + + b.Property("LastSuccessfulScrapeEpoch") + .HasColumnType("INTEGER"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("OpenAlprWebServerApiKey") + .HasColumnType("TEXT"); + + b.Property("OpenAlprWebServerUrl") + .HasColumnType("TEXT"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimeZoneOffset") + .HasColumnType("REAL"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Agents"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Alert", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Alerts"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Camera", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CameraPassword") + .HasColumnType("TEXT"); + + b.Property("CameraUsername") + .HasColumnType("TEXT"); + + b.Property("DayFocus") + .HasColumnType("TEXT"); + + b.Property("DayZoom") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasColumnType("TEXT"); + + b.Property("LatestProcessedPlateUuid") + .HasColumnType("TEXT"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("Manufacturer") + .HasColumnType("INTEGER"); + + b.Property("ModelNumber") + .HasColumnType("TEXT"); + + b.Property("NextClearOverlayScheduleId") + .HasColumnType("TEXT"); + + b.Property("NextDayNightScheduleId") + .HasColumnType("TEXT"); + + b.Property("NightFocus") + .HasColumnType("TEXT"); + + b.Property("NightZoom") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprEnabled") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprName") + .HasColumnType("TEXT"); + + b.Property("PlatesSeen") + .HasColumnType("INTEGER"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimezoneOffset") + .HasColumnType("REAL"); + + b.Property("UpdateDayNightModeEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateDayNightModeUrl") + .HasColumnType("TEXT"); + + b.Property("UpdateOverlayEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateOverlayTextUrl") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Cameras"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Enricher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("EnricherType") + .HasColumnType("INTEGER"); + + b.Property("EnrichmentType") + .HasColumnType("INTEGER"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Enrichers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Ignore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Ignores"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AgentImageScrapeOccurredOn") + .HasColumnType("REAL"); + + b.Property("AlertDescription") + .HasColumnType("TEXT"); + + b.Property("BestNumber") + .HasColumnType("TEXT"); + + b.Property("Confidence") + .HasColumnType("REAL"); + + b.Property("Direction") + .HasColumnType("REAL"); + + b.Property("IsAlert") + .HasColumnType("INTEGER"); + + b.Property("IsEnriched") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprProcessingTimeMs") + .HasColumnType("REAL"); + + b.Property("OpenAlprUuid") + .HasColumnType("TEXT"); + + b.Property("PlateCoordinates") + .HasColumnType("TEXT"); + + b.Property("PlateJpeg") + .HasColumnType("BLOB"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("VehicleColor") + .HasColumnType("TEXT"); + + b.Property("VehicleJpeg") + .HasColumnType("BLOB"); + + b.Property("VehicleMake") + .HasColumnType("TEXT"); + + b.Property("VehicleMakeModel") + .HasColumnType("TEXT"); + + b.Property("VehicleRegion") + .HasColumnType("TEXT"); + + b.Property("VehicleType") + .HasColumnType("TEXT"); + + b.Property("VehicleYear") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OpenAlprUuid"); + + b.HasIndex("ReceivedOnEpoch"); + + b.HasIndex("BestNumber", "ReceivedOnEpoch"); + + b.ToTable("PlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlateGroupId", "Number"); + + b.ToTable("PlateGroupPossibleNumbers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupRaw", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.Property("RawPlateGroup") + .HasColumnType("TEXT"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("WasProcessedCorrectly") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RawPlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Pushover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiToken") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("SendPlatePreview") + .HasColumnType("INTEGER"); + + b.Property("UserKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PushoverAlertClients"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebhookForward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ForwardGroupPreviews") + .HasColumnType("INTEGER"); + + b.Property("ForwardGroups") + .HasColumnType("INTEGER"); + + b.Property("ForwardSinglePlates") + .HasColumnType("INTEGER"); + + b.Property("FowardingDestination") + .HasColumnType("TEXT"); + + b.Property("IgnoreSslErrors") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("WebhookForwards"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithMany("PossibleNumbers") + .HasForeignKey("PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Navigation("PossibleNumbers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231101133429_removePreviewJpegs.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231101133429_removePreviewJpegs.cs new file mode 100644 index 00000000..166013cb --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231101133429_removePreviewJpegs.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + public partial class removePreviewJpegs : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "PlatePreviewJpeg", + table: "PlateGroups"); + + migrationBuilder.DropColumn( + name: "VehiclePreviewJpeg", + table: "PlateGroups"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "PlatePreviewJpeg", + table: "PlateGroups", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "VehiclePreviewJpeg", + table: "PlateGroups", + type: "TEXT", + nullable: true); + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231101210623_imagecompression.Designer.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231101210623_imagecompression.Designer.cs new file mode 100644 index 00000000..ac624df9 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231101210623_imagecompression.Designer.cs @@ -0,0 +1,419 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OpenAlprWebhookProcessor.Data; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + [DbContext(typeof(ProcessorContext))] + [Migration("20231101210623_imagecompression")] + partial class imagecompression + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.12"); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Agent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("EndpointUrl") + .HasColumnType("TEXT"); + + b.Property("Hostname") + .HasColumnType("TEXT"); + + b.Property("IsDebugEnabled") + .HasColumnType("INTEGER"); + + b.Property("IsImageCompressionEnabled") + .HasColumnType("INTEGER"); + + b.Property("LastSuccessfulScrapeEpoch") + .HasColumnType("INTEGER"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("OpenAlprWebServerApiKey") + .HasColumnType("TEXT"); + + b.Property("OpenAlprWebServerUrl") + .HasColumnType("TEXT"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimeZoneOffset") + .HasColumnType("REAL"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Agents"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Alert", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Alerts"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Camera", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CameraPassword") + .HasColumnType("TEXT"); + + b.Property("CameraUsername") + .HasColumnType("TEXT"); + + b.Property("DayFocus") + .HasColumnType("TEXT"); + + b.Property("DayZoom") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasColumnType("TEXT"); + + b.Property("LatestProcessedPlateUuid") + .HasColumnType("TEXT"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("Manufacturer") + .HasColumnType("INTEGER"); + + b.Property("ModelNumber") + .HasColumnType("TEXT"); + + b.Property("NextClearOverlayScheduleId") + .HasColumnType("TEXT"); + + b.Property("NextDayNightScheduleId") + .HasColumnType("TEXT"); + + b.Property("NightFocus") + .HasColumnType("TEXT"); + + b.Property("NightZoom") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprEnabled") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprName") + .HasColumnType("TEXT"); + + b.Property("PlatesSeen") + .HasColumnType("INTEGER"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimezoneOffset") + .HasColumnType("REAL"); + + b.Property("UpdateDayNightModeEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateDayNightModeUrl") + .HasColumnType("TEXT"); + + b.Property("UpdateOverlayEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateOverlayTextUrl") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Cameras"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Enricher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("EnricherType") + .HasColumnType("INTEGER"); + + b.Property("EnrichmentType") + .HasColumnType("INTEGER"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Enrichers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Ignore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Ignores"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AgentImageScrapeOccurredOn") + .HasColumnType("REAL"); + + b.Property("AlertDescription") + .HasColumnType("TEXT"); + + b.Property("BestNumber") + .HasColumnType("TEXT"); + + b.Property("Confidence") + .HasColumnType("REAL"); + + b.Property("Direction") + .HasColumnType("REAL"); + + b.Property("IsAlert") + .HasColumnType("INTEGER"); + + b.Property("IsEnriched") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprProcessingTimeMs") + .HasColumnType("REAL"); + + b.Property("OpenAlprUuid") + .HasColumnType("TEXT"); + + b.Property("PlateCoordinates") + .HasColumnType("TEXT"); + + b.Property("PlateJpeg") + .HasColumnType("BLOB"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("VehicleColor") + .HasColumnType("TEXT"); + + b.Property("VehicleJpeg") + .HasColumnType("BLOB"); + + b.Property("VehicleMake") + .HasColumnType("TEXT"); + + b.Property("VehicleMakeModel") + .HasColumnType("TEXT"); + + b.Property("VehicleRegion") + .HasColumnType("TEXT"); + + b.Property("VehicleType") + .HasColumnType("TEXT"); + + b.Property("VehicleYear") + .HasColumnType("TEXT"); + + b.Property("isPlateJpegCompressed") + .HasColumnType("INTEGER"); + + b.Property("isVehicleJpegCompressed") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OpenAlprUuid"); + + b.HasIndex("ReceivedOnEpoch"); + + b.HasIndex("BestNumber", "ReceivedOnEpoch"); + + b.ToTable("PlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlateGroupId", "Number"); + + b.ToTable("PlateGroupPossibleNumbers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupRaw", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.Property("RawPlateGroup") + .HasColumnType("TEXT"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("WasProcessedCorrectly") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RawPlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Pushover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiToken") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("SendPlatePreview") + .HasColumnType("INTEGER"); + + b.Property("UserKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PushoverAlertClients"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebhookForward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ForwardGroupPreviews") + .HasColumnType("INTEGER"); + + b.Property("ForwardGroups") + .HasColumnType("INTEGER"); + + b.Property("ForwardSinglePlates") + .HasColumnType("INTEGER"); + + b.Property("FowardingDestination") + .HasColumnType("TEXT"); + + b.Property("IgnoreSslErrors") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("WebhookForwards"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithMany("PossibleNumbers") + .HasForeignKey("PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Navigation("PossibleNumbers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231101210623_imagecompression.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231101210623_imagecompression.cs new file mode 100644 index 00000000..cebe2e99 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231101210623_imagecompression.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + public partial class imagecompression : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "isPlateJpegCompressed", + table: "PlateGroups", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "isVehicleJpegCompressed", + table: "PlateGroups", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "IsImageCompressionEnabled", + table: "Agents", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "isPlateJpegCompressed", + table: "PlateGroups"); + + migrationBuilder.DropColumn( + name: "isVehicleJpegCompressed", + table: "PlateGroups"); + + migrationBuilder.DropColumn( + name: "IsImageCompressionEnabled", + table: "Agents"); + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231102123548_filterindexes.Designer.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231102123548_filterindexes.Designer.cs new file mode 100644 index 00000000..a62bfa01 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231102123548_filterindexes.Designer.cs @@ -0,0 +1,431 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OpenAlprWebhookProcessor.Data; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + [DbContext(typeof(ProcessorContext))] + [Migration("20231102123548_filterindexes")] + partial class filterindexes + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.12"); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Agent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("EndpointUrl") + .HasColumnType("TEXT"); + + b.Property("Hostname") + .HasColumnType("TEXT"); + + b.Property("IsDebugEnabled") + .HasColumnType("INTEGER"); + + b.Property("IsImageCompressionEnabled") + .HasColumnType("INTEGER"); + + b.Property("LastSuccessfulScrapeEpoch") + .HasColumnType("INTEGER"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("OpenAlprWebServerApiKey") + .HasColumnType("TEXT"); + + b.Property("OpenAlprWebServerUrl") + .HasColumnType("TEXT"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimeZoneOffset") + .HasColumnType("REAL"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Agents"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Alert", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Alerts"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Camera", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CameraPassword") + .HasColumnType("TEXT"); + + b.Property("CameraUsername") + .HasColumnType("TEXT"); + + b.Property("DayFocus") + .HasColumnType("TEXT"); + + b.Property("DayZoom") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasColumnType("TEXT"); + + b.Property("LatestProcessedPlateUuid") + .HasColumnType("TEXT"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("Manufacturer") + .HasColumnType("INTEGER"); + + b.Property("ModelNumber") + .HasColumnType("TEXT"); + + b.Property("NextClearOverlayScheduleId") + .HasColumnType("TEXT"); + + b.Property("NextDayNightScheduleId") + .HasColumnType("TEXT"); + + b.Property("NightFocus") + .HasColumnType("TEXT"); + + b.Property("NightZoom") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprEnabled") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprName") + .HasColumnType("TEXT"); + + b.Property("PlatesSeen") + .HasColumnType("INTEGER"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimezoneOffset") + .HasColumnType("REAL"); + + b.Property("UpdateDayNightModeEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateDayNightModeUrl") + .HasColumnType("TEXT"); + + b.Property("UpdateOverlayEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateOverlayTextUrl") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Cameras"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Enricher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("EnricherType") + .HasColumnType("INTEGER"); + + b.Property("EnrichmentType") + .HasColumnType("INTEGER"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Enrichers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Ignore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Ignores"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AgentImageScrapeOccurredOn") + .HasColumnType("REAL"); + + b.Property("AlertDescription") + .HasColumnType("TEXT"); + + b.Property("BestNumber") + .HasColumnType("TEXT"); + + b.Property("Confidence") + .HasColumnType("REAL"); + + b.Property("Direction") + .HasColumnType("REAL"); + + b.Property("IsAlert") + .HasColumnType("INTEGER"); + + b.Property("IsEnriched") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprProcessingTimeMs") + .HasColumnType("REAL"); + + b.Property("OpenAlprUuid") + .HasColumnType("TEXT"); + + b.Property("PlateCoordinates") + .HasColumnType("TEXT"); + + b.Property("PlateJpeg") + .HasColumnType("BLOB"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("VehicleColor") + .HasColumnType("TEXT"); + + b.Property("VehicleJpeg") + .HasColumnType("BLOB"); + + b.Property("VehicleMake") + .HasColumnType("TEXT"); + + b.Property("VehicleMakeModel") + .HasColumnType("TEXT"); + + b.Property("VehicleRegion") + .HasColumnType("TEXT"); + + b.Property("VehicleType") + .HasColumnType("TEXT"); + + b.Property("VehicleYear") + .HasColumnType("TEXT"); + + b.Property("isPlateJpegCompressed") + .HasColumnType("INTEGER"); + + b.Property("isVehicleJpegCompressed") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OpenAlprUuid"); + + b.HasIndex("ReceivedOnEpoch"); + + b.HasIndex("VehicleColor"); + + b.HasIndex("VehicleMake"); + + b.HasIndex("VehicleMakeModel"); + + b.HasIndex("VehicleRegion"); + + b.HasIndex("VehicleType"); + + b.HasIndex("VehicleYear"); + + b.HasIndex("BestNumber", "ReceivedOnEpoch"); + + b.ToTable("PlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlateGroupId", "Number"); + + b.ToTable("PlateGroupPossibleNumbers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupRaw", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.Property("RawPlateGroup") + .HasColumnType("TEXT"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("WasProcessedCorrectly") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RawPlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Pushover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiToken") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("SendPlatePreview") + .HasColumnType("INTEGER"); + + b.Property("UserKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PushoverAlertClients"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebhookForward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ForwardGroupPreviews") + .HasColumnType("INTEGER"); + + b.Property("ForwardGroups") + .HasColumnType("INTEGER"); + + b.Property("ForwardSinglePlates") + .HasColumnType("INTEGER"); + + b.Property("FowardingDestination") + .HasColumnType("TEXT"); + + b.Property("IgnoreSslErrors") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("WebhookForwards"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithMany("PossibleNumbers") + .HasForeignKey("PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Navigation("PossibleNumbers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231102123548_filterindexes.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231102123548_filterindexes.cs new file mode 100644 index 00000000..a309b7c5 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231102123548_filterindexes.cs @@ -0,0 +1,69 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + public partial class filterindexes : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_PlateGroups_VehicleColor", + table: "PlateGroups", + column: "VehicleColor"); + + migrationBuilder.CreateIndex( + name: "IX_PlateGroups_VehicleMake", + table: "PlateGroups", + column: "VehicleMake"); + + migrationBuilder.CreateIndex( + name: "IX_PlateGroups_VehicleMakeModel", + table: "PlateGroups", + column: "VehicleMakeModel"); + + migrationBuilder.CreateIndex( + name: "IX_PlateGroups_VehicleRegion", + table: "PlateGroups", + column: "VehicleRegion"); + + migrationBuilder.CreateIndex( + name: "IX_PlateGroups_VehicleType", + table: "PlateGroups", + column: "VehicleType"); + + migrationBuilder.CreateIndex( + name: "IX_PlateGroups_VehicleYear", + table: "PlateGroups", + column: "VehicleYear"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_PlateGroups_VehicleColor", + table: "PlateGroups"); + + migrationBuilder.DropIndex( + name: "IX_PlateGroups_VehicleMake", + table: "PlateGroups"); + + migrationBuilder.DropIndex( + name: "IX_PlateGroups_VehicleMakeModel", + table: "PlateGroups"); + + migrationBuilder.DropIndex( + name: "IX_PlateGroups_VehicleRegion", + table: "PlateGroups"); + + migrationBuilder.DropIndex( + name: "IX_PlateGroups_VehicleType", + table: "PlateGroups"); + + migrationBuilder.DropIndex( + name: "IX_PlateGroups_VehicleYear", + table: "PlateGroups"); + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231103154356_possibleplatefk.Designer.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231103154356_possibleplatefk.Designer.cs new file mode 100644 index 00000000..68cc4ca5 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231103154356_possibleplatefk.Designer.cs @@ -0,0 +1,434 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OpenAlprWebhookProcessor.Data; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + [DbContext(typeof(ProcessorContext))] + [Migration("20231103154356_possibleplatefk")] + partial class possibleplatefk + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.13"); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Agent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("EndpointUrl") + .HasColumnType("TEXT"); + + b.Property("Hostname") + .HasColumnType("TEXT"); + + b.Property("IsDebugEnabled") + .HasColumnType("INTEGER"); + + b.Property("IsImageCompressionEnabled") + .HasColumnType("INTEGER"); + + b.Property("LastSuccessfulScrapeEpoch") + .HasColumnType("INTEGER"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("OpenAlprWebServerApiKey") + .HasColumnType("TEXT"); + + b.Property("OpenAlprWebServerUrl") + .HasColumnType("TEXT"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimeZoneOffset") + .HasColumnType("REAL"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Agents"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Alert", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Alerts"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Camera", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CameraPassword") + .HasColumnType("TEXT"); + + b.Property("CameraUsername") + .HasColumnType("TEXT"); + + b.Property("DayFocus") + .HasColumnType("TEXT"); + + b.Property("DayZoom") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasColumnType("TEXT"); + + b.Property("LatestProcessedPlateUuid") + .HasColumnType("TEXT"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("Manufacturer") + .HasColumnType("INTEGER"); + + b.Property("ModelNumber") + .HasColumnType("TEXT"); + + b.Property("NextClearOverlayScheduleId") + .HasColumnType("TEXT"); + + b.Property("NextDayNightScheduleId") + .HasColumnType("TEXT"); + + b.Property("NightFocus") + .HasColumnType("TEXT"); + + b.Property("NightZoom") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprEnabled") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprName") + .HasColumnType("TEXT"); + + b.Property("PlatesSeen") + .HasColumnType("INTEGER"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimezoneOffset") + .HasColumnType("REAL"); + + b.Property("UpdateDayNightModeEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateDayNightModeUrl") + .HasColumnType("TEXT"); + + b.Property("UpdateOverlayEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateOverlayTextUrl") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Cameras"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Enricher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("EnricherType") + .HasColumnType("INTEGER"); + + b.Property("EnrichmentType") + .HasColumnType("INTEGER"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Enrichers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Ignore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Ignores"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AgentImageScrapeOccurredOn") + .HasColumnType("REAL"); + + b.Property("AlertDescription") + .HasColumnType("TEXT"); + + b.Property("BestNumber") + .HasColumnType("TEXT"); + + b.Property("Confidence") + .HasColumnType("REAL"); + + b.Property("Direction") + .HasColumnType("REAL"); + + b.Property("IsAlert") + .HasColumnType("INTEGER"); + + b.Property("IsEnriched") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprProcessingTimeMs") + .HasColumnType("REAL"); + + b.Property("OpenAlprUuid") + .HasColumnType("TEXT"); + + b.Property("PlateCoordinates") + .HasColumnType("TEXT"); + + b.Property("PlateJpeg") + .HasColumnType("BLOB"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("VehicleColor") + .HasColumnType("TEXT"); + + b.Property("VehicleJpeg") + .HasColumnType("BLOB"); + + b.Property("VehicleMake") + .HasColumnType("TEXT"); + + b.Property("VehicleMakeModel") + .HasColumnType("TEXT"); + + b.Property("VehicleRegion") + .HasColumnType("TEXT"); + + b.Property("VehicleType") + .HasColumnType("TEXT"); + + b.Property("VehicleYear") + .HasColumnType("TEXT"); + + b.Property("isPlateJpegCompressed") + .HasColumnType("INTEGER"); + + b.Property("isVehicleJpegCompressed") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("OpenAlprUuid"); + + b.HasIndex("ReceivedOnEpoch"); + + b.HasIndex("VehicleColor"); + + b.HasIndex("VehicleMake"); + + b.HasIndex("VehicleMakeModel"); + + b.HasIndex("VehicleRegion"); + + b.HasIndex("VehicleType"); + + b.HasIndex("VehicleYear"); + + b.HasIndex("BestNumber", "ReceivedOnEpoch"); + + b.ToTable("PlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Number"); + + b.HasIndex("PlateGroupId", "Number"); + + b.ToTable("PlateGroupPossibleNumbers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupRaw", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.Property("RawPlateGroup") + .HasColumnType("TEXT"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("WasProcessedCorrectly") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RawPlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Pushover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiToken") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("SendPlatePreview") + .HasColumnType("INTEGER"); + + b.Property("UserKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PushoverAlertClients"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebhookForward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ForwardGroupPreviews") + .HasColumnType("INTEGER"); + + b.Property("ForwardGroups") + .HasColumnType("INTEGER"); + + b.Property("ForwardSinglePlates") + .HasColumnType("INTEGER"); + + b.Property("FowardingDestination") + .HasColumnType("TEXT"); + + b.Property("IgnoreSslErrors") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("WebhookForwards"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithMany("PossibleNumbers") + .HasForeignKey("PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Navigation("PossibleNumbers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231103154356_possibleplatefk.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231103154356_possibleplatefk.cs new file mode 100644 index 00000000..8eb95ab9 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231103154356_possibleplatefk.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + /// + public partial class possibleplatefk : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_PlateGroupPossibleNumbers_Number", + table: "PlateGroupPossibleNumbers", + column: "Number"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_PlateGroupPossibleNumbers_Number", + table: "PlateGroupPossibleNumbers"); + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231104132311_splitImages.Designer.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231104132311_splitImages.Designer.cs new file mode 100644 index 00000000..f4ed6458 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231104132311_splitImages.Designer.cs @@ -0,0 +1,496 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OpenAlprWebhookProcessor.Data; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + [DbContext(typeof(ProcessorContext))] + [Migration("20231104132311_splitImages")] + partial class splitImages + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.13"); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Agent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("EndpointUrl") + .HasColumnType("TEXT"); + + b.Property("Hostname") + .HasColumnType("TEXT"); + + b.Property("IsDebugEnabled") + .HasColumnType("INTEGER"); + + b.Property("IsImageCompressionEnabled") + .HasColumnType("INTEGER"); + + b.Property("LastSuccessfulScrapeEpoch") + .HasColumnType("INTEGER"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("OpenAlprWebServerApiKey") + .HasColumnType("TEXT"); + + b.Property("OpenAlprWebServerUrl") + .HasColumnType("TEXT"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimeZoneOffset") + .HasColumnType("REAL"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Agents"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Alert", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Alerts"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Camera", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CameraPassword") + .HasColumnType("TEXT"); + + b.Property("CameraUsername") + .HasColumnType("TEXT"); + + b.Property("DayFocus") + .HasColumnType("TEXT"); + + b.Property("DayZoom") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasColumnType("TEXT"); + + b.Property("LatestProcessedPlateUuid") + .HasColumnType("TEXT"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("Manufacturer") + .HasColumnType("INTEGER"); + + b.Property("ModelNumber") + .HasColumnType("TEXT"); + + b.Property("NextClearOverlayScheduleId") + .HasColumnType("TEXT"); + + b.Property("NextDayNightScheduleId") + .HasColumnType("TEXT"); + + b.Property("NightFocus") + .HasColumnType("TEXT"); + + b.Property("NightZoom") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprEnabled") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprName") + .HasColumnType("TEXT"); + + b.Property("PlatesSeen") + .HasColumnType("INTEGER"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimezoneOffset") + .HasColumnType("REAL"); + + b.Property("UpdateDayNightModeEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateDayNightModeUrl") + .HasColumnType("TEXT"); + + b.Property("UpdateOverlayEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateOverlayTextUrl") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Cameras"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Enricher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("EnricherType") + .HasColumnType("INTEGER"); + + b.Property("EnrichmentType") + .HasColumnType("INTEGER"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Enrichers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Ignore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Ignores"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AgentImageScrapeOccurredOn") + .HasColumnType("REAL"); + + b.Property("AlertDescription") + .HasColumnType("TEXT"); + + b.Property("BestNumber") + .HasColumnType("TEXT"); + + b.Property("Confidence") + .HasColumnType("REAL"); + + b.Property("Direction") + .HasColumnType("REAL"); + + b.Property("IsAlert") + .HasColumnType("INTEGER"); + + b.Property("IsEnriched") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprProcessingTimeMs") + .HasColumnType("REAL"); + + b.Property("OpenAlprUuid") + .HasColumnType("TEXT"); + + b.Property("PlateCoordinates") + .HasColumnType("TEXT"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("VehicleColor") + .HasColumnType("TEXT"); + + b.Property("VehicleMake") + .HasColumnType("TEXT"); + + b.Property("VehicleMakeModel") + .HasColumnType("TEXT"); + + b.Property("VehicleRegion") + .HasColumnType("TEXT"); + + b.Property("VehicleType") + .HasColumnType("TEXT"); + + b.Property("VehicleYear") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OpenAlprUuid"); + + b.HasIndex("ReceivedOnEpoch"); + + b.HasIndex("VehicleColor"); + + b.HasIndex("VehicleMake"); + + b.HasIndex("VehicleMakeModel"); + + b.HasIndex("VehicleRegion"); + + b.HasIndex("VehicleType"); + + b.HasIndex("VehicleYear"); + + b.HasIndex("BestNumber", "ReceivedOnEpoch"); + + b.ToTable("PlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Number"); + + b.HasIndex("PlateGroupId", "Number"); + + b.ToTable("PlateGroupPossibleNumbers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupRaw", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.Property("RawPlateGroup") + .HasColumnType("TEXT"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("WasProcessedCorrectly") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RawPlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsCompressed") + .HasColumnType("INTEGER"); + + b.Property("Jpeg") + .HasColumnType("BLOB"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.HasIndex("PlateGroupId") + .IsUnique(); + + b.ToTable("PlateImage"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Pushover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiToken") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("SendPlatePreview") + .HasColumnType("INTEGER"); + + b.Property("UserKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PushoverAlertClients"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.VehicleImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsCompressed") + .HasColumnType("INTEGER"); + + b.Property("Jpeg") + .HasColumnType("BLOB"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlateGroupId") + .IsUnique(); + + b.ToTable("VehicleImage"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebhookForward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ForwardGroupPreviews") + .HasColumnType("INTEGER"); + + b.Property("ForwardGroups") + .HasColumnType("INTEGER"); + + b.Property("ForwardSinglePlates") + .HasColumnType("INTEGER"); + + b.Property("FowardingDestination") + .HasColumnType("TEXT"); + + b.Property("IgnoreSslErrors") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("WebhookForwards"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithMany("PossibleNumbers") + .HasForeignKey("PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateImage", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithOne("PlateImage") + .HasForeignKey("OpenAlprWebhookProcessor.Data.PlateImage", "PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.VehicleImage", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithOne("VehicleImage") + .HasForeignKey("OpenAlprWebhookProcessor.Data.VehicleImage", "PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Navigation("PlateImage"); + + b.Navigation("PossibleNumbers"); + + b.Navigation("VehicleImage"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231104132311_splitImages.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231104132311_splitImages.cs new file mode 100644 index 00000000..e8fd0f52 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231104132311_splitImages.cs @@ -0,0 +1,153 @@ +using System; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + /// + public partial class splitImages : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PlateImage", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + PlateGroupId = table.Column(type: "TEXT", nullable: false), + Jpeg = table.Column(type: "BLOB", nullable: true), + IsCompressed = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PlateImage", x => x.Id); + table.ForeignKey( + name: "FK_PlateImage_PlateGroups_PlateGroupId", + column: x => x.PlateGroupId, + principalTable: "PlateGroups", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "VehicleImage", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + PlateGroupId = table.Column(type: "TEXT", nullable: false), + Jpeg = table.Column(type: "BLOB", nullable: true), + IsCompressed = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_VehicleImage", x => x.Id); + table.ForeignKey( + name: "FK_VehicleImage_PlateGroups_PlateGroupId", + column: x => x.PlateGroupId, + principalTable: "PlateGroups", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_PlateImage_Id", + table: "PlateImage", + column: "Id"); + + migrationBuilder.CreateIndex( + name: "IX_PlateImage_PlateGroupId", + table: "PlateImage", + column: "PlateGroupId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_VehicleImage_PlateGroupId", + table: "VehicleImage", + column: "PlateGroupId", + unique: true); + + + + migrationBuilder.Sql( + @"INSERT INTO PlateImage + (Id, PlateGroupId, Jpeg, IsCompressed) + SELECT lower( + hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || '4' || + substr(hex( randomblob(2)), 2) || '-' || + substr('AB89', 1 + (abs(random()) % 4) , 1) || + substr(hex(randomblob(2)), 2) || '-' || + hex(randomblob(6))) + , Id, PlateJpeg, isPlateJpegCompressed + FROM PlateGroups + WHERE PlateJpeg IS NOT NULL;"); + + migrationBuilder.Sql( + @"INSERT INTO VehicleImage + (Id, PlateGroupId, Jpeg, IsCompressed) + SELECT lower( + hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-' || '4' || + substr(hex( randomblob(2)), 2) || '-' || + substr('AB89', 1 + (abs(random()) % 4) , 1) || + substr(hex(randomblob(2)), 2) || '-' || + hex(randomblob(6))) + , Id, VehicleJpeg, isVehicleJpegCompressed + FROM PlateGroups + WHERE VehicleJpeg IS NOT NULL;"); + + migrationBuilder.DropColumn( + name: "PlateJpeg", + table: "PlateGroups"); + + migrationBuilder.DropColumn( + name: "VehicleJpeg", + table: "PlateGroups"); + + migrationBuilder.DropColumn( + name: "isPlateJpegCompressed", + table: "PlateGroups"); + + migrationBuilder.DropColumn( + name: "isVehicleJpegCompressed", + table: "PlateGroups"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PlateImage"); + + migrationBuilder.DropTable( + name: "VehicleImage"); + + migrationBuilder.AddColumn( + name: "PlateJpeg", + table: "PlateGroups", + type: "BLOB", + nullable: true); + + migrationBuilder.AddColumn( + name: "VehicleJpeg", + table: "PlateGroups", + type: "BLOB", + nullable: true); + + migrationBuilder.AddColumn( + name: "isPlateJpegCompressed", + table: "PlateGroups", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "isVehicleJpegCompressed", + table: "PlateGroups", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231105153059_pushnotification.Designer.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231105153059_pushnotification.Designer.cs new file mode 100644 index 00000000..8459e341 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231105153059_pushnotification.Designer.cs @@ -0,0 +1,548 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OpenAlprWebhookProcessor.Data; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + [DbContext(typeof(ProcessorContext))] + [Migration("20231105153059_pushnotification")] + partial class pushnotification + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.13"); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Agent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("EndpointUrl") + .HasColumnType("TEXT"); + + b.Property("Hostname") + .HasColumnType("TEXT"); + + b.Property("IsDebugEnabled") + .HasColumnType("INTEGER"); + + b.Property("IsImageCompressionEnabled") + .HasColumnType("INTEGER"); + + b.Property("LastSuccessfulScrapeEpoch") + .HasColumnType("INTEGER"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("OpenAlprWebServerApiKey") + .HasColumnType("TEXT"); + + b.Property("OpenAlprWebServerUrl") + .HasColumnType("TEXT"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimeZoneOffset") + .HasColumnType("REAL"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Agents"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Alert", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Alerts"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Camera", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CameraPassword") + .HasColumnType("TEXT"); + + b.Property("CameraUsername") + .HasColumnType("TEXT"); + + b.Property("DayFocus") + .HasColumnType("TEXT"); + + b.Property("DayZoom") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasColumnType("TEXT"); + + b.Property("LatestProcessedPlateUuid") + .HasColumnType("TEXT"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("Manufacturer") + .HasColumnType("INTEGER"); + + b.Property("ModelNumber") + .HasColumnType("TEXT"); + + b.Property("NextClearOverlayScheduleId") + .HasColumnType("TEXT"); + + b.Property("NextDayNightScheduleId") + .HasColumnType("TEXT"); + + b.Property("NightFocus") + .HasColumnType("TEXT"); + + b.Property("NightZoom") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprEnabled") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprName") + .HasColumnType("TEXT"); + + b.Property("PlatesSeen") + .HasColumnType("INTEGER"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimezoneOffset") + .HasColumnType("REAL"); + + b.Property("UpdateDayNightModeEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateDayNightModeUrl") + .HasColumnType("TEXT"); + + b.Property("UpdateOverlayEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateOverlayTextUrl") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Cameras"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Enricher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("EnricherType") + .HasColumnType("INTEGER"); + + b.Property("EnrichmentType") + .HasColumnType("INTEGER"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Enrichers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Ignore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Ignores"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.MobilePushSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PushSubscriptions"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.MobilePushSubscriptionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MobilePushSubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MobilePushSubscriptionId"); + + b.ToTable("PushSubscriptionKeys"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AgentImageScrapeOccurredOn") + .HasColumnType("REAL"); + + b.Property("AlertDescription") + .HasColumnType("TEXT"); + + b.Property("BestNumber") + .HasColumnType("TEXT"); + + b.Property("Confidence") + .HasColumnType("REAL"); + + b.Property("Direction") + .HasColumnType("REAL"); + + b.Property("IsAlert") + .HasColumnType("INTEGER"); + + b.Property("IsEnriched") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprProcessingTimeMs") + .HasColumnType("REAL"); + + b.Property("OpenAlprUuid") + .HasColumnType("TEXT"); + + b.Property("PlateCoordinates") + .HasColumnType("TEXT"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("VehicleColor") + .HasColumnType("TEXT"); + + b.Property("VehicleMake") + .HasColumnType("TEXT"); + + b.Property("VehicleMakeModel") + .HasColumnType("TEXT"); + + b.Property("VehicleRegion") + .HasColumnType("TEXT"); + + b.Property("VehicleType") + .HasColumnType("TEXT"); + + b.Property("VehicleYear") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OpenAlprUuid"); + + b.HasIndex("ReceivedOnEpoch"); + + b.HasIndex("VehicleColor"); + + b.HasIndex("VehicleMake"); + + b.HasIndex("VehicleMakeModel"); + + b.HasIndex("VehicleRegion"); + + b.HasIndex("VehicleType"); + + b.HasIndex("VehicleYear"); + + b.HasIndex("BestNumber", "ReceivedOnEpoch"); + + b.ToTable("PlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Number"); + + b.HasIndex("PlateGroupId", "Number"); + + b.ToTable("PlateGroupPossibleNumbers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupRaw", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.Property("RawPlateGroup") + .HasColumnType("TEXT"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("WasProcessedCorrectly") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RawPlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsCompressed") + .HasColumnType("INTEGER"); + + b.Property("Jpeg") + .HasColumnType("BLOB"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.HasIndex("PlateGroupId") + .IsUnique(); + + b.ToTable("PlateImage"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Pushover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiToken") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("SendPlatePreview") + .HasColumnType("INTEGER"); + + b.Property("UserKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PushoverAlertClients"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.VehicleImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsCompressed") + .HasColumnType("INTEGER"); + + b.Property("Jpeg") + .HasColumnType("BLOB"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlateGroupId") + .IsUnique(); + + b.ToTable("VehicleImage"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebhookForward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ForwardGroupPreviews") + .HasColumnType("INTEGER"); + + b.Property("ForwardGroups") + .HasColumnType("INTEGER"); + + b.Property("ForwardSinglePlates") + .HasColumnType("INTEGER"); + + b.Property("FowardingDestination") + .HasColumnType("TEXT"); + + b.Property("IgnoreSslErrors") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("WebhookForwards"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.MobilePushSubscriptionKey", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.MobilePushSubscription", "MobilePushSubscription") + .WithMany("Keys") + .HasForeignKey("MobilePushSubscriptionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MobilePushSubscription"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithMany("PossibleNumbers") + .HasForeignKey("PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateImage", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithOne("PlateImage") + .HasForeignKey("OpenAlprWebhookProcessor.Data.PlateImage", "PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.VehicleImage", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithOne("VehicleImage") + .HasForeignKey("OpenAlprWebhookProcessor.Data.VehicleImage", "PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.MobilePushSubscription", b => + { + b.Navigation("Keys"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Navigation("PlateImage"); + + b.Navigation("PossibleNumbers"); + + b.Navigation("VehicleImage"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231105153059_pushnotification.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231105153059_pushnotification.cs new file mode 100644 index 00000000..ebad5c6d --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231105153059_pushnotification.cs @@ -0,0 +1,62 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + /// + public partial class pushnotification : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PushSubscriptions", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Endpoint = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PushSubscriptions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PushSubscriptionKeys", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + MobilePushSubscriptionId = table.Column(type: "TEXT", nullable: false), + Key = table.Column(type: "TEXT", nullable: true), + Value = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PushSubscriptionKeys", x => x.Id); + table.ForeignKey( + name: "FK_PushSubscriptionKeys_PushSubscriptions_MobilePushSubscriptionId", + column: x => x.MobilePushSubscriptionId, + principalTable: "PushSubscriptions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_PushSubscriptionKeys_MobilePushSubscriptionId", + table: "PushSubscriptionKeys", + column: "MobilePushSubscriptionId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PushSubscriptionKeys"); + + migrationBuilder.DropTable( + name: "PushSubscriptions"); + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231105221859_webpush.Designer.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231105221859_webpush.Designer.cs new file mode 100644 index 00000000..153860c9 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231105221859_webpush.Designer.cs @@ -0,0 +1,568 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OpenAlprWebhookProcessor.Data; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + [DbContext(typeof(ProcessorContext))] + [Migration("20231105221859_webpush")] + partial class webpush + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.13"); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Agent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("EndpointUrl") + .HasColumnType("TEXT"); + + b.Property("Hostname") + .HasColumnType("TEXT"); + + b.Property("IsDebugEnabled") + .HasColumnType("INTEGER"); + + b.Property("IsImageCompressionEnabled") + .HasColumnType("INTEGER"); + + b.Property("LastSuccessfulScrapeEpoch") + .HasColumnType("INTEGER"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("OpenAlprWebServerApiKey") + .HasColumnType("TEXT"); + + b.Property("OpenAlprWebServerUrl") + .HasColumnType("TEXT"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimeZoneOffset") + .HasColumnType("REAL"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Agents"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Alert", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Alerts"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Camera", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CameraPassword") + .HasColumnType("TEXT"); + + b.Property("CameraUsername") + .HasColumnType("TEXT"); + + b.Property("DayFocus") + .HasColumnType("TEXT"); + + b.Property("DayZoom") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasColumnType("TEXT"); + + b.Property("LatestProcessedPlateUuid") + .HasColumnType("TEXT"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("Manufacturer") + .HasColumnType("INTEGER"); + + b.Property("ModelNumber") + .HasColumnType("TEXT"); + + b.Property("NextClearOverlayScheduleId") + .HasColumnType("TEXT"); + + b.Property("NextDayNightScheduleId") + .HasColumnType("TEXT"); + + b.Property("NightFocus") + .HasColumnType("TEXT"); + + b.Property("NightZoom") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprEnabled") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprName") + .HasColumnType("TEXT"); + + b.Property("PlatesSeen") + .HasColumnType("INTEGER"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimezoneOffset") + .HasColumnType("REAL"); + + b.Property("UpdateDayNightModeEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateDayNightModeUrl") + .HasColumnType("TEXT"); + + b.Property("UpdateOverlayEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateOverlayTextUrl") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Cameras"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Enricher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("EnricherType") + .HasColumnType("INTEGER"); + + b.Property("EnrichmentType") + .HasColumnType("INTEGER"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Enrichers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Ignore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Ignores"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AgentImageScrapeOccurredOn") + .HasColumnType("REAL"); + + b.Property("AlertDescription") + .HasColumnType("TEXT"); + + b.Property("BestNumber") + .HasColumnType("TEXT"); + + b.Property("Confidence") + .HasColumnType("REAL"); + + b.Property("Direction") + .HasColumnType("REAL"); + + b.Property("IsAlert") + .HasColumnType("INTEGER"); + + b.Property("IsEnriched") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprProcessingTimeMs") + .HasColumnType("REAL"); + + b.Property("OpenAlprUuid") + .HasColumnType("TEXT"); + + b.Property("PlateCoordinates") + .HasColumnType("TEXT"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("VehicleColor") + .HasColumnType("TEXT"); + + b.Property("VehicleMake") + .HasColumnType("TEXT"); + + b.Property("VehicleMakeModel") + .HasColumnType("TEXT"); + + b.Property("VehicleRegion") + .HasColumnType("TEXT"); + + b.Property("VehicleType") + .HasColumnType("TEXT"); + + b.Property("VehicleYear") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OpenAlprUuid"); + + b.HasIndex("ReceivedOnEpoch"); + + b.HasIndex("VehicleColor"); + + b.HasIndex("VehicleMake"); + + b.HasIndex("VehicleMakeModel"); + + b.HasIndex("VehicleRegion"); + + b.HasIndex("VehicleType"); + + b.HasIndex("VehicleYear"); + + b.HasIndex("BestNumber", "ReceivedOnEpoch"); + + b.ToTable("PlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Number"); + + b.HasIndex("PlateGroupId", "Number"); + + b.ToTable("PlateGroupPossibleNumbers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupRaw", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.Property("RawPlateGroup") + .HasColumnType("TEXT"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("WasProcessedCorrectly") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RawPlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsCompressed") + .HasColumnType("INTEGER"); + + b.Property("Jpeg") + .HasColumnType("BLOB"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.HasIndex("PlateGroupId") + .IsUnique(); + + b.ToTable("PlateImage"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Pushover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiToken") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("SendPlatePreview") + .HasColumnType("INTEGER"); + + b.Property("UserKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PushoverAlertClients"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.VehicleImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsCompressed") + .HasColumnType("INTEGER"); + + b.Property("Jpeg") + .HasColumnType("BLOB"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlateGroupId") + .IsUnique(); + + b.ToTable("VehicleImage"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebPushSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("WebPushSettings"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebPushSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("WebPushSubscriptions"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebPushSubscriptionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MobilePushSubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MobilePushSubscriptionId"); + + b.ToTable("WebPushSubscriptionKeys"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebhookForward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ForwardGroupPreviews") + .HasColumnType("INTEGER"); + + b.Property("ForwardGroups") + .HasColumnType("INTEGER"); + + b.Property("ForwardSinglePlates") + .HasColumnType("INTEGER"); + + b.Property("FowardingDestination") + .HasColumnType("TEXT"); + + b.Property("IgnoreSslErrors") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("WebhookForwards"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithMany("PossibleNumbers") + .HasForeignKey("PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateImage", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithOne("PlateImage") + .HasForeignKey("OpenAlprWebhookProcessor.Data.PlateImage", "PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.VehicleImage", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithOne("VehicleImage") + .HasForeignKey("OpenAlprWebhookProcessor.Data.VehicleImage", "PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebPushSubscriptionKey", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.WebPushSubscription", "MobilePushSubscription") + .WithMany("Keys") + .HasForeignKey("MobilePushSubscriptionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MobilePushSubscription"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Navigation("PlateImage"); + + b.Navigation("PossibleNumbers"); + + b.Navigation("VehicleImage"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebPushSubscription", b => + { + b.Navigation("Keys"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231105221859_webpush.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231105221859_webpush.cs new file mode 100644 index 00000000..ab8001f2 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231105221859_webpush.cs @@ -0,0 +1,122 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + /// + public partial class webpush : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PushSubscriptionKeys"); + + migrationBuilder.DropTable( + name: "PushSubscriptions"); + + migrationBuilder.CreateTable( + name: "WebPushSettings", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Subject = table.Column(type: "TEXT", nullable: true), + PublicKey = table.Column(type: "TEXT", nullable: true), + PrivateKey = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_WebPushSettings", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "WebPushSubscriptions", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Endpoint = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_WebPushSubscriptions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "WebPushSubscriptionKeys", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + MobilePushSubscriptionId = table.Column(type: "TEXT", nullable: false), + Key = table.Column(type: "TEXT", nullable: true), + Value = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_WebPushSubscriptionKeys", x => x.Id); + table.ForeignKey( + name: "FK_WebPushSubscriptionKeys_WebPushSubscriptions_MobilePushSubscriptionId", + column: x => x.MobilePushSubscriptionId, + principalTable: "WebPushSubscriptions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_WebPushSubscriptionKeys_MobilePushSubscriptionId", + table: "WebPushSubscriptionKeys", + column: "MobilePushSubscriptionId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "WebPushSettings"); + + migrationBuilder.DropTable( + name: "WebPushSubscriptionKeys"); + + migrationBuilder.DropTable( + name: "WebPushSubscriptions"); + + migrationBuilder.CreateTable( + name: "PushSubscriptions", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Endpoint = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PushSubscriptions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PushSubscriptionKeys", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + MobilePushSubscriptionId = table.Column(type: "TEXT", nullable: false), + Key = table.Column(type: "TEXT", nullable: true), + Value = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PushSubscriptionKeys", x => x.Id); + table.ForeignKey( + name: "FK_PushSubscriptionKeys_PushSubscriptions_MobilePushSubscriptionId", + column: x => x.MobilePushSubscriptionId, + principalTable: "PushSubscriptions", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_PushSubscriptionKeys_MobilePushSubscriptionId", + table: "PushSubscriptionKeys", + column: "MobilePushSubscriptionId"); + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231106012346_isenabled.Designer.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231106012346_isenabled.Designer.cs new file mode 100644 index 00000000..f28a26ac --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231106012346_isenabled.Designer.cs @@ -0,0 +1,571 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OpenAlprWebhookProcessor.Data; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + [DbContext(typeof(ProcessorContext))] + [Migration("20231106012346_isenabled")] + partial class isenabled + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.13"); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Agent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("EndpointUrl") + .HasColumnType("TEXT"); + + b.Property("Hostname") + .HasColumnType("TEXT"); + + b.Property("IsDebugEnabled") + .HasColumnType("INTEGER"); + + b.Property("IsImageCompressionEnabled") + .HasColumnType("INTEGER"); + + b.Property("LastSuccessfulScrapeEpoch") + .HasColumnType("INTEGER"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("OpenAlprWebServerApiKey") + .HasColumnType("TEXT"); + + b.Property("OpenAlprWebServerUrl") + .HasColumnType("TEXT"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimeZoneOffset") + .HasColumnType("REAL"); + + b.Property("Uid") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Agents"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Alert", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Alerts"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Camera", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CameraPassword") + .HasColumnType("TEXT"); + + b.Property("CameraUsername") + .HasColumnType("TEXT"); + + b.Property("DayFocus") + .HasColumnType("TEXT"); + + b.Property("DayZoom") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasColumnType("TEXT"); + + b.Property("LatestProcessedPlateUuid") + .HasColumnType("TEXT"); + + b.Property("Latitude") + .HasColumnType("REAL"); + + b.Property("Longitude") + .HasColumnType("REAL"); + + b.Property("Manufacturer") + .HasColumnType("INTEGER"); + + b.Property("ModelNumber") + .HasColumnType("TEXT"); + + b.Property("NextClearOverlayScheduleId") + .HasColumnType("TEXT"); + + b.Property("NextDayNightScheduleId") + .HasColumnType("TEXT"); + + b.Property("NightFocus") + .HasColumnType("TEXT"); + + b.Property("NightZoom") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprEnabled") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprName") + .HasColumnType("TEXT"); + + b.Property("PlatesSeen") + .HasColumnType("INTEGER"); + + b.Property("SunriseOffset") + .HasColumnType("INTEGER"); + + b.Property("SunsetOffset") + .HasColumnType("INTEGER"); + + b.Property("TimezoneOffset") + .HasColumnType("REAL"); + + b.Property("UpdateDayNightModeEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateDayNightModeUrl") + .HasColumnType("TEXT"); + + b.Property("UpdateOverlayEnabled") + .HasColumnType("INTEGER"); + + b.Property("UpdateOverlayTextUrl") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Cameras"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Enricher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("EnricherType") + .HasColumnType("INTEGER"); + + b.Property("EnrichmentType") + .HasColumnType("INTEGER"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Enrichers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Ignore", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("IsStrictMatch") + .HasColumnType("INTEGER"); + + b.Property("PlateNumber") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Ignores"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AgentImageScrapeOccurredOn") + .HasColumnType("REAL"); + + b.Property("AlertDescription") + .HasColumnType("TEXT"); + + b.Property("BestNumber") + .HasColumnType("TEXT"); + + b.Property("Confidence") + .HasColumnType("REAL"); + + b.Property("Direction") + .HasColumnType("REAL"); + + b.Property("IsAlert") + .HasColumnType("INTEGER"); + + b.Property("IsEnriched") + .HasColumnType("INTEGER"); + + b.Property("Notes") + .HasColumnType("TEXT"); + + b.Property("OpenAlprCameraId") + .HasColumnType("INTEGER"); + + b.Property("OpenAlprProcessingTimeMs") + .HasColumnType("REAL"); + + b.Property("OpenAlprUuid") + .HasColumnType("TEXT"); + + b.Property("PlateCoordinates") + .HasColumnType("TEXT"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("VehicleColor") + .HasColumnType("TEXT"); + + b.Property("VehicleMake") + .HasColumnType("TEXT"); + + b.Property("VehicleMakeModel") + .HasColumnType("TEXT"); + + b.Property("VehicleRegion") + .HasColumnType("TEXT"); + + b.Property("VehicleType") + .HasColumnType("TEXT"); + + b.Property("VehicleYear") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("OpenAlprUuid"); + + b.HasIndex("ReceivedOnEpoch"); + + b.HasIndex("VehicleColor"); + + b.HasIndex("VehicleMake"); + + b.HasIndex("VehicleMakeModel"); + + b.HasIndex("VehicleRegion"); + + b.HasIndex("VehicleType"); + + b.HasIndex("VehicleYear"); + + b.HasIndex("BestNumber", "ReceivedOnEpoch"); + + b.ToTable("PlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Number"); + + b.HasIndex("PlateGroupId", "Number"); + + b.ToTable("PlateGroupPossibleNumbers"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupRaw", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.Property("RawPlateGroup") + .HasColumnType("TEXT"); + + b.Property("ReceivedOnEpoch") + .HasColumnType("INTEGER"); + + b.Property("WasProcessedCorrectly") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("RawPlateGroups"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsCompressed") + .HasColumnType("INTEGER"); + + b.Property("Jpeg") + .HasColumnType("BLOB"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.HasIndex("PlateGroupId") + .IsUnique(); + + b.ToTable("PlateImage"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Pushover", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ApiToken") + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("SendPlatePreview") + .HasColumnType("INTEGER"); + + b.Property("UserKey") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("PushoverAlertClients"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.VehicleImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsCompressed") + .HasColumnType("INTEGER"); + + b.Property("Jpeg") + .HasColumnType("BLOB"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlateGroupId") + .IsUnique(); + + b.ToTable("VehicleImage"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebPushSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("WebPushSettings"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebPushSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("WebPushSubscriptions"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebPushSubscriptionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MobilePushSubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MobilePushSubscriptionId"); + + b.ToTable("WebPushSubscriptionKeys"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebhookForward", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ForwardGroupPreviews") + .HasColumnType("INTEGER"); + + b.Property("ForwardGroups") + .HasColumnType("INTEGER"); + + b.Property("ForwardSinglePlates") + .HasColumnType("INTEGER"); + + b.Property("FowardingDestination") + .HasColumnType("TEXT"); + + b.Property("IgnoreSslErrors") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("WebhookForwards"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroupPossibleNumbers", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithMany("PossibleNumbers") + .HasForeignKey("PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateImage", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithOne("PlateImage") + .HasForeignKey("OpenAlprWebhookProcessor.Data.PlateImage", "PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.VehicleImage", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithOne("VehicleImage") + .HasForeignKey("OpenAlprWebhookProcessor.Data.VehicleImage", "PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebPushSubscriptionKey", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.WebPushSubscription", "MobilePushSubscription") + .WithMany("Keys") + .HasForeignKey("MobilePushSubscriptionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MobilePushSubscription"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => + { + b.Navigation("PlateImage"); + + b.Navigation("PossibleNumbers"); + + b.Navigation("VehicleImage"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebPushSubscription", b => + { + b.Navigation("Keys"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/20231106012346_isenabled.cs b/OpenAlprWebhookProcessor/Data/Migrations/20231106012346_isenabled.cs new file mode 100644 index 00000000..8b0b0840 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/Migrations/20231106012346_isenabled.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace OpenAlprWebhookProcessor.Migrations +{ + /// + public partial class isenabled : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IsEnabled", + table: "WebPushSettings", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IsEnabled", + table: "WebPushSettings"); + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/Migrations/ProcessorContextModelSnapshot.cs b/OpenAlprWebhookProcessor/Data/Migrations/ProcessorContextModelSnapshot.cs index 23eac3ae..fea49da5 100644 --- a/OpenAlprWebhookProcessor/Data/Migrations/ProcessorContextModelSnapshot.cs +++ b/OpenAlprWebhookProcessor/Data/Migrations/ProcessorContextModelSnapshot.cs @@ -15,7 +15,7 @@ partial class ProcessorContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.2"); + modelBuilder.HasAnnotation("ProductVersion", "7.0.13"); modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Agent", b => { @@ -32,6 +32,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("IsDebugEnabled") .HasColumnType("INTEGER"); + b.Property("IsImageCompressionEnabled") + .HasColumnType("INTEGER"); + b.Property("LastSuccessfulScrapeEpoch") .HasColumnType("INTEGER"); @@ -258,30 +261,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("PlateCoordinates") .HasColumnType("TEXT"); - b.Property("PlateJpeg") - .HasColumnType("BLOB"); - - b.Property("PlatePreviewJpeg") - .HasColumnType("TEXT"); - b.Property("ReceivedOnEpoch") .HasColumnType("INTEGER"); b.Property("VehicleColor") .HasColumnType("TEXT"); - b.Property("VehicleJpeg") - .HasColumnType("BLOB"); - b.Property("VehicleMake") .HasColumnType("TEXT"); b.Property("VehicleMakeModel") .HasColumnType("TEXT"); - b.Property("VehiclePreviewJpeg") - .HasColumnType("TEXT"); - b.Property("VehicleRegion") .HasColumnType("TEXT"); @@ -297,6 +288,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("ReceivedOnEpoch"); + b.HasIndex("VehicleColor"); + + b.HasIndex("VehicleMake"); + + b.HasIndex("VehicleMakeModel"); + + b.HasIndex("VehicleRegion"); + + b.HasIndex("VehicleType"); + + b.HasIndex("VehicleYear"); + b.HasIndex("BestNumber", "ReceivedOnEpoch"); b.ToTable("PlateGroups"); @@ -316,7 +319,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("PlateGroupId"); + b.HasIndex("Number"); + + b.HasIndex("PlateGroupId", "Number"); b.ToTable("PlateGroupPossibleNumbers"); }); @@ -344,6 +349,31 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("RawPlateGroups"); }); + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsCompressed") + .HasColumnType("INTEGER"); + + b.Property("Jpeg") + .HasColumnType("BLOB"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id"); + + b.HasIndex("PlateGroupId") + .IsUnique(); + + b.ToTable("PlateImage"); + }); + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.Pushover", b => { b.Property("Id") @@ -367,6 +397,88 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("PushoverAlertClients"); }); + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.VehicleImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsCompressed") + .HasColumnType("INTEGER"); + + b.Property("Jpeg") + .HasColumnType("BLOB"); + + b.Property("PlateGroupId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("PlateGroupId") + .IsUnique(); + + b.ToTable("VehicleImage"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebPushSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("PrivateKey") + .HasColumnType("TEXT"); + + b.Property("PublicKey") + .HasColumnType("TEXT"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("WebPushSettings"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebPushSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Endpoint") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("WebPushSubscriptions"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebPushSubscriptionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("MobilePushSubscriptionId") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MobilePushSubscriptionId"); + + b.ToTable("WebPushSubscriptionKeys"); + }); + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebhookForward", b => { b.Property("Id") @@ -404,9 +516,51 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("PlateGroup"); }); + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateImage", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithOne("PlateImage") + .HasForeignKey("OpenAlprWebhookProcessor.Data.PlateImage", "PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.VehicleImage", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.PlateGroup", "PlateGroup") + .WithOne("VehicleImage") + .HasForeignKey("OpenAlprWebhookProcessor.Data.VehicleImage", "PlateGroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("PlateGroup"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebPushSubscriptionKey", b => + { + b.HasOne("OpenAlprWebhookProcessor.Data.WebPushSubscription", "MobilePushSubscription") + .WithMany("Keys") + .HasForeignKey("MobilePushSubscriptionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MobilePushSubscription"); + }); + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.PlateGroup", b => { + b.Navigation("PlateImage"); + b.Navigation("PossibleNumbers"); + + b.Navigation("VehicleImage"); + }); + + modelBuilder.Entity("OpenAlprWebhookProcessor.Data.WebPushSubscription", b => + { + b.Navigation("Keys"); }); #pragma warning restore 612, 618 } diff --git a/OpenAlprWebhookProcessor/Data/Webhook.cs b/OpenAlprWebhookProcessor/Data/PlateGroup.cs similarity index 71% rename from OpenAlprWebhookProcessor/Data/Webhook.cs rename to OpenAlprWebhookProcessor/Data/PlateGroup.cs index 04d59451..67489c06 100644 --- a/OpenAlprWebhookProcessor/Data/Webhook.cs +++ b/OpenAlprWebhookProcessor/Data/PlateGroup.cs @@ -24,13 +24,9 @@ public class PlateGroup public List PossibleNumbers { get; set; } - public byte[] PlateJpeg { get; set; } + public PlateImage PlateImage { get; set; } - public string PlatePreviewJpeg { get; set; } - - public byte[] VehicleJpeg { get; set; } - - public string VehiclePreviewJpeg { get; set; } + public VehicleImage VehicleImage { get; set; } public double? AgentImageScrapeOccurredOn { get; set; } @@ -56,6 +52,7 @@ public class PlateGroup public bool IsEnriched { get; set; } + public static void OnModelCreating(ModelBuilder builder) { builder.Entity() @@ -66,6 +63,24 @@ public static void OnModelCreating(ModelBuilder builder) builder.Entity() .HasIndex(x => x.OpenAlprUuid); + + builder.Entity() + .HasIndex(x => x.VehicleMake); + + builder.Entity() + .HasIndex(x => x.VehicleMakeModel); + + builder.Entity() + .HasIndex(x => x.VehicleColor); + + builder.Entity() + .HasIndex(x => x.VehicleType); + + builder.Entity() + .HasIndex(x => x.VehicleYear); + + builder.Entity() + .HasIndex(x => x.VehicleRegion); } } } diff --git a/OpenAlprWebhookProcessor/Data/PlateGroupPossibleNumbers.cs b/OpenAlprWebhookProcessor/Data/PlateGroupPossibleNumbers.cs index 1b5f3d47..47d32923 100644 --- a/OpenAlprWebhookProcessor/Data/PlateGroupPossibleNumbers.cs +++ b/OpenAlprWebhookProcessor/Data/PlateGroupPossibleNumbers.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.EntityFrameworkCore; +using System; namespace OpenAlprWebhookProcessor.Data { @@ -11,5 +12,14 @@ public class PlateGroupPossibleNumbers public PlateGroup PlateGroup { get; set; } public string Number { get; set; } + + public static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasIndex(x => x.Number); + + builder.Entity() + .HasIndex(x => new { x.PlateGroupId, x.Number }); + } } } diff --git a/OpenAlprWebhookProcessor/Data/PlateImage.cs b/OpenAlprWebhookProcessor/Data/PlateImage.cs new file mode 100644 index 00000000..ea9e92f0 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/PlateImage.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using System; + +namespace OpenAlprWebhookProcessor.Data +{ + public class PlateImage + { + public Guid Id { get; set; } + + public Guid PlateGroupId { get; set; } + + public PlateGroup PlateGroup { get; set; } + + public byte[] Jpeg { get; set; } + + public bool IsCompressed { get; set; } + + public static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasIndex(x => x.Id); + + builder.Entity() + .HasIndex(x => x.PlateGroupId); + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/ProcessorContext.cs b/OpenAlprWebhookProcessor/Data/ProcessorContext.cs index eb232a98..6d2ff576 100644 --- a/OpenAlprWebhookProcessor/Data/ProcessorContext.cs +++ b/OpenAlprWebhookProcessor/Data/ProcessorContext.cs @@ -1,5 +1,4 @@ using Microsoft.EntityFrameworkCore; -using System; using System.Reflection; namespace OpenAlprWebhookProcessor.Data @@ -31,6 +30,16 @@ public ProcessorContext(DbContextOptions options) public DbSet Enrichers { get; set; } + public DbSet PlateImages { get; set; } + + public DbSet VehicleImages { get; set; } + + public DbSet WebPushSubscriptions { get; set; } + + public DbSet WebPushSubscriptionKeys { get; set; } + + public DbSet WebPushSettings { get; set; } + protected override void OnModelCreating(ModelBuilder modelBuilder) { foreach (var entityType in modelBuilder.Model.GetEntityTypes()) diff --git a/OpenAlprWebhookProcessor/Data/VehicleImage.cs b/OpenAlprWebhookProcessor/Data/VehicleImage.cs new file mode 100644 index 00000000..4d8a2ed9 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/VehicleImage.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using System; + +namespace OpenAlprWebhookProcessor.Data +{ + public class VehicleImage + { + public Guid Id { get; set; } + + public Guid PlateGroupId { get; set; } + + public PlateGroup PlateGroup { get; set; } + + public byte[] Jpeg { get; set; } + + public bool IsCompressed { get; set; } + + public static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasIndex(x => x.Id); + + builder.Entity() + .HasIndex(x => x.PlateGroupId); + } + } +} diff --git a/OpenAlprWebhookProcessor/Data/WebPushSettings.cs b/OpenAlprWebhookProcessor/Data/WebPushSettings.cs new file mode 100644 index 00000000..23966748 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/WebPushSettings.cs @@ -0,0 +1,17 @@ +using System; + +namespace OpenAlprWebhookProcessor.Data +{ + public class WebPushSettings + { + public Guid Id { get; set; } + + public bool IsEnabled { get; set; } + + public string Subject { get; set; } + + public string PublicKey { get; set; } + + public string PrivateKey { get; set; } + } +} diff --git a/OpenAlprWebhookProcessor/Data/WebPushSubscription.cs b/OpenAlprWebhookProcessor/Data/WebPushSubscription.cs new file mode 100644 index 00000000..bac6457b --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/WebPushSubscription.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace OpenAlprWebhookProcessor.Data +{ + public class WebPushSubscription + { + public Guid Id { get; set; } + + public string Endpoint { get; set; } + + public List Keys { get; set; } + } +} diff --git a/OpenAlprWebhookProcessor/Data/WebPushSubscriptionKey.cs b/OpenAlprWebhookProcessor/Data/WebPushSubscriptionKey.cs new file mode 100644 index 00000000..36d723d7 --- /dev/null +++ b/OpenAlprWebhookProcessor/Data/WebPushSubscriptionKey.cs @@ -0,0 +1,17 @@ +using System; + +namespace OpenAlprWebhookProcessor.Data +{ + public class WebPushSubscriptionKey + { + public Guid Id { get; set; } + + public WebPushSubscription MobilePushSubscription { get; set; } + + public Guid MobilePushSubscriptionId { get; set; } + + public string Key { get; set; } + + public string Value { get; set; } + } +} diff --git a/OpenAlprWebhookProcessor/Dockerfile b/OpenAlprWebhookProcessor/Dockerfile index 3f06823d..13885a58 100644 --- a/OpenAlprWebhookProcessor/Dockerfile +++ b/OpenAlprWebhookProcessor/Dockerfile @@ -1,15 +1,19 @@ #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:6.0.0-rc.2-bullseye-slim-amd64 AS base +FROM mcr.microsoft.com/dotnet/aspnet:7.0-bullseye-slim-amd64 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/sdk:6.0.100-rc.2-bullseye-slim-amd64 AS build +FROM mcr.microsoft.com/dotnet/sdk:7.0-bullseye-slim-amd64 AS build WORKDIR /src -RUN curl -sL https://deb.nodesource.com/setup_15.x | bash - -RUN apt-get install -y nodejs +RUN apt-get update && apt-get install -y ca-certificates curl gnupg \ + && mkdir -p /etc/apt/keyrings \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && NODE_MAJOR=20 \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \ + && apt-get update && apt-get install nodejs -y COPY ["OpenAlprWebhookProcessor/OpenAlprWebhookProcessor.csproj", "OpenAlprWebhookProcessor/"] RUN dotnet restore "OpenAlprWebhookProcessor/OpenAlprWebhookProcessor.csproj" @@ -23,4 +27,6 @@ RUN dotnet publish "OpenAlprWebhookProcessor.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . +ENV SQLITE_TMPDIR=/app/config/ + ENTRYPOINT ["dotnet", "OpenAlprWebhookProcessor.dll"] \ No newline at end of file diff --git a/OpenAlprWebhookProcessor/ImageRelay/GetImage/GetImageHandler.cs b/OpenAlprWebhookProcessor/ImageRelay/GetImage/GetImageHandler.cs index 7679b630..7305c9e1 100644 --- a/OpenAlprWebhookProcessor/ImageRelay/GetImage/GetImageHandler.cs +++ b/OpenAlprWebhookProcessor/ImageRelay/GetImage/GetImageHandler.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore; +using ImageMagick; +using Microsoft.EntityFrameworkCore; using OpenAlprWebhookProcessor.Data; using System; using System.IO; @@ -17,25 +18,35 @@ public async static Task GetImageFromLocalAsync( CancellationToken cancellationToken) { var plateGroup = await processorContext.PlateGroups + .Include(x => x.VehicleImage) .Where(x => x.OpenAlprUuid == imageId) .FirstOrDefaultAsync(cancellationToken); + var isImageCompressionEnabled = await processorContext.Agents + .AsNoTracking() + .Select(x => x.IsImageCompressionEnabled) + .FirstOrDefaultAsync(cancellationToken); + if (plateGroup == null) { throw new ArgumentException("No image found with that id."); } - if (plateGroup.VehicleJpeg == null) + if (plateGroup.VehicleImage == null) { - plateGroup.VehicleJpeg = await GetImageFromAgentAsync( - processorContext, - imageId, - cancellationToken); + plateGroup.VehicleImage = new VehicleImage() + { + Jpeg = await GetImageFromAgentAsync( + processorContext, + imageId, + cancellationToken), + IsCompressed = isImageCompressionEnabled, + }; await processorContext.SaveChangesAsync(cancellationToken); } - return new MemoryStream(plateGroup.VehicleJpeg); + return new MemoryStream(plateGroup.VehicleImage.Jpeg); } public async static Task GetCropImageFromLocalAsync( @@ -44,25 +55,37 @@ public async static Task GetCropImageFromLocalAsync( CancellationToken cancellationToken) { var plateGroup = await processorContext.PlateGroups + .Include(x => x.PlateImage) .Where(x => x.OpenAlprUuid == imageId) .FirstOrDefaultAsync(cancellationToken); + var isImageCompressionEnabled = await processorContext.Agents + .AsNoTracking() + .Select(x => x.IsImageCompressionEnabled) + .FirstOrDefaultAsync(cancellationToken); + if (plateGroup == null) { throw new ArgumentException("No image found with that id."); } - if (plateGroup.PlateJpeg == null) + if (plateGroup.PlateImage == null) { - plateGroup.PlateJpeg = await GetCropImageFromAgentAsync( + var image = await GetCropImageFromAgentAsync( processorContext, imageId + "?" + plateGroup.PlateCoordinates, cancellationToken); + plateGroup.PlateImage = new PlateImage() + { + Jpeg = image, + IsCompressed = isImageCompressionEnabled, + }; + await processorContext.SaveChangesAsync(cancellationToken); } - return new MemoryStream(plateGroup.PlateJpeg); + return new MemoryStream(plateGroup.PlateImage.Jpeg); } public async static Task GetImageFromAgentAsync( @@ -70,7 +93,9 @@ public async static Task GetImageFromAgentAsync( string imageId, CancellationToken cancellationToken) { - var agent = await processorContext.Agents.FirstOrDefaultAsync(cancellationToken); + var agent = await processorContext.Agents + .AsNoTracking() + .FirstOrDefaultAsync(cancellationToken); if (agent == null || string.IsNullOrWhiteSpace(agent.EndpointUrl)) { @@ -91,7 +116,9 @@ public async static Task GetImageFromAgentAsync( throw new ArgumentException("Image not found for that id."); } - return await result.Content.ReadAsByteArrayAsync(cancellationToken); + var imageBytes = await result.Content.ReadAsByteArrayAsync(cancellationToken); + + return agent.IsImageCompressionEnabled ? CompressImage(imageBytes) : imageBytes; } public async static Task GetCropImageFromAgentAsync( @@ -99,7 +126,9 @@ public async static Task GetCropImageFromAgentAsync( string imageId, CancellationToken cancellationToken) { - var agent = await processorContext.Agents.FirstOrDefaultAsync(cancellationToken); + var agent = await processorContext.Agents + .AsNoTracking() + .FirstOrDefaultAsync(cancellationToken); if (agent == null || string.IsNullOrWhiteSpace(agent.EndpointUrl)) { @@ -120,7 +149,28 @@ public async static Task GetCropImageFromAgentAsync( throw new ArgumentException("Image not found for that id."); } - return await result.Content.ReadAsByteArrayAsync(cancellationToken); + var imageBytes = await result.Content.ReadAsByteArrayAsync(cancellationToken); + + return agent.IsImageCompressionEnabled ? CompressImage(imageBytes) : imageBytes; + } + + public static byte[] CompressImage(byte[] rawImage) + { + var optimizer = new ImageOptimizer(); + + try + { + using (var stream = new MemoryStream(rawImage)) + { + optimizer.Compress(stream); + + return stream.ToArray(); + } + } + catch (Exception ex) + { + return null; + } } } } diff --git a/OpenAlprWebhookProcessor/ImageRelay/SnapshotRelay/GetSnapshotHandler.cs b/OpenAlprWebhookProcessor/ImageRelay/SnapshotRelay/GetSnapshotHandler.cs index d31632e5..5757b97c 100644 --- a/OpenAlprWebhookProcessor/ImageRelay/SnapshotRelay/GetSnapshotHandler.cs +++ b/OpenAlprWebhookProcessor/ImageRelay/SnapshotRelay/GetSnapshotHandler.cs @@ -21,9 +21,11 @@ public async Task GetSnapshotAsync( Guid cameraId, CancellationToken cancellationToken) { - var dbCamera = await _processorContext.Cameras.FirstOrDefaultAsync(x => - x.Id == cameraId, - cancellationToken); + var dbCamera = await _processorContext.Cameras + .AsNoTracking() + .FirstOrDefaultAsync(x => + x.Id == cameraId, + cancellationToken); var camera = CameraFactory.Create(dbCamera.Manufacturer, dbCamera); diff --git a/OpenAlprWebhookProcessor/LicensePlates/Enricher/EnrichLicensePlateRequestHandler.cs b/OpenAlprWebhookProcessor/LicensePlates/Enricher/EnrichLicensePlateRequestHandler.cs index 632ddd9b..ef4cc86d 100644 --- a/OpenAlprWebhookProcessor/LicensePlates/Enricher/EnrichLicensePlateRequestHandler.cs +++ b/OpenAlprWebhookProcessor/LicensePlates/Enricher/EnrichLicensePlateRequestHandler.cs @@ -23,6 +23,7 @@ public EnrichLicensePlateRequestHandler( public async Task HandleAsync(Guid plateId) { var plateGroup = await _processorContext.PlateGroups + .AsNoTracking() .Where(x => x.Id == plateId) .FirstOrDefaultAsync(); diff --git a/OpenAlprWebhookProcessor/LicensePlates/GetLicensePlateCounts/GetLicensePlateCountsHandler.cs b/OpenAlprWebhookProcessor/LicensePlates/GetLicensePlateCounts/GetLicensePlateCountsHandler.cs index e4cc6473..19faf84a 100644 --- a/OpenAlprWebhookProcessor/LicensePlates/GetLicensePlateCounts/GetLicensePlateCountsHandler.cs +++ b/OpenAlprWebhookProcessor/LicensePlates/GetLicensePlateCounts/GetLicensePlateCountsHandler.cs @@ -10,11 +10,11 @@ namespace OpenAlprWebhookProcessor.LicensePlates.GetLicensePlateCounts { public class GetLicensePlateCountsHandler { - private readonly ProcessorContext _processerContext; + private readonly ProcessorContext _processorContext; public GetLicensePlateCountsHandler(ProcessorContext processorContext) { - _processerContext = processorContext; + _processorContext = processorContext; } public async Task HandleAsync( @@ -23,12 +23,21 @@ public async Task HandleAsync( { var aWeekAgo = DateTimeOffset.UtcNow.AddDays(-7).ToUnixTimeMilliseconds(); - var results = await _processerContext.PlateGroups + var results = await _processorContext.PlateGroups + .AsNoTracking() .Where(x => x.ReceivedOnEpoch > aWeekAgo) .Select(y => y.ReceivedOnEpoch) .ToListAsync(cancellationToken); - var groupedResults = results.GroupBy(x => DateTimeOffset.FromUnixTimeMilliseconds(x).Date); + return new GetLicensePlateCountsResponse() + { + Counts = GroupByDay(results), + }; + } + + private static List GroupByDay(List plateCounts) + { + var groupedResults = plateCounts.GroupBy(x => DateTimeOffset.FromUnixTimeMilliseconds(x).Date); var parsedResults = new List(); foreach (var date in groupedResults) @@ -40,10 +49,7 @@ public async Task HandleAsync( }); } - return new GetLicensePlateCountsResponse() - { - Counts = parsedResults, - }; + return parsedResults; } } } diff --git a/OpenAlprWebhookProcessor/LicensePlates/GetLicensePlateCounts/GetLicensePlateCountsResponse.cs b/OpenAlprWebhookProcessor/LicensePlates/GetLicensePlateCounts/GetLicensePlateCountsResponse.cs index c1240a0e..ee22e6de 100644 --- a/OpenAlprWebhookProcessor/LicensePlates/GetLicensePlateCounts/GetLicensePlateCountsResponse.cs +++ b/OpenAlprWebhookProcessor/LicensePlates/GetLicensePlateCounts/GetLicensePlateCountsResponse.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace OpenAlprWebhookProcessor.LicensePlates.GetLicensePlateCounts { diff --git a/OpenAlprWebhookProcessor/LicensePlates/GetPlateFilters/GetLicensePlateFiltersHandler.cs b/OpenAlprWebhookProcessor/LicensePlates/GetPlateFilters/GetLicensePlateFiltersHandler.cs index 5b76d578..b84a45c8 100644 --- a/OpenAlprWebhookProcessor/LicensePlates/GetPlateFilters/GetLicensePlateFiltersHandler.cs +++ b/OpenAlprWebhookProcessor/LicensePlates/GetPlateFilters/GetLicensePlateFiltersHandler.cs @@ -1,5 +1,9 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using OpenAlprWebhookProcessor.Data; +using System; using System.Globalization; using System.Linq; using System.Threading; @@ -11,68 +15,73 @@ public class GetLicensePlateFiltersHandler { private static readonly TextInfo _textInfo = CultureInfo.CurrentCulture.TextInfo; + private readonly ILogger _logger; + private readonly ProcessorContext _processerContext; - public GetLicensePlateFiltersHandler(ProcessorContext processerContext) + public GetLicensePlateFiltersHandler( + ILogger logger, + ProcessorContext processerContext) { + _logger = logger; _processerContext = processerContext; } public async Task HandleAsync(CancellationToken cancellationToken) { + _processerContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + var response = new GetLicensePlateFiltersResponse { VehicleMakes = await _processerContext.PlateGroups .Where(x => !string.IsNullOrWhiteSpace(x.VehicleMake)) .Select(x => x.VehicleMake) .Distinct() - .OrderBy(x => x) .ToListAsync(cancellationToken), VehicleModels = await _processerContext.PlateGroups .Where(x => !string.IsNullOrWhiteSpace(x.VehicleMakeModel)) .Select(x => x.VehicleMakeModel) .Distinct() - .OrderBy(x => x) .ToListAsync(cancellationToken), VehicleColors = await _processerContext.PlateGroups .Where(x => !string.IsNullOrWhiteSpace(x.VehicleColor)) .Select(x => x.VehicleColor) .Distinct() - .OrderBy(x => x) .ToListAsync(cancellationToken), VehicleTypes = await _processerContext.PlateGroups .Where(x => !string.IsNullOrWhiteSpace(x.VehicleType)) .Select(x => x.VehicleType) .Distinct() - .OrderBy(x => x) .ToListAsync(cancellationToken), VehicleYears = await _processerContext.PlateGroups .Where(x => !string.IsNullOrWhiteSpace(x.VehicleYear)) .Select(x => x.VehicleYear) .Distinct() - .OrderBy(x => x) .ToListAsync(cancellationToken), VehicleRegions = await _processerContext.PlateGroups .Where(x => !string.IsNullOrWhiteSpace(x.VehicleRegion)) .Select(x => x.VehicleRegion) .Distinct() - .OrderBy(x => x) .ToListAsync(cancellationToken), }; response.VehicleMakes = response.VehicleMakes + .OrderBy(x => x) .Select(x => _textInfo?.ToTitleCase(x.Split('_')[0])) .ToList(); response.VehicleModels = response.VehicleModels + .OrderBy(x => x) .Select(x => _textInfo?.ToTitleCase(x.Split('_')[1])) .ToList(); response.VehicleColors = response.VehicleColors + .OrderBy(x => x) .Select(x => _textInfo?.ToTitleCase(x)) .ToList(); response.VehicleTypes = response.VehicleTypes + .OrderBy(x => x) .Select(x => _textInfo?.ToTitleCase(x)) .ToList(); diff --git a/OpenAlprWebhookProcessor/LicensePlates/GetStatistics/GetStatisticsHandler.cs b/OpenAlprWebhookProcessor/LicensePlates/GetStatistics/GetStatisticsHandler.cs index 2fd4e04d..3139d6ca 100644 --- a/OpenAlprWebhookProcessor/LicensePlates/GetStatistics/GetStatisticsHandler.cs +++ b/OpenAlprWebhookProcessor/LicensePlates/GetStatistics/GetStatisticsHandler.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using OpenAlprWebhookProcessor.Data; +using OpenAlprWebhookProcessor.WebPushSubscriptions; using System; using System.Linq; using System.Threading; @@ -11,28 +12,42 @@ public class GetStatisticsHandler { private readonly ProcessorContext _processorContext; - public GetStatisticsHandler(ProcessorContext processorContext) + private readonly WebPushNotificationProducer _pushNotificationProducer; + + public GetStatisticsHandler(ProcessorContext processorContext, + WebPushNotificationProducer pushNotificationProducer) { _processorContext = processorContext; + _pushNotificationProducer = pushNotificationProducer; } public async Task HandleAsync( string plateNumber, CancellationToken cancellationToken) { - var plateStatistics = new PlateStatistics(); - var endingEpoch = DateTimeOffset.UtcNow.AddDays(-90).ToUnixTimeMilliseconds(); var seenPlates = await _processorContext.PlateGroups - .Where(x => x.BestNumber == plateNumber || x.PossibleNumbers.Any(x => x.Number == plateNumber)) + .AsNoTracking() + .Where(x => x.BestNumber == plateNumber) .Select(x => x.ReceivedOnEpoch) .ToListAsync(cancellationToken); - plateStatistics.TotalSeen = seenPlates.Count; + var seenPossiblePlates = await _processorContext.PlateGroupPossibleNumbers + .AsNoTracking() + .Where(x => x.Number == plateNumber) + .Select(x => x.PlateGroup.ReceivedOnEpoch) + .ToListAsync(cancellationToken); + + seenPlates.AddRange(seenPossiblePlates); + seenPlates = seenPlates.OrderBy(x => x).ToList(); - plateStatistics.Last90Days = seenPlates - .Count(x => x > endingEpoch); + var plateStatistics = new PlateStatistics + { + TotalSeen = seenPlates.Count, + Last90Days = seenPlates + .Count(x => x > endingEpoch) + }; var firstSeenEpoch = seenPlates .FirstOrDefault(); @@ -43,9 +58,7 @@ public async Task HandleAsync( } var lastSeenEpoch = seenPlates - .OrderByDescending(x => x) - .Select(x => x) - .FirstOrDefault(); + .LastOrDefault(); if (lastSeenEpoch != 0) { @@ -55,4 +68,4 @@ public async Task HandleAsync( return plateStatistics; } } -} +} \ No newline at end of file diff --git a/OpenAlprWebhookProcessor/LicensePlates/SearchLicensePlates/SearchLicensePlateHandler.cs b/OpenAlprWebhookProcessor/LicensePlates/SearchLicensePlates/SearchLicensePlateHandler.cs index 9617ed58..82d364f5 100644 --- a/OpenAlprWebhookProcessor/LicensePlates/SearchLicensePlates/SearchLicensePlateHandler.cs +++ b/OpenAlprWebhookProcessor/LicensePlates/SearchLicensePlates/SearchLicensePlateHandler.cs @@ -22,9 +22,10 @@ public async Task HandleAsync( SearchLicensePlateRequest request, CancellationToken cancellationToken) { + _processerContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + var dbRequest = _processerContext.PlateGroups - .AsQueryable() - .AsNoTracking(); + .AsQueryable(); if (!string.IsNullOrWhiteSpace(request.PlateNumber)) { @@ -125,7 +126,8 @@ public async Task HandleAsync( platesToAlert)); } - var enricher = await _processerContext.Enrichers.FirstOrDefaultAsync(cancellationToken); + var enricher = await _processerContext.Enrichers + .FirstOrDefaultAsync(cancellationToken); if (enricher == null || !enricher.IsEnabled) { diff --git a/OpenAlprWebhookProcessor/OpenAlprWebhookProcessor.csproj b/OpenAlprWebhookProcessor/OpenAlprWebhookProcessor.csproj index 8e698653..03a9c801 100644 --- a/OpenAlprWebhookProcessor/OpenAlprWebhookProcessor.csproj +++ b/OpenAlprWebhookProcessor/OpenAlprWebhookProcessor.csproj @@ -1,11 +1,11 @@ - + - net6.0 + net7.0 true Latest false - ClientApp\ + angularapp\ $(DefaultItemExcludes);$(SpaRoot)node_modules\** @@ -17,27 +17,29 @@ - + - - - - - - - - + + + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - - + + - - + @@ -47,6 +49,16 @@ + + + + + + + + + + @@ -64,8 +76,8 @@ - - + + diff --git a/OpenAlprWebhookProcessor/Settings/Agent.cs b/OpenAlprWebhookProcessor/Settings/Agent.cs index fc9db618..e12de68a 100644 --- a/OpenAlprWebhookProcessor/Settings/Agent.cs +++ b/OpenAlprWebhookProcessor/Settings/Agent.cs @@ -25,5 +25,7 @@ public class Agent public double TimezoneOffset { get; set; } public bool IsDebugEnabled { get; set; } + + public bool IsImageCompressionEnabled { get; set; } } } diff --git a/OpenAlprWebhookProcessor/Settings/Enrichers/GetEnrichersRequestHandler.cs b/OpenAlprWebhookProcessor/Settings/Enrichers/GetEnrichersRequestHandler.cs index 6cd9f0e9..0543432a 100644 --- a/OpenAlprWebhookProcessor/Settings/Enrichers/GetEnrichersRequestHandler.cs +++ b/OpenAlprWebhookProcessor/Settings/Enrichers/GetEnrichersRequestHandler.cs @@ -16,7 +16,9 @@ public GetEnrichersRequestHandler(ProcessorContext processorContext) public async Task HandleAsync(CancellationToken cancellationToken) { - var enricher = await _processorContext.Enrichers.FirstOrDefaultAsync(cancellationToken); + var enricher = await _processorContext.Enrichers + .AsNoTracking() + .FirstOrDefaultAsync(cancellationToken); if (enricher == null) { diff --git a/OpenAlprWebhookProcessor/Settings/GetAgent/GetAgentRequestHandler.cs b/OpenAlprWebhookProcessor/Settings/GetAgent/GetAgentRequestHandler.cs index 26e7ad5d..7127e198 100644 --- a/OpenAlprWebhookProcessor/Settings/GetAgent/GetAgentRequestHandler.cs +++ b/OpenAlprWebhookProcessor/Settings/GetAgent/GetAgentRequestHandler.cs @@ -16,7 +16,9 @@ public GetAgentRequestHandler(ProcessorContext processorContext) public async Task HandleAsync(CancellationToken cancellationToken) { - var agent = await _processorContext.Agents.FirstOrDefaultAsync(cancellationToken); + var agent = await _processorContext.Agents + .AsNoTracking() + .FirstOrDefaultAsync(cancellationToken); if (agent == null) { @@ -28,6 +30,7 @@ public async Task HandleAsync(CancellationToken cancellationToken) EndpointUrl = agent.EndpointUrl, Hostname = agent.Hostname, IsDebugEnabled = agent.IsDebugEnabled, + IsImageCompressionEnabled = agent.IsImageCompressionEnabled, Latitude = agent.Latitude, Longitude = agent.Longitude, OpenAlprWebServerApiKey = agent.OpenAlprWebServerApiKey, diff --git a/OpenAlprWebhookProcessor/Settings/GetDebugPlateGroups/GetDebugPlateGroupRequestHandler.cs b/OpenAlprWebhookProcessor/Settings/GetDebugPlateGroups/GetDebugPlateGroupRequestHandler.cs index 9b0661de..ef623bfd 100644 --- a/OpenAlprWebhookProcessor/Settings/GetDebugPlateGroups/GetDebugPlateGroupRequestHandler.cs +++ b/OpenAlprWebhookProcessor/Settings/GetDebugPlateGroups/GetDebugPlateGroupRequestHandler.cs @@ -22,7 +22,9 @@ public async Task HandleAsync( bool onlyFailedPlateGroups, CancellationToken cancellationToken) { - var query = _processorContext.RawPlateGroups.AsQueryable(); + var query = _processorContext.RawPlateGroups + .AsNoTracking() + .AsQueryable(); var stopEpoch = DateTimeOffset.UtcNow.AddDays(-1).ToUnixTimeMilliseconds(); diff --git a/OpenAlprWebhookProcessor/Settings/GetIgnores/GetIgnoresRequestHandler.cs b/OpenAlprWebhookProcessor/Settings/GetIgnores/GetIgnoresRequestHandler.cs index 5b070bb1..b106a5a8 100644 --- a/OpenAlprWebhookProcessor/Settings/GetIgnores/GetIgnoresRequestHandler.cs +++ b/OpenAlprWebhookProcessor/Settings/GetIgnores/GetIgnoresRequestHandler.cs @@ -18,7 +18,7 @@ public async Task> HandleAsync() { var ignores = new List(); - foreach (var dbIgnore in await _processorContext.Ignores.ToListAsync()) + foreach (var dbIgnore in await _processorContext.Ignores.AsNoTracking().ToListAsync()) { var ignore = new Ignore() { diff --git a/OpenAlprWebhookProcessor/Settings/GetWebhookForwards/GetWebhookForwardsRequestHandler.cs b/OpenAlprWebhookProcessor/Settings/GetWebhookForwards/GetWebhookForwardsRequestHandler.cs index 462bb4fc..17939a41 100644 --- a/OpenAlprWebhookProcessor/Settings/GetWebhookForwards/GetWebhookForwardsRequestHandler.cs +++ b/OpenAlprWebhookProcessor/Settings/GetWebhookForwards/GetWebhookForwardsRequestHandler.cs @@ -17,7 +17,9 @@ public GetWebhookForwardsRequestHandler(ProcessorContext processorContext) public async Task> HandleAsync(CancellationToken cancellationToken) { - var webhookForwards = await _processorContext.WebhookForwards.ToListAsync(cancellationToken); + var webhookForwards = await _processorContext.WebhookForwards + .AsNoTracking() + .ToListAsync(cancellationToken); var forwards = new List(); diff --git a/OpenAlprWebhookProcessor/Settings/UpsertAgent/UpsertAgentRequestHandler.cs b/OpenAlprWebhookProcessor/Settings/UpsertAgent/UpsertAgentRequestHandler.cs index 534b50fd..e03e2485 100644 --- a/OpenAlprWebhookProcessor/Settings/UpsertAgent/UpsertAgentRequestHandler.cs +++ b/OpenAlprWebhookProcessor/Settings/UpsertAgent/UpsertAgentRequestHandler.cs @@ -1,5 +1,7 @@ using Microsoft.EntityFrameworkCore; using OpenAlprWebhookProcessor.Data; +using OpenAlprWebhookProcessor.WebhookProcessor; +using System.Linq; using System.Threading.Tasks; namespace OpenAlprWebhookProcessor.Settings @@ -8,14 +10,27 @@ public class UpsertAgentRequestHandler { private readonly ProcessorContext _processorContext; - public UpsertAgentRequestHandler(ProcessorContext processorContext) + private readonly ImageRetrieverService _imageRetrieverService; + + public UpsertAgentRequestHandler( + ProcessorContext processorContext, + ImageRetrieverService imageRetrieverService) { _processorContext = processorContext; + _imageRetrieverService = imageRetrieverService; } public async Task HandleAsync(Agent agent) { - var dbAgent = await _processorContext.Agents.FirstOrDefaultAsync(); + var dbAgent = await _processorContext.Agents + .FirstOrDefaultAsync(); + + var wasImageCompressionEnabled = false; + + if (agent.IsImageCompressionEnabled && !dbAgent.IsImageCompressionEnabled) + { + wasImageCompressionEnabled = true; + } if (dbAgent == null) { @@ -24,6 +39,7 @@ public async Task HandleAsync(Agent agent) EndpointUrl = agent.EndpointUrl, Hostname = agent.Hostname, IsDebugEnabled = agent.IsDebugEnabled, + IsImageCompressionEnabled = agent.IsImageCompressionEnabled, Latitude = agent.Latitude, Longitude = agent.Longitude, OpenAlprWebServerApiKey = agent.OpenAlprWebServerApiKey, @@ -42,6 +58,7 @@ public async Task HandleAsync(Agent agent) dbAgent.EndpointUrl = agent.EndpointUrl; dbAgent.Hostname = agent.Hostname; dbAgent.IsDebugEnabled = agent.IsDebugEnabled; + dbAgent.IsImageCompressionEnabled = agent.IsImageCompressionEnabled; dbAgent.Latitude = agent.Latitude; dbAgent.Longitude = agent.Longitude; dbAgent.OpenAlprWebServerApiKey = agent.OpenAlprWebServerApiKey; @@ -54,6 +71,11 @@ public async Task HandleAsync(Agent agent) } await _processorContext.SaveChangesAsync(); + + if (wasImageCompressionEnabled) + { + _imageRetrieverService.AddImageCompressionJob(); + } } } } diff --git a/OpenAlprWebhookProcessor/Startup.cs b/OpenAlprWebhookProcessor/Startup.cs index 5189fab6..c402dffc 100644 --- a/OpenAlprWebhookProcessor/Startup.cs +++ b/OpenAlprWebhookProcessor/Startup.cs @@ -9,9 +9,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; -using Microsoft.OpenApi.Models; using OpenAlprWebhookProcessor.Alerts; using OpenAlprWebhookProcessor.Cameras; using OpenAlprWebhookProcessor.Data; @@ -45,10 +43,12 @@ using OpenAlprWebhookProcessor.Settings.Enrichers; using OpenAlprWebhookProcessor.LicensePlates.Enricher; using OpenAlprWebhookProcessor.LicensePlates.Enricher.LicensePlateData; -using System.Text.Json.Serialization; using OpenAlprWebhookProcessor.Settings.GetDebugPlateGroups; using OpenAlprWebhookProcessor.Settings.GetDebubPlateGroups; -using OpenAlprWebhookProcessor.ImageRelay.GetImage; +using OpenAlprWebhookProcessor.WebPushSubscriptions; +using Lib.Net.Http.WebPush; +using OpenAlprWebhookProcessor.WebPushSubscriptions; +using OpenAlprWebhookProcessor.Alerts.WebPush; namespace OpenAlprWebhookProcessor { @@ -58,7 +58,7 @@ public class Startup private readonly string UsersContextConnectionString = $"Data Source={configurationDirectory}/users.db"; - private readonly string ProcessorContextConnectionString = $"Data Source={configurationDirectory}/processor.db"; + private readonly string ProcessorContextConnectionString = $"Data Source={configurationDirectory}/processor.db;foreign keys=true;"; public Startup(IConfiguration configuration) { @@ -70,12 +70,7 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { services.AddCors(); - services - .AddControllersWithViews() - .AddJsonOptions(options => - { - options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); - }); + services.AddControllersWithViews(); services.AddSignalR(); @@ -128,7 +123,7 @@ public void ConfigureServices(IServiceCollection services) { context.Token = context.Request.Cookies["jwtToken"]; } - + return Task.CompletedTask; } }; @@ -137,17 +132,11 @@ public void ConfigureServices(IServiceCollection services) services.AddSpaStaticFiles(configuration => { - configuration.RootPath = "ClientApp/dist"; + configuration.RootPath = "angularapp/dist"; }); services.AddScoped(); - services.AddLogging(config => - { - config.AddDebug(); - config.AddConsole(); - }); - services.AddDbContext(options => options.UseSqlite(ProcessorContextConnectionString)); @@ -189,9 +178,20 @@ public void ConfigureServices(IServiceCollection services) services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddHttpClient(); + + services.AddSingleton(); + services.AddHostedService(); services.AddSingleton(); services.AddSingleton(p => p.GetService()); @@ -204,12 +204,6 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(p => p.GetService()); - - - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); - }); var mapper = new MapperConfiguration(mc => { @@ -252,14 +246,6 @@ public void Configure( app.UseSerilogRequestLogging(); - app.UseSwagger(); - - app.UseSwaggerUI(c => - { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "OpenALPR Webhook Processor"); - c.RoutePrefix = "/swagger"; - }); - app.UseStaticFiles(); app.UseHangfireDashboard(); @@ -290,7 +276,7 @@ public void Configure( app.UseSpa(spa => { - spa.Options.SourcePath = "ClientApp"; + spa.Options.SourcePath = "angularapp"; if (env.IsDevelopment()) { @@ -299,10 +285,10 @@ public void Configure( }); Log.Logger = new LoggerConfiguration() - .MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", Serilog.Events.LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", Serilog.Events.LogEventLevel.Error) .Enrich.FromLogContext() .WriteTo.File( - "log-.txt", + "./config/log-.txt", rollingInterval: RollingInterval.Day, shared: true, flushToDiskInterval: TimeSpan.FromSeconds(5), diff --git a/OpenAlprWebhookProcessor/SystemLogs/LogsController.cs b/OpenAlprWebhookProcessor/SystemLogs/LogsController.cs index da16722e..5b9d886b 100644 --- a/OpenAlprWebhookProcessor/SystemLogs/LogsController.cs +++ b/OpenAlprWebhookProcessor/SystemLogs/LogsController.cs @@ -1,12 +1,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Serilog; -using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; -using System.Threading; using System.Threading.Tasks; namespace OpenAlprWebhookProcessor.SystemLogs @@ -23,7 +20,7 @@ public LogsController() [HttpGet] public async Task> GetLogs() { - var currentLogFile = Directory.GetFiles(".") + var currentLogFile = Directory.GetFiles("./config/") .LastOrDefault(x => x.Contains("log-")); using (var stream = System.IO.File.Open( diff --git a/OpenAlprWebhookProcessor/SystemLogs/SignalrSinkExtension.cs b/OpenAlprWebhookProcessor/SystemLogs/SignalrSinkExtension.cs index 9c042880..d51ca97e 100644 --- a/OpenAlprWebhookProcessor/SystemLogs/SignalrSinkExtension.cs +++ b/OpenAlprWebhookProcessor/SystemLogs/SignalrSinkExtension.cs @@ -8,9 +8,9 @@ namespace OpenAlprWebhookProcessor.SystemLogs public static class SignalrSinkExtension { public static LoggerConfiguration Signalr( - this LoggerSinkConfiguration loggerConfiguration, - IHubContext processorHub, - IFormatProvider formatProvider = null) + this LoggerSinkConfiguration loggerConfiguration, + IHubContext processorHub, + IFormatProvider formatProvider = null) { return loggerConfiguration.Sink(new SignalrSink(processorHub)); } diff --git a/OpenAlprWebhookProcessor/Users/UserService.cs b/OpenAlprWebhookProcessor/Users/UserService.cs index 1e2801ae..0d097f7a 100644 --- a/OpenAlprWebhookProcessor/Users/UserService.cs +++ b/OpenAlprWebhookProcessor/Users/UserService.cs @@ -153,7 +153,7 @@ public async Task AuthenticateAsync( user.PasswordHash, user.PasswordSalt)) { - return null; + return null; } var jwtToken = await GenerateJwtTokenAsync(user); @@ -175,7 +175,7 @@ public async Task RefreshTokenAsync( string ipAddress, CancellationToken cancellationToken) { - var user = await _usersContext.Users.SingleOrDefaultAsync(u => + var user = await _usersContext.Users.SingleOrDefaultAsync(u => u.RefreshTokens.Any(t => t.Token == token), cancellationToken); @@ -252,12 +252,17 @@ public async Task GetJwtSecretKeyAsync() { jwtKey = new JwtKey { - Key = GenerateJwtSecretKey(30) + Key = GenerateJwtSecretKey(128) }; _usersContext.Add(jwtKey); await _usersContext.SaveChangesAsync(); } + else if (jwtKey.Key.Length < 128) + { + jwtKey.Key = GenerateJwtSecretKey(128); + await _usersContext.SaveChangesAsync(); + } return Convert.FromBase64String(jwtKey.Key); } @@ -286,10 +291,10 @@ private async Task GenerateJwtTokenAsync(User user) private static RefreshToken GenerateRefreshToken(string ipAddress) { - using (var rngCryptoServiceProvider = new RNGCryptoServiceProvider()) + using (var rng = RandomNumberGenerator.Create()) { - var randomBytes = new byte[64]; - rngCryptoServiceProvider.GetBytes(randomBytes); + var randomBytes = new byte[128]; + rng.GetBytes(randomBytes); return new RefreshToken { Token = Convert.ToBase64String(randomBytes), @@ -361,10 +366,12 @@ private static bool VerifyPasswordHash( private static string GenerateJwtSecretKey(int keyLength) { - RNGCryptoServiceProvider rngCryptoServiceProvider = new RNGCryptoServiceProvider(); - byte[] randomBytes = new byte[keyLength]; - rngCryptoServiceProvider.GetBytes(randomBytes); - return Convert.ToBase64String(randomBytes); + using (var rng = RandomNumberGenerator.Create()) + { + byte[] randomBytes = new byte[keyLength]; + rng.GetBytes(randomBytes); + return Convert.ToBase64String(randomBytes); + } } } } diff --git a/OpenAlprWebhookProcessor/WebPushSubscriptions/AngularWebPushNotification.cs b/OpenAlprWebhookProcessor/WebPushSubscriptions/AngularWebPushNotification.cs new file mode 100644 index 00000000..e2bd1d61 --- /dev/null +++ b/OpenAlprWebhookProcessor/WebPushSubscriptions/AngularWebPushNotification.cs @@ -0,0 +1,53 @@ +using Lib.Net.Http.WebPush; +using Newtonsoft.Json.Serialization; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace OpenAlprWebhookProcessor.WebPushSubscriptions +{ + public class AngularWebPushNotification + { + private const string WRAPPER_START = "{\"notification\":"; + private const string WRAPPER_END = "}"; + + public class NotificationAction + { + public string Action { get; } + + public string Title { get; } + + public NotificationAction(string action, string title) + { + Action = action; + Title = title; + } + } + + private static readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + + public string Title { get; set; } + + public string Body { get; set; } + + public string Icon { get; set; } + + public IList Vibrate { get; set; } = new List(); + + public IDictionary Data { get; set; } + + public IList Actions { get; set; } = new List(); + + public PushMessage ToPushMessage(string topic = null, int? timeToLive = null, PushMessageUrgency urgency = PushMessageUrgency.Normal) + { + return new PushMessage(WRAPPER_START + JsonConvert.SerializeObject(this, _jsonSerializerSettings) + WRAPPER_END) + { + Topic = topic, + TimeToLive = timeToLive, + Urgency = urgency + }; + } + } +} diff --git a/OpenAlprWebhookProcessor/WebPushSubscriptions/IWebPushSubscriptionsService.cs b/OpenAlprWebhookProcessor/WebPushSubscriptions/IWebPushSubscriptionsService.cs new file mode 100644 index 00000000..fdeec354 --- /dev/null +++ b/OpenAlprWebhookProcessor/WebPushSubscriptions/IWebPushSubscriptionsService.cs @@ -0,0 +1,14 @@ +using Lib.Net.Http.WebPush; +using System.Collections.Generic; + +namespace OpenAlprWebhookProcessor.WebPushSubscriptions +{ + public interface IWebPushSubscriptionsService + { + List GetAll(); + + void Insert(PushSubscription subscription); + + void Delete(string endpoint); + } +} \ No newline at end of file diff --git a/OpenAlprWebhookProcessor/WebPushSubscriptions/VapidKeys/VapidDetails.cs b/OpenAlprWebhookProcessor/WebPushSubscriptions/VapidKeys/VapidDetails.cs new file mode 100644 index 00000000..e8ebfc66 --- /dev/null +++ b/OpenAlprWebhookProcessor/WebPushSubscriptions/VapidKeys/VapidDetails.cs @@ -0,0 +1,30 @@ +namespace OpenAlprWebhookProcessor.WebPushSubscriptions.VapidKeys +{ + public class VapidDetails + { + public VapidDetails() + { + } + + /// This should be a URL or a 'mailto:' email address + /// The VAPID public key as a base64 encoded string + /// The VAPID private key as a base64 encoded string + public VapidDetails( + string subject, + string publicKey, + string privateKey) + { + Subject = subject; + PublicKey = publicKey; + PrivateKey = privateKey; + } + + public string Subject { get; set; } + + public string PublicKey { get; set; } + + public string PrivateKey { get; set; } + + public long Expiration { get; set; } = -1; + } +} diff --git a/OpenAlprWebhookProcessor/WebPushSubscriptions/VapidKeys/VapidKeyGenerator.cs b/OpenAlprWebhookProcessor/WebPushSubscriptions/VapidKeys/VapidKeyGenerator.cs new file mode 100644 index 00000000..89618cb4 --- /dev/null +++ b/OpenAlprWebhookProcessor/WebPushSubscriptions/VapidKeys/VapidKeyGenerator.cs @@ -0,0 +1,48 @@ +using Org.BouncyCastle.Asn1.Nist; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using System; +using Org.BouncyCastle.Utilities.Encoders; + +namespace OpenAlprWebhookProcessor.WebPushSubscriptions.VapidKeys +{ + public class VapidKeyGenerator + { + public static VapidDetails GenerateVapidKeys() + { + var results = new VapidDetails(); + + var keys = GenerateKeys(); + var publicKey = ((ECPublicKeyParameters)keys.Public).Q.GetEncoded(false); + var privateKey = ((ECPrivateKeyParameters)keys.Private).D.ToByteArrayUnsigned(); + + results.PublicKey = System.Text.Encoding.UTF8.GetString(UrlBase64.Encode(publicKey)); + results.PrivateKey = System.Text.Encoding.UTF8.GetString(UrlBase64.Encode(ByteArrayPadLeft(privateKey, 32))); + + results.PublicKey = results.PublicKey.TrimEnd('.'); + results.PrivateKey = results.PrivateKey.TrimEnd('.'); + + return results; + } + + private static AsymmetricCipherKeyPair GenerateKeys() + { + var ecParameters = NistNamedCurves.GetByName("P-256"); + var ecSpec = new ECDomainParameters(ecParameters.Curve, ecParameters.G, ecParameters.N, ecParameters.H, + ecParameters.GetSeed()); + var keyPairGenerator = GeneratorUtilities.GetKeyPairGenerator("ECDH"); + keyPairGenerator.Init(new ECKeyGenerationParameters(ecSpec, new SecureRandom())); + + return keyPairGenerator.GenerateKeyPair(); + } + + private static byte[] ByteArrayPadLeft(byte[] src, int size) + { + var dst = new byte[size]; + var startAt = dst.Length - src.Length; + Array.Copy(src, 0, dst, startAt, src.Length); + return dst; + } + } +} diff --git a/OpenAlprWebhookProcessor/WebPushSubscriptions/VapidKeys/VapidKeyHelper.cs b/OpenAlprWebhookProcessor/WebPushSubscriptions/VapidKeys/VapidKeyHelper.cs new file mode 100644 index 00000000..3fab31fd --- /dev/null +++ b/OpenAlprWebhookProcessor/WebPushSubscriptions/VapidKeys/VapidKeyHelper.cs @@ -0,0 +1,63 @@ +using Microsoft.EntityFrameworkCore; +using OpenAlprWebhookProcessor.Data; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace OpenAlprWebhookProcessor.WebPushSubscriptions.VapidKeys +{ + public static class VapidKeyHelper + { + public static VapidDetails GetVapidKeys(ProcessorContext processorContext) + { + var pushSettings = processorContext.WebPushSettings.FirstOrDefault(); + + if (pushSettings == null || string.IsNullOrWhiteSpace(pushSettings.PublicKey)) + { + pushSettings = AddVapidKeys(processorContext); + processorContext.SaveChanges(); + } + + return new VapidDetails() + { + Subject = pushSettings.Subject, + PublicKey = pushSettings.PublicKey, + PrivateKey = pushSettings.PrivateKey, + }; + } + + public static async Task GetVapidKeysAsync( + ProcessorContext processorContext, + CancellationToken cancellationToken) + { + var pushSettings = await processorContext.WebPushSettings.FirstOrDefaultAsync(cancellationToken); + + if (pushSettings == null || string.IsNullOrWhiteSpace(pushSettings.PublicKey)) + { + pushSettings = AddVapidKeys(processorContext); + await processorContext.SaveChangesAsync(cancellationToken); + } + + return new VapidDetails() + { + PublicKey = pushSettings.PublicKey, + PrivateKey = pushSettings.PrivateKey, + }; + } + + public static WebPushSettings AddVapidKeys(ProcessorContext processorContext) + { + var vapidKeys = VapidKeyGenerator.GenerateVapidKeys(); + + var pushSettings = new WebPushSettings() + { + PublicKey = vapidKeys.PublicKey, + PrivateKey = vapidKeys.PrivateKey, + }; + + processorContext.WebPushSettings.Add(pushSettings); + + return pushSettings; + } + } +} diff --git a/OpenAlprWebhookProcessor/WebPushSubscriptions/WebPushNotificationProducer.cs b/OpenAlprWebhookProcessor/WebPushSubscriptions/WebPushNotificationProducer.cs new file mode 100644 index 00000000..59149b47 --- /dev/null +++ b/OpenAlprWebhookProcessor/WebPushSubscriptions/WebPushNotificationProducer.cs @@ -0,0 +1,111 @@ +using Lib.Net.Http.WebPush.Authentication; +using Lib.Net.Http.WebPush; +using Microsoft.Extensions.Hosting; +using System.Threading; +using OpenAlprWebhookProcessor.Data; +using OpenAlprWebhookProcessor.WebPushSubscriptions.VapidKeys; +using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using OpenAlprWebhookProcessor.Alerts; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Flurl.Util; + +namespace OpenAlprWebhookProcessor.WebPushSubscriptions +{ + public class WebPushNotificationProducer : BackgroundService, IAlertClient + { + private readonly IWebPushSubscriptionsService _pushSubscriptionsService; + + private readonly PushServiceClient _pushClient; + + private readonly IServiceProvider _serviceProvider; + + private readonly ILogger _logger; + + public WebPushNotificationProducer( + IWebPushSubscriptionsService pushSubscriptionsService, + IServiceProvider serviceProvider, + ILogger logger, + PushServiceClient pushClient) + { + _pushSubscriptionsService = pushSubscriptionsService; + _pushClient = pushClient; + _serviceProvider = serviceProvider; + _logger = logger; + + using(var scope = _serviceProvider.CreateScope()) + { + var processorContext = scope.ServiceProvider.GetRequiredService(); + + var keys = VapidKeyHelper.GetVapidKeys(processorContext); + + _pushClient.DefaultAuthentication = new VapidAuthentication( + keys.PublicKey, + keys.PrivateKey) + { + Subject = "mailto:" + keys.Subject, + }; + } + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + } + + public async Task SendAlertAsync( + Alerts.Alert alert, + byte[] plateJpeg, + CancellationToken cancellationToken) + { + PushMessage notification = new AngularWebPushNotification + { + Title = $"Plate Seen: {alert.PlateNumber}", + Body = $"Plate {alert.PlateNumber} seen at {DateTimeOffset.UtcNow:g}", + Icon = "assets/icons/icon-96x96.png", + }.ToPushMessage(); + + foreach (PushSubscription subscription in _pushSubscriptionsService.GetAll()) + { + try + { + await _pushClient.RequestPushMessageDeliveryAsync( + subscription, + notification, + cancellationToken); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to send WebPush message"); + _pushSubscriptionsService.Delete(subscription.Endpoint); + } + } + } + + public async Task VerifyCredentialsAsync(CancellationToken cancellationToken) + { + using (var scope = _serviceProvider.CreateScope()) + { + var processorContext = scope.ServiceProvider.GetRequiredService(); + + var keys = await VapidKeyHelper.GetVapidKeysAsync( + processorContext, + cancellationToken); + + var credentialsValid = !string.IsNullOrWhiteSpace(keys.PrivateKey) + && !string.IsNullOrWhiteSpace(keys.PublicKey) + && !string.IsNullOrWhiteSpace(keys.Subject); + + if (!credentialsValid) + { + _logger.LogError("WebPush credentials are missing."); + } + else + { + _logger.LogInformation("WebPush credentials are present."); + } + } + } + } +} diff --git a/OpenAlprWebhookProcessor/WebPushSubscriptions/WebPushPublicKeyController.cs b/OpenAlprWebhookProcessor/WebPushSubscriptions/WebPushPublicKeyController.cs new file mode 100644 index 00000000..6917eb2b --- /dev/null +++ b/OpenAlprWebhookProcessor/WebPushSubscriptions/WebPushPublicKeyController.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using OpenAlprWebhookProcessor.Data; +using System.Threading; +using System.Threading.Tasks; +using OpenAlprWebhookProcessor.WebPushSubscriptions.VapidKeys; + +namespace OpenAlprWebhookProcessor.WebPushSubscriptions +{ + [Route("api/[controller]")] + [ApiController] + public class WebPushPublicKeyController : ControllerBase + { + private readonly ProcessorContext _processorContext; + + public WebPushPublicKeyController(ProcessorContext processorContext) + { + _processorContext = processorContext; + } + + public async Task Get(CancellationToken cancellationToken) + { + var keys = await VapidKeyHelper.GetVapidKeysAsync( + _processorContext, + cancellationToken); + + return Content(keys.PublicKey, "text/plain"); + } + } +} diff --git a/OpenAlprWebhookProcessor/WebPushSubscriptions/WebPushSubscriptionsController.cs b/OpenAlprWebhookProcessor/WebPushSubscriptions/WebPushSubscriptionsController.cs new file mode 100644 index 00000000..8a9a7ac1 --- /dev/null +++ b/OpenAlprWebhookProcessor/WebPushSubscriptions/WebPushSubscriptionsController.cs @@ -0,0 +1,29 @@ +using Lib.Net.Http.WebPush; +using Microsoft.AspNetCore.Mvc; + +namespace OpenAlprWebhookProcessor.WebPushSubscriptions +{ + [Route("api/[controller]")] + [ApiController] + public class WebPushSubscriptionsController : ControllerBase + { + private readonly IWebPushSubscriptionsService _pushSubscriptionsService; + + public WebPushSubscriptionsController(IWebPushSubscriptionsService pushSubscriptionsService) + { + _pushSubscriptionsService = pushSubscriptionsService; + } + + [HttpPost] + public void Post([FromBody] PushSubscription subscription) + { + _pushSubscriptionsService.Insert(subscription); + } + + [HttpDelete("{endpoint}")] + public void Delete(string endpoint) + { + _pushSubscriptionsService.Delete(endpoint); + } + } +} diff --git a/OpenAlprWebhookProcessor/WebPushSubscriptions/WebPushSubscriptionsService.cs b/OpenAlprWebhookProcessor/WebPushSubscriptions/WebPushSubscriptionsService.cs new file mode 100644 index 00000000..3db9995d --- /dev/null +++ b/OpenAlprWebhookProcessor/WebPushSubscriptions/WebPushSubscriptionsService.cs @@ -0,0 +1,97 @@ +using Lib.Net.Http.WebPush; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using OpenAlprWebhookProcessor.Data; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace OpenAlprWebhookProcessor.WebPushSubscriptions +{ + internal partial class WebPushSubscriptionsService : IWebPushSubscriptionsService + { + private readonly IServiceProvider _serviceProvider; + + public WebPushSubscriptionsService(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public List GetAll() + { + using (var scope = _serviceProvider.CreateScope()) + { + var processorContext = scope.ServiceProvider.GetRequiredService(); + + var subscriptions = processorContext.WebPushSubscriptions + .Include(x => x.Keys) + .AsNoTracking() + .ToList(); + + var pushSubscriptions = new List(); + + foreach (var subscription in subscriptions.Where(x => x.Keys != null)) + { + var newPushSubscription = new Lib.Net.Http.WebPush.PushSubscription() + { + Endpoint = subscription.Endpoint, + Keys = new Dictionary(), + }; + + newPushSubscription.SetKey(PushEncryptionKeyName.Auth, subscription.Keys.First(x => x.Key == "auth").Value); + newPushSubscription.SetKey(PushEncryptionKeyName.P256DH, subscription.Keys.First(x => x.Key == "p256dh").Value); + + pushSubscriptions.Add(newPushSubscription); + } + + return pushSubscriptions; + } + } + + public void Insert(Lib.Net.Http.WebPush.PushSubscription subscription) + { + using (var scope = _serviceProvider.CreateScope()) + { + var processorContext = scope.ServiceProvider.GetRequiredService(); + + var existingSubscription = processorContext.WebPushSubscriptions + .Include(x => x.Keys) + .FirstOrDefault(x => x.Endpoint == subscription.Endpoint); + + if (existingSubscription == null) + { + var pushSubscription = new Data.WebPushSubscription + { + Endpoint = subscription.Endpoint, + Keys = new List(), + }; + + foreach (var key in subscription.Keys) + { + pushSubscription.Keys.Add(new WebPushSubscriptionKey() + { + Key = key.Key, + Value = key.Value, + }); + } + + processorContext.WebPushSubscriptions.Add(pushSubscription); + processorContext.SaveChanges(); + } + } + } + + public void Delete(string endpoint) + { + using (var scope = _serviceProvider.CreateScope()) + { + var processorContext = scope.ServiceProvider.GetRequiredService(); + + var endpointToRemove = processorContext.WebPushSubscriptions.FirstOrDefault(x => x.Endpoint == endpoint); + processorContext.WebPushSubscriptions.Remove(endpointToRemove); + processorContext.SaveChanges(); + + } + } + } +} diff --git a/OpenAlprWebhookProcessor/WebhookProcessor/GroupWebhookHandler.cs b/OpenAlprWebhookProcessor/WebhookProcessor/GroupWebhookHandler.cs index f4f30de0..bf98259a 100644 --- a/OpenAlprWebhookProcessor/WebhookProcessor/GroupWebhookHandler.cs +++ b/OpenAlprWebhookProcessor/WebhookProcessor/GroupWebhookHandler.cs @@ -50,7 +50,10 @@ public async Task HandleWebhookAsync( bool isBulkImport, CancellationToken cancellationToken) { - var agent = await _processorContext.Agents.FirstOrDefaultAsync(cancellationToken); + var agent = await _processorContext.Agents + .AsNoTracking() + .FirstOrDefaultAsync(cancellationToken); + PlateGroupRaw rawDebugPlateGroup = null; if (agent.IsDebugEnabled) @@ -68,6 +71,7 @@ public async Task HandleWebhookAsync( } var camera = await _processorContext.Cameras + .AsNoTracking() .Where(x => x.OpenAlprCameraId == webhook.Group.CameraId) .FirstOrDefaultAsync(cancellationToken); @@ -116,8 +120,6 @@ public async Task HandleWebhookAsync( plateGroup.OpenAlprUuid = webhook.Group.BestUuid; plateGroup.BestNumber = webhook.Group.BestPlateNumber; plateGroup.PossibleNumbers = webhook.Group.Candidates.Select(x => new PlateGroupPossibleNumbers() { Number = x.Plate }).ToList(); - plateGroup.PlatePreviewJpeg = webhook.Group.BestPlate.PlateCropJpeg; - plateGroup.VehiclePreviewJpeg = webhook.Group.VehicleCropJpeg; plateGroup.Confidence = Math.Round(webhook.Group.BestPlate.Confidence, 2); plateGroup.ReceivedOnEpoch = webhook.Group.EpochStart; @@ -184,7 +186,9 @@ public async Task HandleWebhookAsync( } } - var forwards = await _processorContext.WebhookForwards.ToListAsync(cancellationToken); + var forwards = await _processorContext.WebhookForwards + .AsNoTracking() + .ToListAsync(cancellationToken); foreach (var forward in forwards) { diff --git a/OpenAlprWebhookProcessor/WebhookProcessor/ImageRetrieverService.cs b/OpenAlprWebhookProcessor/WebhookProcessor/ImageRetrieverService.cs index 2883e91e..7e2849ea 100644 --- a/OpenAlprWebhookProcessor/WebhookProcessor/ImageRetrieverService.cs +++ b/OpenAlprWebhookProcessor/WebhookProcessor/ImageRetrieverService.cs @@ -6,6 +6,7 @@ using OpenAlprWebhookProcessor.ImageRelay; using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -16,6 +17,8 @@ public class ImageRetrieverService : IHostedService { private readonly BlockingCollection _imageRequestsToProcess; + private readonly BlockingCollection _imageCompressionRequestsToProcess; + private readonly CancellationTokenSource _cancellationTokenSource; private readonly IServiceProvider _serviceProvider; @@ -25,6 +28,7 @@ public ImageRetrieverService(IServiceProvider serviceProvider) _cancellationTokenSource = new CancellationTokenSource(); _serviceProvider = serviceProvider; _imageRequestsToProcess = new BlockingCollection(); + _imageCompressionRequestsToProcess = new BlockingCollection(); } public Task StartAsync(CancellationToken cancellationToken) @@ -33,6 +37,10 @@ public Task StartAsync(CancellationToken cancellationToken) await ProcessImageRequestsAsync(), cancellationToken); + Task.Run(async () => + await ProcessImageCompressionRequestsAsync(), + cancellationToken); + return Task.CompletedTask; } @@ -53,6 +61,11 @@ public void AddJob(string openAlprImageId) } } + public void AddImageCompressionJob() + { + _imageCompressionRequestsToProcess.Add("allImages"); + } + private async Task ProcessImageRequestsAsync() { foreach (var job in _imageRequestsToProcess.GetConsumingEnumerable(_cancellationTokenSource.Token)) @@ -65,9 +78,16 @@ private async Task ProcessImageRequestsAsync() var processorContext = scope.ServiceProvider.GetRequiredService(); var plateGroups = await processorContext.PlateGroups + .Include(x => x.PlateImage) + .Include(x => x.VehicleImage) .Where(x => x.OpenAlprUuid == job) .ToListAsync(_cancellationTokenSource.Token); + var isImageCompressionEnabled = await processorContext.Agents + .AsNoTracking() + .Select(x => x.IsImageCompressionEnabled) + .FirstOrDefaultAsync(); + foreach (var plateGroup in plateGroups) { if (plateGroup == null) @@ -88,8 +108,17 @@ private async Task ProcessImageRequestsAsync() job + "?" + plateGroup.PlateCoordinates, _cancellationTokenSource.Token); - plateGroup.PlateJpeg = cropImage; - plateGroup.VehicleJpeg = image; + plateGroup.PlateImage = new PlateImage() + { + Jpeg = cropImage, + IsCompressed = isImageCompressionEnabled, + }; + + plateGroup.VehicleImage = new VehicleImage() + { + Jpeg = image, + IsCompressed = isImageCompressionEnabled, + }; } catch (Exception ex) { @@ -104,5 +133,72 @@ private async Task ProcessImageRequestsAsync() } } } + + private async Task ProcessImageCompressionRequestsAsync() + { + foreach (var job in _imageCompressionRequestsToProcess.GetConsumingEnumerable(_cancellationTokenSource.Token)) + { + using (var scope = _serviceProvider.CreateScope()) + { + bool keepPaging = true; + long lastReceivedOnEpoch = 0; + + while (keepPaging) + { + var processorContext = scope.ServiceProvider.GetRequiredService(); + var logger = scope.ServiceProvider.GetRequiredService>(); + + var isImageCompressionEnabled = await processorContext.Agents + .AsNoTracking() + .Select(x => x.IsImageCompressionEnabled) + .FirstOrDefaultAsync(); + + if (!isImageCompressionEnabled) + { + logger.LogWarning("Image compression disabled, check agent settings."); + break; + } + + var plateGroups = await processorContext.PlateGroups + .Include(x => x.PlateImage) + .Include(x => x.VehicleImage) + .OrderBy(x => x.ReceivedOnEpoch) + .Where(x => x.ReceivedOnEpoch > lastReceivedOnEpoch) + .Where(x => !x.PlateImage.IsCompressed || !x.VehicleImage.IsCompressed) + .Where(x => x.PlateImage.Jpeg.Length > 0 || x.VehicleImage.Jpeg.Length > 0) + .Take(25) + .ToListAsync(_cancellationTokenSource.Token); + + if (!plateGroups.Any()) + { + keepPaging = false; + } + else + { + lastReceivedOnEpoch = plateGroups.First().ReceivedOnEpoch; + } + + logger.LogInformation("Searcing for images newer than {epoch}: {numberOfRequests} images queued for compression", lastReceivedOnEpoch, plateGroups.Count); + + foreach (var plateGroup in plateGroups) + { + if (!plateGroup.VehicleImage.IsCompressed && plateGroup.VehicleImage.Jpeg != null) + { + plateGroup.VehicleImage.Jpeg = GetImageHandler.CompressImage(plateGroup.VehicleImage.Jpeg); + plateGroup.VehicleImage.IsCompressed = true; + } + + if (!plateGroup.PlateImage.IsCompressed && plateGroup.PlateImage.Jpeg != null) + { + plateGroup.PlateImage.Jpeg = GetImageHandler.CompressImage(plateGroup.PlateImage.Jpeg); + plateGroup.PlateImage.IsCompressed = true; + } + } + + await processorContext.SaveChangesAsync(_cancellationTokenSource.Token); + } + } + } + } } } \ No newline at end of file diff --git a/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprAgentScraper/OpenAlprAgentScraper.cs b/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprAgentScraper/OpenAlprAgentScraper.cs index 0cb2b53c..019143eb 100644 --- a/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprAgentScraper/OpenAlprAgentScraper.cs +++ b/OpenAlprWebhookProcessor/WebhookProcessor/OpenAlprAgentScraper/OpenAlprAgentScraper.cs @@ -53,7 +53,9 @@ public async Task ScrapeAgentAsync(CancellationToken cancellationToken) DateTimeOffset lastSuccessfulScrape; if (agent.LastSuccessfulScrapeEpoch == 0) { - lastSuccessfulScrape = await GetEarliestGroupEpochAsync(agent, cancellationToken); + lastSuccessfulScrape = await GetEarliestGroupEpochAsync( + agent, + cancellationToken); } else { @@ -63,10 +65,12 @@ public async Task ScrapeAgentAsync(CancellationToken cancellationToken) var startDate = DateTimeOffset.UtcNow; while (startDate > lastSuccessfulScrape) { - _logger.LogInformation("Scraping between " - + lastSuccessfulScrape.ToUnixTimeMilliseconds().ToString() - + " and " - + lastSuccessfulScrape.AddMinutes(minutesToScrape).ToUnixTimeMilliseconds().ToString()); + _logger.LogInformation("Scraping between {startTime} and {endTime}", + lastSuccessfulScrape.ToUnixTimeMilliseconds().ToString(), + lastSuccessfulScrape.AddMinutes(minutesToScrape).ToUnixTimeMilliseconds().ToString()); + + var timer = new Stopwatch(); + timer.Start(); var scrapeResults = await _httpClient.GetAsync( agent.EndpointUrl @@ -75,6 +79,9 @@ public async Task ScrapeAgentAsync(CancellationToken cancellationToken) .Replace("{1}", lastSuccessfulScrape.AddMinutes(minutesToScrape).ToUnixTimeMilliseconds().ToString()), cancellationToken); + timer.Stop(); + _logger.LogInformation("Scraping took {seconds} seconds", timer.Elapsed.Seconds); + if (!scrapeResults.IsSuccessStatusCode) { var error = await scrapeResults.Content.ReadAsStringAsync(cancellationToken); @@ -87,11 +94,15 @@ public async Task ScrapeAgentAsync(CancellationToken cancellationToken) var metaDatasToQuery = JsonSerializer.Deserialize>(content); - _logger.LogInformation("Found " + metaDatasToQuery.Count + " entries for: " + lastSuccessfulScrape.ToString()); + _logger.LogInformation("Found {count} entries for: {date}", + metaDatasToQuery.Count, + lastSuccessfulScrape.ToString()); foreach (var metadata in metaDatasToQuery) { - var timer = new Stopwatch(); + _logger.LogDebug("querying key: {key}", metadata.Key); + + timer.Reset(); timer.Start(); var newGroup = await _httpClient.GetAsync( @@ -103,7 +114,7 @@ public async Task ScrapeAgentAsync(CancellationToken cancellationToken) if (!newGroup.IsSuccessStatusCode) { - _logger.LogError($"Bad response received from Agent: {newGroup.StatusCode} {newGroup.ReasonPhrase}"); + _logger.LogError("Bad response received from Agent: {statusCode} {reasonPhrase}", newGroup.StatusCode, newGroup.ReasonPhrase); continue; } @@ -113,6 +124,7 @@ public async Task ScrapeAgentAsync(CancellationToken cancellationToken) { timer.Reset(); timer.Start(); + _logger.LogDebug("deserializing key: {key}", metadata.Key); group = await JsonSerializer.DeserializeAsync( await newGroup.Content.ReadAsStreamAsync(cancellationToken), cancellationToken: cancellationToken); @@ -125,10 +137,10 @@ await newGroup.Content.ReadAsStreamAsync(cancellationToken), continue; } - _logger.LogInformation("date: " + DateTimeOffset.FromUnixTimeMilliseconds(group.EpochStart).ToString() + " querying: " + metadata.Key); - try { + _logger.LogInformation("date: {date} querying: {key}", DateTimeOffset.FromUnixTimeMilliseconds(group.EpochStart).ToString(), metadata.Key); + timer.Reset(); timer.Start(); await _groupWebhookHandler.HandleWebhookAsync( @@ -148,8 +160,11 @@ await _groupWebhookHandler.HandleWebhookAsync( timer.Reset(); timer.Start(); + _logger.LogDebug("Saving agent status, last scrape {scrapeEpoch}", group.EpochStart); + agent.LastSuccessfulScrapeEpoch = group.EpochStart; await _processorContext.SaveChangesAsync(cancellationToken); + timer.Stop(); _logger.LogDebug("Took {seconds} to update agent status.", timer.Elapsed.TotalSeconds); } @@ -169,6 +184,7 @@ public async Task ScrapeAgentImagesAsync(CancellationToken cancellationToken) _logger.LogInformation("Searching for plates with missings images"); var plateGroupIds = await _processorContext.PlateGroups + .AsNoTracking() .Where(x => x.AgentImageScrapeOccurredOn == null) .Select(x => x.OpenAlprUuid) .ToListAsync(cancellationToken); @@ -193,7 +209,7 @@ private async Task GetEarliestGroupEpochAsync( if (!result.IsSuccessStatusCode) { - throw new ArgumentException("Unable to get earliest group epoch"); + throw new ArgumentException($"Unable to get earliest group epoch: Status code: {result.StatusCode} Reason: {result.ReasonPhrase}"); } var content = await result.Content.ReadAsStringAsync(cancellationToken); diff --git a/OpenAlprWebhookProcessor/WebhookProcessor/SinglePlateWebhookHandler.cs b/OpenAlprWebhookProcessor/WebhookProcessor/SinglePlateWebhookHandler.cs index f308d8b1..d3632dd2 100644 --- a/OpenAlprWebhookProcessor/WebhookProcessor/SinglePlateWebhookHandler.cs +++ b/OpenAlprWebhookProcessor/WebhookProcessor/SinglePlateWebhookHandler.cs @@ -33,6 +33,7 @@ public async Task HandleWebhookAsync( CancellationToken cancellationToken) { var camera = await _processorContext.Cameras + .AsNoTracking() .Where(x => x.OpenAlprCameraId == webhook.CameraId) .FirstOrDefaultAsync(cancellationToken); @@ -63,7 +64,9 @@ public async Task HandleWebhookAsync( _cameraUpdateService.ScheduleOverlayRequest(updateRequest); } - var forwards = await _processorContext.WebhookForwards.ToListAsync(cancellationToken); + var forwards = await _processorContext.WebhookForwards + .AsNoTracking() + .ToListAsync(cancellationToken); foreach (var forward in forwards) { diff --git a/OpenAlprWebhookProcessor/WebhookProcessor/WebhookController.cs b/OpenAlprWebhookProcessor/WebhookProcessor/WebhookController.cs index 4922a831..fda3f6f8 100644 --- a/OpenAlprWebhookProcessor/WebhookProcessor/WebhookController.cs +++ b/OpenAlprWebhookProcessor/WebhookProcessor/WebhookController.cs @@ -86,7 +86,7 @@ await _singlePlateWebhookHandler.HandleWebhookAsync( } else { - _logger.LogInformation("Unknown payload received, ignoring: " + rawWebhook); + _logger.LogInformation("Unknown payload received, ignoring: {rawWebhook}", rawWebhook); } } @@ -96,7 +96,7 @@ await _singlePlateWebhookHandler.HandleWebhookAsync( [HttpGet] public ActionResult Get() { - _logger.LogInformation("test succeeded from: " + Request.HttpContext.Connection.RemoteIpAddress); + _logger.LogInformation("test succeeded from: {remoteIpAddress}", Request.HttpContext.Connection.RemoteIpAddress); return Ok("Webhook Processor"); } } diff --git a/OpenAlprWebhookProcessor/ClientApp/.browserslistrc b/OpenAlprWebhookProcessor/angularapp/.browserslistrc similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/.browserslistrc rename to OpenAlprWebhookProcessor/angularapp/.browserslistrc diff --git a/OpenAlprWebhookProcessor/ClientApp/.editorconfig b/OpenAlprWebhookProcessor/angularapp/.editorconfig similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/.editorconfig rename to OpenAlprWebhookProcessor/angularapp/.editorconfig diff --git a/OpenAlprWebhookProcessor/ClientApp/.gitignore b/OpenAlprWebhookProcessor/angularapp/.gitignore similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/.gitignore rename to OpenAlprWebhookProcessor/angularapp/.gitignore diff --git a/OpenAlprWebhookProcessor/ClientApp/LICENSE b/OpenAlprWebhookProcessor/angularapp/LICENSE similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/LICENSE rename to OpenAlprWebhookProcessor/angularapp/LICENSE diff --git a/OpenAlprWebhookProcessor/ClientApp/README.md b/OpenAlprWebhookProcessor/angularapp/README.md similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/README.md rename to OpenAlprWebhookProcessor/angularapp/README.md diff --git a/OpenAlprWebhookProcessor/ClientApp/angular.json b/OpenAlprWebhookProcessor/angularapp/angular.json similarity index 88% rename from OpenAlprWebhookProcessor/ClientApp/angular.json rename to OpenAlprWebhookProcessor/angularapp/angular.json index 916bd540..7d30c650 100644 --- a/OpenAlprWebhookProcessor/ClientApp/angular.json +++ b/OpenAlprWebhookProcessor/angularapp/angular.json @@ -13,7 +13,7 @@ "build": { "builder": "@angular-devkit/build-angular:browser", "options": { - "outputPath": "dist/angularapp", + "outputPath": "dist/", "index": "src/index.html", "main": "src/main.ts", "polyfills": [ @@ -21,8 +21,9 @@ ], "tsConfig": "tsconfig.app.json", "assets": [ - "src/favicon.ico", - "src/assets" + "src/assets/favicon.ico", + "src/assets", + "src/manifest.webmanifest" ], "styles": [ "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", @@ -30,7 +31,9 @@ "./node_modules/highlight.js/styles/dark.css", "src/styles.less" ], - "scripts": [] + "scripts": [], + "serviceWorker": true, + "ngswConfigPath": "ngsw-config.json" }, "configurations": { "production": { @@ -38,7 +41,7 @@ { "type": "initial", "maximumWarning": "500kb", - "maximumError": "1mb" + "maximumError": "5mb" }, { "type": "anyComponentStyle", @@ -95,8 +98,9 @@ ], "tsConfig": "tsconfig.spec.json", "assets": [ - "src/favicon.ico", - "src/assets" + "src/assets/favicon.ico", + "src/assets", + "src/manifest.webmanifest" ], "styles": [ "@angular/material/prebuilt-themes/purple-green.css", diff --git a/OpenAlprWebhookProcessor/ClientApp/e2e/protractor.conf.js b/OpenAlprWebhookProcessor/angularapp/e2e/protractor.conf.js similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/e2e/protractor.conf.js rename to OpenAlprWebhookProcessor/angularapp/e2e/protractor.conf.js diff --git a/OpenAlprWebhookProcessor/ClientApp/e2e/src/app.e2e-spec.ts b/OpenAlprWebhookProcessor/angularapp/e2e/src/app.e2e-spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/e2e/src/app.e2e-spec.ts rename to OpenAlprWebhookProcessor/angularapp/e2e/src/app.e2e-spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/e2e/src/app.po.ts b/OpenAlprWebhookProcessor/angularapp/e2e/src/app.po.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/e2e/src/app.po.ts rename to OpenAlprWebhookProcessor/angularapp/e2e/src/app.po.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/e2e/tsconfig.json b/OpenAlprWebhookProcessor/angularapp/e2e/tsconfig.json similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/e2e/tsconfig.json rename to OpenAlprWebhookProcessor/angularapp/e2e/tsconfig.json diff --git a/OpenAlprWebhookProcessor/ClientApp/karma.conf.js b/OpenAlprWebhookProcessor/angularapp/karma.conf.js similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/karma.conf.js rename to OpenAlprWebhookProcessor/angularapp/karma.conf.js diff --git a/OpenAlprWebhookProcessor/angularapp/ngsw-config.json b/OpenAlprWebhookProcessor/angularapp/ngsw-config.json new file mode 100644 index 00000000..e3c0630f --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/ngsw-config.json @@ -0,0 +1,30 @@ +{ + "$schema": "./node_modules/@angular/service-worker/config/schema.json", + "index": "/index.html", + "assetGroups": [ + { + "name": "app", + "installMode": "prefetch", + "resources": { + "files": [ + "/assets/favicon.ico", + "/index.html", + "/manifest.webmanifest", + "/*.css", + "/*.js" + ] + } + }, + { + "name": "assets", + "installMode": "lazy", + "updateMode": "prefetch", + "resources": { + "files": [ + "/assets/**", + "/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)" + ] + } + } + ] +} diff --git a/OpenAlprWebhookProcessor/ClientApp/package-lock.json b/OpenAlprWebhookProcessor/angularapp/package-lock.json similarity index 96% rename from OpenAlprWebhookProcessor/ClientApp/package-lock.json rename to OpenAlprWebhookProcessor/angularapp/package-lock.json index 1c2acb26..92d95f58 100644 --- a/OpenAlprWebhookProcessor/ClientApp/package-lock.json +++ b/OpenAlprWebhookProcessor/angularapp/package-lock.json @@ -1,11 +1,11 @@ { - "name": "clientapp", + "name": "angularapp", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "clientapp", + "name": "angularapp", "version": "0.0.0", "dependencies": { "@angular/animations": "^16.2.0", @@ -18,6 +18,8 @@ "@angular/platform-browser": "^16.2.0", "@angular/platform-browser-dynamic": "^16.2.0", "@angular/router": "^16.2.0", + "@angular/service-worker": "^16.2.0", + "@microsoft/signalr": "^7.0.12", "@swimlane/ngx-charts": "^20.4.1", "jest-editor-support": "*", "ngx-highlightjs": "^10.0.0", @@ -65,12 +67,12 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1602.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.8.tgz", - "integrity": "sha512-bNdu2tF29Y/jOxMXlu9pmNbIlyZs9hRjLmi/tcfcMFay+3AhpNO59DWlUmI4gpvWu8CEXdQHSMuJTDHaNR+Ctg==", + "version": "0.1602.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.9.tgz", + "integrity": "sha512-U3vfb/e2sFfg0D9FyyRBXRPP7g4FBFtGK8Q3JPmvAVsHHwi5AUFRNR7YBChB/T5TMNY077HcTyEirVh2FeUpdA==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.8", + "@angular-devkit/core": "16.2.9", "rxjs": "7.8.1" }, "engines": { @@ -80,15 +82,15 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "16.2.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.2.8.tgz", - "integrity": "sha512-PgTaWerhDO3JjHjgJl/VWB1y1awN8eHrm7sqdpIsgKbVpi26oyByjtPS1gKKhinps9Che66lCbnxrkx2X3rWTg==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.2.9.tgz", + "integrity": "sha512-S1C4UYxRVyNt3C0wCxbT2jZ1dN5i37kS0mol3PQjbR8gQ0GQzHmzhjTBl1oImo8aouET9yhrk9etk65oat4mBQ==", "dev": true, "dependencies": { "@ampproject/remapping": "2.2.1", - "@angular-devkit/architect": "0.1602.8", - "@angular-devkit/build-webpack": "0.1602.8", - "@angular-devkit/core": "16.2.8", + "@angular-devkit/architect": "0.1602.9", + "@angular-devkit/build-webpack": "0.1602.9", + "@angular-devkit/core": "16.2.9", "@babel/core": "7.22.9", "@babel/generator": "7.22.9", "@babel/helper-annotate-as-pure": "7.22.5", @@ -100,7 +102,7 @@ "@babel/runtime": "7.22.6", "@babel/template": "7.22.5", "@discoveryjs/json-ext": "0.5.7", - "@ngtools/webpack": "16.2.8", + "@ngtools/webpack": "16.2.9", "@vitejs/plugin-basic-ssl": "1.0.1", "ansi-colors": "4.1.3", "autoprefixer": "10.4.14", @@ -208,12 +210,12 @@ "dev": true }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1602.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1602.8.tgz", - "integrity": "sha512-wGE2R6hnhSVpH7jvqtkZ63IX9oMRd+uh7sC65hGgzajPqThQcNdnGG3+79QGWapgkoHuZHpDlKOBFt0IOMAaMA==", + "version": "0.1602.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1602.9.tgz", + "integrity": "sha512-+3IxovfBPR2Vy730mGa0SVKkd5LQVom85gjXOs7WcnnnZmfc1q/BtFlqTgW1UWvTxP8IQdm7UYWVclQfL/WExw==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1602.8", + "@angular-devkit/architect": "0.1602.9", "rxjs": "7.8.1" }, "engines": { @@ -227,9 +229,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "16.2.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.8.tgz", - "integrity": "sha512-PTGozYvh1Bin5lB15PwcXa26Ayd17bWGLS3H8Rs0s+04mUDvfNofmweaX1LgumWWy3nCUTDuwHxX10M3G0wE2g==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.9.tgz", + "integrity": "sha512-dcHWjHBNGm3yCeNz19y8A1At4KgyC6XHNnbFL0y+nnZYiaESXjUoXJYKASedI6A+Bpl0HNq2URhH6bL6Af3+4w==", "dev": true, "dependencies": { "ajv": "8.12.0", @@ -254,12 +256,12 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "16.2.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.8.tgz", - "integrity": "sha512-MBiKZOlR9/YMdflALr7/7w/BGAfo/BGTrlkqsIB6rDWV1dYiCgxI+033HsiNssLS6RQyCFx/e7JA2aBBzu9zEg==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.9.tgz", + "integrity": "sha512-lB51CGCILpcSI37CwKUAGDLxMqh7zmuRbiPo9s9mSkCM4ccqxFlaL+VFTq2/laneARD6aikpOHnkVm5myNzQPw==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.8", + "@angular-devkit/core": "16.2.9", "jsonc-parser": "3.2.0", "magic-string": "0.30.1", "ora": "5.4.1", @@ -272,9 +274,9 @@ } }, "node_modules/@angular/animations": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.2.11.tgz", - "integrity": "sha512-xdLYXsGi7OuJawhiVIppl2VkPHhPdxUP/nR6+ETR3TdAscVruCWJs4z9XKval4fbik/brekbFNFuYtlx6csDhQ==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-16.2.12.tgz", + "integrity": "sha512-MD0ElviEfAJY8qMOd6/jjSSvtqER2RDAi0lxe6EtUacC1DHCYkaPrKW4vLqY+tmZBg1yf+6n+uS77pXcHHcA3w==", "dependencies": { "tslib": "^2.3.0" }, @@ -282,13 +284,13 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/core": "16.2.11" + "@angular/core": "16.2.12" } }, "node_modules/@angular/cdk": { - "version": "16.2.10", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-16.2.10.tgz", - "integrity": "sha512-kOQrPxSMPi66aM9XfwZIjQXhH+q0PkhK4BNMHB9RkvaaQ34ovOrKaGsT7t0+sjlVhiwTiy2mB1Qgz6NlIB0ZZw==", + "version": "16.2.11", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-16.2.11.tgz", + "integrity": "sha512-FcJ9xd9ptjULdScnBNg7YkVnY9NKePFfmvvs2zt841Hd489L8BUkTUdbvtCLhMJTTSN+k+D+RYFhevZuhPKVVg==", "dependencies": { "tslib": "^2.3.0" }, @@ -302,15 +304,15 @@ } }, "node_modules/@angular/cli": { - "version": "16.2.8", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.2.8.tgz", - "integrity": "sha512-iPrDv+SemRb6ZhayxwLsEdykHpV2TYSgH5Smg8GqSaIR/KUiemuzBrIKEUEaIG4n2dVEOtcsuh2JRHQndF7wmw==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.2.9.tgz", + "integrity": "sha512-wkpV/Ni26LUeDmhee2TPXXEq3feEdZMSG8+nkfUK9kqIcxm0IjI1GLPeiVOX7aQobuKNe2cCAFNwsrXWjj+2og==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1602.8", - "@angular-devkit/core": "16.2.8", - "@angular-devkit/schematics": "16.2.8", - "@schematics/angular": "16.2.8", + "@angular-devkit/architect": "0.1602.9", + "@angular-devkit/core": "16.2.9", + "@angular-devkit/schematics": "16.2.9", + "@schematics/angular": "16.2.9", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.3", "ini": "4.1.1", @@ -336,9 +338,9 @@ } }, "node_modules/@angular/common": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.2.11.tgz", - "integrity": "sha512-h80WUR2OYlqxQy+4XgNtWT2vB+vZ6oCrFX/q8cU5jAvbvGQfJuH0zfcbSlUflStmAhk5/OT25F0mt96cqapEAw==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.2.12.tgz", + "integrity": "sha512-B+WY/cT2VgEaz9HfJitBmgdk4I333XG/ybC98CMC4Wz8E49T8yzivmmxXB3OD6qvjcOB6ftuicl6WBqLbZNg2w==", "dependencies": { "tslib": "^2.3.0" }, @@ -346,14 +348,14 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/core": "16.2.11", + "@angular/core": "16.2.12", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.2.11.tgz", - "integrity": "sha512-9q/E3uurvoQbdTTWDyWCLpzmfJ4+et7SUca1/EljD/X7Xg2FNU5GpTMutBtWFL7wDyWk1oswivuq9/C4GVW7fA==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.2.12.tgz", + "integrity": "sha512-6SMXUgSVekGM7R6l1Z9rCtUGtlg58GFmgbpMCsGf+VXxP468Njw8rjT2YZkf5aEPxEuRpSHhDYjqz7n14cwCXQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -361,7 +363,7 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/core": "16.2.11" + "@angular/core": "16.2.12" }, "peerDependenciesMeta": { "@angular/core": { @@ -370,9 +372,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.2.11.tgz", - "integrity": "sha512-ZtZCXfkVBH78HUm2Byf+WX3Y6WzQK9EXYXNU/ni1rvSZ1vLNwieLDfWb/xwiO7QojrHZTym1RJ10jTMinTguqw==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.2.12.tgz", + "integrity": "sha512-pWSrr152562ujh6lsFZR8NfNc5Ljj+zSTQO44DsuB0tZjwEpnRcjJEgzuhGXr+CoiBf+jTSPZKemtSktDk5aaA==", "dev": true, "dependencies": { "@babel/core": "7.23.2", @@ -393,7 +395,7 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/compiler": "16.2.11", + "@angular/compiler": "16.2.12", "typescript": ">=4.9.3 <5.2" } }, @@ -472,9 +474,9 @@ } }, "node_modules/@angular/core": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.2.11.tgz", - "integrity": "sha512-Jb+7/p1vczQRQ3iC1QxUS5cE4X1hPVAvbrFnyMpSx6Pq5o274v/lK6PvhUZrfKrp9FxFp9pN+WHjUqNFqOuJZg==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.2.12.tgz", + "integrity": "sha512-GLLlDeke/NjroaLYOks0uyzFVo6HyLl7VOm0K1QpLXnYvW63W9Ql/T3yguRZa7tRkOAeFZ3jw+1wnBD4O8MoUA==", "dependencies": { "tslib": "^2.3.0" }, @@ -487,9 +489,9 @@ } }, "node_modules/@angular/forms": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.11.tgz", - "integrity": "sha512-2powweUorehB1opfev6/sUeb3Bdey+Txq4gjI1Qdeo9c9OgtaKu6wK0KXgoism8HXXRFcGHMfS0dUVoDPVrtiQ==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.12.tgz", + "integrity": "sha512-1Eao89hlBgLR3v8tU91vccn21BBKL06WWxl7zLpQmG6Hun+2jrThgOE4Pf3os4fkkbH4Apj0tWL2fNIWe/blbw==", "dependencies": { "tslib": "^2.3.0" }, @@ -497,16 +499,16 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/common": "16.2.11", - "@angular/core": "16.2.11", - "@angular/platform-browser": "16.2.11", + "@angular/common": "16.2.12", + "@angular/core": "16.2.12", + "@angular/platform-browser": "16.2.12", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/material": { - "version": "16.2.10", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-16.2.10.tgz", - "integrity": "sha512-0XhMwbcxpEESL11mVO8ycwxa+Jlh+8egOSRleD30zFUesqBA5EhtRpH8cqtna03f/xxtRq00Q315igMIMNiOSg==", + "version": "16.2.11", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-16.2.11.tgz", + "integrity": "sha512-hrkRD9/38++nIyo3k/KQpxsIaWm+FOJVmoJa83qvwZZt+fHKfT7xaNvRPZ+L2oqFaAvH5ivnL1u1nDuDyWz/0w==", "dependencies": { "@material/animation": "15.0.0-canary.bc9ae6c9c.0", "@material/auto-init": "15.0.0-canary.bc9ae6c9c.0", @@ -559,7 +561,7 @@ }, "peerDependencies": { "@angular/animations": "^16.0.0 || ^17.0.0", - "@angular/cdk": "16.2.10", + "@angular/cdk": "16.2.11", "@angular/common": "^16.0.0 || ^17.0.0", "@angular/core": "^16.0.0 || ^17.0.0", "@angular/forms": "^16.0.0 || ^17.0.0", @@ -568,9 +570,9 @@ } }, "node_modules/@angular/platform-browser": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.11.tgz", - "integrity": "sha512-gUptbI3lbaRg+L8rcTlxKtFunYmR/M/mm9/l9uRd+5qk2mnFI0+s/tzRoaq7K0XaRGKZiWLNTz6FTkviO1zo2g==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.12.tgz", + "integrity": "sha512-NnH7ju1iirmVEsUq432DTm0nZBGQsBrU40M3ZeVHMQ2subnGiyUs3QyzDz8+VWLL/T5xTxWLt9BkDn65vgzlIQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -578,9 +580,9 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/animations": "16.2.11", - "@angular/common": "16.2.11", - "@angular/core": "16.2.11" + "@angular/animations": "16.2.12", + "@angular/common": "16.2.12", + "@angular/core": "16.2.12" }, "peerDependenciesMeta": { "@angular/animations": { @@ -589,9 +591,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-16.2.11.tgz", - "integrity": "sha512-e+A7z6MUJaqC4Fdq7XQfIhAox3ZPM1lczM6G08fUKPbFDEe+c9i7C8YRLL+69BXDuG790btugIeOQcn5lnJcFg==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-16.2.12.tgz", + "integrity": "sha512-ya54jerNgreCVAR278wZavwjrUWImMr2F8yM5n9HBvsMBbFaAQ83anwbOEiHEF2BlR+gJiEBLfpuPRMw20pHqw==", "dependencies": { "tslib": "^2.3.0" }, @@ -599,16 +601,16 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/common": "16.2.11", - "@angular/compiler": "16.2.11", - "@angular/core": "16.2.11", - "@angular/platform-browser": "16.2.11" + "@angular/common": "16.2.12", + "@angular/compiler": "16.2.12", + "@angular/core": "16.2.12", + "@angular/platform-browser": "16.2.12" } }, "node_modules/@angular/router": { - "version": "16.2.11", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-16.2.11.tgz", - "integrity": "sha512-QTssqJue+xQ8M1gzmfJcIHPIpPOijVwGnXQjt7cnFggNe/CedOckLEzk2j7/6aC1b5aQKuZePPw6XMvk8ciilQ==", + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-16.2.12.tgz", + "integrity": "sha512-aU6QnYSza005V9P3W6PpkieL56O0IHps96DjqI1RS8yOJUl3THmokqYN4Fm5+HXy4f390FN9i6ftadYQDKeWmA==", "dependencies": { "tslib": "^2.3.0" }, @@ -616,12 +618,30 @@ "node": "^16.14.0 || >=18.10.0" }, "peerDependencies": { - "@angular/common": "16.2.11", - "@angular/core": "16.2.11", - "@angular/platform-browser": "16.2.11", + "@angular/common": "16.2.12", + "@angular/core": "16.2.12", + "@angular/platform-browser": "16.2.12", "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/service-worker": { + "version": "16.2.12", + "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-16.2.12.tgz", + "integrity": "sha512-o0z0s4c76NmRASa+mUHn/q6vUKQNa06mGmLBDKm84vRQ1sQ2TJv+R1p8K9WkiM5mGy6tjQCDOgaz13TcxMFWOQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "bin": { + "ngsw-config": "ngsw-config.js" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/common": "16.2.12", + "@angular/core": "16.2.12" + } + }, "node_modules/@assemblyscript/loader": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", @@ -2439,6 +2459,342 @@ "node": ">=10.0.0" } }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.17.tgz", + "integrity": "sha512-wHsmJG/dnL3OkpAcwbgoBTTMHVi4Uyou3F5mf58ZtmUyIKfcdA7TROav/6tCzET4A3QW2Q2FC+eFneMU+iyOxg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.17.tgz", + "integrity": "sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.17.tgz", + "integrity": "sha512-O+FeWB/+xya0aLg23hHEM2E3hbfwZzjqumKMSIqcHbNvDa+dza2D0yLuymRBQQnC34CWrsJUXyH2MG5VnLd6uw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.17.tgz", + "integrity": "sha512-M9uJ9VSB1oli2BE/dJs3zVr9kcCBBsE883prage1NWz6pBS++1oNn/7soPNS3+1DGj0FrkSvnED4Bmlu1VAE9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.17.tgz", + "integrity": "sha512-XDre+J5YeIJDMfp3n0279DFNrGCXlxOuGsWIkRb1NThMZ0BsrWXoTg23Jer7fEXQ9Ye5QjrvXpxnhzl3bHtk0g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.17.tgz", + "integrity": "sha512-cjTzGa3QlNfERa0+ptykyxs5A6FEUQQF0MuilYXYBGdBxD3vxJcKnzDlhDCa1VAJCmAxed6mYhA2KaJIbtiNuQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.17.tgz", + "integrity": "sha512-sOxEvR8d7V7Kw8QqzxWc7bFfnWnGdaFBut1dRUYtu+EIRXefBc/eIsiUiShnW0hM3FmQ5Zf27suDuHsKgZ5QrA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.17.tgz", + "integrity": "sha512-2d3Lw6wkwgSLC2fIvXKoMNGVaeY8qdN0IC3rfuVxJp89CRfA3e3VqWifGDfuakPmp90+ZirmTfye1n4ncjv2lg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.17.tgz", + "integrity": "sha512-c9w3tE7qA3CYWjT+M3BMbwMt+0JYOp3vCMKgVBrCl1nwjAlOMYzEo+gG7QaZ9AtqZFj5MbUc885wuBBmu6aADQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.17.tgz", + "integrity": "sha512-1DS9F966pn5pPnqXYz16dQqWIB0dmDfAQZd6jSSpiT9eX1NzKh07J6VKR3AoXXXEk6CqZMojiVDSZi1SlmKVdg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.17.tgz", + "integrity": "sha512-EvLsxCk6ZF0fpCB6w6eOI2Fc8KW5N6sHlIovNe8uOFObL2O+Mr0bflPHyHwLT6rwMg9r77WOAWb2FqCQrVnwFg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.17.tgz", + "integrity": "sha512-e0bIdHA5p6l+lwqTE36NAW5hHtw2tNRmHlGBygZC14QObsA3bD4C6sXLJjvnDIjSKhW1/0S3eDy+QmX/uZWEYQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.17.tgz", + "integrity": "sha512-BAAilJ0M5O2uMxHYGjFKn4nJKF6fNCdP1E0o5t5fvMYYzeIqy2JdAP88Az5LHt9qBoUa4tDaRpfWt21ep5/WqQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.17.tgz", + "integrity": "sha512-Wh/HW2MPnC3b8BqRSIme/9Zhab36PPH+3zam5pqGRH4pE+4xTrVLx2+XdGp6fVS3L2x+DrsIcsbMleex8fbE6g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.17.tgz", + "integrity": "sha512-j/34jAl3ul3PNcK3pfI0NSlBANduT2UO5kZ7FCaK33XFv3chDhICLY8wJJWIhiQ+YNdQ9dxqQctRg2bvrMlYgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.17.tgz", + "integrity": "sha512-QM50vJ/y+8I60qEmFxMoxIx4de03pGo2HwxdBeFd4nMh364X6TIBZ6VQ5UQmPbQWUVWHWws5MmJXlHAXvJEmpQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.17.tgz", + "integrity": "sha512-/jGlhWR7Sj9JPZHzXyyMZ1RFMkNPjC6QIAan0sDOtIo2TYk3tZn5UDrkE0XgsTQCxWTTOcMPf9p6Rh2hXtl5TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.17.tgz", + "integrity": "sha512-rSEeYaGgyGGf4qZM2NonMhMOP/5EHp4u9ehFiBrg7stH6BYEEjlkVREuDEcQ0LfIl53OXLxNbfuIj7mr5m29TA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.17.tgz", + "integrity": "sha512-Y7ZBbkLqlSgn4+zot4KUNYst0bFoO68tRgI6mY2FIM+b7ZbyNVtNbDP5y8qlu4/knZZ73fgJDlXID+ohY5zt5g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.17.tgz", + "integrity": "sha512-bwPmTJsEQcbZk26oYpc4c/8PvTY3J5/QK8jM19DVlEsAB41M39aWovWoHtNm78sd6ip6prilxeHosPADXtEJFw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.17.tgz", + "integrity": "sha512-H/XaPtPKli2MhW+3CQueo6Ni3Avggi6hP/YvgkEe1aSaxw+AeO8MFjq8DlgfTd9Iz4Yih3QCZI6YLMoyccnPRg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/win32-x64": { "version": "0.18.17", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.17.tgz", @@ -2480,9 +2836,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -2570,9 +2926,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", - "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3744,9 +4100,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "16.2.8", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.2.8.tgz", - "integrity": "sha512-GeblhLBwXe3qPYa4YHxbo0xujRl1FKkfIusU1mTIhkQBRtZY4Xgz4iMnPIEMJTU3XXGMkS+SCx34lqbwwMhR5A==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.2.9.tgz", + "integrity": "sha512-rOclD7FfT4OSwVA0nDnULbJS6TORJ0+sQiuT2ebaNFErYr3LOm6Zut05tnmzFw8q1cePrILbG+xpnbggNr9Pyw==", "dev": true, "engines": { "node": "^16.14.0 || >=18.10.0", @@ -3969,13 +4325,13 @@ } }, "node_modules/@schematics/angular": { - "version": "16.2.8", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.2.8.tgz", - "integrity": "sha512-yxfxJ2IMRIt+dQcqyJR30qd/osb5NwRsi9US3gFIHP1jfjOAs1Nk8ENNd5ycYV+yykCa78KWhmbOw4G1zpR56Q==", + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.2.9.tgz", + "integrity": "sha512-uiU2YbZRVHgk1N1DDsek/5CKhfpZ8myJYNJk8eHV5LswnXOP3aqvH23VhneaAgOYwK5fISC7eMG0pLVKMvFfZQ==", "dev": true, "dependencies": { - "@angular-devkit/core": "16.2.8", - "@angular-devkit/schematics": "16.2.8", + "@angular-devkit/core": "16.2.9", + "@angular-devkit/schematics": "16.2.9", "jsonc-parser": "3.2.0" }, "engines": { @@ -4290,9 +4646,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", - "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.4.tgz", + "integrity": "sha512-2JwWnHK9H+wUZNorf2Zr6ves96WHoWDJIftkcxPKsS7Djta6Zu519LarhRNljPXkpsZR2ZMwNCPeW7omW07BJw==", "dev": true }, "node_modules/@types/express": { @@ -4388,9 +4744,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.8.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz", - "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==", + "version": "20.8.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", + "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", "dependencies": { "undici-types": "~5.26.4" } @@ -5572,9 +5928,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001557", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001557.tgz", - "integrity": "sha512-91oR7hLNUP3gG6MLU+n96em322a8Xzes8wWdBKhLgUoiJsAF5irZnxSUCbc+qUZXNnPCfUwLOi9ZCZpkvjQajw==", + "version": "1.0.30001561", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001561.tgz", + "integrity": "sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw==", "funding": [ { "type": "opencollective", @@ -5999,9 +6355,9 @@ } }, "node_modules/core-js": { - "version": "3.33.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.1.tgz", - "integrity": "sha512-qVSq3s+d4+GsqN0teRCJtM6tdEEXyWxjzbhVrCHmBS5ZTM0FS2MOS0D13dUXAWDUN6a+lHI/N1hF9Ytz6iLl9Q==", + "version": "3.33.2", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.2.tgz", + "integrity": "sha512-XeBzWI6QL3nJQiHmdzbAOiMYqjrb7hwU7A39Qhvd/POSa/t9E1AeZyEZx3fNvp/vtM8zXwhoL0FsiS0hD0pruQ==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -6009,9 +6365,9 @@ } }, "node_modules/core-js-compat": { - "version": "3.33.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.1.tgz", - "integrity": "sha512-6pYKNOgD/j/bkC5xS5IIg6bncid3rfrI42oBH1SQJbsmYPKF7rhzcFzYCcxYMmNQQ0rCEB8WqpW7QHndOggaeQ==", + "version": "3.33.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.2.tgz", + "integrity": "sha512-axfo+wxFVxnqf8RvxTzoAlzW4gRoacrHeoFlc9n0x50+7BEyZL/Rt3hicaED1/CEd7I6tPCPVUYcJwCMO5XUYw==", "dev": true, "dependencies": { "browserslist": "^4.22.1" @@ -6770,9 +7126,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.569", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.569.tgz", - "integrity": "sha512-LsrJjZ0IbVy12ApW3gpYpcmHS3iRxH4bkKOW98y1/D+3cvDUWGcbzbsFinfUS8knpcZk/PG/2p/RnkMCYN7PVg==" + "version": "1.4.576", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.576.tgz", + "integrity": "sha512-yXsZyXJfAqzWk1WKryr0Wl0MN2D47xodPvEEwlVePBnhU5E7raevLQR+E6b9JAD3GfL/7MbAL9ZtWQQPcLx7wA==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -7047,15 +7403,15 @@ } }, "node_modules/eslint": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", - "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.52.0", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -8015,6 +8371,19 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -9242,38 +9611,18 @@ } }, "node_modules/jest-editor-support": { - "version": "31.1.1", - "resolved": "https://registry.npmjs.org/jest-editor-support/-/jest-editor-support-31.1.1.tgz", - "integrity": "sha512-bCKpKWAMLhjK7QDX9geBDzfyvZacl937hnT3Z+yfERBx8bB7yZg2uhvCBErqO51Ie+wgg42Ffo8rn+Nhdr2hHQ==", + "version": "31.1.2", + "resolved": "https://registry.npmjs.org/jest-editor-support/-/jest-editor-support-31.1.2.tgz", + "integrity": "sha512-QlCN8dWVxMcmvzbkH2G1gSNjMPFM+69+Lx9WZcpYiHEaqrb1QKuVJrVNfbjHrlv0PhgCvzf1EdYKh8qqrQ3Rhw==", "dependencies": { "@babel/parser": "^7.20.7", "@babel/runtime": "^7.20.7", - "@babel/traverse": "7.20.10", + "@babel/traverse": "7.23.2", "@babel/types": "^7.20.7", "core-js": "^3.17.3", "jest-snapshot": "^27.2.0" } }, - "node_modules/jest-editor-support/node_modules/@babel/traverse": { - "version": "7.20.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.10.tgz", - "integrity": "sha512-oSf1juCgymrSez8NI4A2sr4+uB/mFd9MXplYGPEBnfAuWmmyeVcHa6xLPiaRBcXkcb/28bgxmQLTVwFKE1yfsg==", - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.7", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/jest-get-type": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", @@ -9708,9 +10057,9 @@ } }, "node_modules/jiti": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz", - "integrity": "sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", "dev": true, "bin": { "jiti": "bin/jiti.js" @@ -11133,6 +11482,28 @@ "ngx-filesaver": "14.0.0" } }, + "node_modules/nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "!win32" + ], + "dependencies": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "optional": true + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -11205,6 +11576,18 @@ "node": "^12.13 || ^14.13 || >=16" } }, + "node_modules/node-gyp-build": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz", + "integrity": "sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==", + "dev": true, + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -12348,9 +12731,9 @@ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "engines": { "node": ">=6" } diff --git a/OpenAlprWebhookProcessor/ClientApp/package.json b/OpenAlprWebhookProcessor/angularapp/package.json similarity index 90% rename from OpenAlprWebhookProcessor/ClientApp/package.json rename to OpenAlprWebhookProcessor/angularapp/package.json index 0bbe1d85..af2b9220 100644 --- a/OpenAlprWebhookProcessor/ClientApp/package.json +++ b/OpenAlprWebhookProcessor/angularapp/package.json @@ -1,9 +1,9 @@ { - "name": "clientapp", + "name": "angularapp", "version": "0.0.0", "scripts": { "ng": "ng", - "start": "ng serve --open", + "start": "ng serve", "build": "ng build", "test": "ng test", "lint": "ng lint", @@ -21,6 +21,8 @@ "@angular/platform-browser": "^16.2.0", "@angular/platform-browser-dynamic": "^16.2.0", "@angular/router": "^16.2.0", + "@angular/service-worker": "^16.2.0", + "@microsoft/signalr": "^7.0.12", "@swimlane/ngx-charts": "^20.4.1", "jest-editor-support": "*", "ngx-highlightjs": "^10.0.0", diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/_components/alert.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/_components/alert.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/_components/alert.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/_components/alert.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/_components/alert.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_components/alert.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/_components/alert.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/_components/alert.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/_components/index.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_components/index.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/_components/index.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/_components/index.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/_helpers/app.initializer.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_helpers/app.initializer.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/_helpers/app.initializer.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/_helpers/app.initializer.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/_helpers/auth.guard.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_helpers/auth.guard.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/_helpers/auth.guard.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/_helpers/auth.guard.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/_helpers/error.interceptor.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_helpers/error.interceptor.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/_helpers/error.interceptor.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/_helpers/error.interceptor.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/_helpers/index.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_helpers/index.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/_helpers/index.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/_helpers/index.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/_helpers/jwt.interceptor.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_helpers/jwt.interceptor.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/_helpers/jwt.interceptor.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/_helpers/jwt.interceptor.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/_models/alert.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_models/alert.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/_models/alert.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/_models/alert.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/_models/index.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_models/index.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/_models/index.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/_models/index.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/_models/user.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_models/user.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/_models/user.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/_models/user.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/_services/account.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_services/account.service.ts similarity index 92% rename from OpenAlprWebhookProcessor/ClientApp/src/app/_services/account.service.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/_services/account.service.ts index 33b611d4..c4efb819 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/_services/account.service.ts +++ b/OpenAlprWebhookProcessor/angularapp/src/app/_services/account.service.ts @@ -22,7 +22,7 @@ export class AccountService { return this.userSubject.value; } - login(username, password) { + login(username: string, password: string) { return this.http.post(`/users/authenticate`, { username, password }) .pipe(map(user => { // store user details and jwt token in local storage to keep user logged in between page refreshes @@ -34,10 +34,21 @@ export class AccountService { } logout() { - this.http.post(`/users/revoke-token`, {}, { withCredentials: true }).subscribe(); this.stopRefreshTokenTimer(); + + this.http.post(`/users/revoke-token`, {}, { withCredentials: true }).subscribe(() => { + this.finalizeLogout(); + }, + (error) => { + this.finalizeLogout(); + }); + } + + finalizeLogout() { + this.userValue.jwtToken = ''; localStorage.removeItem('user'); this.router.navigate(['/account/login']); + this.userSubject.next(new User()); } refreshToken() { diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/_services/alert.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_services/alert.service.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/_services/alert.service.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/_services/alert.service.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/_services/index.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_services/index.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/_services/index.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/_services/index.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/_services/local-storage.service.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_services/local-storage.service.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/_services/local-storage.service.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/_services/local-storage.service.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/_services/local-storage.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_services/local-storage.service.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/_services/local-storage.service.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/_services/local-storage.service.ts diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/_services/nav-bar.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_services/nav-bar.service.ts new file mode 100644 index 00000000..54b3ab5c --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/app/_services/nav-bar.service.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class NavBarService { + public settingsButtonClicked: BehaviorSubject = new BehaviorSubject(false); + + public settingsClicked() { + this.settingsButtonClicked.next(true); + } +} \ No newline at end of file diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/_services/push-subscriber.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/_services/push-subscriber.service.ts new file mode 100644 index 00000000..e2dfa45a --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/app/_services/push-subscriber.service.ts @@ -0,0 +1,65 @@ +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { SwPush } from '@angular/service-worker'; + +@Injectable({ providedIn: 'root' }) +export class PushSubscriberService { + private _subscription: PushSubscription; + private baseUrl: string = document.getElementsByTagName('base')[0].href; + + readonly httpOptions = { + headers: new HttpHeaders({ + 'Content-Type': 'application/json' + }) + }; + + constructor( + private swPush: SwPush, + private httpClient: HttpClient) { + swPush.subscription.subscribe((subscription) => { + this._subscription = subscription!; + }); + } + + public subscribe() { + this.httpClient.get(this.baseUrl + 'api/WebPushPublicKey', { responseType: 'text' }).subscribe(publicKey => { + this.swPush.requestSubscription({ + serverPublicKey: publicKey + }) + .then(subscription => this.httpClient.post(this.baseUrl + 'api/WebPushSubscriptions', subscription, this.httpOptions).subscribe( + success => { + console.log("sent subscription to server."); + }, + error => console.error(error) + )) + .catch(error => { + this.resetSubscription(); + }); + }, error => console.error(error)); + }; + + public unsubscribe() { + this.swPush.unsubscribe() + .then(() => this.httpClient.delete(this.baseUrl + 'api/WebPushSubscriptions/' + encodeURIComponent(this._subscription.endpoint)).subscribe( + () => { }, + error => console.error(error) + )) + .catch(error => console.error(error)); + } + + public resetSubscription() { + navigator.serviceWorker.ready.then(registration => { + registration.pushManager.getSubscription() + .then(pushSubscription => { + if(pushSubscription){ + pushSubscription.unsubscribe().then(successful => { + // You've successfully unsubscribed + this.subscribe(); + }).catch(e => { + // Unsubscription failed + }) + } + }); + }); + } +} \ No newline at end of file diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/account/account-routing.module.ts b/OpenAlprWebhookProcessor/angularapp/src/app/account/account-routing.module.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/account/account-routing.module.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/account/account-routing.module.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/account/account.module.ts b/OpenAlprWebhookProcessor/angularapp/src/app/account/account.module.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/account/account.module.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/account/account.module.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/account/layout.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/account/layout.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/account/layout.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/account/layout.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/account/layout.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/account/layout.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/account/layout.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/account/layout.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/account/login.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/account/login.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/account/login.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/account/login.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/account/login.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/account/login.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/account/login.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/account/login.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/account/register.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/account/register.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/account/register.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/account/register.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/account/register.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/account/register.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/account/register.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/account/register.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/app-routing.module.ts b/OpenAlprWebhookProcessor/angularapp/src/app/app-routing.module.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/app-routing.module.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/app-routing.module.ts diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/app.component.css b/OpenAlprWebhookProcessor/angularapp/src/app/app.component.css new file mode 100644 index 00000000..b82fa477 --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/app/app.component.css @@ -0,0 +1,15 @@ +@media only screen and (max-width: 750px) { + span { + display: none; + } + + .home-button { + display: none; + } +} + +@media only screen and (min-width: 751px) { + .menu-button { + display: none; + } +} \ No newline at end of file diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/app.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/app.component.html new file mode 100644 index 00000000..e043c78a --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/app/app.component.html @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/app.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/app.component.ts new file mode 100644 index 00000000..0b227fdc --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/app/app.component.ts @@ -0,0 +1,77 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { AccountService } from './_services'; +import { User } from './_models'; +import { SignalrService } from './signalr/signalr.service'; +import { NavigationEnd, Router } from '@angular/router'; +import { NavBarService } from './_services/nav-bar.service'; +import { SwUpdate, VersionEvent, VersionReadyEvent } from '@angular/service-worker'; +import { PushSubscriberService } from './_services/push-subscriber.service'; + +@Component({ + selector: 'app', + templateUrl: 'app.component.html', + styleUrls: ['app.component.css'] +}) +export class AppComponent implements OnInit, OnDestroy { + user: User; + appSettingsVisible: boolean; + menuButtonVisible: boolean; + topBarVisible: boolean; + + constructor( + private signalRService: SignalrService, + private accountService: AccountService, + private navBarService: NavBarService, + private router: Router, + private swUpdate: SwUpdate, + private pushSubscriberService: PushSubscriberService) { + this.accountService.user.subscribe(x => { + this.topBarVisible = x.id !== undefined; + }); + + this.router.events.subscribe((routerEvent) => { + if(routerEvent instanceof(NavigationEnd)) { + this.menuButtonVisible =(routerEvent as NavigationEnd).url == "/settings"; + } + }); + + this.swUpdate.unrecoverable.subscribe(event => { + confirm('An error occurred, please reload the page.') + { + window.location.reload(); + } + }); + + if (this.swUpdate.isEnabled) { + this.swUpdate.versionUpdates.subscribe((event: VersionEvent) => { + switch (event.type) { + case 'VERSION_READY': + if (confirm("You're using an old version of the control panel. Want to update?")) { + window.location.reload(); + } + break; + case 'VERSION_INSTALLATION_FAILED': + console.log(`Failed to install app version '${event.version.hash}': ${event.error}`); + break; + } + }); + } + } + + public ngOnInit() { + this.signalRService.startConnection(); + this.pushSubscriberService.subscribe(); + } + + public ngOnDestroy() { + this.signalRService.stopConnection(); + } + + public logout() { + this.accountService.logout(); + } + + public settingsButtonClicked() { + this.navBarService.settingsClicked(); + } +} diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/app.module.ts b/OpenAlprWebhookProcessor/angularapp/src/app/app.module.ts similarity index 88% rename from OpenAlprWebhookProcessor/ClientApp/src/app/app.module.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/app.module.ts index 71cce123..29d5980f 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/app.module.ts +++ b/OpenAlprWebhookProcessor/angularapp/src/app/app.module.ts @@ -1,4 +1,4 @@ -import { APP_INITIALIZER, NgModule } from '@angular/core'; +import { APP_INITIALIZER, NgModule, isDevMode } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; @@ -30,6 +30,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { NgxChartsModule } from '@swimlane/ngx-charts'; import { HighlightModule, HIGHLIGHT_OPTIONS } from 'ngx-highlightjs'; import { DatePipe } from '@angular/common'; +import { ServiceWorkerModule } from '@angular/service-worker'; @NgModule({ imports: [ @@ -56,6 +57,12 @@ import { DatePipe } from '@angular/common'; MatExpansionModule, NgxChartsModule, HighlightModule, + ServiceWorkerModule.register('ngsw-worker.js', { + enabled: !isDevMode(), + // Register the ServiceWorker as soon as the application is stable + // or after 30 seconds (whichever comes first). + registrationStrategy: 'registerWhenStable:30' + }), ], declarations: [ AppComponent, diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/fast-search/fast-search.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/fast-search/fast-search.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/fast-search/fast-search.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/fast-search/fast-search.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/fast-search/fast-search.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/fast-search/fast-search.component.less similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/fast-search/fast-search.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/fast-search/fast-search.component.less diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/fast-search/fast-search.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/fast-search/fast-search.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/fast-search/fast-search.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/fast-search/fast-search.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/fast-search/fast-search.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/fast-search/fast-search.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/fast-search/fast-search.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/fast-search/fast-search.component.ts diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/home/home.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/home/home.component.html new file mode 100644 index 00000000..d2d60d53 --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/app/home/home.component.html @@ -0,0 +1,25 @@ +
+ + + Plates Seen + Last 7 days + + +
+ +
+
+
+
diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/home/home.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/home/home.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/home/home.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/home/home.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/home/home.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/home/home.service.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/home/home.service.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/home/home.service.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/home/index.ts b/OpenAlprWebhookProcessor/angularapp/src/app/home/index.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/home/index.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/home/index.ts diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/home/plateCountResponse.ts b/OpenAlprWebhookProcessor/angularapp/src/app/home/plateCountResponse.ts new file mode 100644 index 00000000..f6d0228c --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/app/home/plateCountResponse.ts @@ -0,0 +1,9 @@ +export interface DayCounts { + counts: DayCount[]; + weeklyUniqueCounts: DayCount[]; +} + +export interface DayCount { + date: Date; + count: number; +} diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/edit-plate/edit-plate.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/plates/edit-plate/edit-plate.component.html similarity index 54% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/edit-plate/edit-plate.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/edit-plate/edit-plate.component.html index 9ef3950b..94b8afda 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/edit-plate/edit-plate.component.html +++ b/OpenAlprWebhookProcessor/angularapp/src/app/plates/edit-plate/edit-plate.component.html @@ -1,11 +1,9 @@

Edit Plate

-
- - Plate Number - - -
+ + Plate Number + +
diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/edit-plate/edit-plate.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/plates/edit-plate/edit-plate.component.less similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/edit-plate/edit-plate.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/edit-plate/edit-plate.component.less diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/edit-plate/edit-plate.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/plates/edit-plate/edit-plate.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/edit-plate/edit-plate.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/edit-plate/edit-plate.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/edit-plate/edit-plate.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/plates/edit-plate/edit-plate.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/edit-plate/edit-plate.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/edit-plate/edit-plate.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/layout.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/plates/layout.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/layout.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/layout.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/layout.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/plates/layout.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/layout.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/layout.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plate.service.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate.service.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/plate.service.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plate.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plate.component.html similarity index 82% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plate.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plate.component.html index fcf91ef4..3a3db24a 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plate.component.html +++ b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plate.component.html @@ -1,4 +1,4 @@ -
+
Images @@ -9,16 +9,16 @@ color="primary" mode="indeterminate" diameter="100" - [hidden]="!loadingImage"> - + - help_center - - help_center diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plate.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plate.component.less similarity index 80% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plate.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plate.component.less index 7482ea45..42fb5e5a 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plate.component.less +++ b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plate.component.less @@ -24,10 +24,19 @@ .resize-mobile { max-width:100%; } + + .mobilePlateContainer { + flex-direction: column; + } } + @media only screen and (min-width: 1027px) { .resize-mobile { max-height: 300px; max-width: 500px; } + + .mobilePlateContainer { + flex-direction: row; + } } \ No newline at end of file diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plate.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plate.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plate.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plate.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plate.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plate.component.ts similarity index 57% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plate.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plate.component.ts index bd1e8997..f730b5c7 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plate.component.ts +++ b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plate.component.ts @@ -1,5 +1,5 @@ import { DatePipe } from '@angular/common'; -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core'; import { SnackbarService } from 'app/snackbar/snackbar.service'; import { SnackBarType } from 'app/snackbar/snackbartype'; import { Lightbox } from 'ngx-lightbox'; @@ -7,19 +7,27 @@ import { Url } from 'url'; import { PlateService } from '../plate.service'; import { Plate } from './plate'; import { PlateStatisticsData } from './plateStatistics'; +import { Subscription } from 'rxjs'; @Component({ selector: 'app-plate', templateUrl: './plate.component.html', styleUrls: ['./plate.component.less'] }) -export class PlateComponent implements OnInit { +export class PlateComponent implements OnInit, OnChanges, OnDestroy { @Input() plate: Plate; + @Input() isVisible: boolean; + + public isInitialized: boolean; + + public loadingVehicleImage: boolean; + public vehicleImageUrl: string; + public loadingVehicleImageFailed: boolean; - public loadingImage: boolean; - public loadingImageFailed: boolean; public loadingPlateImage: boolean; public loadingPlateImageFailed: boolean; + public plateImageUrl: string; + public loadingStatistics: boolean; public loadingStatisticsFailed: boolean; public isSavingNotes: boolean; @@ -27,6 +35,8 @@ export class PlateComponent implements OnInit { public plateStatistics: PlateStatisticsData[] = []; public displayedColumns: string[] = ['key', 'value']; + private statisticsSubscription = new Subscription(); + constructor( private lightbox: Lightbox, private plateService: PlateService, @@ -35,15 +45,51 @@ export class PlateComponent implements OnInit { ) { } ngOnInit(): void { - this.loadingImage = true; + this.loadingVehicleImage = true; this.loadingPlateImage = true; this.getPlateStatistics(); + this.getPlateImages(); + this.isInitialized = true; + } + + ngOnChanges(): void { + if(this.isInitialized) { + if(!this.isVisible) { + this.loadingStatistics = false; + this.loadingStatisticsFailed = false; + this.statisticsSubscription.unsubscribe(); + + if(this.loadingVehicleImage) { + this.vehicleImageUrl = ''; + } + + if(this.loadingPlateImage) { + this.plateImageUrl = ''; + } + } + else { + if(this.plateStatistics.length == 0) { + this.getPlateStatistics(); + } + + this.getPlateImages(); + } + } + } + + ngOnDestroy(): void { + this.statisticsSubscription.unsubscribe(); + this.vehicleImageUrl = ''; + this.plateImageUrl = ''; } private getPlateStatistics() { this.loadingStatistics = true; - this.plateService.getPlateStatistics(this.plate.plateNumber).subscribe(result => { + this.statisticsSubscription.closed = false; + this.statisticsSubscription.add(this.plateService.getPlateStatistics(this.plate.plateNumber).subscribe(result => { this.loadingStatistics = false; + this.loadingStatisticsFailed = false; + this.plateStatistics.push({ key: "Confidence", value: this.plate.processedPlateConfidence + "%", @@ -87,7 +133,21 @@ export class PlateComponent implements OnInit { error => { this.loadingStatistics = false; this.loadingStatisticsFailed = true; - }); + })); + } + + private getPlateImages() { + if(!this.vehicleImageUrl) { + this.loadingVehicleImage = true; + this.loadingVehicleImageFailed = false; + this.vehicleImageUrl = this.plate.imageUrl.toString(); + } + + if(!this.plateImageUrl) { + this.loadingPlateImage = true; + this.loadingPlateImageFailed = false; + this.plateImageUrl = this.plate.cropImageUrl.toString(); + } } public openLightbox(url: Url, plateNumber: string) { @@ -100,13 +160,13 @@ export class PlateComponent implements OnInit { this.lightbox.open(albums, 0); } - public imageLoaded() { - this.loadingImage = false; + public vehicleImageLoaded() { + this.loadingVehicleImage = false; } - public imageFailedToLoad() { - this.loadingImage = false; - this.loadingImageFailed = true; + public vehicleImageFailedToLoad() { + this.loadingVehicleImage = false; + this.loadingVehicleImageFailed = true; } public plateImageLoaded() { @@ -123,6 +183,10 @@ export class PlateComponent implements OnInit { this.plateService.upsertPlate(this.plate).subscribe(_ => { this.isSavingNotes = false; this.snackbarService.create(`Notes saved for: ${this.plate.plateNumber}`, SnackBarType.Saved); + }, + error => { + this.isSavingNotes = false; + this.snackbarService.create(`Failed to save notes for: ${this.plate.plateNumber}`, SnackBarType.Error); }); } diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plate.ts b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plate.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plate.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plate.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plateResponse.ts b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plateResponse.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plateResponse.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plateResponse.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plateStatistics.ts b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plateStatistics.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/plate/plateStatistics.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/plate/plateStatistics.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plates-routing.module.ts b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plates-routing.module.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/plates-routing.module.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/plates-routing.module.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plates.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plates.component.html similarity index 75% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/plates.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/plates.component.html index 2fd21e7b..60e2e726 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plates.component.html +++ b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plates.component.html @@ -12,9 +12,9 @@ - - - + + + {{plate.openAlprCameraId}} {{plate.plateNumber}} {{plate.vehicleDescription}} @@ -23,7 +23,7 @@ - + @@ -35,29 +35,28 @@ -
-
- - - - - -
+
+ + + + +
- + Filters @@ -70,7 +69,7 @@ - + Plate Number Invalid regex pattern Invalid search term -
-
    -
  • - Include Ignored Plates -
  • -
  • - Regex Search -
  • -
  • - Lenient Matches -
  • -
  • - Plates Seen Less Than 10 Times -
  • -
-
+
    +
  • + Include Ignored Plates +
  • +
  • + Regex Search +
  • +
  • + Lenient Matches +
  • +
  • + Plates Seen Less Than 10 Times +
  • +
+ (click)="clearFilters()">Clear
diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plates.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plates.component.less similarity index 84% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/plates.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/plates.component.less index 057dd8a0..8bfef48b 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plates.component.less +++ b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plates.component.less @@ -4,13 +4,17 @@ } } -.alert { +.alertPlate { background-color: #FFEB3B; } -.ignore { - background-color: #9E9E9E -} +.ignorePlate { + background: rgba(0, 0, 0, 0.05); +} + +.ignorePlateText { + color: rgba(0, 0, 0, 0.2); +} .pulse { margin-right: 7px; @@ -26,6 +30,11 @@ animation: none; } +ul { + list-style-type: none; + padding-left: 0px; +} + @-webkit-keyframes pulse { 0% { -webkit-box-shadow: 0 0 0 0 rgba(204,169,44, 0.4); diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plates.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plates.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/plates.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/plates.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plates.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plates.component.ts similarity index 93% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/plates.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/plates.component.ts index 9cccbcf5..6f4dc959 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plates.component.ts +++ b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plates.component.ts @@ -60,6 +60,7 @@ export class PlatesComponent implements OnInit, OnDestroy, AfterViewInit { panelOpenState = false; public range: FormGroup; public plates: Plate[] = []; + public visiblePlateIds: string[] = []; public totalNumberOfPlates: number; public todaysDate: Date; @@ -96,7 +97,8 @@ export class PlatesComponent implements OnInit, OnDestroy, AfterViewInit { private eventSubscriptions = new Subscription(); private searchSubscription = new Subscription(); - + private filterSubscription = new Subscription(); + @ViewChild(MatPaginator) paginator: MatPaginator; constructor( @@ -116,7 +118,7 @@ export class PlatesComponent implements OnInit, OnDestroy, AfterViewInit { ngOnInit(): void { this.isSignalrConnected = this.signalRHub.isConnected; var pageSize = this.localStorageService.getData(this.pageSizeCacheKey); - this.pageSize = pageSize != null ? parseInt(pageSize) : 25; + this.pageSize = pageSize != '' ? parseInt(pageSize) : 25; this.setInitialDateFilter(); this.searchPlates(); this.populateFilters(); @@ -125,6 +127,7 @@ export class PlatesComponent implements OnInit, OnDestroy, AfterViewInit { ngOnDestroy(): void { this.eventSubscriptions.unsubscribe(); this.searchSubscription.unsubscribe(); + this.filterSubscription.unsubscribe(); } ngAfterViewInit(): void { @@ -156,6 +159,18 @@ export class PlatesComponent implements OnInit, OnDestroy, AfterViewInit { })); } + public plateOpened(plateId: string) { + this.visiblePlateIds.push(plateId); + } + + public plateClosed(plateIdToRemove: string) { + this.visiblePlateIds.forEach((plateId, index) => { + if(plateId === plateIdToRemove) { + this.visiblePlateIds.splice(index, 1); + } + }) + } + public onPaginatorPage($event) { this.pageSize = $event.pageSize; this.localStorageService.setData(this.pageSizeCacheKey, this.pageSize); @@ -166,9 +181,9 @@ export class PlatesComponent implements OnInit, OnDestroy, AfterViewInit { } public populateFilters() { - this.plateService.getFilters().subscribe(result => { + this.filterSubscription.add(this.plateService.getFilters().subscribe(result => { this.vehicleFilters = result; - }); + })); } public searchPlates(plateNumber: string = '') { @@ -205,14 +220,14 @@ export class PlatesComponent implements OnInit, OnDestroy, AfterViewInit { } this.isLoading = true; - this.searchSubscription = this.plateService.searchPlates(request).subscribe(result => { + this.searchSubscription.add(this.plateService.searchPlates(request).subscribe(result => { this.totalNumberOfPlates = result.totalCount; this.plates = result.plates; this.isLoading = false; }, error => { this.isLoading = false; this.snackbarService.create(`Error searching for plates, check the logs`, SnackBarType.Error) - }); + })); } public deletePlate(plateId: string = '', plateNumber: string = '') { diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/plates.module.ts b/OpenAlprWebhookProcessor/angularapp/src/app/plates/plates.module.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/plates.module.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/plates.module.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/plates/vehicleFilters.ts b/OpenAlprWebhookProcessor/angularapp/src/app/plates/vehicleFilters.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/plates/vehicleFilters.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/plates/vehicleFilters.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alert/alert.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alert/alert.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alert/alert.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alert/alert.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alert/alert.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alert/alert.component.less similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alert/alert.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alert/alert.component.less diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alert/alert.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alert/alert.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alert/alert.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alert/alert.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alert/alert.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alert/alert.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alert/alert.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alert/alert.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alert/alert.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alert/alert.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alert/alert.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alert/alert.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alerts.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alerts.component.html similarity index 98% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alerts.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alerts.component.html index 3167cc34..69b5fbd3 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alerts.component.html +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alerts.component.html @@ -1,4 +1,5 @@ +
@@ -47,4 +48,4 @@
-
\ No newline at end of file + diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alerts.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alerts.component.less similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alerts.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alerts.component.less diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alerts.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alerts.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alerts.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alerts.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alerts.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alerts.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alerts.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alerts.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alerts.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alerts.service.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/alerts.service.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/alerts.service.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/pushover/pushover.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/pushover/pushover.component.html similarity index 94% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/pushover/pushover.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/pushover/pushover.component.html index 380b386c..c188a926 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/pushover/pushover.component.html +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/pushover/pushover.component.html @@ -8,7 +8,7 @@ (change)="onPushoverToggle($event)"> -
+
User Key diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/pushover/pushover.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/pushover/pushover.component.less similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/pushover/pushover.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/pushover/pushover.component.less diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/pushover/pushover.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/pushover/pushover.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/pushover/pushover.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/pushover/pushover.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/pushover/pushover.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/pushover/pushover.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/pushover/pushover.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/pushover/pushover.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/pushover/pushover.service.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/pushover/pushover.service.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/pushover/pushover.service.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/pushover/pushover.service.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/pushover/pushover.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/pushover/pushover.service.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/pushover/pushover.service.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/pushover/pushover.service.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/pushover/pushover.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/pushover/pushover.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/alerts/pushover/pushover.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/pushover/pushover.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/app.component.css b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.component.css similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/app.component.css rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.component.css diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.component.html new file mode 100644 index 00000000..c36c4cb0 --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.component.html @@ -0,0 +1,46 @@ + + + WebPush + Native Browser & Mobile notifications + + + +
+ + Email Address + + help_center + + + Public Key + + help_center + + + Private Key + + + +
+
+ + + + +
diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.component.spec.ts new file mode 100644 index 00000000..d94031f1 --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { WebpushComponent } from './webpush.component'; + +describe('WebpushComponent', () => { + let component: WebpushComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [WebpushComponent] + }); + fixture = TestBed.createComponent(WebpushComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.component.ts new file mode 100644 index 00000000..4dbe6bc5 --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.component.ts @@ -0,0 +1,74 @@ +import { animate, style, transition, trigger } from '@angular/animations'; +import { Component, OnInit } from '@angular/core'; +import { MatSlideToggleChange } from '@angular/material/slide-toggle'; +import { SnackbarService } from 'app/snackbar/snackbar.service'; +import { SnackBarType } from 'app/snackbar/snackbartype'; +import { Webpush } from './webpush'; +import { WebpushService } from './webpush.service'; + +@Component({ + selector: 'app-webpush', + templateUrl: './webpush.component.html', + styleUrls: ['./webpush.component.css'], + animations: [ + trigger( + 'inOutAnimation', + [ + transition( + ':enter', [ + style({ height: 0, opacity: 0 }), + animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)', + style({ height: '*', opacity: 1 }))]), + transition( + ':leave', [ + style({ height: '*', opacity: 1 }), + animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)', + style({ height: 0, opacity: 0 }))]) + ])] +}) +export class WebpushComponent implements OnInit { + public client: Webpush; + public isSaving: boolean; + public isTesting: boolean; + public hidePrivateKey: boolean = true; + + constructor( + private webpushService: WebpushService, + private snackbarService: SnackbarService) { } + + ngOnInit(): void { + this.webpushService.getWebpush().subscribe(result => { + this.client = result; + }) + } + + public saveClient() { + this.isSaving = true; + this.webpushService.upsertWebpush(this.client).subscribe(_ => { + this.snackbarService.create("WebPush client saved.", SnackBarType.Saved); + this.isSaving = false; + }); + } + + public testClient() { + this.isTesting = true; + this.webpushService.testWebpush().subscribe(_ => { + this.snackbarService.create("WebPush client test successful.", SnackBarType.Successful); + this.isTesting = false; + }, + _ => { + this.snackbarService.create("WebPush client test failed.", SnackBarType.Error); + this.isTesting = false; + }); + } + + public onWebPushToggle(event: MatSlideToggleChange) { + if (!event.checked) { + this.client.isEnabled = event.checked; + this.isSaving = true; + this.webpushService.upsertWebpush(this.client).subscribe(_ => { + this.isSaving = false; + }); + } + } +} diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.service.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.service.spec.ts new file mode 100644 index 00000000..e16563fb --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { WebpushService } from './webpush.service'; + +describe('WebpushService', () => { + let service: WebpushService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(WebpushService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.service.ts new file mode 100644 index 00000000..efd13206 --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { Webpush } from './webpush'; +import { Observable } from 'rxjs'; +import { HttpClient } from '@angular/common/http'; + +@Injectable({ + providedIn: 'root' +}) +export class WebpushService { + + constructor(private http: HttpClient) { } + + public upsertWebpush(pushover: Webpush): Observable { + return this.http.post('/alerts/webpush', pushover); + } + + public getWebpush(): Observable { + return this.http.get('/alerts/webpush'); + } + + public testWebpush(): Observable { + return this.http.post('/alerts/webpush/test', null); + } +} diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.ts new file mode 100644 index 00000000..e9f0e3f3 --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/alerts/webpush/webpush.ts @@ -0,0 +1,10 @@ +export class Webpush { + isEnabled: boolean; + emailAddress: string; + publicKey: string; + privateKey: boolean; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/camera.service.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/camera.service.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/camera.service.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/camera.service.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/camera.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/camera.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/camera.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/camera.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/camera/camera.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/camera/camera.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/camera/camera.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/camera/camera.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/camera/camera.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/camera/camera.component.less similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/camera/camera.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/camera/camera.component.less diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/camera/camera.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/camera/camera.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/camera/camera.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/camera/camera.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/camera/camera.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/camera/camera.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/camera/camera.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/camera/camera.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/cameras.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/cameras.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/cameras.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/cameras.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/cameras.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/cameras.component.less similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/cameras.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/cameras.component.less diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/cameras.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/cameras.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/cameras.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/cameras.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/cameras.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/cameras.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/cameras.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/cameras.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/edit-camera/edit-camera.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/edit-camera/edit-camera.component.html similarity index 82% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/edit-camera/edit-camera.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/edit-camera/edit-camera.component.html index cdf1bd5e..a0597687 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/edit-camera/edit-camera.component.html +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/edit-camera/edit-camera.component.html @@ -1,26 +1,26 @@

Edit Camera

-
- +
+ Manufacturer Hikvision Dahua - + Model Number - + Ip Address - + Camera Username - + Camera Password {{hidePassword ? 'visibility_off' : 'visibility'}} @@ -28,12 +28,12 @@

Edit Camera

OpenALPR Integration

-
- +
+ OpenALPR Name - + OpenALPR Id @@ -41,8 +41,8 @@

OpenALPR IntegrationOverlays

-
- +
+ Camera Overlay Text Url @@ -53,52 +53,52 @@

OverlaysDay/Night Toggle

-
- +
+ Latitude - + Longitude - + Sunset Offset (minutes +/-) - + Sunrise Offset (minutes +/-) - + Timezone Offset (hours +/-) - + Camera Day/Night Mode Url - + Day Zoom - + Day Focus - + Night Zoom - + Night Focus - + Current Zoom - + Current Focus diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/edit-camera/edit-camera.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/edit-camera/edit-camera.component.less similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/edit-camera/edit-camera.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/edit-camera/edit-camera.component.less diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/edit-camera/edit-camera.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/edit-camera/edit-camera.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/edit-camera/edit-camera.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/edit-camera/edit-camera.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/edit-camera/edit-camera.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/edit-camera/edit-camera.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/edit-camera/edit-camera.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/edit-camera/edit-camera.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/edit-camera/edit-camera.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/edit-camera/edit-camera.service.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/edit-camera/edit-camera.service.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/edit-camera/edit-camera.service.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/edit-camera/zoomfocus.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/edit-camera/zoomfocus.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/cameras/edit-camera/zoomfocus.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/cameras/edit-camera/zoomfocus.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enricher.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enricher.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enricher.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enricher.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enrichers.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enrichers.component.html similarity index 78% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enrichers.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enrichers.component.html index 0c513af2..8e17db64 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enrichers.component.html +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enrichers.component.html @@ -1,16 +1,16 @@ -
+
- + LicensePlateData API https://licenseplatedata.com/ -
- +
+ API Key help_center @@ -27,7 +27,7 @@ Test - diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enrichers.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enrichers.component.less similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enrichers.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enrichers.component.less diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enrichers.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enrichers.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enrichers.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enrichers.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enrichers.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enrichers.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enrichers.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enrichers.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enrichers.service.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enrichers.service.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enrichers.service.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enrichers.service.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enrichers.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enrichers.service.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enrichers.service.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enrichers.service.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enrichmentType.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enrichmentType.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/enrichers/enrichmentType.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/enrichers/enrichmentType.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/forwards/forward.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/forwards/forward.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/forwards/forward.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/forwards/forward.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/forwards/forwards.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/settings/forwards/forwards.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/forwards/forwards.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/forwards/forwards.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/forwards/forwards.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/settings/forwards/forwards.component.less similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/forwards/forwards.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/forwards/forwards.component.less diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/forwards/forwards.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/forwards/forwards.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/forwards/forwards.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/forwards/forwards.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/forwards/forwards.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/forwards/forwards.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/forwards/forwards.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/forwards/forwards.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/forwards/forwards.service.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/forwards/forwards.service.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/forwards/forwards.service.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/forwards/forwards.service.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/forwards/forwards.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/forwards/forwards.service.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/forwards/forwards.service.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/forwards/forwards.service.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignore/ignore.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignore/ignore.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignore/ignore.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignore/ignore.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignore/ignore.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignore/ignore.component.less similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignore/ignore.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignore/ignore.component.less diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignore/ignore.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignore/ignore.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignore/ignore.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignore/ignore.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignore/ignore.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignore/ignore.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignore/ignore.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignore/ignore.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignore/ignore.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignore/ignore.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignore/ignore.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignore/ignore.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignores.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignores.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignores.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignores.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignores.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignores.component.less similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignores.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignores.component.less diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignores.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignores.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignores.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignores.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignores.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignores.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/ignores/ignores.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/ignores/ignores.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/layout.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/settings/layout.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/layout.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/layout.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/layout.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/layout.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/layout.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/layout.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/openalpr-agent/agent.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/agent.ts similarity index 91% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/openalpr-agent/agent.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/agent.ts index fcb1391b..d7ca3373 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/openalpr-agent/agent.ts +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/agent.ts @@ -2,6 +2,7 @@ export class Agent { endpointUrl: string; hostname: string; isDebugEnabled: boolean; + isImageCompressionEnabled: boolean; latitude: number; longitude: number; uid: string; diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/openalpr-agent/openalpr-agent.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.html similarity index 90% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/openalpr-agent/openalpr-agent.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.html index f9a1c8ef..46e7317b 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/openalpr-agent/openalpr-agent.component.html +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.html @@ -46,21 +46,21 @@ style="cursor:default" matSuffix>help_center - + Sunset Offset (minutes +/-) help_center - + Sunrise Offset (minutes +/-) help_center - + Timezone Offset (hours +/-)
- Debug Mode + Debug Mode help_center
+
+ + Image Compression + help_center +
diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/openalpr-agent/openalpr-agent.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.less similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/openalpr-agent/openalpr-agent.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.less diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/openalpr-agent/openalpr-agent.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/openalpr-agent/openalpr-agent.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/openalpr-agent/openalpr-agent.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/openalpr-agent/openalpr-agent.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/openalpr-agent/openalpr-agent.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings-routing.module.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/settings-routing.module.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings-routing.module.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/settings-routing.module.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings.component.css b/OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.component.css similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings.component.css rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.component.css diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.component.html similarity index 93% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.component.html index 2dc10cae..8de83316 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings.component.html +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.component.html @@ -1,8 +1,10 @@ - + videocam diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.component.ts similarity index 58% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.component.ts index 838a8d04..2d047fe0 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings.component.ts +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { MatSidenav } from '@angular/material/sidenav'; +import { NavBarService } from 'app/_services/nav-bar.service'; @Component({ selector: 'app-settings', @@ -8,13 +9,18 @@ import { MatSidenav } from '@angular/material/sidenav'; }) export class SettingsComponent implements OnInit { visibleSetting: string; + navBarVisible: boolean = false; - constructor() { } + constructor(private navBarService: NavBarService) { } ngOnInit(): void { + this.navBarService.settingsButtonClicked.subscribe(() => { + this.navBarVisible = !this.navBarVisible; + }) } public openSettings(settingName: string) { this.visibleSetting = settingName; + this.navBarVisible = !this.navBarVisible; } } diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings.module.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.module.ts similarity index 97% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings.module.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.module.ts index 8283d0ef..bd69382d 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings.module.ts +++ b/OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.module.ts @@ -34,6 +34,7 @@ import { HighlightModule } from 'ngx-highlightjs'; import { PushoverComponent } from './alerts/pushover/pushover.component'; import { EnrichersComponent } from './enrichers/enrichers.component'; import { MatRadioModule } from '@angular/material/radio'; +import { WebpushComponent } from './alerts/webpush/webpush.component'; @NgModule({ declarations: [ @@ -50,6 +51,7 @@ import { MatRadioModule } from '@angular/material/radio'; SystemLogsComponent, PushoverComponent, EnrichersComponent, + WebpushComponent, ], imports: [ CommonModule, diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.service.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/settings.service.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/settings.service.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/systemLogs/systemLogs.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/settings/systemLogs/systemLogs.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/systemLogs/systemLogs.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/systemLogs/systemLogs.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/systemLogs/systemLogs.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/settings/systemLogs/systemLogs.component.less similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/systemLogs/systemLogs.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/systemLogs/systemLogs.component.less diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/systemLogs/systemLogs.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/systemLogs/systemLogs.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/systemLogs/systemLogs.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/systemLogs/systemLogs.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/systemLogs/systemLogs.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/systemLogs/systemLogs.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/systemLogs/systemLogs.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/systemLogs/systemLogs.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/systemLogs/systemLogs.service.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/systemLogs/systemLogs.service.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/systemLogs/systemLogs.service.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/systemLogs/systemLogs.service.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/settings/systemLogs/systemLogs.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/settings/systemLogs/systemLogs.service.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/settings/systemLogs/systemLogs.service.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/settings/systemLogs/systemLogs.service.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/signalr/signalr.service.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/signalr/signalr.service.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/signalr/signalr.service.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/signalr/signalr.service.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/signalr/signalr.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/signalr/signalr.service.ts similarity index 98% rename from OpenAlprWebhookProcessor/ClientApp/src/app/signalr/signalr.service.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/signalr/signalr.service.ts index 3ad2501e..1976c916 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/app/signalr/signalr.service.ts +++ b/OpenAlprWebhookProcessor/angularapp/src/app/signalr/signalr.service.ts @@ -30,7 +30,6 @@ export class SignalrService { this.hubConnection .start() .then(() => { - console.log('Connection started'); this.snackbarService.create(`Connected to server!`, SnackBarType.Connected); this.connectionEstablished.next(true); this.triggerConnectionStatusChange(true); diff --git a/OpenAlprWebhookProcessor/angularapp/src/app/snackbar/snackbar.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/snackbar/snackbar.component.html new file mode 100644 index 00000000..682019d6 --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/app/snackbar/snackbar.component.html @@ -0,0 +1,8 @@ +
+
+ {{getIcon}} +
+
+ {{data.message}} +
+
\ No newline at end of file diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/snackbar/snackbar.component.less b/OpenAlprWebhookProcessor/angularapp/src/app/snackbar/snackbar.component.less similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/snackbar/snackbar.component.less rename to OpenAlprWebhookProcessor/angularapp/src/app/snackbar/snackbar.component.less diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/snackbar/snackbar.component.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/snackbar/snackbar.component.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/snackbar/snackbar.component.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/snackbar/snackbar.component.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/snackbar/snackbar.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/snackbar/snackbar.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/snackbar/snackbar.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/snackbar/snackbar.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/snackbar/snackbar.service.spec.ts b/OpenAlprWebhookProcessor/angularapp/src/app/snackbar/snackbar.service.spec.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/snackbar/snackbar.service.spec.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/snackbar/snackbar.service.spec.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/snackbar/snackbar.service.ts b/OpenAlprWebhookProcessor/angularapp/src/app/snackbar/snackbar.service.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/snackbar/snackbar.service.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/snackbar/snackbar.service.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/snackbar/snackbartype.ts b/OpenAlprWebhookProcessor/angularapp/src/app/snackbar/snackbartype.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/snackbar/snackbartype.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/snackbar/snackbartype.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/users/add-edit.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/users/add-edit.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/users/add-edit.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/users/add-edit.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/users/add-edit.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/users/add-edit.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/users/add-edit.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/users/add-edit.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/users/layout.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/users/layout.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/users/layout.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/users/layout.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/users/layout.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/users/layout.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/users/layout.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/users/layout.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/users/list.component.html b/OpenAlprWebhookProcessor/angularapp/src/app/users/list.component.html similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/users/list.component.html rename to OpenAlprWebhookProcessor/angularapp/src/app/users/list.component.html diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/users/list.component.ts b/OpenAlprWebhookProcessor/angularapp/src/app/users/list.component.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/users/list.component.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/users/list.component.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/users/users-routing.module.ts b/OpenAlprWebhookProcessor/angularapp/src/app/users/users-routing.module.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/users/users-routing.module.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/users/users-routing.module.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/app/users/users.module.ts b/OpenAlprWebhookProcessor/angularapp/src/app/users/users.module.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/app/users/users.module.ts rename to OpenAlprWebhookProcessor/angularapp/src/app/users/users.module.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/src/assets/.gitkeep b/OpenAlprWebhookProcessor/angularapp/src/assets/.gitkeep similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/assets/.gitkeep rename to OpenAlprWebhookProcessor/angularapp/src/assets/.gitkeep diff --git a/OpenAlprWebhookProcessor/angularapp/src/assets/favicon.ico b/OpenAlprWebhookProcessor/angularapp/src/assets/favicon.ico new file mode 100644 index 00000000..b43de942 Binary files /dev/null and b/OpenAlprWebhookProcessor/angularapp/src/assets/favicon.ico differ diff --git a/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-128x128.png b/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-128x128.png new file mode 100644 index 00000000..2b4e28db Binary files /dev/null and b/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-128x128.png differ diff --git a/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-144x144.png b/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-144x144.png new file mode 100644 index 00000000..69072195 Binary files /dev/null and b/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-144x144.png differ diff --git a/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-152x152.png b/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-152x152.png new file mode 100644 index 00000000..f692b92b Binary files /dev/null and b/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-152x152.png differ diff --git a/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-192x192.png b/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-192x192.png new file mode 100644 index 00000000..c1ecd158 Binary files /dev/null and b/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-192x192.png differ diff --git a/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-384x384.png b/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-384x384.png new file mode 100644 index 00000000..d585d7cf Binary files /dev/null and b/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-384x384.png differ diff --git a/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-512x512.png b/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-512x512.png new file mode 100644 index 00000000..dd2fc8be Binary files /dev/null and b/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-512x512.png differ diff --git a/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-72x72.png b/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-72x72.png new file mode 100644 index 00000000..3e92845e Binary files /dev/null and b/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-72x72.png differ diff --git a/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-96x96.png b/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-96x96.png new file mode 100644 index 00000000..05589123 Binary files /dev/null and b/OpenAlprWebhookProcessor/angularapp/src/assets/icons/icon-96x96.png differ diff --git a/OpenAlprWebhookProcessor/ClientApp/src/environments/environment.prod.ts b/OpenAlprWebhookProcessor/angularapp/src/environments/environment.prod.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/environments/environment.prod.ts rename to OpenAlprWebhookProcessor/angularapp/src/environments/environment.prod.ts diff --git a/OpenAlprWebhookProcessor/angularapp/src/environments/environment.ts b/OpenAlprWebhookProcessor/angularapp/src/environments/environment.ts new file mode 100644 index 00000000..9923be36 --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/environments/environment.ts @@ -0,0 +1,3 @@ +export const environment = { + production: false +}; \ No newline at end of file diff --git a/OpenAlprWebhookProcessor/ClientApp/src/index.html b/OpenAlprWebhookProcessor/angularapp/src/index.html similarity index 65% rename from OpenAlprWebhookProcessor/ClientApp/src/index.html rename to OpenAlprWebhookProcessor/angularapp/src/index.html index 0cc75352..434cd407 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/index.html +++ b/OpenAlprWebhookProcessor/angularapp/src/index.html @@ -1,16 +1,20 @@ - + OpenALPR Webhook Processor - + + + + Loading... + diff --git a/OpenAlprWebhookProcessor/ClientApp/src/main.ts b/OpenAlprWebhookProcessor/angularapp/src/main.ts similarity index 71% rename from OpenAlprWebhookProcessor/ClientApp/src/main.ts rename to OpenAlprWebhookProcessor/angularapp/src/main.ts index 0c807a58..3d030cf0 100644 --- a/OpenAlprWebhookProcessor/ClientApp/src/main.ts +++ b/OpenAlprWebhookProcessor/angularapp/src/main.ts @@ -9,4 +9,8 @@ if (environment.production) { } platformBrowserDynamic().bootstrapModule(AppModule) - .catch(err => console.error(err)); \ No newline at end of file + .catch(err => console.error(err)); + +export function getBaseUrl() { + return document.getElementsByTagName('base')[0].href; + } \ No newline at end of file diff --git a/OpenAlprWebhookProcessor/angularapp/src/manifest.webmanifest b/OpenAlprWebhookProcessor/angularapp/src/manifest.webmanifest new file mode 100644 index 00000000..388699d0 --- /dev/null +++ b/OpenAlprWebhookProcessor/angularapp/src/manifest.webmanifest @@ -0,0 +1,59 @@ +{ + "name": "OpenALPR Webhook Processor", + "short_name": "OpenALPR Webhook", + "theme_color": "#1976d2", + "background_color": "#fafafa", + "display": "standalone", + "scope": "./", + "start_url": "./", + "icons": [ + { + "src": "assets/icons/icon-72x72.png", + "sizes": "72x72", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-96x96.png", + "sizes": "96x96", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-128x128.png", + "sizes": "128x128", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-144x144.png", + "sizes": "144x144", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-152x152.png", + "sizes": "152x152", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-384x384.png", + "sizes": "384x384", + "type": "image/png", + "purpose": "maskable any" + }, + { + "src": "assets/icons/icon-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable any" + } + ] +} diff --git a/OpenAlprWebhookProcessor/ClientApp/src/proxy.conf.js b/OpenAlprWebhookProcessor/angularapp/src/proxy.conf.js similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/proxy.conf.js rename to OpenAlprWebhookProcessor/angularapp/src/proxy.conf.js diff --git a/OpenAlprWebhookProcessor/ClientApp/src/styles.less b/OpenAlprWebhookProcessor/angularapp/src/styles.less similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/styles.less rename to OpenAlprWebhookProcessor/angularapp/src/styles.less diff --git a/OpenAlprWebhookProcessor/ClientApp/src/test.ts b/OpenAlprWebhookProcessor/angularapp/src/test.ts similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/src/test.ts rename to OpenAlprWebhookProcessor/angularapp/src/test.ts diff --git a/OpenAlprWebhookProcessor/ClientApp/tsconfig.app.json b/OpenAlprWebhookProcessor/angularapp/tsconfig.app.json similarity index 85% rename from OpenAlprWebhookProcessor/ClientApp/tsconfig.app.json rename to OpenAlprWebhookProcessor/angularapp/tsconfig.app.json index 9051fcaf..84f9d835 100644 --- a/OpenAlprWebhookProcessor/ClientApp/tsconfig.app.json +++ b/OpenAlprWebhookProcessor/angularapp/tsconfig.app.json @@ -6,8 +6,7 @@ "types": [ "node" ] }, "files": [ - "src/main.ts", - "src/polyfills.ts" + "src/main.ts" ], "include": [ "src/**/*.d.ts" diff --git a/OpenAlprWebhookProcessor/ClientApp/tsconfig.json b/OpenAlprWebhookProcessor/angularapp/tsconfig.json similarity index 100% rename from OpenAlprWebhookProcessor/ClientApp/tsconfig.json rename to OpenAlprWebhookProcessor/angularapp/tsconfig.json diff --git a/OpenAlprWebhookProcessor/ClientApp/tsconfig.spec.json b/OpenAlprWebhookProcessor/angularapp/tsconfig.spec.json similarity index 87% rename from OpenAlprWebhookProcessor/ClientApp/tsconfig.spec.json rename to OpenAlprWebhookProcessor/angularapp/tsconfig.spec.json index 092345b0..61c1bb35 100644 --- a/OpenAlprWebhookProcessor/ClientApp/tsconfig.spec.json +++ b/OpenAlprWebhookProcessor/angularapp/tsconfig.spec.json @@ -8,8 +8,7 @@ ] }, "files": [ - "src/test.ts", - "src/polyfills.ts" + "src/test.ts" ], "include": [ "src/**/*.spec.ts", diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 0ce756a8..00000000 --- a/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "OpenAlprWebhookProcessor", - "lockfileVersion": 3, - "requires": true, - "packages": {} -}