diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..57c2dc0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,26 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +**/helmvalues \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1f4c05e..8c140ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,45 +1,7 @@ -# ---> C++ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -# ---> VisualStudio ## 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 @@ -48,9 +10,6 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs -# Mono auto generated files -mono_crash.* - # Build results [Dd]ebug/ [Dd]ebugPublic/ @@ -58,62 +17,41 @@ mono_crash.* [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 +# Visual Studio 2015 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 +# 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 +# DNX 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 +*_i.h *.ilk *.meta *.obj -*.iobj *.pch *.pdb -*.ipdb *.pgc *.pgd *.rsp @@ -123,15 +61,15 @@ StyleCopReport.xml *.tlh *.tmp *.tmp_proj -*_wpftmp.csproj *.log -*.tlog *.vspscc *.vssscc .builds *.pidb *.svclog *.scc +*.log +*.log* # Chutzpah Test files _Chutzpah* @@ -153,9 +91,6 @@ ipch/ *.vspx *.sap -# Visual Studio Trace Files -*.e2e - # TFS 2012 Local Workspace $tf/ @@ -167,25 +102,15 @@ _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user +# JustCode is a .NET coding add-in +.JustCode + # 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 @@ -217,7 +142,7 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, +# TODO: 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 @@ -229,21 +154,16 @@ PublishScripts/ # NuGet Packages *.nupkg -# NuGet Symbol Packages -*.snupkg # The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* +**/packages/* # except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ +!**/packages/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 +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets -# Nuget personal access tokens and Credentials -nuget.config - # Microsoft Azure Build Output csx/ *.build.csdef @@ -257,15 +177,12 @@ 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/ +!*.[Cc]ache/ # Others ClientBin/ @@ -273,15 +190,11 @@ ClientBin/ *~ *.dbmdl *.dbproj.schemaview -*.jfm *.pfx *.publishsettings +node_modules/ 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/ @@ -296,22 +209,15 @@ _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/ @@ -321,7 +227,6 @@ FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat -node_modules/ # Visual Studio 6 build log *.plg @@ -329,9 +234,6 @@ node_modules/ # 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 @@ -347,120 +249,15 @@ 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 - -# VS Code files for those working on multiple tools -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -# Windows Installer files from build outputs -*.cab -*.msi -*.msix -*.msm -*.msp - # JetBrains Rider .idea/ *.sln.iml +/LiPoUploadService/Resources/Uploads +/package-lock.json +LiPoValidationService/mono_crash.21acb2abea.0.json -# ---> VisualStudioCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -*.code-workspace - -# Local History for Visual Studio Code -.history/ - -# ---> MonoDevelop -#User Specific -*.userprefs -*.usertasks - -#Mono Project Files -*.pidb -*.resources -test-results/ - -# ---> MicrosoftOffice -*.tmp - -# Word temporary -~$*.doc* - -# Word Auto Backup File -Backup of *.doc* - -# Excel temporary -~$*.xls* - -# Excel Backup File -*.xlk - -# PowerPoint temporary -~$*.ppt* - -# Visio autosave temporary files -*.~vsd* - +UploadRepository/mono_crash.21acb2abea.0.json +UploadRepository/mono_crash.21acb2abea.1.json +LiPoUploadStagingService/appsettings.Development.json +LiPoTaskService/appsettings.Development.json +LiPoTaskService/mono_crash.21acb2ae56.0.json diff --git a/BusinessLogic/LookupLogic/FwLookupLogic.cs b/BusinessLogic/LookupLogic/FwLookupLogic.cs new file mode 100644 index 0000000..3aa9d42 --- /dev/null +++ b/BusinessLogic/LookupLogic/FwLookupLogic.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using BusinessLogic.LookupLogic.Interfaces; +using Datamodels.Lookups; +using Datamodels.BusinessModels; +using Repositories.Interfaces; + +namespace BusinessLogic.LookupLogic +{ + public class FwLookupLogic: ILookupLogic + { + + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + private readonly IFwMariaLookupRepo _lookupRepo; + + public FwLookupLogic(ILogger logger, IConfiguration configuration, IFwMariaLookupRepo lookupRepo) + { + _logger = logger; + _configuration = configuration; + _lookupRepo = lookupRepo; + } + + public async Task>> GetAllLookups() + { + var result = await _lookupRepo.GetAllLookups(); + return result; + } + + public async Task> GetLookupCategoryById(long id) + { + var result = await _lookupRepo.GetLookupCategoryById(id); + return result; + } + + public async Task>> GetLookupCategoryByName(string categoryName) + { + var result = await _lookupRepo.GetLookupCategoryByName(categoryName); + return result; + } + + public async Task>> GetActiveLookupCategories() + { + var result = await _lookupRepo.GetActiveLookupCategories(); + return result; + } + } +} diff --git a/BusinessLogic/LookupLogic/Interfaces/ILookupLogic.cs b/BusinessLogic/LookupLogic/Interfaces/ILookupLogic.cs new file mode 100644 index 0000000..ecf1b3a --- /dev/null +++ b/BusinessLogic/LookupLogic/Interfaces/ILookupLogic.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Datamodels.Lookups; +using Datamodels.BusinessModels; + +namespace BusinessLogic.LookupLogic.Interfaces +{ + public interface ILookupLogic + { + Task>> GetAllLookups(); + Task> GetLookupCategoryById(long id); + Task>> GetLookupCategoryByName(string categoryName); + Task>> GetActiveLookupCategories(); + } +} \ No newline at end of file diff --git a/BusinessLogic/LookupLogic/LookupLogic.csproj b/BusinessLogic/LookupLogic/LookupLogic.csproj new file mode 100644 index 0000000..77196f7 --- /dev/null +++ b/BusinessLogic/LookupLogic/LookupLogic.csproj @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + netstandard2.1 + + + diff --git a/BusinessLogic/SearchLogic/ISearchLogic.cs b/BusinessLogic/SearchLogic/ISearchLogic.cs new file mode 100644 index 0000000..0c08979 --- /dev/null +++ b/BusinessLogic/SearchLogic/ISearchLogic.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Datamodels.BusinessModels; +using Datamodels.DatabaseModels; +using Datamodels.SearchModels; + +namespace SearchLogic +{ + public interface ISearchLogic + { + Task>> GetAllAddresses(); + Task>> GetFilteredData(FilterModel filter, long skip = 0, long take = -1); + } +} diff --git a/BusinessLogic/SearchLogic/SearchLogic.cs b/BusinessLogic/SearchLogic/SearchLogic.cs new file mode 100644 index 0000000..4c82091 --- /dev/null +++ b/BusinessLogic/SearchLogic/SearchLogic.cs @@ -0,0 +1,68 @@ +using Datamodels.DatabaseModels; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Repositories.Interfaces; +using Datamodels.BusinessModels; +using Datamodels.SearchModels; +using System.Linq; +using System.Linq.Expressions; + +namespace SearchLogic +{ + public class SearchLogic : ISearchLogic + { + private readonly ILogger _logger; + private readonly IFwMariaSearchRepo _searchRepo; + + public SearchLogic(ILogger logger, IFwMariaSearchRepo searchRepo) + { + _logger = logger; + _searchRepo = searchRepo; + } + + public async Task>> GetAllAddresses() + { + var result = await _searchRepo.GetAllAddresses(); + return result; + } + + public async Task>> GetFilteredData(FilterModel filter, long skip = 0, long take = -1) + { + List expList = new List(); + + foreach (SearchDescription filterModel in filter.SearchDescriptions) + { + var exp = GetFilterCondition
(filterModel); + if (exp != null) + { + expList.Add(exp); + } + + } + var result = await _searchRepo.GetFilteredData
(expList, 0, -1); + return result; + } + + private Expression GetFilterCondition(SearchDescription searchDescription) where T : IEntityClass + { + Expression> predicate; + ParameterExpression pe = Expression.Parameter(typeof(T), typeof(T).Name); + if (typeof(T).GetProperty(searchDescription.SearchData.FieldName) != null) + { + //var exp = Expression.Parameter(typeof(T), "x"); + var exp = Expression.Parameter(typeof(T), typeof(T).Name); + Expression left = Expression.Property(pe, searchDescription.SearchData.FieldName); + Expression right = Expression.Constant(searchDescription.SearchData.Values[0]); + Expression e1 = Expression.Equal(left, right); + + //predicate = Expression.Lambda>(e1, new ParameterExpression[] { pe }); + + return e1; + } + + return null; + } + } +} diff --git a/BusinessLogic/SearchLogic/SearchLogic.csproj b/BusinessLogic/SearchLogic/SearchLogic.csproj new file mode 100644 index 0000000..ac2fa79 --- /dev/null +++ b/BusinessLogic/SearchLogic/SearchLogic.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.1 + + + + + + + + diff --git a/Datamodels/BusinessModels/ResultModel.cs b/Datamodels/BusinessModels/ResultModel.cs new file mode 100644 index 0000000..71e61a2 --- /dev/null +++ b/Datamodels/BusinessModels/ResultModel.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Datamodels.BusinessModels +{ + public enum GeneralResults + { + Success = 0, + DatabaseError = 1, + GeneralError = 2, + LogicWarning = 3, + Created = 4, + Updated = 5, + Deleted = 6 + } + + public class ResultObject + { + public GeneralResults GeneralResult { get; set; } = GeneralResults.Success; + public string UserMessage { get; set; } + public string ServerMessage { get; set; } + public string StackTrace { get; set; } + } + + public class DataResult : ResultObject + { + public T Data { get; set; } + public long TotalCount { get; set; } + public long Skip { get; set; } + public long Take { get; set; } + } +} \ No newline at end of file diff --git a/Datamodels/BusinessModels/SortModel.cs b/Datamodels/BusinessModels/SortModel.cs new file mode 100644 index 0000000..158e72b --- /dev/null +++ b/Datamodels/BusinessModels/SortModel.cs @@ -0,0 +1,17 @@ +namespace Datamodels.BusinessModels +{ + + public enum SortDirections + { + Ascending, + Descending + } + + public sealed class SortModel + { + public int SortFieldOrder { get; set; } + public string SortFieldName { get; set; } + public SortDirections SortDirection { get; set; } = SortDirections.Ascending; + + } +} \ No newline at end of file diff --git a/Datamodels/DatabaseModels/Address.cs b/Datamodels/DatabaseModels/Address.cs new file mode 100644 index 0000000..2922d48 --- /dev/null +++ b/Datamodels/DatabaseModels/Address.cs @@ -0,0 +1,18 @@ +using System; +using Datamodels.Lookups; + +namespace Datamodels.DatabaseModels +{ + public class Address: IEntityClass + { + public long Id { get; set; } + public string StreetName { get; set; } + public int StreetNumber { get; set; } + public string Zip { get; set; } + public string City { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime ChangedAt { get; set; } + public LookupValue AddressType { get; set; } //something like "Job" or "Private" + public Person Person { get; set; } + } +} \ No newline at end of file diff --git a/Datamodels/DatabaseModels/Communication.cs b/Datamodels/DatabaseModels/Communication.cs new file mode 100644 index 0000000..675c424 --- /dev/null +++ b/Datamodels/DatabaseModels/Communication.cs @@ -0,0 +1,15 @@ +using System; +using Datamodels.Lookups; + +namespace Datamodels.DatabaseModels +{ + public class Communication + { + public long Id { get; set; } + public string CommunicationValue { get; set; } //mobile phone, home phone, job mail, etc. + public LookupValue CommunicationType { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime ChangedAt { get; set; } + public Person Person { get; set; } + } +} \ No newline at end of file diff --git a/Datamodels/DatabaseModels/IEntityClass.cs b/Datamodels/DatabaseModels/IEntityClass.cs new file mode 100644 index 0000000..f4a73d6 --- /dev/null +++ b/Datamodels/DatabaseModels/IEntityClass.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Datamodels.DatabaseModels +{ + public interface IEntityClass + { + } +} diff --git a/Datamodels/DatabaseModels/Person.cs b/Datamodels/DatabaseModels/Person.cs new file mode 100644 index 0000000..7c5185e --- /dev/null +++ b/Datamodels/DatabaseModels/Person.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using Datamodels.Lookups; + +namespace Datamodels.DatabaseModels +{ + public class Person + { + public long Id { get; set; } + public string LastName { get; set; } + public string FirstName { get; set; } + public DateTime? Birthday { get; set; } + public bool IsActive { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime ChangedAt { get; set; } + public LookupValue Gender { get; set; } + public List
Addresses { get; set; } + public List Communications { get; set; } + } +} \ No newline at end of file diff --git a/Datamodels/Datamodels.csproj b/Datamodels/Datamodels.csproj new file mode 100644 index 0000000..1aac6d4 --- /dev/null +++ b/Datamodels/Datamodels.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.1 + + + + + + + diff --git a/Datamodels/Lookups/LookupCategory.cs b/Datamodels/Lookups/LookupCategory.cs new file mode 100644 index 0000000..382d3ed --- /dev/null +++ b/Datamodels/Lookups/LookupCategory.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace Datamodels.Lookups +{ + public sealed class LookupCategory + { + public long Id { get; set; } + public string CategoryName { get; set; } + public bool IsActive { get; set; } = true; + public List LookupValues { get; set; } + } +} \ No newline at end of file diff --git a/Datamodels/Lookups/LookupValue.cs b/Datamodels/Lookups/LookupValue.cs new file mode 100644 index 0000000..cadd910 --- /dev/null +++ b/Datamodels/Lookups/LookupValue.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Datamodels.Lookups +{ + public sealed class LookupValue { + + public long Id { get; set; } + public string Value { get; set; } + public bool IsActive { get; set; } = true; + [JsonIgnore] + public LookupCategory LookupCategory { get; set; } + + } +} \ No newline at end of file diff --git a/Datamodels/SearchModels/FieldModel.cs b/Datamodels/SearchModels/FieldModel.cs new file mode 100644 index 0000000..62a727d --- /dev/null +++ b/Datamodels/SearchModels/FieldModel.cs @@ -0,0 +1,21 @@ +using System; + +namespace Datamodels.SearchModels +{ + + public enum FieldTypes + { + String, + Integer, + Double, + Boolean, + DateTime, + Currency, + Blob + } + public sealed class FieldModel + { + public string FieldName { get; set; } + public FieldTypes FieldType { get; set; } + } +} \ No newline at end of file diff --git a/Datamodels/SearchModels/FilterModel.cs b/Datamodels/SearchModels/FilterModel.cs new file mode 100644 index 0000000..16bf6bc --- /dev/null +++ b/Datamodels/SearchModels/FilterModel.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Datamodels.SearchModels +{ + public class FilterModel + { + public TableModel TableName { get; set; } + public List SearchDescriptions { get; set; } + public long Skip { get; set; } + public long Take { get; set; } + } +} diff --git a/Datamodels/SearchModels/SearchDescription.cs b/Datamodels/SearchModels/SearchDescription.cs new file mode 100644 index 0000000..a3db4cd --- /dev/null +++ b/Datamodels/SearchModels/SearchDescription.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Datamodels.SearchModels +{ + public enum JoinTypes + { + And, + Or + } + public class SearchDescription + { + public string TableName { get; set; } + public SearchObject SearchData { get; set; } + public JoinTypes JoinType { get; set; } + + } +} diff --git a/Datamodels/SearchModels/SearchObject.cs b/Datamodels/SearchModels/SearchObject.cs new file mode 100644 index 0000000..0af2537 --- /dev/null +++ b/Datamodels/SearchModels/SearchObject.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Datamodels.SearchModels +{ + public enum SearchTypes + { + Phrase, + Range, + Terms + } + + public enum PhraseOperators + { + Equal, + NotEqual, + LowerThan, + LowerThanOrEqual, + GreaterThan, + GreaterThanOrEqual, + StartsWith, + Contains, + EndsWith + } + + public class SearchObject + { + public List Values { get; set; } + public string FieldName { get; set; } + public SearchTypes SearchType { get; set; } + public PhraseOperators PhraseOperator { get; set; } + + + } +} diff --git a/Datamodels/SearchModels/TableModel.cs b/Datamodels/SearchModels/TableModel.cs new file mode 100644 index 0000000..28644f5 --- /dev/null +++ b/Datamodels/SearchModels/TableModel.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; + +namespace Datamodels.SearchModels +{ + public sealed class TableModel + { + public string TableName { get; set; } + public List Fields { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/README.md b/README.md index 22780d7..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,2 +0,0 @@ -# TxSearchApi - diff --git a/Repositories/FwDbContext.cs b/Repositories/FwDbContext.cs new file mode 100644 index 0000000..2850486 --- /dev/null +++ b/Repositories/FwDbContext.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using Datamodels.Lookups; +using Datamodels.DatabaseModels; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; + +namespace Repositories +{ + public class FwDbContext: DbContext + { + public string _connectionString { get; set; } = "SERVER=127.0.0.1;DATABASE=testdb;PORT=3306;USER=root;PASSWORD=example"; + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + + optionsBuilder.UseMySql(_connectionString); + } + + public DbSet LookupCategories { get; set; } + public DbSet LookupValues { get; set; } + public DbSet
Address { get; set; } + public DbSet Person { get; set; } + public DbSet Communication { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + + modelBuilder.Entity() + .HasKey(x => x.Id); + + modelBuilder.Entity() + .Property(x => x.Id).ValueGeneratedOnAdd(); + + modelBuilder.Entity() + .HasKey(x => x.Id); + + modelBuilder.Entity() + .Property(x => x.Id).ValueGeneratedOnAdd(); + + modelBuilder.Entity() + .HasMany(x => x.LookupValues) + .WithOne(y => y.LookupCategory); + + modelBuilder.Entity() + .Property(x => x.Id).ValueGeneratedOnAdd(); + + modelBuilder.Entity() + .HasKey(x => x.Id); + + modelBuilder.Entity() + .HasMany(x => x.Communications) + .WithOne(y => y.Person); + + modelBuilder.Entity() + .HasMany(x => x.Addresses) + .WithOne(y => y.Person); + + modelBuilder.Entity
() + .Property(x => x.Id).ValueGeneratedOnAdd(); + + modelBuilder.Entity
() + .HasKey(x => x.Id); + + modelBuilder.Entity() + .Property(x => x.Id).ValueGeneratedOnAdd(); + + modelBuilder.Entity() + .HasKey(x => x.Id); + + } + + } +} diff --git a/Repositories/FwMariaLookupRepo.cs b/Repositories/FwMariaLookupRepo.cs new file mode 100644 index 0000000..9a3250d --- /dev/null +++ b/Repositories/FwMariaLookupRepo.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Linq; +using Datamodels.BusinessModels; +using Datamodels.Lookups; +using Repositories.Interfaces; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; + +namespace Repositories +{ + public class FwMariaLookupRepo: IFwMariaLookupRepo + { + + private readonly FwDbContext _dbContext = new FwDbContext(); + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + + public FwMariaLookupRepo(ILogger logger, IConfiguration configuration) + { + _logger = logger; + _configuration = configuration; + try + { + if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development") + { + _dbContext._connectionString = "SERVER=127.0.0.1;DATABASE=testdb;PORT=3306;USER=root;PASSWORD=example"; + } + else + { + string usedDb = configuration.GetSection("GeneralSettings").GetSection("DbToUse").Get(); + string connectionString = configuration.GetConnectionString(usedDb); + _dbContext._connectionString = connectionString; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Database Connection Configuration not valid"); + } + } + + public async Task>> GetAllLookups() + { + var result = await Task.Run(() => + { + try + { + var newList = _dbContext.LookupCategories.Include(vals => vals.LookupValues).OrderBy(o => o.CategoryName).ToList(); + return new DataResult> { Data = newList }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reading Lookup Data"); + return new DataResult> + { + UserMessage = "Daten für Lookups konnten nicht gelesen werden.", + ServerMessage = ex.Message, + GeneralResult = GeneralResults.DatabaseError, + StackTrace = ex.StackTrace + }; + } + }); + return result; + } + + public async Task> GetLookupCategoryById(long id) + { + var result = await Task.Run(() => + { + try + { + var cat = _dbContext.LookupCategories.Where(x => x.Id == id).Include(vals => vals.LookupValues).FirstOrDefault(); + return new DataResult { Data = cat }; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error reading Lookup Category with Id: {id}"); + return new DataResult + { + UserMessage = $"Daten für Lookup Kategorie mit Id {id} konnten nicht gelesen werden.", + ServerMessage = ex.Message, + GeneralResult = GeneralResults.DatabaseError, + StackTrace = ex.StackTrace + }; + } + }); + return result; + } + + public async Task>> GetLookupCategoryByName(string categoryName) + { + var result = await Task.Run(() => + { + try + { + var cat = _dbContext.LookupCategories.Where(x => x.CategoryName.ToUpper().Contains(categoryName.ToUpper())).Include(vals => vals.LookupValues).ToList(); + return new DataResult> { Data = cat }; + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error reading Lookup Category with Name: {categoryName}"); + return new DataResult> + { + UserMessage = $"Daten für Lookup Kategorie mit Namen {categoryName} konnten nicht gelesen werden.", + ServerMessage = ex.Message, + GeneralResult = GeneralResults.DatabaseError, + StackTrace = ex.StackTrace + }; + } + }); + return result; + } + + public async Task>> GetActiveLookupCategories() + { + var result = await Task.Run(() => + { + try + { + var catList = _dbContext.LookupCategories.Where(x => x.IsActive).Include(vals => vals.LookupValues).OrderBy(o => o.CategoryName).ToList(); + return new DataResult> { Data = catList }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reading active Lookup Data"); + return new DataResult> + { + UserMessage = "Daten für aktive Lookups konnten nicht gelesen werden.", + ServerMessage = ex.Message, + GeneralResult = GeneralResults.DatabaseError, + StackTrace = ex.StackTrace + }; + } + }); + return result; + } + + public void DeleteAllCategories() + { + _dbContext.Database.ExecuteSqlRaw("delete from LookupValues"); + _dbContext.Database.ExecuteSqlRaw("delete from LookupCategories"); + + } + } +} \ No newline at end of file diff --git a/Repositories/FwMariaSearchRepo.cs b/Repositories/FwMariaSearchRepo.cs new file mode 100644 index 0000000..2cecb75 --- /dev/null +++ b/Repositories/FwMariaSearchRepo.cs @@ -0,0 +1,151 @@ +using Datamodels.BusinessModels; +using Datamodels.DatabaseModels; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Repositories.Interfaces; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Datamodels.SearchModels; +using System.Linq.Expressions; +using System.Diagnostics; + +namespace Repositories +{ + public class FwMariaSearchRepo : IFwMariaSearchRepo + { + private readonly FwDbContext _dbContext = new FwDbContext(); + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + + public FwMariaSearchRepo(ILogger logger, IConfiguration configuration) + { + _logger = logger; + _configuration = configuration; + try + { + if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development") + { + _dbContext._connectionString = "SERVER=127.0.0.1;DATABASE=testdb;PORT=3306;USER=root;PASSWORD=example"; + } + else + { + string usedDb = configuration.GetSection("GeneralSettings").GetSection("DbToUse").Get(); + string connectionString = configuration.GetConnectionString(usedDb); + _dbContext._connectionString = connectionString; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Database Connection Configuration not valid"); + } + } + + public async Task>> GetAllAddresses() + { + try + { + var result = await _dbContext.Address.Where(x => x.City == "Augsburg" || x.City == "München").Where(y => y.StreetName.Contains("bruck")).ToListAsync().ConfigureAwait(false); + return new DataResult> { Data = result }; + } + catch (Exception ex) + { + return new DataResult> + { + GeneralResult = GeneralResults.DatabaseError, + UserMessage = "Fehler beim Laden der Adressen", + ServerMessage = ex.Message + }; + } + } + + public Task GetAllSortedData(TableModel queryFieldDef, List sortModel) + { + throw new NotImplementedException(); + } + + public Task GetBlobData(string tableName, string fieldName, long id) + { + throw new NotImplementedException(); + } + + public Task GetDataFilteredAndSorted(List filter, List sortModel, long skip = 0, long take = -1) + { + throw new NotImplementedException(); + } + + public async Task>> GetFilteredData(List expressions, long skip = 0, long take = -1) where T : IEntityClass + { + + var where = FilterLinq.GetWherePredicate(wherefield, wherefieldvalue).Compile(); + items = await _dbContext.Address.Where((Expression>)where).ToListAsync(); + var result = new DataResult>(); + if (typeof(T).Equals(typeof(Address))) + { + Expression combined = null; + foreach (var exp in expressions) + { + if (combined == null) + { + combined = exp; + } + else + { + combined = Expression.Or(combined, exp); + } + } + ParameterExpression pe = Expression.Parameter(typeof(T), typeof(T).Name); + combined = Expression.Lambda>(combined, new ParameterExpression[] { pe }); + //var addressFound = await _dbContext.Address.Where((Expression>)combined).ToListAsync(); + var addressFound = await _dbContext.Address.Where(a => a.City == "Augsburg" | a.City == "München").ToListAsync(); + result.Data = new List(addressFound.Cast()); + + } + + return result; + } + } + + public class FilterLinq + { + public static Expression> GetWherePredicate(string whereFieldList, string whereFieldValues) + { + //the 'IN' parameter for expression ie T=> condition + ParameterExpression pe = Expression.Parameter(typeof(T), typeof(T).Name); + + //combine them with and 1=1 Like no expression + Expression combined = null; + if (whereFieldList != null) + { + string[] field = whereFieldList.Split(';'); + string[] fieldValue = whereFieldValues.Split(';'); + for (int i = 0; i < field.Count(); i++) + { + //Expression for accessing Fields name property + Expression columnNameProperty = Expression.Property(pe, field[i]); + + //the name constant to match + Expression columnValue = Expression.Constant(fieldValue[i]); + + //the first expression: PatientantLastName = ? + Expression e1 = Expression.Equal(columnNameProperty, columnValue); + + if (combined == null) + { + combined = e1; + } + else + { + combined = Expression.And(combined, e1); + } + } + } + + //create and return the predicate + return Expression.Lambda>(combined, new ParameterExpression[] { pe }); + } + } +} diff --git a/Repositories/Interfaces/IFwMariaLookupRepo.cs b/Repositories/Interfaces/IFwMariaLookupRepo.cs new file mode 100644 index 0000000..4cc3771 --- /dev/null +++ b/Repositories/Interfaces/IFwMariaLookupRepo.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Datamodels.BusinessModels; +using Datamodels.Lookups; + +namespace Repositories.Interfaces +{ + public interface IFwMariaLookupRepo + { + Task>> GetAllLookups(); + Task> GetLookupCategoryById(long id); + Task>> GetLookupCategoryByName(string categoryName); + Task>> GetActiveLookupCategories(); + } +} \ No newline at end of file diff --git a/Repositories/Interfaces/IFwMariaSearchRepo.cs b/Repositories/Interfaces/IFwMariaSearchRepo.cs new file mode 100644 index 0000000..1175aae --- /dev/null +++ b/Repositories/Interfaces/IFwMariaSearchRepo.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using System; +using Datamodels.BusinessModels; +using Datamodels.DatabaseModels; +using Datamodels.SearchModels; +using System.Linq.Expressions; + +namespace Repositories.Interfaces +{ + public interface IFwMariaSearchRepo + { + Task>> GetAllAddresses(); + + /// + /// Get Filtered Data from DB Provider without sorting + /// + /// Object describing filters + /// From row + /// To Row (-1 for all rows) + /// Table- and FieldDefinitions to load from + /// Result Object with Result information and list of DataObjects + Task>> GetFilteredData (List expressions, long skip = 0, long take = -1) where T: IEntityClass; + + /// + /// Get all Data from query field definition sorted by sortModel + /// + /// Table and Field Descriptions to Query from + /// Sorting Definitions + /// Result Object with Result information and list of DataObjects + Task GetAllSortedData (TableModel queryFieldDef, List sortModel); + + /// + /// Get filtered and sorted DB Provider with given range of records + /// + /// Table- and Field Description to Query from + /// Object describing filter + /// Sorting Definitions + /// From Row + /// To Row (-1) for all rows + /// Result Object with Result information and list of DataObjects + Task GetDataFilteredAndSorted(List filter, List sortModel, long skip=0, long take=-1); + + /// + /// Get Blob String from specific Field in specific Table - works only with id long fields + /// + /// Name of Table + /// Name of Field + /// id value of Record + /// String with Blob data + Task GetBlobData(string tableName, string fieldName, long id); + } +} \ No newline at end of file diff --git a/Repositories/Migrations/20210706081036_v1.Designer.cs b/Repositories/Migrations/20210706081036_v1.Designer.cs new file mode 100644 index 0000000..1c4d9b4 --- /dev/null +++ b/Repositories/Migrations/20210706081036_v1.Designer.cs @@ -0,0 +1,202 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Repositories; + +namespace Repositories.Migrations +{ + [DbContext(typeof(FwDbContext))] + [Migration("20210706081036_v1")] + partial class v1 + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.16") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Datamodels.DatabaseModels.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AddressTypeId") + .HasColumnType("bigint"); + + b.Property("ChangedAt") + .HasColumnType("datetime(6)"); + + b.Property("City") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("PersonId") + .HasColumnType("bigint"); + + b.Property("StreetName") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("StreetNumber") + .HasColumnType("int"); + + b.Property("Zip") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("AddressTypeId"); + + b.HasIndex("PersonId"); + + b.ToTable("Address"); + }); + + modelBuilder.Entity("Datamodels.DatabaseModels.Communication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ChangedAt") + .HasColumnType("datetime(6)"); + + b.Property("CommunicationTypeId") + .HasColumnType("bigint"); + + b.Property("CommunicationValue") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("PersonId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CommunicationTypeId"); + + b.HasIndex("PersonId"); + + b.ToTable("Communication"); + }); + + modelBuilder.Entity("Datamodels.DatabaseModels.Person", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("Birthday") + .HasColumnType("datetime(6)"); + + b.Property("ChangedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FirstName") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("GenderId") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("LastName") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("GenderId"); + + b.ToTable("Person"); + }); + + modelBuilder.Entity("Datamodels.Lookups.LookupCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CategoryName") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("LookupCategories"); + }); + + modelBuilder.Entity("Datamodels.Lookups.LookupValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("LookupCategoryId") + .HasColumnType("bigint"); + + b.Property("Value") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("LookupCategoryId"); + + b.ToTable("LookupValues"); + }); + + modelBuilder.Entity("Datamodels.DatabaseModels.Address", b => + { + b.HasOne("Datamodels.Lookups.LookupValue", "AddressType") + .WithMany() + .HasForeignKey("AddressTypeId"); + + b.HasOne("Datamodels.DatabaseModels.Person", "Person") + .WithMany("Addresses") + .HasForeignKey("PersonId"); + }); + + modelBuilder.Entity("Datamodels.DatabaseModels.Communication", b => + { + b.HasOne("Datamodels.Lookups.LookupValue", "CommunicationType") + .WithMany() + .HasForeignKey("CommunicationTypeId"); + + b.HasOne("Datamodels.DatabaseModels.Person", "Person") + .WithMany("Communications") + .HasForeignKey("PersonId"); + }); + + modelBuilder.Entity("Datamodels.DatabaseModels.Person", b => + { + b.HasOne("Datamodels.Lookups.LookupValue", "Gender") + .WithMany() + .HasForeignKey("GenderId"); + }); + + modelBuilder.Entity("Datamodels.Lookups.LookupValue", b => + { + b.HasOne("Datamodels.Lookups.LookupCategory", "LookupCategory") + .WithMany("LookupValues") + .HasForeignKey("LookupCategoryId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Repositories/Migrations/20210706081036_v1.cs b/Repositories/Migrations/20210706081036_v1.cs new file mode 100644 index 0000000..2368273 --- /dev/null +++ b/Repositories/Migrations/20210706081036_v1.cs @@ -0,0 +1,181 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Repositories.Migrations +{ + public partial class v1 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "LookupCategories", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + CategoryName = table.Column(nullable: true), + IsActive = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LookupCategories", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "LookupValues", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Value = table.Column(nullable: true), + IsActive = table.Column(nullable: false), + LookupCategoryId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_LookupValues", x => x.Id); + table.ForeignKey( + name: "FK_LookupValues_LookupCategories_LookupCategoryId", + column: x => x.LookupCategoryId, + principalTable: "LookupCategories", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Person", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + LastName = table.Column(nullable: true), + FirstName = table.Column(nullable: true), + Birthday = table.Column(nullable: true), + IsActive = table.Column(nullable: false), + CreatedAt = table.Column(nullable: false), + ChangedAt = table.Column(nullable: false), + GenderId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Person", x => x.Id); + table.ForeignKey( + name: "FK_Person_LookupValues_GenderId", + column: x => x.GenderId, + principalTable: "LookupValues", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Address", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + StreetName = table.Column(nullable: true), + StreetNumber = table.Column(nullable: false), + Zip = table.Column(nullable: true), + City = table.Column(nullable: true), + CreatedAt = table.Column(nullable: false), + ChangedAt = table.Column(nullable: false), + AddressTypeId = table.Column(nullable: true), + PersonId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Address", x => x.Id); + table.ForeignKey( + name: "FK_Address_LookupValues_AddressTypeId", + column: x => x.AddressTypeId, + principalTable: "LookupValues", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Address_Person_PersonId", + column: x => x.PersonId, + principalTable: "Person", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Communication", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + CommunicationValue = table.Column(nullable: true), + CommunicationTypeId = table.Column(nullable: true), + CreatedAt = table.Column(nullable: false), + ChangedAt = table.Column(nullable: false), + PersonId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Communication", x => x.Id); + table.ForeignKey( + name: "FK_Communication_LookupValues_CommunicationTypeId", + column: x => x.CommunicationTypeId, + principalTable: "LookupValues", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Communication_Person_PersonId", + column: x => x.PersonId, + principalTable: "Person", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Address_AddressTypeId", + table: "Address", + column: "AddressTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_Address_PersonId", + table: "Address", + column: "PersonId"); + + migrationBuilder.CreateIndex( + name: "IX_Communication_CommunicationTypeId", + table: "Communication", + column: "CommunicationTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_Communication_PersonId", + table: "Communication", + column: "PersonId"); + + migrationBuilder.CreateIndex( + name: "IX_LookupValues_LookupCategoryId", + table: "LookupValues", + column: "LookupCategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_Person_GenderId", + table: "Person", + column: "GenderId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Address"); + + migrationBuilder.DropTable( + name: "Communication"); + + migrationBuilder.DropTable( + name: "Person"); + + migrationBuilder.DropTable( + name: "LookupValues"); + + migrationBuilder.DropTable( + name: "LookupCategories"); + } + } +} diff --git a/Repositories/Migrations/FwDbContextModelSnapshot.cs b/Repositories/Migrations/FwDbContextModelSnapshot.cs new file mode 100644 index 0000000..41c53bb --- /dev/null +++ b/Repositories/Migrations/FwDbContextModelSnapshot.cs @@ -0,0 +1,200 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Repositories; + +namespace Repositories.Migrations +{ + [DbContext(typeof(FwDbContext))] + partial class FwDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.16") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Datamodels.DatabaseModels.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AddressTypeId") + .HasColumnType("bigint"); + + b.Property("ChangedAt") + .HasColumnType("datetime(6)"); + + b.Property("City") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("PersonId") + .HasColumnType("bigint"); + + b.Property("StreetName") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("StreetNumber") + .HasColumnType("int"); + + b.Property("Zip") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("AddressTypeId"); + + b.HasIndex("PersonId"); + + b.ToTable("Address"); + }); + + modelBuilder.Entity("Datamodels.DatabaseModels.Communication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ChangedAt") + .HasColumnType("datetime(6)"); + + b.Property("CommunicationTypeId") + .HasColumnType("bigint"); + + b.Property("CommunicationValue") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("PersonId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CommunicationTypeId"); + + b.HasIndex("PersonId"); + + b.ToTable("Communication"); + }); + + modelBuilder.Entity("Datamodels.DatabaseModels.Person", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("Birthday") + .HasColumnType("datetime(6)"); + + b.Property("ChangedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FirstName") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("GenderId") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("LastName") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("GenderId"); + + b.ToTable("Person"); + }); + + modelBuilder.Entity("Datamodels.Lookups.LookupCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CategoryName") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("LookupCategories"); + }); + + modelBuilder.Entity("Datamodels.Lookups.LookupValue", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("LookupCategoryId") + .HasColumnType("bigint"); + + b.Property("Value") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("LookupCategoryId"); + + b.ToTable("LookupValues"); + }); + + modelBuilder.Entity("Datamodels.DatabaseModels.Address", b => + { + b.HasOne("Datamodels.Lookups.LookupValue", "AddressType") + .WithMany() + .HasForeignKey("AddressTypeId"); + + b.HasOne("Datamodels.DatabaseModels.Person", "Person") + .WithMany("Addresses") + .HasForeignKey("PersonId"); + }); + + modelBuilder.Entity("Datamodels.DatabaseModels.Communication", b => + { + b.HasOne("Datamodels.Lookups.LookupValue", "CommunicationType") + .WithMany() + .HasForeignKey("CommunicationTypeId"); + + b.HasOne("Datamodels.DatabaseModels.Person", "Person") + .WithMany("Communications") + .HasForeignKey("PersonId"); + }); + + modelBuilder.Entity("Datamodels.DatabaseModels.Person", b => + { + b.HasOne("Datamodels.Lookups.LookupValue", "Gender") + .WithMany() + .HasForeignKey("GenderId"); + }); + + modelBuilder.Entity("Datamodels.Lookups.LookupValue", b => + { + b.HasOne("Datamodels.Lookups.LookupCategory", "LookupCategory") + .WithMany("LookupValues") + .HasForeignKey("LookupCategoryId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Repositories/Repositories.csproj b/Repositories/Repositories.csproj new file mode 100644 index 0000000..1a08ffa --- /dev/null +++ b/Repositories/Repositories.csproj @@ -0,0 +1,22 @@ + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + netstandard2.1 + + + diff --git a/SearchWebApi/Controllers/LookupController.cs b/SearchWebApi/Controllers/LookupController.cs new file mode 100644 index 0000000..f01aba0 --- /dev/null +++ b/SearchWebApi/Controllers/LookupController.cs @@ -0,0 +1,57 @@ +using BusinessLogic.LookupLogic.Interfaces; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using System.Threading.Tasks; + +namespace FwSearchApi.Controllers +{ + [ApiController] + [Route("[controller]")] + public class LookupController : ControllerBase + { + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + private readonly ILookupLogic _lookupLogic; + + public LookupController(IConfiguration configuration, ILogger logger, ILookupLogic lookupLogic) + { + _configuration = configuration; + _logger = logger; + _lookupLogic = lookupLogic; + } + + [HttpGet] + [Route("GetAllLookups")] + public async Task GetAllLookups() + { + var result = await _lookupLogic.GetAllLookups(); + _logger.LogInformation($"Lookups zur Verfügung gestellt von {nameof(GetAllLookups)}"); + return new JsonResult(result); + } + + [HttpGet] + [Route("GetCategoriesById")] + public async Task GetLookupCategoriesById(long id) + { + var result = await _lookupLogic.GetLookupCategoryById(id); + return new JsonResult(result); + } + + [HttpGet] + [Route("GetCategoriesByName")] + public async Task GetLookupCategoriesByName(string categoryName) + { + var result = await _lookupLogic.GetLookupCategoryByName(categoryName); + return new JsonResult(result); + } + + [HttpGet] + [Route("GetActiveCategories")] + public async Task GetActiveLookupCategories() + { + var result = await _lookupLogic.GetActiveLookupCategories(); + return new JsonResult(result); + } + } +} \ No newline at end of file diff --git a/SearchWebApi/Controllers/SearchController.cs b/SearchWebApi/Controllers/SearchController.cs new file mode 100644 index 0000000..cf441b5 --- /dev/null +++ b/SearchWebApi/Controllers/SearchController.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using System.Threading.Tasks; +using SearchLogic; +using Datamodels.SearchModels; +using System.Collections.Generic; +using Datamodels.DatabaseModels; + +namespace FwSearchApi.Controllers +{ + [ApiController] + [Route("[controller]")] + public class SearchController : ControllerBase + { + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + private readonly ISearchLogic _searchLogic; + + public SearchController(ILogger logger, IConfiguration configuration, ISearchLogic searchLogic) + { + _logger = logger; + _configuration = configuration; + _searchLogic = searchLogic; + } + + /// + /// This is for Test only + /// + [HttpGet] + [Route("GetAddresses")] + public async Task GetAllAddresses() + { + var result = await _searchLogic.GetAllAddresses(); + return new JsonResult(result); + } + + [HttpPost] + [Route("GetFilteredData")] + public async Task GetFilteredData([FromBody] FilterModel filter, [FromQuery] long skip = 0, [FromQuery] long take = -1) + { + var result = await _searchLogic.GetFilteredData(filter, skip, take); + return new JsonResult(result); + } + } +} \ No newline at end of file diff --git a/SearchWebApi/Controllers/WeatherForecastController.cs b/SearchWebApi/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..3e27f77 --- /dev/null +++ b/SearchWebApi/Controllers/WeatherForecastController.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace FeatureWerkAPIs.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + public IEnumerable Get() + { + var rng = new Random(); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = rng.Next(-20, 55), + Summary = Summaries[rng.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} diff --git a/SearchWebApi/Program.cs b/SearchWebApi/Program.cs new file mode 100644 index 0000000..e0c58bf --- /dev/null +++ b/SearchWebApi/Program.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Serilog; + +namespace FwSearchApi +{ + + public class Program + { + public static IConfiguration Configuration { get; } = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", optional: true, reloadOnChange: true) + .Build(); + + public static void Main(string[] args) + { + Log.Logger = new LoggerConfiguration() + .ReadFrom.Configuration(Configuration) + .CreateLogger(); + try { + CreateWebHostBuilder(args).Build().Run(); + } + finally{ + Log.CloseAndFlush(); + } + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseConfiguration(Configuration) + .UseStartup() + .UseSerilog() + .SuppressStatusMessages(true); + } +} diff --git a/SearchWebApi/Properties/launchSettings.json b/SearchWebApi/Properties/launchSettings.json new file mode 100644 index 0000000..1e5a5ef --- /dev/null +++ b/SearchWebApi/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:20693", + "sslPort": 0 + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchUrl": "weatherforecast", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "FeatureWerkAPIs": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "weatherforecast", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:5000" + } + } +} \ No newline at end of file diff --git a/SearchWebApi/SearchWebApi.csproj b/SearchWebApi/SearchWebApi.csproj new file mode 100644 index 0000000..8db113b --- /dev/null +++ b/SearchWebApi/SearchWebApi.csproj @@ -0,0 +1,25 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + + + + + + + + + + diff --git a/SearchWebApi/Startup.cs b/SearchWebApi/Startup.cs new file mode 100644 index 0000000..0a614cd --- /dev/null +++ b/SearchWebApi/Startup.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Repositories; +using Repositories.Interfaces; +using BusinessLogic.LookupLogic; +using BusinessLogic.LookupLogic.Interfaces; +using SearchLogic; + +namespace FwSearchApi +{ + public class Startup + { + + readonly ILogger _logger; + public IConfiguration _configuration { get; } + + public Startup(IConfiguration configuration, ILogger logger) + { + _configuration = configuration; + _logger = logger; + _logger.LogInformation("Starting up FeatureWerk Search API"); + } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers().AddNewtonsoftJson((options) => + { + // keine Optionen hier + }); + + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddSwaggerGen(); + + _logger.LogInformation("Services for FeatureWerkAPIs configured"); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseSwagger(); + + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "FeatureWerk Search API V1"); + }); + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/SearchWebApi/WeatherForecast.cs b/SearchWebApi/WeatherForecast.cs new file mode 100644 index 0000000..00689b1 --- /dev/null +++ b/SearchWebApi/WeatherForecast.cs @@ -0,0 +1,15 @@ +using System; + +namespace FeatureWerkAPIs +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string Summary { get; set; } + } +} diff --git a/SearchWebApi/appsettings.Development.json b/SearchWebApi/appsettings.Development.json new file mode 100644 index 0000000..0553900 --- /dev/null +++ b/SearchWebApi/appsettings.Development.json @@ -0,0 +1,40 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "ConnectionStrings": { + "mariadb": "SERVER=127.0.0.1;DATABASE=testdb;PORT=3306;USER=root;PASSWORD=example", + "postgres": "", + "sqlserver": "", + "oracle": "" + }, + "GeneralSettings": { + "DbToUse": "mariadb" + }, + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "System": "Information", + "Microsoft": "Warning" + } + }, + "WriteTo": [ + { "Name": "File", + "Args": { + "path": "/tmp/FwSearchApi.log", + "rollingInterval": "Day" + } + } + ], + "Enrich": ["FromLogContext", "WithMachineName"], + "Properties": { + "Appname": "FeatureWerkSearchAPI", + "Environment": "Development" + } + } +} diff --git a/SearchWebApi/appsettings.json b/SearchWebApi/appsettings.json new file mode 100644 index 0000000..214d63f --- /dev/null +++ b/SearchWebApi/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/Tests/Repo/fwrepo/SimpleDbTests.cs b/Tests/Repo/fwrepo/SimpleDbTests.cs new file mode 100644 index 0000000..f031fc1 --- /dev/null +++ b/Tests/Repo/fwrepo/SimpleDbTests.cs @@ -0,0 +1,79 @@ +using System; +using Xunit; +using Repositories; +using Microsoft.EntityFrameworkCore; +using Datamodels.Lookups; +using System.Threading.Tasks; +using System.Linq; +using Datamodels.BusinessModels; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Configuration; +using Moq; + +namespace fwrepo +{ + public class SimpleDbTests + { + + private readonly FwDbContext testdb = new FwDbContext(); + private readonly FwMariaLookupRepo repo = new FwMariaLookupRepo(Mock.Of>(), Mock.Of()); + + [Fact] + public void CreateSomeLookupData() + { + repo.DeleteAllCategories(); + + var rand = new Random(); + int catCount = rand.Next(10, 50); + for (int i = 0; i < catCount; i++) + { + var category = new LookupCategory + { + CategoryName = $"{i} Category" + }; + testdb.LookupCategories.Add(category); + var valRnd = new Random(); + int valCount = valRnd.Next(10,50); + for (int x = 0; x < valCount; x++) + { + testdb.LookupValues.Add(new LookupValue + { + Value = $"Category { i } - Value {x}", + LookupCategory = category + }); + } + } + int result = testdb.SaveChanges(); + Assert.True(result > 0); + } + + [Fact] + public async Task GetAllLookupCategories() + { + var result = await repo.GetAllLookups(); + Assert.NotEmpty(result.Data); + Assert.NotEmpty(result.Data.First().LookupValues); + } + + [Fact] + public async Task GetLookupCategoryById() + { + var result = await repo.GetLookupCategoryById(65); + Assert.NotNull(result.Data); + } + + [Fact] + public async Task GetLookupCategoryByName() + { + var result = await repo.GetLookupCategoryByName("Category"); + Assert.NotEmpty(result.Data); + } + + [Fact] + public async Task GetActiveCategories() + { + var result = await repo.GetActiveLookupCategories(); + Assert.NotEmpty(result.Data); + } + } +} diff --git a/Tests/Repo/fwrepo/fwrepo.csproj b/Tests/Repo/fwrepo/fwrepo.csproj new file mode 100644 index 0000000..99a8759 --- /dev/null +++ b/Tests/Repo/fwrepo/fwrepo.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + + + diff --git a/Tools/EF/Migration/Migration.csproj b/Tools/EF/Migration/Migration.csproj new file mode 100644 index 0000000..a65e492 --- /dev/null +++ b/Tools/EF/Migration/Migration.csproj @@ -0,0 +1,19 @@ + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + Exe + netcoreapp3.1 + + + diff --git a/Tools/EF/Migration/Program.cs b/Tools/EF/Migration/Program.cs new file mode 100644 index 0000000..e3cb1a7 --- /dev/null +++ b/Tools/EF/Migration/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace Migration +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello World!"); + } + } +} diff --git a/TxSearchApi.sln b/TxSearchApi.sln new file mode 100644 index 0000000..bcd7fde --- /dev/null +++ b/TxSearchApi.sln @@ -0,0 +1,72 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31424.327 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SearchWebApi", "SearchWebApi\SearchWebApi.csproj", "{DAB6C3F6-6437-491F-A3AA-D9A2518C96AC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LookupLogic", "BusinessLogic\LookupLogic\LookupLogic.csproj", "{20943D77-BB49-4D84-A466-ADB9EA9ED0A7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SearchLogic", "BusinessLogic\SearchLogic\SearchLogic.csproj", "{566B6716-C97C-4A79-A342-9160C3989EDF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Datamodels", "Datamodels\Datamodels.csproj", "{43A6B0AF-3828-477D-A6B6-37A562E3F289}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Repositories", "Repositories\Repositories.csproj", "{913847E9-2460-4E1A-9C86-F1CA3BA08C40}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Migration", "Tools\EF\Migration\Migration.csproj", "{D258FDB3-484F-4E0A-B48E-42C9139679D3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "fwrepo", "Tests\Repo\fwrepo\fwrepo.csproj", "{38A7FB4F-2874-4D82-A2E7-088AF95CEDBA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{92B23E8B-5C97-4116-B178-FF2B90FE3B44}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{16706E07-A95C-476A-90E3-3FF782B4A14E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{37670BBA-CF6C-476A-ACA8-E1F62B30EA77}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DAB6C3F6-6437-491F-A3AA-D9A2518C96AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DAB6C3F6-6437-491F-A3AA-D9A2518C96AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DAB6C3F6-6437-491F-A3AA-D9A2518C96AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DAB6C3F6-6437-491F-A3AA-D9A2518C96AC}.Release|Any CPU.Build.0 = Release|Any CPU + {20943D77-BB49-4D84-A466-ADB9EA9ED0A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {20943D77-BB49-4D84-A466-ADB9EA9ED0A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20943D77-BB49-4D84-A466-ADB9EA9ED0A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {20943D77-BB49-4D84-A466-ADB9EA9ED0A7}.Release|Any CPU.Build.0 = Release|Any CPU + {566B6716-C97C-4A79-A342-9160C3989EDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {566B6716-C97C-4A79-A342-9160C3989EDF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {566B6716-C97C-4A79-A342-9160C3989EDF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {566B6716-C97C-4A79-A342-9160C3989EDF}.Release|Any CPU.Build.0 = Release|Any CPU + {43A6B0AF-3828-477D-A6B6-37A562E3F289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43A6B0AF-3828-477D-A6B6-37A562E3F289}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43A6B0AF-3828-477D-A6B6-37A562E3F289}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43A6B0AF-3828-477D-A6B6-37A562E3F289}.Release|Any CPU.Build.0 = Release|Any CPU + {913847E9-2460-4E1A-9C86-F1CA3BA08C40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {913847E9-2460-4E1A-9C86-F1CA3BA08C40}.Debug|Any CPU.Build.0 = Debug|Any CPU + {913847E9-2460-4E1A-9C86-F1CA3BA08C40}.Release|Any CPU.ActiveCfg = Release|Any CPU + {913847E9-2460-4E1A-9C86-F1CA3BA08C40}.Release|Any CPU.Build.0 = Release|Any CPU + {D258FDB3-484F-4E0A-B48E-42C9139679D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D258FDB3-484F-4E0A-B48E-42C9139679D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D258FDB3-484F-4E0A-B48E-42C9139679D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D258FDB3-484F-4E0A-B48E-42C9139679D3}.Release|Any CPU.Build.0 = Release|Any CPU + {38A7FB4F-2874-4D82-A2E7-088AF95CEDBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {38A7FB4F-2874-4D82-A2E7-088AF95CEDBA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38A7FB4F-2874-4D82-A2E7-088AF95CEDBA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {38A7FB4F-2874-4D82-A2E7-088AF95CEDBA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {38A7FB4F-2874-4D82-A2E7-088AF95CEDBA} = {16706E07-A95C-476A-90E3-3FF782B4A14E} + {16706E07-A95C-476A-90E3-3FF782B4A14E} = {92B23E8B-5C97-4116-B178-FF2B90FE3B44} + {37670BBA-CF6C-476A-ACA8-E1F62B30EA77} = {92B23E8B-5C97-4116-B178-FF2B90FE3B44} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {52A7E990-B64B-4E17-BA59-FE07B325B88C} + EndGlobalSection +EndGlobal