diff --git a/Core/Resgrid.Model/Repositories/IDocumentDbRepository.cs b/Core/Resgrid.Model/Repositories/IDocumentDbRepository.cs
new file mode 100644
index 000000000..72ff7cb5d
--- /dev/null
+++ b/Core/Resgrid.Model/Repositories/IDocumentDbRepository.cs
@@ -0,0 +1,16 @@
+using System.Threading.Tasks;
+
+namespace Resgrid.Model.Repositories
+{
+ ///
+ /// Interface IDocumentDbRepository
+ ///
+ public interface IDocumentDbRepository
+ {
+ ///
+ /// Updates the Postgres document database schema.
+ ///
+ /// If the operation was successful
+ Task UpdateDocumentDatabaseAsync();
+ }
+}
diff --git a/Providers/Resgrid.Providers.MigrationsPg/Sql/EF0003_PopulateDocDb.sql b/Providers/Resgrid.Providers.MigrationsPg/Sql/EF0003_PopulateDocDb.sql
index f2ac3af0e..9ace3f60e 100644
--- a/Providers/Resgrid.Providers.MigrationsPg/Sql/EF0003_PopulateDocDb.sql
+++ b/Providers/Resgrid.Providers.MigrationsPg/Sql/EF0003_PopulateDocDb.sql
@@ -41,7 +41,7 @@ END IF;
CREATE TABLE IF NOT EXISTS public.maplayers(
id serial,
departmentid integer,
- oid text
+ oid text,
data jsonb NOT NULL
);
diff --git a/Repositories/Resgrid.Repositories.NoSqlRepository/DocumentDbRepository.cs b/Repositories/Resgrid.Repositories.NoSqlRepository/DocumentDbRepository.cs
new file mode 100644
index 000000000..d5990ad14
--- /dev/null
+++ b/Repositories/Resgrid.Repositories.NoSqlRepository/DocumentDbRepository.cs
@@ -0,0 +1,62 @@
+using Npgsql;
+using Resgrid.Config;
+using Resgrid.Model.Repositories;
+using System;
+using System.IO;
+using System.Reflection;
+using System.Threading.Tasks;
+
+namespace Resgrid.Repositories.NoSqlRepository
+{
+ public class DocumentDbRepository : IDocumentDbRepository
+ {
+ public async Task UpdateDocumentDatabaseAsync()
+ {
+ try
+ {
+ if (DataConfig.DocDatabaseType != DatabaseTypes.Postgres)
+ return true;
+
+ if (string.IsNullOrWhiteSpace(DataConfig.DocumentConnectionString))
+ throw new InvalidOperationException("DocumentConnectionString is required when DocDatabaseType is Postgres.");
+
+ var assembly = Assembly.Load("Resgrid.Providers.MigrationsPg");
+ const string resourceName = "Resgrid.Providers.MigrationsPg.Sql.EF0003_PopulateDocDb.sql";
+
+ using Stream stream = assembly.GetManifestResourceStream(resourceName)
+ ?? throw new InvalidOperationException($"Unable to find document database migration resource '{resourceName}'.");
+ using StreamReader reader = new StreamReader(stream);
+
+ string migrationScript = await reader.ReadToEndAsync();
+
+ if (string.IsNullOrWhiteSpace(migrationScript))
+ throw new InvalidOperationException("Document database migration script is empty.");
+
+ await using var conn = new NpgsqlConnection(DataConfig.DocumentConnectionString);
+ await using var cmd = conn.CreateCommand();
+ await conn.OpenAsync();
+ await using var tran = await conn.BeginTransactionAsync();
+
+ try
+ {
+ cmd.Transaction = tran;
+ cmd.CommandText = migrationScript;
+ await cmd.ExecuteNonQueryAsync();
+ await tran.CommitAsync();
+ }
+ catch
+ {
+ await tran.RollbackAsync();
+ throw;
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Framework.Logging.LogException(ex);
+ return false;
+ }
+ }
+ }
+}
diff --git a/Repositories/Resgrid.Repositories.NoSqlRepository/NoSqlDataModule.cs b/Repositories/Resgrid.Repositories.NoSqlRepository/NoSqlDataModule.cs
index 6543844d8..085502ca9 100644
--- a/Repositories/Resgrid.Repositories.NoSqlRepository/NoSqlDataModule.cs
+++ b/Repositories/Resgrid.Repositories.NoSqlRepository/NoSqlDataModule.cs
@@ -10,6 +10,7 @@ protected override void Load(ContainerBuilder builder)
{
builder.RegisterGeneric(typeof(MongoRepository<>)).As(typeof(IMongoRepository<>)).InstancePerLifetimeScope();
+ builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().InstancePerLifetimeScope();
diff --git a/Tools/Resgrid.Console/Commands/DbUpdateCommand.cs b/Tools/Resgrid.Console/Commands/DbUpdateCommand.cs
index 53d791965..7c4c0dbce 100644
--- a/Tools/Resgrid.Console/Commands/DbUpdateCommand.cs
+++ b/Tools/Resgrid.Console/Commands/DbUpdateCommand.cs
@@ -21,7 +21,8 @@ namespace Resgrid.Console.Commands
public sealed class DbUpdateCommand(
IConfiguration configuration,
ILogger logger,
- IMigrationRunner migrationRunner) : ICommandService
+ IMigrationRunner migrationRunner,
+ IDocumentDbRepository documentDbRepository) : ICommandService
{
///
/// Executes the main functionality of the application.
@@ -38,6 +39,17 @@ public async Task ExecuteMainAsync(string[] args, CancellationToken ca
{
migrationRunner.MigrateUp();
+ if (Config.DataConfig.DocDatabaseType == Config.DatabaseTypes.Postgres)
+ {
+ var result = await documentDbRepository.UpdateDocumentDatabaseAsync();
+
+ if (!result)
+ {
+ logger.LogError("Postgres document database update did not complete successfully.");
+ return ExitCode.Failed;
+ }
+ }
+
logger.LogInformation("Completed updating the Resgrid Database!");
}
catch (Exception ex)
diff --git a/Tools/Resgrid.Console/Commands/MigrateDocsDbCommand.cs b/Tools/Resgrid.Console/Commands/MigrateDocsDbCommand.cs
index a82f8b297..a55f869fc 100644
--- a/Tools/Resgrid.Console/Commands/MigrateDocsDbCommand.cs
+++ b/Tools/Resgrid.Console/Commands/MigrateDocsDbCommand.cs
@@ -21,6 +21,7 @@ namespace Resgrid.Console.Commands
public sealed class MigrateDocsDbCommand(
IConfiguration configuration,
ILogger logger,
+ IDocumentDbRepository documentDbRepository,
IMongoRepository mapLayersRepository,
IMongoRepository unitsLocationRepository,
IMongoRepository personnelLocationRepository,
@@ -41,6 +42,19 @@ public async Task ExecuteMainAsync(string[] args, CancellationToken ca
try
{
+ if (Config.DataConfig.DocDatabaseType == Config.DatabaseTypes.Postgres)
+ {
+ logger.LogInformation("Ensuring Postgres document tables exist...");
+
+ var schemaUpdated = await documentDbRepository.UpdateDocumentDatabaseAsync();
+
+ if (!schemaUpdated)
+ {
+ logger.LogError("Failed to update the Postgres document database schema.");
+ return ExitCode.Failed;
+ }
+ }
+
logger.LogInformation("Migrating Map Layers...");
var layers = mapLayersRepository.AsQueryable().ToList();
diff --git a/Workers/Resgrid.Workers.Console/Program.cs b/Workers/Resgrid.Workers.Console/Program.cs
index eb0afb3f8..b903f6c90 100644
--- a/Workers/Resgrid.Workers.Console/Program.cs
+++ b/Workers/Resgrid.Workers.Console/Program.cs
@@ -71,14 +71,17 @@ static async Task Main(string[] args)
services.AddOptions();
var upgradeDatabase = Environment.GetEnvironmentVariable("RESGRID__DODBUPGRADE");
+ var runDatabaseUpgrade = !String.IsNullOrWhiteSpace(upgradeDatabase) && upgradeDatabase.ToLower() == "true";
- if (!String.IsNullOrWhiteSpace(upgradeDatabase) && upgradeDatabase.ToLower() == "true")
+ if (runDatabaseUpgrade)
{
services.AddSingleton();
}
-
- services.AddSingleton();
- services.AddSingleton();
+ else
+ {
+ services.AddSingleton();
+ services.AddSingleton();
+ }
})
.ConfigureLogging((hostingContext, logging) => {
@@ -411,10 +414,12 @@ await Client.ScheduleAsync("Weather Alert Import",
public class DatabaseUpgradeService : BackgroundService
{
private ILogger _logger;
+ private readonly IHostApplicationLifetime _hostApplicationLifetime;
- public DatabaseUpgradeService(ILogger logger)
+ public DatabaseUpgradeService(ILogger logger, IHostApplicationLifetime hostApplicationLifetime)
{
_logger = logger;
+ _hostApplicationLifetime = hostApplicationLifetime;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
@@ -434,9 +439,11 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
UpdateDatabase(scope.ServiceProvider);
await UpdateOidcDatabaseAsync(logger, scope.ServiceProvider);
+ await UpdateDocumentDatabaseAsync(logger, scope.ServiceProvider);
}
_logger.Log(LogLevel.Information, "Completed updating the Resgrid Database!");
+ _hostApplicationLifetime.StopApplication();
}
catch (Exception ex)
{
@@ -482,6 +489,33 @@ private static async Task UpdateOidcDatabaseAsync(ILogger logger, IServ
}
}
+ ///
+ /// Update the document database
+ ///
+ private static async Task UpdateDocumentDatabaseAsync(ILogger logger, IServiceProvider serviceProvider)
+ {
+ if (Config.DataConfig.DocDatabaseType != Config.DatabaseTypes.Postgres)
+ return;
+
+ logger.Log(LogLevel.Information, "Starting Document Database Upgrade");
+
+ try
+ {
+ var documentDbRepository = Bootstrapper.GetKernel().Resolve();
+ bool result = await documentDbRepository.UpdateDocumentDatabaseAsync();
+
+ if (result)
+ logger.Log(LogLevel.Information, "Completed updating the Document Database!");
+ else
+ throw new InvalidOperationException("UpdateDocumentDatabaseAsync returned false; the document database was not fully updated.");
+ }
+ catch (Exception ex)
+ {
+ logger.Log(LogLevel.Error, ex, "There was an error trying to update the Document Database.");
+ throw;
+ }
+ }
+
private static IServiceProvider CreateServices()
{
if (Config.DataConfig.DatabaseType == Config.DatabaseTypes.Postgres)