diff --git a/FunderMaps.sln b/FunderMaps.sln
index 527d62389..4bc26ef17 100644
--- a/FunderMaps.sln
+++ b/FunderMaps.sln
@@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunderMaps.Portal", "src\Fu
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunderMaps.BatchNode", "src\FunderMaps.BatchNode\FunderMaps.BatchNode.csproj", "{532227C4-BAD4-46AB-B45D-65F15317AFFA}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunderMaps.Cli", "src\FunderMaps.Cli\FunderMaps.Cli.csproj", "{ACDD237D-7C7E-425B-88CD-EF90A0037505}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -89,6 +91,10 @@ Global
{532227C4-BAD4-46AB-B45D-65F15317AFFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{532227C4-BAD4-46AB-B45D-65F15317AFFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{532227C4-BAD4-46AB-B45D-65F15317AFFA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {ACDD237D-7C7E-425B-88CD-EF90A0037505}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ACDD237D-7C7E-425B-88CD-EF90A0037505}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ACDD237D-7C7E-425B-88CD-EF90A0037505}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ACDD237D-7C7E-425B-88CD-EF90A0037505}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -106,6 +112,7 @@ Global
{2A345D40-2842-4AAA-A552-630FAD97B9D5} = {0219D434-DD4F-4187-ADED-8CD4A62E90F1}
{CF0E7A1A-20A8-4A15-8B57-D7218B9F33D1} = {2E7D6F8C-F448-46D2-88BA-2CA8DD7836E9}
{532227C4-BAD4-46AB-B45D-65F15317AFFA} = {2E7D6F8C-F448-46D2-88BA-2CA8DD7836E9}
+ {ACDD237D-7C7E-425B-88CD-EF90A0037505} = {2E7D6F8C-F448-46D2-88BA-2CA8DD7836E9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CDA50611-E011-4C1E-8823-7A6943652DCE}
diff --git a/contrib/FunderMapsCli/.gitignore b/contrib/FunderMapsCli/.gitignore
deleted file mode 100644
index 4b2410fb2..000000000
--- a/contrib/FunderMapsCli/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-Protos
diff --git a/contrib/FunderMapsCli/Makefile b/contrib/FunderMapsCli/Makefile
deleted file mode 100644
index 88b9e10ce..000000000
--- a/contrib/FunderMapsCli/Makefile
+++ /dev/null
@@ -1,4 +0,0 @@
-all: protos
-
-protos:
- python3 -m grpc_tools.protoc -I ../../src/FunderMaps.Infrastructure/ --python_out=. --grpc_python_out=. ../../src/FunderMaps.Infrastructure/Protos/*.proto
diff --git a/contrib/FunderMapsCli/batch_client.py b/contrib/FunderMapsCli/batch_client.py
deleted file mode 100755
index fd5f59c3c..000000000
--- a/contrib/FunderMapsCli/batch_client.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# Copyright 2020 FunderMaps B.V.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-""" FunderMaps Commandline Interface """
-
-import logging
-import json
-import sys
-
-import grpc
-
-from Protos import protocol_pb2
-from Protos import batch_pb2
-from Protos import batch_pb2_grpc
-from google.protobuf import empty_pb2
-
-GRPC_HOST = 'localhost:5000'
-
-
-def _make_protocol():
- proto_version = 0xa1
- return protocol_pb2.FunderMapsProtocol(
- version=proto_version,
- user_agent='batch_client.py')
-
-
-def _print_response(response):
- if not response.task_id:
- print("Server returned an error")
- else:
- print(f"Task ID : {response.task_id}")
-
-
-def bundle_create(stub, bundle):
- print("-------------- Bundle --------------")
- print(f"Bundle : {bundle}")
-
- request = batch_pb2.EnqueueRequest(
- protocol=_make_protocol(),
- name="bundle_building",
- payload=json.dumps({
- "BundleId": bundle,
- "Formats": [0, 1]})
- )
-
- response = stub.Enqueue(request)
- _print_response(response)
-
-
-def bundle_batch(stub):
- print("-------------- Bundle --------------")
- print(f"Bundle : all")
-
- request = batch_pb2.EnqueueRequest(
- protocol=_make_protocol(),
- name="bundle_batch",
- payload=json.dumps({
- "Formats": [0, 1]})
- )
-
- response = stub.Enqueue(request)
- _print_response(response)
-
-
-def foobar(stub):
- print("-------------- Foobar --------------")
-
- request = batch_pb2.EnqueueRequest(
- protocol=_make_protocol(),
- name="foobar")
-
- response = stub.Enqueue(request)
- _print_response(response)
-
-
-def email_send(stub, recp):
- print("-------------- Email --------------")
-
- request = batch_pb2.EnqueueRequest(
- protocol=_make_protocol(),
- name="incident_notify",
- payload=json.dumps({
- 'recipients': [recp],
- }))
-
- response = stub.Enqueue(request)
- _print_response(response)
-
-
-def get_status(stub):
- print("-------------- Status --------------")
-
- response = stub.Status(empty_pb2.Empty())
- print(f"Jobs succeeded: {response.jobs_succeeded}")
- print(f"Jobs failed: {response.jobs_failed}")
-
-
-def run():
- # NOTE: .close() is possible on a channel and should be used in circumstances
- # in which the with statement does not fit the needs of the code.
- creds = grpc.ssl_channel_credentials()
- with grpc.secure_channel(GRPC_HOST, creds) as channel:
- # with grpc.insecure_channel(GRPC_HOST) as channel:
- stub = batch_pb2_grpc.BatchStub(channel)
- if len(sys.argv) == 1:
- print('Command required')
- return
-
- if sys.argv[1] == 'foobar':
- foobar(stub)
- elif sys.argv[1] == 'email':
- email_send(stub, sys.argv[2])
- elif sys.argv[1] == 'bundle':
- if sys.argv[2] == 'all':
- bundle_batch(stub)
- else:
- bundle_create(stub, sys.argv[2])
- elif sys.argv[1] == 'status':
- get_status(stub)
- else:
- print('Command required')
-
-
-if __name__ == '__main__':
- logging.basicConfig()
- run()
diff --git a/src/FunderMaps.AspNetCore/FunderMaps.AspNetCore.csproj b/src/FunderMaps.AspNetCore/FunderMaps.AspNetCore.csproj
index 6d32b2073..a89da1a7a 100644
--- a/src/FunderMaps.AspNetCore/FunderMaps.AspNetCore.csproj
+++ b/src/FunderMaps.AspNetCore/FunderMaps.AspNetCore.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/src/FunderMaps.BatchNode/Command/CommandInfo.cs b/src/FunderMaps.BatchNode/Command/CommandInfo.cs
index a92fe57b4..e2df6683d 100644
--- a/src/FunderMaps.BatchNode/Command/CommandInfo.cs
+++ b/src/FunderMaps.BatchNode/Command/CommandInfo.cs
@@ -6,7 +6,7 @@ namespace FunderMaps.BatchNode.Command
///
/// Command descriptor.
///
- public class CommandInfo
+ public class CommandInfo // TODO: Move to core
{
///
/// Command filename.
diff --git a/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleBatch.cs b/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleBatch.cs
index f6084a1b5..c8c0fd94f 100644
--- a/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleBatch.cs
+++ b/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleBatch.cs
@@ -14,7 +14,7 @@ namespace FunderMaps.BatchNode.Jobs.BundleBuilder
///
/// Bundle batch job entry.
///
- internal class BundleBatch : CommandTask
+ internal class BundleBatch : CommandTask // TODO: Rename to BundleAllJob
{
private const string TaskName = "BUNDLE_BATCH";
diff --git a/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleBuildingContext.cs b/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleBuildingContext.cs
index 12a5c78bf..d79ceb5f0 100644
--- a/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleBuildingContext.cs
+++ b/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleBuildingContext.cs
@@ -7,7 +7,7 @@ namespace FunderMaps.BatchNode.Jobs.BundleBuilder
///
/// Context for executing a bundle build task.
///
- public class BundleBuildingContext
+ public class BundleBuildingContext // TODO: Remove, this is moved to core
{
///
/// The id of the bundle to process.
diff --git a/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleLayerSource.cs b/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleLayerSource.cs
index 4931af815..296ea271f 100644
--- a/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleLayerSource.cs
+++ b/src/FunderMaps.BatchNode/Jobs/BundleBuilder/BundleLayerSource.cs
@@ -32,9 +32,9 @@ public BundleLayerSource(Bundle bundle, Layer layer, string workspace)
layerOutputName = layer.Slug;
// Select layer field as follows:
- // * If no column is specified then select everything from the layer
- // * If wildcard is found, then only use wildcard.
- // * If no geometry column was found, then add one.
+ // - If no column is specified then select everything from the layer
+ // - If wildcard is found, then only use wildcard.
+ // - If no geometry column was found, then add one.
List columns = new(configuration.ColumnNames);
if (columns.Count == 0)
{
@@ -65,4 +65,4 @@ public override void Imbue(CommandInfo commandInfo)
commandInfo.ArgumentList.Add(layerOutputName);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/FunderMaps.BatchNode/Program.cs b/src/FunderMaps.BatchNode/Program.cs
index 63e5b2409..49df84a1a 100644
--- a/src/FunderMaps.BatchNode/Program.cs
+++ b/src/FunderMaps.BatchNode/Program.cs
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Hosting;
using System.Threading.Tasks;
@@ -29,6 +30,13 @@ public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
+ webBuilder.ConfigureKestrel(options =>
+ {
+ options.ConfigureEndpointDefaults(config =>
+ {
+ config.Protocols = HttpProtocols.Http2;
+ });
+ });
webBuilder.UseStartup();
});
}
diff --git a/src/FunderMaps.BatchNode/appsettings.json b/src/FunderMaps.BatchNode/appsettings.json
index c542d1a25..ca2a86a4a 100644
--- a/src/FunderMaps.BatchNode/appsettings.json
+++ b/src/FunderMaps.BatchNode/appsettings.json
@@ -21,10 +21,5 @@
"SecretKey": "",
"ServiceUri": "",
"BlobStorageName": ""
- },
- "Kestrel": {
- "EndpointDefaults": {
- "Protocols": "Http2"
- }
}
-}
+}
\ No newline at end of file
diff --git a/src/FunderMaps.Cli/CommandDriver.cs b/src/FunderMaps.Cli/CommandDriver.cs
new file mode 100644
index 000000000..f29dfb5ad
--- /dev/null
+++ b/src/FunderMaps.Cli/CommandDriver.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace FunderMaps.Cli.Drivers
+{
+ internal abstract class CommandDriver
+ {
+ protected static Task ResolveSelfScope(IServiceProvider services, Func shadowFunc)
+ {
+ var scopeFactory = services.GetRequiredService();
+ using var serviceScope = scopeFactory.CreateScope();
+ var serviceProvider = serviceScope.ServiceProvider;
+ TDriver driver = serviceProvider.GetService();
+ return shadowFunc(driver);
+ }
+ }
+}
diff --git a/src/FunderMaps.Cli/DriverHandler.cs b/src/FunderMaps.Cli/DriverHandler.cs
new file mode 100644
index 000000000..27a71fc3d
--- /dev/null
+++ b/src/FunderMaps.Cli/DriverHandler.cs
@@ -0,0 +1,38 @@
+using System;
+using System.CommandLine.Invocation;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Hosting;
+
+namespace FunderMaps.Cli
+{
+ ///
+ /// Driver command handler.
+ ///
+ public static class DriverHandler
+ {
+ ///
+ /// Create delegate with default parametes.
+ ///
+ /// Handler action.
+ /// See .
+ public static ICommandHandler InstantiateDriver(Func action)
+ => CommandHandler.Create(action);
+
+ ///
+ /// Create delegate with default parametes.
+ ///
+ /// Handler action.
+ /// See .
+ public static ICommandHandler InstantiateDriver(Func action)
+ => CommandHandler.Create(action);
+
+ ///
+ /// Create delegate with default parametes.
+ ///
+ /// Handler action.
+ /// See .
+ public static ICommandHandler InstantiateDriver(Func action)
+ => CommandHandler.Create(action);
+ }
+}
diff --git a/src/FunderMaps.Cli/Drivers/BatchDriver.cs b/src/FunderMaps.Cli/Drivers/BatchDriver.cs
new file mode 100644
index 000000000..e32ac88e0
--- /dev/null
+++ b/src/FunderMaps.Cli/Drivers/BatchDriver.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using FunderMaps.Core.Interfaces;
+using FunderMaps.Core.MapBundle;
+using FunderMaps.Core.Threading;
+using FunderMaps.Core.Types;
+using Microsoft.Extensions.Hosting;
+
+namespace FunderMaps.Cli.Drivers
+{
+ ///
+ /// gRPC channel factory.
+ ///
+ internal class BatchDriver : CommandDriver
+ {
+ private readonly IBatchService _batchService;
+ private readonly IBundleService _bundleService;
+
+ ///
+ /// Create new instance.
+ ///
+ public BatchDriver(IBatchService batchService, IBundleService bundleService)
+ => (_batchService, _bundleService) = (batchService, bundleService);
+
+ private async Task StatusAsync(CancellationToken token = default)
+ {
+ DispatchManagerStatus status = await _batchService.StatusAsync(token);
+ Console.WriteLine($"Jobs failed: {status.JobsFailed}");
+ Console.WriteLine($"Jobs succeeded: {status.JobsSucceeded}");
+ }
+
+ private async Task BuildBundleAsync(IEnumerable bundleIdList, CancellationToken token = default)
+ {
+ foreach (var bundleId in bundleIdList)
+ {
+ await _bundleService.BuildAsync(new BundleBuildingContext
+ {
+ BundleId = bundleId,
+ Formats = new List { GeometryFormat.GeoPackage },
+ });
+ }
+ }
+
+ private async Task BuildAllAsync(CancellationToken token = default)
+ {
+ await _bundleService.BuildAllAsync(new BundleBuildingContext
+ {
+ Formats = new List { GeometryFormat.GeoPackage },
+ });
+ }
+
+ #region Factory Methods
+
+ public static Task StatusAsync(FileInfo config, IHost host)
+ => ResolveSelfScope(host.Services, self => self.StatusAsync());
+
+ public static Task BuildBundleAsync(FileInfo config, IHost host, IEnumerable bundleId)
+ => ResolveSelfScope(host.Services, self => self.BuildBundleAsync(bundleId));
+
+ public static Task BuildAllAsync(FileInfo config, IHost host)
+ => ResolveSelfScope(host.Services, self => self.BuildAllAsync());
+
+ #endregion Factory Methods
+ }
+}
diff --git a/src/FunderMaps.Cli/FunderMaps.Cli.csproj b/src/FunderMaps.Cli/FunderMaps.Cli.csproj
new file mode 100644
index 000000000..0d4f3a5ec
--- /dev/null
+++ b/src/FunderMaps.Cli/FunderMaps.Cli.csproj
@@ -0,0 +1,23 @@
+
+
+
+
+ Exe
+ net5.0
+
+
+
+ $(SolutionDir)DocumentationFunderMapsCli.xml
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/FunderMaps.Cli/Program.cs b/src/FunderMaps.Cli/Program.cs
new file mode 100644
index 000000000..e5a5527f9
--- /dev/null
+++ b/src/FunderMaps.Cli/Program.cs
@@ -0,0 +1,110 @@
+using System.CommandLine;
+using System.CommandLine.Builder;
+using System.CommandLine.Hosting;
+using Microsoft.Extensions.Hosting;
+using System.CommandLine.Parsing;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using System.Collections.Generic;
+using System;
+using System.IO;
+using FunderMaps.Cli.Drivers;
+using Microsoft.Extensions.Logging;
+
+namespace FunderMaps.Cli
+{
+ ///
+ /// Application entry.
+ ///
+ public static class Program
+ {
+ ///
+ /// Application entry point.
+ ///
+ /// Commandline arguments.
+ public static Task Main(string[] args)
+ => CreateHostBuilder(args).Build().InvokeAsync(args);
+
+ ///
+ /// Build a host and run the application.
+ ///
+ ///
+ /// The signature of this method should not be changed.
+ /// External tooling expects this function be present.
+ ///
+ /// Commandline arguments.
+ /// See .
+ public static CommandLineBuilder CreateHostBuilder(string[] args)
+ => BuildCommandLine()
+ .AddLocalCommands()
+ .UseHost(_ => Host.CreateDefaultBuilder(),
+ host =>
+ {
+ host.ConfigureLogging(logging =>
+ {
+ logging.ClearProviders();
+ logging.AddConsole();
+ logging.SetMinimumLevel(LogLevel.Error);
+ });
+ host.ConfigureServices(services =>
+ {
+ // Configure FunderMaps services.
+ services.AddFunderMapsCoreServices();
+ services.AddFunderMapsInfrastructureServices();
+
+ services.AddTransient();
+ });
+ })
+ .UseDefaults();
+
+ ///
+ /// Build commandline builder.
+ ///
+ /// See .
+ private static CommandLineBuilder BuildCommandLine()
+ {
+ RootCommand root = new();
+ root.AddGlobalOption(new Option(new[] { "-c", "--config" }, "An option whose argument is parsed as a FileInfo"));
+ root.Description = "FunderMaps CommandLine Interface";
+
+ return new(root);
+ }
+
+ ///
+ /// Register local commands.
+ ///
+ /// See .
+ private static CommandLineBuilder AddLocalCommands(this CommandLineBuilder rootBuilder)
+ {
+ Command command = new("batch", "Run batch operations");
+ command.AddAlias("b");
+ command.AddOption(new Option(new[] { "-H", "--host" }, "The batch host to execute the command on"));
+ command.AddOption(new Option("--ignore-cert", "Ignore TLS certificate checks"));
+
+ {
+ Command subcommand = new("build-bundle", "The maplayer bundle(s)");
+ subcommand.AddArgument(new Argument("bundleId", "(Optional) Bundle identifiers to build")
+ {
+ Arity = ArgumentArity.OneOrMore,
+ });
+ subcommand.Handler = DriverHandler.InstantiateDriver>(BatchDriver.BuildBundleAsync);
+ command.AddCommand(subcommand);
+ }
+
+ {
+ Command subcommand = new("build-bundle-all", "The all maplayer bundles");
+ subcommand.Handler = DriverHandler.InstantiateDriver(BatchDriver.BuildAllAsync);
+ command.AddCommand(subcommand);
+ }
+
+ {
+ Command subcommand = new("status", "Print the batch status reports");
+ subcommand.Handler = DriverHandler.InstantiateDriver(BatchDriver.StatusAsync);
+ command.AddCommand(subcommand);
+ }
+
+ rootBuilder.AddCommand(command);
+ return rootBuilder;
+ }
+ }
+}
diff --git a/src/FunderMaps.Core/DataAnnotations/GuidAttribute.cs b/src/FunderMaps.Core/DataAnnotations/GuidAttribute.cs
index 5f8434ef3..5286ede8c 100644
--- a/src/FunderMaps.Core/DataAnnotations/GuidAttribute.cs
+++ b/src/FunderMaps.Core/DataAnnotations/GuidAttribute.cs
@@ -7,7 +7,7 @@ namespace FunderMaps.Core.DataAnnotations
/// Guid validation attribute.
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
- public class GuidAttribute : ValidationAttribute
+ public class GuidAttribute : ValidationAttribute // TODO: Remove
{
///
/// Returns true if the value is an non empty guid.
diff --git a/src/FunderMaps.Core/Extensions/FunderMapsCoreServiceCollectionExtensions.cs b/src/FunderMaps.Core/Extensions/FunderMapsCoreServiceCollectionExtensions.cs
index d416d4257..183b6821d 100644
--- a/src/FunderMaps.Core/Extensions/FunderMapsCoreServiceCollectionExtensions.cs
+++ b/src/FunderMaps.Core/Extensions/FunderMapsCoreServiceCollectionExtensions.cs
@@ -1,5 +1,6 @@
using FunderMaps.Core.Components;
using FunderMaps.Core.Interfaces;
+using FunderMaps.Core.MapBundle;
using FunderMaps.Core.Notification;
using FunderMaps.Core.Services;
using FunderMaps.Core.Threading;
@@ -88,6 +89,7 @@ public static IServiceCollection AddFunderMapsCoreServices(this IServiceCollecti
// Register core services in DI container.
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
// Register core services in DI container.
// NOTE: These services take time to initialize are used more often. Registering
diff --git a/src/FunderMaps.Core/FunderMaps.Core.csproj b/src/FunderMaps.Core/FunderMaps.Core.csproj
index 7fc8780cb..3d3705ba8 100644
--- a/src/FunderMaps.Core/FunderMaps.Core.csproj
+++ b/src/FunderMaps.Core/FunderMaps.Core.csproj
@@ -22,7 +22,7 @@
-
+
diff --git a/src/FunderMaps.Core/Interfaces/IBatchService.cs b/src/FunderMaps.Core/Interfaces/IBatchService.cs
index c02db69a4..d90786d6a 100644
--- a/src/FunderMaps.Core/Interfaces/IBatchService.cs
+++ b/src/FunderMaps.Core/Interfaces/IBatchService.cs
@@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
+using FunderMaps.Core.Threading;
namespace FunderMaps.Core.Interfaces
{
@@ -14,10 +15,16 @@ public interface IBatchService
///
/// Name of task to run.
/// Task payload.
- /// Canellation token.
+ /// Cancellation token.
/// Task identifier if task was enqueued.
Task EnqueueAsync(string name, object value, CancellationToken token = default);
+ ///
+ /// Batch service processing status.
+ ///
+ /// Cancellation token.
+ Task StatusAsync(CancellationToken token = default);
+
///
/// Test the batch service backend.
///
diff --git a/src/FunderMaps.Core/MapBundle/BundleBuildingContext.cs b/src/FunderMaps.Core/MapBundle/BundleBuildingContext.cs
new file mode 100644
index 000000000..dda453cb7
--- /dev/null
+++ b/src/FunderMaps.Core/MapBundle/BundleBuildingContext.cs
@@ -0,0 +1,22 @@
+using FunderMaps.Core.Types;
+using System;
+using System.Collections.Generic;
+
+namespace FunderMaps.Core.MapBundle
+{
+ ///
+ /// Context for executing a bundle build task.
+ ///
+ public record BundleBuildingContext
+ {
+ ///
+ /// The id of the bundle to process.
+ ///
+ public Guid BundleId { get; init; }
+
+ ///
+ /// Contains all formats we wish to export.
+ ///
+ public IEnumerable Formats { get; set; }
+ }
+}
diff --git a/src/FunderMaps.Core/MapBundle/BundleHub.cs b/src/FunderMaps.Core/MapBundle/BundleHub.cs
new file mode 100644
index 000000000..67b588702
--- /dev/null
+++ b/src/FunderMaps.Core/MapBundle/BundleHub.cs
@@ -0,0 +1,40 @@
+using FunderMaps.Core.Abstractions;
+using FunderMaps.Core.Interfaces;
+using System;
+using System.Threading.Tasks;
+
+#pragma warning disable CA1812 // Internal class is never instantiated
+namespace FunderMaps.Core.MapBundle
+{
+ ///
+ /// Process bundles.
+ ///
+ internal class BundleHub : AppServiceBase, IBundleService
+ {
+ private const string TaskBuildName = "BUNDLE_BUILDING";
+ private const string TaskBuildAllName = "BUNDLE_BATCH";
+
+ private readonly IBatchService _batchService;
+
+ ///
+ /// Create new instance.
+ ///
+ public BundleHub(AppContext appContext, IBatchService batchService)
+ => (AppContext, _batchService) = (appContext, batchService);
+
+ ///
+ /// Build a bundle.
+ ///
+ /// Bundle building context.
+ public Task BuildAsync(BundleBuildingContext context)
+ => _batchService.EnqueueAsync(TaskBuildName, context, AppContext.CancellationToken);
+
+ ///
+ /// Build all bundles.
+ ///
+ /// Bundle building context.
+ public Task BuildAllAsync(BundleBuildingContext context)
+ => _batchService.EnqueueAsync(TaskBuildAllName, context, AppContext.CancellationToken);
+ }
+}
+#pragma warning restore CA1812 // Internal class is never instantiated
diff --git a/src/FunderMaps.Core/MapBundle/IBundleService.cs b/src/FunderMaps.Core/MapBundle/IBundleService.cs
new file mode 100644
index 000000000..7ffb9be3e
--- /dev/null
+++ b/src/FunderMaps.Core/MapBundle/IBundleService.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Threading.Tasks;
+
+namespace FunderMaps.Core.MapBundle
+{
+ ///
+ /// Bundle service.
+ ///
+ public interface IBundleService
+ {
+ ///
+ /// Build a bundle.
+ ///
+ /// Bundle building context.
+ Task BuildAsync(BundleBuildingContext context);
+
+ ///
+ /// Build all bundles.
+ ///
+ /// Bundle building context.
+ Task BuildAllAsync(BundleBuildingContext context);
+ }
+}
diff --git a/src/FunderMaps.Core/Services/NullBatchService.cs b/src/FunderMaps.Core/Services/NullBatchService.cs
index 6c6b8e0f2..e67e75eda 100644
--- a/src/FunderMaps.Core/Services/NullBatchService.cs
+++ b/src/FunderMaps.Core/Services/NullBatchService.cs
@@ -1,4 +1,5 @@
using FunderMaps.Core.Interfaces;
+using FunderMaps.Core.Threading;
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -12,14 +13,12 @@ namespace FunderMaps.Core.Services
internal class NullBatchService : IBatchService
{
public Task EnqueueAsync(string name, object value, CancellationToken token = default)
- {
- return Task.FromResult(Guid.NewGuid());
- }
+ => Task.FromResult(Guid.NewGuid());
- public Task TestService()
- {
- return Task.CompletedTask;
- }
+ public Task StatusAsync(CancellationToken token = default)
+ => Task.FromResult(new DispatchManagerStatus());
+
+ public Task TestService() => Task.CompletedTask;
}
}
#pragma warning restore CA1812 // Internal class is never instantiated
diff --git a/src/FunderMaps.Core/Types/BuildingType.cs b/src/FunderMaps.Core/Types/BuildingType.cs
index 72533dfbd..872296aea 100644
--- a/src/FunderMaps.Core/Types/BuildingType.cs
+++ b/src/FunderMaps.Core/Types/BuildingType.cs
@@ -16,9 +16,9 @@ public enum BuildingType
Shed = 1,
///
- /// House boat.
+ /// Houseboat.
///
- HouseBoat = 2,
+ Houseboat = 2,
///
/// Mobile home.
diff --git a/src/FunderMaps.Data/Providers/NpgsqlDbProvider.cs b/src/FunderMaps.Data/Providers/NpgsqlDbProvider.cs
index 7b550b4a2..215b774ed 100644
--- a/src/FunderMaps.Data/Providers/NpgsqlDbProvider.cs
+++ b/src/FunderMaps.Data/Providers/NpgsqlDbProvider.cs
@@ -49,6 +49,7 @@ static NpgsqlDbProvider()
NpgsqlConnection.GlobalTypeMapper.MapEnum("application.role");
NpgsqlConnection.GlobalTypeMapper.MapEnum("report.audit_status");
NpgsqlConnection.GlobalTypeMapper.MapEnum("report.base_measurement_level");
+ NpgsqlConnection.GlobalTypeMapper.MapEnum("geocoder.building_type");
NpgsqlConnection.GlobalTypeMapper.MapEnum("maplayer.bundle_status");
NpgsqlConnection.GlobalTypeMapper.MapEnum("report.construction_pile");
NpgsqlConnection.GlobalTypeMapper.MapEnum("report.crack_type");
diff --git a/src/FunderMaps.Infrastructure/BatchClient/BatchClient.cs b/src/FunderMaps.Infrastructure/BatchClient/BatchClient.cs
index 68f191623..7316cd654 100644
--- a/src/FunderMaps.Infrastructure/BatchClient/BatchClient.cs
+++ b/src/FunderMaps.Infrastructure/BatchClient/BatchClient.cs
@@ -12,7 +12,7 @@ namespace FunderMaps.Infrastructure.BatchClient
///
/// Client connector to the batch node.
///
- internal class BatchClient : IBatchService // TODO: Inherit from AppServiceBase
+ internal class BatchClient : IBatchService // TODO: Inherit from AppServiceBase // TODO: Rename to BatchProxy
{
private const string UserAgent = "FunderMaps.Infrastructure";
@@ -29,7 +29,7 @@ public BatchClient(ChannelFactory channelFactory)
///
/// Name of task to run.
/// Task payload.
- /// Canellation token.
+ /// Cancellation token.
public async Task EnqueueAsync(string name, object value = null, CancellationToken token = default)
{
EnqueueRequest request = new()
@@ -39,19 +39,19 @@ public async Task EnqueueAsync(string name, object value = null, Cancellat
Payload = JsonSerializer.Serialize(value),
};
- var client = new Batch.BatchClient(_channelFactory.RemoteChannel);
+ Batch.BatchClient client = new(_channelFactory.RemoteChannel);
EnqueueResponse response = await client.EnqueueAsync(request, null, null, token);
return Guid.Parse(response.TaskId);
}
///
- /// Job processing status.
+ /// Batch service processing status.
///
- /// Canellation token.
+ /// Cancellation token.
public async Task StatusAsync(CancellationToken token = default)
{
- var client = new Batch.BatchClient(_channelFactory.RemoteChannel);
+ Batch.BatchClient client = new(_channelFactory.RemoteChannel);
StatusResponse response = await client.StatusAsync(new(), null, null, token);
return new()
@@ -64,10 +64,7 @@ public async Task StatusAsync(CancellationToken token = d
///
/// Test the batch service backend.
///
- public async Task TestService()
- {
- await StatusAsync();
- }
+ public async Task TestService() => await StatusAsync();
}
}
#pragma warning restore CA1812 // Internal class is never instantiated
diff --git a/src/FunderMaps.Infrastructure/BatchClient/BatchOptions.cs b/src/FunderMaps.Infrastructure/BatchClient/BatchOptions.cs
index 0e2666632..62028c3b4 100644
--- a/src/FunderMaps.Infrastructure/BatchClient/BatchOptions.cs
+++ b/src/FunderMaps.Infrastructure/BatchClient/BatchOptions.cs
@@ -5,16 +5,16 @@ namespace FunderMaps.Infrastructure.BatchClient
///
/// Options for the batch service.
///
- public sealed class BatchOptions
+ public sealed record BatchOptions
{
///
/// Base service uri for batch service.
///
- public Uri ServiceUri { get; set; }
+ public Uri ServiceUri { get; init; } = new("http://localhost");
///
/// Name of the blob storage.
///
- public bool TlsValidate { get; set; }
+ public bool TlsValidate { get; set; } = true;
}
}
diff --git a/src/FunderMaps.Infrastructure/BatchClient/ChannelFactory.cs b/src/FunderMaps.Infrastructure/BatchClient/ChannelFactory.cs
index 596db08c9..9a200ee5c 100644
--- a/src/FunderMaps.Infrastructure/BatchClient/ChannelFactory.cs
+++ b/src/FunderMaps.Infrastructure/BatchClient/ChannelFactory.cs
@@ -25,9 +25,15 @@ public ChannelFactory(IOptions options, ILoggerFactory loggerFacto
{
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
- // NOTE: The httpHandler is disposed when the channel is disposed.
+ // NOTE: The httpHandler is disposed when the GrpcChannel is disposed.
HttpClientHandler httpHandler = new();
+ // There is no TLS when connecting over HTTP, disable TLS verification.
+ if (_options.ServiceUri.Scheme == "http")
+ {
+ _options.TlsValidate = false;
+ }
+
if (!_options.TlsValidate)
{
httpHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
diff --git a/src/FunderMaps.Infrastructure/Email/EmailService.cs b/src/FunderMaps.Infrastructure/Email/EmailService.cs
index bd0fe6e81..69c86ceac 100644
--- a/src/FunderMaps.Infrastructure/Email/EmailService.cs
+++ b/src/FunderMaps.Infrastructure/Email/EmailService.cs
@@ -13,20 +13,29 @@
namespace FunderMaps.Infrastructure.Email
{
// FUTURE: Catch ex.
- // FUTURE: Turn this into a factory
// FUTURE: We can configure much more and we will in a next release.
// TODO: Check input.
- internal class EmailService : IEmailService, IDisposable
+ ///
+ /// Send email to the MTA.
+ ///
+ ///
+ /// Keep the connection to the remote host succinct. MTA's are
+ /// typically not prepared to deal with long living connections.
+ /// Also its unclear if is designed
+ /// to reuse transport connections.
+ ///
+ internal class EmailService : IEmailService
{
private readonly EmailOptions _options;
- private readonly ISmtpClient emailClient = new SmtpClient();
- private bool disposedValue;
///
/// Logger.
///
public ILogger Logger { get; }
+ ///
+ /// Create new instance.
+ ///
public EmailService(IOptions options, ILogger logger)
{
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
@@ -85,10 +94,12 @@ public async Task SendAsync(EmailMessage emailMessage)
Logger.LogDebug($"Message prepared, try sending message to MTA");
- await emailClient.ConnectAsync(_options.SmtpServer, _options.SmtpPort, _options.SmtpTls);
- await emailClient.AuthenticateAsync(_options.SmtpUsername, _options.SmtpPassword);
- await emailClient.SendAsync(message);
- await emailClient.DisconnectAsync(quit: true);
+ using SmtpClient client = new();
+
+ await client.ConnectAsync(_options.SmtpServer, _options.SmtpPort, _options.SmtpTls);
+ await client.AuthenticateAsync(_options.SmtpUsername, _options.SmtpPassword);
+ await client.SendAsync(message);
+ await client.DisconnectAsync(quit: true);
Logger.LogInformation($"Message sent with success");
}
@@ -99,36 +110,13 @@ public async Task SendAsync(EmailMessage emailMessage)
///
public async Task TestService()
{
- await emailClient.ConnectAsync(_options.SmtpServer, _options.SmtpPort, _options.SmtpTls);
- await emailClient.AuthenticateAsync(_options.SmtpUsername, _options.SmtpPassword);
- await emailClient.DisconnectAsync(quit: true);
- }
-
- #region Disposable Pattern
-
- protected virtual void Dispose(bool disposing)
- {
- if (!disposedValue)
- {
- if (disposing)
- {
- emailClient.Dispose();
- }
-
- // TODO: free unmanaged resources (unmanaged objects) and override finalizer
- // TODO: set large fields to null
- disposedValue = true;
- }
- }
+ using SmtpClient client = new();
- public void Dispose()
- {
- // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
- Dispose(disposing: true);
- GC.SuppressFinalize(this);
+ await client.ConnectAsync(_options.SmtpServer, _options.SmtpPort, _options.SmtpTls);
+ await client.AuthenticateAsync(_options.SmtpUsername, _options.SmtpPassword);
+ await client.NoOpAsync();
+ await client.DisconnectAsync(quit: true);
}
-
- #endregion Disposable Pattern
}
}
#pragma warning restore CA1812 // Internal class is never instantiated
diff --git a/src/FunderMaps.Infrastructure/FunderMaps.Infrastructure.csproj b/src/FunderMaps.Infrastructure/FunderMaps.Infrastructure.csproj
index 859eb3f74..f9fa0e5a1 100644
--- a/src/FunderMaps.Infrastructure/FunderMaps.Infrastructure.csproj
+++ b/src/FunderMaps.Infrastructure/FunderMaps.Infrastructure.csproj
@@ -18,13 +18,13 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
-
+
+
diff --git a/src/FunderMaps.WebApi/Controllers/Application/ReviewerController.cs b/src/FunderMaps.WebApi/Controllers/Application/ReviewerController.cs
index fb49f2fc1..8e0282bd6 100644
--- a/src/FunderMaps.WebApi/Controllers/Application/ReviewerController.cs
+++ b/src/FunderMaps.WebApi/Controllers/Application/ReviewerController.cs
@@ -44,7 +44,7 @@ public ReviewerController(IMapper mapper, Core.AppContext appContext, IOrganizat
/// Return all reviewers.
///
/// Cache response for 1 hour.
- [HttpGet("reviewer"), ResponseCache(Duration = 60 * 60)]
+ [HttpGet("reviewer")]
public async Task GetAllAsync([FromQuery] PaginationDto pagination)
{
// Act.
diff --git a/tests/FunderMaps.Core.Tests/Entities/BuildingTests.cs b/tests/FunderMaps.Core.Tests/Entities/BuildingTests.cs
index a752190aa..5948f75ed 100644
--- a/tests/FunderMaps.Core.Tests/Entities/BuildingTests.cs
+++ b/tests/FunderMaps.Core.Tests/Entities/BuildingTests.cs
@@ -20,7 +20,7 @@ public void ValidateBuilding()
Geometry = "7b2274797065223a224d756c7469506f6c79676f6e222c22636f6f7264696e61746573223a5b5b5b5b352e3833333038343731312c35332e3039333332373638385d2c5b352e3833333033393531392c35332e3039333331343130385d2c5b352e3833333031393439362c35332e30393333333832325d2c5b352e3833323938353930392c35332e3039333332383132385d2c5b352e3833333036303236382c35332e3039333233383438355d2c5b352e38333331333930362c35332e3039333236323136365d2c5b352e3833333038343731312c35332e3039333332373638385d5d5d5d7d",
ExternalId = "external-1",
ExternalSource = ExternalDataSource.NlBag,
- BuildingType = BuildingType.HouseBoat,
+ BuildingType = BuildingType.Houseboat,
NeighborhoodId = "gfm-neighborhood",
};
var entity2 = new Building
diff --git a/tests/FunderMaps.Core.Tests/FunderMaps.Core.Tests.csproj b/tests/FunderMaps.Core.Tests/FunderMaps.Core.Tests.csproj
index e5f0fb72f..c74baa5dd 100644
--- a/tests/FunderMaps.Core.Tests/FunderMaps.Core.Tests.csproj
+++ b/tests/FunderMaps.Core.Tests/FunderMaps.Core.Tests.csproj
@@ -8,7 +8,7 @@
-
+
all
diff --git a/tests/FunderMaps.IntegrationTests/Backend/Application/ReviewerTests.cs b/tests/FunderMaps.IntegrationTests/Backend/Application/ReviewerTests.cs
index 26b50cb3f..633697245 100644
--- a/tests/FunderMaps.IntegrationTests/Backend/Application/ReviewerTests.cs
+++ b/tests/FunderMaps.IntegrationTests/Backend/Application/ReviewerTests.cs
@@ -92,8 +92,6 @@ public async Task GetAllReviewerReturnAllReviewer(OrganizationRole role, int exp
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(expectedCount, returnList.Count);
- Assert.True(response.Headers.CacheControl.Public);
- Assert.NotNull(response.Headers.CacheControl.MaxAge);
}
}
}
diff --git a/tests/FunderMaps.IntegrationTests/FunderMaps.IntegrationTests.csproj b/tests/FunderMaps.IntegrationTests/FunderMaps.IntegrationTests.csproj
index 8a76c114a..dc50ea511 100644
--- a/tests/FunderMaps.IntegrationTests/FunderMaps.IntegrationTests.csproj
+++ b/tests/FunderMaps.IntegrationTests/FunderMaps.IntegrationTests.csproj
@@ -8,7 +8,7 @@
-
+