Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ad518abd07 | |||
| 64da453152 | |||
| 8e6bc709f3 | |||
| 9e2246a459 |
@@ -381,7 +381,3 @@ FodyWeavers.xsd
|
|||||||
.idea
|
.idea
|
||||||
/.ollamassist/conversations
|
/.ollamassist/conversations
|
||||||
/write_readme.py
|
/write_readme.py
|
||||||
|
|
||||||
# n8n local workflow artifacts
|
|
||||||
.n8n-docs-rc-flow/
|
|
||||||
logs/
|
|
||||||
|
|||||||
@@ -21,30 +21,20 @@
|
|||||||
|
|
||||||
<PackageReference Include="AutoMapper" Version="16.1.1" />
|
<PackageReference Include="AutoMapper" Version="16.1.1" />
|
||||||
|
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.11" />
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0">
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="5.3.0" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="5.3.0" />
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.9" />
|
|
||||||
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.9" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.9" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.9" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.9" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.9" />
|
||||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.19.1" />
|
|
||||||
<PackageReference Include="MongoDB.Driver" Version="3.9.0" />
|
<PackageReference Include="MongoDB.Driver" Version="3.9.0" />
|
||||||
<PackageReference Include="Quartz" Version="3.18.1" />
|
<PackageReference Include="Quartz" Version="3.18.1" />
|
||||||
<PackageReference Include="StackExchange.Redis" Version="3.0.0" />
|
<PackageReference Include="StackExchange.Redis" Version="3.0.0" />
|
||||||
<PackageReference Include="System.Text.Json" Version="10.0.9" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="Microsoft.AspNetCore.Http.Features">
|
|
||||||
<HintPath>..\..\..\..\..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\7.0.10\Microsoft.AspNetCore.Http.Features.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -10,12 +10,12 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AuditLogs" Version="1.0.59" />
|
<PackageReference Include="AuditLogs" Version="1.0.59" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.11" />
|
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.11" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.28" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.3.11" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.11" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0">
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="5.3.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.9" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.9" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.9" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.9" />
|
||||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.19.1" />
|
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.19.1" />
|
||||||
|
|||||||
@@ -10,15 +10,11 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AuditLogs" Version="1.0.59" />
|
<PackageReference Include="AuditLogs" Version="1.0.59" />
|
||||||
<PackageReference Include="BCrypt.Net-Next" Version="4.2.0" />
|
<PackageReference Include="BCrypt.Net-Next" Version="4.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.3.11" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.3.11" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.28" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.28" />
|
||||||
<PackageReference Include="MongoDB.Bson" Version="3.9.0" />
|
<PackageReference Include="MongoDB.Bson" Version="3.9.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
<PackageReference Include="Newtonsoft.Json.Bson" Version="1.0.3" />
|
<PackageReference Include="Newtonsoft.Json.Bson" Version="1.0.3" />
|
||||||
<PackageReference Include="Serilog" Version="4.3.1" />
|
<PackageReference Include="Serilog" Version="4.3.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.49.1" />
|
|
||||||
<PackageReference Include="Snappier" Version="1.3.1" />
|
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.19.1" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.19.1" />
|
||||||
<PackageReference Include="System.Text.Json" Version="10.0.9" />
|
<PackageReference Include="System.Text.Json" Version="10.0.9" />
|
||||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||||
|
|||||||
@@ -79,7 +79,6 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="entity">The entity to insert.</param>
|
/// <param name="entity">The entity to insert.</param>
|
||||||
/// <exception cref="Exception">Throws and re-throws exceptions after logging.</exception>
|
/// <exception cref="Exception">Throws and re-throws exceptions after logging.</exception>
|
||||||
/// <!-- aidoc:v1 sig=c7a7ad8 body=26b3581 -->
|
|
||||||
public override async Task InsertOneAsync(T entity)
|
public override async Task InsertOneAsync(T entity)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -114,12 +113,11 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates an existing master list entity with full replacement.
|
/// Persists the supplied <typeparamref name="T"/> entity in the collection by replacing the document identified by <see cref="T.Id"/>, or inserting a new document when no match is found (upsert). Logs and rethrows any exception raised during the operation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="entity">The entity with updated values.</param>
|
/// <typeparam name="T">The document type stored in the collection.</typeparam>
|
||||||
/// <exception cref="Exception">Throws and re-throws exceptions after logging.</exception>
|
/// <param name="entity">The entity whose <see cref="T.Id"/> locates the target document and whose state is written to the collection.</param>
|
||||||
/// <!-- aidoc-review:v1 severity=medium kind=wrong_summary
|
/// <!-- aidoc:v1 sig=019b691 body=863650a -->
|
||||||
/// "Summary states 'Updates an existing' entity, but the ReplaceOptions { IsUpsert = true } means the entity will be inserted if it does not already exist, so the 'existing' qualifier is contradicted by the code's actual behavior." -->
|
|
||||||
public async Task Update(T entity)
|
public async Task Update(T entity)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -169,17 +167,12 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds a master list entity by its ID with options projection limited to 100 items.
|
/// Asynchronously retrieves an entity of type <typeparamref name="T"/> from the collection by its <see cref="ObjectId"/>, applying a projection that truncates the <c>options</c> field to 100 elements. Returns <see langword="null"/> when no matching document is found, or when an exception is caught and logged by <see cref="Log"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The ObjectId of the entity to retrieve.</param>
|
/// <typeparam name="T">The document type stored in the collection, expected to expose an <see cref="ObjectId"/> identifier.</typeparam>
|
||||||
/// <returns>The MasterList entity if found; otherwise, null.</returns>
|
/// <param name="id">The <see cref="ObjectId"/> of the entity to locate.</param>
|
||||||
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
|
/// <returns>A <see cref="Task{T}"/> that resolves to the matching entity, or <see langword="null"/> if the document is not found or the operation fails.</returns>
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=wrong_summary
|
/// <!-- aidoc:v1 sig=81ac96a body=1519944 -->
|
||||||
/// "Method is generic (returns Task<T?>) and works on any entity type T, not specifically a 'master list entity'." -->
|
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=wrong_returns
|
|
||||||
/// "Documentation says it returns 'The MasterList entity' but the method actually returns Task<T?>, a generic type." -->
|
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=wrong_exception
|
|
||||||
/// "Documents <exception cref=\"Exception\"> but the catch block swallows all exceptions and never rethrows, so the method never throws." -->
|
|
||||||
public async Task<T?> FindById(ObjectId id)
|
public async Task<T?> FindById(ObjectId id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -203,16 +196,13 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds a specific option within a master list by master and option IDs with locale translation.
|
/// Retrieves a single <see cref="OptionList"/> item from a master document identified by <paramref name="masterId"/> and <paramref name="optionId"/>, applying locale-aware name resolution. When <paramref name="locale"/> is <see cref="LocaleEnum.Default"/> or matches the document's default locale, the base name is returned; otherwise, the matching entry from the <c>localeItems</c> map is used, falling back to the base name when no translation is found. Returns <see langword="null"/> when the option is not found or if an error occurs during aggregation.
|
||||||
/// Uses MongoDB aggregation to apply translations and return the translated option.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="masterId">The ObjectId of the master list.</param>
|
/// <param name="masterId">The <see cref="ObjectId"/> of the master document that contains the option collection to search.</param>
|
||||||
/// <param name="optionId">The ObjectId of the option to retrieve.</param>
|
/// <param name="optionId">The <see cref="ObjectId"/> of the specific option within the master document's options array.</param>
|
||||||
/// <param name="locale">The locale for translation.</param>
|
/// <param name="locale">The <see cref="LocaleEnum"/> value that drives translation selection; <see cref="LocaleEnum.Default"/> resolves to the document's default locale, while any other value attempts a locale-specific lookup.</param>
|
||||||
/// <returns>The OptionList with translated fields if found; otherwise, null.</returns>
|
/// <returns>A <see cref="Task{TResult}"/> that yields the matched <see cref="OptionList"/>, or <see langword="null"/> if no document/option is found or the operation fails.</returns>
|
||||||
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
|
/// <!-- aidoc:v1 sig=fdb2628 body=0e4983e -->
|
||||||
/// <!-- aidoc-review:v1 severity=medium kind=extra_exception
|
|
||||||
/// "The method catches all exceptions internally (try/catch) and never throws; documenting <exception cref=\"Exception\"> is misleading. Callers will not receive any exception to handle." -->
|
|
||||||
public async Task<OptionList?> FindOptionItemById(ObjectId masterId, ObjectId optionId, LocaleEnum locale)
|
public async Task<OptionList?> FindOptionItemById(ObjectId masterId, ObjectId optionId, LocaleEnum locale)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -358,8 +348,7 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
/// <param name="optionId">The ObjectId of the option to retrieve.</param>
|
/// <param name="optionId">The ObjectId of the option to retrieve.</param>
|
||||||
/// <returns>The OptionList if found; otherwise, null.</returns>
|
/// <returns>The OptionList if found; otherwise, null.</returns>
|
||||||
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
|
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
|
||||||
/// <!-- aidoc-review:v1 severity=medium kind=extra_exception
|
/// <!-- aidoc:v1 sig=ccc34cb body=71837cc -->
|
||||||
/// "The method contains a catch-all for Exception and never lets it propagate to callers, so documenting <exception cref=\"Exception\"> is misleading; the tag should be removed since no exception escapes this method." -->
|
|
||||||
public async Task<OptionList?> FindOptionItemById(ObjectId masterId, ObjectId optionId)
|
public async Task<OptionList?> FindOptionItemById(ObjectId masterId, ObjectId optionId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -385,17 +374,12 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds a master list entity by its ID with optional locale translation for options.
|
/// Asynchronously finds an entity of type <typeparamref name="T"/> by its MongoDB <see cref="ObjectId"/>, executing an aggregation pipeline that unwinds the <c>options</c> array and resolves localized option names. When <paramref name="locale"/> is <c>null</c> or <see cref="LocaleEnum.Default"/>, the original option name is returned; otherwise, the option name is translated using the supplied locale, falling back to the original name when no matching <c>localeItems</c> entry exists. Returns <c>null</c> when no document is found or when an exception is caught and logged.
|
||||||
/// Uses MongoDB aggregation to unwind options and apply translations.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The ObjectId of the entity to retrieve.</param>
|
/// <param name="id">The MongoDB <see cref="ObjectId"/> that uniquely identifies the document to retrieve.</param>
|
||||||
/// <param name="locale">Optional locale for translated option names.</param>
|
/// <param name="locale">The optional <see cref="LocaleEnum"/> used to resolve translated option names; pass <c>null</c> or <see cref="LocaleEnum.Default"/> to use the default (non-translated) names.</param>
|
||||||
/// <returns>The MasterList entity with translated options if found; otherwise, null.</returns>
|
/// <returns>A <see cref="Task{T}"/> that yields the matched entity of type <typeparamref name="T"/>, or <c>null</c> if no document matches <paramref name="id"/> or if an error occurs during aggregation.</returns>
|
||||||
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
|
/// <!-- aidoc:v1 sig=cb4a658 body=f2e88d8 -->
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=extra_exception
|
|
||||||
/// "The <exception cref=\"Exception\"> tag is misleading: the catch block handles all exceptions internally and the method returns null, so no Exception is ever thrown to callers. Listing Exception implies callers must handle it, which they cannot." -->
|
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=wrong_returns
|
|
||||||
/// "Documentation says 'The MasterList entity' but the method signature is generic Task<T?>, so the returned type is T, not necessarily a MasterList." -->
|
|
||||||
public async Task<T?> FindById(ObjectId id, LocaleEnum? locale)
|
public async Task<T?> FindById(ObjectId id, LocaleEnum? locale)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -573,17 +557,11 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds a master list entity by its name.
|
/// Asynchronously finds a single <typeparamref name="T"/> entity by its <see cref="T.Name"/> using a MongoDB query, projecting only the first 100 elements of the <c>options</c> field to limit payload size. Returns <c>null</c> if no matching document is found or if an error occurs during the search.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name of the master list to retrieve.</param>
|
/// <param name="name">The name value used to build the equality filter against the entity's <c>Name</c> field.</param>
|
||||||
/// <returns>The MasterList entity if found; otherwise, null.</returns>
|
/// <returns>A <see cref="Task{T}"/> that yields the matching <typeparamref name="T"/> instance, or <c>null</c> when no document matches or the operation fails.</returns>
|
||||||
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
|
/// <!-- aidoc:v1 sig=80a1541 body=8b5b558 -->
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=wrong_exception
|
|
||||||
/// "The <exception> tag documents Exception being thrown, but the method catches all exceptions internally and returns null instead of letting any exception propagate to the caller." -->
|
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=wrong_summary
|
|
||||||
/// "The summary specifies a 'master list entity', but the method is generic (T) and could operate on any type with a Name property; the documentation should not assume a specific type." -->
|
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=wrong_returns
|
|
||||||
/// "The <returns> tag describes a MasterList entity, but the method returns Task<T?> where T is a generic type parameter, not necessarily MasterList." -->
|
|
||||||
public async Task<T?> FindByName(string name)
|
public async Task<T?> FindByName(string name)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -623,10 +601,7 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filter">The pagination and filtering parameters.</param>
|
/// <param name="filter">The pagination and filtering parameters.</param>
|
||||||
/// <returns>A fluent queryable for MasterList results.</returns>
|
/// <returns>A fluent queryable for MasterList results.</returns>
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=wrong_summary
|
/// <!-- aidoc:v1 sig=bbb39c7 body=f42ebaa -->
|
||||||
/// "Summary claims the method 'retrieves paginated master lists', but the code only builds filters/sort and returns an IFindFluent without any pagination logic (no Skip/Limit)." -->
|
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=wrong_returns
|
|
||||||
/// "Returns tag specifies 'MasterList results', but the method is generic and returns IFindFluent<T, T>, not specifically MasterList." -->
|
|
||||||
public IFindFluent<T, T> GetPaginatedMasterList(PaginationFilter filter)
|
public IFindFluent<T, T> GetPaginatedMasterList(PaginationFilter filter)
|
||||||
{
|
{
|
||||||
var filterBuilder = Builders<T>.Filter;
|
var filterBuilder = Builders<T>.Filter;
|
||||||
@@ -649,13 +624,12 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves paginated options within a master list with optional text filtering.
|
/// Retrieves the options belonging to the master list identified by <paramref name="listId"/>, optionally narrowed down by the text contained in <paramref name="filter"/>. Returns an empty collection when no master list exists for the given identifier, and when a non-empty filter text is supplied, applies a case-insensitive match against each option's name, description, or option type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filter">The pagination and filtering parameters.</param>
|
/// <param name="filter">The <see cref="PaginationFilter"/> that provides the optional text used to filter the returned options.</param>
|
||||||
/// <param name="listId">The ObjectId of the master list.</param>
|
/// <param name="listId">The <see cref="ObjectId"/> of the master list whose options should be retrieved.</param>
|
||||||
/// <returns>A list of filtered OptionList items.</returns>
|
/// <returns>A <see cref="Task"/> that resolves to a <see cref="List{OptionList}"/> of options matching the filter, or an empty list when the master list cannot be found.</returns>
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=wrong_summary
|
/// <!-- aidoc:v1 sig=2ac8d75 body=b25fb18 -->
|
||||||
/// "Summary and method name claim 'paginated' results, but the code applies only text filtering with no skip/take/page logic — the PaginationFilter's pagination fields are unused." -->
|
|
||||||
public async Task<List<OptionList>> GetPaginatedOptions(PaginationFilter filter, ObjectId listId)
|
public async Task<List<OptionList>> GetPaginatedOptions(PaginationFilter filter, ObjectId listId)
|
||||||
{
|
{
|
||||||
//TODO: LOCALE
|
//TODO: LOCALE
|
||||||
@@ -675,14 +649,12 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a new option to a master list.
|
/// Adds a new <see cref="OptionList"/> entry to the master list identified by <paramref name="id"/>. Returns the created option on success, or <c>null</c> if a matching option already exists, the master list cannot be retrieved, the update does not modify any document, or the operation throws an exception that is logged.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The ObjectId of the master list.</param>
|
/// <param name="id">The <see cref="ObjectId"/> of the master list to update.</param>
|
||||||
/// <param name="opt">The option element to add.</param>
|
/// <param name="opt">The <see cref="FilterOptionListElement"/> describing the option to add; its locale items are reused when <see cref="FilterOptionListElement.IsFromAdmPanel"/> is <c>true</c>, otherwise new locale entries are generated from the master list's <see cref="LocaleEnum"/> default.</param>
|
||||||
/// <returns>The newly created OptionList if successful; otherwise, null if duplicate exists.</returns>
|
/// <returns>A <see cref="Task{OptionList}"/> that resolves to the newly created <see cref="OptionList"/> when the push update succeeds, or <c>null</c> when the operation is skipped, no document is modified, or the update fails.</returns>
|
||||||
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
|
/// <!-- aidoc:v1 sig=1602f0d body=e7fd138 -->
|
||||||
/// <!-- aidoc-review:v1 severity=medium kind=extra_exception
|
|
||||||
/// "The method catches all exceptions internally and returns null; it never throws an Exception to the caller, so documenting <exception cref=\"Exception\"> is misleading." -->
|
|
||||||
public async Task<OptionList?> AddOptionToMasterList(ObjectId id, FilterOptionListElement opt)
|
public async Task<OptionList?> AddOptionToMasterList(ObjectId id, FilterOptionListElement opt)
|
||||||
{
|
{
|
||||||
var exist = await GetMasterListByIdAndSearchOptions(id, opt);
|
var exist = await GetMasterListByIdAndSearchOptions(id, opt);
|
||||||
@@ -722,16 +694,11 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves all master list entities.
|
/// Asynchronously retrieves all entities from the underlying collection.
|
||||||
|
/// If an exception occurs during retrieval, the error is logged and an empty collection is returned rather than propagating the failure.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>An enumerable of all MasterList entities.</returns>
|
/// <returns>A <see cref="Task"/> that yields an <see cref="IEnumerable{T}"/> containing all retrieved entities, or an empty collection if the operation fails.</returns>
|
||||||
/// <exception cref="Exception">Logs errors and returns empty list on failure.</exception>
|
/// <!-- aidoc:v1 sig=3a61c61 body=40d7129 -->
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=wrong_summary
|
|
||||||
/// "Summary says 'Retrieves all master list entities' but the method is generic over T and operates on the configured Collection, not specifically MasterList entities." -->
|
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=wrong_returns
|
|
||||||
/// "Returns tag says 'An enumerable of all MasterList entities' but the method returns Task<IEnumerable<T>>, where T is a generic type parameter." -->
|
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=extra_exception
|
|
||||||
/// "<exception cref=\"Exception\"> implies the method may throw Exception, but the catch block swallows all exceptions and returns an empty list; the method does not throw Exception." -->
|
|
||||||
public async Task<IEnumerable<T>> GetAll()
|
public async Task<IEnumerable<T>> GetAll()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -747,12 +714,10 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves all master list entities without options, returning only metadata.
|
/// Retrieves all master list entries from the underlying collection without applying any filter and projects each result into a <see cref="MasterListDto"/>, where the <see cref="MasterListDto.Options"/> property is set to the count of associated options rather than the option list itself.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>An enumerable of MasterListDto containing id, name, description, listType, and options count.</returns>
|
/// <returns>A <see cref="Task"/> that resolves to an <see cref="IEnumerable{T}"/> containing the projected <see cref="MasterListDto"/> instances, or an empty collection if an error is encountered while querying the data store.</returns>
|
||||||
/// <exception cref="Exception">Logs errors and returns empty list on failure.</exception>
|
/// <!-- aidoc:v1 sig=e493435 body=1c299ba -->
|
||||||
/// <!-- aidoc-review:v1 severity=medium kind=extra_exception
|
|
||||||
/// "The method catches all exceptions internally (try/catch around the entire body) and returns an empty list; it never throws Exception to the caller, so the <exception cref=\"Exception\"> tag is misleading." -->
|
|
||||||
public async Task<IEnumerable<MasterListDto>> GetAllWithoutOptions()
|
public async Task<IEnumerable<MasterListDto>> GetAllWithoutOptions()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -777,12 +742,13 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Counts the total number of master list entities in the collection.
|
/// Asynchronously counts all entities in the underlying <see cref="Collection"/> by invoking
|
||||||
|
/// <c>CountDocumentsAsync</c> with a filter that matches every document. If the operation fails,
|
||||||
|
/// the exception is logged via <c>Log.Error</c> and the method returns <c>0</c> as a safe fallback.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The total count of entities.</returns>
|
/// <returns>A <see cref="Task{Int32}"/> that represents the asynchronous count operation. The result
|
||||||
/// <exception cref="Exception">Logs errors and returns 0 on failure.</exception>
|
/// is the total number of entities, or <c>0</c> if an error occurred while querying the collection.</returns>
|
||||||
/// <!-- aidoc-review:v1 severity=medium kind=wrong_exception
|
/// <!-- aidoc:v1 sig=f1f0a98 body=01cd3d1 -->
|
||||||
/// "The method catches all Exception internally and returns 0, so it never throws Exception to callers. The <exception cref=\"Exception\"> tag misleads readers into expecting exceptions to propagate." -->
|
|
||||||
public async Task<int> Count()
|
public async Task<int> Count()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -798,15 +764,12 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Searches for options within a master list using multiple filter criteria.
|
/// Retrieves the options of a master list identified by <paramref name="id"/>, applying the optional <paramref name="filters"/> to restrict the results by option type, text, name, or description, and resolving the localized <c>name</c> and <c>description</c> fields for each option based on the requested locale. When <paramref name="filters"/> is <c>null</c>, no options are returned. Text searches are performed case-insensitively against both the name and description of each option using a normalized pattern, and the final result is ordered alphabetically by name. Any exception during execution is logged and an empty list is returned.
|
||||||
/// Uses MongoDB aggregation pipeline to apply filters and locale translations.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The ObjectId of the master list.</param>
|
/// <param name="id">The <see cref="ObjectId"/> of the master list whose options will be queried.</param>
|
||||||
/// <param name="filters">The filter criteria including text, name, description, and optionType.</param>
|
/// <param name="filters">Optional <see cref="FilterOptionListElement"/> containing the search criteria (option type, text, name, description, and locale). When <c>null</c>, the method returns an empty list.</param>
|
||||||
/// <returns>A list of matching OptionList items ordered by name.</returns>
|
/// <returns>A <see cref="Task{List{OptionList}}"/> containing the filtered, localized, and alphabetically ordered options; an empty list is returned when no filters are provided, no options match, or an error occurs.</returns>
|
||||||
/// <exception cref="Exception">Logs errors and returns empty list on failure.</exception>
|
/// <!-- aidoc:v1 sig=f6ee5be body=17d6a96 -->
|
||||||
/// <!-- aidoc-review:v1 severity=medium kind=extra_exception
|
|
||||||
/// "The method catches all Exception instances and logs them without rethrowing, so no Exception is propagated to the caller; documenting <exception cref=\"Exception\"> is misleading." -->
|
|
||||||
public async Task<List<OptionList>> GetMasterListByIdAndSearchOptions(ObjectId id, FilterOptionListElement? filters)
|
public async Task<List<OptionList>> GetMasterListByIdAndSearchOptions(ObjectId id, FilterOptionListElement? filters)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -1015,15 +978,13 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates a specific option within a master list with locale-aware field updates.
|
/// Updates an option within a master list, handling duplicate detection, locale-specific translations, and conditional field updates based on the master list's required field rules.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The ObjectId of the master list.</param>
|
/// <param name="id">The <see cref="MongoDB.Bson.ObjectId"/> of the master list that contains the option to update.</param>
|
||||||
/// <param name="newOpt">The OptionList with updated values.</param>
|
/// <param name="newOpt">The <see cref="OptionList"/> containing the new values to apply to the existing option.</param>
|
||||||
/// <param name="locale">The locale for translation updates.</param>
|
/// <param name="locale">The <see cref="LocaleEnum"/> indicating which locale the <paramref name="newOpt"/> applies to; when it matches the master's default locale, the main field is updated, otherwise the translation stored in <c>localeItems</c> is updated.</param>
|
||||||
/// <returns>The updated OptionList if successful; otherwise, null.</returns>
|
/// <returns>A <see cref="Task{T}"/> containing the updated <see cref="OptionList"/>, the existing option when a duplicate is detected, or <c>null</c> when the option is not found, no fields qualify for update, or the update operation fails.</returns>
|
||||||
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
|
/// <!-- aidoc:v1 sig=e771f23 body=5520a4a -->
|
||||||
/// <!-- aidoc-review:v1 severity=medium kind=extra_exception
|
|
||||||
/// "The method catches all exceptions internally and returns null; it does not propagate any exception to callers, so the <exception cref=\"Exception\"/> tag is misleading." -->
|
|
||||||
public async Task<OptionList?> UpdateMasterListOption(ObjectId id, OptionList newOpt, LocaleEnum locale)
|
public async Task<OptionList?> UpdateMasterListOption(ObjectId id, OptionList newOpt, LocaleEnum locale)
|
||||||
{
|
{
|
||||||
// 1. Evitar duplicados
|
// 1. Evitar duplicados
|
||||||
@@ -1118,14 +1079,12 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates a specific option within a master list with full replacement.
|
/// Updates an existing option inside the master list identified by <paramref name="id"/> with the data from <paramref name="newOpt"/>. Returns <see langword="null"/> when the master list is not found, when no document is modified, or when an exception is caught and logged.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The ObjectId of the master list.</param>
|
/// <param name="id">The <see cref="ObjectId"/> of the master list that owns the option to update.</param>
|
||||||
/// <param name="newOpt">The OptionList with updated values.</param>
|
/// <param name="newOpt">The <see cref="OptionList"/> whose <see cref="ObjectId"/> targets the option to replace and whose values are persisted to the document.</param>
|
||||||
/// <returns>The updated OptionList if successful; otherwise, null.</returns>
|
/// <returns>A task containing the updated <see cref="OptionList"/> when the update succeeds, or <see langword="null"/> when the master list is missing, nothing was modified, or the update failed.</returns>
|
||||||
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
|
/// <!-- aidoc:v1 sig=6def54c body=9554543 -->
|
||||||
/// <!-- aidoc-review:v1 severity=medium kind=extra_exception
|
|
||||||
/// "The <exception cref=\"Exception\"> tag is misleading because the method catches Exception rather than throwing it. The described behavior (log and return null) matches the catch block, but this is not an exception thrown by the method." -->
|
|
||||||
public async Task<OptionList?> UpdateMasterListOption(ObjectId id, OptionList newOpt)
|
public async Task<OptionList?> UpdateMasterListOption(ObjectId id, OptionList newOpt)
|
||||||
{
|
{
|
||||||
var master = await FindById(id);
|
var master = await FindById(id);
|
||||||
@@ -1180,16 +1139,12 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the metadata details for a master list.
|
/// Updates the option details of a master list document identified by <paramref name="id"/>, persisting only the non-null fields supplied in <paramref name="opt"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The ObjectId of the master list.</param>
|
/// <param name="id">The <see cref="ObjectId"/> of the master list entry to update.</param>
|
||||||
/// <param name="opt">The UpdateMasterListDetailsDto with updated values.</param>
|
/// <param name="opt">The <see cref="UpdateMasterListDetailsDto"/> whose non-null <see cref="UpdateMasterListDetailsDto.CanAddElement"/> and <see cref="UpdateMasterListDetailsDto.OptionListDetails"/> values are written to the document.</param>
|
||||||
/// <returns>The updated UpdateMasterListDetailsDto if successful; otherwise, null.</returns>
|
/// <returns>The supplied <see cref="UpdateMasterListDetailsDto"/> when at least one field is modified, or <see langword="null"/> when the document is not found, no changes are applied, or the operation fails.</returns>
|
||||||
/// <exception cref="Exception">Logs errors and returns null on failure.</exception>
|
/// <!-- aidoc:v1 sig=53d4179 body=49f50ca -->
|
||||||
/// <!-- aidoc-review:v1 severity=medium kind=extra_exception
|
|
||||||
/// "The method catches all exceptions internally and never throws them, so documenting <exception cref=\"Exception\"> is misleading" -->
|
|
||||||
/// <!-- aidoc-review:v1 severity=medium kind=wrong_summary
|
|
||||||
/// "Summary says 'metadata details' but the method updates option details (CanAddElement, OptionListDetails)" -->
|
|
||||||
public async Task<UpdateMasterListDetailsDto?> UpdateOptionDetailsToMasterList(ObjectId id,
|
public async Task<UpdateMasterListDetailsDto?> UpdateOptionDetailsToMasterList(ObjectId id,
|
||||||
UpdateMasterListDetailsDto opt)
|
UpdateMasterListDetailsDto opt)
|
||||||
{
|
{
|
||||||
@@ -1219,14 +1174,13 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the name of a master list.
|
/// Updates the name of the master list document identified by <paramref name="id"/>.
|
||||||
|
/// Returns <c>true</c> if a document was modified, <c>false</c> if no document matched the id or if an exception was caught and logged.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The ObjectId of the master list.</param>
|
/// <param name="id">The <see cref="MongoDB.Bson.ObjectId"/> of the master list entry to update.</param>
|
||||||
/// <param name="name">The new name.</param>
|
/// <param name="name">The new name to assign to the entry.</param>
|
||||||
/// <returns>True if the update was successful; otherwise, false.</returns>
|
/// <returns>A <see cref="Task{Boolean}"/> that resolves to <c>true</c> when the update modified a document; otherwise, <c>false</c>.</returns>
|
||||||
/// <exception cref="Exception">Logs errors and returns false on failure.</exception>
|
/// <!-- aidoc:v1 sig=5b79a51 body=36dfe99 -->
|
||||||
/// <!-- aidoc-review:v1 severity=medium kind=extra_exception
|
|
||||||
/// "The method catches all exceptions internally and never rethrows them, so <exception cref=\"Exception\"> is misleading; callers cannot actually receive an exception from this method." -->
|
|
||||||
public async Task<bool> UpdateMasterListName(ObjectId id, string name)
|
public async Task<bool> UpdateMasterListName(ObjectId id, string name)
|
||||||
{
|
{
|
||||||
var filter = Builders<T>.Filter.And(
|
var filter = Builders<T>.Filter.And(
|
||||||
@@ -1247,14 +1201,13 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the description of a master list.
|
/// Updates the <see cref="T.Description"/> field of the master list entry identified by <paramref name="id"/>.
|
||||||
|
/// Returns <c>true</c> when the matched document is modified, and <c>false</c> when no document matches the filter or the underlying update operation throws, in which case the exception is logged and the call resolves to <c>false</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The ObjectId of the master list.</param>
|
/// <param name="id">The <see cref="ObjectId"/> of the master list entry to update.</param>
|
||||||
/// <param name="description">The new description.</param>
|
/// <param name="description">The new description value to set on the entry.</param>
|
||||||
/// <returns>True if the update was successful; otherwise, false.</returns>
|
/// <returns>A <see cref="Task{Boolean}"/> that yields <c>true</c> if the update modified a document; otherwise, <c>false</c>.</returns>
|
||||||
/// <exception cref="Exception">Logs errors and returns false on failure.</exception>
|
/// <!-- aidoc:v1 sig=5a27a55 body=3552f46 -->
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=extra_exception
|
|
||||||
/// "The method catches all exceptions internally and returns false; it never throws an Exception, so the <exception cref=\"Exception\"> tag is incorrect." -->
|
|
||||||
public async Task<bool> UpdateMasterListDescription(ObjectId id, string description)
|
public async Task<bool> UpdateMasterListDescription(ObjectId id, string description)
|
||||||
{
|
{
|
||||||
var filter = Builders<T>.Filter.And(
|
var filter = Builders<T>.Filter.And(
|
||||||
@@ -1281,8 +1234,7 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
/// <param name="oldOpt">The OptionList to remove.</param>
|
/// <param name="oldOpt">The OptionList to remove.</param>
|
||||||
/// <returns>True if the option was removed; otherwise, false.</returns>
|
/// <returns>True if the option was removed; otherwise, false.</returns>
|
||||||
/// <exception cref="Exception">Logs errors and returns false on failure.</exception>
|
/// <exception cref="Exception">Logs errors and returns false on failure.</exception>
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=extra_exception
|
/// <!-- aidoc:v1 sig=8de50cd body=d974b9b -->
|
||||||
/// "The method catches all exceptions in a try/catch and returns false; it does not throw Exception, so the <exception> tag is misleading." -->
|
|
||||||
public async Task<bool> RemoveMasterListOption(ObjectId id, OptionList oldOpt)
|
public async Task<bool> RemoveMasterListOption(ObjectId id, OptionList oldOpt)
|
||||||
{
|
{
|
||||||
var filter = Builders<T>.Filter.Eq("_id", id);
|
var filter = Builders<T>.Filter.Eq("_id", id);
|
||||||
@@ -1311,11 +1263,12 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates necessary indexes for the MasterList collection.
|
/// Creates background MongoDB indexes on the <see cref="DiagnosisList"/> collection to optimize queries against the nested <c>options</c> document fields, configuring the indexes with <c>spanish</c> as both the default and override language for text indexing.
|
||||||
/// Currently creates text indexes for DiagnosisList on options.name, options.description, and options._id.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <!-- aidoc-review:v1 severity=high kind=wrong_summary
|
/// <remarks>
|
||||||
/// "Documentation claims 'text indexes' are created, but the code uses Builders<T>.IndexKeys.Ascending(), which creates ascending (non-text) indexes." -->
|
/// When the generic type parameter is not <see cref="DiagnosisList"/>, the method performs no actions and returns without creating any indexes.
|
||||||
|
/// </remarks>
|
||||||
|
/// <!-- aidoc:v1 sig=4955da2 body=2c8d1f7 -->
|
||||||
public override async Task CreateIndexes()
|
public override async Task CreateIndexes()
|
||||||
{
|
{
|
||||||
if (typeof(T) == typeof(DiagnosisList))
|
if (typeof(T) == typeof(DiagnosisList))
|
||||||
@@ -1354,8 +1307,7 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
/// <param name="id">Optional master list ObjectId to filter results.</param>
|
/// <param name="id">Optional master list ObjectId to filter results.</param>
|
||||||
/// <returns>A list of matching OptionList items ordered by name.</returns>
|
/// <returns>A list of matching OptionList items ordered by name.</returns>
|
||||||
/// <exception cref="Exception">Logs errors and returns empty list on failure.</exception>
|
/// <exception cref="Exception">Logs errors and returns empty list on failure.</exception>
|
||||||
/// <!-- aidoc-review:v1 severity=medium kind=extra_exception
|
/// <!-- aidoc:v1 sig=6ebeac8 body=1d7bbb7 -->
|
||||||
/// "The method catches all exceptions internally (catch (Exception ex)) and never throws to the caller; the <exception cref=\"Exception\"> tag is misleading and should be removed. The catch-block description belongs in the summary or remarks instead." -->
|
|
||||||
private async Task<List<OptionList>> GetOptionsByTextSearch(string? textSearch, ObjectId? id = null)
|
private async Task<List<OptionList>> GetOptionsByTextSearch(string? textSearch, ObjectId? id = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -1428,11 +1380,11 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a fluent query for paginated results with combined filters.
|
/// Creates a find query against the underlying collection by combining the provided <paramref name="filters"/> with a logical AND and applying the specified <paramref name="sort"/>. When the filter list is empty, an empty filter is used so that the query matches every document.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filters">List of filter definitions to apply.</param>
|
/// <param name="filters">The list of <see cref="FilterDefinition{T}"/> predicates to combine; an empty list causes no filtering to be applied.</param>
|
||||||
/// <param name="sort">Sort definition for the query results.</param>
|
/// <param name="sort">The <see cref="SortDefinition{T}"/> used to order the returned documents.</param>
|
||||||
/// <returns>A fluent queryable for T results.</returns>
|
/// <returns>An <see cref="IFindFluent{T,T}"/> configured with the combined filter and sort, ready for further chaining.</returns>
|
||||||
/// <!-- aidoc:v1 sig=704d715 body=2a35652 -->
|
/// <!-- aidoc:v1 sig=704d715 body=2a35652 -->
|
||||||
private IFindFluent<T, T> CreateFindFluent(List<FilterDefinition<T>> filters, SortDefinition<T> sort)
|
private IFindFluent<T, T> CreateFindFluent(List<FilterDefinition<T>> filters, SortDefinition<T> sort)
|
||||||
{
|
{
|
||||||
@@ -1449,8 +1401,7 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
/// <param name="localeList">The default locale to exclude from translations.</param>
|
/// <param name="localeList">The default locale to exclude from translations.</param>
|
||||||
/// <param name="opt">The option name to use as default translation.</param>
|
/// <param name="opt">The option name to use as default translation.</param>
|
||||||
/// <returns>A Locale object with translations for all other locales.</returns>
|
/// <returns>A Locale object with translations for all other locales.</returns>
|
||||||
/// <!-- aidoc-review:v1 severity=medium kind=stale_summary
|
/// <!-- aidoc:v1 sig=147db59 body=9b63847 -->
|
||||||
/// "Summary says items are created for all locales except 'the specified default', but the code also always skips LocaleEnum.Default in addition to the localeList parameter; only one exclusion is documented when two occur." -->
|
|
||||||
private Locale GetNewItemLocale(LocaleEnum localeList, string opt)
|
private Locale GetNewItemLocale(LocaleEnum localeList, string opt)
|
||||||
{
|
{
|
||||||
var newLocale = new Locale();
|
var newLocale = new Locale();
|
||||||
@@ -1524,16 +1475,13 @@ public class MasterListRepository<T> : MongoRepository<T>, IMasterListRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Searches for options within a master list by name with locale translation.
|
/// Asynchronously retrieves a filtered list of <see cref="OptionList"/> entries from the master collection by matching the document with the specified <paramref name="id"/> and filtering options whose resolved name equals <paramref name="newOptName"/> for the given <paramref name="locale"/>. When the requested locale differs from the document's default, the pipeline uses the translated name from <c>localeItems</c>, falling back to the original name if no translation is available. Returns an empty list if no matching options are found or if an exception is logged during execution.
|
||||||
/// Uses MongoDB aggregation pipeline to apply locale-aware filtering.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">The ObjectId of the master list.</param>
|
/// <param name="id">The <see cref="ObjectId"/> identifying the master document to aggregate.</param>
|
||||||
/// <param name="newOptName">The option name to search for.</param>
|
/// <param name="newOptName">The option name used to filter the options after locale-based name resolution.</param>
|
||||||
/// <param name="locale">The locale for translation.</param>
|
/// <param name="locale">The <see cref="LocaleEnum"/> value used to select the translated name for each option.</param>
|
||||||
/// <returns>A list of matching OptionList items ordered by name.</returns>
|
/// <returns>A <see cref="Task"/> containing a <see cref="List{T}"/> of <see cref="OptionList"/> entries ordered by name, or an empty list when no match exists or an error is caught.</returns>
|
||||||
/// <exception cref="Exception">Logs errors and returns empty list on failure.</exception>
|
/// <!-- aidoc:v1 sig=342dc63 body=21f31fc -->
|
||||||
/// <!-- aidoc-review:v1 severity=medium kind=extra_exception
|
|
||||||
/// "The <exception cref=\"Exception\"> tag documents an exception that is never thrown to the caller: the method catches all exceptions internally, logs them, and returns an empty list. The descriptive text 'Logs errors and returns empty list on failure' is accurate but should not be tagged as an exception." -->
|
|
||||||
private async Task<List<OptionList>> GetMasterListByIdAndTextSearch(
|
private async Task<List<OptionList>> GetMasterListByIdAndTextSearch(
|
||||||
ObjectId id, string newOptName, LocaleEnum locale)
|
ObjectId id, string newOptName, LocaleEnum locale)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,8 +14,6 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="5.3.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.9" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.9" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.9" />
|
||||||
<PackageReference Include="MongoMigrations.Core" Version="4.0.15" />
|
<PackageReference Include="MongoMigrations.Core" Version="4.0.15" />
|
||||||
<PackageReference Include="StackExchange.Redis" Version="3.0.0" />
|
<PackageReference Include="StackExchange.Redis" Version="3.0.0" />
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ using adas_core.Domain.Models.MongoModels;
|
|||||||
using adas_core.LdapLogin.Configuration;
|
using adas_core.LdapLogin.Configuration;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.HttpResults;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using MongoDB.Bson;
|
using MongoDB.Bson;
|
||||||
using Novell.Directory.Ldap;
|
using System.DirectoryServices.Protocols;
|
||||||
|
using System.Net;
|
||||||
|
using Authorization = adas_core.Domain.Models.MongoModels.Authorization;
|
||||||
|
|
||||||
namespace adas_core.LdapLogin;
|
namespace adas_core.LdapLogin;
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ namespace adas_core.LdapLogin;
|
|||||||
/// The service also handles the creation of new users in the application if they do not already exist, based on the LDAP information.
|
/// The service also handles the creation of new users in the application if they do not already exist, based on the LDAP information.
|
||||||
/// It uses configuration settings for connecting to the LDAP server and for mapping LDAP attributes to user properties and authorities.
|
/// It uses configuration settings for connecting to the LDAP server and for mapping LDAP attributes to user properties and authorities.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <!-- aidoc:v1 sig=4e5637f -->
|
||||||
public class LdapLoginService : ILoginService
|
public class LdapLoginService : ILoginService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -50,6 +52,7 @@ public class LdapLoginService : ILoginService
|
|||||||
/// <param name="userService">The user service for managing user information.</param>
|
/// <param name="userService">The user service for managing user information.</param>
|
||||||
/// <param name="authorityService">The authority service for managing user authorities.</param>
|
/// <param name="authorityService">The authority service for managing user authorities.</param>
|
||||||
/// <param name="logger">The logger for logging LDAP login operations.</param>
|
/// <param name="logger">The logger for logging LDAP login operations.</param>
|
||||||
|
/// <!-- aidoc:v1 sig=f1b8e4e body=6c4718f -->
|
||||||
public LdapLoginService(
|
public LdapLoginService(
|
||||||
IOptions<LdapConfig> ldapConfig,
|
IOptions<LdapConfig> ldapConfig,
|
||||||
IValidator<LdapConfig> validator,
|
IValidator<LdapConfig> validator,
|
||||||
@@ -82,26 +85,21 @@ public class LdapLoginService : ILoginService
|
|||||||
/// <returns>The authenticated user.</returns>
|
/// <returns>The authenticated user.</returns>
|
||||||
/// <exception cref="LoginServicesException">Thrown when there is an error during the login process.</exception>
|
/// <exception cref="LoginServicesException">Thrown when there is an error during the login process.</exception>
|
||||||
/// <exception cref="UserNotFoundException">Thrown when the user is not found in the LDAP directory.</exception>
|
/// <exception cref="UserNotFoundException">Thrown when the user is not found in the LDAP directory.</exception>
|
||||||
|
/// <!-- aidoc:v1 sig=bdfb451 body=e3fc1ac -->
|
||||||
public async Task<User> Login(string username, string password)
|
public async Task<User> Login(string username, string password)
|
||||||
{
|
{
|
||||||
// Check for LDAP config
|
if (_ldapConfig.Server == null)
|
||||||
if (_ldapConfig.Server == null) throw new LoginServicesException("LDAP Config not found");
|
throw new LoginServicesException("LDAP Config not found");
|
||||||
var conn = new LdapConnection();
|
|
||||||
|
var identifier = new LdapDirectoryIdentifier(_ldapConfig.Server, _ldapConfig.Port ?? 389);
|
||||||
|
var connection = new LdapConnection(identifier);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await conn.ConnectAsync(_ldapConfig.Server, _ldapConfig.Port ?? 389);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.LogError("[LDAP] Error connecting to {server}, port {port}, Exception: {e}", _ldapConfig.Server,
|
|
||||||
_ldapConfig.Port, e.Message);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_ldapConfig.LdapUser != null)
|
if (_ldapConfig.LdapUser != null)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("[LDAP] _ldapConfig.LdapUser is enabled with {LdapUser}", _ldapConfig.LdapUser);
|
_logger.LogInformation("[LDAP] Using configured LDAP user {LdapUser}", _ldapConfig.LdapUser);
|
||||||
await conn.BindAsync(_ldapConfig.LdapUser, _ldapConfig.LdapPassword);
|
connection.Credential = new NetworkCredential(_ldapConfig.LdapUser, _ldapConfig.LdapPassword);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -109,56 +107,52 @@ public class LdapLoginService : ILoginService
|
|||||||
? _ldapConfig.UserDomainName + @"\"
|
? _ldapConfig.UserDomainName + @"\"
|
||||||
: "") + username;
|
: "") + username;
|
||||||
|
|
||||||
try
|
connection.Credential = new NetworkCredential(ldapUser, password);
|
||||||
{
|
}
|
||||||
await conn.BindAsync(ldapUser, password);
|
|
||||||
|
connection.AuthType = AuthType.Basic;
|
||||||
|
connection.Bind();
|
||||||
}
|
}
|
||||||
catch (LdapException e)
|
catch (LdapException e)
|
||||||
{
|
{
|
||||||
_logger.LogError("[LDAP] Error binding ldapUser: {LdapUser} and password", ldapUser);
|
_logger.LogError("[LDAP] Error binding user {username}", username);
|
||||||
|
|
||||||
throw new UserNotFoundException(username, e);
|
throw new UserNotFoundException(username, e);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var results = await conn.SearchAsync(
|
SearchResultEntry? entry = null;
|
||||||
_ldapConfig.SearchBase,
|
|
||||||
LdapConnection.ScopeSub,
|
|
||||||
$"({_ldapConfig.UserNameProperty}={username})",
|
|
||||||
null,
|
|
||||||
false);
|
|
||||||
|
|
||||||
|
|
||||||
LdapEntry? entry = null;
|
|
||||||
|
|
||||||
while (await results.HasMoreAsync())
|
|
||||||
{
|
|
||||||
LdapEntry? current = null;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
current = await results.NextAsync();
|
var request = new SearchRequest(
|
||||||
}
|
_ldapConfig.SearchBase,
|
||||||
catch (LdapException ex)
|
$"({_ldapConfig.UserNameProperty}={username})",
|
||||||
{
|
SearchScope.Subtree
|
||||||
_logger.LogWarning("[LDAP] Skipping invalid entry: {error}", ex.Message);
|
);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current != null)
|
var response = (SearchResponse)connection.SendRequest(request);
|
||||||
|
|
||||||
|
foreach (SearchResultEntry current in response.Entries)
|
||||||
{
|
{
|
||||||
entry = current;
|
entry = current;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError("[LDAP] Search error for user {username}: {error}", username, e.Message);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry == null)
|
||||||
|
throw new LoginServicesException("LDAP User not found");
|
||||||
|
|
||||||
if (entry == null) throw new LoginServicesException("LDAP User not found");
|
|
||||||
var userEntryLdap = GetUser(entry);
|
var userEntryLdap = GetUser(entry);
|
||||||
var user = await GetOrCreateUser(userEntryLdap, entry);
|
var user = await GetOrCreateUser(userEntryLdap, entry);
|
||||||
_logger.LogInformation("[LDAP] entry is {entry} and user {user}", entry, user);
|
|
||||||
// user.Authorization.AddRange(GetAuthorities(entry));
|
_logger.LogInformation("[LDAP] entry found and user {user}", user);
|
||||||
conn.Disconnect();
|
|
||||||
|
connection.Dispose();
|
||||||
|
|
||||||
return user ?? throw new LoginServicesException("LDAP User not found");
|
return user ?? throw new LoginServicesException("LDAP User not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,10 +162,9 @@ public class LdapLoginService : ILoginService
|
|||||||
/// <param name="context">The HTTP context of the request.</param>
|
/// <param name="context">The HTTP context of the request.</param>
|
||||||
/// <returns>A task representing the asynchronous operation.</returns>
|
/// <returns>A task representing the asynchronous operation.</returns>
|
||||||
/// <exception cref="LoginServicesException">Thrown when the method is not implemented.</exception>
|
/// <exception cref="LoginServicesException">Thrown when the method is not implemented.</exception>
|
||||||
|
/// <!-- aidoc:v1 sig=851dc90 -->
|
||||||
public Task<User> Login(HttpContext context)
|
public Task<User> Login(HttpContext context)
|
||||||
{
|
=> throw new LoginServicesException("Not implemented");
|
||||||
throw new LoginServicesException("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method is not implemented in the LdapLoginService, as the authentication process is handled through the Login(string username, string password) method.
|
/// This method is not implemented in the LdapLoginService, as the authentication process is handled through the Login(string username, string password) method.
|
||||||
@@ -180,10 +173,9 @@ public class LdapLoginService : ILoginService
|
|||||||
/// <param name="password">The password of the user to authenticate.</param>
|
/// <param name="password">The password of the user to authenticate.</param>
|
||||||
/// <returns>A task representing the asynchronous operation.</returns>
|
/// <returns>A task representing the asynchronous operation.</returns>
|
||||||
/// <exception cref="LoginServicesException">Thrown when the method is not implemented.</exception>
|
/// <exception cref="LoginServicesException">Thrown when the method is not implemented.</exception>
|
||||||
|
/// <!-- aidoc:v1 sig=a0b1f46 -->
|
||||||
public Task<User> Authenticate(string username, string password)
|
public Task<User> Authenticate(string username, string password)
|
||||||
{
|
=> throw new LoginServicesException("Not implemented");
|
||||||
throw new LoginServicesException("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method is not implemented in the LdapLoginService, as the user retrieval process is handled through the Login(string username, string password) method and the GetOrCreateUser(User userEntryLdap, LdapEntry entry) method.
|
/// This method is not implemented in the LdapLoginService, as the user retrieval process is handled through the Login(string username, string password) method and the GetOrCreateUser(User userEntryLdap, LdapEntry entry) method.
|
||||||
@@ -191,10 +183,9 @@ public class LdapLoginService : ILoginService
|
|||||||
/// <param name="id">The ID of the user to retrieve.</param>
|
/// <param name="id">The ID of the user to retrieve.</param>
|
||||||
/// <returns>A task representing the asynchronous operation.</returns>
|
/// <returns>A task representing the asynchronous operation.</returns>
|
||||||
/// <exception cref="LoginServicesException">Thrown when the method is not implemented.</exception>
|
/// <exception cref="LoginServicesException">Thrown when the method is not implemented.</exception>
|
||||||
|
/// <!-- aidoc:v1 sig=877a869 -->
|
||||||
public Task<User?> GetById(ObjectId id)
|
public Task<User?> GetById(ObjectId id)
|
||||||
{
|
=> throw new LoginServicesException("Not implemented");
|
||||||
throw new LoginServicesException("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method is not implemented in the LdapLoginService, as the user retrieval process is handled through the Login(string username, string password) method and the GetOrCreateUser(User userEntryLdap, LdapEntry entry) method.
|
/// This method is not implemented in the LdapLoginService, as the user retrieval process is handled through the Login(string username, string password) method and the GetOrCreateUser(User userEntryLdap, LdapEntry entry) method.
|
||||||
@@ -202,31 +193,29 @@ public class LdapLoginService : ILoginService
|
|||||||
/// <param name="email">The email of the user to retrieve.</param>
|
/// <param name="email">The email of the user to retrieve.</param>
|
||||||
/// <returns>A task representing the asynchronous operation.</returns>
|
/// <returns>A task representing the asynchronous operation.</returns>
|
||||||
/// <exception cref="LoginServicesException">Thrown when the method is not implemented.</exception>
|
/// <exception cref="LoginServicesException">Thrown when the method is not implemented.</exception>
|
||||||
|
/// <!-- aidoc:v1 sig=ba92b09 -->
|
||||||
public Task<User?> GetByEmail(string email)
|
public Task<User?> GetByEmail(string email)
|
||||||
{
|
=> throw new LoginServicesException("Not implemented");
|
||||||
throw new LoginServicesException("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method is not implemented in the LdapLoginService, as the user retrieval process is handled through the Login(string username, string password) method and the GetOrCreateUser(User userEntryLdap, LdapEntry entry) method.
|
/// Retrieves a <see cref="User"/> matching the specified <paramref name="username"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="username">The username of the user to retrieve.</param>
|
/// <param name="username">The username to look up.</param>
|
||||||
/// <returns>A task representing the asynchronous operation.</returns>
|
/// <returns>A <see cref="Task{TResult}"/> that resolves to the <see cref="User"/> if found, or <see langword="null"/> when no matching user exists.</returns>
|
||||||
/// <exception cref="LoginServicesException">Thrown when the method is not implemented.</exception>
|
/// <exception cref="LoginServicesException">Thrown because the operation is not yet implemented.</exception>
|
||||||
|
/// <!-- aidoc:v1 sig=5184c30 -->
|
||||||
public Task<User?> GetByUsername(string username)
|
public Task<User?> GetByUsername(string username)
|
||||||
{
|
=> throw new LoginServicesException("Not implemented");
|
||||||
throw new LoginServicesException("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method is not implemented in the LdapLoginService, as the user retrieval process is handled through the Login(string username, string password) method and the GetOrCreateUser(User userEntryLdap, LdapEntry entry) method.
|
/// Retrieves all <see cref="User"/> entities from the system. This method is not yet implemented and currently throws a <see cref="LoginServicesException"/> when invoked.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A task representing the asynchronous operation.</returns>
|
/// <returns>A <see cref="Task{List{User}}"/> that represents the asynchronous operation, intended to contain the full collection of <see cref="User"/> entries.</returns>
|
||||||
/// <exception cref="LoginServicesException">Thrown when the method is not implemented.</exception>
|
/// <exception cref="LoginServicesException">Thrown unconditionally because the method has not been implemented.</exception>
|
||||||
|
/// <!-- aidoc:v1 sig=aa066d6 -->
|
||||||
public Task<List<User>> GetAllUsers()
|
public Task<List<User>> GetAllUsers()
|
||||||
{
|
=> throw new LoginServicesException("Not implemented");
|
||||||
throw new LoginServicesException("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method retrieves an existing user from the application based on the information obtained from the LDAP entry, or creates a new user if one does not already exist.
|
/// This method retrieves an existing user from the application based on the information obtained from the LDAP entry, or creates a new user if one does not already exist.
|
||||||
@@ -235,55 +224,53 @@ public class LdapLoginService : ILoginService
|
|||||||
/// <param name="userEntryLdap">The user information obtained from the LDAP entry.</param>
|
/// <param name="userEntryLdap">The user information obtained from the LDAP entry.</param>
|
||||||
/// <param name="entry">The LDAP entry containing the user's information.</param>
|
/// <param name="entry">The LDAP entry containing the user's information.</param>
|
||||||
/// <returns>The existing or newly created user with updated authorities.</returns>
|
/// <returns>The existing or newly created user with updated authorities.</returns>
|
||||||
|
/// <!-- aidoc:v1 sig=87de1c2 body=a43bebd -->
|
||||||
private async Task<User?> GetOrCreateUser(User userEntryLdap, LdapEntry entry)
|
private async Task<User?> GetOrCreateUser(User userEntryLdap, LdapEntry entry)
|
||||||
|
|
||||||
{
|
{
|
||||||
var userToReturn = (await _userService.Value.GetUserByUserName(userEntryLdap.UserName) ??
|
var userToReturn = (await _userService.Value.GetUserByUserName(userEntryLdap.UserName) ??
|
||||||
await _userService.Value.GetUserByName(userEntryLdap.Name)) ??
|
await _userService.Value.GetUserByName(userEntryLdap.Name)) ??
|
||||||
await _userService.Value.CreateUser(userEntryLdap);
|
await _userService.Value.CreateUser(userEntryLdap);
|
||||||
|
|
||||||
if (userToReturn == null) return userToReturn;
|
if (userToReturn == null) return userToReturn;
|
||||||
|
|
||||||
userToReturn.Authorization = [];
|
userToReturn.Authorization = [];
|
||||||
|
|
||||||
var authorities = await CheckAuthorities(userToReturn, entry);
|
var authorities = await CheckAuthorities(userToReturn, entry);
|
||||||
|
|
||||||
userToReturn.Authorization.AddRange(authorities);
|
userToReturn.Authorization.AddRange(authorities);
|
||||||
//foreach (var authorization in userToReturn.Authorization)
|
|
||||||
//{
|
|
||||||
// Enum.TryParse<RolesType>(authorization.Rol, out var compareRole);
|
|
||||||
// if (compareRole == RolesType.Admin) userToReturn.Rol = RolesType.Admin;
|
|
||||||
//}
|
|
||||||
return userToReturn;
|
return userToReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method checks the authorities of a user based on the LDAP entry and the application's configuration.
|
/// This method checks the authorities of a user based on the LDAP entry and the application's configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="user">The user whose authorities are being checked.</param>
|
/// <param name="user">The user whose authorities are being checked.</param>
|
||||||
/// <param name="entry">The LDAP entry containing the user's information.</param>
|
/// <param name="entry">The LDAP entry containing the user's information.</param>
|
||||||
/// <returns>A list of updated authorities for the user.</returns>
|
/// <returns>A list of updated authorities for the user.</returns>
|
||||||
|
/// <!-- aidoc:v1 sig=d2bafd5 body=4af15eb -->
|
||||||
private async Task<List<Authorization>> CheckAuthorities(User user, LdapEntry entry)
|
private async Task<List<Authorization>> CheckAuthorities(User user, LdapEntry entry)
|
||||||
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var authorizationMap = GetAuthoritiesMap(entry, user);
|
var authorizationMap = GetAuthoritiesMap(entry, user);
|
||||||
var authorizationWhiteList = GetAuthoritiesWhiteList(entry, user);
|
var authorizationWhiteList = GetAuthoritiesWhiteList(entry, user);
|
||||||
_logger.LogInformation(
|
|
||||||
"[LDAP] user {user} authorizationMap count is {authorizationMap}, authorizationWhiteList count is {authorizationWhiteList}",
|
|
||||||
user.UserName, authorizationMap.Count, authorizationWhiteList.Count);
|
|
||||||
|
|
||||||
// Primero, creamos un HashSet con los DisplayID de la lista blanca para búsqueda eficiente
|
|
||||||
var whiteListDisplayIds = new HashSet<string?>(authorizationWhiteList.Select(a => a.DisplayId));
|
var whiteListDisplayIds = new HashSet<string?>(authorizationWhiteList.Select(a => a.DisplayId));
|
||||||
|
|
||||||
// Filtramos los elementos de authorizationMap que no están en la lista blanca, basándonos en DisplayID
|
var uniqueMapAuthorizations = authorizationMap
|
||||||
var uniqueMapAuthorizations = authorizationMap.Where(a => !whiteListDisplayIds.Contains(a.DisplayId));
|
.Where(a => !whiteListDisplayIds.Contains(a.DisplayId));
|
||||||
|
|
||||||
// Finalmente, combinamos los elementos únicos de authorizationMap con los de authorizationWhiteList
|
|
||||||
var combinedList = authorizationWhiteList.Concat(uniqueMapAuthorizations).ToList();
|
var combinedList = authorizationWhiteList.Concat(uniqueMapAuthorizations).ToList();
|
||||||
|
|
||||||
var userAuthorities = await _authorityService.GetUserAuthorities(user.Id);
|
var userAuthorities = await _authorityService.GetUserAuthorities(user.Id);
|
||||||
|
|
||||||
foreach (var auth in combinedList)
|
foreach (var auth in combinedList)
|
||||||
{
|
{
|
||||||
var authFound = userAuthorities.Find(c => c.DisplayId == auth.DisplayId);
|
var authFound = userAuthorities.Find(c => c.DisplayId == auth.DisplayId);
|
||||||
|
|
||||||
if (authFound is { CanUpdate: true })
|
if (authFound is { CanUpdate: true })
|
||||||
{
|
{
|
||||||
authFound.Rol = auth.Rol;
|
authFound.Rol = auth.Rol;
|
||||||
@@ -299,42 +286,41 @@ public class LdapLoginService : ILoginService
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError("[LDAP] CheckAuthorities for user {user} has exception {ex}", user.UserName, e.Message);
|
_logger.LogError("[LDAP] CheckAuthorities error for user {user}: {error}",
|
||||||
|
user.UserName, e.Message);
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method retrieves a list of authorities for a user based on a whitelist defined in the application's configuration.
|
/// This method retrieves a list of authorities for a user based on a whitelist defined in the application's configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="entry">The LDAP entry containing the user's information.</param>
|
/// <param name="entry">The LDAP entry containing the user's information.</param>
|
||||||
/// <param name="user">The user whose authorities are being retrieved.</param>
|
/// <param name="user">The user whose authorities are being retrieved.</param>
|
||||||
/// <returns>A list of authorities for the user based on the whitelist.</returns>
|
/// <returns>A list of authorities for the user based on the whitelist.</returns>
|
||||||
|
/// <!-- aidoc:v1 sig=a8263f0 body=9e6cc38 -->
|
||||||
private List<Authorization> GetAuthoritiesWhiteList(LdapEntry entry, User user)
|
private List<Authorization> GetAuthoritiesWhiteList(LdapEntry entry, User user)
|
||||||
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userWhiteList = new List<Authorization>();
|
var result = new List<Authorization>();
|
||||||
var userNameProperty = _ldapConfig.UserNameProperty;
|
|
||||||
|
|
||||||
var whiteList = _ldapConfig.WhiteList.FindAll(u =>
|
var whiteList = _ldapConfig.WhiteList.FindAll(u =>
|
||||||
(u.Name != null && entry.Dn.Contains(u.Name, StringComparison.CurrentCultureIgnoreCase)) ||
|
(u.Name != null && entry.DistinguishedName.Contains(u.Name, StringComparison.CurrentCultureIgnoreCase)) ||
|
||||||
(u.Username != null &&
|
(u.Username != null &&
|
||||||
userNameProperty != null &&
|
entry.Attributes[_ldapConfig.UserNameProperty]?[0]?.ToString()
|
||||||
entry.GetAttributeSet().TryGetValue(userNameProperty, out var attr) &&
|
?.Equals(u.Username, StringComparison.CurrentCultureIgnoreCase) == true)
|
||||||
attr.StringValue != null &&
|
|
||||||
attr.StringValue.Equals(u.Username, StringComparison.CurrentCultureIgnoreCase))
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (whiteList.Count == 0) return userWhiteList;
|
|
||||||
|
|
||||||
foreach (var authorityMap in whiteList)
|
foreach (var authorityMap in whiteList)
|
||||||
{
|
{
|
||||||
if (!Enum.TryParse<PermissionEnum.RolesType>(authorityMap.Rol, out _))
|
if (!Enum.TryParse<PermissionEnum.RolesType>(authorityMap.Rol, out _))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
userWhiteList.Add(new Authorization
|
result.Add(new Authorization
|
||||||
{
|
{
|
||||||
UserId = user.Id,
|
UserId = user.Id,
|
||||||
DisplayId = authorityMap.DisplayId,
|
DisplayId = authorityMap.DisplayId,
|
||||||
@@ -342,70 +328,59 @@ public class LdapLoginService : ILoginService
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return userWhiteList;
|
return result;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError("[LDAP] GetAuthoritiesWhiteList for user {user} has exception {ex}", user.UserName,
|
_logger.LogError("[LDAP] GetAuthoritiesWhiteList error: {error}", e.Message);
|
||||||
e.Message);
|
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method retrieves a list of authorities for a user based on the LDAP entry and the application's configuration for mapping LDAP groups to authorities.
|
/// Maps an <see cref="LdapEntry"/> to a <see cref="User"/> object, populating the username from the configured attribute and, when the corresponding properties are configured, composing the user's name from the first and last name attributes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ldapEntry">The LDAP entry containing the user's information.</param>
|
/// <param name="ldapEntry">The <see cref="LdapEntry"/> from which the <see cref="User"/> properties are read.</param>
|
||||||
/// <returns>The existing or newly created user with updated authorities.</returns>
|
/// <returns>A <see cref="User"/> instance populated from the LDAP entry attributes.</returns>
|
||||||
|
/// <!-- aidoc:v1 sig=947181a body=33ffec8 -->
|
||||||
private User GetUser(LdapEntry ldapEntry)
|
private User GetUser(LdapEntry ldapEntry)
|
||||||
{
|
|
||||||
var attributes = ldapEntry.GetAttributeSet();
|
|
||||||
|
|
||||||
var user = new User();
|
{
|
||||||
|
var user = new User
|
||||||
|
{
|
||||||
|
UserName = entry.Attributes[_ldapConfig.UserNameProperty]?[0]?.ToString() ?? ""
|
||||||
|
};
|
||||||
|
|
||||||
// UserName
|
if (!string.IsNullOrWhiteSpace(_ldapConfig.FirstNameProperty))
|
||||||
if (!string.IsNullOrWhiteSpace(_ldapConfig.UserNameProperty) &&
|
|
||||||
attributes.TryGetValue(_ldapConfig.UserNameProperty, out var userAttr) &&
|
|
||||||
userAttr?.StringValue != null)
|
|
||||||
{
|
{
|
||||||
user.UserName = userAttr.StringValue;
|
var first = entry.Attributes[_ldapConfig.FirstNameProperty]?[0]?.ToString();
|
||||||
}
|
if (first != null)
|
||||||
else
|
user.Name = first;
|
||||||
{
|
|
||||||
user.UserName = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("[LDAP] GetUser UserName is {UserName} ", user.UserName);
|
if (!string.IsNullOrWhiteSpace(_ldapConfig.LastNameProperty))
|
||||||
|
|
||||||
// First name
|
|
||||||
if (!string.IsNullOrWhiteSpace(_ldapConfig.FirstNameProperty) &&
|
|
||||||
attributes.TryGetValue(_ldapConfig.FirstNameProperty, out var firstNameAttr) &&
|
|
||||||
firstNameAttr?.StringValue != null)
|
|
||||||
{
|
|
||||||
user.Name = firstNameAttr.StringValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Last name
|
|
||||||
if (!string.IsNullOrWhiteSpace(_ldapConfig.LastNameProperty) &&
|
|
||||||
attributes.TryGetValue(_ldapConfig.LastNameProperty, out var lastNameAttr) &&
|
|
||||||
lastNameAttr?.StringValue != null)
|
|
||||||
{
|
{
|
||||||
|
var last = entry.Attributes[_ldapConfig.LastNameProperty]?[0]?.ToString();
|
||||||
|
if (last != null)
|
||||||
user.Name = string.IsNullOrEmpty(user.Name)
|
user.Name = string.IsNullOrEmpty(user.Name)
|
||||||
? lastNameAttr.StringValue
|
? last
|
||||||
: $"{user.Name} {lastNameAttr.StringValue}";
|
: $"{user.Name} {last}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This method retrieves a list of authorities for a user based on the LDAP entry and the application's configuration for mapping LDAP groups to authorities.
|
/// This method retrieves a list of authorities for a user based on the LDAP entry and the application's configuration for mapping LDAP groups to authorities.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ldapEntry">The LDAP entry containing the user's information.</param>
|
/// <param name="ldapEntry">The LDAP entry containing the user's information.</param>
|
||||||
/// <param name="user">The user whose authorities are being retrieved.</param>
|
/// <param name="user">The user whose authorities are being retrieved.</param>
|
||||||
/// <returns>A list of authorities for the user based on the LDAP entry and the application's configuration.</returns>
|
/// <returns>A list of authorities for the user based on the LDAP entry and the application's configuration.</returns>
|
||||||
|
/// <!-- aidoc:v1 sig=1e1f0f2 body=18015c0 -->
|
||||||
private List<Authorization> GetAuthoritiesMap(LdapEntry ldapEntry, User user)
|
private List<Authorization> GetAuthoritiesMap(LdapEntry ldapEntry, User user)
|
||||||
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -414,16 +389,14 @@ public class LdapLoginService : ILoginService
|
|||||||
if (!_ldapConfig.AuthoritiesMap.Any())
|
if (!_ldapConfig.AuthoritiesMap.Any())
|
||||||
return authorities;
|
return authorities;
|
||||||
|
|
||||||
var attributes = ldapEntry.GetAttributeSet();
|
var groupAttr = entry.Attributes[_ldapConfig.GroupsProperty];
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(_ldapConfig.GroupsProperty) ||
|
if (groupAttr == null)
|
||||||
!attributes.TryGetValue(_ldapConfig.GroupsProperty!, out var groupsAttr) ||
|
|
||||||
groupsAttr?.StringValueArray == null)
|
|
||||||
{
|
|
||||||
return authorities;
|
return authorities;
|
||||||
}
|
|
||||||
|
|
||||||
var groups = groupsAttr.StringValueArray.ToList();
|
var groups = groupAttr.GetValues(typeof(string))
|
||||||
|
.Cast<string>()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
foreach (var authorityMap in _ldapConfig.AuthoritiesMap)
|
foreach (var authorityMap in _ldapConfig.AuthoritiesMap)
|
||||||
{
|
{
|
||||||
@@ -446,7 +419,7 @@ public class LdapLoginService : ILoginService
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.LogError("Error getting Authorities Map. Return new empty list. Exception: {e}", e);
|
_logger.LogError("[LDAP] GetAuthoritiesMap error: {error}", e.Message);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,12 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AuditLogs" Version="1.0.59" />
|
<PackageReference Include="AuditLogs" Version="1.0.59" />
|
||||||
<PackageReference Include="FluentValidation" Version="12.1.1" />
|
<PackageReference Include="FluentValidation" Version="12.1.1" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.11" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0">
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="5.3.0" />
|
<PackageReference Include="System.DirectoryServices.Protocols" Version="10.0.9" />
|
||||||
<PackageReference Include="Novell.Directory.Ldap.NETStandard" Version="4.0.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -14,12 +14,11 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AuditLogs" Version="1.0.59" />
|
<PackageReference Include="AuditLogs" Version="1.0.59" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.11" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0">
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="5.3.0" />
|
|
||||||
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -25,9 +25,8 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="10.0.9" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="8.0.28" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
|
||||||
<PackageReference Include="Microsoft.QualityTools.Testing.Fakes" Version="18.1.1" />
|
|
||||||
<PackageReference Include="Mongo2Go" Version="4.1.0" />
|
<PackageReference Include="Mongo2Go" Version="4.1.0" />
|
||||||
<PackageReference Include="MongoMigrations.Core" Version="4.0.15" />
|
<PackageReference Include="MongoMigrations.Core" Version="4.0.15" />
|
||||||
<PackageReference Include="Moq" Version="4.20.72" />
|
<PackageReference Include="Moq" Version="4.20.72" />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="5.3.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -15,11 +15,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AuditLogs" Version="1.0.59" />
|
<PackageReference Include="AuditLogs" Version="1.0.59" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.9" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.9" />
|
|
||||||
<PackageReference Include="Serilog" Version="4.3.1" />
|
<PackageReference Include="Serilog" Version="4.3.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.49.1" />
|
|
||||||
<PackageReference Include="Snappier" Version="1.3.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AuditLogs" Version="1.0.59" />
|
<PackageReference Include="AuditLogs" Version="1.0.59" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.9" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -30,15 +30,11 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="5.3.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="8.0.28" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.28" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="10.0.9" />
|
|
||||||
<PackageReference Include="Microsoft.IdentityModel.Protocols" Version="8.19.1" />
|
<PackageReference Include="Microsoft.IdentityModel.Protocols" Version="8.19.1" />
|
||||||
<PackageReference Include="Microsoft.OpenApi" Version="3.7.0" />
|
|
||||||
<PackageReference Include="MongoDB.Driver" Version="3.9.0" />
|
<PackageReference Include="MongoDB.Driver" Version="3.9.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
||||||
<PackageReference Include="Quartz" Version="3.18.1" />
|
|
||||||
<PackageReference Include="Serilog" Version="4.3.1" />
|
<PackageReference Include="Serilog" Version="4.3.1" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||||
<PackageReference Include="Serilog.Enrichers.Dynamic" Version="1.0.9" />
|
<PackageReference Include="Serilog.Enrichers.Dynamic" Version="1.0.9" />
|
||||||
@@ -48,12 +44,6 @@
|
|||||||
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
|
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||||
|
|
||||||
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="10.0.9" />
|
|
||||||
<PackageReference Include="System.Management" Version="10.0.9" />
|
|
||||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
|
||||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.2" />
|
|
||||||
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
|
|
||||||
<PackageReference Include="WebPush" Version="1.0.13" />
|
<PackageReference Include="WebPush" Version="1.0.13" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
//"ConnectionString": "mongodb://smartuci:!H12o2020@localhost:27017/?authSource=smartuci",
|
//"ConnectionString": "mongodb://smartuci:!H12o2020@localhost:27017/?authSource=smartuci",
|
||||||
//"ConnectionString": "mongodb://admin:4q*pHca@10.0.20.78:22007",
|
//"ConnectionString": "mongodb://admin:4q*pHca@10.0.20.78:22007",
|
||||||
//"ConnectionString": "mongodb://smacsuci:2(R*aQpu2r@sumo.julianrojas.xyz:27017/?authSource=admin",
|
//"ConnectionString": "mongodb://smacsuci:2(R*aQpu2r@sumo.julianrojas.xyz:27017/?authSource=admin",
|
||||||
"DatabaseName": "adasDevNewStandard"
|
"DatabaseName": "adas"
|
||||||
},
|
},
|
||||||
"DatabaseConfigurationAudit": {
|
"DatabaseConfigurationAudit": {
|
||||||
"ConnectionString": "mongodb://smacsuci:2(R*aQpu2r@julianrojas.xyz:27017/?authSource=admin",
|
"ConnectionString": "mongodb://smacsuci:2(R*aQpu2r@julianrojas.xyz:27017/?authSource=admin",
|
||||||
|
|||||||
Reference in New Issue
Block a user