adding
This commit is contained in:
@@ -0,0 +1,364 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
/.idea/
|
||||
/.vscode/
|
||||
@@ -0,0 +1,37 @@
|
||||
# .NET Core SDK
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS aspnetcore-build
|
||||
|
||||
# Copy Projects
|
||||
COPY LCU.API.Internal.Document/LCU.API.Internal.Document.csproj ./source/LCU.API.Internal.Document/
|
||||
COPY LCU.API.Internal.DocumentLib/LCU.API.Internal.DocumentLib.csproj ./source/LCU.API.Internal.DocumentLib/
|
||||
COPY LCU.Library.Internal.DocumentModels/LCU.Library.Internal.DocumentModels.csproj ./source/LCU.Library.Internal.DocumentModels/
|
||||
|
||||
# Install Credential Provider and set env variables to enable Nuget restore with auth
|
||||
ARG PAT=bh2lq3jibktw67b2wdbqyfln7cd3je6lk52kijntpj43v6kimpgq
|
||||
RUN curl -L https://raw.githubusercontent.com/Microsoft/artifacts-credprovider/master/helpers/installcredprovider.sh | sh
|
||||
|
||||
ENV NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED true
|
||||
ENV VSS_NUGET_EXTERNAL_FEED_ENDPOINTS "{\"endpointCredentials\": [{\"endpoint\":\"https://landmarkcu.pkgs.visualstudio.com/_packaging/LCU-Internal/nuget/v3/index.json\", \"password\":\"${PAT}\"}]}"
|
||||
|
||||
RUN dotnet restore -s "https://landmarkcu.pkgs.visualstudio.com/_packaging/LCU-Internal/nuget/v3/index.json" -s "https://api.nuget.org/v3/index.json" ./source/LCU.API.Internal.Document/LCU.API.Internal.Document.csproj
|
||||
|
||||
# Copy All Files
|
||||
COPY . ./source/
|
||||
|
||||
LABEL test=true
|
||||
COPY LCU.API.Internal.DocumentTest/LCU.API.Internal.DocumentTest.csproj ./source/LCU.API.Internal.DocumentTest/
|
||||
RUN dotnet restore ./source/LCU.API.Internal.DocumentTest/LCU.API.Internal.DocumentTest.csproj
|
||||
RUN dotnet test -c Release --results-directory /testresults --logger "trx;LogFileName=test_results.trx" ./source/LCU.API.Internal.DocumentTest/LCU.API.Internal.DocumentTest.csproj
|
||||
|
||||
# .NET Core Publish
|
||||
RUN dotnet publish ./source/LCU.API.Internal.Document/LCU.API.Internal.Document.csproj -c Release -o /dist
|
||||
|
||||
# ASP.NET Core
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine3.20
|
||||
RUN apk add --no-cache icu-libs && apk add --no-cache tzdata;
|
||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
|
||||
ENV ASPNETCORE_ENVIRONMENT=Development
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=aspnetcore-build /dist .
|
||||
ENTRYPOINT ["dotnet", "LCU.API.Internal.Document.dll"]
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace LCU.API.Internal.Document.PerformanceTest.API;
|
||||
|
||||
public static class ApiFactory
|
||||
{
|
||||
public static IDocumentApi Api => Environment.Type switch
|
||||
{
|
||||
EnvironmentType.Local => new DirectInMemoryDocumentApi(),
|
||||
EnvironmentType.Azure => new DirectInMemoryDocumentApi(),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
using LCU.API.Internal.Document.Configurations;
|
||||
using LCU.API.Internal.Document.Controllers;
|
||||
using LCU.API.Internal.Document.PerformanceTest.DB;
|
||||
using LCU.API.Internal.DocumentLib.Configurations;
|
||||
using LCU.API.Internal.DocumentLib.DataAccess;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
using LCU.Library.Internal.DocumentModels.Responses;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace LCU.API.Internal.Document.PerformanceTest.API;
|
||||
|
||||
public class DirectInMemoryDocumentApi : IDocumentApi
|
||||
{
|
||||
private IServiceProvider? _services;
|
||||
|
||||
public Task SpinUp(DatabaseConfiguration databaseConfiguration)
|
||||
{
|
||||
if (_services is not null) return Task.CompletedTask;
|
||||
|
||||
var services = new ServiceCollection();
|
||||
ServiceRegistrations.AddDocumentServices(services);
|
||||
ServiceRegistrations.AddDocumentDataAccesses(services);
|
||||
ServiceRegistrations.AddDocumentRepositories(services);
|
||||
services.AddTransient<DocumentController>();
|
||||
services.TryAddSingleton(databaseConfiguration);
|
||||
services.AddSingleton<IBlobStorageClient, NullBlobStorageClient>();
|
||||
services.AddSingleton<ApiConfiguration>();
|
||||
services.AddSingleton<NotificationApiConfiguration>();
|
||||
_services = services.BuildServiceProvider();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<IList<DocumentInformationResponse>> GetDocumentInformationByFilter(DocumentInformationFilter filter)
|
||||
{
|
||||
var controller = _services?.GetRequiredService<DocumentController>() ?? throw new InvalidOperationException();
|
||||
var response = await controller.PostDocumentInformationByFilter(new DocumentInformationFilteredSearch{ Filter = filter });
|
||||
|
||||
return (response as OkObjectResult)?.Value as IList<DocumentInformationResponse> ?? throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public Task TearDown()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using LCU.API.Internal.DocumentLib.Configurations;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
using LCU.Library.Internal.DocumentModels.Responses;
|
||||
|
||||
namespace LCU.API.Internal.Document.PerformanceTest.API;
|
||||
|
||||
public interface IDocumentApi
|
||||
{
|
||||
Task SpinUp(DatabaseConfiguration databaseConfiguration);
|
||||
Task<IList<DocumentInformationResponse>> GetDocumentInformationByFilter(DocumentInformationFilter filter);
|
||||
Task TearDown();
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
using Azure;
|
||||
using Azure.Core;
|
||||
using Azure.Identity;
|
||||
using Azure.ResourceManager;
|
||||
using Azure.ResourceManager.CosmosDB;
|
||||
using Azure.ResourceManager.CosmosDB.Models;
|
||||
using Azure.ResourceManager.Resources;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace LCU.API.Internal.Document.PerformanceTest.DB;
|
||||
|
||||
public class AzureCosmosDb : IDatabase
|
||||
{
|
||||
private readonly string _performanceTestingResourceName = "Perf_MongoDb";
|
||||
|
||||
public async Task<IMongoClient> SpinUp()
|
||||
{
|
||||
Console.WriteLine("Currently in Azure Spin Up.");
|
||||
//todo test this - cd: I'm not connected to the landmark subscription in the portal, only AD
|
||||
var azureClient = new ArmClient(new DefaultAzureCredential());
|
||||
var subscription = await azureClient.GetDefaultSubscriptionAsync();
|
||||
var resourceGroup = (
|
||||
await subscription
|
||||
.GetResourceGroups()
|
||||
.CreateOrUpdateAsync
|
||||
(
|
||||
WaitUntil.Completed,
|
||||
_performanceTestingResourceName,
|
||||
new ResourceGroupData(AzureLocation.CentralUS)
|
||||
)
|
||||
)
|
||||
.Value;
|
||||
|
||||
var cosmosAccount = (
|
||||
await resourceGroup
|
||||
.GetCosmosDBAccounts()
|
||||
.CreateOrUpdateAsync
|
||||
(
|
||||
WaitUntil.Completed,
|
||||
_performanceTestingResourceName.ToLower().Replace("_",""),
|
||||
new CosmosDBAccountCreateOrUpdateContent
|
||||
(
|
||||
AzureLocation.CentralUS,
|
||||
new CosmosDBAccountLocation[] {
|
||||
new CosmosDBAccountLocation {
|
||||
LocationName = AzureLocation.CentralUS,
|
||||
FailoverPriority = 0,
|
||||
IsZoneRedundant = false
|
||||
},
|
||||
new CosmosDBAccountLocation {
|
||||
LocationName = AzureLocation.EastUS2,
|
||||
FailoverPriority = 1,
|
||||
IsZoneRedundant = false
|
||||
}
|
||||
}
|
||||
)
|
||||
{ Kind = "MongoDB"}
|
||||
)
|
||||
)
|
||||
.Value;
|
||||
|
||||
var mongoDb = cosmosAccount.GetMongoDBDatabases();
|
||||
|
||||
await mongoDb
|
||||
.CreateOrUpdateAsync
|
||||
(
|
||||
WaitUntil.Completed,
|
||||
_performanceTestingResourceName,
|
||||
new MongoDBDatabaseCreateOrUpdateContent
|
||||
(
|
||||
AzureLocation.CentralUS,
|
||||
new MongoDBDatabaseResourceInfo(_performanceTestingResourceName)
|
||||
)
|
||||
);
|
||||
|
||||
//todo test to ensure that there's only one connection string or that it can be determined which is the one we need for mongo
|
||||
var allConnections = cosmosAccount
|
||||
.GetConnectionStrings()
|
||||
.AsPages()
|
||||
.ToArray()
|
||||
.SelectMany(p => p.Values)
|
||||
.ToArray();
|
||||
if (allConnections.Length == 0) throw new Exception("No Connection Strings Found.");
|
||||
_connectionString = cosmosAccount.GetConnectionStrings().AsPages().ToArray()[0].Values[0].ConnectionString;
|
||||
|
||||
return new MongoClient(ConnectionString);
|
||||
}
|
||||
|
||||
public async Task TearDown()
|
||||
{
|
||||
var azureClient = new ArmClient(new DefaultAzureCredential());
|
||||
var subscription = await azureClient.GetDefaultSubscriptionAsync();
|
||||
var resourceGroup = (await subscription.GetResourceGroupAsync(_performanceTestingResourceName)).Value;
|
||||
await resourceGroup.DeleteAsync(WaitUntil.Completed);
|
||||
}
|
||||
|
||||
public string ConnectionString => _connectionString ?? throw new InvalidOperationException("spin up the database first");
|
||||
private string? _connectionString;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
namespace LCU.API.Internal.Document.PerformanceTest.DB;
|
||||
|
||||
public static class DatabaseFactory
|
||||
{
|
||||
public static IDatabase Database => Environment.Type switch
|
||||
{
|
||||
EnvironmentType.Local => new LocalMongoDatabase(),
|
||||
EnvironmentType.Azure => new AzureCosmosDb(),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace LCU.API.Internal.Document.PerformanceTest.DB;
|
||||
|
||||
public interface IDatabase
|
||||
{
|
||||
Task<IMongoClient> SpinUp();
|
||||
Task TearDown();
|
||||
string ConnectionString { get; }
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
using Mongo2Go;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace LCU.API.Internal.Document.PerformanceTest.DB;
|
||||
|
||||
public class LocalMongoDatabase : IDatabase
|
||||
{
|
||||
private MongoDbRunner? _mongoDb;
|
||||
|
||||
public Task<IMongoClient> SpinUp()
|
||||
{
|
||||
_mongoDb ??= MongoDbRunner.Start();
|
||||
|
||||
return Task.FromResult<IMongoClient>(new MongoClient(ConnectionString));
|
||||
}
|
||||
|
||||
public Task TearDown()
|
||||
{
|
||||
_mongoDb?.Dispose();
|
||||
_mongoDb = null;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public string ConnectionString => _mongoDb?.ConnectionString ?? throw new InvalidOperationException("spin up the database first");
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using Azure.Storage.Blobs;
|
||||
using LCU.API.Internal.DocumentLib.DataAccess;
|
||||
|
||||
namespace LCU.API.Internal.Document.PerformanceTest.DB;
|
||||
|
||||
public class NullBlobStorageClient : IBlobStorageClient
|
||||
{
|
||||
public BlobContainerClient GetClient() => null!;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace LCU.API.Internal.Document.PerformanceTest;
|
||||
|
||||
public enum EnvironmentType
|
||||
{
|
||||
Local,
|
||||
Azure
|
||||
}
|
||||
|
||||
public static class Environment
|
||||
{
|
||||
public static EnvironmentType Type { get; set; } = EnvironmentType.Local;
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using LCU.API.Internal.Document.PerformanceTest.API;
|
||||
using LCU.API.Internal.Document.PerformanceTest.DB;
|
||||
using LCU.API.Internal.DocumentLib.Configurations;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace LCU.API.Internal.Document.PerformanceTest;
|
||||
|
||||
[RPlotExporter]
|
||||
public class GetDocumentInformationByFilterLoadTest
|
||||
{
|
||||
static GetDocumentInformationByFilterLoadTest()
|
||||
{
|
||||
Environment.Type = EnvironmentType.Local;
|
||||
}
|
||||
|
||||
//[Params(1, 10, 100, 1000, 10000, 100000, 250000, 500000, 750000, 1000000, 2000000)]
|
||||
[Params(1, 10, 100, 1000, 10000, 100000, 250000, 500000, 750000, 1000000)]
|
||||
public int DocumentCount { get; set; }
|
||||
|
||||
private DocumentInformation _document = null!;
|
||||
|
||||
private readonly DatabaseConfiguration _config;
|
||||
private readonly IDatabase _database = DatabaseFactory.Database;
|
||||
private readonly IDocumentApi _api = ApiFactory.Api;
|
||||
|
||||
private IMongoClient _client = null!;
|
||||
|
||||
public GetDocumentInformationByFilterLoadTest()
|
||||
{
|
||||
_config = new DatabaseConfiguration
|
||||
{
|
||||
DatabaseName = $"{DocumentCount}Documents",
|
||||
DocumentContainerName = "Documents",
|
||||
AuditContainerName = "Audits"
|
||||
};
|
||||
}
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
_client = _database.SpinUp().Result;
|
||||
_config.ConnectionString = _database.ConnectionString;
|
||||
|
||||
var db = _client.GetDatabase(_config.DatabaseName);
|
||||
db.CreateCollection(_config.DocumentContainerName);
|
||||
db.CreateCollection(_config.AuditContainerName);
|
||||
var documentCollection = db.GetCollection<DocumentInformation>(_config.DocumentContainerName);
|
||||
|
||||
Task.WaitAll
|
||||
(
|
||||
_api.SpinUp(_config),
|
||||
documentCollection.InsertManyAsync(Enumerable.Repeat(() => DocumentFactory(), DocumentCount).Select(f => f()))
|
||||
);
|
||||
}
|
||||
|
||||
[IterationSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_document = DocumentFactory();
|
||||
|
||||
_client.GetDatabase(_config.DatabaseName)
|
||||
.GetCollection<DocumentInformation>(_config.DocumentContainerName)
|
||||
.InsertOne(_document);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void GetDocumentInformationByFilterRun()
|
||||
{
|
||||
var oneThirdAttributes = _document.Attributes.Count / 3;
|
||||
var filter = new DocumentInformationFilter
|
||||
{
|
||||
AttributesContains = _document.Attributes.ToArray()[..oneThirdAttributes].ToDictionary(p => p.Key, p => p.Value),
|
||||
AttributesContainsAny = _document.Attributes.ToArray()[oneThirdAttributes..^oneThirdAttributes].ToDictionary(p => p.Key, p => p.Value),
|
||||
AttributesEquals = _document.Attributes.ToArray()[^oneThirdAttributes..].ToDictionary(p => p.Key, p => p.Value),
|
||||
DataSource = _document.DataSource,
|
||||
Name = _document.Name,
|
||||
Type = _document.Type,
|
||||
Source = _document.Source,
|
||||
ScanStatus = _document.ScanStatus,
|
||||
};
|
||||
|
||||
_api.GetDocumentInformationByFilter(filter).Wait();
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void GlobalCleanup()
|
||||
{
|
||||
_client.DropDatabase(_config.DatabaseName);
|
||||
|
||||
var dbDestruction = _database.TearDown();
|
||||
var apiDestruction = _api.TearDown();
|
||||
|
||||
Task.WaitAll(dbDestruction, apiDestruction);
|
||||
}
|
||||
|
||||
private static readonly Random Random = new Random(DateTime.Now.Millisecond);
|
||||
private static DocumentInformation DocumentFactory() => new()
|
||||
{
|
||||
Name = Guid.NewGuid().ToString(),
|
||||
Type = Random.Next().ToString(),
|
||||
Source = Random.Next().ToString(),
|
||||
DataSource = Random.Next().ToString(),
|
||||
FileSize = Random.Next(),
|
||||
ScanStatus = Random.Next() % 2 == 0,
|
||||
UploadDate = DateTime.Now.AddDays(-Random.Next(1, 10)),
|
||||
LastModifiedDate = DateTime.Now.AddDays(-Random.Next(1, 10)),
|
||||
CreatedDate = DateTime.Now.AddDays(-Random.Next(1, 10)),
|
||||
Attributes = Enumerable
|
||||
.Repeat
|
||||
(
|
||||
() => new KeyValuePair<string, string>(Guid.NewGuid().ToString(), Random.Next().ToString()),
|
||||
Random.Next(10, 100)
|
||||
)
|
||||
.Select(f => f())
|
||||
.ToDictionary(p => p.Key, p => p.Value)
|
||||
};
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
using System.Configuration;
|
||||
using Azure.Identity;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using LCU.API.Internal.Document.PerformanceTest.API;
|
||||
using LCU.API.Internal.DocumentLib.Configurations;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace LCU.API.Internal.Document.PerformanceTest;
|
||||
|
||||
public enum Indexing
|
||||
{
|
||||
Default,
|
||||
FieldSpecific,
|
||||
Wildcard
|
||||
}
|
||||
|
||||
//todo it would be great to get the RU cost output
|
||||
[RPlotExporter]
|
||||
[ShortRunJob]
|
||||
public class IndexingStrategyPerformanceTest
|
||||
{
|
||||
[Params(Indexing.Default, Indexing.FieldSpecific, Indexing.Wildcard)]
|
||||
public Indexing Strategy { get; set; }
|
||||
|
||||
private readonly IDocumentApi _api = ApiFactory.Api;
|
||||
private readonly DatabaseConfiguration _config = new();
|
||||
|
||||
public IndexingStrategyPerformanceTest()
|
||||
{
|
||||
var configBuilder = new ConfigurationBuilder()
|
||||
.AddJsonFile("appsettings.json")
|
||||
.AddJsonFile("appsettings.Development.json");
|
||||
var baseConfig = configBuilder.Build();
|
||||
|
||||
var clientSecretCredentials = new ClientSecretCredential
|
||||
(
|
||||
baseConfig["TenantId"],
|
||||
baseConfig["ClientId"],
|
||||
baseConfig["ClientSecret"]
|
||||
);
|
||||
var vaultLocation = new Uri($"https://{baseConfig["AzureKeyVaultUrl"]}.vault.azure.net/");
|
||||
configBuilder
|
||||
.AddAzureKeyVault(vaultLocation, clientSecretCredentials)
|
||||
.Build()
|
||||
.Bind("MongoDatabase", _config);
|
||||
}
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
switch (Strategy)
|
||||
{
|
||||
case Indexing.Default:
|
||||
break;
|
||||
case Indexing.FieldSpecific:
|
||||
new MongoClient(_config.ConnectionString)
|
||||
.GetDatabase(_config.DatabaseName)
|
||||
.GetCollection<DocumentInformation>(_config.DocumentContainerName)
|
||||
.Indexes
|
||||
.CreateOne
|
||||
(
|
||||
new CreateIndexModel<DocumentInformation>
|
||||
(
|
||||
Builders<DocumentInformation>.IndexKeys.Ascending(d => d.Name),
|
||||
new CreateIndexOptions { Name = nameof(DocumentInformation.Name) }
|
||||
)
|
||||
);
|
||||
break;
|
||||
case Indexing.Wildcard:
|
||||
new MongoClient(_config.ConnectionString)
|
||||
.GetDatabase(_config.DatabaseName)
|
||||
.GetCollection<DocumentInformation>(_config.DocumentContainerName)
|
||||
.Indexes
|
||||
.CreateOne
|
||||
(
|
||||
new CreateIndexModel<DocumentInformation>
|
||||
(
|
||||
Builders<DocumentInformation>.IndexKeys.Wildcard(),
|
||||
new CreateIndexOptions { Name = "wildcard" }
|
||||
)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
_api.SpinUp(_config).Wait();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
//todo report the RU cost in the benchmark
|
||||
public async Task DocumentSearch_ByName()
|
||||
{
|
||||
var filter = new DocumentInformationFilter
|
||||
{
|
||||
Name = "5498-SA"
|
||||
};
|
||||
|
||||
await _api.GetDocumentInformationByFilter(filter);
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
public void GlobalCleanup()
|
||||
{
|
||||
switch (Strategy)
|
||||
{
|
||||
case Indexing.Default:
|
||||
break;
|
||||
case Indexing.FieldSpecific:
|
||||
new MongoClient(_config.ConnectionString)
|
||||
.GetDatabase(_config.DatabaseName)
|
||||
.GetCollection<DocumentInformation>(_config.DocumentContainerName)
|
||||
.Indexes
|
||||
.DropOne(nameof(DocumentInformation.Name));
|
||||
break;
|
||||
case Indexing.Wildcard:
|
||||
new MongoClient(_config.ConnectionString)
|
||||
.GetDatabase(_config.DatabaseName)
|
||||
.GetCollection<DocumentInformation>(_config.DocumentContainerName)
|
||||
.Indexes
|
||||
.DropOne("wildcard");
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.ResourceManager" Version="1.3.1" />
|
||||
<PackageReference Include="Azure.ResourceManager.CosmosDB" Version="1.0.0" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
|
||||
<PackageReference Include="Mongo2Go" Version="3.1.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\LCU.API.Internal.Document\appsettings.json" Link="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LCU.API.Internal.DocumentLib\LCU.API.Internal.DocumentLib.csproj" />
|
||||
<ProjectReference Include="..\LCU.API.Internal.Document\LCU.API.Internal.Document.csproj" />
|
||||
<ProjectReference Include="..\LCU.Library.Internal.DocumentModels\LCU.Library.Internal.DocumentModels.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,5 @@
|
||||
using BenchmarkDotNet.Running;
|
||||
using LCU.API.Internal.Document.PerformanceTest;
|
||||
|
||||
//BenchmarkRunner.Run<GetDocumentInformationByFilterLoadTest>();
|
||||
BenchmarkRunner.Run<IndexingStrategyPerformanceTest>();
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
@@ -0,0 +1,122 @@
|
||||
# Running Benchmarks
|
||||
|
||||
`dotnet run -c Release`
|
||||
|
||||
## past results
|
||||
|
||||
### 2022.09.09 - local mongodb - "old" regex matching
|
||||
note: default mongodb indexing is not the same as CosmosDB Mongo default indexing
|
||||
``` ini
|
||||
|
||||
BenchmarkDotNet=v0.13.2, OS=Windows 10 (10.0.19042.1826/20H2/October2020Update)
|
||||
11th Gen Intel Core i5-1145G7 2.60GHz, 1 CPU, 8 logical and 4 physical cores
|
||||
.NET SDK=6.0.400
|
||||
[Host] : .NET 6.0.8 (6.0.822.36306), X64 RyuJIT AVX2
|
||||
Job-HPFGMU : .NET 6.0.8 (6.0.822.36306), X64 RyuJIT AVX2
|
||||
|
||||
InvocationCount=1 UnrollFactor=1
|
||||
|
||||
```
|
||||
| Method | DocumentCount | Mean | Error | StdDev |
|
||||
|---------------------------------- |-------------- |--------------:|--------------:|--------------:|
|
||||
| **GetDocumentInformationByFilterRun** | **1** | **3.756 ms** | **0.6389 ms** | **1.874 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **10** | **3.868 ms** | **0.6174 ms** | **1.820 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **100** | **8.027 ms** | **1.0094 ms** | **2.976 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **1000** | **41.938 ms** | **7.9785 ms** | **23.525 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **10000** | **337.205 ms** | **51.8841 ms** | **152.167 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **100000** | **3,429.605 ms** | **632.3863 ms** | **1,864.605 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **250000** | **7,896.811 ms** | **1,431.8274 ms** | **4,221.775 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **500000** | **16,629.300 ms** | **2,749.5569 ms** | **8,107.131 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **750000** | **23,883.445 ms** | **4,083.3474 ms** | **11,911.316 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **1000000** | **32,725.371 ms** | **5,751.8744 ms** | **16,778.487 ms** |
|
||||

|
||||
|
||||
### 2022.09.12 - local mongodb - "new" regex matching
|
||||
``` ini
|
||||
|
||||
BenchmarkDotNet=v0.13.2, OS=Windows 10 (10.0.19042.1826/20H2/October2020Update)
|
||||
11th Gen Intel Core i5-1145G7 2.60GHz, 1 CPU, 8 logical and 4 physical cores
|
||||
.NET SDK=6.0.400
|
||||
[Host] : .NET 6.0.8 (6.0.822.36306), X64 RyuJIT AVX2
|
||||
Job-ZGOEWK : .NET 6.0.8 (6.0.822.36306), X64 RyuJIT AVX2
|
||||
|
||||
InvocationCount=1 UnrollFactor=1
|
||||
|
||||
```
|
||||
| Method | DocumentCount | Mean | Error | StdDev | Median |
|
||||
|---------------------------------- |-------------- |--------------:|--------------:|--------------:|--------------:|
|
||||
| **GetDocumentInformationByFilterRun** | **1** | **3.924 ms** | **0.6009 ms** | **1.724 ms** | **3.724 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **10** | **3.675 ms** | **0.6997 ms** | **2.063 ms** | **3.096 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **100** | **5.679 ms** | **0.8358 ms** | **2.464 ms** | **5.332 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **1000** | **35.936 ms** | **5.6629 ms** | **16.697 ms** | **37.860 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **10000** | **305.437 ms** | **53.4368 ms** | **157.560 ms** | **291.339 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **100000** | **2,946.267 ms** | **483.7081 ms** | **1,418.632 ms** | **2,793.991 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **250000** | **8,456.390 ms** | **1,315.9931 ms** | **3,796.941 ms** | **8,575.654 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **500000** | **15,775.558 ms** | **2,830.8954 ms** | **8,167.781 ms** | **15,527.663 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **750000** | **24,948.355 ms** | **3,861.0450 ms** | **11,323.777 ms** | **25,630.684 ms** |
|
||||
| **GetDocumentInformationByFilterRun** | **1000000** | **30,470.661 ms** | **5,342.0572 ms** | **15,583.032 ms** | **29,073.994 ms** |
|
||||

|
||||
|
||||
### 2022.09.28 - indexing strategies
|
||||
``` ini
|
||||
|
||||
BenchmarkDotNet=v0.13.2, OS=Windows 10 (10.0.19042.1826/20H2/October2020Update)
|
||||
11th Gen Intel Core i5-1145G7 2.60GHz, 1 CPU, 8 logical and 4 physical cores
|
||||
.NET SDK=6.0.400
|
||||
[Host] : .NET 6.0.8 (6.0.822.36306), X64 RyuJIT AVX2
|
||||
ShortRun : .NET 6.0.8 (6.0.822.36306), X64 RyuJIT AVX2
|
||||
|
||||
Job=ShortRun IterationCount=3 LaunchCount=1
|
||||
WarmupCount=3
|
||||
|
||||
```
|
||||
| Method | Strategy | Mean | Error | StdDev |
|
||||
|---------------------- |-------------- |----------:|---------:|---------:|
|
||||
| **DocumentSearch_ByName** | **Default** | **171.51 ms** | **68.40 ms** | **3.749 ms** |
|
||||
| **DocumentSearch_ByName** | **FieldSpecific** | **91.36 ms** | **29.19 ms** | **1.600 ms** |
|
||||
| **DocumentSearch_ByName** | **Wildcard** | **94.74 ms** | **78.58 ms** | **4.307 ms** |
|
||||

|
||||
|
||||
## required tools
|
||||
- dotnet 6+
|
||||
-- `choco install dotnet`
|
||||
-- probably installed with visual studio
|
||||
- R
|
||||
-- `choco install r.project` then add "C:\Program Files\R\R-4.2.1\bin" to your path, assuming you used the default install directory and the version is 4.2.1
|
||||
--- better to manually add to the path because setx will only give you 1024 characters. Your path is likely already longer than that and will get truncated.
|
||||
-- this is for outputting visual graphs, not just text tables into standard out
|
||||
|
||||
## output
|
||||
- written to the console
|
||||
- `.\BenchmarkDotNet.Artifacts` relative to where ever you ran the code from
|
||||
|
||||
### graphs
|
||||
The plots should be produced automatically and you'll find them in the results folder.
|
||||
|
||||
If not you can manually generate them:
|
||||
- `cd .\BenchmarkDotNet.Artifacts\results`
|
||||
- `rscript BuildPlots.R`
|
||||
|
||||
## benchmarkdotnet docs
|
||||
https://benchmarkdotnet.org/
|
||||
|
||||
## gotchas
|
||||
|
||||
### Debugging
|
||||
This will be more difficult than you might be accustomed to. By default benchmark dotnet takes control of a lot of parts of the runtime and even rebuilds some parts of the assembly to ensure isolation and consistent optimzation. See their docs for enabling debugging.
|
||||
|
||||
It's possible to run or attach to a release optimized assembly. Some of the things you might expect to be there won't be availabe to the debugger; you're missing debug symbols and the IL has been optimized.
|
||||
|
||||
There's always good old console output and file logging.
|
||||
|
||||
You might also want to try setting up the problematic scenario in a unit test and then debug *that* instead of debugging the benchmark itself.
|
||||
|
||||
### Singletons
|
||||
Benchmarkdotnet ends up invoking msbuild in order to build separate assemblies (for controlling optimization and isolation). This means that, e.g. `Environment.Type = EnvironmentType.Azure;` in program.cs will not affect the benchmarks when they are run; Environment.Type will be recreated every benchmark because benchmarks are running in a different assembly than the `BenchmarkRunner.Run<T>();` statement which caused the benchmark to run.
|
||||
|
||||
One option to deal with this is setting the option inside the benchmark class e.g. in a static constructor (ensuring the value is set before anything else). There are almost surely other options, perhaps a way to pass arguments (other than `[Params()]`) to the benchmark class.
|
||||
|
||||
### Azure Rate Limiting
|
||||
Despite having an Azure environment with code to spin it up and tear it down trying to run a benchmark against a real Azure CosmosDB will likely run you into rate limiting issues. Specifically inserting enough documents to produce a notable database load in a short amount of time. As of 2022.09.13 the rate limit is 1000 records per 5 minutes... You can see where this might produce a problem trying to produce a database on the order of hundreds of thousands of records.
|
||||
|
||||
See https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/request-limits-and-throttling#network-throttling for current information.
|
||||
@@ -0,0 +1,87 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.32112.339
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.API.Internal.Document", "LCU.API.Internal.Document\LCU.API.Internal.Document.csproj", "{424A4E9B-D5C8-4ABF-A073-30CCE4759E57}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.API.Internal.DocumentTest", "LCU.API.Internal.DocumentTest\LCU.API.Internal.DocumentTest.csproj", "{AB05AA6E-F86D-45D0-BF61-4536D6EE6495}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.Library.Internal.DocumentModels", "LCU.Library.Internal.DocumentModels\LCU.Library.Internal.DocumentModels.csproj", "{8D9A978B-DEA8-41A7-8F52-BE0A78E08511}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.API.Internal.DocumentLib", "LCU.API.Internal.DocumentLib\LCU.API.Internal.DocumentLib.csproj", "{A4C80FE4-0880-4884-ABDD-88DA6DDA9EC5}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.API.Internal.Document.PerformanceTest", "LCU.API.Internal.Document.PerformanceTest\LCU.API.Internal.Document.PerformanceTest.csproj", "{9E11130D-8E7A-4A3A-8128-64B2FDDF214B}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Migrations", "Migrations", "{410AA7F0-3C37-4F31-BA3D-2FF85163FB41}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OwnerNumberMigration", "OwnerNumberMigration\OwnerNumberMigration.csproj", "{00B4C125-83E7-4375-9F57-5D5BE17E24B2}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OwnerNumberMigrationTest", "OwnerNumberMigrationTest\OwnerNumberMigrationTest.csproj", "{F21E3FE1-4587-4FE7-ACDD-7ABA5E6FA121}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{25142763-0DF7-4898-A4C6-395DAF0456F9}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.gitignore = .gitignore
|
||||
azure-pipelines-iis.yml = azure-pipelines-iis.yml
|
||||
azure-pipelines.yml = azure-pipelines.yml
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.API.Internal.DocumentLibTest", "LCU.API.Internal.DocumentLibTest\LCU.API.Internal.DocumentLibTest.csproj", "{7755B890-92CC-4811-861F-6C99ABB210CE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LCU.Library.Internal.DocumentModelsTest", "LCU.Library.Internal.DocumentModelsTest\LCU.Library.Internal.DocumentModelsTest.csproj", "{82F351A4-6C78-493D-A76A-5DF57CAC709D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{424A4E9B-D5C8-4ABF-A073-30CCE4759E57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{424A4E9B-D5C8-4ABF-A073-30CCE4759E57}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{424A4E9B-D5C8-4ABF-A073-30CCE4759E57}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{424A4E9B-D5C8-4ABF-A073-30CCE4759E57}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AB05AA6E-F86D-45D0-BF61-4536D6EE6495}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AB05AA6E-F86D-45D0-BF61-4536D6EE6495}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AB05AA6E-F86D-45D0-BF61-4536D6EE6495}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AB05AA6E-F86D-45D0-BF61-4536D6EE6495}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8D9A978B-DEA8-41A7-8F52-BE0A78E08511}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8D9A978B-DEA8-41A7-8F52-BE0A78E08511}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8D9A978B-DEA8-41A7-8F52-BE0A78E08511}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8D9A978B-DEA8-41A7-8F52-BE0A78E08511}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A4C80FE4-0880-4884-ABDD-88DA6DDA9EC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A4C80FE4-0880-4884-ABDD-88DA6DDA9EC5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A4C80FE4-0880-4884-ABDD-88DA6DDA9EC5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A4C80FE4-0880-4884-ABDD-88DA6DDA9EC5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9E11130D-8E7A-4A3A-8128-64B2FDDF214B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9E11130D-8E7A-4A3A-8128-64B2FDDF214B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9E11130D-8E7A-4A3A-8128-64B2FDDF214B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9E11130D-8E7A-4A3A-8128-64B2FDDF214B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{00B4C125-83E7-4375-9F57-5D5BE17E24B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{00B4C125-83E7-4375-9F57-5D5BE17E24B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{00B4C125-83E7-4375-9F57-5D5BE17E24B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{00B4C125-83E7-4375-9F57-5D5BE17E24B2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F21E3FE1-4587-4FE7-ACDD-7ABA5E6FA121}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F21E3FE1-4587-4FE7-ACDD-7ABA5E6FA121}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F21E3FE1-4587-4FE7-ACDD-7ABA5E6FA121}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F21E3FE1-4587-4FE7-ACDD-7ABA5E6FA121}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7755B890-92CC-4811-861F-6C99ABB210CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7755B890-92CC-4811-861F-6C99ABB210CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7755B890-92CC-4811-861F-6C99ABB210CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7755B890-92CC-4811-861F-6C99ABB210CE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{82F351A4-6C78-493D-A76A-5DF57CAC709D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{82F351A4-6C78-493D-A76A-5DF57CAC709D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{82F351A4-6C78-493D-A76A-5DF57CAC709D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{82F351A4-6C78-493D-A76A-5DF57CAC709D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{00B4C125-83E7-4375-9F57-5D5BE17E24B2} = {410AA7F0-3C37-4F31-BA3D-2FF85163FB41}
|
||||
{F21E3FE1-4587-4FE7-ACDD-7ABA5E6FA121} = {410AA7F0-3C37-4F31-BA3D-2FF85163FB41}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {AB145BEF-D8D6-4CB9-BE2C-79F06F523B0D}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
namespace LCU.API.Internal.Document.Configurations;
|
||||
|
||||
public class ApiConfiguration
|
||||
{
|
||||
public string AppName { get; set; }
|
||||
public string SeqUrl { get; set; }
|
||||
public string SeqApiKey { get; set; }
|
||||
public int FileUploadCutoffHours { get; set; }
|
||||
public string[] CorsAllowedOrigins { get; set; }
|
||||
public List<string> MaliciousFileFoundEmailList { get; set; }
|
||||
public bool IsAksDeploy { get; set; }
|
||||
}
|
||||
+588
@@ -0,0 +1,588 @@
|
||||
using LCU.API.Internal.Document.Services;
|
||||
using LCU.Library.Internal.ApiSecurity.Attributes;
|
||||
using LCU.Library.Internal.DocumentModels.Requests;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
using LCU.Library.Internal.DocumentModels.Responses;
|
||||
using LCU.Library.Internal.Exceptions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Serilog;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace LCU.API.Internal.Document.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("[controller]")]
|
||||
public class DocumentController : ControllerBase
|
||||
{
|
||||
private readonly IDocumentService _documentService;
|
||||
private readonly IDocumentAuditService _documentAuditService;
|
||||
private readonly List<DocumentAction> _allowedExternalActions = new()
|
||||
{
|
||||
DocumentAction.Download
|
||||
};
|
||||
|
||||
public DocumentController(IDocumentService documentService,
|
||||
IDocumentAuditService documentAuditService)
|
||||
{
|
||||
_documentService = documentService;
|
||||
_documentAuditService = documentAuditService;
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpGet("{documentId:guid}")]
|
||||
[ProducesResponseType(typeof(DocumentResponse), 200)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetDocumentById(Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.GetDocument(documentId);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentId:{documentId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpGet("{personOrOrgNumber:long}/{documentId:guid}")]
|
||||
[ProducesResponseType(typeof(DocumentResponse), 200)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetMemberDocumentById(long personOrOrgNumber, Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.GetDocument(personOrOrgNumber, documentId);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentId:{documentId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[Obsolete]
|
||||
[HttpGet("GetDocumentByBlobId/{documentBlobId:guid}")]
|
||||
[ProducesResponseType(typeof(byte[]), 200)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetDocumentByBlobId(Guid documentBlobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.GetDocumentByBlobId(documentBlobId);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentBlobId:{documentBlobId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpGet("Content/{documentBlobId:guid}")]
|
||||
[ProducesResponseType(typeof(byte[]), 200)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetDocumentContentByBlobId(Guid documentBlobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.GetDocumentByBlobId(documentBlobId);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentBlobId:{documentBlobId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpGet("{personOrOrgNumber:long}/Content/{documentBlobId:guid}")]
|
||||
[ProducesResponseType(typeof(byte[]), 200)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetMemberDocumentContentByBlobId(long personOrOrgNumber, Guid documentBlobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.GetDocumentByBlobId(personOrOrgNumber, documentBlobId);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentBlobId:{documentBlobId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpPost("Search")]
|
||||
[ProducesResponseType(typeof(List<DocumentInformationResponse>),200)]
|
||||
[ProducesResponseType(204)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> PostDocumentInformationByFilter([FromBody] DocumentInformationFilteredSearch documentInformationFilteredSearch)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.SearchDocumentInformation(documentInformationFilteredSearch.Filter, documentInformationFilteredSearch.Skip, documentInformationFilteredSearch.Take, documentInformationFilteredSearch.SortDefinition);
|
||||
|
||||
if (!result.Any())
|
||||
{
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpGet("AuditInformation/{documentId:guid}")]
|
||||
[ProducesResponseType(typeof(List<DocumentAuditResponse>),200)]
|
||||
[ProducesResponseType(204)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetDocumentAuditInformationById(Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentAuditService.GetDocumentAudits(documentId);
|
||||
|
||||
if (!result.Any())
|
||||
{
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpGet("AuditInformation")]
|
||||
[ProducesResponseType(typeof(List<DocumentAuditResponse>), 200)]
|
||||
[ProducesResponseType(204)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetDocumentAuditInformationByIds([FromQuery]Guid[] documentIds)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentAuditService.GetDocumentAudits(documentIds);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[Authorize]
|
||||
[HttpPost("{documentId:guid}/Actions")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> ExternalActionPerformed([FromRoute]Guid documentId, [FromBody] DocumentActionNotificationRequest action)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_allowedExternalActions.Contains(action.Action))
|
||||
{
|
||||
throw new BadHttpRequestException($"{action.Action.ToString()} action type not allowed externally.");
|
||||
}
|
||||
|
||||
await _documentAuditService.AuditAction(documentId,
|
||||
HttpContext.User.Claims.Single(c => c.Type == "name").Value, action.Source, action.Action);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
catch (BadHttpRequestException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status400BadRequest, ex.Message);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpPost]
|
||||
[ProducesResponseType(201)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> UploadDocument([FromBody] DocumentRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.UploadDocument(request);
|
||||
return CreatedAtAction(nameof(GetDocumentById), new { documentId = result.DocumentId }, result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[Authorize]
|
||||
[HttpPut("{documentId:guid}")]
|
||||
[ProducesResponseType(typeof(DocumentInformationResponse),200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> UpdateDocument([FromBody] UpdateDocumentRequest request, Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.UpdateDocument(request, documentId, HttpContext.User.Claims.Single(c => c.Type == "name").Value);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentId:{documentId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[Authorize]
|
||||
[HttpPut("DocumentContent/{documentId:guid}")]
|
||||
[ProducesResponseType(typeof(DocumentInformationResponse),200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> UpdateDocumentContent([FromBody] UpdateDocumentContentRequest request, Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.UpdateDocumentContent(request, documentId, HttpContext.User.Claims.Single(c => c.Type == "name").Value);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentId:{documentId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[Authorize]
|
||||
[HttpPut("DocumentInformation/{documentId:guid}")]
|
||||
[ProducesResponseType(typeof(DocumentInformationResponse),200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> UpdateDocumentInformation([FromBody] UpdateDocumentInformationRequest request,
|
||||
Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.UpdateDocumentInformation(request, documentId, HttpContext.User.Claims.Single(c => c.Type == "name").Value);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentId:{documentId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpGet("DocumentInformation/{documentId:guid}")]
|
||||
[ProducesResponseType(typeof(DocumentInformationResponse),200)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetDocumentInformation(Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _documentService.GetDocumentInformation(documentId);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound($"The document with DocumentId:{documentId} could not be found.");
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[Authorize]
|
||||
[HttpPut("DocumentScanStatus")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(400)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(404)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> UpdateDocumentScanStatus([FromBody] UpdateDocumentScanStatusRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _documentService.UpdateDocumentScanStatus(request.ScanStatus, request.DocumentBlobIds, HttpContext.User.Claims.Single(c => c.Type == "name").Value);
|
||||
return Ok();
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is currently a developer endpoint, still waiting on guidance on how the 'Delete' Operation
|
||||
/// should work. This will remove both the documents contents from blob storage and its information from cosmos.
|
||||
/// </summary>
|
||||
/// <param name="documentId"></param>
|
||||
/// <returns></returns>
|
||||
[ApiKey]
|
||||
[HttpDelete("{documentId:guid}")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(204)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> DeleteDocument(Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _documentService.DeleteDocument(documentId);
|
||||
return Ok();
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpDelete("DeleteDocumentContent")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(204)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> DeleteDocumentContent([FromBody] DeleteDocumentContentRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _documentService.DeleteDocumentContent(request);
|
||||
return Ok();
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, ex.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Unexpected Exception in {GetType().Name}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[ApiKey]
|
||||
[HttpPost("Count")]
|
||||
[ProducesResponseType(typeof(long), 200)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> CountDocuments(DocumentInformationFilter filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
var count = await _documentService.CountDocuments(filter);
|
||||
|
||||
return Ok(count);
|
||||
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, "Unexpected Exception");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[ApiKey]
|
||||
[HttpPost("GetDocumentsForCleanup")]
|
||||
[ProducesResponseType(typeof(IEnumerable<Guid>), 200)]
|
||||
[ProducesResponseType(204)]
|
||||
[ProducesResponseType(401)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetDocumentsForCleanup([FromBody] GetDocumentCleanupRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var documents = await _documentService.GetDocumentsForCleanup(request);
|
||||
|
||||
if (!documents.Any())
|
||||
{
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
return Ok(documents);
|
||||
}
|
||||
catch (ServiceException ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, "Unexpected Exception");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Net;
|
||||
using LCU.API.Internal.Document.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Serilog;
|
||||
using LCU.Library.Internal.ApiSecurity.Attributes;
|
||||
|
||||
namespace LCU.API.Internal.Document.Controllers;
|
||||
|
||||
[Route("[controller]")]
|
||||
[ApiController]
|
||||
public class StatusController : ControllerBase
|
||||
{
|
||||
private readonly IEnvironmentView _environmentView;
|
||||
|
||||
public StatusController(IEnvironmentView environmentView)
|
||||
{
|
||||
_environmentView = environmentView;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult Get()
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Verbose($"{_environmentView.Application}'s Status Endpoint Was Hit");
|
||||
return Ok(_environmentView);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure to return status");
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
namespace LCU.API.Internal.Document.Exceptions;
|
||||
|
||||
public class BlobApiServiceException : Exception
|
||||
{
|
||||
public BlobApiServiceException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public BlobApiServiceException(string message, Exception exception) : base(message, exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
namespace LCU.API.Internal.Document.Exceptions;
|
||||
|
||||
public class DocumentAuditServiceException : Exception
|
||||
{
|
||||
public DocumentAuditServiceException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public DocumentAuditServiceException(string message, Exception exception) : base(message, exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
namespace LCU.API.Internal.Document.Exceptions;
|
||||
|
||||
public class DocumentInformationServiceException : Exception
|
||||
{
|
||||
public DocumentInformationServiceException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public DocumentInformationServiceException(string message, Exception exception) : base(message, exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<NoWarn>1701;1702; 8618; 1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<NoWarn>1701;1702; 8618; 1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.2" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.13.2" />
|
||||
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.3.0" />
|
||||
<PackageReference Include="LCU.Api.Internal.MaidBotLib" Version="1.0.0" />
|
||||
<PackageReference Include="LCU.Library.Internal.ApiSecurity" Version="1.0.1" />
|
||||
<PackageReference Include="LCU.Library.Internal.Exceptions" Version="1.0.0" />
|
||||
<PackageReference Include="LCU.Library.Internal.Logging" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.FeatureManagement.AspNetCore" Version="2.5.1" />
|
||||
<PackageReference Include="Microsoft.Identity.Web" Version="1.25.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||
<PackageReference Include="LCU.Library.Internal.Configuration" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LCU.API.Internal.DocumentLib\LCU.API.Internal.DocumentLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ProjectExtensions><VisualStudio><UserProperties appsettings_1staging_1json__JsonSchema="https://github.com/fbecart/zinoma/releases/latest/download/zinoma-schema.json" /></VisualStudio></ProjectExtensions>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="LCU.Library.Internal.DocumentModelsTest" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace LCU.API.Internal.Document.Models;
|
||||
|
||||
public class EnvironmentView : IEnvironmentView
|
||||
{
|
||||
private readonly IWebHostEnvironment _env;
|
||||
|
||||
public EnvironmentView(IWebHostEnvironment env)
|
||||
{
|
||||
_env = env;
|
||||
}
|
||||
|
||||
public string Application => _env.ApplicationName;
|
||||
public DateTime CurrentDate => DateTime.Now;
|
||||
public string BuildVersion => AssembleBuildVersion();
|
||||
public DateTime BuildDate => AssemblyBuildDate();
|
||||
public string Environment => _env.EnvironmentName;
|
||||
|
||||
private static DateTime AssemblyBuildDate()
|
||||
{
|
||||
//https://stackoverflow.com/a/804310/266594
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var fileInfo = new FileInfo(assembly.Location);
|
||||
var lastModified = fileInfo.LastWriteTime;
|
||||
|
||||
return lastModified;
|
||||
}
|
||||
|
||||
private static string AssembleBuildVersion()
|
||||
{
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
return version == null ? "-.-.-" : version.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace LCU.API.Internal.Document.Models;
|
||||
|
||||
public interface IEnvironmentView
|
||||
{
|
||||
string Application { get; }
|
||||
DateTime CurrentDate { get; }
|
||||
string BuildVersion { get; }
|
||||
DateTime BuildDate { get; }
|
||||
string Environment { get; }
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Azure.Identity;
|
||||
using LCU.API.Internal.Document.Configurations;
|
||||
using LCU.API.Internal.Document.Models;
|
||||
using LCU.API.Internal.Document.Services;
|
||||
using LCU.API.Internal.DocumentLib.Configurations;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.API.Internal.DocumentLib.DataAccess;
|
||||
using LCU.API.Internal.DocumentLib.Repositories;
|
||||
using LCU.Library.Internal.Logging;
|
||||
using LCU.Library.Internal.Logging.Middleware;
|
||||
using LCU.Library.Internal.Logging.Models;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using LCU.Library.Internal.ApiSecurity;
|
||||
using Serilog;
|
||||
using LCU.API.Internal.DocumentLib.Utilities;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.Identity.Web;
|
||||
using MongoDB.Bson.Serialization;
|
||||
using LCU.Library.Internal.Configuration;
|
||||
using Microsoft.FeatureManagement;
|
||||
|
||||
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
|
||||
|
||||
var builder = BuildBuilder(args);
|
||||
BuildServicesAndConfigurations(builder);
|
||||
BuildAppAndRun(builder);
|
||||
|
||||
WebApplicationBuilder BuildBuilder(string[] args)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Configuration.AddJsonFile("/app/config/mapped-appsettings/primary.json", true, true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/documentapi_configuration.json", true, true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-azure-client-id.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-azure-tenant-id.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-client-id.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-client-secret.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-tenant-id.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-swagger-oauth-scope.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-seq-api-key.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-blob-storage-connection-string.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-mongo-database-connection-string.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-type-uploader-api-key.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-type-retriever-api-key.json", optional: true, reloadOnChange: true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/document-api-type-cleanup-api-key.json", optional: true, reloadOnChange: true);
|
||||
|
||||
if (!bool.Parse(builder.Configuration.GetSection("Primary")["IsAksDeploy"]))
|
||||
{
|
||||
var clientSecretCredentials = new ClientSecretCredential(builder.Configuration["TenantId"], builder.Configuration["ClientId"], builder.Configuration["ClientSecret"]);
|
||||
|
||||
builder.Configuration.AddAzureKeyVault(
|
||||
new Uri($"https://{builder.Configuration["AzureKeyVaultUrl"]}.vault.azure.net/"), clientSecretCredentials);
|
||||
}
|
||||
|
||||
builder.Host.UseSerilog();
|
||||
return builder;
|
||||
}
|
||||
|
||||
void BuildServicesAndConfigurations(WebApplicationBuilder builder)
|
||||
{
|
||||
var services = builder.Services;
|
||||
var configuration = builder.Configuration;
|
||||
|
||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddMicrosoftIdentityWebApi(configuration.GetSection("AzureAd"));
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("user_token", policy =>
|
||||
{
|
||||
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
|
||||
policy.RequireAuthenticatedUser();
|
||||
});
|
||||
options.DefaultPolicy = options.GetPolicy("user_token")!;
|
||||
});
|
||||
|
||||
services.AddFeatureManagement();
|
||||
|
||||
var documentTypeConfiguration = configuration.Bind<ApiConfiguration>(services, "Primary");
|
||||
configuration.Bind<BlobStorageConfiguration>(services, "BlobStorage");
|
||||
configuration.Bind<DatabaseConfiguration>(services, "MongoDatabase");
|
||||
configuration.Bind<NotificationApiConfiguration>(services);
|
||||
|
||||
var loggerConfiguration = new LoggingConfiguration(documentTypeConfiguration.AppName,
|
||||
documentTypeConfiguration.SeqApiKey,
|
||||
documentTypeConfiguration.SeqUrl);
|
||||
|
||||
var lcuLogging = new LcuLogging(loggerConfiguration);
|
||||
lcuLogging.UseLcuLogging();
|
||||
|
||||
ServiceRegistrations.AddDocumentServices(services);
|
||||
ServiceRegistrations.AddDocumentDataAccesses(services);
|
||||
ServiceRegistrations.AddDocumentRepositories(services);
|
||||
|
||||
BsonSerializer.RegisterDiscriminatorConvention(typeof(DocumentAudit), new DocumentAuditDiscriminatorConvention());
|
||||
|
||||
services.AddControllers().AddJsonOptions(options => options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
|
||||
services.AddEndpointsApiExplorer();
|
||||
services.AddSwaggerGen(s =>
|
||||
{
|
||||
s.SwaggerDoc("v1", new OpenApiInfo { Title = $"{documentTypeConfiguration.AppName}", Version = "v1" });
|
||||
s.UseOneOfForPolymorphism();
|
||||
|
||||
var authority = $"https://login.microsoftonline.com/{configuration.GetSection("AzureAd")["TenantId"]}";
|
||||
s.AddSecurityDefinition
|
||||
(
|
||||
nameof(SecuritySchemeType.OpenIdConnect),
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Type = SecuritySchemeType.OAuth2,
|
||||
Flows = new OpenApiOAuthFlows
|
||||
{
|
||||
AuthorizationCode = new OpenApiOAuthFlow
|
||||
{
|
||||
AuthorizationUrl = new Uri($"{authority}/oauth2/v2.0/authorize"),
|
||||
TokenUrl = new Uri($"{authority}/oauth2/v2.0/token")
|
||||
}
|
||||
}
|
||||
//24.10.2022 these SHOULD have enabled OIDC discovery (the discovery info is returned from this URI) but swagger UI is not displaying the auth options
|
||||
//oidc discovery does work, I have set it up with IdentityServer4 in the past
|
||||
//Type = SecuritySchemeType.OpenIdConnect,
|
||||
//OpenIdConnectUrl = new Uri($"https://login.microsoftonline.com/{configuration.GetSection("AzureAd")["TenantId"]}/v2.0/.well-known/openid-configuration")
|
||||
}
|
||||
);
|
||||
s.AddSecurityRequirement
|
||||
(
|
||||
new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = nameof(SecuritySchemeType.OpenIdConnect)
|
||||
}
|
||||
},
|
||||
Array.Empty<string>()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
s.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Name = "ApiKey"
|
||||
});
|
||||
s.AddSecurityDefinition("ApiClient", new OpenApiSecurityScheme
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Name = "Client"
|
||||
});
|
||||
s.AddSecurityRequirement
|
||||
(
|
||||
new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "ApiKey"
|
||||
}
|
||||
},
|
||||
Array.Empty<string>()
|
||||
}
|
||||
}
|
||||
);
|
||||
s.AddSecurityRequirement
|
||||
(
|
||||
new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "ApiClient"
|
||||
}
|
||||
},
|
||||
Array.Empty<string>()
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
services.AddApiKeySecurity(configuration);
|
||||
}
|
||||
|
||||
void BuildAppAndRun(WebApplicationBuilder builder)
|
||||
{
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment()) app.UseDeveloperExceptionPage();
|
||||
|
||||
app.UseSwagger(s =>
|
||||
{
|
||||
s.PreSerializeFilters.Add((doc, request) =>
|
||||
{
|
||||
doc.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{request.Scheme}://{request.Host.Value}{request.PathBase}" } };
|
||||
});
|
||||
});
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
c.SwaggerEndpoint("v1/swagger.json", "LCU.API.Internal.Document v1");
|
||||
|
||||
c.OAuthClientId(app.Configuration.GetSection("AzureAd")["ClientId"]);
|
||||
c.OAuthUsePkce();
|
||||
c.OAuthScopes(app.Configuration.GetSection("Swagger")["OAuthScope"]);
|
||||
});
|
||||
app.UseMiddleware<RequestResponseLoggingMiddleware>();
|
||||
app.UseSerilogRequestLogging(options =>
|
||||
{
|
||||
options.EnrichDiagnosticContext = RequestResponseLoggingMiddleware.RequestDataParser.EnrichFromRequest;
|
||||
});
|
||||
|
||||
app.UseRouting();
|
||||
app.UseCors(MyAllowSpecificOrigins);
|
||||
app.UseHttpsRedirection();
|
||||
app.UseAuthentication().UseAuthorization();
|
||||
app.MapControllers();
|
||||
app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
|
||||
|
||||
app.Run();
|
||||
}
|
||||
|
||||
public static class ServiceRegistrations
|
||||
{
|
||||
public static void AddDocumentServices(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<DocumentService.DocumentIsForMemberStrategy>(p =>
|
||||
{
|
||||
const string checkDocumentOwner = "GetDocumentCheckOwnership";
|
||||
|
||||
return p.GetRequiredService<IFeatureManager>().IsEnabledAsync(checkDocumentOwner).Result
|
||||
? DocumentService.Ownership.CheckPersonOrOrgNumber.Strategy
|
||||
: DocumentService.Ownership.Unchecked.Strategy;
|
||||
});
|
||||
services.AddScoped<IDocumentService, DocumentService>();
|
||||
services.AddScoped<IDocumentInformationService, DocumentInformationService>();
|
||||
services.AddScoped<IBlobApiService, BlobApiService>();
|
||||
services.AddScoped<IDocumentAuditService, DocumentAuditService>();
|
||||
services.AddScoped<IEnvironmentView, EnvironmentView>();
|
||||
services.AddScoped<INotificationApiService, NotificationApiService>();
|
||||
}
|
||||
|
||||
public static void AddDocumentDataAccesses(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IBlobStorageClient, BlobStorageClient>();
|
||||
services.AddScoped<IBlobApiDao, BlobApiDao>();
|
||||
}
|
||||
|
||||
public static void AddDocumentRepositories(IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<IDataClient, DataClient>();
|
||||
services.AddTransient<IMongoCollectionFactory, MongoCollectionFactory>();
|
||||
services.AddScoped<IDocumentInformationRepository, DocumentInformationRepository>();
|
||||
services.AddScoped<IDocumentAuditRepository, DocumentAuditRepository>();
|
||||
services.AddScoped<INotificationApiDao, NotificationApiDao>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:9009",
|
||||
"sslPort": 44312
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"LCU.API.Internal.Document": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7037;http://localhost:5037",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using LCU.API.Internal.Document.Exceptions;
|
||||
using LCU.API.Internal.DocumentLib.DataAccess;
|
||||
using LCU.Library.Internal.Exceptions;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services;
|
||||
|
||||
public class BlobApiService : IBlobApiService
|
||||
{
|
||||
private readonly IBlobApiDao _blobApiDao;
|
||||
|
||||
public BlobApiService(IBlobApiDao blobApiDao)
|
||||
{
|
||||
_blobApiDao = blobApiDao;
|
||||
}
|
||||
|
||||
public async Task<byte[]> GetDocumentContent(Guid? documentBlobId)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
if (documentBlobId is null || documentBlobId == Guid.Empty)
|
||||
{
|
||||
return Array.Empty<byte>();
|
||||
}
|
||||
|
||||
var result = await _blobApiDao.GetDocumentFromBlob(documentBlobId ?? Guid.Empty);
|
||||
|
||||
if (result?.Value == null){
|
||||
throw new ServiceException($"Could not find the Document Content for DocumentBlobId:{documentBlobId}");
|
||||
}
|
||||
|
||||
await using var memoryStream = new MemoryStream();
|
||||
await result.Value.Content.CopyToAsync(memoryStream);
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
catch (ServiceException exception){
|
||||
Log.Error(exception.Message);
|
||||
throw new BlobApiServiceException(exception.Message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Retrieve Document Content with DocumentBlobId: {documentBlobId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new BlobApiServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Guid> UploadDocumentContent(byte[] content)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _blobApiDao.SaveDocumentToBlob(content);
|
||||
}
|
||||
catch (Exception exception){
|
||||
const string message = "Failure to Upload Document Content.";
|
||||
Log.Error(exception, message);
|
||||
throw new BlobApiServiceException($"{message}. Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateDocumentContent(Guid? documentBlobId, byte[] content)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (documentBlobId is null || documentBlobId == Guid.Empty)
|
||||
{
|
||||
throw new Exception("DocumentBlobId cannot be null.");
|
||||
}
|
||||
|
||||
await _blobApiDao.UpdateDocumentInBlob(documentBlobId ?? Guid.Empty, content);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Update Document with DocumentBlobId: {documentBlobId}";
|
||||
Log.Error(exception, message);
|
||||
throw new BlobApiServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteDocumentContent(Guid? documentBlobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (documentBlobId is null || documentBlobId == Guid.Empty)
|
||||
{
|
||||
throw new Exception("DocumentBlobId cannot be null.");
|
||||
}
|
||||
|
||||
await _blobApiDao.RemoveDocumentFromBlob(documentBlobId ?? Guid.Empty);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Delete Document with DocumentBlobId: {documentBlobId}";
|
||||
Log.Error(exception, message);
|
||||
throw new BlobApiServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
using LCU.API.Internal.Document.Exceptions;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.API.Internal.DocumentLib.Repositories;
|
||||
using LCU.Library.Internal.DocumentModels.Requests;
|
||||
using LCU.Library.Internal.DocumentModels.Responses;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services;
|
||||
|
||||
public class DocumentAuditService : IDocumentAuditService
|
||||
{
|
||||
private const string NameField = "Name";
|
||||
private const string TypeField = "Type";
|
||||
private const string AttributesField = "Attributes";
|
||||
private const string ScanStatusField = "ScanStatus";
|
||||
private const string ContentField = "Content";
|
||||
private readonly IDocumentAuditRepository _documentAuditRepository;
|
||||
|
||||
public DocumentAuditService(IDocumentAuditRepository documentAuditRepository)
|
||||
{
|
||||
_documentAuditRepository = documentAuditRepository;
|
||||
}
|
||||
|
||||
public async Task<List<DocumentAuditResponse>> GetDocumentAudits(params Guid[] documentIds)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
static DocumentAuditResponse ResponseFactory(DocumentAudit audit) => audit switch
|
||||
{
|
||||
DocumentActionAudit actionAudit => new DocumentActionAuditResponse
|
||||
{
|
||||
Action = actionAudit.Action,
|
||||
ActionDescription = actionAudit.ActionDescription,
|
||||
Date = actionAudit.Date,
|
||||
DocumentId = actionAudit.DocumentId,
|
||||
Source = actionAudit.Source,
|
||||
UserId = actionAudit.UserId
|
||||
},
|
||||
DocumentFieldAudit fieldAudit => new DocumentFieldAuditResponse
|
||||
{
|
||||
Field = fieldAudit.Field,
|
||||
CurrentValue = fieldAudit.CurrentValue,
|
||||
PreviousValue = fieldAudit.PreviousValue,
|
||||
Date = fieldAudit.Date,
|
||||
DocumentId = fieldAudit.DocumentId,
|
||||
Source = fieldAudit.Source,
|
||||
UserId = fieldAudit.UserId
|
||||
},
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(audit))
|
||||
};
|
||||
|
||||
return (await _documentAuditRepository.GetDocumentAudits(documentIds))
|
||||
.Select(ResponseFactory)
|
||||
.ToList();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Retrieve Document Audit Information for DocumentIds: {documentIds}";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentAuditServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DocumentAudit>> CreateDocumentUploadedAudit(Guid documentId, DateTime uploadDate)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
var audits = new DocumentAudit[]
|
||||
{
|
||||
new DocumentActionAudit
|
||||
(
|
||||
documentId,
|
||||
"System",
|
||||
uploadDate,
|
||||
"System",
|
||||
DocumentAction.Upload,
|
||||
"Document Uploaded"
|
||||
)
|
||||
};
|
||||
|
||||
return await _documentAuditRepository.SaveDocumentAudits(audits);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Create Document Audit for DocumentId: {documentId}";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentAuditServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<DocumentAudit>> CreateDocumentDeletedAudit(Guid documentId,
|
||||
DateTime deleteDate)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
var audits = new DocumentAudit[]
|
||||
{
|
||||
new DocumentActionAudit
|
||||
(
|
||||
documentId,
|
||||
"System",
|
||||
deleteDate,
|
||||
"System",
|
||||
DocumentAction.Delete,
|
||||
"Document Deleted"
|
||||
)
|
||||
};
|
||||
|
||||
return await _documentAuditRepository.SaveDocumentAudits(audits);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Create Document Audit for DocumentId: {documentId}";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentAuditServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<IEnumerable<DocumentAudit>> CreateDocumentContentDeletedAudit(Guid documentId,
|
||||
DateTime deleteDate,
|
||||
string? reason)
|
||||
{
|
||||
try
|
||||
{
|
||||
var audits = new DocumentAudit[]
|
||||
{
|
||||
new DocumentActionAudit
|
||||
(
|
||||
documentId,
|
||||
"System",
|
||||
deleteDate,
|
||||
"System",
|
||||
DocumentAction.DeleteContent,
|
||||
"DocumentContent Deleted" + (!string.IsNullOrWhiteSpace(reason) ? $". Reason: {reason}" : string.Empty)
|
||||
)
|
||||
};
|
||||
|
||||
return await _documentAuditRepository.SaveDocumentAudits(audits);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Create Document Audit for DocumentId: {documentId}";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentAuditServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DocumentAudit>> CreateDocumentAudits(DocumentInformation oldDocumentInformation,
|
||||
DocumentInformation newDocumentInformation, bool wasDocumentContentUpdated, DateTime updateDate, string userId)
|
||||
{
|
||||
DocumentFieldAudit AuditLogFactory(string field, object? oldValue, object? newValue) => new
|
||||
(
|
||||
newDocumentInformation.DocumentId,
|
||||
newDocumentInformation.Source,
|
||||
updateDate,
|
||||
userId,
|
||||
field,
|
||||
oldValue,
|
||||
newValue
|
||||
);
|
||||
|
||||
var documentAudits = new List<DocumentFieldAudit>();
|
||||
|
||||
try
|
||||
{
|
||||
if (oldDocumentInformation.Name != newDocumentInformation.Name){
|
||||
documentAudits.Add(AuditLogFactory(NameField, oldDocumentInformation.Name, newDocumentInformation.Name));
|
||||
}
|
||||
|
||||
if (oldDocumentInformation.Type != newDocumentInformation.Type){
|
||||
documentAudits.Add(AuditLogFactory(TypeField, oldDocumentInformation.Type, newDocumentInformation.Type));
|
||||
}
|
||||
|
||||
if (oldDocumentInformation.ScanStatus != newDocumentInformation.ScanStatus){
|
||||
documentAudits.Add(AuditLogFactory(ScanStatusField, oldDocumentInformation.ScanStatus, newDocumentInformation.ScanStatus));
|
||||
}
|
||||
|
||||
if (!AttributesAreEqual(oldDocumentInformation.Attributes, newDocumentInformation.Attributes)){
|
||||
documentAudits.Add(AuditLogFactory(AttributesField, oldDocumentInformation.Attributes, newDocumentInformation.Attributes));
|
||||
}
|
||||
|
||||
if (wasDocumentContentUpdated){
|
||||
documentAudits.Add(AuditLogFactory(ContentField, "NOT CAPTURED", "NOT CAPTURED"));
|
||||
}
|
||||
|
||||
if (!documentAudits.Any()){
|
||||
return new List<DocumentAudit>();
|
||||
}
|
||||
|
||||
return await _documentAuditRepository.SaveDocumentAudits(documentAudits.ToArray());
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message =
|
||||
$"Failure to Create Document Audit Information for DocumentId: {oldDocumentInformation.DocumentId}";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentAuditServiceException($"{message}. Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AuditAction(Guid documentId, string userId, string actionSource, DocumentAction action)
|
||||
{
|
||||
try
|
||||
{
|
||||
var description = action switch
|
||||
{
|
||||
DocumentAction.Download => $"Document downloaded from {actionSource}.",
|
||||
DocumentAction.Upload => "Document Uploaded",
|
||||
DocumentAction.Delete => "Document Deleted",
|
||||
DocumentAction.DeleteContent => "DocumentContent Deleted",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
await _documentAuditRepository.SaveDocumentAudits(new DocumentActionAudit(documentId, actionSource, DateTime.Now, userId, action, description));
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message =
|
||||
$"Failure to Create Document Audit Information for DocumentId: {documentId} on DocumentAction: {action} from source {actionSource}";
|
||||
Log.Error(exception, message);
|
||||
|
||||
throw new DocumentAuditServiceException($"{message}. Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool AttributesAreEqual(Dictionary<string, string>? oldAttributes, Dictionary<string, string>? newAttributes)
|
||||
{
|
||||
oldAttributes ??= new Dictionary<string, string>();
|
||||
newAttributes ??= new Dictionary<string, string>();
|
||||
|
||||
if (oldAttributes.Count != newAttributes.Count){
|
||||
return false;
|
||||
}
|
||||
|
||||
var attributes = oldAttributes
|
||||
.Where(d2 => newAttributes.Any(d1 => d1.Key == d2.Key && d2.Value.Equals(d1.Value))).ToList();
|
||||
return attributes.Count == oldAttributes.Count;
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
using LCU.API.Internal.Document.Configurations;
|
||||
using LCU.API.Internal.Document.Exceptions;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.API.Internal.DocumentLib.Repositories;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Models.Pagination;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services;
|
||||
|
||||
public class DocumentInformationService : IDocumentInformationService
|
||||
{
|
||||
private readonly IDocumentInformationRepository _documentInformationRepository;
|
||||
private readonly ApiConfiguration _apiConfiguration;
|
||||
|
||||
public DocumentInformationService(IDocumentInformationRepository documentInformationRepository,
|
||||
ApiConfiguration apiConfiguration)
|
||||
{
|
||||
_documentInformationRepository = documentInformationRepository;
|
||||
_apiConfiguration = apiConfiguration;
|
||||
}
|
||||
|
||||
public async Task<DocumentInformation?> GetDocumentInformation(Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var documentInformation = await _documentInformationRepository.GetDocumentInformation(documentId);
|
||||
|
||||
if (documentInformation == null ||
|
||||
documentInformation.UploadDate >= DateTime.UtcNow.AddHours(_apiConfiguration.FileUploadCutoffHours) ||
|
||||
documentInformation.ScanStatus == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return documentInformation;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Retrieve DocumentInformation with DocumentId: {documentId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentInformationServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentInformation?> GetDocumentInformationByBlobId(Guid? documentBlobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (documentBlobId is null || documentBlobId == Guid.Empty)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var documentInformation = await _documentInformationRepository.GetDocumentInformationByBlobId(documentBlobId ?? Guid.Empty);
|
||||
return documentInformation ?? null;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Retrieve DocumentInformation with DocumentBlobId: {documentBlobId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentInformationServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteDocumentInformation(Guid documentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var documentInformation = await _documentInformationRepository.GetDocumentInformation(documentId);
|
||||
|
||||
if (documentInformation == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _documentInformationRepository.RemoveDocumentInformation(documentId);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Delete DocumentInformation with DocumentId: {documentId}";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentInformationServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentInformation> UploadDocumentInformation(DocumentInformation information)
|
||||
{
|
||||
try{
|
||||
return await _documentInformationRepository.SaveDocumentInformation(information);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Upload DocumentInformation with Name: {information.Name} of type: {information.Type} from source: {information.Source}";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentInformationServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentInformation?> UpdateDocumentInformation(Guid documentId, DocumentInformation information)
|
||||
{
|
||||
try
|
||||
{
|
||||
var documentInformation = await _documentInformationRepository.GetDocumentInformation(documentId);
|
||||
|
||||
if (documentInformation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return await _documentInformationRepository.UpdateDocumentInformation(documentId, information);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Update DocumentInformation with DocumentId: {documentId} and Name: {information.Name} of type: {information.Type}.";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentInformationServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<DocumentInformation>> SearchDocumentInformation(DocumentInformationFilter filter, int? skip = null, int? take = null, List<SortableField>? sortDefinition = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var documentInformationFound = await _documentInformationRepository.SearchDocumentInformation(filter, skip, take, sortDefinition);
|
||||
return documentInformationFound ?? new List<DocumentInformation>();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
const string message = "Failure to Search Document Information";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentInformationServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<long> CountDocuments(DocumentInformationFilter filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _documentInformationRepository.CountDocuments(filter);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
const string message = "Failure to Count Document Information";
|
||||
Log.Error(exception, message);
|
||||
|
||||
throw new DocumentInformationServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<Guid>> GetDocumentsForCleanup(GetDocumentCleanupRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _documentInformationRepository.GetDocumentsForCleanup(request);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
const string message = "Failure to GetDocumentsForCleanup";
|
||||
Log.Error(exception, message);
|
||||
throw new DocumentInformationServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,748 @@
|
||||
using System.Numerics;
|
||||
using System.Text.Json;
|
||||
using LCU.API.Internal.Document.Exceptions;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.Library.Internal.DocumentModels.Requests;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
using LCU.Library.Internal.DocumentModels.Responses;
|
||||
using LCU.Library.Internal.Exceptions;
|
||||
using LCU.Api.Internal.MaidBotLib.Models;
|
||||
using Serilog;
|
||||
using LCU.API.Internal.NotificationsApiLib.Models;
|
||||
using LCU.API.Internal.Document.Configurations;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Models.Pagination;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services;
|
||||
|
||||
// TODO: Add hash of content to document information so the content can be compared before updates.
|
||||
// Any basic hash should be fine since it will only be used as a checksum to the content.
|
||||
// Example can be found https://stackoverflow.com/questions/10520048/calculate-md5-checksum-for-a-file/10520086#10520086
|
||||
public class DocumentService : IDocumentService
|
||||
{
|
||||
private readonly IBlobApiService _blobApiService;
|
||||
private readonly IDocumentInformationService _documentInformationService;
|
||||
private readonly IDocumentAuditService _documentAuditService;
|
||||
private readonly INotificationApiService _notificationApiService;
|
||||
private readonly ApiConfiguration _apiConfiguration;
|
||||
private readonly DocumentIsForMemberStrategy DocumentIsForMember;
|
||||
|
||||
public DocumentService
|
||||
(
|
||||
IBlobApiService blobApiService,
|
||||
IDocumentInformationService documentInformationService,
|
||||
IDocumentAuditService documentAuditService,
|
||||
INotificationApiService notificationApiService,
|
||||
ApiConfiguration apiConfiguration,
|
||||
DocumentIsForMemberStrategy documentIsForMember
|
||||
)
|
||||
{
|
||||
_blobApiService = blobApiService;
|
||||
_documentInformationService = documentInformationService;
|
||||
_documentAuditService = documentAuditService;
|
||||
_notificationApiService = notificationApiService;
|
||||
_apiConfiguration = apiConfiguration;
|
||||
DocumentIsForMember = documentIsForMember;
|
||||
}
|
||||
|
||||
public async Task<DocumentResponse?> GetDocument(Guid documentId)
|
||||
{
|
||||
try{
|
||||
var documentInformation = await _documentInformationService.GetDocumentInformation(documentId);
|
||||
|
||||
if (documentInformation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new DocumentResponse
|
||||
{
|
||||
Content = await _blobApiService.GetDocumentContent(documentInformation.DocumentBlobId),
|
||||
DocumentInformation = ConvertDocumentInformationToDocumentInformationResponse(documentInformation)
|
||||
};
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
var message = $"Failure to Retrieve Document Information. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (BlobApiServiceException exception){
|
||||
var message =
|
||||
$"Failure to Retrieve Document Content from Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Retrieve Document with DocumentId: {documentId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentResponse?> GetDocument(long personOrOrgNumber, Guid documentId)
|
||||
{
|
||||
try{
|
||||
var documentInformation = await _documentInformationService.GetDocumentInformation(documentId);
|
||||
|
||||
if (documentInformation is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!DocumentIsForMember(documentInformation, personOrOrgNumber)) return null;
|
||||
|
||||
return new DocumentResponse
|
||||
{
|
||||
Content = await _blobApiService.GetDocumentContent(documentInformation.DocumentBlobId),
|
||||
DocumentInformation = ConvertDocumentInformationToDocumentInformationResponse(documentInformation)
|
||||
};
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
var message = $"Failure to Retrieve Document Information. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (BlobApiServiceException exception){
|
||||
var message =
|
||||
$"Failure to Retrieve Document Content from Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Retrieve Document with DocumentId: {documentId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]?> GetDocumentByBlobId(Guid documentBlobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _blobApiService.GetDocumentContent(documentBlobId);
|
||||
}
|
||||
catch (BlobApiServiceException exception)
|
||||
{
|
||||
var message =
|
||||
$"Failure to Retrieve Document Content from Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Retrieve Document with DocumentBlobId: {documentBlobId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]?> GetDocumentByBlobId(long personOrOrgNumber, Guid documentBlobId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var document = await _documentInformationService.GetDocumentInformationByBlobId(documentBlobId);
|
||||
if(document is null) return null;
|
||||
|
||||
if(!DocumentIsForMember(document, personOrOrgNumber)) return null;
|
||||
|
||||
return await _blobApiService.GetDocumentContent(documentBlobId);
|
||||
}
|
||||
catch (BlobApiServiceException exception)
|
||||
{
|
||||
var message =
|
||||
$"Failure to Retrieve Document Content from Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = $"Failure to Retrieve Document with DocumentBlobId: {documentBlobId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteDocument(Guid documentId)
|
||||
{
|
||||
try{
|
||||
var deleteDate = DateTime.UtcNow;
|
||||
|
||||
var documentInformation = await _documentInformationService.GetDocumentInformation(documentId);
|
||||
if (documentInformation == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _documentInformationService.DeleteDocumentInformation(documentId);
|
||||
await _blobApiService.DeleteDocumentContent(documentInformation.DocumentBlobId);
|
||||
await _documentAuditService.CreateDocumentDeletedAudit(documentInformation.DocumentId, deleteDate);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
var message = $"Failure to Remove Document Information. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (BlobApiServiceException exception){
|
||||
var message =
|
||||
$"Failure to Remove Document Content from Blob Storage, but DocumentInformation was removed. Exception Message: {exception.Message}";
|
||||
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
|
||||
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)).Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Delete Document with DocumentId: {documentId}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteDocumentContent(DeleteDocumentContentRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var deleteDate = DateTime.UtcNow;
|
||||
|
||||
var documentInformation = await _documentInformationService.GetDocumentInformation(request.DocumentId);
|
||||
if (documentInformation?.DocumentBlobId is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _blobApiService.DeleteDocumentContent(documentInformation.DocumentBlobId);
|
||||
|
||||
documentInformation.DocumentBlobId = null;
|
||||
await _documentInformationService.UpdateDocumentInformation(documentInformation.DocumentId, documentInformation);
|
||||
|
||||
await _documentAuditService.CreateDocumentContentDeletedAudit(documentInformation.DocumentId, deleteDate, request.Reason);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
var message =
|
||||
$"Failure to Remove Document Content from Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (BlobApiServiceException exception){
|
||||
var message =
|
||||
$"Failure to Remove Document Content from Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
|
||||
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)).Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Delete Document Content with DocumentId: {request.DocumentId}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentInformationResponse> UploadDocument(DocumentRequest request)
|
||||
{
|
||||
var docBlobId = Guid.Empty;
|
||||
try{
|
||||
docBlobId = await _blobApiService.UploadDocumentContent(request.Content);
|
||||
var fileSize = CalculateFileSize(request.Content);
|
||||
var uploadDate = DateTime.UtcNow;
|
||||
|
||||
var documentInformation = new DocumentInformation
|
||||
{
|
||||
DocumentId = Guid.NewGuid(),
|
||||
DocumentBlobId = docBlobId,
|
||||
TraceId = request.TraceId,
|
||||
Name = request.Name,
|
||||
Type = request.Type,
|
||||
Source = request.Source,
|
||||
DataSource = request.DataSource,
|
||||
PersonNumbers = request.PersonNumbers,
|
||||
OrganizationNumbers = request.OrganizationNumbers,
|
||||
AccountNumber = request.AccountNumber,
|
||||
Attributes = request.Attributes,
|
||||
CreatedDate = request.CreatedDate,
|
||||
FileSize = fileSize,
|
||||
UploadDate = DateTime.UtcNow,
|
||||
ScanStatus = null
|
||||
};
|
||||
|
||||
var uploadedDocumentInformation = await _documentInformationService.UploadDocumentInformation(documentInformation);
|
||||
await _documentAuditService.CreateDocumentUploadedAudit(documentInformation.DocumentId, uploadDate);
|
||||
return ConvertDocumentInformationToDocumentInformationResponse(uploadedDocumentInformation);
|
||||
}
|
||||
catch (BlobApiServiceException exception){
|
||||
var message = $"Failure to Upload Content to Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
await RollBackDocumentContent(docBlobId);
|
||||
|
||||
var message = $"Failure to Upload Document Information. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message =
|
||||
$"Failure to Upload Document Name: {request.Name} of type: {request.Type} from source: {request.Source}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message}. Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentInformationResponse?> UpdateDocument(UpdateDocumentRequest request, Guid documentId, string userId)
|
||||
{
|
||||
DocumentInformation? documentInformation = null;
|
||||
var modifiedDate = DateTime.UtcNow;
|
||||
|
||||
try{
|
||||
documentInformation = await _documentInformationService.GetDocumentInformation(documentId);
|
||||
|
||||
if (documentInformation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var content = request.Content;
|
||||
var fileSize = CalculateFileSize(content);
|
||||
|
||||
var updatedDocumentInformation = new DocumentInformation
|
||||
{
|
||||
DocumentId = documentId,
|
||||
DocumentBlobId = documentInformation.DocumentBlobId,
|
||||
TraceId = documentInformation.TraceId,
|
||||
UploadDate = documentInformation.UploadDate,
|
||||
Source = documentInformation.Source,
|
||||
DataSource = documentInformation.DataSource,
|
||||
CreatedDate = documentInformation.CreatedDate,
|
||||
Name = request.Name,
|
||||
Type = request.Type,
|
||||
PersonNumbers = request.PersonNumbers,
|
||||
OrganizationNumbers = request.OrganizationNumbers,
|
||||
AccountNumber = request.AccountNumber,
|
||||
Attributes = request.Attributes,
|
||||
LastModifiedDate = modifiedDate,
|
||||
FileSize = fileSize,
|
||||
ScanStatus = null
|
||||
};
|
||||
|
||||
var docInfo = await _documentInformationService.UpdateDocumentInformation(documentId, updatedDocumentInformation);
|
||||
await _documentAuditService.CreateDocumentAudits(documentInformation, updatedDocumentInformation, true, modifiedDate, userId);
|
||||
await _blobApiService.UpdateDocumentContent(documentInformation.DocumentBlobId, content);
|
||||
return ConvertDocumentInformationToDocumentInformationResponse(docInfo);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
var message = $"Failure to Update Document Information for DocumentId: {documentId}. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (DocumentAuditServiceException exception){
|
||||
await RollbackDocumentInformation(documentId, documentInformation);
|
||||
|
||||
var message = $"Failure to Create Document Audits. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (BlobApiServiceException exception){
|
||||
await RollbackDocumentInformation(documentId, documentInformation);
|
||||
|
||||
var message = $"Failure to Update Document Content from Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Update Document with DocumentId: {documentId} Name: {request.Name} of type: {request.Type} from source: {request.UpdateSource}.";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message}. Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentInformationResponse?> GetDocumentInformation(Guid documentId)
|
||||
{
|
||||
try{
|
||||
var documentInformation = await _documentInformationService.GetDocumentInformation(documentId);
|
||||
|
||||
if (documentInformation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ConvertDocumentInformationToDocumentInformationResponse(documentInformation);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
var message = $"Failure to Retrieve Document Information. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Retrieve Document with DocumentId: {documentId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentInformationResponse?> UpdateDocumentContent(UpdateDocumentContentRequest request, Guid documentId, string userId)
|
||||
{
|
||||
DocumentInformation? documentInformation = null;
|
||||
var modifiedDate = DateTime.UtcNow;
|
||||
|
||||
try{
|
||||
documentInformation = await _documentInformationService.GetDocumentInformation(documentId);
|
||||
|
||||
if (documentInformation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var content = request.Content;
|
||||
var fileSize = CalculateFileSize(content);
|
||||
|
||||
var updatedDocumentInformation = new DocumentInformation
|
||||
{
|
||||
DocumentId = documentId,
|
||||
DocumentBlobId = documentInformation.DocumentBlobId,
|
||||
TraceId = documentInformation.TraceId,
|
||||
Name = documentInformation.Name,
|
||||
Type = documentInformation.Type,
|
||||
Source = documentInformation.Source,
|
||||
DataSource = documentInformation.DataSource,
|
||||
PersonNumbers = documentInformation.PersonNumbers,
|
||||
OrganizationNumbers = documentInformation.OrganizationNumbers,
|
||||
AccountNumber = documentInformation.AccountNumber,
|
||||
Attributes = documentInformation.Attributes,
|
||||
UploadDate = documentInformation.UploadDate,
|
||||
CreatedDate = documentInformation.CreatedDate,
|
||||
LastModifiedDate = modifiedDate,
|
||||
FileSize = fileSize,
|
||||
ScanStatus = null
|
||||
};
|
||||
|
||||
var docInfo = await _documentInformationService.UpdateDocumentInformation(documentId, updatedDocumentInformation);
|
||||
await _documentAuditService.CreateDocumentAudits(documentInformation, updatedDocumentInformation, true, modifiedDate, userId);
|
||||
await _blobApiService.UpdateDocumentContent(documentInformation.DocumentBlobId, content);
|
||||
return ConvertDocumentInformationToDocumentInformationResponse(docInfo);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
var message = $"Failure to Update Document Information for DocumentId: {documentId}. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (DocumentAuditServiceException exception){
|
||||
await RollbackDocumentInformation(documentId, documentInformation);
|
||||
|
||||
var message = $"Failure to Create Document Audits. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (BlobApiServiceException exception){
|
||||
await RollbackDocumentInformation(documentId, documentInformation);
|
||||
|
||||
var message = $"Failure to Update Document Content from Blob Storage. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Update Document Content for DocumentId: {documentId}.";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message}. Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<DocumentInformationResponse?> UpdateDocumentInformation(UpdateDocumentInformationRequest request, Guid documentId, string userId)
|
||||
{
|
||||
DocumentInformation? documentInformation = null;
|
||||
var modifiedDate = DateTime.UtcNow;
|
||||
|
||||
try{
|
||||
documentInformation = await _documentInformationService.GetDocumentInformation(documentId);
|
||||
if (documentInformation == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var updatedDocumentInformation = new DocumentInformation
|
||||
{
|
||||
DocumentId = documentId,
|
||||
DocumentBlobId = documentInformation.DocumentBlobId,
|
||||
TraceId = documentInformation.TraceId,
|
||||
Source = documentInformation.Source,
|
||||
DataSource = documentInformation.DataSource,
|
||||
CreatedDate = documentInformation.CreatedDate,
|
||||
Name = request.Name,
|
||||
Type = request.Type,
|
||||
PersonNumbers = request.PersonNumbers,
|
||||
OrganizationNumbers = request.OrganizationNumbers,
|
||||
AccountNumber = request.AccountNumber,
|
||||
Attributes = request.Attributes,
|
||||
UploadDate = documentInformation.UploadDate,
|
||||
LastModifiedDate = modifiedDate,
|
||||
FileSize = documentInformation.FileSize,
|
||||
ScanStatus = documentInformation.ScanStatus
|
||||
};
|
||||
|
||||
var docInfo = await _documentInformationService.UpdateDocumentInformation(documentId, updatedDocumentInformation);
|
||||
await _documentAuditService.CreateDocumentAudits(documentInformation, updatedDocumentInformation, false, modifiedDate, userId);
|
||||
return ConvertDocumentInformationToDocumentInformationResponse(docInfo);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
var message = $"Failure to Update Document Information for DocumentId: {documentId} with Name: {request.Name} of type: {request.Type} from source: {request.UpdateSource}. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (DocumentAuditServiceException exception){
|
||||
await RollbackDocumentInformation(documentId, documentInformation);
|
||||
|
||||
var message = $"Failure to Create Document Audits. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
var message = $"Failure to Update Document Information for DocumentId: {documentId} with Name: {request.Name} of type: {request.Type} from source: {request.UpdateSource}.";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<DocumentInformationResponse>> SearchDocumentInformation(DocumentInformationFilter filter, int? skip = null, int? take = null, List<SortableField>? sortDefinition = null)
|
||||
{
|
||||
try{
|
||||
var documentInformationFound = await _documentInformationService.SearchDocumentInformation(filter, skip, take, sortDefinition);
|
||||
|
||||
return documentInformationFound.Select(ConvertDocumentInformationToDocumentInformationResponse).ToList();
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
Log.Error(exception, exception.Message);
|
||||
throw new ServiceException(exception.Message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
const string message = "Failure to Search Document Information";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateDocumentScanStatus(bool status, List<Guid> documentBlobIds, string userId)
|
||||
{
|
||||
foreach (var documentBlobId in documentBlobIds){
|
||||
DocumentInformation? documentInformation = null;
|
||||
var modifiedDate = DateTime.UtcNow;
|
||||
|
||||
try{
|
||||
documentInformation = await _documentInformationService.GetDocumentInformationByBlobId(documentBlobId);
|
||||
if (documentInformation == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var updatedDocumentInformation = new DocumentInformation
|
||||
{
|
||||
DocumentBlobId = documentBlobId,
|
||||
|
||||
DocumentId = documentInformation.DocumentId,
|
||||
Source = documentInformation.Source,
|
||||
DataSource = documentInformation.DataSource,
|
||||
TraceId = documentInformation.TraceId,
|
||||
Name = documentInformation.Name,
|
||||
Type = documentInformation.Type,
|
||||
PersonNumbers = documentInformation.PersonNumbers,
|
||||
OrganizationNumbers = documentInformation.OrganizationNumbers,
|
||||
AccountNumber = documentInformation.AccountNumber,
|
||||
Attributes = documentInformation.Attributes,
|
||||
UploadDate = documentInformation.UploadDate,
|
||||
CreatedDate = documentInformation.CreatedDate,
|
||||
FileSize = documentInformation.FileSize,
|
||||
|
||||
ScanStatus = status,
|
||||
LastModifiedDate = modifiedDate
|
||||
};
|
||||
|
||||
await _documentInformationService.UpdateDocumentInformation(updatedDocumentInformation.DocumentId,
|
||||
updatedDocumentInformation);
|
||||
await _documentAuditService.CreateDocumentAudits(documentInformation, updatedDocumentInformation, false,
|
||||
modifiedDate, userId);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
await NotifyAboutMaliciousFile(documentInformation);
|
||||
}
|
||||
}
|
||||
catch (DocumentInformationServiceException exception){
|
||||
Log.Error(exception, $"Failure to Update Document Scan Status for DocumentBlobId: {documentBlobId}");
|
||||
}
|
||||
catch (DocumentAuditServiceException exception){
|
||||
await RollbackDocumentInformation(documentInformation.DocumentId, documentInformation);
|
||||
|
||||
var message = $"Failure to Create Document Audits. Exception Message: {exception.Message}";
|
||||
Log.Error(exception, message);
|
||||
}
|
||||
catch (Exception exception){
|
||||
Log.Error(exception,$"Failure to Update Document Scan Status for DocumentBlobId: {documentBlobId}, caused by unexpected exception");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<long> CountDocuments(DocumentInformationFilter filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _documentInformationService.CountDocuments(filter);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception)
|
||||
{
|
||||
Log.Error(exception, exception.Message);
|
||||
|
||||
throw new ServiceException(exception.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
const string message = "Failure to Count Document Information";
|
||||
Log.Error(exception, message);
|
||||
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<Guid>> GetDocumentsForCleanup(GetDocumentCleanupRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _documentInformationService.GetDocumentsForCleanup(request);
|
||||
}
|
||||
catch (DocumentInformationServiceException exception)
|
||||
{
|
||||
Log.Error(exception, exception.Message);
|
||||
|
||||
throw new ServiceException(exception.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
const string message = "Failure GetDocumentsForCleanup";
|
||||
Log.Error(exception, message);
|
||||
|
||||
throw new ServiceException($"{message} Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RollBackDocumentContent(Guid docBlobId)
|
||||
{
|
||||
try{
|
||||
Log.Information($"Removing {docBlobId} from Blob Storage, as part of a rollback transaction.");
|
||||
await _blobApiService.DeleteDocumentContent(docBlobId);
|
||||
}
|
||||
catch (Exception inception){
|
||||
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
|
||||
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
|
||||
.Error(inception,
|
||||
$"Failed to remove {docBlobId} from Blob Storage, an inconsistency in data now exists between DocumentInformation and Content. Exception Message: {inception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RollbackDocumentInformation(Guid documentId, DocumentInformation documentInformation)
|
||||
{
|
||||
try{
|
||||
Log.Information(
|
||||
$"Reverting Document Information for DocumentId:{documentId}, as part of a rollback transaction.");
|
||||
await _documentInformationService.UpdateDocumentInformation(documentId, documentInformation);
|
||||
}
|
||||
catch (Exception inception){
|
||||
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
|
||||
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
|
||||
.Error($"Failed to update document information for DocumentId:{documentId}, an inconsistency in data now exists between DocumentInformation. DocumentInformation should be updated to: {JsonSerializer.Serialize(documentInformation)} Exception Message: {inception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static BigInteger CalculateFileSize(byte[] bytes)
|
||||
{
|
||||
var fileBase64Value = Convert.ToBase64String(bytes);
|
||||
var size = fileBase64Value.Length / 4 * 3;
|
||||
|
||||
if (fileBase64Value.EndsWith("=="))
|
||||
size -= 2;
|
||||
else if (fileBase64Value.EndsWith("=")) size -= 1;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
private static DocumentInformationResponse ConvertDocumentInformationToDocumentInformationResponse(
|
||||
DocumentInformation updatedDocumentInformation)
|
||||
{
|
||||
return new DocumentInformationResponse
|
||||
{
|
||||
DocumentId = updatedDocumentInformation.DocumentId,
|
||||
DocumentBlobId = updatedDocumentInformation.DocumentBlobId,
|
||||
TraceId = updatedDocumentInformation.TraceId,
|
||||
Name = updatedDocumentInformation.Name,
|
||||
Type = updatedDocumentInformation.Type,
|
||||
Source = updatedDocumentInformation.Source,
|
||||
DataSource = updatedDocumentInformation.DataSource,
|
||||
FileSize = updatedDocumentInformation.FileSize,
|
||||
ScanStatus = updatedDocumentInformation.ScanStatus,
|
||||
PersonNumbers = updatedDocumentInformation.PersonNumbers,
|
||||
OrganizationNumbers = updatedDocumentInformation.OrganizationNumbers,
|
||||
AccountNumber = updatedDocumentInformation.AccountNumber,
|
||||
Attributes = updatedDocumentInformation.Attributes,
|
||||
UploadDate = updatedDocumentInformation.UploadDate,
|
||||
LastModifiedDate = updatedDocumentInformation.LastModifiedDate,
|
||||
CreatedDate = updatedDocumentInformation.CreatedDate
|
||||
};
|
||||
}
|
||||
|
||||
private async Task NotifyAboutMaliciousFile(DocumentInformation documentInformation)
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||
var documentAttributesSerialized =
|
||||
JsonSerializer.Serialize(
|
||||
documentInformation.Attributes,
|
||||
new JsonSerializerOptions { WriteIndented = true }
|
||||
);
|
||||
|
||||
var emailRequest = new EmailRequest()
|
||||
{
|
||||
Subject = "DocumentApi: Malicious File Reported",
|
||||
Recipient = _apiConfiguration.MaliciousFileFoundEmailList,
|
||||
Body = "This is an auto generated message. <br>" +
|
||||
"<br>" +
|
||||
"Azure Defender has returned a malicious file from scan<br>" +
|
||||
"<br>" +
|
||||
"Document Attributes:<br>" +
|
||||
$"{documentAttributesSerialized}"
|
||||
};
|
||||
|
||||
await _notificationApiService.SendEmailAsync(emailRequest);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var message = $"Failure to send email. Azure Defender returned a file as malicious. DocumentInformation: {JsonSerializer.Serialize(documentInformation)}. Exception Message: {ex.Message}";
|
||||
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
|
||||
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment)).Error(ex, message);
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
}
|
||||
|
||||
public delegate bool DocumentIsForMemberStrategy(DocumentInformation document, long personOrOrgNumber);
|
||||
|
||||
public static class Ownership
|
||||
{
|
||||
public static class Unchecked
|
||||
{
|
||||
public static DocumentIsForMemberStrategy Strategy => DocumentIsForMember;
|
||||
|
||||
private static bool DocumentIsForMember(DocumentInformation document, long personOrOrgNumber)
|
||||
{
|
||||
var personOrOrgNumbers = (document.PersonNumbers ?? Array.Empty<string>())
|
||||
.Concat(document.OrganizationNumbers ?? Array.Empty<string>())
|
||||
.Select(n => long.TryParse(n, out var number) ? number : -1);
|
||||
Log.Information("checking {personOrOrgNumber} on {document.DocumentId} in {personOrOrgNumbers} ignored", personOrOrgNumber, document.DocumentId, JsonSerializer.Serialize(personOrOrgNumbers));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CheckPersonOrOrgNumber
|
||||
{
|
||||
public static DocumentIsForMemberStrategy Strategy => DocumentIsForMember;
|
||||
|
||||
private static bool DocumentIsForMember(DocumentInformation document, long personOrOrgNumber) => (document.PersonNumbers ?? Array.Empty<string>())
|
||||
.Concat(document.OrganizationNumbers ?? Array.Empty<string>())
|
||||
.Select(n => long.TryParse(n, out var number) ? number : -1)
|
||||
.Contains(personOrOrgNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace LCU.API.Internal.Document.Services;
|
||||
|
||||
public interface IBlobApiService
|
||||
{
|
||||
Task<byte[]> GetDocumentContent(Guid? documentBlobId);
|
||||
Task<Guid> UploadDocumentContent(byte[] content);
|
||||
Task UpdateDocumentContent(Guid? documentBlobId, byte[] content);
|
||||
Task DeleteDocumentContent(Guid? documentBlobId);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.Library.Internal.DocumentModels.Requests;
|
||||
using LCU.Library.Internal.DocumentModels.Responses;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services;
|
||||
|
||||
public interface IDocumentAuditService
|
||||
{
|
||||
Task<List<DocumentAuditResponse>> GetDocumentAudits(params Guid[] documentIds);
|
||||
Task<IEnumerable<DocumentAudit>> CreateDocumentAudits(DocumentInformation oldDocumentInformation, DocumentInformation newDocumentInformation, bool wasDocumentContentUpdated, DateTime updateDate, string userId);
|
||||
public Task<IEnumerable<DocumentAudit>> CreateDocumentUploadedAudit(Guid documentId, DateTime uploadDate);
|
||||
public Task<IEnumerable<DocumentAudit>> CreateDocumentDeletedAudit(Guid documentId, DateTime deleteDate);
|
||||
public Task<IEnumerable<DocumentAudit>> CreateDocumentContentDeletedAudit(Guid documentId, DateTime deleteDate, string? reason);
|
||||
Task AuditAction(Guid documentId, string userId, string actionSource, DocumentAction action);
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Models.Pagination;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services;
|
||||
|
||||
public interface IDocumentInformationService
|
||||
{
|
||||
Task<DocumentInformation?> GetDocumentInformation(Guid documentId);
|
||||
Task<DocumentInformation?> GetDocumentInformationByBlobId(Guid? documentBlobId);
|
||||
Task DeleteDocumentInformation(Guid documentId);
|
||||
Task<DocumentInformation> UploadDocumentInformation(DocumentInformation information);
|
||||
Task<DocumentInformation?> UpdateDocumentInformation(Guid documentId, DocumentInformation information);
|
||||
Task<List<DocumentInformation>> SearchDocumentInformation(DocumentInformationFilter filter, int? skip = null, int? take = null, List<SortableField>? sortDefinition = null);
|
||||
Task<long> CountDocuments(DocumentInformationFilter filter);
|
||||
Task<List<Guid>> GetDocumentsForCleanup(GetDocumentCleanupRequest request);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Models.Pagination;
|
||||
using LCU.Library.Internal.DocumentModels.Requests;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
using LCU.Library.Internal.DocumentModels.Responses;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services;
|
||||
|
||||
public interface IDocumentService
|
||||
{
|
||||
Task<DocumentResponse?> GetDocument(Guid documentId);
|
||||
Task<DocumentResponse?> GetDocument(long personOrOrgNumber, Guid documentId);
|
||||
Task<byte[]?> GetDocumentByBlobId(Guid documentBlobId);
|
||||
Task<byte[]?> GetDocumentByBlobId(long personOrOrgNumber, Guid documentBlobId);
|
||||
Task<List<DocumentInformationResponse>> SearchDocumentInformation(DocumentInformationFilter filter, int? skip = null, int? take = null, List<SortableField>? sortDefinition = null);
|
||||
Task DeleteDocument(Guid documentId);
|
||||
Task DeleteDocumentContent(DeleteDocumentContentRequest request);
|
||||
Task<DocumentInformationResponse> UploadDocument(DocumentRequest request);
|
||||
Task<DocumentInformationResponse?> GetDocumentInformation(Guid documentId);
|
||||
Task<DocumentInformationResponse?> UpdateDocument(UpdateDocumentRequest request, Guid documentId, string userId);
|
||||
Task<DocumentInformationResponse?> UpdateDocumentContent(UpdateDocumentContentRequest request, Guid documentId, string userId);
|
||||
Task<DocumentInformationResponse?> UpdateDocumentInformation(UpdateDocumentInformationRequest request, Guid documentId, string userId);
|
||||
Task UpdateDocumentScanStatus(bool status, List<Guid> documentBlobIds, string userId);
|
||||
Task<long> CountDocuments(DocumentInformationFilter filter);
|
||||
Task<List<Guid>> GetDocumentsForCleanup(GetDocumentCleanupRequest request);
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using LCU.API.Internal.NotificationsApiLib.Models;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services
|
||||
{
|
||||
public interface INotificationApiService
|
||||
{
|
||||
Task SendEmailAsync(EmailRequest emailRequest);
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using LCU.API.Internal.DocumentLib.DataAccess;
|
||||
using LCU.API.Internal.NotificationsApiLib.Models;
|
||||
using LCU.Library.Internal.Exceptions;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Internal.Document.Services
|
||||
{
|
||||
public class NotificationApiService : INotificationApiService
|
||||
{
|
||||
private readonly INotificationApiDao _notificationServiceApiDao;
|
||||
|
||||
public NotificationApiService(INotificationApiDao notificationApiServiceDao)
|
||||
{
|
||||
_notificationServiceApiDao = notificationApiServiceDao;
|
||||
}
|
||||
|
||||
public async Task SendEmailAsync(EmailRequest emailRequest)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _notificationServiceApiDao.SendEmailAsync(emailRequest);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Failure to send email. Subject: {emailRequest.Subject}; Recipients: {String.Join(", ", emailRequest.Recipient)}");
|
||||
throw new ServiceException($"Failure to send email. Subject: {emailRequest.Subject}; Recipients: {String.Join(", ", emailRequest.Recipient)}; Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"AzureAd": {
|
||||
"ClientId": "c92072ab-987f-4b67-9923-730e06b2766f",
|
||||
"Instance": "https://login.microsoftonline.com/",
|
||||
"Domain": "landmarkcu.onmicrosoft.com",
|
||||
"TenantId": "63cd1554-2783-4a86-ae50-b9e6c21c9499",
|
||||
"CallbackPath": "/signin-oidc"
|
||||
},
|
||||
"AzureKeyVaultUrl": "qa-DocumentApi-kv",
|
||||
"ClientId": "c92072ab-987f-4b67-9923-730e06b2766f",
|
||||
"ClientSecret": "OjS8Q~TuByD~pylYMu5pbw9UGNG5fldT6eP9YcBI",
|
||||
"TenantId": "63cd1554-2783-4a86-ae50-b9e6c21c9499",
|
||||
"Swagger": {
|
||||
"OAuthScope": "api://c92072ab-987f-4b67-9923-730e06b2766f/user.groups"
|
||||
},
|
||||
"Primary": {
|
||||
"AppName": "Document Api",
|
||||
"SeqUrl": "https://qa-seq.lcu-internal.com",
|
||||
"SeqApiKey": "ftd9RlWVEI1ctg3dqusa",
|
||||
"FileUploadCutoffHours": -2,
|
||||
"IsAksDeploy": "false",
|
||||
"CorsAllowedOrigins": [
|
||||
"http://lcu-internal.com",
|
||||
"https://lcu-internal.com",
|
||||
"https://qa-api.lcu-internal.com"
|
||||
],
|
||||
"MaliciousFileFoundEmailList": [
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
]
|
||||
},
|
||||
"BlobStorage": {
|
||||
"connectionString": "VALUE-IN-KEYVAULT",
|
||||
"containerName": "documents"
|
||||
},
|
||||
"MongoDatabase": {
|
||||
"connectionString": "VALUE-IN-KEYVAULT",
|
||||
"databaseName": "DocumentsApi",
|
||||
"documentContainerName": "DocumentsInformation",
|
||||
"auditContainerName": "AuditInformation"
|
||||
},
|
||||
"ApiSecurityConfiguration": {
|
||||
"ApiKeyAuthenticationFailureBehavior": "Error",
|
||||
"ExternalApiKeySet": {
|
||||
"DocumentTypeUploader": "VALUE-IN-KEYVAULT",
|
||||
"DocumentTypeRetriever": "VALUE-IN-KEYVAULT",
|
||||
"DocumentTypeCleanup": "VALUE-IN-KEYVAULT"
|
||||
},
|
||||
"InternalApiKeySet": {}
|
||||
},
|
||||
"NotificationApiConfiguration": {
|
||||
"Url": "https://qa-notificationsapi.lcu-internal.com",
|
||||
"Email": "/api/Email"
|
||||
},
|
||||
"FeatureManagement": {
|
||||
"GetDocumentCheckOwnership": true
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
namespace LCU.API.Internal.DocumentLib.Configurations;
|
||||
|
||||
public class BlobStorageConfiguration
|
||||
{
|
||||
public string ConnectionString { get; set; }
|
||||
public string ContainerName { get; set; }
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
namespace LCU.API.Internal.DocumentLib.Configurations;
|
||||
|
||||
public class DatabaseConfiguration
|
||||
{
|
||||
public string ConnectionString { get; set; }
|
||||
public string DatabaseName { get; set; }
|
||||
|
||||
public string DocumentContainerName { get; set; }
|
||||
|
||||
public string AuditContainerName { get; set; }
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.Configurations;
|
||||
public class NotificationApiConfiguration
|
||||
{
|
||||
public string Url { get; set; }
|
||||
public string Email { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public string EmailUrl
|
||||
{
|
||||
get
|
||||
{
|
||||
return Url + Email;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using LCU.Library.Internal.DocumentModels.Requests;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.Containers;
|
||||
|
||||
[BsonKnownTypes(typeof(DocumentFieldAudit), typeof(DocumentActionAudit))]
|
||||
public abstract class DocumentAudit
|
||||
{
|
||||
protected DocumentAudit(Guid documentId, string source, DateTime date, string userId)
|
||||
{
|
||||
DocumentId = documentId;
|
||||
Source = source;
|
||||
Date = date;
|
||||
UserId = userId;
|
||||
}
|
||||
|
||||
public Guid DocumentId { get; set; }
|
||||
public string Source { get; set; }
|
||||
|
||||
[BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
public string UserId { get; set; }
|
||||
}
|
||||
|
||||
[BsonIgnoreExtraElements]
|
||||
public class DocumentFieldAudit : DocumentAudit
|
||||
{
|
||||
public DocumentFieldAudit(Guid documentId, string source, DateTime date, string userId, string field, object previousValue, object currentValue)
|
||||
: base(documentId, source, date, userId)
|
||||
{
|
||||
Field = field;
|
||||
PreviousValue = previousValue;
|
||||
CurrentValue = currentValue;
|
||||
}
|
||||
|
||||
public string Field { get; set; }
|
||||
public object PreviousValue { get; set; }
|
||||
public object CurrentValue { get; set; }
|
||||
}
|
||||
|
||||
[BsonIgnoreExtraElements]
|
||||
public class DocumentActionAudit : DocumentAudit
|
||||
{
|
||||
public DocumentActionAudit(Guid documentId, string source, DateTime date, string userId, DocumentAction action, string description)
|
||||
: base(documentId, source, date, userId)
|
||||
{
|
||||
Action = action;
|
||||
ActionDescription = description;
|
||||
}
|
||||
|
||||
public DocumentAction Action { get; set; }
|
||||
public string ActionDescription { get; set; }
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.IO;
|
||||
using MongoDB.Bson.Serialization.Conventions;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.Containers;
|
||||
|
||||
public class DocumentAuditDiscriminatorConvention : IDiscriminatorConvention
|
||||
{
|
||||
public static string Discriminator = StandardDiscriminatorConvention.Scalar.ElementName;
|
||||
public string ElementName => Discriminator;
|
||||
|
||||
public Type GetActualType(IBsonReader bsonReader, Type nominalType)
|
||||
{
|
||||
var bookmark = bsonReader.GetBookmark();
|
||||
|
||||
try
|
||||
{
|
||||
bsonReader.ReadStartDocument();
|
||||
|
||||
if (!bsonReader.FindElement(ElementName)) return typeof(DocumentFieldAudit);
|
||||
|
||||
return bsonReader.ReadString() switch
|
||||
{
|
||||
nameof(DocumentFieldAudit) => typeof(DocumentFieldAudit),
|
||||
nameof(DocumentActionAudit) => typeof(DocumentActionAudit),
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
bsonReader.ReturnToBookmark(bookmark);
|
||||
}
|
||||
}
|
||||
|
||||
public BsonValue GetDiscriminator(Type nominalType, Type actualType)
|
||||
{
|
||||
return actualType.Name;
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using MongoDB.Bson.Serialization;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
using MongoDB.Bson.Serialization.Serializers;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.Containers;
|
||||
|
||||
[BsonIgnoreExtraElements]
|
||||
public class DocumentInformation
|
||||
{
|
||||
public Guid DocumentId { get; set; }
|
||||
public Guid? DocumentBlobId { get; set; }
|
||||
public Guid? TraceId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Source { get; set; }
|
||||
|
||||
public string DataSource { get; set; }
|
||||
|
||||
[BsonSerializer(typeof(BigIntSerializer))]
|
||||
public BigInteger FileSize { get; set; }
|
||||
|
||||
public bool? ScanStatus { get; set; }
|
||||
|
||||
[BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
|
||||
public DateTime UploadDate { get; set; }
|
||||
|
||||
[BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
|
||||
public DateTime? LastModifiedDate { get; set; }
|
||||
|
||||
[BsonDateTimeOptions(Kind = DateTimeKind.Utc)]
|
||||
public DateTime CreatedDate { get; set; }
|
||||
public string[] PersonNumbers { get; set; }
|
||||
public string[] OrganizationNumbers { get; set; }
|
||||
public string AccountNumber { get; set; }
|
||||
public Dictionary<string, string> Attributes { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public class BigIntSerializer : SerializerBase<BigInteger>
|
||||
{
|
||||
public override BigInteger Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
|
||||
{
|
||||
var val = context.Reader.ReadString();
|
||||
return BigInteger.Parse(val);
|
||||
}
|
||||
|
||||
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, BigInteger value)
|
||||
{
|
||||
context.Writer.WriteString(value.ToString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Azure;
|
||||
using Azure.Storage.Blobs;
|
||||
using Azure.Storage.Blobs.Models;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.DataAccess;
|
||||
|
||||
public class BlobApiDao : IBlobApiDao
|
||||
{
|
||||
private readonly BlobContainerClient _client;
|
||||
|
||||
public BlobApiDao(IBlobStorageClient storageClient)
|
||||
{
|
||||
_client = storageClient.GetClient();
|
||||
}
|
||||
|
||||
public async Task<Guid> SaveDocumentToBlob(byte[] fileData)
|
||||
{
|
||||
var blobDocumentId = Guid.NewGuid();
|
||||
|
||||
using var stream = new MemoryStream(fileData, false);
|
||||
await _client.GetBlobClient(blobDocumentId.ToString()).UploadAsync(stream);
|
||||
return blobDocumentId;
|
||||
}
|
||||
|
||||
public async Task UpdateDocumentInBlob(Guid blobDocumentId, byte[] fileData)
|
||||
{
|
||||
using var stream = new MemoryStream(fileData, false);
|
||||
await _client.GetBlobClient(blobDocumentId.ToString()).UploadAsync(stream, true);
|
||||
}
|
||||
|
||||
public async Task<Response> RemoveDocumentFromBlob(Guid blobDocumentId)
|
||||
{
|
||||
return await _client.GetBlobClient(blobDocumentId.ToString())
|
||||
.DeleteAsync(DeleteSnapshotsOption.IncludeSnapshots);
|
||||
}
|
||||
|
||||
public async Task<Response<BlobDownloadStreamingResult>> GetDocumentFromBlob(Guid blobDocumentId)
|
||||
{
|
||||
return await _client.GetBlobClient(blobDocumentId.ToString()).DownloadStreamingAsync();
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using Azure.Storage.Blobs;
|
||||
using LCU.API.Internal.DocumentLib.Configurations;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.DataAccess;
|
||||
|
||||
public class BlobStorageClient : IBlobStorageClient
|
||||
{
|
||||
private readonly BlobContainerClient _containerClient;
|
||||
|
||||
public BlobStorageClient(BlobStorageConfiguration config)
|
||||
{
|
||||
var serviceClient = new BlobServiceClient(config.ConnectionString);
|
||||
_containerClient = serviceClient.GetBlobContainerClient(config.ContainerName);
|
||||
}
|
||||
|
||||
public BlobContainerClient GetClient()
|
||||
{
|
||||
return _containerClient;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Azure;
|
||||
using Azure.Storage.Blobs.Models;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.DataAccess;
|
||||
|
||||
public interface IBlobApiDao
|
||||
{
|
||||
Task<Guid> SaveDocumentToBlob(byte[] fileData);
|
||||
Task UpdateDocumentInBlob(Guid blobDocumentId, byte[] fileData);
|
||||
Task<Response> RemoveDocumentFromBlob(Guid blobDocumentId);
|
||||
Task<Response<BlobDownloadStreamingResult>> GetDocumentFromBlob(Guid blobDocumentId);
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
using Azure.Storage.Blobs;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.DataAccess;
|
||||
|
||||
public interface IBlobStorageClient
|
||||
{
|
||||
BlobContainerClient GetClient();
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
using LCU.API.Internal.NotificationsApiLib.Models;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.DataAccess
|
||||
{
|
||||
public interface INotificationApiDao
|
||||
{
|
||||
Task SendEmailAsync(EmailRequest emailRequest);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using LCU.API.Internal.DocumentLib.Configurations;
|
||||
using LCU.API.Internal.DocumentLib.Utilities;
|
||||
using LCU.API.Internal.NotificationsApiLib.Models;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.DataAccess
|
||||
{
|
||||
public class NotificationApiDao : INotificationApiDao
|
||||
{
|
||||
private readonly NotificationApiConfiguration _notificationApiConfiguration;
|
||||
public NotificationApiDao(NotificationApiConfiguration notificationApiConfiguration)
|
||||
{
|
||||
_notificationApiConfiguration = notificationApiConfiguration;
|
||||
}
|
||||
|
||||
public async Task SendEmailAsync(EmailRequest emailRequest)
|
||||
{
|
||||
await HttpUtilities.PostAsync<EmailRequest>(new Uri(_notificationApiConfiguration.EmailUrl),
|
||||
emailRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.13.0" />
|
||||
<PackageReference Include="LCU.API.Internal.NotificationsApiLib" Version="2.1.0" />
|
||||
<PackageReference Include="LCU.Library.Internal.Exceptions" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.30.1" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="2.20.0" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LCU.Library.Internal.DocumentModels\LCU.Library.Internal.DocumentModels.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,19 @@
|
||||
using LCU.API.Internal.DocumentLib.Configurations;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.Repositories;
|
||||
|
||||
public class DataClient : IDataClient
|
||||
{
|
||||
private readonly MongoClient _client;
|
||||
|
||||
public DataClient(DatabaseConfiguration config)
|
||||
{
|
||||
_client = new MongoClient(config.ConnectionString);
|
||||
}
|
||||
|
||||
public MongoClient GetClient()
|
||||
{
|
||||
return _client;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.API.Internal.DocumentLib.Utilities;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.Repositories;
|
||||
|
||||
public class DocumentAuditRepository : IDocumentAuditRepository
|
||||
{
|
||||
private readonly IMongoCollection<DocumentAudit> _documentAudits;
|
||||
|
||||
public DocumentAuditRepository(IMongoCollectionFactory collectionFactory)
|
||||
{
|
||||
_documentAudits = collectionFactory.GetCollection<DocumentAudit>();
|
||||
}
|
||||
|
||||
public async Task<List<DocumentAudit>> GetDocumentAudits(params Guid[] documentIds)
|
||||
{
|
||||
return await (await _documentAudits.FindAsync(doc => documentIds.Contains(doc.DocumentId))).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<DocumentAudit>> SaveDocumentAudits(params DocumentAudit[] documentAudit)
|
||||
{
|
||||
await _documentAudits.InsertManyAsync(documentAudit);
|
||||
|
||||
return documentAudit;
|
||||
}
|
||||
}
|
||||
+269
@@ -0,0 +1,269 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.API.Internal.DocumentLib.Utilities;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Models.Pagination;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
using Microsoft.Azure.Cosmos.Linq;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.Repositories;
|
||||
|
||||
public class DocumentInformationRepository : IDocumentInformationRepository
|
||||
{
|
||||
private readonly IMongoCollection<DocumentInformation> _documents;
|
||||
|
||||
public DocumentInformationRepository(IMongoCollectionFactory collectionFactory)
|
||||
{
|
||||
_documents = collectionFactory.GetCollection<DocumentInformation>();
|
||||
}
|
||||
|
||||
public async Task<DocumentInformation> GetDocumentInformation(Guid documentId)
|
||||
{
|
||||
return await _documents.Find(doc => doc.DocumentId == documentId)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<List<DocumentInformation>> SearchDocumentInformation(DocumentInformationFilter filter, int? skip = null, int? take = null, List<SortableField>? sortDefinition = null)
|
||||
{
|
||||
var pipelineFilter = FilterFactory(filter);
|
||||
var sorter = Sorter<DocumentInformation>.GetSorter(sortDefinition);
|
||||
if (sorter != null) pipelineFilter = pipelineFilter.Sort(sorter);
|
||||
if (skip.HasValue) pipelineFilter = pipelineFilter.Skip(skip.Value);
|
||||
if (take.HasValue) pipelineFilter = pipelineFilter.Limit(take.Value);
|
||||
|
||||
var documentsInformation = await _documents.AggregateAsync(pipelineFilter);
|
||||
return await documentsInformation.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<long> CountDocuments(DocumentInformationFilter filter)
|
||||
{
|
||||
return (await _documents.AggregateAsync(FilterFactory(filter).Count())).Single().Count;
|
||||
}
|
||||
|
||||
public async Task<DocumentInformation> GetDocumentInformationByBlobId(Guid documentBlobId)
|
||||
{
|
||||
return await _documents
|
||||
.Find(doc => doc.DocumentBlobId == documentBlobId)
|
||||
.SingleOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<DocumentInformation> SaveDocumentInformation(DocumentInformation document)
|
||||
{
|
||||
await _documents.InsertOneAsync(document);
|
||||
return document;
|
||||
}
|
||||
|
||||
public async Task<DocumentInformation> UpdateDocumentInformation(Guid documentId, DocumentInformation newDocument)
|
||||
{
|
||||
await _documents.FindOneAndReplaceAsync(doc => doc.DocumentId == documentId, newDocument);
|
||||
return newDocument;
|
||||
}
|
||||
|
||||
public async Task<DocumentInformation> RemoveDocumentInformation(Guid documentId)
|
||||
{
|
||||
return await _documents.FindOneAndDeleteAsync(document => document.DocumentId == documentId);
|
||||
}
|
||||
|
||||
public async Task<List<Guid>> GetDocumentsForCleanup(GetDocumentCleanupRequest request)
|
||||
{
|
||||
IList<FilterDefinition<DocumentInformation>> filterConcat = new List<FilterDefinition<DocumentInformation>>();
|
||||
foreach(var documentCodeDetail in request.DocumentCodeDetailInfos)
|
||||
{
|
||||
var adjustedDate = DateTime.Now.AddMonths(documentCodeDetail.InternalRetentionMonths * -1);
|
||||
|
||||
var typeFilter = Builders<DocumentInformation>.Filter.Eq(x => x.Type, documentCodeDetail.DocumentCode);
|
||||
var dateFilter = Builders<DocumentInformation>.Filter.Lt(x => x.CreatedDate, adjustedDate);
|
||||
var groupFilter = Builders<DocumentInformation>.Filter.And(typeFilter, dateFilter);
|
||||
|
||||
filterConcat.Add(groupFilter);
|
||||
}
|
||||
|
||||
var filter = Builders<DocumentInformation>.Filter;
|
||||
var oneConcat = filter.Empty;
|
||||
oneConcat &= filter.Or(filterConcat);
|
||||
|
||||
var nullFilter = Builders<DocumentInformation>.Filter.Ne(x => x.DocumentBlobId, null);
|
||||
oneConcat &= filter.And(nullFilter);
|
||||
|
||||
var pipelineFilter = new EmptyPipelineDefinition<DocumentInformation>()
|
||||
.Match(oneConcat)
|
||||
.Limit(request.Limit);
|
||||
|
||||
var documentsInformation = await _documents.AggregateAsync(pipelineFilter);
|
||||
return (await documentsInformation.ToListAsync()).Select(document => document.DocumentId).ToList();
|
||||
}
|
||||
|
||||
private PipelineDefinition<DocumentInformation, DocumentInformation> FilterFactory(DocumentInformationFilter filter)
|
||||
{
|
||||
var builder = Builders<DocumentInformation>.Filter;
|
||||
IList<FilterDefinition<DocumentInformation>> stageOneFiltersAnd = new List<FilterDefinition<DocumentInformation>>();
|
||||
IList<FilterDefinition<DocumentInformation>> stageOneFiltersOr = new List<FilterDefinition<DocumentInformation>>();
|
||||
IList<FilterDefinition<DocumentInformation>> stageTwoFiltersAnd = new List<FilterDefinition<DocumentInformation>>();
|
||||
IList<FilterDefinition<DocumentInformation>> stageTwoFiltersOr = new List<FilterDefinition<DocumentInformation>>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.Name))
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.Eq("Name", filter.Name));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.Type))
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.Eq("Type", filter.Type));
|
||||
}
|
||||
|
||||
if (filter.TypeContainsAny != null && filter.TypeContainsAny.Any())
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.In("Type", filter.TypeContainsAny));
|
||||
}
|
||||
|
||||
if (filter.ScanStatus != null)
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.Eq("ScanStatus", filter.ScanStatus));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.Source))
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.Eq("Source", filter.Source));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.DataSource))
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.Eq("DataSource", filter.DataSource));
|
||||
}
|
||||
|
||||
if (filter.TraceId != null)
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.Eq("TraceId", filter.TraceId));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.PersonNumbersEquals))
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.Eq("PersonNumbers", filter.PersonNumbersEquals));
|
||||
}
|
||||
|
||||
if (filter.PersonNumbersContains != null && filter.PersonNumbersContains.Any())
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.All("PersonNumbers", filter.PersonNumbersContains));
|
||||
}
|
||||
|
||||
if (filter.PersonNumbersContainsAny != null && filter.PersonNumbersContainsAny.Any())
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.In("PersonNumbers", filter.PersonNumbersContainsAny));
|
||||
}
|
||||
|
||||
if (filter.OrganizationNumbersEquals != null && filter.OrganizationNumbersEquals.Any())
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.Eq("OrganizationNumbers", filter.OrganizationNumbersEquals));
|
||||
}
|
||||
|
||||
if (filter.OrganizationNumbersContains != null && filter.OrganizationNumbersContains.Any())
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.All("OrganizationNumbers", filter.OrganizationNumbersContains));
|
||||
}
|
||||
|
||||
if (filter.OrganizationNumbersContainsAny != null && filter.OrganizationNumbersContainsAny.Any())
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.In("OrganizationNumbers", filter.OrganizationNumbersContainsAny));
|
||||
}
|
||||
|
||||
if (filter.AccountNumberEquals != null && filter.AccountNumberEquals.Any())
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.Eq("AccountNumber", filter.AccountNumberEquals));
|
||||
}
|
||||
|
||||
if (filter.AccountNumberContainsAny != null && filter.AccountNumberContainsAny.Any())
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.In("AccountNumber", filter.AccountNumberContainsAny));
|
||||
}
|
||||
|
||||
if (filter.AttributesEquals != null && filter.AttributesEquals.Any())
|
||||
{
|
||||
foreach (var keyValuePair in filter.AttributesEquals)
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.Eq($"Attributes.{keyValuePair.Key.Trim()}", keyValuePair.Value.Trim()));
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.AttributesContains is not null)
|
||||
{
|
||||
foreach (var keyValuePair in filter.AttributesContains)
|
||||
{
|
||||
var queryExpr =
|
||||
new BsonRegularExpression(
|
||||
new Regex($"\\b({keyValuePair.Value.Trim()})\\b", RegexOptions.IgnoreCase));
|
||||
stageTwoFiltersAnd.Add(builder.Regex($"Attributes.{keyValuePair.Key.Trim()}", queryExpr));
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.AttributesContainsAny is not null)
|
||||
{
|
||||
foreach (var keyValuePair in filter.AttributesContainsAny)
|
||||
{
|
||||
foreach (var value in keyValuePair.Value.Split(','))
|
||||
{
|
||||
var queryExpr = new BsonRegularExpression(new Regex($"^{value.Trim()}$", RegexOptions.IgnoreCase));
|
||||
stageTwoFiltersOr.Add(builder.Regex($"Attributes.{keyValuePair.Key.Trim()}", queryExpr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.CreatedDateMinimum != null)
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.Gte("CreatedDate", filter.CreatedDateMinimum));
|
||||
}
|
||||
|
||||
if (filter.CreatedDateMaximum != null)
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.Lte("CreatedDate", filter.CreatedDateMaximum));
|
||||
}
|
||||
|
||||
if (filter.UploadDateMinimum != null)
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.Gte("UploadDate", filter.UploadDateMinimum));
|
||||
}
|
||||
|
||||
if (filter.UploadDateMaximum != null)
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.Lte("UploadDate", filter.UploadDateMaximum));
|
||||
}
|
||||
|
||||
if (filter.LastModifiedMinimum != null)
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.Gte("LastModifiedDate", filter.LastModifiedMinimum));
|
||||
}
|
||||
|
||||
if (filter.LastModifiedDateMaximum != null)
|
||||
{
|
||||
stageOneFiltersAnd.Add(builder.Lte("LastModifiedDate", filter.LastModifiedDateMaximum));
|
||||
}
|
||||
|
||||
var stageOneFilterConcat = builder.Empty;
|
||||
if (stageOneFiltersAnd.Any())
|
||||
{
|
||||
stageOneFilterConcat &= builder.And(stageOneFiltersAnd);
|
||||
}
|
||||
if (stageOneFiltersOr.Any())
|
||||
{
|
||||
stageOneFilterConcat &= builder.Or(stageOneFiltersOr);
|
||||
}
|
||||
|
||||
var stageTwoFilterConcat = builder.Empty;
|
||||
if (stageTwoFiltersAnd.Any())
|
||||
{
|
||||
stageOneFilterConcat &= builder.And(stageTwoFiltersAnd);
|
||||
}
|
||||
if (stageTwoFiltersOr.Any())
|
||||
{
|
||||
stageTwoFilterConcat &= builder.Or(stageTwoFiltersOr);
|
||||
}
|
||||
|
||||
return new EmptyPipelineDefinition<DocumentInformation>()
|
||||
.Match(stageOneFilterConcat)
|
||||
.Match(stageTwoFilterConcat);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.Repositories;
|
||||
|
||||
public interface IDataClient
|
||||
{
|
||||
MongoClient GetClient();
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.Repositories;
|
||||
|
||||
public interface IDocumentAuditRepository
|
||||
{
|
||||
Task<List<DocumentAudit>> GetDocumentAudits(params Guid[] documentIds);
|
||||
Task<IEnumerable<DocumentAudit>> SaveDocumentAudits(params DocumentAudit[] documentAudit);
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Models.Pagination;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.Repositories;
|
||||
|
||||
public interface IDocumentInformationRepository
|
||||
{
|
||||
Task<DocumentInformation> GetDocumentInformation(Guid documentId);
|
||||
Task<DocumentInformation> GetDocumentInformationByBlobId(Guid documentBlobId);
|
||||
Task<List<DocumentInformation>> SearchDocumentInformation(DocumentInformationFilter filter, int? skip = null, int? take = null, List<SortableField>? sortDefinition = null);
|
||||
Task<DocumentInformation> SaveDocumentInformation(DocumentInformation document);
|
||||
Task<DocumentInformation> UpdateDocumentInformation(Guid documentId, DocumentInformation newDocument);
|
||||
Task<DocumentInformation> RemoveDocumentInformation(Guid documentId);
|
||||
Task<long> CountDocuments(DocumentInformationFilter filter);
|
||||
Task<List<Guid>> GetDocumentsForCleanup(GetDocumentCleanupRequest request);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using LCU.Library.Internal.Exceptions;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.Utilities;
|
||||
|
||||
public class HttpUtilities
|
||||
{
|
||||
public static HttpClient CreateClient(Uri uri)
|
||||
{
|
||||
var handler = new HttpClientHandler
|
||||
{
|
||||
SslProtocols = System.Security.Authentication.SslProtocols.Tls12 |
|
||||
System.Security.Authentication.SslProtocols.Tls11 |
|
||||
System.Security.Authentication.SslProtocols.Tls,
|
||||
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
|
||||
};
|
||||
|
||||
var httpClient = new HttpClient(handler)
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(60),
|
||||
BaseAddress = uri
|
||||
};
|
||||
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
public static async Task PostAsync<TRequest>(Uri uri, TRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = CreateClient(uri);
|
||||
var jsonBody = JsonSerializer.Serialize(request);
|
||||
var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
|
||||
var response = await client.PostAsync(uri, content);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new ServiceException($"StatusCode:{response.StatusCode}, " +
|
||||
$"Content: {response.Content.ReadAsStringAsync().Result}");
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, "Failure during PostAsync.");
|
||||
throw new ServiceException($"Failure during Document Upload. Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<TResponse> PostAsync<TRequest, TResponse>(Uri uri, TRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = CreateClient(uri);
|
||||
var jsonBody = JsonSerializer.Serialize(request);
|
||||
var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
|
||||
var response = await client.PostAsync(uri, content);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new ServiceException($"StatusCode:{response.StatusCode}, " +
|
||||
$"Content: {response.Content.ReadAsStringAsync().Result}");
|
||||
}
|
||||
|
||||
var stringResponse = await response.Content.ReadAsStringAsync();
|
||||
|
||||
return JsonSerializer.Deserialize<TResponse>(stringResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, "Failure during PostAsync.");
|
||||
throw new ServiceException($"Failure during PostAsync. Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<TResponse> GetAsync<TResponse>(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
var uri = new Uri(url);
|
||||
var client = CreateClient(uri);
|
||||
|
||||
var response = await client.GetAsync(uri);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new ServiceException($"StatusCode:{response.StatusCode}, " +
|
||||
$"Content: {response.Content.ReadAsStringAsync().Result}");
|
||||
}
|
||||
|
||||
var stringResponse = await response.Content.ReadAsStringAsync();
|
||||
return JsonSerializer.Deserialize<TResponse>(stringResponse, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, "Failure during GetAsync.");
|
||||
throw new ServiceException($"Failure during GetAsync. Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.Utilities;
|
||||
|
||||
public interface IMongoCollectionFactory
|
||||
{
|
||||
IMongoCollection<T> GetCollection<T>();
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using LCU.API.Internal.DocumentLib.Configurations;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.API.Internal.DocumentLib.Repositories;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLib.Utilities;
|
||||
|
||||
public class MongoCollectionFactory : IMongoCollectionFactory
|
||||
{
|
||||
private readonly IDataClient _mongoDataClient;
|
||||
private readonly DatabaseConfiguration _config;
|
||||
|
||||
public MongoCollectionFactory(IDataClient mongoDataClient, DatabaseConfiguration config)
|
||||
{
|
||||
_mongoDataClient = mongoDataClient;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public IMongoCollection<T> GetCollection<T>()
|
||||
{
|
||||
return _mongoDataClient
|
||||
.GetClient()
|
||||
.GetDatabase(_config.DatabaseName)
|
||||
.GetCollection<T>(CollectionNameFor<T>());
|
||||
}
|
||||
|
||||
private string CollectionNameFor<T>()
|
||||
{
|
||||
return typeof(T) switch
|
||||
{
|
||||
var t when t == typeof(DocumentInformation) => _config.DocumentContainerName,
|
||||
var t when t == typeof(DocumentAudit) => _config.AuditContainerName,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
}
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
using FluentAssertions;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.API.Internal.DocumentLibTest.TestDoubles;
|
||||
using LCU.Library.Internal.DocumentModels.Requests;
|
||||
using MongoDB.Bson.Serialization;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLibTest.Containers;
|
||||
|
||||
public class DocumentAuditDiscriminatorConventionTest : IDisposable
|
||||
{
|
||||
private readonly FakeMongoCollectionFactory _collectionFactory = new();
|
||||
private readonly IMongoCollection<DocumentAudit> _collection;
|
||||
|
||||
public DocumentAuditDiscriminatorConventionTest()
|
||||
{
|
||||
_collection = _collectionFactory.GetCollection<DocumentAudit>();
|
||||
}
|
||||
|
||||
private static bool _registered = false;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
if (_registered) return;
|
||||
|
||||
BsonSerializer.RegisterDiscriminatorConvention
|
||||
(
|
||||
typeof(DocumentAudit),
|
||||
new DocumentAuditDiscriminatorConvention()
|
||||
);
|
||||
_registered = true;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindAuditDocuments_DocumentMissingDiscriminator_DeserializesAsFieldAudit()
|
||||
{
|
||||
var audit = FieldAuditFactory();
|
||||
await _collection.InsertOneAsync(audit);
|
||||
await _collection.FindOneAndUpdateAsync
|
||||
(
|
||||
a => a.DocumentId == audit.DocumentId,
|
||||
Builders<DocumentAudit>.Update.Unset(DocumentAuditDiscriminatorConvention.Discriminator)
|
||||
);
|
||||
|
||||
var retrievedAudit = await
|
||||
(await _collection.FindAsync(a => a.DocumentId == audit.DocumentId))
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
Assert.IsInstanceOf<DocumentFieldAudit>(retrievedAudit);
|
||||
retrievedAudit.Should().BeEquivalentTo(audit, a => a.Using(DateTimeComparison.Truncated));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindAuditDocuments_DiscriminatorIsFieldAudit_DeserializesAsFieldAudit()
|
||||
{
|
||||
var audit = FieldAuditFactory();
|
||||
await _collection.InsertOneAsync(audit);
|
||||
await _collection.FindOneAndUpdateAsync
|
||||
(
|
||||
a => a.DocumentId == audit.DocumentId,
|
||||
Builders<DocumentAudit>.Update.Set
|
||||
(
|
||||
DocumentAuditDiscriminatorConvention.Discriminator,
|
||||
nameof(DocumentFieldAudit)
|
||||
)
|
||||
);
|
||||
|
||||
var retrievedAudit = await
|
||||
(await _collection.FindAsync(a => a.DocumentId == audit.DocumentId))
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
Assert.IsInstanceOf<DocumentFieldAudit>(retrievedAudit);
|
||||
retrievedAudit.Should().BeEquivalentTo(audit, a => a.Using(DateTimeComparison.Truncated));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task InsertFieldAuditDocuments_NewDocumentWithConventionRegistered_DiscriminatorIsFieldAudit()
|
||||
{
|
||||
var audit = FieldAuditFactory();
|
||||
|
||||
await _collection.InsertOneAsync(audit);
|
||||
|
||||
var audits = await
|
||||
(await _collection.FindAsync
|
||||
(
|
||||
Builders<DocumentAudit>.Filter.Eq
|
||||
(
|
||||
DocumentAuditDiscriminatorConvention.Discriminator,
|
||||
nameof(DocumentFieldAudit)
|
||||
)
|
||||
)
|
||||
)
|
||||
.ToListAsync();
|
||||
audits.Should().ContainEquivalentOf(audit, a => a.Using(DateTimeComparison.Truncated));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindAuditDocuments_DiscriminatorIsActionAudit_DeserializesAsActionAudit()
|
||||
{
|
||||
var audit = ActionAuditFactory();
|
||||
await _collection.InsertOneAsync(audit);
|
||||
await _collection.FindOneAndUpdateAsync
|
||||
(
|
||||
a => a.DocumentId == audit.DocumentId,
|
||||
Builders<DocumentAudit>.Update.Set
|
||||
(
|
||||
DocumentAuditDiscriminatorConvention.Discriminator,
|
||||
nameof(DocumentActionAudit)
|
||||
)
|
||||
);
|
||||
|
||||
var retrievedAudit = await
|
||||
(await _collection.FindAsync(a => a.DocumentId == audit.DocumentId))
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
Assert.IsInstanceOf<DocumentActionAudit>(retrievedAudit);
|
||||
retrievedAudit.Should().BeEquivalentTo(audit, a => a.Using(DateTimeComparison.Truncated));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task InsertActionAuditDocuments_NewDocumentWithConventionRegistered_DiscriminatorIsActionAudit()
|
||||
{
|
||||
var audit = ActionAuditFactory();
|
||||
|
||||
await _collection.InsertOneAsync(audit);
|
||||
|
||||
var audits = await
|
||||
(await _collection.FindAsync
|
||||
(
|
||||
Builders<DocumentAudit>.Filter.Eq
|
||||
(
|
||||
DocumentAuditDiscriminatorConvention.Discriminator,
|
||||
nameof(DocumentActionAudit)
|
||||
)
|
||||
)
|
||||
)
|
||||
.ToListAsync();
|
||||
audits.Should().ContainEquivalentOf(audit, a => a.Using(DateTimeComparison.Truncated));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FindAuditDocuments_UnknownDiscriminator_ThrowsException()
|
||||
{
|
||||
var audit = FieldAuditFactory();
|
||||
await _collection.InsertOneAsync(audit);
|
||||
await _collection.FindOneAndUpdateAsync
|
||||
(
|
||||
a => a.DocumentId == audit.DocumentId,
|
||||
Builders<DocumentAudit>.Update.Set
|
||||
(
|
||||
DocumentAuditDiscriminatorConvention.Discriminator,
|
||||
Guid.NewGuid().ToString()
|
||||
)
|
||||
);
|
||||
|
||||
Assert.ThrowsAsync<NotSupportedException>(() => _collection.FindAsync(a => a.DocumentId == audit.DocumentId));
|
||||
}
|
||||
|
||||
private static DocumentFieldAudit FieldAuditFactory() => new
|
||||
(
|
||||
Guid.NewGuid(),
|
||||
Guid.NewGuid().ToString(),
|
||||
DateTime.UtcNow,
|
||||
Guid.NewGuid().ToString(),
|
||||
Guid.NewGuid().ToString(),
|
||||
Guid.NewGuid(),
|
||||
Guid.NewGuid()
|
||||
);
|
||||
|
||||
private static DocumentActionAudit ActionAuditFactory() => new
|
||||
(
|
||||
Guid.NewGuid(),
|
||||
Guid.NewGuid().ToString(),
|
||||
DateTime.UtcNow,
|
||||
Guid.NewGuid().ToString(),
|
||||
DocumentAction.Download,
|
||||
Guid.NewGuid().ToString()
|
||||
);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_collectionFactory.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using FluentAssertions;
|
||||
using FluentAssertions.Equivalency;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLibTest;
|
||||
|
||||
public static class DateTimeComparison
|
||||
{
|
||||
public static IEquivalencyStep Truncated { get; } = new TruncatedDateTimeStep();
|
||||
|
||||
public static DateTime Truncate(this DateTime date) => date.AddTicks(-(date.Ticks % 10000));
|
||||
|
||||
private class TruncatedDateTimeStep : IEquivalencyStep
|
||||
{
|
||||
public EquivalencyResult Handle
|
||||
(
|
||||
Comparands comparands,
|
||||
IEquivalencyValidationContext context,
|
||||
IEquivalencyValidator nestedValidator
|
||||
)
|
||||
{
|
||||
if (comparands.Subject is not DateTime subject || comparands.Expectation is not DateTime expectation)
|
||||
return EquivalencyResult.ContinueWithNext;
|
||||
|
||||
subject.Truncate()
|
||||
.Should()
|
||||
.Be(expectation.Truncate(), context.Reason.FormattedMessage, context.Reason.Arguments);
|
||||
|
||||
return EquivalencyResult.AssertionCompleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.7.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
|
||||
<PackageReference Include="Mongo2Go" Version="3.1.3" />
|
||||
<PackageReference Include="Moq" Version="4.18.2" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.3.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LCU.API.Internal.DocumentLib\LCU.API.Internal.DocumentLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Repositories\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
using FluentAssertions;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.API.Internal.DocumentLib.Repositories;
|
||||
using LCU.API.Internal.DocumentLibTest.TestDataFactories;
|
||||
using LCU.API.Internal.DocumentLibTest.TestDoubles;
|
||||
using LCU.Library.Internal.DocumentModels.Requests;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLibTest.Repositories;
|
||||
|
||||
public class DocumentAuditRepositoryTest : IDisposable
|
||||
{
|
||||
private readonly IDocumentAuditRepository _repository;
|
||||
|
||||
private readonly FakeMongoCollectionFactory _collectionFactory = new();
|
||||
|
||||
public DocumentAuditRepositoryTest()
|
||||
{
|
||||
_repository = new DocumentAuditRepository(_collectionFactory);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDocumentAudits_FieldAuditRecordsExistForId_ReturnsMatchedLogsById()
|
||||
{
|
||||
var audits = EnumerableDataSeriesFactory.CreateSeries(() => FieldAuditFactory(), 5).ToArray();
|
||||
await _collectionFactory.GetCollection<DocumentAudit>().InsertManyAsync(audits);
|
||||
var expectedAudits = new[]{ audits.First(), audits.Last() };
|
||||
var documentIds = expectedAudits.Select(a => a.DocumentId).ToArray();
|
||||
|
||||
var retrievedAudits = await _repository.GetDocumentAudits(documentIds);
|
||||
|
||||
retrievedAudits.Should().HaveSameCount(documentIds);
|
||||
expectedAudits.Should().BeEquivalentTo(retrievedAudits, a => a.Using(DateTimeComparison.Truncated));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDocumentAudits_ActionAuditRecordsExistForId_ReturnsMatchedLogsById()
|
||||
{
|
||||
var audits = EnumerableDataSeriesFactory.CreateSeries(() => ActionAuditFactory(), 5).ToArray();
|
||||
await _collectionFactory.GetCollection<DocumentAudit>().InsertManyAsync(audits);
|
||||
var expectedAudits = new[] { audits.First(), audits.Last() };
|
||||
var documentIds = expectedAudits.Select(a => a.DocumentId).ToArray();
|
||||
|
||||
var retrievedAudits = await _repository.GetDocumentAudits(documentIds);
|
||||
|
||||
retrievedAudits.Should().HaveSameCount(documentIds);
|
||||
expectedAudits.Should().BeEquivalentTo(retrievedAudits, a => a.Using(DateTimeComparison.Truncated));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SaveDocumentAudits_FieldAuditsSubmitted_SavesAll()
|
||||
{
|
||||
var audits = EnumerableDataSeriesFactory.CreateSeries(() => FieldAuditFactory(), 5).ToArray();
|
||||
|
||||
await _repository.SaveDocumentAudits(audits);
|
||||
|
||||
var savedAudits = await (
|
||||
await _collectionFactory
|
||||
.GetCollection<DocumentAudit>()
|
||||
.FindAsync(Builders<DocumentAudit>.Filter.In
|
||||
(
|
||||
nameof(DocumentAudit.DocumentId),
|
||||
audits.Select(a => a.DocumentId))
|
||||
)
|
||||
).ToListAsync();
|
||||
audits.Should().BeEquivalentTo(savedAudits, a => a.Using(DateTimeComparison.Truncated));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SaveDocumentAudits_ActionAuditsSubmitted_SavesAll()
|
||||
{
|
||||
var audits = EnumerableDataSeriesFactory.CreateSeries(() => ActionAuditFactory(), 5).ToArray();
|
||||
|
||||
await _repository.SaveDocumentAudits(audits);
|
||||
|
||||
var savedAudits = await (
|
||||
await _collectionFactory
|
||||
.GetCollection<DocumentAudit>()
|
||||
.FindAsync(Builders<DocumentAudit>.Filter.In(nameof(DocumentAudit.DocumentId),
|
||||
audits.Select(a => a.DocumentId)))
|
||||
).ToListAsync();
|
||||
audits.Should().BeEquivalentTo(savedAudits, a => a.Using(DateTimeComparison.Truncated));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SaveDocumentAudits_MultipleTypesOfAudits_CanRetrieveAll()
|
||||
{
|
||||
var documentId = Guid.NewGuid();
|
||||
var fieldAudits = EnumerableDataSeriesFactory.CreateSeries<DocumentAudit>(() => FieldAuditFactory(documentId), 5);
|
||||
var actionAudits = EnumerableDataSeriesFactory.CreateSeries<DocumentAudit>(() => ActionAuditFactory(documentId), 5);
|
||||
var audits = fieldAudits.Concat(actionAudits).ToArray();
|
||||
|
||||
await _repository.SaveDocumentAudits(audits);
|
||||
var savedAudits = await _repository.GetDocumentAudits(documentId);
|
||||
|
||||
audits.Should().BeEquivalentTo(savedAudits, a => a.Using(DateTimeComparison.Truncated));
|
||||
}
|
||||
|
||||
private static DocumentFieldAudit FieldAuditFactory(Guid? documentId = null) => new
|
||||
(
|
||||
documentId ?? Guid.NewGuid(),
|
||||
Guid.NewGuid().ToString(),
|
||||
DateTime.UtcNow,
|
||||
Guid.NewGuid().ToString(),
|
||||
Guid.NewGuid().ToString(),
|
||||
Guid.NewGuid().ToString(),
|
||||
Guid.NewGuid().ToString()
|
||||
);
|
||||
|
||||
private static DocumentActionAudit ActionAuditFactory(Guid? documentId = null) => new
|
||||
(
|
||||
documentId ?? Guid.NewGuid(),
|
||||
Guid.NewGuid().ToString(),
|
||||
DateTime.UtcNow,
|
||||
Guid.NewGuid().ToString(),
|
||||
(DocumentAction)DateTime.Now.Millisecond,
|
||||
Guid.NewGuid().ToString()
|
||||
);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_collectionFactory.Dispose();
|
||||
}
|
||||
}
|
||||
+1230
File diff suppressed because it is too large
Load Diff
+33
@@ -0,0 +1,33 @@
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLibTest.TestDataFactories;
|
||||
|
||||
public static class DocumentInformationFactory
|
||||
{
|
||||
public static DocumentInformation CreateDocumentInformation() => new()
|
||||
{
|
||||
DocumentId = Guid.NewGuid(),
|
||||
DocumentBlobId = Guid.NewGuid(),
|
||||
TraceId = Guid.NewGuid(),
|
||||
Name = Guid.NewGuid().ToString(),
|
||||
Type = Guid.NewGuid().ToString(),
|
||||
Source = Guid.NewGuid().ToString(),
|
||||
DataSource = Guid.NewGuid().ToString(),
|
||||
FileSize = DateTime.Now.Millisecond,
|
||||
ScanStatus = DateTime.Now.Millisecond % 2 == 0,
|
||||
UploadDate = DateTime.UtcNow,
|
||||
LastModifiedDate = DateTime.UtcNow,
|
||||
CreatedDate = DateTime.UtcNow,
|
||||
PersonNumbers = new[] { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() },
|
||||
OrganizationNumbers = new []{ Guid.NewGuid().ToString() , Guid.NewGuid().ToString() },
|
||||
AccountNumber = Guid.NewGuid().ToString(),
|
||||
Attributes = new Dictionary<string, string>
|
||||
{
|
||||
["PersonNumber"] = Guid.NewGuid().ToString(),
|
||||
["PersonNumbers"] = $"{Guid.NewGuid()},{Guid.NewGuid()}",
|
||||
["OrganizationNumber"] = Guid.NewGuid().ToString(),
|
||||
["OrganizationNumbers"] = $"{Guid.NewGuid()},{Guid.NewGuid()}",
|
||||
["AccountNumber"] = Guid.NewGuid().ToString()
|
||||
},
|
||||
};
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
namespace LCU.API.Internal.DocumentLibTest.TestDataFactories;
|
||||
|
||||
public static class EnumerableDataSeriesFactory
|
||||
{
|
||||
public static IEnumerable<T> CreateSeries<T>(Func<T> singleFactory, int count) => Enumerable
|
||||
.Repeat(singleFactory, count)
|
||||
.Select(f => f());
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
using LCU.API.Internal.DocumentLib.Utilities;
|
||||
using Mongo2Go;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace LCU.API.Internal.DocumentLibTest.TestDoubles;
|
||||
|
||||
public class FakeMongoCollectionFactory : IMongoCollectionFactory, IDisposable
|
||||
{
|
||||
private readonly MongoDbRunner _runner = MongoDbRunner.Start();
|
||||
|
||||
public IMongoCollection<T> GetCollection<T>() => new MongoClient(_runner.ConnectionString)
|
||||
.GetDatabase(typeof(T).Name)
|
||||
.GetCollection<T>(typeof(T).Name);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_runner.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
global using NUnit.Framework;
|
||||
+1426
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Azure;
|
||||
using Azure.Storage.Blobs;
|
||||
using Azure.Storage.Blobs.Models;
|
||||
using LCU.API.Internal.DocumentLib.DataAccess;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace LCU.API.Internal.DocumentTest.DataAccess;
|
||||
|
||||
public class BlobApiDaoTest
|
||||
{
|
||||
|
||||
private BlobApiDao _dao;
|
||||
private Mock<BlobClient> _mockClient;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
var mockBlobClient = new Mock<IBlobStorageClient>();
|
||||
var mockBlobContainerClient = new Mock<BlobContainerClient>();
|
||||
mockBlobClient.Setup(client => client.GetClient()).Returns(mockBlobContainerClient.Object);
|
||||
|
||||
_dao = new BlobApiDao(mockBlobClient.Object);
|
||||
_mockClient = new Mock<BlobClient>();
|
||||
mockBlobContainerClient.Setup(client => client.GetBlobClient(It.IsAny<string>())).Returns(_mockClient.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SaveDocumentToBlob_Success()
|
||||
{
|
||||
var fileData = Encoding.UTF8.GetBytes("test");
|
||||
_mockClient.Setup( mock => mock.UploadAsync(It.IsAny<Stream>())).Verifiable();
|
||||
|
||||
var result = await _dao.SaveDocumentToBlob(fileData);
|
||||
|
||||
Assert.NotNull(result);
|
||||
_mockClient.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SaveDocumentToBlob_Exception_ThrowsException()
|
||||
{
|
||||
var fileData = Encoding.UTF8.GetBytes("test");
|
||||
_mockClient.Setup( mock => mock.UploadAsync(It.IsAny<Stream>())).ThrowsAsync(new Exception("Exception thrown from unit test.")).Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<Exception>(async () => await _dao.SaveDocumentToBlob(fileData));
|
||||
_mockClient.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpdateDocumentInBlob_Success()
|
||||
{
|
||||
var blobDocId = Guid.NewGuid();
|
||||
var fileData = Encoding.UTF8.GetBytes("test");
|
||||
_mockClient.Setup( mock => mock.UploadAsync(It.IsAny<Stream>(), true, CancellationToken.None)).Verifiable();
|
||||
|
||||
await _dao.UpdateDocumentInBlob(blobDocId, fileData);
|
||||
|
||||
_mockClient.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UpdateDocumentInBlob_Exception_ThrowsException()
|
||||
{
|
||||
var blobDocId = Guid.NewGuid();
|
||||
var fileData = Encoding.UTF8.GetBytes("test");
|
||||
_mockClient.Setup( mock => mock.UploadAsync(It.IsAny<Stream>(), true, CancellationToken.None)).ThrowsAsync(new Exception("Exception thrown from unit test.")).Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<Exception>(async () => await _dao.UpdateDocumentInBlob(blobDocId, fileData));
|
||||
_mockClient.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RemoveDocumentFromBlob_Success()
|
||||
{
|
||||
var blobDocId = Guid.NewGuid();
|
||||
_mockClient.Setup(mock => mock.DeleteAsync(DeleteSnapshotsOption.IncludeSnapshots, It.IsAny<BlobRequestConditions>(), CancellationToken.None)).Verifiable();
|
||||
|
||||
await _dao.RemoveDocumentFromBlob(blobDocId);
|
||||
|
||||
_mockClient.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RemoveDocumentFromBlob_Exception_ThrowsException()
|
||||
{
|
||||
var blobDocId = Guid.NewGuid();
|
||||
_mockClient.Setup( mock => mock.DeleteAsync(DeleteSnapshotsOption.IncludeSnapshots, It.IsAny<BlobRequestConditions>(), CancellationToken.None)).ThrowsAsync(new Exception("Exception thrown from unit test.")).Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<Exception>(async () => await _dao.RemoveDocumentFromBlob(blobDocId));
|
||||
_mockClient.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDocumentFromBlob_Success()
|
||||
{
|
||||
var blobDocId = Guid.NewGuid();
|
||||
_mockClient.Setup(mock => mock.DownloadStreamingAsync(It.IsAny<HttpRange>(), It.IsAny<BlobRequestConditions>(), It.IsAny<bool>(), It.IsAny<IProgress<long>>(),It.IsAny<CancellationToken>())).Verifiable();
|
||||
|
||||
await _dao.GetDocumentFromBlob(blobDocId);
|
||||
|
||||
_mockClient.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDocumentFromBlob_Exception_ThrowsException()
|
||||
{
|
||||
var blobDocId = Guid.NewGuid();
|
||||
_mockClient.Setup( mock => mock.DownloadStreamingAsync(It.IsAny<HttpRange>(), It.IsAny<BlobRequestConditions>(), It.IsAny<bool>(), It.IsAny<IProgress<long>>(), It.IsAny<CancellationToken>())).ThrowsAsync(new Exception("Exception thrown from unit test.")).Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<Exception>(async () => await _dao.GetDocumentFromBlob(blobDocId));
|
||||
_mockClient.Verify();
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.7.0" />
|
||||
<PackageReference Include="LCU.API.Internal.NotificationsApiLib" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="Moq" Version="4.17.2" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LCU.API.Internal.Document\LCU.API.Internal.Document.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Azure;
|
||||
using Azure.Storage.Blobs.Models;
|
||||
using LCU.API.Internal.Document.Exceptions;
|
||||
using LCU.API.Internal.Document.Services;
|
||||
using LCU.API.Internal.DocumentLib.DataAccess;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace LCU.API.Internal.DocumentTest.Services;
|
||||
|
||||
public class BlobApiServiceTest
|
||||
{
|
||||
private BlobApiService _blobApiService;
|
||||
private Mock<IBlobApiDao> _mockBlobDao;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_mockBlobDao = new Mock<IBlobApiDao>();
|
||||
_blobApiService = new BlobApiService(_mockBlobDao.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDocumentContent_Success()
|
||||
{
|
||||
var docBlobId = Guid.NewGuid();
|
||||
await using var testStream = new MemoryStream(Encoding.UTF8.GetBytes("whatever"));
|
||||
var mockContent = BlobsModelFactory.BlobDownloadStreamingResult(testStream);
|
||||
var mockContentResponse = new Mock<Response<BlobDownloadStreamingResult>>();
|
||||
|
||||
mockContentResponse.Setup(content => content.Value).Returns(mockContent).Verifiable();
|
||||
_mockBlobDao
|
||||
.Setup(x => x.GetDocumentFromBlob(docBlobId)).ReturnsAsync(mockContentResponse.Object)
|
||||
.Verifiable();
|
||||
|
||||
var returnedDocInfo = await _blobApiService.GetDocumentContent(docBlobId);
|
||||
|
||||
Assert.NotNull(returnedDocInfo);
|
||||
_mockBlobDao.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDocument_ContentValueIsNull_DocumentBlobServiceExceptionThrown()
|
||||
{
|
||||
var docBlobId = Guid.NewGuid();
|
||||
|
||||
_mockBlobDao
|
||||
.Setup(x => x.GetDocumentFromBlob(docBlobId)).ReturnsAsync((Response<BlobDownloadStreamingResult>)null)
|
||||
.Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<BlobApiServiceException>(async() => await _blobApiService.GetDocumentContent(docBlobId));
|
||||
_mockBlobDao.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDocument_ExceptionThrown_DocumentBlobServiceExceptionThrown()
|
||||
{
|
||||
var docBlobId = Guid.NewGuid();
|
||||
|
||||
_mockBlobDao
|
||||
.Setup(x => x.GetDocumentFromBlob(docBlobId)).ThrowsAsync(new Exception("Thrown from unit test"))
|
||||
.Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<BlobApiServiceException>(async() => await _blobApiService.GetDocumentContent(docBlobId));
|
||||
_mockBlobDao.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UploadDocumentContent_Success()
|
||||
{
|
||||
var docContent = new byte[10];
|
||||
_mockBlobDao.Setup(x => x.SaveDocumentToBlob(docContent)).Verifiable();
|
||||
|
||||
await _blobApiService.UploadDocumentContent(docContent);
|
||||
_mockBlobDao.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UploadDocumentContent_Exception_ThrowsBlobApiServiceException()
|
||||
{
|
||||
var docContent = new byte[10];
|
||||
_mockBlobDao.Setup(x => x.SaveDocumentToBlob(docContent)).ThrowsAsync(new Exception("Thrown from unit test")).Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<BlobApiServiceException>(async() => await _blobApiService.UploadDocumentContent(docContent));
|
||||
_mockBlobDao.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpdateDocumentContent_Success()
|
||||
{
|
||||
var docBlobId = Guid.NewGuid();
|
||||
var docContent = new byte[10];
|
||||
_mockBlobDao.Setup(x => x.UpdateDocumentInBlob(docBlobId, docContent)).Verifiable();
|
||||
|
||||
await _blobApiService.UpdateDocumentContent(docBlobId, docContent);
|
||||
_mockBlobDao.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UpdateDocumentContent_Exception_ThrowsBlobApiServiceException()
|
||||
{
|
||||
var docBlobId = Guid.NewGuid();
|
||||
var docContent = new byte[10];
|
||||
_mockBlobDao.Setup(x => x.UpdateDocumentInBlob(docBlobId, docContent)).ThrowsAsync(new Exception("Thrown from unit test")).Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<BlobApiServiceException>(async() => await _blobApiService.UpdateDocumentContent(docBlobId, docContent));
|
||||
_mockBlobDao.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UpdateDocumentContent_Exception_DocumentBlobIdIsNull()
|
||||
{
|
||||
Guid? docBlobId = null;
|
||||
var docContent = new byte[10];
|
||||
|
||||
Assert.ThrowsAsync<BlobApiServiceException>(async() => await _blobApiService.UpdateDocumentContent(docBlobId, docContent));
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task DeleteDocumentContent_Success()
|
||||
{
|
||||
var docBlobId = Guid.NewGuid();
|
||||
_mockBlobDao.Setup(x => x.RemoveDocumentFromBlob(docBlobId)).Verifiable();
|
||||
|
||||
await _blobApiService.DeleteDocumentContent(docBlobId);
|
||||
_mockBlobDao.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeleteDocumentContent_Exception_ThrowsBlobApiServiceException()
|
||||
{
|
||||
var docBlobId = Guid.NewGuid();
|
||||
_mockBlobDao.Setup(x => x.RemoveDocumentFromBlob(docBlobId)).ThrowsAsync(new Exception("Thrown from unit test")).Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<BlobApiServiceException>(async() => await _blobApiService.DeleteDocumentContent(docBlobId));
|
||||
_mockBlobDao.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeleteDocumentContent_Exception_DocumentBlobIdIsNull()
|
||||
{
|
||||
Guid? docBlobId = null;
|
||||
|
||||
Assert.ThrowsAsync<BlobApiServiceException>(async() => await _blobApiService.DeleteDocumentContent(docBlobId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDocument_BlobGuidIsEmpty_ReturnsEmptyContent()
|
||||
{
|
||||
var docBlobId = Guid.Empty;
|
||||
var emptyContent = Array.Empty<byte>();
|
||||
|
||||
var result = await _blobApiService.GetDocumentContent(docBlobId);
|
||||
|
||||
Assert.AreEqual(emptyContent, result);
|
||||
_mockBlobDao.Verify(s => s.GetDocumentFromBlob(It.IsAny<Guid>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDocument_BlobGuidIsNull_ReturnsEmptyContent()
|
||||
{
|
||||
Guid? docBlobId = null;
|
||||
var emptyContent = Array.Empty<byte>();
|
||||
|
||||
var result = await _blobApiService.GetDocumentContent(docBlobId);
|
||||
|
||||
Assert.AreEqual(emptyContent, result);
|
||||
_mockBlobDao.Verify(s => s.GetDocumentFromBlob(It.IsAny<Guid>()), Times.Never);
|
||||
}
|
||||
}
|
||||
+557
@@ -0,0 +1,557 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using LCU.API.Internal.Document.Exceptions;
|
||||
using LCU.API.Internal.Document.Services;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.API.Internal.DocumentLib.Repositories;
|
||||
using LCU.Library.Internal.DocumentModels.Requests;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace LCU.API.Internal.DocumentTest.Services;
|
||||
|
||||
public class DocumentAuditServiceTest
|
||||
{
|
||||
private DocumentAuditService _documentAuditService;
|
||||
private Mock<IDocumentAuditRepository> _mockDocumentAuditRepository;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_mockDocumentAuditRepository = new Mock<IDocumentAuditRepository>();
|
||||
_documentAuditService = new DocumentAuditService(_mockDocumentAuditRepository.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDocumentAudits_RepositoryReturnsActionAudits_ReturnsAuditsTransformedToResponse()
|
||||
{
|
||||
var documentId = Guid.NewGuid();
|
||||
var actionAudits = new List<DocumentAudit>
|
||||
{
|
||||
new DocumentActionAudit
|
||||
(
|
||||
documentId,
|
||||
Guid.NewGuid().ToString(),
|
||||
DateTime.Now,
|
||||
Guid.NewGuid().ToString(),
|
||||
Valid<DocumentAction>(),
|
||||
Guid.NewGuid().ToString()
|
||||
)
|
||||
};
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(r => r.GetDocumentAudits(documentId))
|
||||
.ReturnsAsync(actionAudits);
|
||||
|
||||
var audits = await _documentAuditService.GetDocumentAudits(documentId);
|
||||
|
||||
actionAudits.Should().HaveSameCount(audits);
|
||||
actionAudits.Should().AllSatisfy(a => audits.Should().ContainEquivalentOf(a));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDocumentAudits_RepositoryReturnsFieldAudits_ReturnsAuditsTransformedToResponse()
|
||||
{
|
||||
var documentId = Guid.NewGuid();
|
||||
var actionAudits = new List<DocumentAudit>
|
||||
{
|
||||
new DocumentFieldAudit
|
||||
(
|
||||
documentId,
|
||||
Guid.NewGuid().ToString(),
|
||||
DateTime.Now,
|
||||
Guid.NewGuid().ToString(),
|
||||
Guid.NewGuid().ToString(),
|
||||
Guid.NewGuid(),
|
||||
Guid.NewGuid()
|
||||
)
|
||||
};
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(r => r.GetDocumentAudits(documentId))
|
||||
.ReturnsAsync(actionAudits);
|
||||
|
||||
var audits = await _documentAuditService.GetDocumentAudits(documentId);
|
||||
|
||||
actionAudits.Should().HaveSameCount(audits);
|
||||
actionAudits.Should().AllSatisfy(a => audits.Should().ContainEquivalentOf(a));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDocumentAudits_RepositoryReturnsAnyAuditsOfUnknownType_ThrowsDocumentAuditServiceException()
|
||||
{
|
||||
var documentId = Guid.NewGuid();
|
||||
var actionAudits = new List<DocumentAudit>
|
||||
{
|
||||
new UnknownAuditType
|
||||
(
|
||||
documentId,
|
||||
Guid.NewGuid().ToString(),
|
||||
DateTime.Now,
|
||||
Guid.NewGuid().ToString()
|
||||
)
|
||||
};
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(r => r.GetDocumentAudits(documentId))
|
||||
.ReturnsAsync(actionAudits);
|
||||
|
||||
Assert.ThrowsAsync<DocumentAuditServiceException>(() => _documentAuditService.GetDocumentAudits(documentId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDocumentAudits_DocumentAuditRepositoryThrowsException_ThrowsDocumentAuditServiceExceptionThrown()
|
||||
{
|
||||
var docId = Guid.NewGuid();
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(x => x.GetDocumentAudits(docId))
|
||||
.ThrowsAsync(new Exception("Thrown From Unit Test"))
|
||||
.Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<DocumentAuditServiceException>(async () => await _documentAuditService.GetDocumentAudits(docId));
|
||||
_mockDocumentAuditRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDocumentAudits_Exception_DocumentAuditServiceExceptionThrown()
|
||||
{
|
||||
var docId = Guid.NewGuid();
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(x => x.GetDocumentAudits(docId))
|
||||
.ThrowsAsync(new Exception("Thrown From Unit Test"))
|
||||
.Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<DocumentAuditServiceException>(async () => await _documentAuditService.GetDocumentAudits(docId));
|
||||
_mockDocumentAuditRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CreateDocumentUploadedAudit_Success()
|
||||
{
|
||||
var uploadDate = DateTime.Parse("2022-04-28T17:32:35.093Z");
|
||||
var documentInformation = GenerateDocumentInformation();
|
||||
|
||||
var audits = new DocumentAudit[]
|
||||
{
|
||||
new DocumentActionAudit
|
||||
(
|
||||
documentInformation.DocumentId,
|
||||
"System",
|
||||
uploadDate,
|
||||
"System",
|
||||
DocumentAction.Upload,
|
||||
"Document Uploaded"
|
||||
)
|
||||
};
|
||||
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(x => x.SaveDocumentAudits(It.Is<DocumentAudit[]>(list =>
|
||||
list.Length == 1
|
||||
&& list.All(a => a is DocumentActionAudit)
|
||||
&& ((DocumentActionAudit)list.FirstOrDefault()!).Action == DocumentAction.Upload
|
||||
&& ((DocumentActionAudit)list.FirstOrDefault()!).UserId == "System"
|
||||
&& ((DocumentActionAudit)list.FirstOrDefault()!).Source == "System"
|
||||
&& ((DocumentActionAudit)list.FirstOrDefault()!).ActionDescription == "Document Uploaded")))
|
||||
.ReturnsAsync(audits)
|
||||
.Verifiable();
|
||||
|
||||
var result = await _documentAuditService.CreateDocumentUploadedAudit(documentInformation.DocumentId, uploadDate);
|
||||
var resultArray = result.ToArray();
|
||||
|
||||
Assert.NotNull(result);
|
||||
audits.Should().HaveSameCount(resultArray);
|
||||
audits.Should().AllSatisfy(a => resultArray.Should().ContainEquivalentOf(a));
|
||||
|
||||
_mockDocumentAuditRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateDocumentUploadedAudit_DocumentAuditRepositoryThrowsException_ThrowsNewDocumentAuditServiceExceptionThrown()
|
||||
{
|
||||
var uploadDate = DateTime.Parse("2022-04-28T17:32:35.093Z");
|
||||
var documentInformation = GenerateDocumentInformation();
|
||||
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(x => x.SaveDocumentAudits(It.Is<DocumentAudit[]>(list => list.Any())))
|
||||
.ThrowsAsync(new DocumentAuditServiceException("Thrown From Unit Test"))
|
||||
.Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<DocumentAuditServiceException>(() => _documentAuditService.CreateDocumentUploadedAudit(documentInformation.DocumentId, uploadDate));
|
||||
|
||||
_mockDocumentAuditRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateDocumentUploadedAudit_Exception_DocumentAuditServiceExceptionThrown()
|
||||
{
|
||||
var uploadDate = DateTime.Parse("2022-04-28T17:32:35.093Z");
|
||||
var documentInformation = GenerateDocumentInformation();
|
||||
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(x => x.SaveDocumentAudits(It.Is<DocumentAudit[]>(list => list.Any())))
|
||||
.ThrowsAsync(new Exception("Thrown From Unit Test"))
|
||||
.Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<DocumentAuditServiceException>(() => _documentAuditService.CreateDocumentUploadedAudit(documentInformation.DocumentId, uploadDate));
|
||||
|
||||
_mockDocumentAuditRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CreateDocumentDeletedAudit_Success()
|
||||
{
|
||||
var deleteDate = DateTime.Parse("2022-04-28T17:32:35.093Z");
|
||||
var documentInformation = GenerateDocumentInformation();
|
||||
|
||||
var audits = new DocumentAudit[]
|
||||
{
|
||||
new DocumentActionAudit
|
||||
(
|
||||
documentInformation.DocumentId,
|
||||
"System",
|
||||
deleteDate,
|
||||
"System",
|
||||
DocumentAction.Delete,
|
||||
"Document Deleted"
|
||||
)
|
||||
};
|
||||
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(x => x.SaveDocumentAudits(It.Is<DocumentAudit[]>(list =>
|
||||
list.Length == 1
|
||||
&& list.All(a => a is DocumentActionAudit)
|
||||
&& ((DocumentActionAudit)list.FirstOrDefault()!).Action == DocumentAction.Delete
|
||||
&& ((DocumentActionAudit)list.FirstOrDefault()!).UserId == "System"
|
||||
&& ((DocumentActionAudit)list.FirstOrDefault()!).Source == "System"
|
||||
&& ((DocumentActionAudit)list.FirstOrDefault()!).ActionDescription == "Document Deleted")))
|
||||
.ReturnsAsync(audits)
|
||||
.Verifiable();
|
||||
|
||||
var result = await _documentAuditService.CreateDocumentDeletedAudit(documentInformation.DocumentId, deleteDate);
|
||||
var resultArray = result.ToArray();
|
||||
|
||||
Assert.NotNull(result);
|
||||
audits.Should().HaveSameCount(resultArray);
|
||||
audits.Should().AllSatisfy(a => resultArray.Should().ContainEquivalentOf(a));
|
||||
|
||||
_mockDocumentAuditRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateDocumentDeletedAudit_DocumentAuditRepositoryThrowsException_ThrowsNewDocumentAuditServiceExceptionThrown()
|
||||
{
|
||||
var deleteDate = DateTime.Parse("2022-04-28T17:32:35.093Z");
|
||||
var documentInformation = GenerateDocumentInformation();
|
||||
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(x => x.SaveDocumentAudits(It.Is<DocumentAudit[]>(list => list.Any())))
|
||||
.ThrowsAsync(new DocumentAuditServiceException("Thrown From Unit Test"))
|
||||
.Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<DocumentAuditServiceException>(() => _documentAuditService.CreateDocumentDeletedAudit(documentInformation.DocumentId, deleteDate));
|
||||
|
||||
_mockDocumentAuditRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateDocumentDeletedAudit_Exception_DocumentAuditServiceExceptionThrown()
|
||||
{
|
||||
var deleteDate = DateTime.Parse("2022-04-28T17:32:35.093Z");
|
||||
var documentInformation = GenerateDocumentInformation();
|
||||
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(x => x.SaveDocumentAudits(It.Is<DocumentAudit[]>(list => list.Any())))
|
||||
.ThrowsAsync(new Exception("Thrown From Unit Test"))
|
||||
.Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<DocumentAuditServiceException>(() => _documentAuditService.CreateDocumentDeletedAudit(documentInformation.DocumentId, deleteDate));
|
||||
|
||||
_mockDocumentAuditRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CreateDocumentContentDeletedAudit_Success()
|
||||
{
|
||||
var deleteDate = DateTime.Parse("2022-04-28T17:32:35.093Z");
|
||||
var documentInformation = GenerateDocumentInformation();
|
||||
var reason = Guid.NewGuid().ToString();
|
||||
var reasonFull = $"DocumentContent Deleted. Reason: {reason}";
|
||||
|
||||
var audits = new DocumentAudit[]
|
||||
{
|
||||
new DocumentActionAudit
|
||||
(
|
||||
documentInformation.DocumentId,
|
||||
"System",
|
||||
deleteDate,
|
||||
"System",
|
||||
DocumentAction.DeleteContent,
|
||||
reasonFull
|
||||
)
|
||||
};
|
||||
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(x => x.SaveDocumentAudits(It.Is<DocumentAudit[]>(list =>
|
||||
list.Length == 1
|
||||
&& list.All(a => a is DocumentActionAudit)
|
||||
&& ((DocumentActionAudit)list.FirstOrDefault()!).Action == DocumentAction.DeleteContent
|
||||
&& ((DocumentActionAudit)list.FirstOrDefault()!).UserId == "System"
|
||||
&& ((DocumentActionAudit)list.FirstOrDefault()!).Source == "System"
|
||||
&& ((DocumentActionAudit)list.FirstOrDefault()!).ActionDescription == reasonFull)))
|
||||
.ReturnsAsync(audits)
|
||||
.Verifiable();
|
||||
|
||||
var result = await _documentAuditService.CreateDocumentContentDeletedAudit(documentInformation.DocumentId, deleteDate, reason);
|
||||
var resultArray = result.ToArray();
|
||||
|
||||
Assert.NotNull(result);
|
||||
audits.Should().HaveSameCount(resultArray);
|
||||
audits.Should().AllSatisfy(a => resultArray.Should().ContainEquivalentOf(a));
|
||||
|
||||
_mockDocumentAuditRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateDocumentContentDeletedAudit_DocumentAuditRepositoryThrowsException_ThrowsNewDocumentAuditServiceExceptionThrown()
|
||||
{
|
||||
var deleteDate = DateTime.Parse("2022-04-28T17:32:35.093Z");
|
||||
var documentInformation = GenerateDocumentInformation();
|
||||
var reason = Guid.NewGuid().ToString();
|
||||
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(x => x.SaveDocumentAudits(It.Is<DocumentAudit[]>(list => list.Any())))
|
||||
.ThrowsAsync(new DocumentAuditServiceException("Thrown From Unit Test"))
|
||||
.Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<DocumentAuditServiceException>(() => _documentAuditService.CreateDocumentContentDeletedAudit(documentInformation.DocumentId, deleteDate, reason));
|
||||
|
||||
_mockDocumentAuditRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateDocumentContentDeletedAudit_Exception_DocumentAuditServiceExceptionThrown()
|
||||
{
|
||||
var deleteDate = DateTime.Parse("2022-04-28T17:32:35.093Z");
|
||||
var documentInformation = GenerateDocumentInformation();
|
||||
var reason = Guid.NewGuid().ToString();
|
||||
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(x => x.SaveDocumentAudits(It.Is<DocumentAudit[]>(list => list.Any())))
|
||||
.ThrowsAsync(new Exception("Thrown From Unit Test"))
|
||||
.Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<DocumentAuditServiceException>(() => _documentAuditService.CreateDocumentContentDeletedAudit(documentInformation.DocumentId, deleteDate, reason));
|
||||
|
||||
_mockDocumentAuditRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CreateDocumentAudits_No_Differences_Success()
|
||||
{
|
||||
var updateDate = DateTime.Parse("2022-04-28T17:32:35.093Z");
|
||||
var docInformationOld = GenerateDocumentInformation();
|
||||
var docInformationNew = GenerateDocumentInformation();
|
||||
|
||||
var result = await _documentAuditService.CreateDocumentAudits(docInformationOld, docInformationNew, false, updateDate, "testUser");
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.IsEmpty(result);
|
||||
_mockDocumentAuditRepository.Verify(x => x.SaveDocumentAudits(It.IsAny<DocumentAudit[]>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CreateDocumentAudits_No_Differences_ContentChange_Success()
|
||||
{
|
||||
var updateDate = DateTime.Parse("2022-04-28T17:32:35.093Z");
|
||||
var userId = Guid.NewGuid().ToString();
|
||||
var docInformationOld = GenerateDocumentInformation();
|
||||
var docInformationNew = GenerateDocumentInformation();
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(x => x.SaveDocumentAudits(It.Is<DocumentAudit[]>(list =>
|
||||
list.Length == 1
|
||||
&& list.All(a => a is DocumentFieldAudit)
|
||||
&& ((DocumentFieldAudit)list.FirstOrDefault()!).Field == "Content"
|
||||
&& list.All(audit => string.Equals(userId, ((DocumentFieldAudit)audit).UserId)))))
|
||||
.ReturnsAsync(new List<DocumentAudit>())
|
||||
.Verifiable();
|
||||
|
||||
await _documentAuditService.CreateDocumentAudits(docInformationOld, docInformationNew, true, updateDate, userId);
|
||||
|
||||
_mockDocumentAuditRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CreateDocumentAudits_All_Differences_Success()
|
||||
{
|
||||
var updateDate = DateTime.Parse("2022-04-28T17:32:35.093Z");
|
||||
var docInformationOld = GenerateDocumentInformation();
|
||||
var docInformationEverythingChanges = GenerateDocumentInformation();
|
||||
var userId = Guid.NewGuid().ToString();
|
||||
docInformationEverythingChanges.Name = "BrandNewName";
|
||||
docInformationEverythingChanges.Type = "BrandNewType";
|
||||
docInformationEverythingChanges.ScanStatus = true;
|
||||
docInformationEverythingChanges.DataSource = "NEW SOURCE";
|
||||
docInformationEverythingChanges.Attributes = new Dictionary<string, string>
|
||||
{
|
||||
{ "NEW_FIELD", "NEW_VALUE" }
|
||||
};
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(x => x.SaveDocumentAudits(It.Is<DocumentAudit[]>(list =>
|
||||
list.Length == 4
|
||||
&& list.All(a => a is DocumentFieldAudit)
|
||||
&& list.Any(audit => string.Equals("Name", ((DocumentFieldAudit)audit).Field))
|
||||
&& list.Any(audit => string.Equals("Type", ((DocumentFieldAudit)audit).Field))
|
||||
&& list.Any(audit => string.Equals("ScanStatus", ((DocumentFieldAudit)audit).Field))
|
||||
&& list.Any(audit => string.Equals("Attributes", ((DocumentFieldAudit)audit).Field))
|
||||
&& list.All(audit => string.Equals(userId, ((DocumentFieldAudit)audit).UserId)))))
|
||||
.ReturnsAsync(new List<DocumentAudit>())
|
||||
.Verifiable();
|
||||
|
||||
await _documentAuditService.CreateDocumentAudits(docInformationOld, docInformationEverythingChanges, false, updateDate, userId);
|
||||
|
||||
_mockDocumentAuditRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateDocumentAudits_DocumentAuditRepositoryThrowsException_ThrowsNewDocumentAuditServiceExceptionThrown()
|
||||
{
|
||||
var updateDate = DateTime.Parse("2022-04-28T17:32:35.093Z");
|
||||
var docInformationOld = GenerateDocumentInformation();
|
||||
var docInformationNew = GenerateDocumentInformation();
|
||||
var userId = Guid.NewGuid().ToString();
|
||||
docInformationNew.Name = "NewName.txt";
|
||||
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(x => x.SaveDocumentAudits(It.Is<DocumentAudit[]>(list => list.Any())))
|
||||
.ThrowsAsync(new DocumentAuditServiceException("Thrown From Unit Test"))
|
||||
.Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<DocumentAuditServiceException>(() => _documentAuditService.CreateDocumentAudits(docInformationOld, docInformationNew, false, updateDate, userId));
|
||||
|
||||
_mockDocumentAuditRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateDocumentAudits_Exception_DocumentAuditServiceExceptionThrown()
|
||||
{
|
||||
|
||||
var updateDate = DateTime.Parse("2022-04-28T17:32:35.093Z");
|
||||
var docInformationOld = GenerateDocumentInformation();
|
||||
var docInformationNew = GenerateDocumentInformation();
|
||||
var userId = Guid.NewGuid().ToString();
|
||||
docInformationNew.Name = "NewName.txt";
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(x => x.SaveDocumentAudits(It.Is<DocumentAudit[]>(list => list.Any())))
|
||||
.ThrowsAsync(new Exception("Thrown From Unit Test"))
|
||||
.Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<DocumentAuditServiceException>(() => _documentAuditService.CreateDocumentAudits(docInformationOld, docInformationNew, false, updateDate, userId));
|
||||
|
||||
_mockDocumentAuditRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AuditAction_RepositoryThrowsException_ThrowsDocumentAuditServiceException()
|
||||
{
|
||||
var documentId = Guid.NewGuid();
|
||||
var userId = Guid.NewGuid().ToString();
|
||||
var source = Guid.NewGuid().ToString();
|
||||
var action = Valid<DocumentAction>();
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(r => r.SaveDocumentAudits(It.Is<DocumentActionAudit>(a =>
|
||||
a.DocumentId == documentId
|
||||
&& a.UserId == userId
|
||||
&& a.Source == source
|
||||
&& a.Action == action)))
|
||||
.ThrowsAsync(new Exception());
|
||||
|
||||
Assert.ThrowsAsync<DocumentAuditServiceException>(() => _documentAuditService.AuditAction(documentId, userId, source, action));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AuditAction_UnknownActionType_ThrowsDocumentAuditServiceException()
|
||||
{
|
||||
var documentId = Guid.NewGuid();
|
||||
var userId = Guid.NewGuid().ToString();
|
||||
var source = Guid.NewGuid().ToString();
|
||||
var action = Invalid<DocumentAction>();
|
||||
|
||||
Assert.ThrowsAsync<DocumentAuditServiceException>(() => _documentAuditService.AuditAction(documentId, userId, source, action));
|
||||
}
|
||||
|
||||
private static DocumentAction[] AllDocumentActions => AllValid<DocumentAction>();
|
||||
|
||||
[TestCaseSource(nameof(AllDocumentActions))]
|
||||
public async Task AuditAction_ValidAction_SavesActionAudit(DocumentAction action)
|
||||
{
|
||||
var documentId = Guid.NewGuid();
|
||||
var userId = Guid.NewGuid().ToString();
|
||||
var source = Guid.NewGuid().ToString();
|
||||
_mockDocumentAuditRepository
|
||||
.Setup(r => r.SaveDocumentAudits(It.Is<DocumentActionAudit>(a =>
|
||||
a.DocumentId == documentId
|
||||
&& a.UserId == userId
|
||||
&& a.Source == source
|
||||
&& a.Action == action)))
|
||||
.Verifiable();
|
||||
|
||||
await _documentAuditService.AuditAction(documentId, userId, source, action);
|
||||
|
||||
_mockDocumentAuditRepository.Verify();
|
||||
}
|
||||
|
||||
private static List<DocumentAudit> GenerateDocumentAudits()
|
||||
{
|
||||
return new List<DocumentAudit>
|
||||
{
|
||||
new DocumentFieldAudit(Guid.NewGuid(), "UnitTest", DateTime.Parse("2022-04-28T17:32:35.093Z"), "Test", "Test", "0", "1")
|
||||
};
|
||||
}
|
||||
|
||||
private static DocumentInformation GenerateDocumentInformation()
|
||||
{
|
||||
return new DocumentInformation
|
||||
{
|
||||
DocumentId = Guid.NewGuid(),
|
||||
Name = "TestFile.txt",
|
||||
Type = "Test",
|
||||
Source = "Developer's Laptop",
|
||||
ScanStatus = null,
|
||||
UploadDate = DateTime.Parse("2022-03-25T19:16:12.3665642Z"),
|
||||
LastModifiedDate = DateTime.Parse("0001-01-01T00:00:00"),
|
||||
Attributes = new Dictionary<string, string>
|
||||
{
|
||||
{ "IsTest", "true" },
|
||||
{ "Owner", "Me Myself" }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static T[] AllValid<T>()
|
||||
where T : struct, Enum
|
||||
{
|
||||
return Enum.GetValues<T>();
|
||||
}
|
||||
|
||||
private static T Valid<T>()
|
||||
where T : struct, Enum
|
||||
{
|
||||
var values = Enum.GetValues<T>();
|
||||
|
||||
return values[DateTime.Now.Millisecond % values.Length];
|
||||
}
|
||||
|
||||
private static T Invalid<T>()
|
||||
where T : struct, Enum
|
||||
{
|
||||
return (T)Convert.ChangeType(Enum.GetValues<T>().Length + 1, typeof(int));
|
||||
}
|
||||
|
||||
private class UnknownAuditType : DocumentAudit
|
||||
{
|
||||
public UnknownAuditType(Guid documentId, string source, DateTime date, string userId)
|
||||
: base(documentId, source, date, userId)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
+455
@@ -0,0 +1,455 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Internal.Document.Configurations;
|
||||
using LCU.API.Internal.Document.Exceptions;
|
||||
using LCU.API.Internal.Document.Services;
|
||||
using LCU.API.Internal.DocumentLib.Containers;
|
||||
using LCU.API.Internal.DocumentLib.Repositories;
|
||||
using LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace LCU.API.Internal.DocumentTest.Services;
|
||||
|
||||
public class DocumentInformationServiceTest
|
||||
{
|
||||
private DocumentInformationService _documentInformationService;
|
||||
private Mock<IDocumentInformationRepository> _mockDocumentRepository;
|
||||
private ApiConfiguration _apiConfiguration;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_mockDocumentRepository = new Mock<IDocumentInformationRepository>();
|
||||
_apiConfiguration = new ApiConfiguration() { FileUploadCutoffHours = -2 };
|
||||
_documentInformationService = new DocumentInformationService(_mockDocumentRepository.Object,
|
||||
_apiConfiguration);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDocumentInformation_Success()
|
||||
{
|
||||
var docId = Guid.NewGuid();
|
||||
|
||||
var docBlobId = Guid.NewGuid();
|
||||
var docInformation = GenerateDocumentInformation();
|
||||
docInformation.DocumentBlobId = docBlobId;
|
||||
docInformation.UploadDate = DateTime.UtcNow.AddDays(-1);
|
||||
docInformation.ScanStatus = true;
|
||||
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.GetDocumentInformation(docId)).ReturnsAsync(docInformation)
|
||||
.Verifiable();
|
||||
|
||||
var returnedDocInfo = await _documentInformationService.GetDocumentInformation(docId);
|
||||
|
||||
Assert.NotNull(returnedDocInfo);
|
||||
|
||||
Assert.AreEqual(docInformation.DocumentId, returnedDocInfo?.DocumentId);
|
||||
Assert.AreEqual(docInformation.Name, returnedDocInfo?.Name);
|
||||
Assert.AreEqual(docInformation.Type, returnedDocInfo?.Type);
|
||||
Assert.AreEqual(docInformation.Source, returnedDocInfo?.Source);
|
||||
Assert.AreEqual(docInformation.ScanStatus, returnedDocInfo?.ScanStatus);
|
||||
Assert.AreEqual(docInformation.UploadDate, returnedDocInfo?.UploadDate);
|
||||
Assert.AreEqual(docInformation.LastModifiedDate, returnedDocInfo?.LastModifiedDate);
|
||||
Assert.AreEqual(docInformation.PersonNumbers, returnedDocInfo?.PersonNumbers);
|
||||
Assert.AreEqual(docInformation.OrganizationNumbers, returnedDocInfo?.OrganizationNumbers);
|
||||
Assert.AreEqual(docInformation.AccountNumber, returnedDocInfo?.AccountNumber);
|
||||
Assert.AreEqual(docInformation.Attributes, returnedDocInfo?.Attributes);
|
||||
_mockDocumentRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDocument_NoInformationPresent_Null()
|
||||
{
|
||||
var docId = Guid.NewGuid();
|
||||
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.GetDocumentInformation(docId)).ReturnsAsync(null as DocumentInformation)
|
||||
.Verifiable();
|
||||
|
||||
var result = await _documentInformationService.GetDocumentInformation(docId);
|
||||
|
||||
Assert.Null(result);
|
||||
_mockDocumentRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDocument_ScanStatusIsFalse_DocumentIsNull()
|
||||
{
|
||||
var docId = Guid.NewGuid();
|
||||
|
||||
var docInformation = GenerateDocumentInformation();
|
||||
docInformation.ScanStatus = false;
|
||||
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.GetDocumentInformation(docId)).ReturnsAsync(docInformation)
|
||||
.Verifiable();
|
||||
|
||||
var result = await _documentInformationService.GetDocumentInformation(docId);
|
||||
|
||||
Assert.IsNull(result);
|
||||
_mockDocumentRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDocument_ExceptionThrown_ThrowDocumentInformationServiceException()
|
||||
{
|
||||
var docId = Guid.NewGuid();
|
||||
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.GetDocumentInformation(docId))
|
||||
.ThrowsAsync(new Exception("Thrown from unit test"))
|
||||
.Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<DocumentInformationServiceException>(async () => await _documentInformationService.GetDocumentInformation(docId));
|
||||
_mockDocumentRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDocumentInformationByBlobId_Success()
|
||||
{
|
||||
var docBlobId = Guid.NewGuid();
|
||||
|
||||
var docInformation = GenerateDocumentInformation();
|
||||
docInformation.DocumentBlobId = docBlobId;
|
||||
docInformation.ScanStatus = true;
|
||||
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.GetDocumentInformationByBlobId(docBlobId)).ReturnsAsync(docInformation)
|
||||
.Verifiable();
|
||||
|
||||
var returnedDocInfo = await _documentInformationService.GetDocumentInformationByBlobId(docBlobId);
|
||||
|
||||
Assert.NotNull(returnedDocInfo);
|
||||
|
||||
Assert.AreEqual(docInformation.DocumentId, returnedDocInfo?.DocumentId);
|
||||
Assert.AreEqual(docInformation.Name, returnedDocInfo?.Name);
|
||||
Assert.AreEqual(docInformation.Type, returnedDocInfo?.Type);
|
||||
Assert.AreEqual(docInformation.Source, returnedDocInfo?.Source);
|
||||
Assert.AreEqual(docInformation.ScanStatus, returnedDocInfo?.ScanStatus);
|
||||
Assert.AreEqual(docInformation.UploadDate, returnedDocInfo?.UploadDate);
|
||||
Assert.AreEqual(docInformation.LastModifiedDate, returnedDocInfo?.LastModifiedDate);
|
||||
Assert.AreEqual(docInformation.PersonNumbers, returnedDocInfo?.PersonNumbers);
|
||||
Assert.AreEqual(docInformation.OrganizationNumbers, returnedDocInfo?.OrganizationNumbers);
|
||||
Assert.AreEqual(docInformation.AccountNumber, returnedDocInfo?.AccountNumber);
|
||||
Assert.AreEqual(docInformation.Attributes, returnedDocInfo?.Attributes);
|
||||
_mockDocumentRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDocumentByBlobId_NoInformationPresent_Null()
|
||||
{
|
||||
var docBlobId = Guid.NewGuid();
|
||||
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.GetDocumentInformationByBlobId(docBlobId)).ReturnsAsync(null as DocumentInformation)
|
||||
.Verifiable();
|
||||
|
||||
var result = await _documentInformationService.GetDocumentInformationByBlobId(docBlobId);
|
||||
|
||||
Assert.Null(result);
|
||||
_mockDocumentRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDocumentByBlobId_ExceptionThrown_ThrowDocumentInformationServiceException()
|
||||
{
|
||||
var docBlobId = Guid.NewGuid();
|
||||
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.GetDocumentInformationByBlobId(docBlobId))
|
||||
.ThrowsAsync(new Exception("Thrown from unit test"))
|
||||
.Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<DocumentInformationServiceException>(async () => await _documentInformationService.GetDocumentInformationByBlobId(docBlobId));
|
||||
_mockDocumentRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DeleteDocument_Success()
|
||||
{
|
||||
var docId = Guid.NewGuid();
|
||||
|
||||
var docBlobId = Guid.NewGuid();
|
||||
var docInformation = GenerateDocumentInformation();
|
||||
docInformation.DocumentBlobId = docBlobId;
|
||||
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.GetDocumentInformation(docId)).ReturnsAsync(docInformation)
|
||||
.Verifiable();
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.RemoveDocumentInformation(docId)).ReturnsAsync(docInformation)
|
||||
.Verifiable();
|
||||
|
||||
await _documentInformationService.DeleteDocumentInformation(docId);
|
||||
_mockDocumentRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DeleteDocument_NoInformationPresent_Null()
|
||||
{
|
||||
var docId = Guid.NewGuid();
|
||||
|
||||
var docBlobId = Guid.NewGuid();
|
||||
var docInformation = GenerateDocumentInformation();
|
||||
docInformation.DocumentBlobId = docBlobId;
|
||||
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.GetDocumentInformation(docId)).ReturnsAsync(null as DocumentInformation)
|
||||
.Verifiable();
|
||||
|
||||
await _documentInformationService.DeleteDocumentInformation(docId);
|
||||
|
||||
_mockDocumentRepository.Verify(x => x.RemoveDocumentInformation(It.IsAny<Guid>()), Times.Never);
|
||||
_mockDocumentRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeleteDocument_RemoveDocumentInfoThrowsException_ThrowDocumentInformationServiceException()
|
||||
{
|
||||
var docId = Guid.NewGuid();
|
||||
|
||||
var docBlobId = Guid.NewGuid();
|
||||
var docInformation = GenerateDocumentInformation();
|
||||
docInformation.DocumentBlobId = docBlobId;
|
||||
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.GetDocumentInformation(docId)).ReturnsAsync(docInformation)
|
||||
.Verifiable();
|
||||
|
||||
_mockDocumentRepository.Setup(x => x.RemoveDocumentInformation(docId))
|
||||
.ThrowsAsync(new Exception("thrown from unit test")).Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<DocumentInformationServiceException>(async () => await _documentInformationService.DeleteDocumentInformation(docId));
|
||||
_mockDocumentRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UploadDocument_Success()
|
||||
{
|
||||
var docBlobId = Guid.NewGuid();
|
||||
var docInformation = GenerateDocumentInformation();
|
||||
docInformation.DocumentBlobId = docBlobId;
|
||||
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.SaveDocumentInformation(It.Is<DocumentInformation>(info =>
|
||||
info.DocumentBlobId == docBlobId)))
|
||||
.ReturnsAsync(docInformation)
|
||||
.Verifiable();
|
||||
|
||||
var result = await _documentInformationService.UploadDocumentInformation(docInformation);
|
||||
|
||||
Assert.AreEqual(docInformation, result);
|
||||
_mockDocumentRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UploadDocument_ExceptionThrown_ThrowDocumentInformation()
|
||||
{
|
||||
var docInformation = GenerateDocumentInformation();
|
||||
|
||||
_mockDocumentRepository.Setup(x => x.SaveDocumentInformation(It.IsAny<DocumentInformation>()))
|
||||
.ThrowsAsync(new Exception("Error thrown from unit test.")).Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<DocumentInformationServiceException>(async () => await _documentInformationService.UploadDocumentInformation(docInformation));
|
||||
_mockDocumentRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpdateDocument_Success()
|
||||
{
|
||||
var docId = Guid.NewGuid();
|
||||
|
||||
var docInformation = GenerateDocumentInformation();
|
||||
docInformation.DocumentId = docId;
|
||||
|
||||
_mockDocumentRepository.Setup(x => x.GetDocumentInformation(docId)).ReturnsAsync(docInformation).Verifiable();
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.UpdateDocumentInformation(docId, It.Is<DocumentInformation>(info =>
|
||||
info.DocumentId == docId))).ReturnsAsync(docInformation)
|
||||
.Verifiable();
|
||||
|
||||
await _documentInformationService.UpdateDocumentInformation(docId, docInformation);
|
||||
_mockDocumentRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UpdateDocument_NoInformation_Null()
|
||||
{
|
||||
var docId = Guid.NewGuid();
|
||||
var docInformation = GenerateDocumentInformation();
|
||||
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.GetDocumentInformation(docId)).ReturnsAsync(null as DocumentInformation)
|
||||
.Verifiable();
|
||||
|
||||
var result = await _documentInformationService.UpdateDocumentInformation(docId, docInformation);
|
||||
|
||||
Assert.Null(result);
|
||||
_mockDocumentRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UpdateDocument_ExceptionThrown_ThrowDocumentInformationServiceException()
|
||||
{
|
||||
var docBlobId = Guid.NewGuid();
|
||||
var docId = Guid.NewGuid();
|
||||
|
||||
var docInformation = GenerateDocumentInformation();
|
||||
docInformation.DocumentId = docId;
|
||||
docInformation.DocumentBlobId = docBlobId;
|
||||
|
||||
_mockDocumentRepository.Setup(x => x.GetDocumentInformation(docId)).ThrowsAsync(new Exception("Thrown from unit test")).Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<DocumentInformationServiceException>(async () => await _documentInformationService.UpdateDocumentInformation(docId, docInformation));
|
||||
|
||||
_mockDocumentRepository.Verify(
|
||||
x => x.UpdateDocumentInformation(It.IsAny<Guid>(), It.IsAny<DocumentInformation>()), Times.Never);
|
||||
_mockDocumentRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SearchDocumentInformation_Success()
|
||||
{
|
||||
var searchFilter = GenerateDocumentInformationFilter();
|
||||
var random = new Random(DateTime.Now.Millisecond);
|
||||
var skip = random.Next();
|
||||
var take = random.Next();
|
||||
var docInformation = new List<DocumentInformation> { GenerateDocumentInformation() };
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.SearchDocumentInformation(searchFilter, skip, take, null))
|
||||
.ReturnsAsync(docInformation);
|
||||
|
||||
var result = await _documentInformationService.SearchDocumentInformation(searchFilter, skip, take);
|
||||
|
||||
Assert.AreEqual(docInformation, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SearchDocumentInformation_NoneFound_EmptyList()
|
||||
{
|
||||
var searchFilter = GenerateDocumentInformationFilter();
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.SearchDocumentInformation(searchFilter, It.IsAny<int?>(), It.IsAny<int?>(), null))
|
||||
.ReturnsAsync((List<DocumentInformation>)null)
|
||||
.Verifiable();
|
||||
|
||||
var result = await _documentInformationService.SearchDocumentInformation(searchFilter);
|
||||
|
||||
_mockDocumentRepository.Verify();
|
||||
Assert.IsEmpty(result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SearchDocumentInformation_ThrowsException_DocumentInformationServiceException()
|
||||
{
|
||||
var searchFilter = GenerateDocumentInformationFilter();
|
||||
_mockDocumentRepository
|
||||
.Setup(x => x.SearchDocumentInformation(searchFilter, It.IsAny<int?>(), It.IsAny<int?>(), null))
|
||||
.ThrowsAsync(new Exception("Thrown from unit test"))
|
||||
.Verifiable();
|
||||
|
||||
Assert.ThrowsAsync<DocumentInformationServiceException>(async() => await _documentInformationService.SearchDocumentInformation(searchFilter));
|
||||
|
||||
_mockDocumentRepository.Verify();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CountDocuments_RepositoryCountThrows_ThrowsDocumentInformationServiceException()
|
||||
{
|
||||
var filter = new DocumentInformationFilter();
|
||||
_mockDocumentRepository
|
||||
.Setup(r => r.CountDocuments(filter))
|
||||
.ThrowsAsync(new Exception());
|
||||
|
||||
Assert.ThrowsAsync<DocumentInformationServiceException>(() => _documentInformationService.CountDocuments(filter));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CountDocuments_RepositoryReturnsCount_ReturnsCount()
|
||||
{
|
||||
var filter = new DocumentInformationFilter();
|
||||
var expectedCount = DateTime.Now.Millisecond;
|
||||
_mockDocumentRepository
|
||||
.Setup(r => r.CountDocuments(filter))
|
||||
.ReturnsAsync(expectedCount);
|
||||
|
||||
var count = await _documentInformationService.CountDocuments(filter);
|
||||
|
||||
Assert.AreEqual(expectedCount, count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDocumentsForCleanup_Success()
|
||||
{
|
||||
var documents = new List<Guid> { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
|
||||
_mockDocumentRepository.Setup(x => x.GetDocumentsForCleanup(It.IsAny<GetDocumentCleanupRequest>()))
|
||||
.ReturnsAsync(documents).Verifiable();
|
||||
|
||||
var result = await _documentInformationService.GetDocumentsForCleanup(It.IsAny<GetDocumentCleanupRequest>());
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.IsNotEmpty(result);
|
||||
Assert.AreSame(documents, result);
|
||||
_mockDocumentRepository.Verify(x => x.GetDocumentsForCleanup(It.IsAny<GetDocumentCleanupRequest>()), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetDocumentsForCleanup_Success_NoDocumentsFound()
|
||||
{
|
||||
var documents = new List<Guid>();
|
||||
_mockDocumentRepository.Setup(x => x.GetDocumentsForCleanup(It.IsAny<GetDocumentCleanupRequest>()))
|
||||
.ReturnsAsync(documents).Verifiable();
|
||||
|
||||
var result = await _documentInformationService.GetDocumentsForCleanup(It.IsAny<GetDocumentCleanupRequest>());
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.IsEmpty(result);
|
||||
Assert.AreSame(documents, result);
|
||||
_mockDocumentRepository.Verify(x => x.GetDocumentsForCleanup(It.IsAny<GetDocumentCleanupRequest>()), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDocumentsForCleanup_ThrowsDocumentInformationServiceException()
|
||||
{
|
||||
_mockDocumentRepository
|
||||
.Setup(r => r.GetDocumentsForCleanup(It.IsAny<GetDocumentCleanupRequest>()))
|
||||
.ThrowsAsync(new Exception());
|
||||
|
||||
Assert.ThrowsAsync<DocumentInformationServiceException>(() =>
|
||||
_documentInformationService.GetDocumentsForCleanup(It.IsAny<GetDocumentCleanupRequest>()));
|
||||
}
|
||||
|
||||
private static DocumentInformationFilter GenerateDocumentInformationFilter()
|
||||
{
|
||||
return new DocumentInformationFilter
|
||||
{
|
||||
Name = "TestSearch"
|
||||
};
|
||||
}
|
||||
|
||||
private static DocumentInformation GenerateDocumentInformation()
|
||||
{
|
||||
return new DocumentInformation
|
||||
{
|
||||
DocumentId = Guid.NewGuid(),
|
||||
Name = "TestFile.txt",
|
||||
Type = "Test",
|
||||
Source = "Developer's Laptop",
|
||||
DataSource = "DNA",
|
||||
ScanStatus = null,
|
||||
UploadDate = DateTime.Parse("2022-03-25T19:16:12.3665642Z"),
|
||||
LastModifiedDate = DateTime.Parse("0001-01-01T00:00:00"),
|
||||
PersonNumbers = new string[] { "1", "2" },
|
||||
OrganizationNumbers = new string[] { "3", "4" },
|
||||
AccountNumber = "5",
|
||||
Attributes = new Dictionary<string, string>
|
||||
{
|
||||
{ "IsTest", "true" },
|
||||
{ "Owner", "Me Myself" }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+1630
File diff suppressed because it is too large
Load Diff
+56
@@ -0,0 +1,56 @@
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using LCU.Library.Internal.Exceptions;
|
||||
using LCU.API.Internal.Document.Services;
|
||||
using LCU.API.Internal.DocumentLib.DataAccess;
|
||||
using LCU.API.Internal.NotificationsApiLib.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LCU.API.Internal.DocumentTest.Services;
|
||||
public class NotificationApiServiceTest
|
||||
{
|
||||
private NotificationApiService _notificationApiService;
|
||||
private Mock<INotificationApiDao> _mockNotificationApiDao;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_mockNotificationApiDao = new Mock<INotificationApiDao>();
|
||||
_notificationApiService = new NotificationApiService(_mockNotificationApiDao.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SendEmailAsync_Exception()
|
||||
{
|
||||
_mockNotificationApiDao.Setup(x => x.SendEmailAsync(It.IsAny<EmailRequest>()))
|
||||
.ThrowsAsync(new Exception("Error"));
|
||||
|
||||
var request = new EmailRequest()
|
||||
{
|
||||
Subject = "",
|
||||
Body = "",
|
||||
Recipient = new List<string>(new string[] {""})
|
||||
};
|
||||
|
||||
Assert.ThrowsAsync<ServiceException>(async () => await _notificationApiService.SendEmailAsync(request));
|
||||
_mockNotificationApiDao.Verify(x => x.SendEmailAsync(It.IsAny<EmailRequest>()), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SendEmailAsync_Return()
|
||||
{
|
||||
_mockNotificationApiDao.Setup(x => x.SendEmailAsync(It.IsAny<EmailRequest>()));
|
||||
|
||||
var request = new EmailRequest()
|
||||
{
|
||||
Subject = "",
|
||||
Body = "",
|
||||
Recipient = new List<string>(new string[] { "" })
|
||||
};
|
||||
|
||||
await _notificationApiService.SendEmailAsync(request);
|
||||
_mockNotificationApiDao.Verify(x => x.SendEmailAsync(It.IsAny<EmailRequest>()), Times.Once);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Converter;
|
||||
|
||||
public class BigIntegerConverter : JsonConverter<BigInteger>
|
||||
{
|
||||
public override BigInteger Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType != JsonTokenType.Number)
|
||||
throw new JsonException($"Found token {reader.TokenType} but expected token {JsonTokenType.Number}");
|
||||
using var doc = JsonDocument.ParseValue(ref reader);
|
||||
return BigInteger.Parse(doc.RootElement.GetRawText(), NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, BigInteger value, JsonSerializerOptions options)
|
||||
{
|
||||
var s = value.ToString(NumberFormatInfo.InvariantInfo);
|
||||
using var doc = JsonDocument.Parse(s);
|
||||
doc.WriteTo(writer);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Converter;
|
||||
|
||||
public class DynamicObjectJsonConverter : Newtonsoft.Json.JsonConverter
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, dynamic value, JsonSerializer serializer)
|
||||
{
|
||||
serializer.Serialize(writer, value);
|
||||
}
|
||||
|
||||
public override dynamic ReadJson(JsonReader reader, Type objectType, dynamic existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.StartObject) return reader.Value;
|
||||
|
||||
var node = JObject.Load(reader);
|
||||
|
||||
var definition = new ExpandoObject();
|
||||
var definitionPropertyBag = definition as IDictionary<string, object>;
|
||||
foreach (var property in node.Properties())
|
||||
{
|
||||
definitionPropertyBag[property.Name] = property.Value.ToObject<object>();
|
||||
}
|
||||
|
||||
return JsonConvert.DeserializeAnonymousType(node.ToString(), definition);
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type _) => true;
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Converter;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
|
||||
public class JsonKnownTypesAttribute : Attribute
|
||||
{
|
||||
public Type[] Subclasses { get; }
|
||||
|
||||
public JsonKnownTypesAttribute(params Type[] knownTypes)
|
||||
{
|
||||
Subclasses = knownTypes;
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Converter
|
||||
{
|
||||
public class FromJsonQueryAttribute : ModelBinderAttribute
|
||||
{
|
||||
public FromJsonQueryAttribute()
|
||||
{
|
||||
BinderType = typeof(JsonQueryBinder);
|
||||
}
|
||||
}
|
||||
|
||||
public class JsonQueryBinder : IModelBinder
|
||||
{
|
||||
public JsonQueryBinder()
|
||||
{
|
||||
}
|
||||
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
var value = bindingContext.ValueProvider.GetValue(bindingContext.FieldName).FirstValue;
|
||||
if (value == null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var parsed = JsonSerializer.Deserialize(
|
||||
value,
|
||||
bindingContext.ModelType,
|
||||
new JsonSerializerOptions(JsonSerializerDefaults.Web)
|
||||
);
|
||||
bindingContext.Result = ModelBindingResult.Success(parsed);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Failed();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Converter;
|
||||
|
||||
internal static class PolymorphicJsonConverter
|
||||
{
|
||||
public const string TypeProperty = "_t";
|
||||
|
||||
public static Exception NotSupported<T>() => new NotSupportedException($"{typeof(T).Name} must specify known deserialization types");
|
||||
|
||||
public static bool IsSerializationSupportedFor<T>(Type actualType) => typeof(T).GetCustomAttribute<JsonKnownTypesAttribute>() is { } knownTypes
|
||||
&& knownTypes.Subclasses.Contains(actualType);
|
||||
|
||||
public static Type DeserializationTypeFor<T>(string actualType) => typeof(T)
|
||||
.GetCustomAttribute<JsonKnownTypesAttribute>()
|
||||
.Subclasses
|
||||
.Single(t => t.Name == actualType);
|
||||
}
|
||||
|
||||
public class SystemPolymorphicJsonConverter<T> : System.Text.Json.Serialization.JsonConverter<T>
|
||||
{
|
||||
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var document = JsonDocument.ParseValue(ref reader);
|
||||
var type = document.RootElement.GetProperty(PolymorphicJsonConverter.TypeProperty).GetString();
|
||||
var deserializationType = PolymorphicJsonConverter.DeserializationTypeFor<T>(type);
|
||||
|
||||
return (T)document.Deserialize(deserializationType, options);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
|
||||
{
|
||||
var actualType = value.GetType();
|
||||
if (!PolymorphicJsonConverter.IsSerializationSupportedFor<T>(actualType))
|
||||
{
|
||||
throw PolymorphicJsonConverter.NotSupported<T>();
|
||||
}
|
||||
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString(PolymorphicJsonConverter.TypeProperty, actualType.Name);
|
||||
|
||||
var actualValue = JsonSerializer.Serialize(value, actualType, options);
|
||||
writer.WriteRawValue(actualValue.Substring(1, actualValue.Length - 2), true);
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
public class NewtonsoftPolymorphicJsonConverter<T> : Newtonsoft.Json.JsonConverter<T>
|
||||
{
|
||||
public override bool CanRead => _canRead;
|
||||
private bool _canRead = true;
|
||||
|
||||
public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
|
||||
{
|
||||
var document = JObject.Load(reader);
|
||||
var type = document.GetValue(PolymorphicJsonConverter.TypeProperty)!.Value<string>();
|
||||
var deserializationType = PolymorphicJsonConverter.DeserializationTypeFor<T>(type);
|
||||
|
||||
T result;
|
||||
lock (this)
|
||||
{
|
||||
_canRead = false;
|
||||
result = (T)JsonConvert.DeserializeObject(document.ToString(), deserializationType);
|
||||
_canRead = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override bool CanWrite => _canWrite;
|
||||
private bool _canWrite = true;
|
||||
|
||||
public override void WriteJson(JsonWriter writer, T value, Newtonsoft.Json.JsonSerializer serializer)
|
||||
{
|
||||
var actualType = value.GetType();
|
||||
if (!PolymorphicJsonConverter.IsSerializationSupportedFor<T>(actualType))
|
||||
{
|
||||
throw PolymorphicJsonConverter.NotSupported<T>();
|
||||
}
|
||||
|
||||
writer.WriteStartObject();
|
||||
writer.WritePropertyName(PolymorphicJsonConverter.TypeProperty);
|
||||
writer.WriteValue(actualType.Name);
|
||||
|
||||
JObject jValue;
|
||||
lock (this)
|
||||
{
|
||||
_canWrite = false;
|
||||
jValue = JObject.FromObject(value);
|
||||
_canWrite = true;
|
||||
}
|
||||
|
||||
foreach (var property in jValue.Properties())
|
||||
{
|
||||
property.WriteTo(writer);
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Version>1.8.4</Version>
|
||||
<PackageVersion>1.8.5</PackageVersion>
|
||||
<Description>
|
||||
1.0.0 - Initial Setup
|
||||
1.0.1 - Delete Date is now an optional field in requests and responses, and source on update requests has been renamed to updateSource
|
||||
1.0.2 - TraceId has been added as an optional field to documentInformation
|
||||
1.0.3 - DocumentSearchRequest object has been added
|
||||
1.0.4 - EncodeDocumentInformationFilterUri added to DocumentInformationFilter object
|
||||
1.0.5 - Updating DocumentInformationResponse to have a JsonConverter for BigInt
|
||||
1.0.6 - Separating JsonConverter for BigInt to own class
|
||||
1.0.7 - Adding in DataSource field in request and response models and search filter
|
||||
1.0.8 - Adding in CreatedDate field in request and response models and search filter
|
||||
1.0.9 - Updating Filter Query Creation method to not shorten dates
|
||||
1.1.0 - Add AccountNumbers, PersonNumbers and OrganizationNumbers to models and search filter
|
||||
1.1.1 - Add document action notification route for actions on documents not fulfilled by Document API directly
|
||||
1.1.2 - contract type definition for document audit logs
|
||||
1.1.3 - document filter query string serialization fix
|
||||
1.2.0 - Add Sorter to easily create sortable fields
|
||||
1.2.1 - Change PersonNumberEquals and OrganizationNumberEquals to singular string
|
||||
1.3.0 - Add DocumentInformationFilteredSearch
|
||||
1.3.1 - fixed multiple serialization for DocumentFieldAudit Current and Previous Value properties
|
||||
1.3.2 - fixed serialization of Current and Previous Value properties that aren't objects (e.g. null or boolean)
|
||||
1.4.0 - document search filters can no longer be used in query strings, method EncodeDocumentInformationFilterUri removed
|
||||
1.5.0 - Added document action for upload
|
||||
1.5.1 - Removed ArchiveDate and DeleteDate from DocumentInformation
|
||||
1.6.0 - Add GetDocumentCleanupRequest
|
||||
1.7.0 - Add DocumentAction for Delete and DeleteContent, Add DeleteDocumentContentRequest
|
||||
1.8.0 - Make DocumentInformation.DocumentBlobId a nullable Guid for deletion
|
||||
1.8.1 - Add TypeContainsAny
|
||||
1.8.2 - Add BusinessName to DocumentInformationFilter
|
||||
1.8.3 - Reverted MongoDB.Driver version
|
||||
1.8.4 - MongoDB.Driver version
|
||||
1.8.5 - Nuget update DocumentIngestor Model
|
||||
</Description>
|
||||
<PackageReleaseNotes>
|
||||
1.0.0 - Initial Setup
|
||||
1.0.1 - Delete Date is now an optional field in requests and responses, and source on update requests has been renamed to updateSource
|
||||
1.0.2 - TraceId has been added as an optional field to documentInformation
|
||||
1.0.3 - DocumentSearchRequest object has been added
|
||||
1.0.4 - EncodeDocumentInformationFilterUri added to DocumentInformationFilter object
|
||||
1.0.5 - Updating DocumentInformationResponse to have a JsonConverter for BigInt
|
||||
1.0.6 - Separating JsonConverter for BigInt to own class
|
||||
1.0.7 - Adding in DataSource field in request and response models and search filter
|
||||
1.0.8 - Adding in CreatedDate field in request and response models and search filter
|
||||
1.0.9 - Updating Filter Query Creation method to not shorten dates
|
||||
1.1.0 - Add AccountNumbers, PersonNumbers and OrganizationNumbers to models and search filter
|
||||
1.1.1 - Add document action notification route for actions on documents not fulfilled by Document API directly
|
||||
1.1.2 - contract type definition for document audit logs
|
||||
1.1.3 - document filter query string serialization fix
|
||||
1.2.0 - Add Sorter to easily create sortable fields
|
||||
1.2.1 - Change PersonNumberEquals and OrganizationNumberEquals to singular string
|
||||
1.3.0 - Add DocumentInformationFilteredSearch
|
||||
1.3.1 - fixed multiple serialization for DocumentFieldAudit Current and Previous Value properties
|
||||
1.3.2 - fixed serialization of Current and Previous Value properties that aren't objects (e.g. null or boolean)
|
||||
1.4.0 - document search filters can no longer be used in query strings, method EncodeDocumentInformationFilterUri removed
|
||||
1.5.0 - Added document action for upload
|
||||
1.5.1 - Removed ArchiveDate and DeleteDate from DocumentInformation
|
||||
1.6.0 - Add GetDocumentCleanupRequest
|
||||
1.7.0 - Add DocumentAction for Delete and DeleteContent, Add DeleteDocumentContentRequest
|
||||
1.8.0 - Make DocumentInformation.DocumentBlobId a nullable Guid for deletion
|
||||
1.8.1 - Add TypeContainsAny
|
||||
1.8.2 - Add BusinessName to DocumentInformationFilter
|
||||
1.8.3 - Reverted MongoDB.Driver version
|
||||
1.8.4 - MongoDB.Driver version
|
||||
1.8.5 - Nuget update DocumentIngestor Model
|
||||
</PackageReleaseNotes>
|
||||
<RootNamespace>LCU.Library.Internal.DocumentModels</RootNamespace>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LCU.Library.Internal.DocumentIngestorModels" Version="1.19.19" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
||||
<PackageReference Include="Microsoft.OpenApi" Version="1.4.3" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="2.20.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="6.0.10" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Requests;
|
||||
|
||||
public class DeleteDocumentContentRequest
|
||||
{
|
||||
[Required] public Guid DocumentId { get; set; }
|
||||
public string? Reason { get; set; }
|
||||
public string UserName { get; set; } = "System";
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
namespace LCU.Library.Internal.DocumentModels.Requests;
|
||||
|
||||
public enum DocumentAction
|
||||
{
|
||||
Download,
|
||||
Upload,
|
||||
Delete,
|
||||
DeleteContent
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Requests;
|
||||
|
||||
public class DocumentActionNotificationRequest
|
||||
{
|
||||
[Required] public DocumentAction Action { get; set; }
|
||||
[Required] public string Source { get; set; }
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Requests;
|
||||
|
||||
public class DocumentRequest
|
||||
{
|
||||
[Required] public string Name { get; set; }
|
||||
|
||||
[Required] public string Type { get; set; }
|
||||
|
||||
[Required] public byte[] Content { get; set; }
|
||||
|
||||
[Required] public string Source { get; set; }
|
||||
|
||||
[Required] public string DataSource { get; set; }
|
||||
|
||||
[Required] public DateTime CreatedDate { get; set; }
|
||||
|
||||
public Guid? TraceId { get; set; }
|
||||
|
||||
public string[] PersonNumbers { get; set; }
|
||||
|
||||
public string[] OrganizationNumbers { get; set; }
|
||||
|
||||
public string AccountNumber { get; set; }
|
||||
|
||||
public Dictionary<string, string> Attributes { get; set; }
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Web;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
|
||||
public class DocumentInformationFilter
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public string? Type { get; set; }
|
||||
public string? BusinessName { get; set; }
|
||||
public string[]? TypeContainsAny { get; set; }
|
||||
public bool? ScanStatus { get; set; }
|
||||
public string? Source { get; set; }
|
||||
public string? DataSource { get; set; }
|
||||
public Guid? TraceId { get; set; }
|
||||
public string? PersonNumbersEquals { get; set; }
|
||||
public string[]? PersonNumbersContains { get; set; }
|
||||
public string[]? PersonNumbersContainsAny { get; set; }
|
||||
public string? OrganizationNumbersEquals { get; set; }
|
||||
public string[]? OrganizationNumbersContains { get; set; }
|
||||
public string[]? OrganizationNumbersContainsAny { get; set; }
|
||||
public string? AccountNumberEquals { get; set; }
|
||||
public string[]? AccountNumberContainsAny { get; set; }
|
||||
public Dictionary<string, string>? AttributesEquals { get; set; }
|
||||
public Dictionary<string, string>? AttributesContains { get; set; }
|
||||
public Dictionary<string, string>? AttributesContainsAny { get; set; }
|
||||
public DateTime? CreatedDateMinimum { get; set; }
|
||||
public DateTime? CreatedDateMaximum { get; set; }
|
||||
public DateTime? UploadDateMinimum { get; set; }
|
||||
public DateTime? UploadDateMaximum { get; set; }
|
||||
public DateTime? LastModifiedMinimum { get; set; }
|
||||
public DateTime? LastModifiedDateMaximum { get; set; }
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Models.Pagination;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
|
||||
public class DocumentInformationFilteredSearch
|
||||
{
|
||||
public DocumentInformationFilter Filter { get; set; }
|
||||
public int? Skip { get; set; }
|
||||
public int? Take { get; set; }
|
||||
public List<SortableField>? SortDefinition { get; set; }
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using LCU.Library.DocumentType.Models.Responses;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
|
||||
public class GetDocumentCleanupRequest
|
||||
{
|
||||
public List<DocumentCodeDetailsResponse> DocumentCodeDetailInfos { get; set; }
|
||||
public int Limit { get; set; }
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using MongoDB.Driver;
|
||||
using LCU.Library.Internal.DocumentIngestorModels.Models.Pagination;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Requests.Search;
|
||||
|
||||
public class Sorter<T>
|
||||
{
|
||||
public static SortDefinition<T> GetSorter(List<SortableField> fields)
|
||||
{
|
||||
SortDefinition<T>? sortDefinition = null;
|
||||
|
||||
if(fields == null)
|
||||
{
|
||||
return sortDefinition;
|
||||
}
|
||||
|
||||
fields.Sort((a, b) => a.Position.CompareTo(b.Position));
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (sortDefinition == null)
|
||||
{
|
||||
sortDefinition = field.IsDescending ? Builders<T>.Sort.Descending(field.Field) : Builders<T>.Sort.Ascending(field.Field);
|
||||
}
|
||||
else
|
||||
{
|
||||
sortDefinition = field.IsDescending ? sortDefinition.Descending(field.Field) : sortDefinition.Ascending(field.Field);
|
||||
}
|
||||
}
|
||||
|
||||
return sortDefinition;
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Requests;
|
||||
|
||||
public class UpdateDocumentContentRequest
|
||||
{
|
||||
[Required] public byte[] Content { get; set; }
|
||||
|
||||
[Required] public string UpdateSource { get; set; }
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Requests;
|
||||
|
||||
public class UpdateDocumentInformationRequest
|
||||
{
|
||||
[Required] public string Name { get; set; }
|
||||
|
||||
[Required] public string Type { get; set; }
|
||||
|
||||
[Required] public string UpdateSource { get; set; }
|
||||
|
||||
public string[] PersonNumbers { get; set; }
|
||||
|
||||
public string[] OrganizationNumbers { get; set; }
|
||||
|
||||
public string AccountNumber { get; set; }
|
||||
|
||||
public Dictionary<string, string> Attributes { get; set; }
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Requests;
|
||||
|
||||
public class UpdateDocumentRequest
|
||||
{
|
||||
[Required] public string Name { get; set; }
|
||||
|
||||
[Required] public string Type { get; set; }
|
||||
|
||||
[Required] public byte[] Content { get; set; }
|
||||
|
||||
[Required] public string UpdateSource { get; set; }
|
||||
|
||||
public string[] PersonNumbers { get; set; }
|
||||
|
||||
public string[] OrganizationNumbers { get; set; }
|
||||
|
||||
public string AccountNumber { get; set; }
|
||||
|
||||
public Dictionary<string, string> Attributes { get; set; }
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Requests;
|
||||
|
||||
public class UpdateDocumentScanStatusRequest
|
||||
{
|
||||
[Required] public List<Guid> DocumentBlobIds { get; set; }
|
||||
[Required] public bool ScanStatus { get; set; }
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
using LCU.Library.Internal.DocumentModels.Requests;
|
||||
using System;
|
||||
using LCU.Library.Internal.DocumentModels.Converter;
|
||||
|
||||
namespace LCU.Library.Internal.DocumentModels.Responses;
|
||||
|
||||
[JsonKnownTypes(typeof(DocumentFieldAuditResponse), typeof(DocumentActionAuditResponse))]
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(SystemPolymorphicJsonConverter<DocumentAuditResponse>))]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(NewtonsoftPolymorphicJsonConverter<DocumentAuditResponse>))]
|
||||
public abstract class DocumentAuditResponse
|
||||
{
|
||||
public Guid DocumentId { get; set; }
|
||||
public string Source { get; set; }
|
||||
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
public string UserId { get; set; }
|
||||
}
|
||||
|
||||
public class DocumentFieldAuditResponse : DocumentAuditResponse
|
||||
{
|
||||
public string Field { get; set; }
|
||||
[Newtonsoft.Json.JsonConverter(typeof(DynamicObjectJsonConverter))]
|
||||
public dynamic PreviousValue { get; set; }
|
||||
[Newtonsoft.Json.JsonConverter(typeof(DynamicObjectJsonConverter))]
|
||||
public dynamic CurrentValue { get; set; }
|
||||
}
|
||||
|
||||
public class DocumentActionAuditResponse : DocumentAuditResponse
|
||||
{
|
||||
public DocumentAction Action { get; set; }
|
||||
public string ActionDescription { get; set; }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user