feat: added Sdn repo and added some initial tests

This commit is contained in:
Stevan Freeborn
2024-08-21 09:57:24 -05:00
parent 00994cd4d9
commit ecc26d86f6
18 changed files with 474 additions and 373 deletions
@@ -1,5 +1,3 @@
using FluentResults;
namespace SanctionsSearch.Worker.Interfaces;
interface IOfacFileService
@@ -12,4 +10,4 @@ interface IOfacFileService
Task<Result<Stream>> GetConAddressesFileAsync();
Task<Result<Stream>> GetConAltNamesFileAsync();
Task<Result<Stream>> GetConCommentsFileAsync();
}
}
@@ -4,4 +4,4 @@ interface IRepository<T> where T : Entity
{
Task Upsert(T entity);
Task<IEnumerable<T>> Find(Expression<Func<T, bool>> predicate);
}
}
@@ -4,130 +4,130 @@
namespace SanctionsSearch.Worker.Migrations
{
/// <inheritdoc />
public partial class initial_migration : Migration
{
/// <inheritdoc />
public partial class initial_migration : Migration
protected override void Up(MigrationBuilder migrationBuilder)
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Sdns",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", nullable: false),
Type = table.Column<string>(type: "TEXT", nullable: false),
Program = table.Column<string>(type: "TEXT", nullable: false),
Title = table.Column<string>(type: "TEXT", nullable: false),
CallSign = table.Column<string>(type: "TEXT", nullable: false),
VesselType = table.Column<string>(type: "TEXT", nullable: false),
Tonnage = table.Column<string>(type: "TEXT", nullable: false),
GrossRegisteredTonnage = table.Column<string>(type: "TEXT", nullable: false),
VesselFlag = table.Column<string>(type: "TEXT", nullable: false),
VesselOwner = table.Column<string>(type: "TEXT", nullable: false),
Remarks = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Sdns", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Sdns",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Name = table.Column<string>(type: "TEXT", nullable: false),
Type = table.Column<string>(type: "TEXT", nullable: false),
Program = table.Column<string>(type: "TEXT", nullable: false),
Title = table.Column<string>(type: "TEXT", nullable: false),
CallSign = table.Column<string>(type: "TEXT", nullable: false),
VesselType = table.Column<string>(type: "TEXT", nullable: false),
Tonnage = table.Column<string>(type: "TEXT", nullable: false),
GrossRegisteredTonnage = table.Column<string>(type: "TEXT", nullable: false),
VesselFlag = table.Column<string>(type: "TEXT", nullable: false),
VesselOwner = table.Column<string>(type: "TEXT", nullable: false),
Remarks = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Sdns", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Addresses",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
SdnId = table.Column<int>(type: "INTEGER", nullable: false),
StreetAddress = table.Column<string>(type: "TEXT", nullable: false),
CityProvincePostal = table.Column<string>(type: "TEXT", nullable: false),
Country = table.Column<string>(type: "TEXT", nullable: false),
Remarks = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Addresses", x => x.Id);
table.ForeignKey(
name: "FK_Addresses_Sdns_SdnId",
column: x => x.SdnId,
principalTable: "Sdns",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Addresses",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
SdnId = table.Column<int>(type: "INTEGER", nullable: false),
StreetAddress = table.Column<string>(type: "TEXT", nullable: false),
CityProvincePostal = table.Column<string>(type: "TEXT", nullable: false),
Country = table.Column<string>(type: "TEXT", nullable: false),
Remarks = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Addresses", x => x.Id);
table.ForeignKey(
name: "FK_Addresses_Sdns_SdnId",
column: x => x.SdnId,
principalTable: "Sdns",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Aliases",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
SdnId = table.Column<int>(type: "INTEGER", nullable: false),
Type = table.Column<string>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", nullable: false),
Remarks = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Aliases", x => x.Id);
table.ForeignKey(
name: "FK_Aliases_Sdns_SdnId",
column: x => x.SdnId,
principalTable: "Sdns",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Aliases",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
SdnId = table.Column<int>(type: "INTEGER", nullable: false),
Type = table.Column<string>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", nullable: false),
Remarks = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Aliases", x => x.Id);
table.ForeignKey(
name: "FK_Aliases_Sdns_SdnId",
column: x => x.SdnId,
principalTable: "Sdns",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Comments",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
SdnId = table.Column<int>(type: "INTEGER", nullable: false),
Remarks = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Comments", x => x.Id);
table.ForeignKey(
name: "FK_Comments_Sdns_SdnId",
column: x => x.SdnId,
principalTable: "Sdns",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "Comments",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
SdnId = table.Column<int>(type: "INTEGER", nullable: false),
Remarks = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Comments", x => x.Id);
table.ForeignKey(
name: "FK_Comments_Sdns_SdnId",
column: x => x.SdnId,
principalTable: "Sdns",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_Addresses_SdnId",
table: "Addresses",
column: "SdnId");
migrationBuilder.CreateIndex(
name: "IX_Addresses_SdnId",
table: "Addresses",
column: "SdnId");
migrationBuilder.CreateIndex(
name: "IX_Aliases_SdnId",
table: "Aliases",
column: "SdnId");
migrationBuilder.CreateIndex(
name: "IX_Aliases_SdnId",
table: "Aliases",
column: "SdnId");
migrationBuilder.CreateIndex(
name: "IX_Comments_SdnId",
table: "Comments",
column: "SdnId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Addresses");
migrationBuilder.DropTable(
name: "Aliases");
migrationBuilder.DropTable(
name: "Comments");
migrationBuilder.DropTable(
name: "Sdns");
}
migrationBuilder.CreateIndex(
name: "IX_Comments_SdnId",
table: "Comments",
column: "SdnId");
}
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Addresses");
migrationBuilder.DropTable(
name: "Aliases");
migrationBuilder.DropTable(
name: "Comments");
migrationBuilder.DropTable(
name: "Sdns");
}
}
}
@@ -1,107 +1,108 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace SanctionsSearch.Worker.Migrations
{
/// <inheritdoc />
public partial class update_models_with_timestamps : Migration
{
/// <inheritdoc />
public partial class update_models_with_timestamps : Migration
protected override void Up(MigrationBuilder migrationBuilder)
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "Sdns",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "Sdns",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedAt",
table: "Sdns",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedAt",
table: "Sdns",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "Comments",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "Comments",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedAt",
table: "Comments",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedAt",
table: "Comments",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "Aliases",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "Aliases",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedAt",
table: "Aliases",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedAt",
table: "Aliases",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "Addresses",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "CreatedAt",
table: "Addresses",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedAt",
table: "Addresses",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "Sdns");
migrationBuilder.DropColumn(
name: "UpdatedAt",
table: "Sdns");
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "Comments");
migrationBuilder.DropColumn(
name: "UpdatedAt",
table: "Comments");
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "Aliases");
migrationBuilder.DropColumn(
name: "UpdatedAt",
table: "Aliases");
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "Addresses");
migrationBuilder.DropColumn(
name: "UpdatedAt",
table: "Addresses");
}
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedAt",
table: "Addresses",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "Sdns");
migrationBuilder.DropColumn(
name: "UpdatedAt",
table: "Sdns");
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "Comments");
migrationBuilder.DropColumn(
name: "UpdatedAt",
table: "Comments");
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "Aliases");
migrationBuilder.DropColumn(
name: "UpdatedAt",
table: "Aliases");
migrationBuilder.DropColumn(
name: "CreatedAt",
table: "Addresses");
migrationBuilder.DropColumn(
name: "UpdatedAt",
table: "Addresses");
}
}
}
@@ -1,8 +1,9 @@
namespace SanctionsSearch.Worker.Persistence;
class AppDbContext(DbOptions options) : DbContext
class AppDbContext(DbOptions options, TimeProvider timeProvider) : DbContext
{
private readonly DbOptions _options = options;
private readonly TimeProvider _timeProvider = timeProvider;
private SqliteConnection? _connection;
internal string DatabasePath { get; private set; } = string.Empty;
public DbSet<Sdn> Sdns { get; set; } = default!;
@@ -32,8 +33,7 @@ class AppDbContext(DbOptions options) : DbContext
public async override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
// TODO: Use TimeProvider instead of DateTime.UtcNow
var now = DateTime.UtcNow;
var now = _timeProvider.GetUtcNow().DateTime;
foreach (var changedEntity in ChangeTracker.Entries())
{
@@ -2,10 +2,10 @@ namespace SanctionsSearch.Worker.Persistence;
class EfRepository<T> : IRepository<T> where T : Entity
{
protected readonly AppDbContext _context;
protected readonly DbContext _context;
protected readonly ILogger<EfRepository<T>> _logger;
public EfRepository(AppDbContext context, ILogger<EfRepository<T>> logger)
public EfRepository(DbContext context, ILogger<EfRepository<T>> logger)
{
_context = context;
_logger = logger;
@@ -1,7 +1,7 @@
namespace SanctionsSearch.Worker.Persistence;
class SdnRepository(
AppDbContext dbContext,
DbContext dbContext,
ILogger<SdnRepository> logger
) : EfRepository<Sdn>(dbContext, logger), ISdnRepository
{
+64 -55
View File
@@ -1,62 +1,71 @@
using SanctionsSearch.Worker.Persistence;
using SanctionsSearch.Worker.Setup;
namespace SanctionsSearch.Worker;
if (EF.IsDesignTime)
class Program
{
Host.CreateDefaultBuilder().Build().Run();
return;
}
Log.Logger = new LoggerConfiguration()
.Enrich.WithProperty("Application", "SanctionsSearch.Worker")
.Enrich.WithEnvironmentName()
.Enrich.WithMachineName()
.Enrich.WithProcessId()
.Enrich.WithThreadId()
.Enrich.WithExceptionDetails()
.Enrich.FromLogContext()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.File(new CompactJsonFormatter(), "logs/log.json", rollingInterval: RollingInterval.Day)
.CreateLogger();
try
{
Log.Information("Starting Sanctions Search worker");
var builder = Host.CreateApplicationBuilder(args);
builder.Logging.ClearProviders();
builder.Logging.AddSerilog();
builder.Services.ConfigureOptions<OfacFileServiceOptionsSetup>();
builder.Services.ConfigureOptions<DbOptionsSetup>();
builder.Services.AddScoped(rs => rs.GetRequiredService<IOptionsSnapshot<DbOptions>>().Value);
builder.Services.AddDbContext<AppDbContext>();
builder.Services.AddHostedService<Worker>();
var host = builder.Build();
using var scope = host.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var pendingMigrations = await context.Database.GetPendingMigrationsAsync();
if (pendingMigrations.Any())
async static Task Main(string[] args)
{
Log.Information("Applying pending migrations");
await context.Database.MigrateAsync();
if (EF.IsDesignTime) return;
Log.Logger = new LoggerConfiguration()
.Enrich.WithProperty("Application", "SanctionsSearch.Worker")
.Enrich.WithEnvironmentName()
.Enrich.WithMachineName()
.Enrich.WithProcessId()
.Enrich.WithThreadId()
.Enrich.WithExceptionDetails()
.Enrich.FromLogContext()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.File(new CompactJsonFormatter(), "logs/log.json", rollingInterval: RollingInterval.Day)
.CreateLogger();
try
{
Log.Information("Starting Sanctions Search worker");
var builder = CreateHostBuilder(args);
var host = builder.Build();
using var scope = host.Services.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var pendingMigrations = await context.Database.GetPendingMigrationsAsync();
if (pendingMigrations.Any())
{
Log.Information("Applying pending migrations");
await context.Database.MigrateAsync();
}
await host.RunAsync();
Log.Information("Stopping Sanctions Search worker");
}
catch (Exception ex)
{
Log.Fatal(ex, "Worker terminated unexpectedly");
}
finally
{
await Log.CloseAndFlushAsync();
}
}
host.Run();
static HostApplicationBuilder CreateHostBuilder(string[] args)
{
var builder = Host.CreateApplicationBuilder(args);
Log.Information("Stopping Sanctions Search worker");
}
catch (Exception ex)
{
Log.Fatal(ex, "Worker terminated unexpectedly");
}
finally
{
await Log.CloseAndFlushAsync();
builder.Logging.ClearProviders();
builder.Logging.AddSerilog();
builder.Services.ConfigureOptions<OfacFileServiceOptionsSetup>();
builder.Services.ConfigureOptions<DbOptionsSetup>();
builder.Services.AddScoped(rs => rs.GetRequiredService<IOptionsSnapshot<DbOptions>>().Value);
builder.Services.AddSingleton(TimeProvider.System);
builder.Services.AddDbContext<AppDbContext>();
builder.Services.AddHostedService<Worker>();
return builder;
}
}
@@ -1,6 +1,3 @@
using FluentResults;
namespace SanctionsSearch.Worker.Services;
class OfacFileService(
@@ -45,4 +42,4 @@ class OfacFileService(
public Task<Result<Stream>> GetConAddressesFileAsync() => GetFileAsync(_options.GetConAddressesFileUri());
public Task<Result<Stream>> GetConAltNamesFileAsync() => GetFileAsync(_options.GetConAltNamesFileUri());
public Task<Result<Stream>> GetConCommentsFileAsync() => GetFileAsync(_options.GetConCommentsFileUri());
}
}
+11 -10
View File
@@ -1,18 +1,19 @@
global using System.Linq.Expressions;
global using System.Reflection;
global using FluentResults;
global using Microsoft.Data.Sqlite;
global using Microsoft.EntityFrameworkCore;
global using Microsoft.Extensions.Options;
global using SanctionsSearch.Worker.Interfaces;
global using SanctionsSearch.Worker.Options;
global using SanctionsSearch.Worker;
global using SanctionsSearch.Worker.Models;
global using SanctionsSearch.Worker.Options;
global using SanctionsSearch.Worker.Persistence;
global using SanctionsSearch.Worker.Services;
global using SanctionsSearch.Worker.Setup;
global using Serilog;
global using Serilog.Exceptions;
global using Serilog.Formatting.Compact;
global using Microsoft.EntityFrameworkCore;
global using System.Linq.Expressions;
global using System.Reflection;
global using Microsoft.Data.Sqlite;