diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index c31beb8d..50bf6c3d 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -2,11 +2,11 @@ "version": 1, "isRoot": true, "tools": { - "dotnet-format": { - "version": "5.1.225507", + "csharpier": { + "version": "0.25.0", "commands": [ - "dotnet-format" + "dotnet-csharpier" ] } } -} \ No newline at end of file +} diff --git a/.editorconfig b/.editorconfig index 697ce684..c4674a2f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -249,6 +249,8 @@ csharp_space_between_square_brackets = false csharp_preserve_single_line_statements = false csharp_preserve_single_line_blocks = true +csharp_style_namespace_declarations = file_scoped + ########################################## # .NET Naming Conventions # https://docs.microsoft.com/visualstudio/ide/editorconfig-naming-conventions @@ -258,12 +260,16 @@ csharp_preserve_single_line_blocks = true dotnet_diagnostic.CA1000.severity = suggestion dotnet_diagnostic.CA1001.severity = error dotnet_diagnostic.CA1018.severity = error +dotnet_diagnostic.CA1036.severity = silent dotnet_diagnostic.CA1051.severity = suggestion +dotnet_diagnostic.CA1068.severity = error dotnet_diagnostic.CA1069.severity = error dotnet_diagnostic.CA1304.severity = error dotnet_diagnostic.CA1305.severity = suggestion +dotnet_diagnostic.CA1307.severity = suggestion dotnet_diagnostic.CA1309.severity = suggestion dotnet_diagnostic.CA1310.severity = error +dotnet_diagnostic.CA1507.severity = suggestion dotnet_diagnostic.CA1707.severity = suggestion dotnet_diagnostic.CA1708.severity = suggestion dotnet_diagnostic.CA1711.severity = suggestion @@ -273,23 +279,30 @@ dotnet_diagnostic.CA1725.severity = suggestion dotnet_diagnostic.CA1805.severity = suggestion dotnet_diagnostic.CA1816.severity = suggestion dotnet_diagnostic.CA1822.severity = suggestion +dotnet_diagnostic.CA1824.severity = none dotnet_diagnostic.CA1825.severity = error dotnet_diagnostic.CA1826.severity = silent dotnet_diagnostic.CA1827.severity = error dotnet_diagnostic.CA1829.severity = suggestion dotnet_diagnostic.CA1834.severity = error +dotnet_diagnostic.CA1845.severity = suggestion +dotnet_diagnostic.CA1848.severity = suggestion +dotnet_diagnostic.CA1852.severity = suggestion dotnet_diagnostic.CA2016.severity = suggestion dotnet_diagnostic.CA2201.severity = error dotnet_diagnostic.CA2206.severity = error dotnet_diagnostic.CA2208.severity = error dotnet_diagnostic.CA2211.severity = error dotnet_diagnostic.CA2249.severity = error +dotnet_diagnostic.CA2251.severity = error +dotnet_diagnostic.CA2252.severity = none +dotnet_diagnostic.CA2254.severity = suggestion dotnet_diagnostic.CS0169.severity = error dotnet_diagnostic.CS0219.severity = error dotnet_diagnostic.CS1998.severity = error -dotnet_diagnostic.CS8602.severity = Default -dotnet_diagnostic.CS8604.severity = Default +dotnet_diagnostic.CS8602.severity = error +dotnet_diagnostic.CS8604.severity = error dotnet_diagnostic.CS8618.severity = error dotnet_diagnostic.CS0618.severity = error dotnet_diagnostic.CS1998.severity = error @@ -298,6 +311,58 @@ dotnet_diagnostic.CS8600.severity = error dotnet_diagnostic.CS8603.severity = error dotnet_diagnostic.CS8625.severity = error +dotnet_diagnostic.BL0005.severity = suggestion + +dotnet_diagnostic.MVC1000.severity = suggestion + +dotnet_diagnostic.RZ10012.severity = error + +dotnet_diagnostic.IDE0004.severity = error # redundant cast +dotnet_diagnostic.IDE0005.severity = suggestion +dotnet_diagnostic.IDE0007.severity = error # Use var +dotnet_diagnostic.IDE0011.severity = error # Use braces on if statements +dotnet_diagnostic.IDE0010.severity = silent # populate switch +dotnet_diagnostic.IDE0017.severity = suggestion # initialization can be simplified +dotnet_diagnostic.IDE0021.severity = silent # expression body for constructors +dotnet_diagnostic.IDE0022.severity = silent # expression body for methods +dotnet_diagnostic.IDE0023.severity = suggestion # use expression body for operators +dotnet_diagnostic.IDE0024.severity = silent # expression body for operators +dotnet_diagnostic.IDE0025.severity = suggestion # use expression body for properties +dotnet_diagnostic.IDE0027.severity = suggestion # Use expression body for accessors +dotnet_diagnostic.IDE0028.severity = silent +dotnet_diagnostic.IDE0032.severity = suggestion # Use auto property +dotnet_diagnostic.IDE0033.severity = error # prefer tuple name +dotnet_diagnostic.IDE0037.severity = suggestion # simplify anonymous type +dotnet_diagnostic.IDE0040.severity = error # modifiers required +dotnet_diagnostic.IDE0041.severity = error # simplify null +dotnet_diagnostic.IDE0042.severity = error # deconstruct variable +dotnet_diagnostic.IDE0044.severity = suggestion # make field only when possible +dotnet_diagnostic.IDE0047.severity = suggestion # paratemeter name +dotnet_diagnostic.IDE0051.severity = error # unused field +dotnet_diagnostic.IDE0052.severity = error # unused member +dotnet_diagnostic.IDE0053.severity = suggestion # lambda not needed +dotnet_diagnostic.IDE0055.severity = suggestion # Fix formatting +dotnet_diagnostic.IDE0057.severity = suggestion # substring can be simplified +dotnet_diagnostic.IDE0060.severity = suggestion # unused parameters +dotnet_diagnostic.IDE0061.severity = suggestion # local expression body +dotnet_diagnostic.IDE0062.severity = suggestion # local to static +dotnet_diagnostic.IDE0063.severity = error # simplify using +dotnet_diagnostic.IDE0066.severity = suggestion # switch expression +dotnet_diagnostic.IDE0072.severity = suggestion # Populate switch - forces population of all cases even when default specified +dotnet_diagnostic.IDE0078.severity = suggestion # use pattern matching +dotnet_diagnostic.IDE0090.severity = suggestion # new can be simplified +dotnet_diagnostic.IDE0130.severity = suggestion # namespace folder structure +dotnet_diagnostic.IDE0160.severity = silent # Use block namespaces ARE NOT required +dotnet_diagnostic.IDE0161.severity = error # Please use file namespaces +dotnet_diagnostic.IDE0200.severity = suggestion # lambda not needed +dotnet_diagnostic.IDE1006.severity = suggestion # Naming rule violation: These words cannot contain lower case characters +dotnet_diagnostic.IDE0270.severity = suggestion # Null check simplifcation +dotnet_diagnostic.IDE0260.severity = suggestion # Use pattern matching + +dotnet_diagnostic.NX0001.severity = error +dotnet_diagnostic.NX0002.severity = silent +dotnet_diagnostic.NX0003.severity = silent + ########################################## # Styles ########################################## diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..67534058 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" # search for actions - there are other options available + directory: "/" # search in .github/workflows under root `/` + schedule: + interval: "weekly" # check for action update every week diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 2ff43ce5..be472859 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -8,8 +8,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions/setup-dotnet@v1 + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v3 with: - dotnet-version: 5.0.400 + dotnet-version: 7.0.400 - run: dotnet run -p build/build.csproj diff --git a/Conduit.sln b/Conduit.sln index 3d14494e..2e7e2bb0 100644 --- a/Conduit.sln +++ b/Conduit.sln @@ -13,6 +13,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Conduit.IntegrationTests", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "build", "build\build.csproj", "{1A67215E-3B6F-4FDA-AB9B-D2ECDC676A29}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{E0DF0CFA-3DF6-4E15-A3ED-ED68DBB4BE4E}" + ProjectSection(SolutionItems) = preProject + Directory.Packages.props = Directory.Packages.props + Directory.Build.props = Directory.Build.props + global.json = global.json + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{79EC8D73-8DAD-430E-93CE-C1F29DBC33FA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -38,5 +47,6 @@ Global GlobalSection(NestedProjects) = preSolution {BFC1306F-9EBF-414C-8656-C50E3E5AFADA} = {7C288D93-2148-4679-9890-DBFCA0C59BCC} {CFFDDBF0-5CDD-4D88-A82B-68CBF2432999} = {9F832627-4D82-4B5B-84A7-244E8480845E} + {1A67215E-3B6F-4FDA-AB9B-D2ECDC676A29} = {79EC8D73-8DAD-430E-93CE-C1F29DBC33FA} EndGlobalSection EndGlobal diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..e5455b29 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,17 @@ + + + net7.0 + enable + Recommended + true + true + true + true + expanded + + true + true + true + false + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 00000000..e7cbbb24 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/build/Dockerfile b/Dockerfile similarity index 100% rename from build/Dockerfile rename to Dockerfile diff --git a/Makefile b/Makefile index 63cf374f..0bffab67 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ build: - docker-compose build + docker compose build run: - docker-compose up \ No newline at end of file + docker compose up diff --git a/NuGet.config b/NuGet.config new file mode 100644 index 00000000..48259210 --- /dev/null +++ b/NuGet.config @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/build/Program.cs b/build/Program.cs index 94f3e5db..c4d4a6a9 100644 --- a/build/Program.cs +++ b/build/Program.cs @@ -11,7 +11,8 @@ const string Format = "format"; const string Publish = "publish"; -Target(Clean, +Target( + Clean, ForEach("publish", "**/bin", "**/obj"), dir => { @@ -33,18 +34,23 @@ void RemoveDirectory(string d) { RemoveDirectory(d); } - }); + } +); - -Target(Format, () => -{ - Run("dotnet", "tool restore"); - Run("dotnet", "format --check"); -}); +Target( + Format, + () => + { + Run("dotnet", "tool restore"); + Run("dotnet", "csharpier --check"); + } +); Target(Build, DependsOn(Format), () => Run("dotnet", "build . -c Release")); -Target(Test, DependsOn(Build), +Target( + Test, + DependsOn(Build), () => { IEnumerable GetFiles(string d) @@ -56,16 +62,21 @@ IEnumerable GetFiles(string d) { Run("dotnet", $"test {file} -c Release --no-restore --no-build --verbosity=normal"); } - }); + } +); -Target(Publish, DependsOn(Test), +Target( + Publish, + DependsOn(Test), ForEach("src/Conduit"), project => { - Run("dotnet", - $"publish {project} -c Release -f net5.0 -o ./publish --no-restore --no-build --verbosity=normal"); - }); + Run( + "dotnet", + $"publish {project} -c Release -f net7.0 -o ./publish --no-restore --no-build --verbosity=normal" + ); + } +); Target("default", DependsOn(Publish), () => Console.WriteLine("Done!")); await RunTargetsAndExitAsync(args); - diff --git a/build/Properties/launchSettings.json b/build/Properties/launchSettings.json deleted file mode 100644 index a6704d25..00000000 --- a/build/Properties/launchSettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "profiles": { - "build": { - "commandName": "Project" - }, - "Docker": { - "commandName": "Docker" - } - } -} diff --git a/build/build.csproj b/build/build.csproj index f2aa8ba3..2c119570 100644 --- a/build/build.csproj +++ b/build/build.csproj @@ -1,14 +1,10 @@ - net7.0 Exe - Linux - - - - - + + + diff --git a/build/packages.lock.json b/build/packages.lock.json new file mode 100644 index 00000000..f4de76ba --- /dev/null +++ b/build/packages.lock.json @@ -0,0 +1,25 @@ +{ + "version": 2, + "dependencies": { + "net7.0": { + "Bullseye": { + "type": "Direct", + "requested": "[4.2.1, )", + "resolved": "4.2.1", + "contentHash": "LQ/YuE1TSxCPfn5qGwf7RpS4jGhXEES1ylsHUNbPKdyJqbh+3VRLcxhS2aUHM9wOKaLR7uISuaiHBYc5Idfatw==" + }, + "Glob": { + "type": "Direct", + "requested": "[1.1.9, )", + "resolved": "1.1.9", + "contentHash": "AfK5+ECWYTP7G3AAdnU8IfVj+QpGjrh9GC2mpdcJzCvtQ4pnerAGwHsxJ9D4/RnhDUz2DSzd951O/lQjQby2Sw==" + }, + "SimpleExec": { + "type": "Direct", + "requested": "[11.0.0, )", + "resolved": "11.0.0", + "contentHash": "4r/YxcXlD9yk0XGdU07gUFey6ZiWu7LxG76l9d9xTDfObOcLvug1Xa9loQYuqs2nEviyTgicD1Svragh2qzlOA==" + } + } + } +} \ No newline at end of file diff --git a/src/Conduit/Conduit.csproj b/src/Conduit/Conduit.csproj index ed2bd2ea..47acb2f8 100644 --- a/src/Conduit/Conduit.csproj +++ b/src/Conduit/Conduit.csproj @@ -1,37 +1,16 @@ - - net7.0 - true - enable - - - - - - - - - - - - - - - - - - - True - True - Infrastructure.Errors.ErrorHandlingMiddleware.resx - - - - - - ResXFileCodeGenerator - Infrastructure.Errors.ErrorHandlingMiddleware.Designer.cs - + + + + + + + + + + + + diff --git a/src/Conduit/Domain/Article.cs b/src/Conduit/Domain/Article.cs index d4c4b76f..6edd920d 100644 --- a/src/Conduit/Domain/Article.cs +++ b/src/Conduit/Domain/Article.cs @@ -4,42 +4,42 @@ using System.Linq; using System.Text.Json.Serialization; -namespace Conduit.Domain +namespace Conduit.Domain; + +public class Article { - public class Article - { - [JsonIgnore] - public int ArticleId { get; set; } + [JsonIgnore] + public int ArticleId { get; set; } - public string? Slug { get; set; } + public string? Slug { get; set; } - public string? Title { get; set; } + public string? Title { get; set; } - public string? Description { get; set; } + public string? Description { get; set; } - public string? Body { get; set; } + public string? Body { get; set; } - public Person? Author { get; set; } + public Person? Author { get; set; } - public List Comments { get; set; } = new(); + public List Comments { get; set; } = new(); - [NotMapped] - public bool Favorited => ArticleFavorites?.Any() ?? false; + [NotMapped] + public bool Favorited => ArticleFavorites?.Any() ?? false; - [NotMapped] - public int FavoritesCount => ArticleFavorites?.Count ?? 0; + [NotMapped] + public int FavoritesCount => ArticleFavorites?.Count ?? 0; - [NotMapped] - public List TagList => ArticleTags.Where(x => x.TagId is not null).Select(x => x.TagId!).ToList(); + [NotMapped] + public List TagList => + ArticleTags.Where(x => x.TagId is not null).Select(x => x.TagId!).ToList(); - [JsonIgnore] - public List ArticleTags { get; set; } = new(); + [JsonIgnore] + public List ArticleTags { get; set; } = new(); - [JsonIgnore] - public List ArticleFavorites { get; set; } = new(); + [JsonIgnore] + public List ArticleFavorites { get; set; } = new(); - public DateTime CreatedAt { get; set; } + public DateTime CreatedAt { get; set; } - public DateTime UpdatedAt { get; set; } - } -} + public DateTime UpdatedAt { get; set; } +} \ No newline at end of file diff --git a/src/Conduit/Domain/ArticleFavorite.cs b/src/Conduit/Domain/ArticleFavorite.cs index 99ca16dd..56bd7b42 100644 --- a/src/Conduit/Domain/ArticleFavorite.cs +++ b/src/Conduit/Domain/ArticleFavorite.cs @@ -1,11 +1,10 @@ -namespace Conduit.Domain +namespace Conduit.Domain; + +public class ArticleFavorite { - public class ArticleFavorite - { - public int ArticleId { get; set; } - public Article? Article { get; set; } + public int ArticleId { get; set; } + public Article? Article { get; set; } - public int PersonId { get; set; } - public Person? Person { get; set; } - } -} + public int PersonId { get; set; } + public Person? Person { get; set; } +} \ No newline at end of file diff --git a/src/Conduit/Domain/ArticleTag.cs b/src/Conduit/Domain/ArticleTag.cs index b6f35919..ad8499cf 100644 --- a/src/Conduit/Domain/ArticleTag.cs +++ b/src/Conduit/Domain/ArticleTag.cs @@ -1,11 +1,10 @@ -namespace Conduit.Domain +namespace Conduit.Domain; + +public class ArticleTag { - public class ArticleTag - { - public int ArticleId { get; set; } - public Article? Article { get; set; } + public int ArticleId { get; set; } + public Article? Article { get; set; } - public string? TagId { get; set; } - public Tag? Tag { get; set; } - } -} + public string? TagId { get; set; } + public Tag? Tag { get; set; } +} \ No newline at end of file diff --git a/src/Conduit/Domain/Comment.cs b/src/Conduit/Domain/Comment.cs index 15e220a8..5da37ba3 100644 --- a/src/Conduit/Domain/Comment.cs +++ b/src/Conduit/Domain/Comment.cs @@ -1,28 +1,27 @@ using System; using System.Text.Json.Serialization; -namespace Conduit.Domain +namespace Conduit.Domain; + +public class Comment { - public class Comment - { - [JsonPropertyName("id")] - public int CommentId { get; set; } + [JsonPropertyName("id")] + public int CommentId { get; set; } - public string? Body { get; set; } + public string? Body { get; set; } - public Person? Author { get; set; } + public Person? Author { get; set; } - [JsonIgnore] - public int AuthorId { get; set; } + [JsonIgnore] + public int AuthorId { get; set; } - [JsonIgnore] - public Article? Article { get; set; } + [JsonIgnore] + public Article? Article { get; set; } - [JsonIgnore] - public int ArticleId { get; set; } + [JsonIgnore] + public int ArticleId { get; set; } - public DateTime CreatedAt { get; set; } + public DateTime CreatedAt { get; set; } - public DateTime UpdatedAt { get; set; } - } -} + public DateTime UpdatedAt { get; set; } +} \ No newline at end of file diff --git a/src/Conduit/Domain/FollowedPeople.cs b/src/Conduit/Domain/FollowedPeople.cs index f0b6b371..13c60f6e 100644 --- a/src/Conduit/Domain/FollowedPeople.cs +++ b/src/Conduit/Domain/FollowedPeople.cs @@ -1,11 +1,10 @@ -namespace Conduit.Domain +namespace Conduit.Domain; + +public class FollowedPeople { - public class FollowedPeople - { - public int ObserverId { get; set; } - public Person? Observer { get; set; } + public int ObserverId { get; set; } + public Person? Observer { get; set; } - public int TargetId { get; set; } - public Person? Target { get; set; } - } -} + public int TargetId { get; set; } + public Person? Target { get; set; } +} \ No newline at end of file diff --git a/src/Conduit/Domain/Person.cs b/src/Conduit/Domain/Person.cs index 6e33e96c..9a5cb376 100644 --- a/src/Conduit/Domain/Person.cs +++ b/src/Conduit/Domain/Person.cs @@ -2,34 +2,33 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -namespace Conduit.Domain +namespace Conduit.Domain; + +public class Person { - public class Person - { - [JsonIgnore] - public int PersonId { get; set; } + [JsonIgnore] + public int PersonId { get; set; } - public string? Username { get; set; } + public string? Username { get; set; } - public string? Email { get; set; } + public string? Email { get; set; } - public string? Bio { get; set; } + public string? Bio { get; set; } - public string? Image { get; set; } + public string? Image { get; set; } - [JsonIgnore] - public List ArticleFavorites { get; set; } = new(); + [JsonIgnore] + public List ArticleFavorites { get; set; } = new(); - [JsonIgnore] - public List Following { get; set; } = new(); + [JsonIgnore] + public List Following { get; set; } = new(); - [JsonIgnore] - public List Followers { get; set; } = new(); + [JsonIgnore] + public List Followers { get; set; } = new(); - [JsonIgnore] - public byte[] Hash { get; set; } = Array.Empty(); + [JsonIgnore] + public byte[] Hash { get; set; } = Array.Empty(); - [JsonIgnore] - public byte[] Salt { get; set; } = Array.Empty(); - } -} + [JsonIgnore] + public byte[] Salt { get; set; } = Array.Empty(); +} \ No newline at end of file diff --git a/src/Conduit/Domain/Tag.cs b/src/Conduit/Domain/Tag.cs index 0a0e3488..753b8a2f 100644 --- a/src/Conduit/Domain/Tag.cs +++ b/src/Conduit/Domain/Tag.cs @@ -1,11 +1,10 @@ using System.Collections.Generic; -namespace Conduit.Domain +namespace Conduit.Domain; + +public class Tag { - public class Tag - { - public string? TagId { get; set; } + public string? TagId { get; set; } - public List ArticleTags { get; set; } = new(); - } -} + public List ArticleTags { get; set; } = new(); +} \ No newline at end of file diff --git a/src/Conduit/Features/Articles/ArticleEnvelope.cs b/src/Conduit/Features/Articles/ArticleEnvelope.cs index 4425f95c..bbb86f5c 100644 --- a/src/Conduit/Features/Articles/ArticleEnvelope.cs +++ b/src/Conduit/Features/Articles/ArticleEnvelope.cs @@ -1,6 +1,5 @@ using Conduit.Domain; -namespace Conduit.Features.Articles -{ - public record ArticleEnvelope(Article Article); -} +namespace Conduit.Features.Articles; + +public record ArticleEnvelope(Article Article); \ No newline at end of file diff --git a/src/Conduit/Features/Articles/ArticleExtensions.cs b/src/Conduit/Features/Articles/ArticleExtensions.cs index be7b52db..2c0ddee0 100644 --- a/src/Conduit/Features/Articles/ArticleExtensions.cs +++ b/src/Conduit/Features/Articles/ArticleExtensions.cs @@ -2,14 +2,14 @@ using Conduit.Domain; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Articles +namespace Conduit.Features.Articles; + +public static class ArticleExtensions { - public static class ArticleExtensions - { - public static IQueryable
GetAllData(this DbSet
articles) => articles - .Include(x => x.Author) - .Include(x => x.ArticleFavorites) - .Include(x => x.ArticleTags) - .AsNoTracking(); - } + public static IQueryable
GetAllData(this DbSet
articles) => + articles + .Include(x => x.Author) + .Include(x => x.ArticleFavorites) + .Include(x => x.ArticleTags) + .AsNoTracking(); } diff --git a/src/Conduit/Features/Articles/ArticlesController.cs b/src/Conduit/Features/Articles/ArticlesController.cs index 85cb76a6..9aeda171 100644 --- a/src/Conduit/Features/Articles/ArticlesController.cs +++ b/src/Conduit/Features/Articles/ArticlesController.cs @@ -5,58 +5,80 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Conduit.Features.Articles +namespace Conduit.Features.Articles; + +[Route("articles")] +public class ArticlesController : Controller { - [Route("articles")] - public class ArticlesController : Controller + private readonly IMediator _mediator; + + public ArticlesController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public Task Get( + [FromQuery] string tag, + [FromQuery] string author, + [FromQuery] string favorited, + [FromQuery] int? limit, + [FromQuery] int? offset, + CancellationToken cancellationToken + ) + { + return _mediator.Send( + new List.Query(tag, author, favorited, limit, offset), + cancellationToken + ); + } + + [HttpGet("feed")] + public Task GetFeed( + [FromQuery] string tag, + [FromQuery] string author, + [FromQuery] string favorited, + [FromQuery] int? limit, + [FromQuery] int? offset, + CancellationToken cancellationToken + ) + { + return _mediator.Send( + new List.Query(tag, author, favorited, limit, offset) { IsFeed = true } + ); + } + + [HttpGet("{slug}")] + public Task Get(string slug, CancellationToken cancellationToken) + { + return _mediator.Send(new Details.Query(slug), cancellationToken); + } + + [HttpPost] + [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] + public Task Create( + [FromBody] Create.Command command, + CancellationToken cancellationToken + ) + { + return _mediator.Send(command, cancellationToken); + } + + [HttpPut("{slug}")] + [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] + public Task Edit( + string slug, + [FromBody] Edit.Model model, + CancellationToken cancellationToken + ) + { + return _mediator.Send(new Edit.Command(model, slug), cancellationToken); + } + + [HttpDelete("{slug}")] + [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] + public Task Delete(string slug, CancellationToken cancellationToken) { - private readonly IMediator _mediator; - - public ArticlesController(IMediator mediator) - { - _mediator = mediator; - } - - [HttpGet] - public Task Get([FromQuery] string tag, [FromQuery] string author, [FromQuery] string favorited, [FromQuery] int? limit, [FromQuery] int? offset, CancellationToken cancellationToken) - { - return _mediator.Send(new List.Query(tag, author, favorited, limit, offset), cancellationToken); - } - - [HttpGet("feed")] - public Task GetFeed([FromQuery] string tag, [FromQuery] string author, [FromQuery] string favorited, [FromQuery] int? limit, [FromQuery] int? offset, CancellationToken cancellationToken) - { - return _mediator.Send(new List.Query(tag, author, favorited, limit, offset) - { - IsFeed = true - }); - } - - [HttpGet("{slug}")] - public Task Get(string slug, CancellationToken cancellationToken) - { - return _mediator.Send(new Details.Query(slug), cancellationToken); - } - - [HttpPost] - [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] - public Task Create([FromBody] Create.Command command, CancellationToken cancellationToken) - { - return _mediator.Send(command, cancellationToken); - } - - [HttpPut("{slug}")] - [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] - public Task Edit(string slug, [FromBody] Edit.Model model, CancellationToken cancellationToken) - { - return _mediator.Send(new Edit.Command(model, slug), cancellationToken); - } - - [HttpDelete("{slug}")] - [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] - public Task Delete(string slug, CancellationToken cancellationToken) - { - return _mediator.Send(new Delete.Command(slug), cancellationToken); - } + return _mediator.Send(new Delete.Command(slug), cancellationToken); } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Articles/ArticlesEnvelope.cs b/src/Conduit/Features/Articles/ArticlesEnvelope.cs index e603f4a5..b7dde6fb 100644 --- a/src/Conduit/Features/Articles/ArticlesEnvelope.cs +++ b/src/Conduit/Features/Articles/ArticlesEnvelope.cs @@ -1,12 +1,11 @@ using System.Collections.Generic; using Conduit.Domain; -namespace Conduit.Features.Articles +namespace Conduit.Features.Articles; + +public class ArticlesEnvelope { - public class ArticlesEnvelope - { - public List
Articles { get; set; } = new(); + public List
Articles { get; set; } = new(); - public int ArticlesCount { get; set; } - } -} + public int ArticlesCount { get; set; } +} \ No newline at end of file diff --git a/src/Conduit/Features/Articles/Create.cs b/src/Conduit/Features/Articles/Create.cs index 05dc6c0c..3a7a55f8 100644 --- a/src/Conduit/Features/Articles/Create.cs +++ b/src/Conduit/Features/Articles/Create.cs @@ -9,94 +9,95 @@ using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Articles +namespace Conduit.Features.Articles; + +public class Create { - public class Create + public class ArticleData { - public class ArticleData - { - public string? Title { get; set; } + public string? Title { get; set; } - public string? Description { get; set; } + public string? Description { get; set; } - public string? Body { get; set; } + public string? Body { get; set; } - public string[]? TagList { get; set; } - } + public string[]? TagList { get; set; } + } - public class ArticleDataValidator : AbstractValidator + public class ArticleDataValidator : AbstractValidator + { + public ArticleDataValidator() { - public ArticleDataValidator() - { - RuleFor(x => x.Title).NotNull().NotEmpty(); - RuleFor(x => x.Description).NotNull().NotEmpty(); - RuleFor(x => x.Body).NotNull().NotEmpty(); - } + RuleFor(x => x.Title).NotNull().NotEmpty(); + RuleFor(x => x.Description).NotNull().NotEmpty(); + RuleFor(x => x.Body).NotNull().NotEmpty(); } + } - public record Command(ArticleData Article) : IRequest; + public record Command(ArticleData Article) : IRequest; - public class CommandValidator : AbstractValidator + public class CommandValidator : AbstractValidator + { + public CommandValidator() { - public CommandValidator() - { - RuleFor(x => x.Article).NotNull().SetValidator(new ArticleDataValidator()); - } + RuleFor(x => x.Article).NotNull().SetValidator(new ArticleDataValidator()); } + } - public class Handler : IRequestHandler - { - private readonly ConduitContext _context; - private readonly ICurrentUserAccessor _currentUserAccessor; + public class Handler : IRequestHandler + { + private readonly ConduitContext _context; + private readonly ICurrentUserAccessor _currentUserAccessor; - public Handler(ConduitContext context, ICurrentUserAccessor currentUserAccessor) - { - _context = context; - _currentUserAccessor = currentUserAccessor; - } + public Handler(ConduitContext context, ICurrentUserAccessor currentUserAccessor) + { + _context = context; + _currentUserAccessor = currentUserAccessor; + } - public async Task Handle(Command message, CancellationToken cancellationToken) + public async Task Handle( + Command message, + CancellationToken cancellationToken + ) + { + var author = await _context.Persons.FirstAsync( + x => x.Username == _currentUserAccessor.GetCurrentUsername(), + cancellationToken + ); + var tags = new List(); + foreach (var tag in (message.Article.TagList ?? Enumerable.Empty())) { - var author = await _context.Persons.FirstAsync(x => x.Username == _currentUserAccessor.GetCurrentUsername(), cancellationToken); - var tags = new List(); - foreach (var tag in (message.Article.TagList ?? Enumerable.Empty())) + var t = await _context.Tags.FindAsync(tag); + if (t == null) { - var t = await _context.Tags.FindAsync(tag); - if (t == null) - { - t = new Tag() - { - TagId = tag - }; - await _context.Tags.AddAsync(t, cancellationToken); - //save immediately for reuse - await _context.SaveChangesAsync(cancellationToken); - } - tags.Add(t); + t = new Tag() { TagId = tag }; + await _context.Tags.AddAsync(t, cancellationToken); + //save immediately for reuse + await _context.SaveChangesAsync(cancellationToken); } + tags.Add(t); + } - var article = new Article() - { - Author = author, - Body = message.Article.Body, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow, - Description = message.Article.Description, - Title = message.Article.Title, - Slug = message.Article.Title.GenerateSlug() - }; - await _context.Articles.AddAsync(article, cancellationToken); + var article = new Article() + { + Author = author, + Body = message.Article.Body, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow, + Description = message.Article.Description, + Title = message.Article.Title, + Slug = message.Article.Title.GenerateSlug() + }; + await _context.Articles.AddAsync(article, cancellationToken); - await _context.ArticleTags.AddRangeAsync(tags.Select(x => new ArticleTag() - { - Article = article, - Tag = x - }), cancellationToken); + await _context.ArticleTags.AddRangeAsync( + tags.Select(x => new ArticleTag() { Article = article, Tag = x }), + cancellationToken + ); - await _context.SaveChangesAsync(cancellationToken); + await _context.SaveChangesAsync(cancellationToken); - return new ArticleEnvelope(article); - } + return new ArticleEnvelope(article); } } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Articles/Delete.cs b/src/Conduit/Features/Articles/Delete.cs index ecd9e639..4779caad 100644 --- a/src/Conduit/Features/Articles/Delete.cs +++ b/src/Conduit/Features/Articles/Delete.cs @@ -7,32 +7,38 @@ using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Articles +namespace Conduit.Features.Articles; + +public class Delete { - public class Delete - { - public record Command(string Slug) : IRequest; + public record Command(string Slug) : IRequest; - public class CommandValidator : AbstractValidator - { - public CommandValidator() => RuleFor(x => x.Slug).NotNull().NotEmpty(); - } + public class CommandValidator : AbstractValidator + { + public CommandValidator() => RuleFor(x => x.Slug).NotNull().NotEmpty(); + } - public class QueryHandler : IRequestHandler - { - private readonly ConduitContext _context; + public class QueryHandler : IRequestHandler + { + private readonly ConduitContext _context; - public QueryHandler(ConduitContext context) => _context = context; + public QueryHandler(ConduitContext context) => _context = context; - public async Task Handle(Command message, CancellationToken cancellationToken) - { - var article = await _context.Articles - .FirstOrDefaultAsync(x => x.Slug == message.Slug, cancellationToken) ?? throw new RestException(HttpStatusCode.NotFound, new { Article = Constants.NOT_FOUND }); + public async Task Handle(Command message, CancellationToken cancellationToken) + { + var article = + await _context.Articles.FirstOrDefaultAsync( + x => x.Slug == message.Slug, + cancellationToken + ) + ?? throw new RestException( + HttpStatusCode.NotFound, + new { Article = Constants.NOT_FOUND } + ); - _context.Articles.Remove(article); - await _context.SaveChangesAsync(cancellationToken); - await Task.FromResult(Unit.Value); - } + _context.Articles.Remove(article); + await _context.SaveChangesAsync(cancellationToken); + await Task.FromResult(Unit.Value); } } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Articles/Details.cs b/src/Conduit/Features/Articles/Details.cs index ba8037b2..a6fee96f 100644 --- a/src/Conduit/Features/Articles/Details.cs +++ b/src/Conduit/Features/Articles/Details.cs @@ -7,34 +7,40 @@ using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Articles +namespace Conduit.Features.Articles; + +public class Details { - public class Details + public record Query(string Slug) : IRequest; + + public class QueryValidator : AbstractValidator { - public record Query(string Slug) : IRequest; + public QueryValidator() => RuleFor(x => x.Slug).NotNull().NotEmpty(); + } - public class QueryValidator : AbstractValidator - { - public QueryValidator() => RuleFor(x => x.Slug).NotNull().NotEmpty(); - } + public class QueryHandler : IRequestHandler + { + private readonly ConduitContext _context; - public class QueryHandler : IRequestHandler - { - private readonly ConduitContext _context; + public QueryHandler(ConduitContext context) => _context = context; - public QueryHandler(ConduitContext context) => _context = context; + public async Task Handle( + Query message, + CancellationToken cancellationToken + ) + { + var article = await _context.Articles + .GetAllData() + .FirstOrDefaultAsync(x => x.Slug == message.Slug, cancellationToken); - public async Task Handle(Query message, CancellationToken cancellationToken) + if (article == null) { - var article = await _context.Articles.GetAllData() - .FirstOrDefaultAsync(x => x.Slug == message.Slug, cancellationToken); - - if (article == null) - { - throw new RestException(HttpStatusCode.NotFound, new { Article = Constants.NOT_FOUND }); - } - return new ArticleEnvelope(article); + throw new RestException( + HttpStatusCode.NotFound, + new { Article = Constants.NOT_FOUND } + ); } + return new ArticleEnvelope(article); } } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Articles/Edit.cs b/src/Conduit/Features/Articles/Edit.cs index 24be5db4..f6cfca32 100644 --- a/src/Conduit/Features/Articles/Edit.cs +++ b/src/Conduit/Features/Articles/Edit.cs @@ -11,128 +11,160 @@ using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Articles +namespace Conduit.Features.Articles; + +public class Edit { - public class Edit + public class ArticleData { - public class ArticleData - { - public string? Title { get; set; } + public string? Title { get; set; } + + public string? Description { get; set; } + + public string? Body { get; set; } - public string? Description { get; set; } + public string[]? TagList { get; set; } + } + + public record Command(Model Model, string Slug) : IRequest; - public string? Body { get; set; } + public record Model(ArticleData Article); - public string[]? TagList { get; set; } + public class CommandValidator : AbstractValidator + { + public CommandValidator() + { + RuleFor(x => x.Model.Article).NotNull(); } + } - public record Command(Model Model, string Slug) : IRequest; - public record Model(ArticleData Article); + public class Handler : IRequestHandler + { + private readonly ConduitContext _context; - public class CommandValidator : AbstractValidator + public Handler(ConduitContext context) { - public CommandValidator() - { - RuleFor(x => x.Model.Article).NotNull(); - } + _context = context; } - public class Handler : IRequestHandler + public async Task Handle( + Command message, + CancellationToken cancellationToken + ) { - private readonly ConduitContext _context; + var article = await _context.Articles + .Include(x => x.ArticleTags) // include also the article tags since they also need to be updated + .Where(x => x.Slug == message.Slug) + .FirstOrDefaultAsync(cancellationToken); - public Handler(ConduitContext context) + if (article == null) { - _context = context; + throw new RestException( + HttpStatusCode.NotFound, + new { Article = Constants.NOT_FOUND } + ); } - public async Task Handle(Command message, CancellationToken cancellationToken) - { - var article = await _context.Articles - .Include(x => x.ArticleTags) // include also the article tags since they also need to be updated - .Where(x => x.Slug == message.Slug) - .FirstOrDefaultAsync(cancellationToken); - - if (article == null) - { - throw new RestException(HttpStatusCode.NotFound, new { Article = Constants.NOT_FOUND }); - } - - article.Description = message.Model.Article.Description ?? article.Description; - article.Body = message.Model.Article.Body ?? article.Body; - article.Title = message.Model.Article.Title ?? article.Title; - article.Slug = article.Title.GenerateSlug(); + article.Description = message.Model.Article.Description ?? article.Description; + article.Body = message.Model.Article.Body ?? article.Body; + article.Title = message.Model.Article.Title ?? article.Title; + article.Slug = article.Title.GenerateSlug(); - // list of currently saved article tags for the given article - var articleTagList = message.Model.Article.TagList ?? Enumerable.Empty(); + // list of currently saved article tags for the given article + var articleTagList = message.Model.Article.TagList ?? Enumerable.Empty(); - var articleTagsToCreate = GetArticleTagsToCreate(article, articleTagList); - var articleTagsToDelete = GetArticleTagsToDelete(article, articleTagList); + var articleTagsToCreate = GetArticleTagsToCreate(article, articleTagList); + var articleTagsToDelete = GetArticleTagsToDelete(article, articleTagList); - if (_context.ChangeTracker.Entries().First(x => x.Entity == article).State == EntityState.Modified - || articleTagsToCreate.Any() || articleTagsToDelete.Any()) - { - article.UpdatedAt = DateTime.UtcNow; - } + if ( + _context.ChangeTracker.Entries().First(x => x.Entity == article).State + == EntityState.Modified + || articleTagsToCreate.Any() + || articleTagsToDelete.Any() + ) + { + article.UpdatedAt = DateTime.UtcNow; + } - // ensure context is tracking any tags that are about to be created so that it won't attempt to insert a duplicate - _context.Tags.AttachRange(articleTagsToCreate.Where(x => x.Tag is not null).Select(a => a.Tag!).ToArray()); + // ensure context is tracking any tags that are about to be created so that it won't attempt to insert a duplicate + _context.Tags.AttachRange( + articleTagsToCreate.Where(x => x.Tag is not null).Select(a => a.Tag!).ToArray() + ); - // add the new article tags - await _context.ArticleTags.AddRangeAsync(articleTagsToCreate, cancellationToken); + // add the new article tags + await _context.ArticleTags.AddRangeAsync(articleTagsToCreate, cancellationToken); - // delete the tags that do not exist anymore - _context.ArticleTags.RemoveRange(articleTagsToDelete); + // delete the tags that do not exist anymore + _context.ArticleTags.RemoveRange(articleTagsToDelete); - await _context.SaveChangesAsync(cancellationToken); + await _context.SaveChangesAsync(cancellationToken); - return new ArticleEnvelope(await _context.Articles.GetAllData() + article = + await _context.Articles + .GetAllData() .Where(x => x.Slug == article.Slug) - .FirstOrDefaultAsync(cancellationToken)); + .FirstOrDefaultAsync( + x => x.ArticleId == article.ArticleId, + cancellationToken + ); + if (article is null) + { + throw new RestException( + HttpStatusCode.NotFound, + new { Article = Constants.NOT_FOUND } + ); } - /// - /// check which article tags need to be added - /// - static List GetArticleTagsToCreate(Article article, IEnumerable articleTagList) + return new ArticleEnvelope(article); + } + + /// + /// check which article tags need to be added + /// + private static List GetArticleTagsToCreate( + Article article, + IEnumerable articleTagList + ) + { + var articleTagsToCreate = new List(); + foreach (var tag in articleTagList) { - var articleTagsToCreate = new List(); - foreach (var tag in articleTagList) + var at = article.ArticleTags?.FirstOrDefault(t => t.TagId == tag); + if (at == null) { - var at = article.ArticleTags?.FirstOrDefault(t => t.TagId == tag); - if (at == null) + at = new ArticleTag() { - at = new ArticleTag() - { - Article = article, - ArticleId = article.ArticleId, - Tag = new Tag() { TagId = tag }, - TagId = tag - }; - articleTagsToCreate.Add(at); - } + Article = article, + ArticleId = article.ArticleId, + Tag = new Tag() { TagId = tag }, + TagId = tag + }; + articleTagsToCreate.Add(at); } - - return articleTagsToCreate; } - /// - /// check which article tags need to be deleted - /// - static List GetArticleTagsToDelete(Article article, IEnumerable articleTagList) + return articleTagsToCreate; + } + + /// + /// check which article tags need to be deleted + /// + private static List GetArticleTagsToDelete( + Article article, + IEnumerable articleTagList + ) + { + var articleTagsToDelete = new List(); + foreach (var tag in article.ArticleTags) { - var articleTagsToDelete = new List(); - foreach (var tag in article.ArticleTags) + var at = articleTagList.FirstOrDefault(t => t == tag.TagId); + if (at == null) { - var at = articleTagList.FirstOrDefault(t => t == tag.TagId); - if (at == null) - { - articleTagsToDelete.Add(tag); - } + articleTagsToDelete.Add(tag); } - - return articleTagsToDelete; } + + return articleTagsToDelete; } } } diff --git a/src/Conduit/Features/Articles/List.cs b/src/Conduit/Features/Articles/List.cs index 95943d24..7ef61ded 100644 --- a/src/Conduit/Features/Articles/List.cs +++ b/src/Conduit/Features/Articles/List.cs @@ -1,90 +1,132 @@ using System.Linq; +using System.Net; using System.Threading; using System.Threading.Tasks; using Conduit.Domain; using Conduit.Infrastructure; +using Conduit.Infrastructure.Errors; using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Articles +namespace Conduit.Features.Articles; + +public class List { - public class List + public record Query( + string Tag, + string Author, + string FavoritedUsername, + int? Limit, + int? Offset, + bool IsFeed = false + ) : IRequest; + + public class QueryHandler : IRequestHandler { - public record Query(string Tag, string Author, string FavoritedUsername, int? Limit, int? Offset, bool IsFeed = false) : IRequest; + private readonly ConduitContext _context; + private readonly ICurrentUserAccessor _currentUserAccessor; - public class QueryHandler : IRequestHandler + public QueryHandler(ConduitContext context, ICurrentUserAccessor currentUserAccessor) { - private readonly ConduitContext _context; - private readonly ICurrentUserAccessor _currentUserAccessor; + _context = context; + _currentUserAccessor = currentUserAccessor; + } - public QueryHandler(ConduitContext context, ICurrentUserAccessor currentUserAccessor) - { - _context = context; - _currentUserAccessor = currentUserAccessor; - } + public async Task Handle( + Query message, + CancellationToken cancellationToken + ) + { + var queryable = _context.Articles.GetAllData(); - public async Task Handle(Query message, CancellationToken cancellationToken) + if (message.IsFeed && _currentUserAccessor.GetCurrentUsername() != null) { - IQueryable
queryable = _context.Articles.GetAllData(); + var currentUser = await _context.Persons + .Include(x => x.Following) + .FirstOrDefaultAsync( + x => x.Username == _currentUserAccessor.GetCurrentUsername(), + cancellationToken + ); - if (message.IsFeed && _currentUserAccessor.GetCurrentUsername() != null) + if (currentUser is null) { - var currentUser = await _context.Persons.Include(x => x.Following).FirstOrDefaultAsync(x => x.Username == _currentUserAccessor.GetCurrentUsername(), cancellationToken); - queryable = queryable.Where(x => currentUser.Following.Select(y => y.TargetId).Contains(x.Author!.PersonId)); + throw new RestException( + HttpStatusCode.NotFound, + new { User = Constants.NOT_FOUND } + ); } + queryable = queryable.Where( + x => + currentUser.Following + .Select(y => y.TargetId) + .Contains(x.Author!.PersonId) + ); + } - if (!string.IsNullOrWhiteSpace(message.Tag)) + if (!string.IsNullOrWhiteSpace(message.Tag)) + { + var tag = await _context.ArticleTags.FirstOrDefaultAsync( + x => x.TagId == message.Tag, + cancellationToken + ); + if (tag != null) { - var tag = await _context.ArticleTags.FirstOrDefaultAsync(x => x.TagId == message.Tag, cancellationToken); - if (tag != null) - { - queryable = queryable.Where(x => x.ArticleTags.Select(y => y.TagId).Contains(tag.TagId)); - } - else - { - return new ArticlesEnvelope(); - } + queryable = queryable.Where( + x => x.ArticleTags.Select(y => y.TagId).Contains(tag.TagId) + ); } - - if (!string.IsNullOrWhiteSpace(message.Author)) + else { - var author = await _context.Persons.FirstOrDefaultAsync(x => x.Username == message.Author, cancellationToken); - if (author != null) - { - queryable = queryable.Where(x => x.Author == author); - } - else - { - return new ArticlesEnvelope(); - } + return new ArticlesEnvelope(); } + } - if (!string.IsNullOrWhiteSpace(message.FavoritedUsername)) + if (!string.IsNullOrWhiteSpace(message.Author)) + { + var author = await _context.Persons.FirstOrDefaultAsync( + x => x.Username == message.Author, + cancellationToken + ); + if (author != null) { - var author = await _context.Persons.FirstOrDefaultAsync(x => x.Username == message.FavoritedUsername, cancellationToken); - if (author != null) - { - queryable = queryable.Where(x => x.ArticleFavorites.Any(y => y.PersonId == author.PersonId)); - } - else - { - return new ArticlesEnvelope(); - } + queryable = queryable.Where(x => x.Author == author); } + else + { + return new ArticlesEnvelope(); + } + } - var articles = await queryable - .OrderByDescending(x => x.CreatedAt) - .Skip(message.Offset ?? 0) - .Take(message.Limit ?? 20) - .AsNoTracking() - .ToListAsync(cancellationToken); - - return new ArticlesEnvelope() + if (!string.IsNullOrWhiteSpace(message.FavoritedUsername)) + { + var author = await _context.Persons.FirstOrDefaultAsync( + x => x.Username == message.FavoritedUsername, + cancellationToken + ); + if (author != null) { - Articles = articles, - ArticlesCount = queryable.Count() - }; + queryable = queryable.Where( + x => x.ArticleFavorites.Any(y => y.PersonId == author.PersonId) + ); + } + else + { + return new ArticlesEnvelope(); + } } + + var articles = await queryable + .OrderByDescending(x => x.CreatedAt) + .Skip(message.Offset ?? 0) + .Take(message.Limit ?? 20) + .AsNoTracking() + .ToListAsync(cancellationToken); + + return new ArticlesEnvelope() + { + Articles = articles, + ArticlesCount = queryable.Count() + }; } } } diff --git a/src/Conduit/Features/Comments/CommentEnvelope.cs b/src/Conduit/Features/Comments/CommentEnvelope.cs index 7e65a1fb..9b784580 100644 --- a/src/Conduit/Features/Comments/CommentEnvelope.cs +++ b/src/Conduit/Features/Comments/CommentEnvelope.cs @@ -1,6 +1,5 @@ using Conduit.Domain; -namespace Conduit.Features.Comments -{ - public record CommentEnvelope(Comment Comment); -} +namespace Conduit.Features.Comments; + +public record CommentEnvelope(Comment Comment); \ No newline at end of file diff --git a/src/Conduit/Features/Comments/CommentsController.cs b/src/Conduit/Features/Comments/CommentsController.cs index 81c2fae8..920615f4 100644 --- a/src/Conduit/Features/Comments/CommentsController.cs +++ b/src/Conduit/Features/Comments/CommentsController.cs @@ -5,36 +5,39 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Conduit.Features.Comments +namespace Conduit.Features.Comments; + +[Route("articles")] +public class CommentsController : Controller { - [Route("articles")] - public class CommentsController : Controller - { - private readonly IMediator _mediator; + private readonly IMediator _mediator; - public CommentsController(IMediator mediator) - { - _mediator = mediator; - } + public CommentsController(IMediator mediator) + { + _mediator = mediator; + } - [HttpPost("{slug}/comments")] - [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] - public Task Create(string slug, [FromBody] Create.Model model, CancellationToken cancellationToken) - { - return _mediator.Send(new Create.Command(model, slug), cancellationToken); - } + [HttpPost("{slug}/comments")] + [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] + public Task Create( + string slug, + [FromBody] Create.Model model, + CancellationToken cancellationToken + ) + { + return _mediator.Send(new Create.Command(model, slug), cancellationToken); + } - [HttpGet("{slug}/comments")] - public Task Get(string slug, CancellationToken cancellationToken) - { - return _mediator.Send(new List.Query(slug), cancellationToken); - } + [HttpGet("{slug}/comments")] + public Task Get(string slug, CancellationToken cancellationToken) + { + return _mediator.Send(new List.Query(slug), cancellationToken); + } - [HttpDelete("{slug}/comments/{id}")] - [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] - public Task Delete(string slug, int id, CancellationToken cancellationToken) - { - return _mediator.Send(new Delete.Command(slug, id), cancellationToken); - } + [HttpDelete("{slug}/comments/{id}")] + [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] + public Task Delete(string slug, int id, CancellationToken cancellationToken) + { + return _mediator.Send(new Delete.Command(slug, id), cancellationToken); } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Comments/CommentsEnvelope.cs b/src/Conduit/Features/Comments/CommentsEnvelope.cs index 80e3cfe8..869987ec 100644 --- a/src/Conduit/Features/Comments/CommentsEnvelope.cs +++ b/src/Conduit/Features/Comments/CommentsEnvelope.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using Conduit.Domain; -namespace Conduit.Features.Comments -{ - public record CommentsEnvelope(List Comments); -} +namespace Conduit.Features.Comments; + +public record CommentsEnvelope(List Comments); \ No newline at end of file diff --git a/src/Conduit/Features/Comments/Create.cs b/src/Conduit/Features/Comments/Create.cs index 3f5ba91e..f1c3d369 100644 --- a/src/Conduit/Features/Comments/Create.cs +++ b/src/Conduit/Features/Comments/Create.cs @@ -9,60 +9,71 @@ using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Comments +namespace Conduit.Features.Comments; + +public class Create { - public class Create + public record CommentData(string? Body); + + public record Command(Model Model, string Slug) : IRequest; + + public record Model(CommentData Comment) : IRequest; + + public class CommandValidator : AbstractValidator { - public record CommentData(string? Body); - public record Command(Model Model, string Slug) : IRequest; - public record Model(CommentData Comment) : IRequest; - public class CommandValidator : AbstractValidator + public CommandValidator() { - public CommandValidator() - { - RuleFor(x => x.Model.Comment.Body).NotEmpty(); - } + RuleFor(x => x.Model.Comment.Body).NotEmpty(); } + } + + public class Handler : IRequestHandler + { + private readonly ConduitContext _context; + private readonly ICurrentUserAccessor _currentUserAccessor; - public class Handler : IRequestHandler + public Handler(ConduitContext context, ICurrentUserAccessor currentUserAccessor) { - private readonly ConduitContext _context; - private readonly ICurrentUserAccessor _currentUserAccessor; + _context = context; + _currentUserAccessor = currentUserAccessor; + } - public Handler(ConduitContext context, ICurrentUserAccessor currentUserAccessor) - { - _context = context; - _currentUserAccessor = currentUserAccessor; - } + public async Task Handle( + Command message, + CancellationToken cancellationToken + ) + { + var article = await _context.Articles + .Include(x => x.Comments) + .FirstOrDefaultAsync(x => x.Slug == message.Slug, cancellationToken); - public async Task Handle(Command message, CancellationToken cancellationToken) + if (article == null) { - var article = await _context.Articles - .Include(x => x.Comments) - .FirstOrDefaultAsync(x => x.Slug == message.Slug, cancellationToken); - - if (article == null) - { - throw new RestException(HttpStatusCode.NotFound, new { Article = Constants.NOT_FOUND }); - } + throw new RestException( + HttpStatusCode.NotFound, + new { Article = Constants.NOT_FOUND } + ); + } - var author = await _context.Persons.FirstAsync(x => x.Username == _currentUserAccessor.GetCurrentUsername(), cancellationToken); + var author = await _context.Persons.FirstAsync( + x => x.Username == _currentUserAccessor.GetCurrentUsername(), + cancellationToken + ); - var comment = new Comment() - { - Author = author, - Body = message.Model.Comment.Body ?? string.Empty, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow - }; - await _context.Comments.AddAsync(comment, cancellationToken); + var comment = new Comment() + { + Author = author, + Body = message.Model.Comment.Body ?? string.Empty, + CreatedAt = DateTime.UtcNow, + UpdatedAt = DateTime.UtcNow + }; + await _context.Comments.AddAsync(comment, cancellationToken); - article.Comments.Add(comment); + article.Comments.Add(comment); - await _context.SaveChangesAsync(cancellationToken); + await _context.SaveChangesAsync(cancellationToken); - return new CommentEnvelope(comment); - } + return new CommentEnvelope(comment); } } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Comments/Delete.cs b/src/Conduit/Features/Comments/Delete.cs index 5e022e83..5c59154b 100644 --- a/src/Conduit/Features/Comments/Delete.cs +++ b/src/Conduit/Features/Comments/Delete.cs @@ -8,35 +8,44 @@ using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Comments +namespace Conduit.Features.Comments; + +public class Delete { - public class Delete - { - public record Command(string Slug, int Id) : IRequest; + public record Command(string Slug, int Id) : IRequest; - public class CommandValidator : AbstractValidator - { - public CommandValidator() => RuleFor(x => x.Slug).NotNull().NotEmpty(); - } + public class CommandValidator : AbstractValidator + { + public CommandValidator() => RuleFor(x => x.Slug).NotNull().NotEmpty(); + } - public class QueryHandler : IRequestHandler - { - private readonly ConduitContext _context; + public class QueryHandler : IRequestHandler + { + private readonly ConduitContext _context; - public QueryHandler(ConduitContext context) => _context = context; + public QueryHandler(ConduitContext context) => _context = context; - public async Task Handle(Command message, CancellationToken cancellationToken) - { - var article = await _context.Articles + public async Task Handle(Command message, CancellationToken cancellationToken) + { + var article = + await _context.Articles .Include(x => x.Comments) - .FirstOrDefaultAsync(x => x.Slug == message.Slug, cancellationToken) ?? throw new RestException(HttpStatusCode.NotFound, new { Article = Constants.NOT_FOUND }); + .FirstOrDefaultAsync(x => x.Slug == message.Slug, cancellationToken) + ?? throw new RestException( + HttpStatusCode.NotFound, + new { Article = Constants.NOT_FOUND } + ); - var comment = article.Comments.FirstOrDefault(x => x.CommentId == message.Id) ?? throw new RestException(HttpStatusCode.NotFound, new { Comment = Constants.NOT_FOUND }); + var comment = + article.Comments.FirstOrDefault(x => x.CommentId == message.Id) + ?? throw new RestException( + HttpStatusCode.NotFound, + new { Comment = Constants.NOT_FOUND } + ); - _context.Comments.Remove(comment); - await _context.SaveChangesAsync(cancellationToken); - await Task.FromResult(Unit.Value); - } + _context.Comments.Remove(comment); + await _context.SaveChangesAsync(cancellationToken); + await Task.FromResult(Unit.Value); } } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Comments/List.cs b/src/Conduit/Features/Comments/List.cs index 078507bd..8b02e972 100644 --- a/src/Conduit/Features/Comments/List.cs +++ b/src/Conduit/Features/Comments/List.cs @@ -6,35 +6,40 @@ using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Comments +namespace Conduit.Features.Comments; + +public class List { - public class List + public record Query(string Slug) : IRequest; + + public class QueryHandler : IRequestHandler { - public record Query(string Slug) : IRequest; + private readonly ConduitContext _context; - public class QueryHandler : IRequestHandler + public QueryHandler(ConduitContext context) { - private readonly ConduitContext _context; + _context = context; + } - public QueryHandler(ConduitContext context) - { - _context = context; - } + public async Task Handle( + Query message, + CancellationToken cancellationToken + ) + { + var article = await _context.Articles + .Include(x => x.Comments) + .ThenInclude(x => x.Author) + .FirstOrDefaultAsync(x => x.Slug == message.Slug, cancellationToken); - public async Task Handle(Query message, CancellationToken cancellationToken) + if (article == null) { - var article = await _context.Articles - .Include(x => x.Comments) - .ThenInclude(x => x.Author) - .FirstOrDefaultAsync(x => x.Slug == message.Slug, cancellationToken); - - if (article == null) - { - throw new RestException(HttpStatusCode.NotFound, new { Article = Constants.NOT_FOUND }); - } - - return new CommentsEnvelope(article.Comments); + throw new RestException( + HttpStatusCode.NotFound, + new { Article = Constants.NOT_FOUND } + ); } + + return new CommentsEnvelope(article.Comments); } } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Favorites/Add.cs b/src/Conduit/Features/Favorites/Add.cs index df916b09..95d08745 100644 --- a/src/Conduit/Features/Favorites/Add.cs +++ b/src/Conduit/Features/Favorites/Add.cs @@ -9,60 +9,98 @@ using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Favorites +namespace Conduit.Features.Favorites; + +public class Add { - public class Add + public record Command(string Slug) : IRequest; + + public class CommandValidator : AbstractValidator { - public record Command(string Slug) : IRequest; + public CommandValidator() + { + DefaultValidatorExtensions.NotNull(RuleFor(x => x.Slug)).NotEmpty(); + } + } - public class CommandValidator : AbstractValidator + public class QueryHandler : IRequestHandler + { + private readonly ConduitContext _context; + private readonly ICurrentUserAccessor _currentUserAccessor; + + public QueryHandler(ConduitContext context, ICurrentUserAccessor currentUserAccessor) { - public CommandValidator() - { - DefaultValidatorExtensions.NotNull(RuleFor(x => x.Slug)).NotEmpty(); - } + _context = context; + _currentUserAccessor = currentUserAccessor; } - public class QueryHandler : IRequestHandler + public async Task Handle( + Command message, + CancellationToken cancellationToken + ) { - private readonly ConduitContext _context; - private readonly ICurrentUserAccessor _currentUserAccessor; + var article = await _context.Articles.FirstOrDefaultAsync( + x => x.Slug == message.Slug, + cancellationToken + ); - public QueryHandler(ConduitContext context, ICurrentUserAccessor currentUserAccessor) + if (article == null) { - _context = context; - _currentUserAccessor = currentUserAccessor; + throw new RestException( + HttpStatusCode.NotFound, + new { Article = Constants.NOT_FOUND } + ); } - public async Task Handle(Command message, CancellationToken cancellationToken) + var person = await _context.Persons.FirstOrDefaultAsync( + x => x.Username == _currentUserAccessor.GetCurrentUsername(), + cancellationToken + ); + + if (person is null) { - var article = await _context.Articles.FirstOrDefaultAsync(x => x.Slug == message.Slug, cancellationToken); + throw new RestException( + HttpStatusCode.NotFound, + new { Article = Constants.NOT_FOUND } + ); + } - if (article == null) - { - throw new RestException(HttpStatusCode.NotFound, new { Article = Constants.NOT_FOUND }); - } + var favorite = await _context.ArticleFavorites.FirstOrDefaultAsync( + x => x.ArticleId == article.ArticleId && x.PersonId == person.PersonId, + cancellationToken + ); - var person = await _context.Persons.FirstOrDefaultAsync(x => x.Username == _currentUserAccessor.GetCurrentUsername(), cancellationToken); + if (favorite == null) + { + favorite = new ArticleFavorite() + { + Article = article, + ArticleId = article.ArticleId, + Person = person, + PersonId = person.PersonId + }; + await _context.ArticleFavorites.AddAsync(favorite, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); + } - var favorite = await _context.ArticleFavorites.FirstOrDefaultAsync(x => x.ArticleId == article.ArticleId && x.PersonId == person.PersonId, cancellationToken); - if (favorite == null) - { - favorite = new ArticleFavorite() - { - Article = article, - ArticleId = article.ArticleId, - Person = person, - PersonId = person.PersonId - }; - await _context.ArticleFavorites.AddAsync(favorite, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); - } - return new ArticleEnvelope(await _context.Articles.GetAllData() - .FirstOrDefaultAsync(x => x.ArticleId == article.ArticleId, cancellationToken)); + article = + await _context.Articles + .GetAllData() + .FirstOrDefaultAsync( + x => x.ArticleId == article.ArticleId, + cancellationToken + ); + if (article is null) + { + throw new RestException( + HttpStatusCode.NotFound, + new { Article = Constants.NOT_FOUND } + ); } + + return new ArticleEnvelope(article); } } } diff --git a/src/Conduit/Features/Favorites/Delete.cs b/src/Conduit/Features/Favorites/Delete.cs index 8eb77429..9839b4bd 100644 --- a/src/Conduit/Features/Favorites/Delete.cs +++ b/src/Conduit/Features/Favorites/Delete.cs @@ -8,48 +8,85 @@ using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Favorites +namespace Conduit.Features.Favorites; + +public class Delete { - public class Delete + public record Command(string Slug) : IRequest; + + public class CommandValidator : AbstractValidator + { + public CommandValidator() + { + DefaultValidatorExtensions.NotNull(RuleFor(x => x.Slug)).NotEmpty(); + } + } + + public class QueryHandler : IRequestHandler { - public record Command(string Slug) : IRequest; + private readonly ConduitContext _context; + private readonly ICurrentUserAccessor _currentUserAccessor; - public class CommandValidator : AbstractValidator + public QueryHandler(ConduitContext context, ICurrentUserAccessor currentUserAccessor) { - public CommandValidator() - { - DefaultValidatorExtensions.NotNull(RuleFor(x => x.Slug)).NotEmpty(); - } + _context = context; + _currentUserAccessor = currentUserAccessor; } - public class QueryHandler : IRequestHandler + public async Task Handle( + Command message, + CancellationToken cancellationToken + ) { - private readonly ConduitContext _context; - private readonly ICurrentUserAccessor _currentUserAccessor; + var article = + await _context.Articles.FirstOrDefaultAsync( + x => x.Slug == message.Slug, + cancellationToken + ) + ?? throw new RestException( + HttpStatusCode.NotFound, + new { Article = Constants.NOT_FOUND } + ); - public QueryHandler(ConduitContext context, ICurrentUserAccessor currentUserAccessor) + var person = await _context.Persons.FirstOrDefaultAsync( + x => x.Username == _currentUserAccessor.GetCurrentUsername(), + cancellationToken + ); + if (person is null) { - _context = context; - _currentUserAccessor = currentUserAccessor; + throw new RestException( + HttpStatusCode.NotFound, + new { Article = Constants.NOT_FOUND } + ); } - public async Task Handle(Command message, CancellationToken cancellationToken) - { - var article = await _context.Articles.FirstOrDefaultAsync(x => x.Slug == message.Slug, cancellationToken) ?? throw new RestException(HttpStatusCode.NotFound, new { Article = Constants.NOT_FOUND }); - - var person = await _context.Persons.FirstOrDefaultAsync(x => x.Username == _currentUserAccessor.GetCurrentUsername(), cancellationToken); - - var favorite = await _context.ArticleFavorites.FirstOrDefaultAsync(x => x.ArticleId == article.ArticleId && x.PersonId == person.PersonId, cancellationToken); + var favorite = await _context.ArticleFavorites.FirstOrDefaultAsync( + x => x.ArticleId == article.ArticleId && x.PersonId == person.PersonId, + cancellationToken + ); - if (favorite != null) - { - _context.ArticleFavorites.Remove(favorite); - await _context.SaveChangesAsync(cancellationToken); - } + if (favorite != null) + { + _context.ArticleFavorites.Remove(favorite); + await _context.SaveChangesAsync(cancellationToken); + } - return new ArticleEnvelope(await _context.Articles.GetAllData() - .FirstOrDefaultAsync(x => x.ArticleId == article.ArticleId, cancellationToken)); + article = + await _context.Articles + .GetAllData() + .FirstOrDefaultAsync( + x => x.ArticleId == article.ArticleId, + cancellationToken + ); + if (article is null) + { + throw new RestException( + HttpStatusCode.NotFound, + new { Article = Constants.NOT_FOUND } + ); } + + return new ArticleEnvelope(article); } } } diff --git a/src/Conduit/Features/Favorites/FavoritesController.cs b/src/Conduit/Features/Favorites/FavoritesController.cs index c5c37379..3a70b054 100644 --- a/src/Conduit/Features/Favorites/FavoritesController.cs +++ b/src/Conduit/Features/Favorites/FavoritesController.cs @@ -6,30 +6,32 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Conduit.Features.Favorites +namespace Conduit.Features.Favorites; + +[Route("articles")] +public class FavoritesController : Controller { - [Route("articles")] - public class FavoritesController : Controller - { - private readonly IMediator _mediator; + private readonly IMediator _mediator; - public FavoritesController(IMediator mediator) - { - _mediator = mediator; - } + public FavoritesController(IMediator mediator) + { + _mediator = mediator; + } - [HttpPost("{slug}/favorite")] - [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] - public Task FavoriteAdd(string slug, CancellationToken cancellationToken) - { - return _mediator.Send(new Add.Command(slug), cancellationToken); - } + [HttpPost("{slug}/favorite")] + [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] + public Task FavoriteAdd(string slug, CancellationToken cancellationToken) + { + return _mediator.Send(new Add.Command(slug), cancellationToken); + } - [HttpDelete("{slug}/favorite")] - [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] - public Task FavoriteDelete(string slug, CancellationToken cancellationToken) - { - return _mediator.Send(new Delete.Command(slug), cancellationToken); - } + [HttpDelete("{slug}/favorite")] + [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] + public Task FavoriteDelete( + string slug, + CancellationToken cancellationToken + ) + { + return _mediator.Send(new Delete.Command(slug), cancellationToken); } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Followers/Add.cs b/src/Conduit/Features/Followers/Add.cs index f8ab2556..0d07bd59 100644 --- a/src/Conduit/Features/Followers/Add.cs +++ b/src/Conduit/Features/Followers/Add.cs @@ -9,61 +9,87 @@ using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Followers +namespace Conduit.Features.Followers; + +public class Add { - public class Add + public record Command(string Username) : IRequest; + + public class CommandValidator : AbstractValidator { - public record Command(string Username) : IRequest; + public CommandValidator() + { + DefaultValidatorExtensions.NotNull(RuleFor(x => x.Username)).NotEmpty(); + } + } - public class CommandValidator : AbstractValidator + public class QueryHandler : IRequestHandler + { + private readonly ConduitContext _context; + private readonly ICurrentUserAccessor _currentUserAccessor; + private readonly IProfileReader _profileReader; + + public QueryHandler( + ConduitContext context, + ICurrentUserAccessor currentUserAccessor, + IProfileReader profileReader + ) { - public CommandValidator() - { - DefaultValidatorExtensions.NotNull(RuleFor(x => x.Username)).NotEmpty(); - } + _context = context; + _currentUserAccessor = currentUserAccessor; + _profileReader = profileReader; } - public class QueryHandler : IRequestHandler + public async Task Handle( + Command message, + CancellationToken cancellationToken + ) { - private readonly ConduitContext _context; - private readonly ICurrentUserAccessor _currentUserAccessor; - private readonly IProfileReader _profileReader; + var target = await _context.Persons.FirstOrDefaultAsync( + x => x.Username == message.Username, + cancellationToken + ); - public QueryHandler(ConduitContext context, ICurrentUserAccessor currentUserAccessor, IProfileReader profileReader) + if (target is null) { - _context = context; - _currentUserAccessor = currentUserAccessor; - _profileReader = profileReader; + throw new RestException( + HttpStatusCode.NotFound, + new { User = Constants.NOT_FOUND } + ); } - public async Task Handle(Command message, CancellationToken cancellationToken) - { - var target = await _context.Persons.FirstOrDefaultAsync(x => x.Username == message.Username, cancellationToken); - - if (target == null) - { - throw new RestException(HttpStatusCode.NotFound, new { User = Constants.NOT_FOUND }); - } + var observer = await _context.Persons.FirstOrDefaultAsync( + x => x.Username == _currentUserAccessor.GetCurrentUsername(), + cancellationToken + ); - var observer = await _context.Persons.FirstOrDefaultAsync(x => x.Username == _currentUserAccessor.GetCurrentUsername(), cancellationToken); + if (observer is null) + { + throw new RestException( + HttpStatusCode.NotFound, + new { User = Constants.NOT_FOUND } + ); + } - var followedPeople = await _context.FollowedPeople.FirstOrDefaultAsync(x => x.ObserverId == observer.PersonId && x.TargetId == target.PersonId, cancellationToken); + var followedPeople = await _context.FollowedPeople.FirstOrDefaultAsync( + x => x.ObserverId == observer.PersonId && x.TargetId == target.PersonId, + cancellationToken + ); - if (followedPeople == null) + if (followedPeople == null) + { + followedPeople = new FollowedPeople() { - followedPeople = new FollowedPeople() - { - Observer = observer, - ObserverId = observer.PersonId, - Target = target, - TargetId = target.PersonId - }; - await _context.FollowedPeople.AddAsync(followedPeople, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); - } - - return await _profileReader.ReadProfile(message.Username, cancellationToken); + Observer = observer, + ObserverId = observer.PersonId, + Target = target, + TargetId = target.PersonId + }; + await _context.FollowedPeople.AddAsync(followedPeople, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); } + + return await _profileReader.ReadProfile(message.Username, cancellationToken); } } } diff --git a/src/Conduit/Features/Followers/Delete.cs b/src/Conduit/Features/Followers/Delete.cs index da510eab..7ef62e5c 100644 --- a/src/Conduit/Features/Followers/Delete.cs +++ b/src/Conduit/Features/Followers/Delete.cs @@ -8,54 +8,80 @@ using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Followers +namespace Conduit.Features.Followers; + +public class Delete { - public class Delete + public record Command(string Username) : IRequest; + + public class CommandValidator : AbstractValidator + { + public CommandValidator() + { + DefaultValidatorExtensions.NotNull(RuleFor(x => x.Username)).NotEmpty(); + } + } + + public class QueryHandler : IRequestHandler { - public record Command(string Username) : IRequest; + private readonly ConduitContext _context; + private readonly ICurrentUserAccessor _currentUserAccessor; + private readonly IProfileReader _profileReader; - public class CommandValidator : AbstractValidator + public QueryHandler( + ConduitContext context, + ICurrentUserAccessor currentUserAccessor, + IProfileReader profileReader + ) { - public CommandValidator() - { - DefaultValidatorExtensions.NotNull(RuleFor(x => x.Username)).NotEmpty(); - } + _context = context; + _currentUserAccessor = currentUserAccessor; + _profileReader = profileReader; } - public class QueryHandler : IRequestHandler + public async Task Handle( + Command message, + CancellationToken cancellationToken + ) { - private readonly ConduitContext _context; - private readonly ICurrentUserAccessor _currentUserAccessor; - private readonly IProfileReader _profileReader; + var target = await _context.Persons.FirstOrDefaultAsync( + x => x.Username == message.Username, + cancellationToken + ); - public QueryHandler(ConduitContext context, ICurrentUserAccessor currentUserAccessor, IProfileReader profileReader) + if (target is null) { - _context = context; - _currentUserAccessor = currentUserAccessor; - _profileReader = profileReader; + throw new RestException( + HttpStatusCode.NotFound, + new { User = Constants.NOT_FOUND } + ); } - public async Task Handle(Command message, CancellationToken cancellationToken) - { - var target = await _context.Persons.FirstOrDefaultAsync(x => x.Username == message.Username, cancellationToken); - - if (target == null) - { - throw new RestException(HttpStatusCode.NotFound, new { User = Constants.NOT_FOUND }); - } - - var observer = await _context.Persons.FirstOrDefaultAsync(x => x.Username == _currentUserAccessor.GetCurrentUsername(), cancellationToken); + var observer = await _context.Persons.FirstOrDefaultAsync( + x => x.Username == _currentUserAccessor.GetCurrentUsername(), + cancellationToken + ); - var followedPeople = await _context.FollowedPeople.FirstOrDefaultAsync(x => x.ObserverId == observer.PersonId && x.TargetId == target.PersonId, cancellationToken); + if (observer is null) + { + throw new RestException( + HttpStatusCode.NotFound, + new { User = Constants.NOT_FOUND } + ); + } - if (followedPeople != null) - { - _context.FollowedPeople.Remove(followedPeople); - await _context.SaveChangesAsync(cancellationToken); - } + var followedPeople = await _context.FollowedPeople.FirstOrDefaultAsync( + x => x.ObserverId == observer.PersonId && x.TargetId == target.PersonId, + cancellationToken + ); - return await _profileReader.ReadProfile(message.Username, cancellationToken); + if (followedPeople != null) + { + _context.FollowedPeople.Remove(followedPeople); + await _context.SaveChangesAsync(cancellationToken); } + + return await _profileReader.ReadProfile(message.Username, cancellationToken); } } } diff --git a/src/Conduit/Features/Followers/FollowersController.cs b/src/Conduit/Features/Followers/FollowersController.cs index 2513a0f1..e011de98 100644 --- a/src/Conduit/Features/Followers/FollowersController.cs +++ b/src/Conduit/Features/Followers/FollowersController.cs @@ -6,30 +6,29 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Conduit.Features.Followers +namespace Conduit.Features.Followers; + +[Route("profiles")] +public class FollowersController : Controller { - [Route("profiles")] - public class FollowersController : Controller - { - private readonly IMediator _mediator; + private readonly IMediator _mediator; - public FollowersController(IMediator mediator) - { - _mediator = mediator; - } + public FollowersController(IMediator mediator) + { + _mediator = mediator; + } - [HttpPost("{username}/follow")] - [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] - public Task Follow(string username, CancellationToken cancellationToken) - { - return _mediator.Send(new Add.Command(username), cancellationToken); - } + [HttpPost("{username}/follow")] + [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] + public Task Follow(string username, CancellationToken cancellationToken) + { + return _mediator.Send(new Add.Command(username), cancellationToken); + } - [HttpDelete("{username}/follow")] - [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] - public Task Unfollow(string username, CancellationToken cancellationToken) - { - return _mediator.Send(new Delete.Command(username), cancellationToken); - } + [HttpDelete("{username}/follow")] + [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] + public Task Unfollow(string username, CancellationToken cancellationToken) + { + return _mediator.Send(new Delete.Command(username), cancellationToken); } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Profiles/Details.cs b/src/Conduit/Features/Profiles/Details.cs index 7a71cc4b..511ddfe4 100644 --- a/src/Conduit/Features/Profiles/Details.cs +++ b/src/Conduit/Features/Profiles/Details.cs @@ -3,33 +3,32 @@ using FluentValidation; using MediatR; -namespace Conduit.Features.Profiles +namespace Conduit.Features.Profiles; + +public class Details { - public class Details - { - public record Query(string Username) : IRequest; + public record Query(string Username) : IRequest; - public class QueryValidator : AbstractValidator + public class QueryValidator : AbstractValidator + { + public QueryValidator() { - public QueryValidator() - { - RuleFor(x => x.Username).NotEmpty(); - } + RuleFor(x => x.Username).NotEmpty(); } + } - public class QueryHandler : IRequestHandler - { - private readonly IProfileReader _profileReader; + public class QueryHandler : IRequestHandler + { + private readonly IProfileReader _profileReader; - public QueryHandler(IProfileReader profileReader) - { - _profileReader = profileReader; - } + public QueryHandler(IProfileReader profileReader) + { + _profileReader = profileReader; + } - public Task Handle(Query message, CancellationToken cancellationToken) - { - return _profileReader.ReadProfile(message.Username, cancellationToken); - } + public Task Handle(Query message, CancellationToken cancellationToken) + { + return _profileReader.ReadProfile(message.Username, cancellationToken); } } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Profiles/IProfileReader.cs b/src/Conduit/Features/Profiles/IProfileReader.cs index a33ae675..87eb7ae5 100644 --- a/src/Conduit/Features/Profiles/IProfileReader.cs +++ b/src/Conduit/Features/Profiles/IProfileReader.cs @@ -1,10 +1,9 @@ using System.Threading; using System.Threading.Tasks; -namespace Conduit.Features.Profiles +namespace Conduit.Features.Profiles; + +public interface IProfileReader { - public interface IProfileReader - { - Task ReadProfile(string username, CancellationToken cancellationToken); - } -} + Task ReadProfile(string username, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/Conduit/Features/Profiles/MappingProfile.cs b/src/Conduit/Features/Profiles/MappingProfile.cs index b65e664e..7733f03f 100644 --- a/src/Conduit/Features/Profiles/MappingProfile.cs +++ b/src/Conduit/Features/Profiles/MappingProfile.cs @@ -1,12 +1,11 @@ using AutoMapper; -namespace Conduit.Features.Profiles +namespace Conduit.Features.Profiles; + +public class MappingProfile : AutoMapper.Profile { - public class MappingProfile : AutoMapper.Profile + public MappingProfile() { - public MappingProfile() - { - CreateMap(MemberList.None); - } + CreateMap(MemberList.None); } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Profiles/Profile.cs b/src/Conduit/Features/Profiles/Profile.cs index ac04b07b..c76eacc9 100644 --- a/src/Conduit/Features/Profiles/Profile.cs +++ b/src/Conduit/Features/Profiles/Profile.cs @@ -1,16 +1,15 @@ using System.Text.Json.Serialization; -namespace Conduit.Features.Profiles +namespace Conduit.Features.Profiles; + +public class Profile { - public class Profile - { - public string? Username { get; set; } + public string? Username { get; set; } - public string? Bio { get; set; } + public string? Bio { get; set; } - public string? Image { get; set; } + public string? Image { get; set; } - [JsonPropertyName("following")] - public bool IsFollowed { get; set; } - } -} + [JsonPropertyName("following")] + public bool IsFollowed { get; set; } +} \ No newline at end of file diff --git a/src/Conduit/Features/Profiles/ProfileEnvelope.cs b/src/Conduit/Features/Profiles/ProfileEnvelope.cs index 47e46330..ce1c42b7 100644 --- a/src/Conduit/Features/Profiles/ProfileEnvelope.cs +++ b/src/Conduit/Features/Profiles/ProfileEnvelope.cs @@ -1,4 +1,3 @@ -namespace Conduit.Features.Profiles -{ - public record ProfileEnvelope(Profile Profile); -} +namespace Conduit.Features.Profiles; + +public record ProfileEnvelope(Profile Profile); \ No newline at end of file diff --git a/src/Conduit/Features/Profiles/ProfileReader.cs b/src/Conduit/Features/Profiles/ProfileReader.cs index 2bd0cd28..613d542b 100644 --- a/src/Conduit/Features/Profiles/ProfileReader.cs +++ b/src/Conduit/Features/Profiles/ProfileReader.cs @@ -7,48 +7,73 @@ using Conduit.Infrastructure.Errors; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Profiles +namespace Conduit.Features.Profiles; + +public class ProfileReader : IProfileReader { - public class ProfileReader : IProfileReader + private readonly ConduitContext _context; + private readonly ICurrentUserAccessor _currentUserAccessor; + private readonly IMapper _mapper; + + public ProfileReader( + ConduitContext context, + ICurrentUserAccessor currentUserAccessor, + IMapper mapper + ) + { + _context = context; + _currentUserAccessor = currentUserAccessor; + _mapper = mapper; + } + + public async Task ReadProfile( + string username, + CancellationToken cancellationToken + ) { - private readonly ConduitContext _context; - private readonly ICurrentUserAccessor _currentUserAccessor; - private readonly IMapper _mapper; + var currentUserName = _currentUserAccessor.GetCurrentUsername(); - public ProfileReader(ConduitContext context, ICurrentUserAccessor currentUserAccessor, IMapper mapper) + var person = await _context.Persons + .AsNoTracking() + .FirstOrDefaultAsync(x => x.Username == username, cancellationToken); + if (person is null) { - _context = context; - _currentUserAccessor = currentUserAccessor; - _mapper = mapper; + throw new RestException( + HttpStatusCode.NotFound, + new { User = Constants.NOT_FOUND } + ); } - public async Task ReadProfile(string username, CancellationToken cancellationToken) + if (person == null) { - var currentUserName = _currentUserAccessor.GetCurrentUsername(); + throw new RestException( + HttpStatusCode.NotFound, + new { User = Constants.NOT_FOUND } + ); + } + var profile = _mapper.Map(person); - var person = await _context.Persons.AsNoTracking() - .FirstOrDefaultAsync(x => x.Username == username, cancellationToken); + if (currentUserName != null) + { + var currentPerson = await _context.Persons + .Include(x => x.Following) + .Include(x => x.Followers) + .FirstOrDefaultAsync(x => x.Username == currentUserName, cancellationToken); - if (person == null) + if (currentPerson is null) { - throw new RestException(HttpStatusCode.NotFound, new { User = Constants.NOT_FOUND }); + throw new RestException( + HttpStatusCode.NotFound, + new { User = Constants.NOT_FOUND } + ); } - var profile = _mapper.Map(person); - if (currentUserName != null) + if (currentPerson.Followers.Any(x => x.TargetId == person.PersonId)) { - var currentPerson = await _context.Persons - .Include(x => x.Following) - .Include(x => x.Followers) - .FirstOrDefaultAsync(x => x.Username == currentUserName, cancellationToken); - - if (currentPerson.Followers.Any(x => x.TargetId == person.PersonId)) - { - profile.IsFollowed = true; - } + profile.IsFollowed = true; } - - return new ProfileEnvelope(profile); } + + return new ProfileEnvelope(profile); } } diff --git a/src/Conduit/Features/Profiles/ProfilesController.cs b/src/Conduit/Features/Profiles/ProfilesController.cs index 84b693f6..4dc16597 100644 --- a/src/Conduit/Features/Profiles/ProfilesController.cs +++ b/src/Conduit/Features/Profiles/ProfilesController.cs @@ -3,22 +3,21 @@ using MediatR; using Microsoft.AspNetCore.Mvc; -namespace Conduit.Features.Profiles +namespace Conduit.Features.Profiles; + +[Route("profiles")] +public class ProfilesController : Controller { - [Route("profiles")] - public class ProfilesController : Controller - { - private readonly IMediator _mediator; + private readonly IMediator _mediator; - public ProfilesController(IMediator mediator) - { - _mediator = mediator; - } + public ProfilesController(IMediator mediator) + { + _mediator = mediator; + } - [HttpGet("{username}")] - public Task Get(string username, CancellationToken cancellationToken) - { - return _mediator.Send(new Details.Query(username), cancellationToken); - } + [HttpGet("{username}")] + public Task Get(string username, CancellationToken cancellationToken) + { + return _mediator.Send(new Details.Query(username), cancellationToken); } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Tags/List.cs b/src/Conduit/Features/Tags/List.cs index 1e97ae50..c10eb79e 100644 --- a/src/Conduit/Features/Tags/List.cs +++ b/src/Conduit/Features/Tags/List.cs @@ -6,29 +6,34 @@ using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Tags +namespace Conduit.Features.Tags; + +public class List { - public class List + public record Query : IRequest; + + public class QueryHandler : IRequestHandler { - public record Query : IRequest; + private readonly ConduitContext _context; - public class QueryHandler : IRequestHandler + public QueryHandler(ConduitContext context) { - private readonly ConduitContext _context; - - public QueryHandler(ConduitContext context) - { - _context = context; - } + _context = context; + } - public async Task Handle(Query message, CancellationToken cancellationToken) + public async Task Handle( + Query message, + CancellationToken cancellationToken + ) + { + var tags = await _context.Tags + .OrderBy(x => x.TagId) + .AsNoTracking() + .ToListAsync(cancellationToken); + return new TagsEnvelope() { - var tags = await _context.Tags.OrderBy(x => x.TagId).AsNoTracking().ToListAsync(cancellationToken); - return new TagsEnvelope() - { - Tags = tags?.Select(x => x.TagId ?? string.Empty).ToList() ?? new List() - }; - } + Tags = tags?.Select(x => x.TagId ?? string.Empty).ToList() ?? new List() + }; } } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Tags/TagsController.cs b/src/Conduit/Features/Tags/TagsController.cs index 17a86ba1..5ec11a48 100644 --- a/src/Conduit/Features/Tags/TagsController.cs +++ b/src/Conduit/Features/Tags/TagsController.cs @@ -3,22 +3,21 @@ using MediatR; using Microsoft.AspNetCore.Mvc; -namespace Conduit.Features.Tags +namespace Conduit.Features.Tags; + +[Route("tags")] +public class TagsController : Controller { - [Route("tags")] - public class TagsController : Controller - { - private readonly IMediator _mediator; + private readonly IMediator _mediator; - public TagsController(IMediator mediator) - { - _mediator = mediator; - } + public TagsController(IMediator mediator) + { + _mediator = mediator; + } - [HttpGet] - public Task Get(CancellationToken cancellationToken) - { - return _mediator.Send(new List.Query(), cancellationToken); - } + [HttpGet] + public Task Get(CancellationToken cancellationToken) + { + return _mediator.Send(new List.Query(), cancellationToken); } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Tags/TagsEnvelope.cs b/src/Conduit/Features/Tags/TagsEnvelope.cs index 13e14f94..1f4da691 100644 --- a/src/Conduit/Features/Tags/TagsEnvelope.cs +++ b/src/Conduit/Features/Tags/TagsEnvelope.cs @@ -1,9 +1,8 @@ using System.Collections.Generic; -namespace Conduit.Features.Tags +namespace Conduit.Features.Tags; + +public class TagsEnvelope { - public class TagsEnvelope - { - public List Tags { get; set; } = new(); - } -} + public List Tags { get; set; } = new(); +} \ No newline at end of file diff --git a/src/Conduit/Features/Users/Create.cs b/src/Conduit/Features/Users/Create.cs index cd6638df..a44b14b4 100644 --- a/src/Conduit/Features/Users/Create.cs +++ b/src/Conduit/Features/Users/Create.cs @@ -12,74 +12,100 @@ using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Users +namespace Conduit.Features.Users; + +public class Create { - public class Create + public class UserData { - public class UserData - { - public string? Username { get; set; } + public string? Username { get; set; } - public string? Email { get; set; } + public string? Email { get; set; } - public string? Password { get; set; } - } + public string? Password { get; set; } + } - public record Command(UserData User) : IRequest; + public record Command(UserData User) : IRequest; - public class CommandValidator : AbstractValidator + public class CommandValidator : AbstractValidator + { + public CommandValidator() { - public CommandValidator() - { - RuleFor(x => x.User.Username).NotNull().NotEmpty(); - RuleFor(x => x.User.Email).NotNull().NotEmpty(); - RuleFor(x => x.User.Password).NotNull().NotEmpty(); - } + RuleFor(x => x.User.Username).NotNull().NotEmpty(); + RuleFor(x => x.User.Email).NotNull().NotEmpty(); + RuleFor(x => x.User.Password).NotNull().NotEmpty(); } + } - public class Handler : IRequestHandler + public class Handler : IRequestHandler + { + private readonly ConduitContext _context; + private readonly IPasswordHasher _passwordHasher; + private readonly IJwtTokenGenerator _jwtTokenGenerator; + private readonly IMapper _mapper; + + public Handler( + ConduitContext context, + IPasswordHasher passwordHasher, + IJwtTokenGenerator jwtTokenGenerator, + IMapper mapper + ) { - private readonly ConduitContext _context; - private readonly IPasswordHasher _passwordHasher; - private readonly IJwtTokenGenerator _jwtTokenGenerator; - private readonly IMapper _mapper; + _context = context; + _passwordHasher = passwordHasher; + _jwtTokenGenerator = jwtTokenGenerator; + _mapper = mapper; + } - public Handler(ConduitContext context, IPasswordHasher passwordHasher, IJwtTokenGenerator jwtTokenGenerator, IMapper mapper) + public async Task Handle( + Command message, + CancellationToken cancellationToken + ) + { + if ( + await _context.Persons + .Where(x => x.Username == message.User.Username) + .AnyAsync(cancellationToken) + ) { - _context = context; - _passwordHasher = passwordHasher; - _jwtTokenGenerator = jwtTokenGenerator; - _mapper = mapper; + throw new RestException( + HttpStatusCode.BadRequest, + new { Username = Constants.IN_USE } + ); } - public async Task Handle(Command message, CancellationToken cancellationToken) + if ( + await _context.Persons + .Where(x => x.Email == message.User.Email) + .AnyAsync(cancellationToken) + ) { - if (await _context.Persons.Where(x => x.Username == message.User.Username).AnyAsync(cancellationToken)) - { - throw new RestException(HttpStatusCode.BadRequest, new { Username = Constants.IN_USE }); - } - - if (await _context.Persons.Where(x => x.Email == message.User.Email).AnyAsync(cancellationToken)) - { - throw new RestException(HttpStatusCode.BadRequest, new { Email = Constants.IN_USE }); - } + throw new RestException( + HttpStatusCode.BadRequest, + new { Email = Constants.IN_USE } + ); + } - var salt = Guid.NewGuid().ToByteArray(); - var person = new Person - { - Username = message.User.Username, - Email = message.User.Email, - Hash = await _passwordHasher.Hash(message.User.Password ?? throw new InvalidOperationException(), salt), - Salt = salt - }; + var salt = Guid.NewGuid().ToByteArray(); + var person = new Person + { + Username = message.User.Username, + Email = message.User.Email, + Hash = await _passwordHasher.Hash( + message.User.Password ?? throw new InvalidOperationException(), + salt + ), + Salt = salt + }; - await _context.Persons.AddAsync(person, cancellationToken); - await _context.SaveChangesAsync(cancellationToken); + await _context.Persons.AddAsync(person, cancellationToken); + await _context.SaveChangesAsync(cancellationToken); - var user = _mapper.Map(person); - user.Token = _jwtTokenGenerator.CreateToken(person.Username ?? throw new InvalidOperationException()); - return new UserEnvelope(user); - } + var user = _mapper.Map(person); + user.Token = _jwtTokenGenerator.CreateToken( + person.Username ?? throw new InvalidOperationException() + ); + return new UserEnvelope(user); } } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Users/Details.cs b/src/Conduit/Features/Users/Details.cs index 7a1705f4..997c5f4c 100644 --- a/src/Conduit/Features/Users/Details.cs +++ b/src/Conduit/Features/Users/Details.cs @@ -10,48 +10,59 @@ using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Users +namespace Conduit.Features.Users; + +public class Details { - public class Details + public record Query(string Username) : IRequest; + + public class QueryValidator : AbstractValidator { - public record Query(string Username) : IRequest; + public QueryValidator() + { + RuleFor(x => x.Username).NotNull().NotEmpty(); + } + } - public class QueryValidator : AbstractValidator + public class QueryHandler : IRequestHandler + { + private readonly ConduitContext _context; + private readonly IJwtTokenGenerator _jwtTokenGenerator; + private readonly IMapper _mapper; + + public QueryHandler( + ConduitContext context, + IJwtTokenGenerator jwtTokenGenerator, + IMapper mapper + ) { - public QueryValidator() - { - RuleFor(x => x.Username).NotNull().NotEmpty(); - } + _context = context; + _jwtTokenGenerator = jwtTokenGenerator; + _mapper = mapper; } - public class QueryHandler : IRequestHandler + public async Task Handle( + Query message, + CancellationToken cancellationToken + ) { - private readonly ConduitContext _context; - private readonly IJwtTokenGenerator _jwtTokenGenerator; - private readonly IMapper _mapper; + var person = await _context.Persons + .AsNoTracking() + .FirstOrDefaultAsync(x => x.Username == message.Username, cancellationToken); - public QueryHandler(ConduitContext context, IJwtTokenGenerator jwtTokenGenerator, IMapper mapper) + if (person == null) { - _context = context; - _jwtTokenGenerator = jwtTokenGenerator; - _mapper = mapper; + throw new RestException( + HttpStatusCode.NotFound, + new { User = Constants.NOT_FOUND } + ); } - public async Task Handle(Query message, CancellationToken cancellationToken) - { - var person = await _context.Persons - .AsNoTracking() - .FirstOrDefaultAsync(x => x.Username == message.Username, cancellationToken); - - if (person == null) - { - throw new RestException(HttpStatusCode.NotFound, new { User = Constants.NOT_FOUND }); - } - - var user = _mapper.Map(person); - user.Token = _jwtTokenGenerator.CreateToken(person.Username ?? throw new InvalidOperationException()); - return new UserEnvelope(user); - } + var user = _mapper.Map(person); + user.Token = _jwtTokenGenerator.CreateToken( + person.Username ?? throw new InvalidOperationException() + ); + return new UserEnvelope(user); } } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Users/Edit.cs b/src/Conduit/Features/Users/Edit.cs index 7901cb87..e7fb845b 100644 --- a/src/Conduit/Features/Users/Edit.cs +++ b/src/Conduit/Features/Users/Edit.cs @@ -1,78 +1,95 @@ using System; using System.Linq; +using System.Net; using System.Threading; using System.Threading.Tasks; using AutoMapper; using Conduit.Infrastructure; +using Conduit.Infrastructure.Errors; using Conduit.Infrastructure.Security; using FluentValidation; using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Users +namespace Conduit.Features.Users; + +public class Edit { - public class Edit + public class UserData { - public class UserData - { - public string? Username { get; set; } + public string? Username { get; set; } - public string? Email { get; set; } + public string? Email { get; set; } - public string? Password { get; set; } + public string? Password { get; set; } - public string? Bio { get; set; } + public string? Bio { get; set; } - public string? Image { get; set; } - } + public string? Image { get; set; } + } - public record Command(UserData User) : IRequest; + public record Command(UserData User) : IRequest; - public class CommandValidator : AbstractValidator + public class CommandValidator : AbstractValidator + { + public CommandValidator() { - public CommandValidator() - { - RuleFor(x => x.User).NotNull(); - } + RuleFor(x => x.User).NotNull(); } + } - public class Handler : IRequestHandler + public class Handler : IRequestHandler + { + private readonly ConduitContext _context; + private readonly IPasswordHasher _passwordHasher; + private readonly ICurrentUserAccessor _currentUserAccessor; + private readonly IMapper _mapper; + + public Handler( + ConduitContext context, + IPasswordHasher passwordHasher, + ICurrentUserAccessor currentUserAccessor, + IMapper mapper + ) { - private readonly ConduitContext _context; - private readonly IPasswordHasher _passwordHasher; - private readonly ICurrentUserAccessor _currentUserAccessor; - private readonly IMapper _mapper; + _context = context; + _passwordHasher = passwordHasher; + _currentUserAccessor = currentUserAccessor; + _mapper = mapper; + } - public Handler(ConduitContext context, IPasswordHasher passwordHasher, - ICurrentUserAccessor currentUserAccessor, IMapper mapper) + public async Task Handle( + Command message, + CancellationToken cancellationToken + ) + { + var currentUsername = _currentUserAccessor.GetCurrentUsername(); + var person = await _context.Persons + .Where(x => x.Username == currentUsername) + .FirstOrDefaultAsync(cancellationToken); + if (person is null) { - _context = context; - _passwordHasher = passwordHasher; - _currentUserAccessor = currentUserAccessor; - _mapper = mapper; + throw new RestException( + HttpStatusCode.NotFound, + new { User = Constants.NOT_FOUND } + ); } - public async Task Handle(Command message, CancellationToken cancellationToken) - { - var currentUsername = _currentUserAccessor.GetCurrentUsername(); - var person = await _context.Persons.Where(x => x.Username == currentUsername).FirstOrDefaultAsync(cancellationToken); - - person.Username = message.User.Username ?? person.Username; - person.Email = message.User.Email ?? person.Email; - person.Bio = message.User.Bio ?? person.Bio; - person.Image = message.User.Image ?? person.Image; + person.Username = message.User.Username ?? person.Username; + person.Email = message.User.Email ?? person.Email; + person.Bio = message.User.Bio ?? person.Bio; + person.Image = message.User.Image ?? person.Image; - if (!string.IsNullOrWhiteSpace(message.User.Password)) - { - var salt = Guid.NewGuid().ToByteArray(); - person.Hash = await _passwordHasher.Hash(message.User.Password, salt); - person.Salt = salt; - } + if (!string.IsNullOrWhiteSpace(message.User.Password)) + { + var salt = Guid.NewGuid().ToByteArray(); + person.Hash = await _passwordHasher.Hash(message.User.Password, salt); + person.Salt = salt; + } - await _context.SaveChangesAsync(cancellationToken); + await _context.SaveChangesAsync(cancellationToken); - return new UserEnvelope(_mapper.Map(person)); - } + return new UserEnvelope(_mapper.Map(person)); } } } diff --git a/src/Conduit/Features/Users/Login.cs b/src/Conduit/Features/Users/Login.cs index d238672e..3d80e9de 100644 --- a/src/Conduit/Features/Users/Login.cs +++ b/src/Conduit/Features/Users/Login.cs @@ -11,61 +11,85 @@ using MediatR; using Microsoft.EntityFrameworkCore; -namespace Conduit.Features.Users +namespace Conduit.Features.Users; + +public class Login { - public class Login + public class UserData { - public class UserData - { - public string? Email { get; set; } + public string? Email { get; set; } - public string? Password { get; set; } - } + public string? Password { get; set; } + } - public record Command(UserData User) : IRequest; + public record Command(UserData User) : IRequest; - public class CommandValidator : AbstractValidator + public class CommandValidator : AbstractValidator + { + public CommandValidator() { - public CommandValidator() - { - RuleFor(x => x.User).NotNull(); - RuleFor(x => x.User.Email).NotNull().NotEmpty(); - RuleFor(x => x.User.Password).NotNull().NotEmpty(); - } + RuleFor(x => x.User).NotNull(); + RuleFor(x => x.User.Email).NotNull().NotEmpty(); + RuleFor(x => x.User.Password).NotNull().NotEmpty(); } + } - public class Handler : IRequestHandler + public class Handler : IRequestHandler + { + private readonly ConduitContext _context; + private readonly IPasswordHasher _passwordHasher; + private readonly IJwtTokenGenerator _jwtTokenGenerator; + private readonly IMapper _mapper; + + public Handler( + ConduitContext context, + IPasswordHasher passwordHasher, + IJwtTokenGenerator jwtTokenGenerator, + IMapper mapper + ) { - private readonly ConduitContext _context; - private readonly IPasswordHasher _passwordHasher; - private readonly IJwtTokenGenerator _jwtTokenGenerator; - private readonly IMapper _mapper; + _context = context; + _passwordHasher = passwordHasher; + _jwtTokenGenerator = jwtTokenGenerator; + _mapper = mapper; + } - public Handler(ConduitContext context, IPasswordHasher passwordHasher, IJwtTokenGenerator jwtTokenGenerator, IMapper mapper) + public async Task Handle( + Command message, + CancellationToken cancellationToken + ) + { + var person = await _context.Persons + .Where(x => x.Email == message.User.Email) + .SingleOrDefaultAsync(cancellationToken); + if (person == null) { - _context = context; - _passwordHasher = passwordHasher; - _jwtTokenGenerator = jwtTokenGenerator; - _mapper = mapper; + throw new RestException( + HttpStatusCode.Unauthorized, + new { Error = "Invalid email / password." } + ); } - public async Task Handle(Command message, CancellationToken cancellationToken) + if ( + !person.Hash.SequenceEqual( + await _passwordHasher.Hash( + message.User.Password ?? throw new InvalidOperationException(), + person.Salt + ) + ) + ) { - var person = await _context.Persons.Where(x => x.Email == message.User.Email).SingleOrDefaultAsync(cancellationToken); - if (person == null) - { - throw new RestException(HttpStatusCode.Unauthorized, new { Error = "Invalid email / password." }); - } - - if (!person.Hash.SequenceEqual(await _passwordHasher.Hash(message.User.Password ?? throw new InvalidOperationException(), person.Salt))) - { - throw new RestException(HttpStatusCode.Unauthorized, new { Error = "Invalid email / password." }); - } - - var user = _mapper.Map(person); - user.Token = _jwtTokenGenerator.CreateToken(person.Username ?? throw new InvalidOperationException()); - return new UserEnvelope(user); + throw new RestException( + HttpStatusCode.Unauthorized, + new { Error = "Invalid email / password." } + ); } + + var user = _mapper.Map(person); + user.Token = _jwtTokenGenerator.CreateToken( + person.Username ?? throw new InvalidOperationException() + ); + return new UserEnvelope(user); } } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Users/MappingProfile.cs b/src/Conduit/Features/Users/MappingProfile.cs index cd9fdce3..eeef93d0 100644 --- a/src/Conduit/Features/Users/MappingProfile.cs +++ b/src/Conduit/Features/Users/MappingProfile.cs @@ -1,12 +1,11 @@ using AutoMapper; -namespace Conduit.Features.Users +namespace Conduit.Features.Users; + +public class MappingProfile : Profile { - public class MappingProfile : Profile + public MappingProfile() { - public MappingProfile() - { - CreateMap(MemberList.None); - } + CreateMap(MemberList.None); } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Users/User.cs b/src/Conduit/Features/Users/User.cs index da902a68..6df19bd4 100644 --- a/src/Conduit/Features/Users/User.cs +++ b/src/Conduit/Features/Users/User.cs @@ -1,17 +1,16 @@ -namespace Conduit.Features.Users -{ - public class User - { - public string? Username { get; set; } +namespace Conduit.Features.Users; - public string? Email { get; set; } +public class User +{ + public string? Username { get; set; } - public string? Bio { get; set; } + public string? Email { get; set; } - public string? Image { get; set; } + public string? Bio { get; set; } - public string? Token { get; set; } - } + public string? Image { get; set; } - public record UserEnvelope(User User); + public string? Token { get; set; } } + +public record UserEnvelope(User User); \ No newline at end of file diff --git a/src/Conduit/Features/Users/UserController.cs b/src/Conduit/Features/Users/UserController.cs index 429e8cbe..ec6999dc 100644 --- a/src/Conduit/Features/Users/UserController.cs +++ b/src/Conduit/Features/Users/UserController.cs @@ -6,31 +6,36 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace Conduit.Features.Users +namespace Conduit.Features.Users; + +[Route("user")] +[Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] +public class UserController { - [Route("user")] - [Authorize(AuthenticationSchemes = JwtIssuerOptions.Schemes)] - public class UserController - { - private readonly IMediator _mediator; - private readonly ICurrentUserAccessor _currentUserAccessor; + private readonly IMediator _mediator; + private readonly ICurrentUserAccessor _currentUserAccessor; - public UserController(IMediator mediator, ICurrentUserAccessor currentUserAccessor) - { - _mediator = mediator; - _currentUserAccessor = currentUserAccessor; - } + public UserController(IMediator mediator, ICurrentUserAccessor currentUserAccessor) + { + _mediator = mediator; + _currentUserAccessor = currentUserAccessor; + } - [HttpGet] - public Task GetCurrent(CancellationToken cancellationToken) - { - return _mediator.Send(new Details.Query(_currentUserAccessor.GetCurrentUsername() ?? ""), cancellationToken); - } + [HttpGet] + public Task GetCurrent(CancellationToken cancellationToken) + { + return _mediator.Send( + new Details.Query(_currentUserAccessor.GetCurrentUsername() ?? ""), + cancellationToken + ); + } - [HttpPut] - public Task UpdateUser([FromBody] Edit.Command command, CancellationToken cancellationToken) - { - return _mediator.Send(command, cancellationToken); - } + [HttpPut] + public Task UpdateUser( + [FromBody] Edit.Command command, + CancellationToken cancellationToken + ) + { + return _mediator.Send(command, cancellationToken); } -} +} \ No newline at end of file diff --git a/src/Conduit/Features/Users/UsersController.cs b/src/Conduit/Features/Users/UsersController.cs index 1061a928..8e78c0f3 100644 --- a/src/Conduit/Features/Users/UsersController.cs +++ b/src/Conduit/Features/Users/UsersController.cs @@ -3,20 +3,24 @@ using MediatR; using Microsoft.AspNetCore.Mvc; -namespace Conduit.Features.Users -{ - [Route("users")] - public class UsersController - { - private readonly IMediator _mediator; +namespace Conduit.Features.Users; - public UsersController(IMediator mediator) => _mediator = mediator; +[Route("users")] +public class UsersController +{ + private readonly IMediator _mediator; - [HttpPost] - public Task Create([FromBody] Create.Command command, CancellationToken cancellationToken) => _mediator.Send(command, cancellationToken); + public UsersController(IMediator mediator) => _mediator = mediator; + [HttpPost] + public Task Create( + [FromBody] Create.Command command, + CancellationToken cancellationToken + ) => _mediator.Send(command, cancellationToken); - [HttpPost("login")] - public Task Login([FromBody] Login.Command command, CancellationToken cancellationToken) => _mediator.Send(command, cancellationToken); - } -} + [HttpPost("login")] + public Task Login( + [FromBody] Login.Command command, + CancellationToken cancellationToken + ) => _mediator.Send(command, cancellationToken); +} \ No newline at end of file diff --git a/src/Conduit/Infrastructure/ConduitContext.cs b/src/Conduit/Infrastructure/ConduitContext.cs index 7a43a434..0e14f06b 100644 --- a/src/Conduit/Infrastructure/ConduitContext.cs +++ b/src/Conduit/Infrastructure/ConduitContext.cs @@ -3,131 +3,126 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; -namespace Conduit.Infrastructure +namespace Conduit.Infrastructure; + +public class ConduitContext : DbContext { - public class ConduitContext : DbContext - { - private IDbContextTransaction? _currentTransaction; + private IDbContextTransaction? _currentTransaction; - public ConduitContext(DbContextOptions options) - : base(options) - { - } + public ConduitContext(DbContextOptions options) + : base(options) { } - public DbSet
Articles { get; set; } = null!; - public DbSet Comments { get; set; } = null!; - public DbSet Persons { get; set; } = null!; - public DbSet Tags { get; set; } = null!; - public DbSet ArticleTags { get; set; } = null!; - public DbSet ArticleFavorites { get; set; } = null!; - public DbSet FollowedPeople { get; set; } = null!; + public DbSet
Articles { get; set; } = null!; + public DbSet Comments { get; set; } = null!; + public DbSet Persons { get; set; } = null!; + public DbSet Tags { get; set; } = null!; + public DbSet ArticleTags { get; set; } = null!; + public DbSet ArticleFavorites { get; set; } = null!; + public DbSet FollowedPeople { get; set; } = null!; - protected override void OnModelCreating(ModelBuilder modelBuilder) + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(b => { - modelBuilder.Entity(b => - { - b.HasKey(t => new { t.ArticleId, t.TagId }); + b.HasKey(t => new { t.ArticleId, t.TagId }); - b.HasOne(pt => pt.Article) + b.HasOne(pt => pt.Article) .WithMany(p => p!.ArticleTags) .HasForeignKey(pt => pt.ArticleId); - b.HasOne(pt => pt.Tag) - .WithMany(t => t!.ArticleTags) - .HasForeignKey(pt => pt.TagId); - }); + b.HasOne(pt => pt.Tag).WithMany(t => t!.ArticleTags).HasForeignKey(pt => pt.TagId); + }); - modelBuilder.Entity(b => - { - b.HasKey(t => new { t.ArticleId, t.PersonId }); + modelBuilder.Entity(b => + { + b.HasKey(t => new { t.ArticleId, t.PersonId }); - b.HasOne(pt => pt.Article) - .WithMany(p => p!.ArticleFavorites) - .HasForeignKey(pt => pt.ArticleId); + b.HasOne(pt => pt.Article) + .WithMany(p => p!.ArticleFavorites) + .HasForeignKey(pt => pt.ArticleId); - b.HasOne(pt => pt.Person) - .WithMany(t => t!.ArticleFavorites) - .HasForeignKey(pt => pt.PersonId); - }); + b.HasOne(pt => pt.Person) + .WithMany(t => t!.ArticleFavorites) + .HasForeignKey(pt => pt.PersonId); + }); - modelBuilder.Entity(b => - { - b.HasKey(t => new { t.ObserverId, t.TargetId }); - - // we need to add OnDelete RESTRICT otherwise for the SqlServer database provider, - // app.ApplicationServices.GetRequiredService().Database.EnsureCreated(); throws the following error: - // System.Data.SqlClient.SqlException - // HResult = 0x80131904 - // Message = Introducing FOREIGN KEY constraint 'FK_FollowedPeople_Persons_TargetId' on table 'FollowedPeople' may cause cycles or multiple cascade paths.Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. - // Could not create constraint or index. See previous errors. - b.HasOne(pt => pt.Observer) - .WithMany(p => p!.Followers) - .HasForeignKey(pt => pt.ObserverId) - .OnDelete(DeleteBehavior.Restrict); - - // we need to add OnDelete RESTRICT otherwise for the SqlServer database provider, - // app.ApplicationServices.GetRequiredService().Database.EnsureCreated(); throws the following error: - // System.Data.SqlClient.SqlException - // HResult = 0x80131904 - // Message = Introducing FOREIGN KEY constraint 'FK_FollowingPeople_Persons_TargetId' on table 'FollowedPeople' may cause cycles or multiple cascade paths.Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. - // Could not create constraint or index. See previous errors. - b.HasOne(pt => pt.Target) - .WithMany(t => t!.Following) - .HasForeignKey(pt => pt.TargetId) - .OnDelete(DeleteBehavior.Restrict); - }); - } + modelBuilder.Entity(b => + { + b.HasKey(t => new { t.ObserverId, t.TargetId }); + + // we need to add OnDelete RESTRICT otherwise for the SqlServer database provider, + // app.ApplicationServices.GetRequiredService().Database.EnsureCreated(); throws the following error: + // System.Data.SqlClient.SqlException + // HResult = 0x80131904 + // Message = Introducing FOREIGN KEY constraint 'FK_FollowedPeople_Persons_TargetId' on table 'FollowedPeople' may cause cycles or multiple cascade paths.Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. + // Could not create constraint or index. See previous errors. + b.HasOne(pt => pt.Observer) + .WithMany(p => p!.Followers) + .HasForeignKey(pt => pt.ObserverId) + .OnDelete(DeleteBehavior.Restrict); + + // we need to add OnDelete RESTRICT otherwise for the SqlServer database provider, + // app.ApplicationServices.GetRequiredService().Database.EnsureCreated(); throws the following error: + // System.Data.SqlClient.SqlException + // HResult = 0x80131904 + // Message = Introducing FOREIGN KEY constraint 'FK_FollowingPeople_Persons_TargetId' on table 'FollowedPeople' may cause cycles or multiple cascade paths.Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints. + // Could not create constraint or index. See previous errors. + b.HasOne(pt => pt.Target) + .WithMany(t => t!.Following) + .HasForeignKey(pt => pt.TargetId) + .OnDelete(DeleteBehavior.Restrict); + }); + } - #region Transaction Handling - public void BeginTransaction() + #region Transaction Handling + public void BeginTransaction() + { + if (_currentTransaction != null) { - if (_currentTransaction != null) - { - return; - } + return; + } - if (!Database.IsInMemory()) - { - _currentTransaction = Database.BeginTransaction(IsolationLevel.ReadCommitted); - } + if (!Database.IsInMemory()) + { + _currentTransaction = Database.BeginTransaction(IsolationLevel.ReadCommitted); } + } - public void CommitTransaction() + public void CommitTransaction() + { + try { - try - { - _currentTransaction?.Commit(); - } - catch - { - RollbackTransaction(); - throw; - } - finally + _currentTransaction?.Commit(); + } + catch + { + RollbackTransaction(); + throw; + } + finally + { + if (_currentTransaction != null) { - if (_currentTransaction != null) - { - _currentTransaction.Dispose(); - _currentTransaction = null; - } + _currentTransaction.Dispose(); + _currentTransaction = null; } } + } - public void RollbackTransaction() + public void RollbackTransaction() + { + try { - try - { - _currentTransaction?.Rollback(); - } - finally + _currentTransaction?.Rollback(); + } + finally + { + if (_currentTransaction != null) { - if (_currentTransaction != null) - { - _currentTransaction.Dispose(); - _currentTransaction = null; - } + _currentTransaction.Dispose(); + _currentTransaction = null; } } - #endregion } -} + #endregion +} \ No newline at end of file diff --git a/src/Conduit/Infrastructure/CurrentUserAccessor.cs b/src/Conduit/Infrastructure/CurrentUserAccessor.cs index c03dd978..e198fd9f 100644 --- a/src/Conduit/Infrastructure/CurrentUserAccessor.cs +++ b/src/Conduit/Infrastructure/CurrentUserAccessor.cs @@ -2,20 +2,21 @@ using System.Security.Claims; using Microsoft.AspNetCore.Http; -namespace Conduit.Infrastructure +namespace Conduit.Infrastructure; + +public class CurrentUserAccessor : ICurrentUserAccessor { - public class CurrentUserAccessor : ICurrentUserAccessor - { - private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IHttpContextAccessor _httpContextAccessor; - public CurrentUserAccessor(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } + public CurrentUserAccessor(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } - public string? GetCurrentUsername() - { - return _httpContextAccessor.HttpContext?.User?.Claims?.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value; - } + public string? GetCurrentUsername() + { + return _httpContextAccessor.HttpContext + ?.User?.Claims?.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier) + ?.Value; } -} +} \ No newline at end of file diff --git a/src/Conduit/Infrastructure/DBContextTransactionPipelineBehavior.cs b/src/Conduit/Infrastructure/DBContextTransactionPipelineBehavior.cs index 55785ec7..63ff1f60 100644 --- a/src/Conduit/Infrastructure/DBContextTransactionPipelineBehavior.cs +++ b/src/Conduit/Infrastructure/DBContextTransactionPipelineBehavior.cs @@ -3,39 +3,43 @@ using System.Threading.Tasks; using MediatR; -namespace Conduit.Infrastructure +namespace Conduit.Infrastructure; + +/// +/// Adds transaction to the processing pipeline +/// +/// +/// +public class DBContextTransactionPipelineBehavior + : IPipelineBehavior + where TRequest : notnull { - /// - /// Adds transaction to the processing pipeline - /// - /// - /// - public class DBContextTransactionPipelineBehavior : IPipelineBehavior - where TRequest : notnull - { - private readonly ConduitContext _context; + private readonly ConduitContext _context; - public DBContextTransactionPipelineBehavior(ConduitContext context) => _context = context; + public DBContextTransactionPipelineBehavior(ConduitContext context) => _context = context; - public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) - { - TResponse? result = default; - - try - { - _context.BeginTransaction(); + public async Task Handle( + TRequest request, + RequestHandlerDelegate next, + CancellationToken cancellationToken + ) + { + TResponse? result = default; - result = await next(); + try + { + _context.BeginTransaction(); - _context.CommitTransaction(); - } - catch (Exception) - { - _context.RollbackTransaction(); - throw; - } + result = await next(); - return result; + _context.CommitTransaction(); + } + catch (Exception) + { + _context.RollbackTransaction(); + throw; } + + return result; } -} +} \ No newline at end of file diff --git a/src/Conduit/Infrastructure/Errors/Constants.cs b/src/Conduit/Infrastructure/Errors/Constants.cs index d9860f58..d5bda2bf 100644 --- a/src/Conduit/Infrastructure/Errors/Constants.cs +++ b/src/Conduit/Infrastructure/Errors/Constants.cs @@ -1,9 +1,8 @@ -namespace Conduit.Infrastructure.Errors +namespace Conduit.Infrastructure.Errors; + +public static class Constants { - public static class Constants - { - public const string NOT_FOUND = "not found"; - public const string IN_USE = "in use"; - public const string InternalServerError = nameof(InternalServerError); - } -} + public const string NOT_FOUND = "not found"; + public const string IN_USE = "in use"; + public const string InternalServerError = nameof(InternalServerError); +} \ No newline at end of file diff --git a/src/Conduit/Infrastructure/Errors/ErrorHandlingMiddleware.cs b/src/Conduit/Infrastructure/Errors/ErrorHandlingMiddleware.cs index 4d82bdbd..fcd19956 100644 --- a/src/Conduit/Infrastructure/Errors/ErrorHandlingMiddleware.cs +++ b/src/Conduit/Infrastructure/Errors/ErrorHandlingMiddleware.cs @@ -6,68 +6,69 @@ using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; -namespace Conduit.Infrastructure.Errors +namespace Conduit.Infrastructure.Errors; + +public class ErrorHandlingMiddleware { - public class ErrorHandlingMiddleware + private readonly RequestDelegate _next; + private readonly ILogger _logger; + private readonly IStringLocalizer _localizer; + private static readonly Action LOGGER_MESSAGE = + LoggerMessage.Define( + LogLevel.Error, + eventId: new EventId(id: 0, name: "ERROR"), + formatString: "{Message}" + ); + + public ErrorHandlingMiddleware( + RequestDelegate next, + IStringLocalizer localizer, + ILogger logger + ) { - private readonly RequestDelegate _next; - private readonly ILogger _logger; - private readonly IStringLocalizer _localizer; - private static readonly Action LOGGER_MESSAGE = LoggerMessage.Define(LogLevel.Error, eventId: - new EventId(id: 0, name: "ERROR"), formatString: "{Message}"); + _next = next; + _logger = logger; + _localizer = localizer; + } - public ErrorHandlingMiddleware( - RequestDelegate next, - IStringLocalizer localizer, - ILogger logger) + public async Task Invoke(HttpContext context) + { + try { - _next = next; - _logger = logger; - _localizer = localizer; + await _next(context); } - - public async Task Invoke(HttpContext context) + catch (Exception ex) { - try - { - await _next(context); - } - catch (Exception ex) - { - await HandleExceptionAsync(context, ex, _logger, _localizer); - } + await HandleExceptionAsync(context, ex, _logger, _localizer); } + } - private static async Task HandleExceptionAsync( - HttpContext context, - Exception exception, - ILogger logger, - IStringLocalizer localizer) + private static async Task HandleExceptionAsync( + HttpContext context, + Exception exception, + ILogger logger, + IStringLocalizer localizer + ) + { + string? result = null; + switch (exception) { - string? result = null; - switch (exception) - { - case RestException re: - context.Response.StatusCode = (int)re.Code; - result = JsonSerializer.Serialize(new - { - errors = re.Errors - }); - break; - case Exception e: - context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; - LOGGER_MESSAGE(logger, "Unhandled Exception", e); - result = JsonSerializer.Serialize(new - { - errors = localizer[Constants.InternalServerError].Value - }); - break; - default: - break; - } - - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(result ?? "{}"); + case RestException re: + context.Response.StatusCode = (int)re.Code; + result = JsonSerializer.Serialize(new { errors = re.Errors }); + break; + case Exception e: + context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; + LOGGER_MESSAGE(logger, "Unhandled Exception", e); + result = JsonSerializer.Serialize( + new { errors = localizer[Constants.InternalServerError].Value } + ); + break; + default: + break; } + + context.Response.ContentType = "application/json"; + await context.Response.WriteAsync(result ?? "{}"); } -} +} \ No newline at end of file diff --git a/src/Conduit/Infrastructure/Errors/RestException.cs b/src/Conduit/Infrastructure/Errors/RestException.cs index cb9d2b38..d61a61a8 100644 --- a/src/Conduit/Infrastructure/Errors/RestException.cs +++ b/src/Conduit/Infrastructure/Errors/RestException.cs @@ -1,18 +1,17 @@ using System; using System.Net; -namespace Conduit.Infrastructure.Errors +namespace Conduit.Infrastructure.Errors; + +public class RestException : Exception { - public class RestException : Exception + public RestException(HttpStatusCode code, object? errors = null) { - public RestException(HttpStatusCode code, object? errors = null) - { - Code = code; - Errors = errors; - } + Code = code; + Errors = errors; + } - public object? Errors { get; set; } + public object? Errors { get; set; } - public HttpStatusCode Code { get; } - } -} + public HttpStatusCode Code { get; } +} \ No newline at end of file diff --git a/src/Conduit/Infrastructure/GroupByApiRootConvention.cs b/src/Conduit/Infrastructure/GroupByApiRootConvention.cs index 27a660fb..338c2a76 100644 --- a/src/Conduit/Infrastructure/GroupByApiRootConvention.cs +++ b/src/Conduit/Infrastructure/GroupByApiRootConvention.cs @@ -2,16 +2,18 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationModels; -namespace Conduit.Infrastructure +namespace Conduit.Infrastructure; + +public class GroupByApiRootConvention : IControllerModelConvention { - public class GroupByApiRootConvention : IControllerModelConvention + public void Apply(ControllerModel controller) { - public void Apply(ControllerModel controller) - { - var controllerNamespace = controller.Attributes.OfType().FirstOrDefault(); - var apiVersion = controllerNamespace?.Template?.Split('/').First().ToLowerInvariant() ?? "default"; + var controllerNamespace = controller.Attributes + .OfType() + .FirstOrDefault(); + var apiVersion = + controllerNamespace?.Template?.Split('/').First().ToLowerInvariant() ?? "default"; - controller.ApiExplorer.GroupName = apiVersion; - } + controller.ApiExplorer.GroupName = apiVersion; } -} +} \ No newline at end of file diff --git a/src/Conduit/Infrastructure/ICurrentUserAccessor.cs b/src/Conduit/Infrastructure/ICurrentUserAccessor.cs index b98c4cd9..10c5d002 100644 --- a/src/Conduit/Infrastructure/ICurrentUserAccessor.cs +++ b/src/Conduit/Infrastructure/ICurrentUserAccessor.cs @@ -1,7 +1,6 @@ -namespace Conduit.Infrastructure +namespace Conduit.Infrastructure; + +public interface ICurrentUserAccessor { - public interface ICurrentUserAccessor - { - string? GetCurrentUsername(); - } -} + string? GetCurrentUsername(); +} \ No newline at end of file diff --git a/src/Conduit/Infrastructure/Security/IJwtTokenGenerator.cs b/src/Conduit/Infrastructure/Security/IJwtTokenGenerator.cs index 683f7087..55a2b139 100644 --- a/src/Conduit/Infrastructure/Security/IJwtTokenGenerator.cs +++ b/src/Conduit/Infrastructure/Security/IJwtTokenGenerator.cs @@ -1,7 +1,6 @@ -namespace Conduit.Infrastructure.Security +namespace Conduit.Infrastructure.Security; + +public interface IJwtTokenGenerator { - public interface IJwtTokenGenerator - { - string CreateToken(string username); - } -} + string CreateToken(string username); +} \ No newline at end of file diff --git a/src/Conduit/Infrastructure/Security/IPasswordHasher.cs b/src/Conduit/Infrastructure/Security/IPasswordHasher.cs index ea9da85c..5ebef64d 100644 --- a/src/Conduit/Infrastructure/Security/IPasswordHasher.cs +++ b/src/Conduit/Infrastructure/Security/IPasswordHasher.cs @@ -1,10 +1,9 @@ using System; using System.Threading.Tasks; -namespace Conduit.Infrastructure.Security +namespace Conduit.Infrastructure.Security; + +public interface IPasswordHasher : IDisposable { - public interface IPasswordHasher : IDisposable - { - Task Hash(string password, byte[] salt); - } -} + Task Hash(string password, byte[] salt); +} \ No newline at end of file diff --git a/src/Conduit/Infrastructure/Security/JwtIssuerOptions.cs b/src/Conduit/Infrastructure/Security/JwtIssuerOptions.cs index 3ea8a4e9..8ef6ea6c 100644 --- a/src/Conduit/Infrastructure/Security/JwtIssuerOptions.cs +++ b/src/Conduit/Infrastructure/Security/JwtIssuerOptions.cs @@ -2,103 +2,102 @@ using System.Threading.Tasks; using Microsoft.IdentityModel.Tokens; -namespace Conduit.Infrastructure.Security +namespace Conduit.Infrastructure.Security; + +public class JwtIssuerOptions { - public class JwtIssuerOptions - { - public const string Schemes = "Bearer"; + public const string Schemes = "Bearer"; - /// - /// "iss" (Issuer) Claim - /// - /// The "iss" (issuer) claim identifies the principal that issued the - /// JWT. The processing of this claim is generally application specific. - /// The "iss" value is a case-sensitive string containing a StringOrURI - /// value. Use of this claim is OPTIONAL. - public string? Issuer { get; set; } + /// + /// "iss" (Issuer) Claim + /// + /// The "iss" (issuer) claim identifies the principal that issued the + /// JWT. The processing of this claim is generally application specific. + /// The "iss" value is a case-sensitive string containing a StringOrURI + /// value. Use of this claim is OPTIONAL. + public string? Issuer { get; set; } - /// - /// "sub" (Subject) Claim - /// - /// The "sub" (subject) claim identifies the principal that is the - /// subject of the JWT. The claims in a JWT are normally statements - /// about the subject. The subject value MUST either be scoped to be - /// locally unique in the context of the issuer or be globally unique. - /// The processing of this claim is generally application specific. The - /// "sub" value is a case-sensitive string containing a StringOrURI - /// value. Use of this claim is OPTIONAL. - public string? Subject { get; set; } + /// + /// "sub" (Subject) Claim + /// + /// The "sub" (subject) claim identifies the principal that is the + /// subject of the JWT. The claims in a JWT are normally statements + /// about the subject. The subject value MUST either be scoped to be + /// locally unique in the context of the issuer or be globally unique. + /// The processing of this claim is generally application specific. The + /// "sub" value is a case-sensitive string containing a StringOrURI + /// value. Use of this claim is OPTIONAL. + public string? Subject { get; set; } - /// - /// "aud" (Audience) Claim - /// - /// The "aud" (audience) claim identifies the recipients that the JWT is - /// intended for. Each principal intended to process the JWT MUST - /// identify itself with a value in the audience claim. If the principal - /// processing the claim does not identify itself with a value in the - /// "aud" claim when this claim is present, then the JWT MUST be - /// rejected. In the general case, the "aud" value is an array of case- - /// sensitive strings, each containing a StringOrURI value. In the - /// special case when the JWT has one audience, the "aud" value MAY be a - /// single case-sensitive string containing a StringOrURI value. The - /// interpretation of audience values is generally application specific. - /// Use of this claim is OPTIONAL. - public string? Audience { get; set; } + /// + /// "aud" (Audience) Claim + /// + /// The "aud" (audience) claim identifies the recipients that the JWT is + /// intended for. Each principal intended to process the JWT MUST + /// identify itself with a value in the audience claim. If the principal + /// processing the claim does not identify itself with a value in the + /// "aud" claim when this claim is present, then the JWT MUST be + /// rejected. In the general case, the "aud" value is an array of case- + /// sensitive strings, each containing a StringOrURI value. In the + /// special case when the JWT has one audience, the "aud" value MAY be a + /// single case-sensitive string containing a StringOrURI value. The + /// interpretation of audience values is generally application specific. + /// Use of this claim is OPTIONAL. + public string? Audience { get; set; } - /// - /// "nbf" (Not Before) Claim (default is UTC NOW) - /// - /// The "nbf" (not before) claim identifies the time before which the JWT - /// MUST NOT be accepted for processing. The processing of the "nbf" - /// claim requires that the current date/time MUST be after or equal to - /// the not-before date/time listed in the "nbf" claim. Implementers MAY - /// provide for some small leeway, usually no more than a few minutes, to - /// account for clock skew. Its value MUST be a number containing a - /// NumericDate value. Use of this claim is OPTIONAL. - public DateTime NotBefore => DateTime.UtcNow; + /// + /// "nbf" (Not Before) Claim (default is UTC NOW) + /// + /// The "nbf" (not before) claim identifies the time before which the JWT + /// MUST NOT be accepted for processing. The processing of the "nbf" + /// claim requires that the current date/time MUST be after or equal to + /// the not-before date/time listed in the "nbf" claim. Implementers MAY + /// provide for some small leeway, usually no more than a few minutes, to + /// account for clock skew. Its value MUST be a number containing a + /// NumericDate value. Use of this claim is OPTIONAL. + public DateTime NotBefore => DateTime.UtcNow; - /// - /// "iat" (Issued At) Claim (default is UTC NOW) - /// - /// The "iat" (issued at) claim identifies the time at which the JWT was - /// issued. This claim can be used to determine the age of the JWT. Its - /// value MUST be a number containing a NumericDate value. Use of this - /// claim is OPTIONAL. - public DateTime IssuedAt => DateTime.UtcNow; + /// + /// "iat" (Issued At) Claim (default is UTC NOW) + /// + /// The "iat" (issued at) claim identifies the time at which the JWT was + /// issued. This claim can be used to determine the age of the JWT. Its + /// value MUST be a number containing a NumericDate value. Use of this + /// claim is OPTIONAL. + public DateTime IssuedAt => DateTime.UtcNow; - /// - /// Set the timespan the token will be valid for (default is 5 min/300 seconds) - /// - public TimeSpan ValidFor { get; set; } = TimeSpan.FromMinutes(5); + /// + /// Set the timespan the token will be valid for (default is 5 min/300 seconds) + /// + public TimeSpan ValidFor { get; set; } = TimeSpan.FromMinutes(5); - /// - /// "exp" (Expiration Time) Claim (returns IssuedAt + ValidFor) - /// - /// The "exp" (expiration time) claim identifies the expiration time on - /// or after which the JWT MUST NOT be accepted for processing. The - /// processing of the "exp" claim requires that the current date/time - /// MUST be before the expiration date/time listed in the "exp" claim. - /// Implementers MAY provide for some small leeway, usually no more than - /// a few minutes, to account for clock skew. Its value MUST be a number - /// containing a NumericDate value. Use of this claim is OPTIONAL. - public DateTime Expiration => IssuedAt.Add(ValidFor); + /// + /// "exp" (Expiration Time) Claim (returns IssuedAt + ValidFor) + /// + /// The "exp" (expiration time) claim identifies the expiration time on + /// or after which the JWT MUST NOT be accepted for processing. The + /// processing of the "exp" claim requires that the current date/time + /// MUST be before the expiration date/time listed in the "exp" claim. + /// Implementers MAY provide for some small leeway, usually no more than + /// a few minutes, to account for clock skew. Its value MUST be a number + /// containing a NumericDate value. Use of this claim is OPTIONAL. + public DateTime Expiration => IssuedAt.Add(ValidFor); - /// - /// "jti" (JWT ID) Claim (default ID is a GUID) - /// - /// The "jti" (JWT ID) claim provides a unique identifier for the JWT. - /// The identifier value MUST be assigned in a manner that ensures that - /// there is a negligible probability that the same value will be - /// accidentally assigned to a different data object; if the application - /// uses multiple issuers, collisions MUST be prevented among values - /// produced by different issuers as well. The "jti" claim can be used - /// to prevent the JWT from being replayed. The "jti" value is a case- - /// sensitive string. Use of this claim is OPTIONAL. - public Func JtiGenerator => () => Guid.NewGuid().ToString(); + /// + /// "jti" (JWT ID) Claim (default ID is a GUID) + /// + /// The "jti" (JWT ID) claim provides a unique identifier for the JWT. + /// The identifier value MUST be assigned in a manner that ensures that + /// there is a negligible probability that the same value will be + /// accidentally assigned to a different data object; if the application + /// uses multiple issuers, collisions MUST be prevented among values + /// produced by different issuers as well. The "jti" claim can be used + /// to prevent the JWT from being replayed. The "jti" value is a case- + /// sensitive string. Use of this claim is OPTIONAL. + public Func JtiGenerator => () => Guid.NewGuid().ToString(); - /// - /// The signing key to use when generating tokens. - /// - public SigningCredentials? SigningCredentials { get; set; } - } -} + /// + /// The signing key to use when generating tokens. + /// + public SigningCredentials? SigningCredentials { get; set; } +} \ No newline at end of file diff --git a/src/Conduit/Infrastructure/Security/JwtTokenGenerator.cs b/src/Conduit/Infrastructure/Security/JwtTokenGenerator.cs index 003ff8e5..b5cee5af 100644 --- a/src/Conduit/Infrastructure/Security/JwtTokenGenerator.cs +++ b/src/Conduit/Infrastructure/Security/JwtTokenGenerator.cs @@ -3,37 +3,39 @@ using System.Security.Claims; using Microsoft.Extensions.Options; -namespace Conduit.Infrastructure.Security +namespace Conduit.Infrastructure.Security; + +public class JwtTokenGenerator : IJwtTokenGenerator { - public class JwtTokenGenerator : IJwtTokenGenerator - { - private readonly JwtIssuerOptions _jwtOptions; + private readonly JwtIssuerOptions _jwtOptions; - public JwtTokenGenerator(IOptions jwtOptions) - { - _jwtOptions = jwtOptions.Value; - } + public JwtTokenGenerator(IOptions jwtOptions) + { + _jwtOptions = jwtOptions.Value; + } - public string CreateToken(string username) + public string CreateToken(string username) + { + var claims = new[] { - var claims = new[] - { - new Claim(JwtRegisteredClaimNames.Sub, username), - new Claim(JwtRegisteredClaimNames.Jti, _jwtOptions.JtiGenerator()), - new Claim(JwtRegisteredClaimNames.Iat, - new DateTimeOffset(_jwtOptions.IssuedAt).ToUnixTimeSeconds().ToString(), - ClaimValueTypes.Integer64) - }; - var jwt = new JwtSecurityToken( - _jwtOptions.Issuer, - _jwtOptions.Audience, - claims, - _jwtOptions.NotBefore, - _jwtOptions.Expiration, - _jwtOptions.SigningCredentials); + new Claim(JwtRegisteredClaimNames.Sub, username), + new Claim(JwtRegisteredClaimNames.Jti, _jwtOptions.JtiGenerator()), + new Claim( + JwtRegisteredClaimNames.Iat, + new DateTimeOffset(_jwtOptions.IssuedAt).ToUnixTimeSeconds().ToString(), + ClaimValueTypes.Integer64 + ) + }; + var jwt = new JwtSecurityToken( + _jwtOptions.Issuer, + _jwtOptions.Audience, + claims, + _jwtOptions.NotBefore, + _jwtOptions.Expiration, + _jwtOptions.SigningCredentials + ); - var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); - return encodedJwt; - } + var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt); + return encodedJwt; } -} +} \ No newline at end of file diff --git a/src/Conduit/Infrastructure/Security/PasswordHasher.cs b/src/Conduit/Infrastructure/Security/PasswordHasher.cs index 9628bb14..43a5bade 100644 --- a/src/Conduit/Infrastructure/Security/PasswordHasher.cs +++ b/src/Conduit/Infrastructure/Security/PasswordHasher.cs @@ -4,23 +4,22 @@ using System.Text; using System.Threading.Tasks; -namespace Conduit.Infrastructure.Security -{ - public class PasswordHasher : IPasswordHasher - { - private readonly HMACSHA512 x = new(Encoding.UTF8.GetBytes("realworld")); +namespace Conduit.Infrastructure.Security; - public Task Hash(string password, byte[] salt) - { - var bytes = Encoding.UTF8.GetBytes(password); +public class PasswordHasher : IPasswordHasher +{ + private readonly HMACSHA512 x = new(Encoding.UTF8.GetBytes("realworld")); - var allBytes = new byte[bytes.Length + salt.Length]; - Buffer.BlockCopy(bytes, 0, allBytes, 0, bytes.Length); - Buffer.BlockCopy(salt, 0, allBytes, bytes.Length, salt.Length); + public Task Hash(string password, byte[] salt) + { + var bytes = Encoding.UTF8.GetBytes(password); - return x.ComputeHashAsync(new MemoryStream(allBytes)); - } + var allBytes = new byte[bytes.Length + salt.Length]; + Buffer.BlockCopy(bytes, 0, allBytes, 0, bytes.Length); + Buffer.BlockCopy(salt, 0, allBytes, bytes.Length, salt.Length); - public void Dispose() => x.Dispose(); + return x.ComputeHashAsync(new MemoryStream(allBytes)); } -} + + public void Dispose() => x.Dispose(); +} \ No newline at end of file diff --git a/src/Conduit/Infrastructure/Slug.cs b/src/Conduit/Infrastructure/Slug.cs index a097c649..1d291dd6 100644 --- a/src/Conduit/Infrastructure/Slug.cs +++ b/src/Conduit/Infrastructure/Slug.cs @@ -1,32 +1,33 @@ using System.Text.RegularExpressions; -namespace Conduit.Infrastructure +namespace Conduit.Infrastructure; + +// https://stackoverflow.com/questions/2920744/url-slugify-algorithm-in-c +public static partial class Slug { - // https://stackoverflow.com/questions/2920744/url-slugify-algorithm-in-c - public static partial class Slug + public static string? GenerateSlug(this string? phrase) { - public static string? GenerateSlug(this string? phrase) + if (phrase is null) { - if (phrase is null) - { - return null; - } - var str = phrase.ToLowerInvariant(); - // invalid chars - str = InvalidCharsRegex().Replace(str, ""); - // convert multiple spaces into one space - str = MultipleSpacesRegex().Replace(str, " ").Trim(); - // cut and trim - str = str[..(str.Length <= 45 ? str.Length : 45)].Trim(); - str = TrimRegex().Replace(str, "-"); // hyphens - return str; + return null; } - - [GeneratedRegex("[^a-z0-9\\s-]")] - private static partial Regex InvalidCharsRegex(); - [GeneratedRegex("\\s+")] - private static partial Regex MultipleSpacesRegex(); - [GeneratedRegex("\\s")] - private static partial Regex TrimRegex(); + var str = phrase.ToLowerInvariant(); + // invalid chars + str = InvalidCharsRegex().Replace(str, ""); + // convert multiple spaces into one space + str = MultipleSpacesRegex().Replace(str, " ").Trim(); + // cut and trim + str = str[..(str.Length <= 45 ? str.Length : 45)].Trim(); + str = TrimRegex().Replace(str, "-"); // hyphens + return str; } -} + + [GeneratedRegex("[^a-z0-9\\s-]")] + private static partial Regex InvalidCharsRegex(); + + [GeneratedRegex("\\s+")] + private static partial Regex MultipleSpacesRegex(); + + [GeneratedRegex("\\s")] + private static partial Regex TrimRegex(); +} \ No newline at end of file diff --git a/src/Conduit/Infrastructure/ValidationPipelineBehavior.cs b/src/Conduit/Infrastructure/ValidationPipelineBehavior.cs index 640c1c58..f10058e0 100644 --- a/src/Conduit/Infrastructure/ValidationPipelineBehavior.cs +++ b/src/Conduit/Infrastructure/ValidationPipelineBehavior.cs @@ -5,30 +5,35 @@ using FluentValidation; using MediatR; -namespace Conduit.Infrastructure -{ - public class ValidationPipelineBehavior : IPipelineBehavior - where TRequest : notnull - { - private readonly List> _validators; +namespace Conduit.Infrastructure; - public ValidationPipelineBehavior(IEnumerable> validators) => _validators = validators.ToList(); +public class ValidationPipelineBehavior + : IPipelineBehavior + where TRequest : notnull +{ + private readonly List> _validators; - public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) - { - var context = new ValidationContext(request); - var failures = _validators - .Select(v => v.Validate(context)) - .SelectMany(result => result.Errors) - .Where(f => f != null) - .ToList(); + public ValidationPipelineBehavior(IEnumerable> validators) => + _validators = validators.ToList(); - if (failures.Count != 0) - { - throw new ValidationException(failures); - } + public async Task Handle( + TRequest request, + RequestHandlerDelegate next, + CancellationToken cancellationToken + ) + { + var context = new ValidationContext(request); + var failures = _validators + .Select(v => v.Validate(context)) + .SelectMany(result => result.Errors) + .Where(f => f != null) + .ToList(); - return await next(); + if (failures.Count != 0) + { + throw new ValidationException(failures); } + + return await next(); } -} +} \ No newline at end of file diff --git a/src/Conduit/Infrastructure/ValidatorActionFilter.cs b/src/Conduit/Infrastructure/ValidatorActionFilter.cs index c9739a6c..415194f5 100644 --- a/src/Conduit/Infrastructure/ValidatorActionFilter.cs +++ b/src/Conduit/Infrastructure/ValidatorActionFilter.cs @@ -3,43 +3,34 @@ using System.Text.Json; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.Extensions.Logging; -namespace Conduit.Infrastructure +namespace Conduit.Infrastructure; + +public class ValidatorActionFilter : IActionFilter { - public class ValidatorActionFilter : IActionFilter + public void OnActionExecuting(ActionExecutingContext filterContext) { - private readonly ILogger logger; - - public ValidatorActionFilter(ILogger logger) + if (!filterContext.ModelState.IsValid) { - this.logger = logger; - } + var result = new ContentResult(); + var errors = new Dictionary(); - public void OnActionExecuting(ActionExecutingContext filterContext) - { - if (!filterContext.ModelState.IsValid) + foreach (var valuePair in filterContext.ModelState) { - var result = new ContentResult(); - var errors = new Dictionary(); - - foreach (var valuePair in filterContext.ModelState) - { - errors.Add(valuePair.Key, valuePair.Value.Errors.Select(x => x.ErrorMessage).ToArray()); - } - - string content = JsonSerializer.Serialize(new { errors }); - result.Content = content; - result.ContentType = "application/json"; - - filterContext.HttpContext.Response.StatusCode = 422; //unprocessable entity; - filterContext.Result = result; + errors.Add( + valuePair.Key, + valuePair.Value.Errors.Select(x => x.ErrorMessage).ToArray() + ); } - } - public void OnActionExecuted(ActionExecutedContext filterContext) - { + var content = JsonSerializer.Serialize(new { errors }); + result.Content = content; + result.ContentType = "application/json"; + filterContext.HttpContext.Response.StatusCode = 422; //unprocessable entity; + filterContext.Result = result; } } + + public void OnActionExecuted(ActionExecutedContext filterContext) { } } diff --git a/src/Conduit/Program.cs b/src/Conduit/Program.cs index 7973d6b3..0e9fe37e 100644 --- a/src/Conduit/Program.cs +++ b/src/Conduit/Program.cs @@ -25,13 +25,18 @@ var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); +builder.Services.AddMediatR( + cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()) +); builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationPipelineBehavior<,>)); -builder.Services.AddScoped(typeof(IPipelineBehavior<,>), typeof(DBContextTransactionPipelineBehavior<,>)); +builder.Services.AddScoped( + typeof(IPipelineBehavior<,>), + typeof(DBContextTransactionPipelineBehavior<,>) +); // take the connection string from the environment variable or use hard-coded database name var connectionString = defaultDatabaseConnectionSrting; + // take the database provider from the environment variable or use hard-coded database provider var databaseProvider = defaultDatabaseProvider; @@ -41,14 +46,18 @@ { options.UseSqlite(connectionString); } - else if (databaseProvider.ToLowerInvariant().Trim().Equals("sqlserver", StringComparison.Ordinal)) + else if ( + databaseProvider.ToLowerInvariant().Trim().Equals("sqlserver", StringComparison.Ordinal) + ) { // only works in windows container options.UseSqlServer(connectionString); } else { - throw new InvalidOperationException("Database provider unknown. Please check configuration"); + throw new InvalidOperationException( + "Database provider unknown. Please check configuration" + ); } }); @@ -57,44 +66,62 @@ // Inject an implementation of ISwaggerProvider with defaulted settings applied builder.Services.AddSwaggerGen(x => { - x.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme - { - In = ParameterLocation.Header, - Description = "Please insert JWT with Bearer into field", - Name = "Authorization", - Type = SecuritySchemeType.ApiKey, - BearerFormat = "JWT" - }); + x.AddSecurityDefinition( + "Bearer", + new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Please insert JWT with Bearer into field", + Name = "Authorization", + Type = SecuritySchemeType.ApiKey, + BearerFormat = "JWT" + } + ); x.SupportNonNullableReferenceTypes(); - x.AddSecurityRequirement(new OpenApiSecurityRequirement() + x.AddSecurityRequirement( + new OpenApiSecurityRequirement() + { + { + new OpenApiSecurityScheme { - { new OpenApiSecurityScheme + Reference = new OpenApiReference { - Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } - }, - Array.Empty()} - }); + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + Array.Empty() + } + } + ); x.SwaggerDoc("v1", new OpenApiInfo { Title = "RealWorld API", Version = "v1" }); x.CustomSchemaIds(y => y.FullName); x.DocInclusionPredicate((version, apiDescription) => true); - x.TagActionsBy(y => new List() - { - y.GroupName ?? throw new InvalidOperationException() - }); + x.TagActionsBy( + y => new List() { y.GroupName ?? throw new InvalidOperationException() } + ); x.CustomSchemaIds(s => s.FullName?.Replace("+", ".")); }); builder.Services.AddCors(); -builder.Services.AddMvc(opt => -{ - opt.Conventions.Add(new GroupByApiRootConvention()); - opt.Filters.Add(typeof(ValidatorActionFilter)); - opt.EnableEndpointRouting = false; -}) - .AddJsonOptions(opt => opt.JsonSerializerOptions.DefaultIgnoreCondition = - System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull); +builder.Services + .AddMvc(opt => + { + opt.Conventions.Add(new GroupByApiRootConvention()); + opt.Filters.Add(typeof(ValidatorActionFilter)); + opt.EnableEndpointRouting = false; + }) + .AddJsonOptions( + opt => + opt.JsonSerializerOptions.DefaultIgnoreCondition = System + .Text + .Json + .Serialization + .JsonIgnoreCondition + .WhenWritingNull + ); builder.Services.AddFluentValidationAutoValidation(); builder.Services.AddFluentValidationClientsideAdapters(); @@ -116,11 +143,7 @@ app.UseMiddleware(); -app.UseCors(builder => - builder - .AllowAnyOrigin() - .AllowAnyHeader() - .AllowAnyMethod()); +app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); app.UseAuthentication(); app.UseMvc(); @@ -133,8 +156,9 @@ using (var scope = app.Services.CreateScope()) { - var dbContext = scope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); + var dbContext = scope.ServiceProvider + .GetRequiredService() + .Database.EnsureCreated(); // use context } app.Run(); - diff --git a/src/Conduit/Startup.cs b/src/Conduit/Startup.cs index d30a20c4..e17b0571 100644 --- a/src/Conduit/Startup.cs +++ b/src/Conduit/Startup.cs @@ -17,132 +17,167 @@ using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; -namespace Conduit +namespace Conduit; + +public class Startup { - public class Startup - { - public const string DEFAULT_DATABASE_CONNECTIONSTRING = "Filename=realworld.db"; - public const string DEFAULT_DATABASE_PROVIDER = "sqlite"; + public const string DEFAULT_DATABASE_CONNECTIONSTRING = "Filename=realworld.db"; + public const string DEFAULT_DATABASE_PROVIDER = "sqlite"; - //private readonly IConfiguration _config; + //private readonly IConfiguration _config; - //public Startup(IConfiguration config) => _config = config; + //public Startup(IConfiguration config) => _config = config; - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + services.AddMediatR( + cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()) + ); + services.AddTransient( + typeof(IPipelineBehavior<,>), + typeof(ValidationPipelineBehavior<,>) + ); + services.AddScoped( + typeof(IPipelineBehavior<,>), + typeof(DBContextTransactionPipelineBehavior<,>) + ); + + // take the connection string from the environment variable or use hard-coded database name + var connectionString = DEFAULT_DATABASE_CONNECTIONSTRING; + // take the database provider from the environment variable or use hard-coded database provider + var databaseProvider = DEFAULT_DATABASE_PROVIDER; + //if (string.IsNullOrWhiteSpace(databaseProvider)) + //{ + // databaseProvider = DEFAULT_DATABASE_PROVIDER; + //} + + services.AddDbContext(options => { - services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); - services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationPipelineBehavior<,>)); - services.AddScoped(typeof(IPipelineBehavior<,>), typeof(DBContextTransactionPipelineBehavior<,>)); - - // take the connection string from the environment variable or use hard-coded database name - var connectionString = DEFAULT_DATABASE_CONNECTIONSTRING; - // take the database provider from the environment variable or use hard-coded database provider - var databaseProvider = DEFAULT_DATABASE_PROVIDER; - //if (string.IsNullOrWhiteSpace(databaseProvider)) - //{ - // databaseProvider = DEFAULT_DATABASE_PROVIDER; - //} - - services.AddDbContext(options => + if ( + databaseProvider + .ToLowerInvariant() + .Trim() + .Equals("sqlite", StringComparison.Ordinal) + ) { - if (databaseProvider.ToLowerInvariant().Trim().Equals("sqlite", StringComparison.Ordinal)) - { - options.UseSqlite(connectionString); - } - else if (databaseProvider.ToLowerInvariant().Trim().Equals("sqlserver", StringComparison.Ordinal)) - { - // only works in windows container - options.UseSqlServer(connectionString); - } - else - { - throw new InvalidOperationException("Database provider unknown. Please check configuration"); - } - }); + options.UseSqlite(connectionString); + } + else if ( + databaseProvider + .ToLowerInvariant() + .Trim() + .Equals("sqlserver", StringComparison.Ordinal) + ) + { + // only works in windows container + options.UseSqlServer(connectionString); + } + else + { + throw new InvalidOperationException( + "Database provider unknown. Please check configuration" + ); + } + }); - services.AddLocalization(x => x.ResourcesPath = "Resources"); + services.AddLocalization(x => x.ResourcesPath = "Resources"); - // Inject an implementation of ISwaggerProvider with defaulted settings applied - services.AddSwaggerGen(x => - { - x.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + // Inject an implementation of ISwaggerProvider with defaulted settings applied + services.AddSwaggerGen(x => + { + x.AddSecurityDefinition( + "Bearer", + new OpenApiSecurityScheme { In = ParameterLocation.Header, Description = "Please insert JWT with Bearer into field", Name = "Authorization", Type = SecuritySchemeType.ApiKey, BearerFormat = "JWT" - }); + } + ); - x.SupportNonNullableReferenceTypes(); + x.SupportNonNullableReferenceTypes(); - x.AddSecurityRequirement(new OpenApiSecurityRequirement() + x.AddSecurityRequirement( + new OpenApiSecurityRequirement() { - { new OpenApiSecurityScheme { - Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } - }, - Array.Empty()} - }); - x.SwaggerDoc("v1", new OpenApiInfo { Title = "RealWorld API", Version = "v1" }); - x.CustomSchemaIds(y => y.FullName); - x.DocInclusionPredicate((version, apiDescription) => true); - x.TagActionsBy(y => new List() - { - y.GroupName ?? throw new InvalidOperationException() - }); - }); + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + Array.Empty() + } + } + ); + x.SwaggerDoc("v1", new OpenApiInfo { Title = "RealWorld API", Version = "v1" }); + x.CustomSchemaIds(y => y.FullName); + x.DocInclusionPredicate((version, apiDescription) => true); + x.TagActionsBy( + y => new List() { y.GroupName ?? throw new InvalidOperationException() } + ); + }); + + services.AddCors(); + services + .AddMvc(opt => + { + opt.Conventions.Add(new GroupByApiRootConvention()); + opt.Filters.Add(typeof(ValidatorActionFilter)); + opt.EnableEndpointRouting = false; + }) + .AddJsonOptions( + opt => + opt.JsonSerializerOptions.DefaultIgnoreCondition = System + .Text + .Json + .Serialization + .JsonIgnoreCondition + .WhenWritingNull + ); + + services.AddFluentValidationAutoValidation(); + services.AddFluentValidationClientsideAdapters(); + services.AddValidatorsFromAssemblyContaining(); + + services.AddAutoMapper(GetType().Assembly); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddSingleton(); + + services.AddJwt(); + } - services.AddCors(); - services.AddMvc(opt => - { - opt.Conventions.Add(new GroupByApiRootConvention()); - opt.Filters.Add(typeof(ValidatorActionFilter)); - opt.EnableEndpointRouting = false; - }) - .AddJsonOptions(opt => opt.JsonSerializerOptions.DefaultIgnoreCondition = - System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull); - - services.AddFluentValidationAutoValidation(); - services.AddFluentValidationClientsideAdapters(); - services.AddValidatorsFromAssemblyContaining(); - - services.AddAutoMapper(GetType().Assembly); - - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddSingleton(); - - services.AddJwt(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) - { - loggerFactory.AddSerilogLogging(); + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) + { + loggerFactory.AddSerilogLogging(); - app.UseMiddleware(); + app.UseMiddleware(); - app.UseCors(builder => - builder - .AllowAnyOrigin() - .AllowAnyHeader() - .AllowAnyMethod()); + app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); - app.UseAuthentication(); - app.UseMvc(); + app.UseAuthentication(); + app.UseMvc(); - // Enable middleware to serve generated Swagger as a JSON endpoint - app.UseSwagger(c => c.RouteTemplate = "swagger/{documentName}/swagger.json"); + // Enable middleware to serve generated Swagger as a JSON endpoint + app.UseSwagger(c => c.RouteTemplate = "swagger/{documentName}/swagger.json"); - // Enable middleware to serve swagger-ui assets(HTML, JS, CSS etc.) - app.UseSwaggerUI(x => x.SwaggerEndpoint("/swagger/v1/swagger.json", "RealWorld API V1")); + // Enable middleware to serve swagger-ui assets(HTML, JS, CSS etc.) + app.UseSwaggerUI( + x => x.SwaggerEndpoint("/swagger/v1/swagger.json", "RealWorld API V1") + ); - app.ApplicationServices.GetRequiredService().Database.EnsureCreated(); - } + app.ApplicationServices.GetRequiredService().Database.EnsureCreated(); } -} +} \ No newline at end of file diff --git a/src/Conduit/StartupExtensions.cs b/src/Conduit/StartupExtensions.cs index d7b00519..74197fa9 100644 --- a/src/Conduit/StartupExtensions.cs +++ b/src/Conduit/StartupExtensions.cs @@ -9,76 +9,86 @@ using Serilog; using Serilog.Sinks.SystemConsole.Themes; -namespace Conduit +namespace Conduit; + +public static class StartupExtensions { - public static class StartupExtensions + public static void AddJwt(this IServiceCollection services) { - public static void AddJwt(this IServiceCollection services) - { - services.AddOptions(); + services.AddOptions(); - var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("somethinglongerforthisdumbalgorithmisrequired")); - var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); - var issuer = "issuer"; - var audience = "audience"; + var signingKey = new SymmetricSecurityKey( + Encoding.ASCII.GetBytes("somethinglongerforthisdumbalgorithmisrequired") + ); + var signingCredentials = new SigningCredentials( + signingKey, + SecurityAlgorithms.HmacSha256 + ); + var issuer = "issuer"; + var audience = "audience"; - services.Configure(options => - { - options.Issuer = issuer; - options.Audience = audience; - options.SigningCredentials = signingCredentials; - }); + services.Configure(options => + { + options.Issuer = issuer; + options.Audience = audience; + options.SigningCredentials = signingCredentials; + }); - var tokenValidationParameters = new TokenValidationParameters - { - // The signing key must match! - ValidateIssuerSigningKey = true, - IssuerSigningKey = signingCredentials.Key, - // Validate the JWT Issuer (iss) claim - ValidateIssuer = true, - ValidIssuer = issuer, - // Validate the JWT Audience (aud) claim - ValidateAudience = true, - ValidAudience = audience, - // Validate the token expiry - ValidateLifetime = true, - // If you want to allow a certain amount of clock drift, set that here: - ClockSkew = TimeSpan.Zero - }; + var tokenValidationParameters = new TokenValidationParameters + { + // The signing key must match! + ValidateIssuerSigningKey = true, + IssuerSigningKey = signingCredentials.Key, + // Validate the JWT Issuer (iss) claim + ValidateIssuer = true, + ValidIssuer = issuer, + // Validate the JWT Audience (aud) claim + ValidateAudience = true, + ValidAudience = audience, + // Validate the token expiry + ValidateLifetime = true, + // If you want to allow a certain amount of clock drift, set that here: + ClockSkew = TimeSpan.Zero + }; - services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddJwtBearer(options => + services + .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.TokenValidationParameters = tokenValidationParameters; + options.Events = new JwtBearerEvents { - options.TokenValidationParameters = tokenValidationParameters; - options.Events = new JwtBearerEvents + OnMessageReceived = (context) => { - OnMessageReceived = (context) => + var token = context.HttpContext.Request.Headers["Authorization"].ToString(); + if ( + token is not null + && token.StartsWith("Token ", StringComparison.OrdinalIgnoreCase) + ) { - var token = context.HttpContext.Request.Headers["Authorization"]; - if (token.Count > 0 && token[0].StartsWith("Token ", StringComparison.OrdinalIgnoreCase)) - { - context.Token = token[0]["Token ".Length..].Trim(); - } - - return Task.CompletedTask; + context.Token = token["Token ".Length..].Trim(); } - }; - }); - } + return Task.CompletedTask; + } + }; + }); + } - public static void AddSerilogLogging(this ILoggerFactory loggerFactory) - { - // Attach the sink to the logger configuration - var log = new LoggerConfiguration() - .MinimumLevel.Verbose() - .Enrich.FromLogContext() - //just for local debug - .WriteTo.Console(outputTemplate: "{Timestamp:HH:mm:ss} [{Level}] {SourceContext} {Message}{NewLine}{Exception}", theme: AnsiConsoleTheme.Code) - .CreateLogger(); + public static void AddSerilogLogging(this ILoggerFactory loggerFactory) + { + // Attach the sink to the logger configuration + var log = new LoggerConfiguration().MinimumLevel + .Verbose() + .Enrich.FromLogContext() + //just for local debug + .WriteTo.Console( + outputTemplate: "{Timestamp:HH:mm:ss} [{Level}] {SourceContext} {Message}{NewLine}{Exception}", + theme: AnsiConsoleTheme.Code + ) + .CreateLogger(); - loggerFactory.AddSerilog(log); - Log.Logger = log; - } + loggerFactory.AddSerilog(log); + Log.Logger = log; } } diff --git a/src/Conduit/packages.lock.json b/src/Conduit/packages.lock.json new file mode 100644 index 00000000..daa84d94 --- /dev/null +++ b/src/Conduit/packages.lock.json @@ -0,0 +1,718 @@ +{ + "version": 2, + "dependencies": { + "net7.0": { + "AutoMapper": { + "type": "Direct", + "requested": "[12.0.1, )", + "resolved": "12.0.1", + "contentHash": "hvV62vl6Hp/WfQ24yzo3Co9+OPl8wH8hApwVtgWpiAynVJkUcs7xvehnSftawL8Pe8FrPffBRM3hwzLQqWDNjA==", + "dependencies": { + "Microsoft.CSharp": "4.7.0" + } + }, + "AutoMapper.Extensions.Microsoft.DependencyInjection": { + "type": "Direct", + "requested": "[12.0.1, )", + "resolved": "12.0.1", + "contentHash": "+g/K+Vpe3gGMKGzjslMOdqNlkikScDjWfVvmWTayrDHaG/n2pPmFBMa+jKX1r/h6BDGFdkyRjAuhFE3ykW+r1g==", + "dependencies": { + "AutoMapper": "[12.0.1]", + "Microsoft.Extensions.Options": "6.0.0" + } + }, + "FluentValidation.AspNetCore": { + "type": "Direct", + "requested": "[11.3.0, )", + "resolved": "11.3.0", + "contentHash": "jtFVgKnDFySyBlPS8bZbTKEEwJZnn11rXXJ2SQnjDhZ56rQqybBg9Joq4crRLz3y0QR8WoOq4iE4piV81w/Djg==", + "dependencies": { + "FluentValidation": "11.5.1", + "FluentValidation.DependencyInjectionExtensions": "11.5.1" + } + }, + "MediatR": { + "type": "Direct", + "requested": "[12.1.1, )", + "resolved": "12.1.1", + "contentHash": "1AbwzzeS6gn4NdcO6A9LfKS5TXXgAiUQM3J18dREHa7O7TrdCXJ5dNFeRBpzPZY7UWl5Kby+n9pWrPJe3SDiMA==", + "dependencies": { + "MediatR.Contracts": "[2.0.1, 3.0.0)", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0" + } + }, + "Microsoft.AspNetCore.Authentication.JwtBearer": { + "type": "Direct", + "requested": "[7.0.10, )", + "resolved": "7.0.10", + "contentHash": "8/GWtq034kzrDapCHYbAnnJXIYAJzFcJoXP5OtlbjIWBQBLxEbstRv3N4DJAvlcGUXxy0DmmNVDiSlFAY8HpWw==", + "dependencies": { + "Microsoft.IdentityModel.Protocols.OpenIdConnect": "6.15.1" + } + }, + "Microsoft.EntityFrameworkCore.InMemory": { + "type": "Direct", + "requested": "[7.0.10, )", + "resolved": "7.0.10", + "contentHash": "WeUjWx80ZVQVXkHuEIO4kOCKUHo6C4RdB1bJZLSngLWqwrQrMTCZE90IGBz0330GXmXKbL4LyJO/rXO/w7cRNA==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "7.0.10" + } + }, + "Microsoft.EntityFrameworkCore.Sqlite": { + "type": "Direct", + "requested": "[7.0.10, )", + "resolved": "7.0.10", + "contentHash": "gGOZWOvpRDC2bQAUNwpYzjYRTFbSJKNK60KOnvrstvvKKeM/4dVo7i0dxIvZaHpUuIRnlDybBrSK253/BBsfhg==", + "dependencies": { + "Microsoft.EntityFrameworkCore.Sqlite.Core": "7.0.10", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.4" + } + }, + "Microsoft.EntityFrameworkCore.SqlServer": { + "type": "Direct", + "requested": "[7.0.10, )", + "resolved": "7.0.10", + "contentHash": "qRMzze1QbKa2vhlhjTxwjhJUMCI4qVDFWQCRq9Pdgf9mC+xLz0ZT/4ALEa0oZRicGzPZAqIIhKrHNAjgD4GsAQ==", + "dependencies": { + "Microsoft.Data.SqlClient": "5.0.2", + "Microsoft.EntityFrameworkCore.Relational": "7.0.10" + } + }, + "Serilog": { + "type": "Direct", + "requested": "[3.0.1, )", + "resolved": "3.0.1", + "contentHash": "E4UmOQ++eNJax1laE+lws7E3zbhKgHsGJbO7ra0yE5smUh+5FfUPIKKBxM3MO1tK4sgpQke6/pLReDxIc/ggNw==" + }, + "Serilog.Extensions.Logging": { + "type": "Direct", + "requested": "[7.0.0, )", + "resolved": "7.0.0", + "contentHash": "9faU0zNQqU7I6soVhLUMYaGNpgWv6cKlKb2S5AnS8gXxzW/em5Ladm/6FMrWTnX41cdbdGPOWNAo6adi4WaJ6A==", + "dependencies": { + "Microsoft.Extensions.Logging": "7.0.0", + "Serilog": "2.12.0" + } + }, + "Serilog.Sinks.Console": { + "type": "Direct", + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "K6N5q+5fetjnJPvCmkWOpJ/V8IEIoMIB1s86OzBrbxwTyHxdx3pmz4H+8+O/Dc/ftUX12DM1aynx/dDowkwzqg==", + "dependencies": { + "Serilog": "2.10.0" + } + }, + "Swashbuckle.AspNetCore": { + "type": "Direct", + "requested": "[6.5.0, )", + "resolved": "6.5.0", + "contentHash": "FK05XokgjgwlCI6wCT+D4/abtQkL1X1/B9Oas6uIwHFmYrIO9WUD5aLC9IzMs9GnHfUXOtXZ2S43gN1mhs5+aA==", + "dependencies": { + "Microsoft.Extensions.ApiDescription.Server": "6.0.5", + "Swashbuckle.AspNetCore.Swagger": "6.5.0", + "Swashbuckle.AspNetCore.SwaggerGen": "6.5.0", + "Swashbuckle.AspNetCore.SwaggerUI": "6.5.0" + } + }, + "Azure.Core": { + "type": "Transitive", + "resolved": "1.24.0", + "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "1.1.1", + "System.Diagnostics.DiagnosticSource": "4.6.0", + "System.Memory.Data": "1.0.2", + "System.Numerics.Vectors": "4.5.0", + "System.Text.Encodings.Web": "4.7.2", + "System.Text.Json": "4.7.2", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Azure.Identity": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "EycyMsb6rD2PK9P0SyibFfEhvWWttdrYhyPF4f41uzdB/44yQlV+2Wehxyg489Rj6gbPvSPgbKq0xsHJBhipZA==", + "dependencies": { + "Azure.Core": "1.24.0", + "Microsoft.Identity.Client": "4.39.0", + "Microsoft.Identity.Client.Extensions.Msal": "2.19.3", + "System.Memory": "4.5.4", + "System.Security.Cryptography.ProtectedData": "4.7.0", + "System.Text.Json": "4.7.2", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "FluentValidation": { + "type": "Transitive", + "resolved": "11.5.1", + "contentHash": "0h1Q5lNOLLyYTWMJmyNoMqhY4CBRvvUWvJP1R4F2CnmmzuWwvB0A8aVmw5+lOuwYnwUwCRrdeMLbc81F38ahNQ==" + }, + "FluentValidation.DependencyInjectionExtensions": { + "type": "Transitive", + "resolved": "11.5.1", + "contentHash": "iWM0LS1MDYX06pcjMEQKqHirl2zkjHlNV23mEJSoR1IZI7KQmTa0RcTtGEJpj5+iHvBCfrzP2mYKM4FtRKVb+A==", + "dependencies": { + "FluentValidation": "11.5.1", + "Microsoft.Extensions.Dependencyinjection.Abstractions": "2.1.0" + } + }, + "MediatR.Contracts": { + "type": "Transitive", + "resolved": "2.0.1", + "contentHash": "FYv95bNT4UwcNA+G/J1oX5OpRiSUxteXaUt2BJbRSdRNiIUNbggJF69wy6mnk2wYToaanpdXZdCwVylt96MpwQ==" + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "1.1.1", + "contentHash": "yuvf07qFWFqtK3P/MRkEKLhn5r2UbSpVueRziSqj0yJQIKFwG1pq9mOayK3zE5qZCTs0CbrwL9M6R8VwqyGy2w==" + }, + "Microsoft.CSharp": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + }, + "Microsoft.Data.SqlClient": { + "type": "Transitive", + "resolved": "5.0.2", + "contentHash": "mxcYU9I5TLzUegLVXiTtOE89RXN3GafL1Y+ExIbXvivvQtxplI4wxOgsiZGO4TZC18OJqui7mLVmiYpdFFImRQ==", + "dependencies": { + "Azure.Identity": "1.6.0", + "Microsoft.Data.SqlClient.SNI.runtime": "5.0.1", + "Microsoft.Identity.Client": "4.45.0", + "Microsoft.IdentityModel.JsonWebTokens": "6.21.0", + "Microsoft.IdentityModel.Protocols.OpenIdConnect": "6.21.0", + "Microsoft.SqlServer.Server": "1.0.0", + "Microsoft.Win32.Registry": "5.0.0", + "System.Buffers": "4.5.1", + "System.Configuration.ConfigurationManager": "5.0.0", + "System.Diagnostics.DiagnosticSource": "5.0.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime.Caching": "5.0.0", + "System.Security.Cryptography.Cng": "5.0.0", + "System.Security.Principal.Windows": "5.0.0", + "System.Text.Encoding.CodePages": "5.0.0", + "System.Text.Encodings.Web": "4.7.2" + } + }, + "Microsoft.Data.SqlClient.SNI.runtime": { + "type": "Transitive", + "resolved": "5.0.1", + "contentHash": "y0X5MxiNdbITJYoafJ2ruaX6hqO0twpCGR/ipiDOe85JKLU8WL4TuAQfDe5qtt3bND5Je26HnrarLSAMMnVTNg==" + }, + "Microsoft.Data.Sqlite.Core": { + "type": "Transitive", + "resolved": "7.0.10", + "contentHash": "SA6LOen490YTforjaNfVDClYpwiG3LmHJM8objq6KwIIFP0fq9Eulhbu6zBl6AduMefQqUA3pllrFFVQpwPoqw==", + "dependencies": { + "SQLitePCLRaw.core": "2.1.4" + } + }, + "Microsoft.EntityFrameworkCore": { + "type": "Transitive", + "resolved": "7.0.10", + "contentHash": "24NbXJqJ/x8u88/agqeb1pLdAF9+9StDLA36+P/3g5xsJPOaB2GxXn7epR8dWpZTgHsNZ7cvBMxBgfFmF+xZlg==", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "7.0.10", + "Microsoft.EntityFrameworkCore.Analyzers": "7.0.10", + "Microsoft.Extensions.Caching.Memory": "7.0.0", + "Microsoft.Extensions.DependencyInjection": "7.0.0", + "Microsoft.Extensions.Logging": "7.0.0" + } + }, + "Microsoft.EntityFrameworkCore.Abstractions": { + "type": "Transitive", + "resolved": "7.0.10", + "contentHash": "Z/lDWmGLiT9uNQrp6UXTKZxofSmAKQCiKOz98FDscTbfAGgBXE3DTTqRsPMc8HFIVVSNANSiFRz3JyLg07HN9Q==" + }, + "Microsoft.EntityFrameworkCore.Analyzers": { + "type": "Transitive", + "resolved": "7.0.10", + "contentHash": "+8NVNpyJTzW6nNh/7RGfldf+mbeboVcn+X1tD8kMBCEJswuy3RqM/qecEEfOfTcWLliZExPMaHwOwtHO6RMpdA==" + }, + "Microsoft.EntityFrameworkCore.Relational": { + "type": "Transitive", + "resolved": "7.0.10", + "contentHash": "PO2QB2Du+pW210UHmepYR12bk+ZOZJCiNkA7zEAxWs+vzvrRAMsUPlDlfgX2LXE7NBsnb0uvZp7a1/qqKf3fRQ==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "7.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "7.0.0" + } + }, + "Microsoft.EntityFrameworkCore.Sqlite.Core": { + "type": "Transitive", + "resolved": "7.0.10", + "contentHash": "9ujvh/RXXyXmSsh0yY3zI7S+E+ILzndLVsDkigXGkDPVrDqp7/ynqElj0Xb/e4qWHbg/ogoRe5rvUs0jzfl+xg==", + "dependencies": { + "Microsoft.Data.Sqlite.Core": "7.0.10", + "Microsoft.EntityFrameworkCore.Relational": "7.0.10", + "Microsoft.Extensions.DependencyModel": "7.0.0" + } + }, + "Microsoft.Extensions.ApiDescription.Server": { + "type": "Transitive", + "resolved": "6.0.5", + "contentHash": "Ckb5EDBUNJdFWyajfXzUIMRkhf52fHZOQuuZg/oiu8y7zDCVwD0iHhew6MnThjHmevanpxL3f5ci2TtHQEN6bw==" + }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "IeimUd0TNbhB4ded3AbgBLQv2SnsiVugDyGV1MvspQFVlA07nDC7Zul7kcwH5jWN3JiTcp/ySE83AIJo8yfKjg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.Caching.Memory": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "xpidBs2KCE2gw1JrD0quHE72kvCaI3xFql5/Peb2GRtUuZX+dYPoK/NTdVMiM67Svym0M0Df9A3xyU0FbMQhHw==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "7.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Logging.Abstractions": "7.0.0", + "Microsoft.Extensions.Options": "7.0.0", + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "f34u2eaqIjNO9YLHBz8rozVZ+TcFiFs0F3r7nUJd7FRkVSxk8u4OpoK226mi49MwexHOR2ibP9MFvRUaLilcQQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "elNeOmkeX3eDVG6pYVeV82p29hr+UKDaBhrZyWvWLw/EVZSYEkZlQdkp0V39k/Xehs2Qa0mvoCvkVj3eQxNQ1Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "h3j/QfmFN4S0w4C2A6X7arXij/M/OVw3uQHSOFxnND4DyAzO1F9eMX7Eti7lU/OkSthEE0WzRsfT/Dmx86jzCw==" + }, + "Microsoft.Extensions.DependencyModel": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "oONNYd71J3LzkWc4fUHl3SvMfiQMYUCo/mDHDEu76hYYxdhdrPYv6fvGv9nnKVyhE9P0h20AU8RZB5OOWQcAXg==", + "dependencies": { + "System.Text.Encodings.Web": "7.0.0", + "System.Text.Json": "7.0.0" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "7.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Logging.Abstractions": "7.0.0", + "Microsoft.Extensions.Options": "7.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "kmn78+LPVMOWeITUjIlfxUPDsI0R6G0RkeAMBmQxAJ7vBJn4q2dTva7pWi65ceN5vPGjJ9q/Uae2WKgvfktJAw==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "lP1yBnTTU42cKpMozuafbvNtQ7QcBjr/CcK3bYOGEMH55Fjt+iecXjT6chR7vbgCMqy3PG3aNQSZgo/EuY/9qQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q==" + }, + "Microsoft.Identity.Client": { + "type": "Transitive", + "resolved": "4.45.0", + "contentHash": "ircobISCLWbtE5eEoLKU+ldfZ8O41vg4lcy38KRj/znH17jvBiAl8oxcyNp89CsuqE3onxIpn21Ca7riyDDrRw==", + "dependencies": { + "Microsoft.IdentityModel.Abstractions": "6.18.0" + } + }, + "Microsoft.Identity.Client.Extensions.Msal": { + "type": "Transitive", + "resolved": "2.19.3", + "contentHash": "zVVZjn8aW7W79rC1crioDgdOwaFTQorsSO6RgVlDDjc7MvbEGz071wSNrjVhzR0CdQn6Sefx7Abf1o7vasmrLg==", + "dependencies": { + "Microsoft.Identity.Client": "4.38.0", + "System.Security.Cryptography.ProtectedData": "4.5.0" + } + }, + "Microsoft.IdentityModel.Abstractions": { + "type": "Transitive", + "resolved": "6.21.0", + "contentHash": "XeE6LQtD719Qs2IG7HDi1TSw9LIkDbJ33xFiOBoHbApVw/8GpIBCbW+t7RwOjErUDyXZvjhZliwRkkLb8Z1uzg==" + }, + "Microsoft.IdentityModel.JsonWebTokens": { + "type": "Transitive", + "resolved": "6.21.0", + "contentHash": "d3h1/BaMeylKTkdP6XwRCxuOoDJZ44V9xaXr6gl5QxmpnZGdoK3bySo3OQN8ehRLJHShb94ElLUvoXyglQtgAw==", + "dependencies": { + "Microsoft.IdentityModel.Tokens": "6.21.0" + } + }, + "Microsoft.IdentityModel.Logging": { + "type": "Transitive", + "resolved": "6.21.0", + "contentHash": "tuEhHIQwvBEhMf8I50hy8FHmRSUkffDFP5EdLsSDV4qRcl2wvOPkQxYqEzWkh+ytW6sbdJGEXElGhmhDfAxAKg==", + "dependencies": { + "Microsoft.IdentityModel.Abstractions": "6.21.0" + } + }, + "Microsoft.IdentityModel.Protocols": { + "type": "Transitive", + "resolved": "6.21.0", + "contentHash": "0FqY5cTLQKtHrClzHEI+QxJl8OBT2vUiEQQB7UKk832JDiJJmetzYZ3AdSrPjN/3l3nkhByeWzXnhrX0JbifKg==", + "dependencies": { + "Microsoft.IdentityModel.Logging": "6.21.0", + "Microsoft.IdentityModel.Tokens": "6.21.0" + } + }, + "Microsoft.IdentityModel.Protocols.OpenIdConnect": { + "type": "Transitive", + "resolved": "6.21.0", + "contentHash": "vtSKL7n6EnAsLyxmiviusm6LKrblT2ndnNqN6rvVq6iIHAnPCK9E2DkDx6h1Jrpy1cvbp40r0cnTg23nhEAGTA==", + "dependencies": { + "Microsoft.IdentityModel.Protocols": "6.21.0", + "System.IdentityModel.Tokens.Jwt": "6.21.0" + } + }, + "Microsoft.IdentityModel.Tokens": { + "type": "Transitive", + "resolved": "6.21.0", + "contentHash": "AAEHZvZyb597a+QJSmtxH3n2P1nIJGpZ4Q89GTenknRx6T6zyfzf592yW/jA5e8EHN4tNMjjXHQaYWEq5+L05w==", + "dependencies": { + "Microsoft.CSharp": "4.5.0", + "Microsoft.IdentityModel.Logging": "6.21.0", + "System.Security.Cryptography.Cng": "4.5.0" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" + }, + "Microsoft.NETCore.Targets": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" + }, + "Microsoft.OpenApi": { + "type": "Transitive", + "resolved": "1.2.3", + "contentHash": "Nug3rO+7Kl5/SBAadzSMAVgqDlfGjJZ0GenQrLywJ84XGKO0uRqkunz5Wyl0SDwcR71bAATXvSdbdzPrYRYKGw==" + }, + "Microsoft.SqlServer.Server": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "N4KeF3cpcm1PUHym1RmakkzfkEv3GRMyofVv40uXsQhCQeglr2OHNcUk2WOG51AKpGO8ynGpo9M/kFXSzghwug==" + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "Bh6blKG8VAKvXiLe2L+sEsn62nc1Ij34MrNxepD2OCrS5cpCwQa9MeLyhVQPQ/R4Wlzwuy6wMK8hLb11QPDRsQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0" + } + }, + "SQLitePCLRaw.bundle_e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==", + "dependencies": { + "SQLitePCLRaw.lib.e_sqlite3": "2.1.4", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.4" + } + }, + "SQLitePCLRaw.core": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==", + "dependencies": { + "System.Memory": "4.5.3" + } + }, + "SQLitePCLRaw.lib.e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg==" + }, + "SQLitePCLRaw.provider.e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==", + "dependencies": { + "SQLitePCLRaw.core": "2.1.4" + } + }, + "Swashbuckle.AspNetCore.Swagger": { + "type": "Transitive", + "resolved": "6.5.0", + "contentHash": "XWmCmqyFmoItXKFsQSwQbEAsjDKcxlNf1l+/Ki42hcb6LjKL8m5Db69OTvz5vLonMSRntYO1XLqz0OP+n3vKnA==", + "dependencies": { + "Microsoft.OpenApi": "1.2.3" + } + }, + "Swashbuckle.AspNetCore.SwaggerGen": { + "type": "Transitive", + "resolved": "6.5.0", + "contentHash": "Y/qW8Qdg9OEs7V013tt+94OdPxbRdbhcEbw4NiwGvf4YBcfhL/y7qp/Mjv/cENsQ2L3NqJ2AOu94weBy/h4KvA==", + "dependencies": { + "Swashbuckle.AspNetCore.Swagger": "6.5.0" + } + }, + "Swashbuckle.AspNetCore.SwaggerUI": { + "type": "Transitive", + "resolved": "6.5.0", + "contentHash": "OvbvxX+wL8skxTBttcBsVxdh73Fag4xwqEU2edh4JMn7Ws/xJHnY/JB1e9RoCb6XpDxUF3hD9A0Z1lEUx40Pfw==" + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "aM7cbfEfVNlEEOj3DsZP+2g9NRwbkyiAv2isQEzw7pnkDg9ekCU2m1cdJLM02Uq691OaCS91tooaxcEn8d0q5w==", + "dependencies": { + "System.Security.Cryptography.ProtectedData": "5.0.0", + "System.Security.Permissions": "5.0.0" + } + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==" + }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "SztFwAnpfKC8+sEKXAFxCBWhKQaEd97EiOL7oZJZP56zbqnLpmxACWA8aGseaUExciuEAUuR9dY8f7HkTRAdnw==", + "dependencies": { + "Microsoft.Win32.SystemEvents": "5.0.0" + } + }, + "System.Formats.Asn1": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "MTvUIktmemNB+El0Fgw9egyqT9AYSIk6DTJeoDSpc3GIHxHCMo8COqkWT1mptX5tZ1SlQ6HJZ0OsSvMth1c12w==" + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.IdentityModel.Tokens.Jwt": { + "type": "Transitive", + "resolved": "6.21.0", + "contentHash": "JRD8AuypBE+2zYxT3dMJomQVsPYsCqlyZhWel3J1d5nzQokSRyTueF+Q4ID3Jcu6zSZKuzOdJ1MLTkbQsDqcvQ==", + "dependencies": { + "Microsoft.IdentityModel.JsonWebTokens": "6.21.0", + "Microsoft.IdentityModel.Tokens": "6.21.0" + } + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==" + }, + "System.Memory.Data": { + "type": "Transitive", + "resolved": "1.0.2", + "contentHash": "JGkzeqgBsiZwKJZ1IxPNsDFZDhUvuEdX8L8BDC8N3KOj+6zMcNU28CNN59TpZE/VJYy9cP+5M+sbxtWJx3/xtw==", + "dependencies": { + "System.Text.Encodings.Web": "4.7.2", + "System.Text.Json": "4.6.0" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "System.Runtime.Caching": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "30D6MkO8WF9jVGWZIP0hmCN8l9BTY4LCsAzLIe4xFSXzs+AjDotR7DpSmj27pFskDURzUvqYYY0ikModgBTxWw==", + "dependencies": { + "System.Configuration.ConfigurationManager": "5.0.0" + } + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Cryptography.Cng": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "jIMXsKn94T9JY7PvPq/tMfqa6GAaHpElRDpmG+SuL+D3+sTw2M8VhnibKnN8Tq+4JqbPJ/f+BwtLeDMEnzAvRg==", + "dependencies": { + "System.Formats.Asn1": "5.0.0" + } + }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "HGxMSAFAPLNoxBvSfW08vHde0F9uh7BjASwu6JF9JnXuEPhCY3YUqURn0+bQV/4UWeaqymmrHWV+Aw9riQCtCA==" + }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "uE8juAhEkp7KDBCdjDIE3H9R1HJuEHqeqX8nLX9gmYKWwsqk3T5qZlPx8qle5DPKimC/Fy3AFTdV7HamgCh9qQ==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Windows.Extensions": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0" + } + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "OP6umVGxc0Z0MvZQBVigj4/U31Pw72ITihDWP9WiWDm+q5aoe0GaJivsfYGq53o6dxH7DcXWiCTl7+0o2CGdmg==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "DaGSsVqKsn/ia6RG8frjwmJonfos0srquhw09TlT8KRw5I43E+4gs+/bZj4K0vShJ5H9imCuXupb4RmS+dBy3w==", + "dependencies": { + "System.Text.Encodings.Web": "7.0.0" + } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" + }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "c1ho9WU9ZxMZawML+ssPKZfdnrg/OjR3pe0m9v8230z3acqphwvPJqzAkH54xRYm5ntZHGG1EPP3sux9H3qSPg==", + "dependencies": { + "System.Drawing.Common": "5.0.0" + } + } + } + } +} \ No newline at end of file diff --git a/tests/Conduit.IntegrationTests/Conduit.IntegrationTests.csproj b/tests/Conduit.IntegrationTests/Conduit.IntegrationTests.csproj index d123b5d9..80e3d1fa 100644 --- a/tests/Conduit.IntegrationTests/Conduit.IntegrationTests.csproj +++ b/tests/Conduit.IntegrationTests/Conduit.IntegrationTests.csproj @@ -1,19 +1,10 @@ - - net7.0 - true - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + - - \ No newline at end of file + diff --git a/tests/Conduit.IntegrationTests/Features/Articles/ArticleHelpers.cs b/tests/Conduit.IntegrationTests/Features/Articles/ArticleHelpers.cs index fd74d733..ab7c5149 100644 --- a/tests/Conduit.IntegrationTests/Features/Articles/ArticleHelpers.cs +++ b/tests/Conduit.IntegrationTests/Features/Articles/ArticleHelpers.cs @@ -1,34 +1,54 @@ using System.Linq; +using System.Net; using System.Threading.Tasks; using Conduit.Features.Articles; +using Conduit.Infrastructure.Errors; using Conduit.IntegrationTests.Features.Users; using Microsoft.EntityFrameworkCore; -namespace Conduit.IntegrationTests.Features.Articles +namespace Conduit.IntegrationTests.Features.Articles; + +public static class ArticleHelpers { - public static class ArticleHelpers + /// + /// creates an article based on the given Create command. It also creates a default user + /// + /// + /// + /// + public static async Task CreateArticle( + SliceFixture fixture, + Create.Command command + ) { - /// - /// creates an article based on the given Create command. It also creates a default user - /// - /// - /// - /// - public static async Task CreateArticle(SliceFixture fixture, Create.Command command) + // first create the default user + var user = await UserHelpers.CreateDefaultUser(fixture); + if (user.Username is null) { - // first create the default user - var user = await UserHelpers.CreateDefaultUser(fixture); + throw new RestException(HttpStatusCode.BadRequest); + } - var dbContext = fixture.GetDbContext(); - var currentAccessor = new StubCurrentUserAccessor(user.Username); + var dbContext = fixture.GetDbContext(); + var currentAccessor = new StubCurrentUserAccessor(user.Username); - var articleCreateHandler = new Create.Handler(dbContext, currentAccessor); - var created = await articleCreateHandler.Handle(command, new System.Threading.CancellationToken()); + var articleCreateHandler = new Create.Handler(dbContext, currentAccessor); + var created = await articleCreateHandler.Handle( + command, + new System.Threading.CancellationToken() + ); - var dbArticle = await fixture.ExecuteDbContextAsync(db => db.Articles.Where(a => a.ArticleId == created.Article.ArticleId) - .SingleOrDefaultAsync()); + var dbArticle = await fixture.ExecuteDbContextAsync( + db => + db.Articles + .Where(a => a.ArticleId == created.Article.ArticleId) + .SingleOrDefaultAsync() + ); + if (dbArticle is null) + { + throw new RestException(HttpStatusCode.NotFound, new {Article = Constants.NOT_FOUND}); - return dbArticle; } + + return dbArticle; } } diff --git a/tests/Conduit.IntegrationTests/Features/Articles/CreateTests.cs b/tests/Conduit.IntegrationTests/Features/Articles/CreateTests.cs index 8d14c127..ed266d4f 100644 --- a/tests/Conduit.IntegrationTests/Features/Articles/CreateTests.cs +++ b/tests/Conduit.IntegrationTests/Features/Articles/CreateTests.cs @@ -3,26 +3,27 @@ using Conduit.Features.Articles; using Xunit; -namespace Conduit.IntegrationTests.Features.Articles +namespace Conduit.IntegrationTests.Features.Articles; + +public class CreateTests : SliceFixture { - public class CreateTests : SliceFixture + [Fact] + public async Task Expect_Create_Article() { - [Fact] - public async Task Expect_Create_Article() - { - var command = new Create.Command(new Create.ArticleData() + var command = new Create.Command( + new Create.ArticleData() { Title = "Test article dsergiu77", Description = "Description of the test article", Body = "Body of the test article", TagList = new string[] { "tag1", "tag2" } - }); + } + ); - var article = await ArticleHelpers.CreateArticle(this, command); + var article = await ArticleHelpers.CreateArticle(this, command); - Assert.NotNull(article); - Assert.Equal(article.Title, command.Article.Title); - Assert.Equal(article.TagList.Count(), command.Article.TagList.Count()); - } + Assert.NotNull(article); + Assert.Equal(article.Title, command.Article.Title); + Assert.Equal(article.TagList.Count(), command.Article.TagList?.Count()); } } diff --git a/tests/Conduit.IntegrationTests/Features/Articles/DeleteTests.cs b/tests/Conduit.IntegrationTests/Features/Articles/DeleteTests.cs index eb43a339..a88d7f67 100644 --- a/tests/Conduit.IntegrationTests/Features/Articles/DeleteTests.cs +++ b/tests/Conduit.IntegrationTests/Features/Articles/DeleteTests.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using System.Threading.Tasks; using Conduit.Features.Articles; @@ -6,100 +7,121 @@ using Microsoft.EntityFrameworkCore; using Xunit; -namespace Conduit.IntegrationTests.Features.Articles +namespace Conduit.IntegrationTests.Features.Articles; + +public class DeleteTests : SliceFixture { - public class DeleteTests : SliceFixture + [Fact] + public async Task Expect_Delete_Article() { - [Fact] - public async Task Expect_Delete_Article() - { - var createCmd = new Create.Command(new Create.ArticleData() + var createCmd = new Create.Command( + new Create.ArticleData() { Title = "Test article dsergiu77", Description = "Description of the test article", Body = "Body of the test article", + } + ); - }); - - var article = await ArticleHelpers.CreateArticle(this, createCmd); - var slug = article.Slug; + var article = await ArticleHelpers.CreateArticle(this, createCmd); + var slug = article.Slug ?? throw new InvalidOperationException(); - var deleteCmd = new Delete.Command(slug); + var deleteCmd = new Delete.Command(slug); - var dbContext = GetDbContext(); + var dbContext = GetDbContext(); - var articleDeleteHandler = new Delete.QueryHandler(dbContext); - await articleDeleteHandler.Handle(deleteCmd, new System.Threading.CancellationToken()); + var articleDeleteHandler = new Delete.QueryHandler(dbContext); + await articleDeleteHandler.Handle(deleteCmd, new System.Threading.CancellationToken()); - var dbArticle = await ExecuteDbContextAsync(db => db.Articles.Where(d => d.Slug == deleteCmd.Slug).SingleOrDefaultAsync()); + var dbArticle = await ExecuteDbContextAsync( + db => db.Articles.Where(d => d.Slug == deleteCmd.Slug).SingleOrDefaultAsync() + ); - Assert.Null(dbArticle); - } + Assert.Null(dbArticle); + } - [Fact] - public async Task Expect_Delete_Article_With_Tags() - { - var createCmd = new Create.Command(new Create.ArticleData() + [Fact] + public async Task Expect_Delete_Article_With_Tags() + { + var createCmd = new Create.Command( + new Create.ArticleData() { Title = "Test article dsergiu77", Description = "Description of the test article", Body = "Body of the test article", TagList = new string[] { "tag1", "tag2" } - }); + } + ); - var article = await ArticleHelpers.CreateArticle(this, createCmd); - var dbArticleWithTags = await ExecuteDbContextAsync( - db => db.Articles.Include(a => a.ArticleTags) - .Where(d => d.Slug == article.Slug).SingleOrDefaultAsync() - ); + var article = await ArticleHelpers.CreateArticle(this, createCmd); + var dbArticleWithTags = await ExecuteDbContextAsync( + db => + db.Articles + .Include(a => a.ArticleTags) + .Where(d => d.Slug == article.Slug) + .SingleOrDefaultAsync() + ); - var deleteCmd = new Delete.Command(article.Slug); + var deleteCmd = new Delete.Command(article.Slug?? throw new InvalidOperationException()); - var dbContext = GetDbContext(); + var dbContext = GetDbContext(); - var articleDeleteHandler = new Delete.QueryHandler(dbContext); - await articleDeleteHandler.Handle(deleteCmd, new System.Threading.CancellationToken()); + var articleDeleteHandler = new Delete.QueryHandler(dbContext); + await articleDeleteHandler.Handle(deleteCmd, new System.Threading.CancellationToken()); - var dbArticle = await ExecuteDbContextAsync(db => db.Articles.Where(d => d.Slug == deleteCmd.Slug).SingleOrDefaultAsync()); - Assert.Null(dbArticle); - } + var dbArticle = await ExecuteDbContextAsync( + db => db.Articles.Where(d => d.Slug == deleteCmd.Slug).SingleOrDefaultAsync() + ); + Assert.Null(dbArticle); + } - [Fact] - public async Task Expect_Delete_Article_With_Comments() - { - var createArticleCmd = new Create.Command(new Create.ArticleData() + [Fact] + public async Task Expect_Delete_Article_With_Comments() + { + var createArticleCmd = new Create.Command( + new Create.ArticleData() { Title = "Test article dsergiu77", Description = "Description of the test article", Body = "Body of the test article", - }); - - var article = await ArticleHelpers.CreateArticle(this, createArticleCmd); - var dbArticle = await ExecuteDbContextAsync( - db => db.Articles.Include(a => a.ArticleTags) - .Where(d => d.Slug == article.Slug).SingleOrDefaultAsync() - ); - - var articleId = dbArticle.ArticleId; - var slug = dbArticle.Slug; - - // create article comment - var createCommentCmd = - new Conduit.Features.Comments.Create.Command( - new(new Conduit.Features.Comments.Create.CommentData("article comment")), slug); - - var comment = await CommentHelpers.CreateComment(this, createCommentCmd, UserHelpers.DefaultUserName); - - // delete article with comment - var deleteCmd = new Delete.Command(slug); - - var dbContext = GetDbContext(); - - var articleDeleteHandler = new Delete.QueryHandler(dbContext); - await articleDeleteHandler.Handle(deleteCmd, new System.Threading.CancellationToken()); - - var deleted = await ExecuteDbContextAsync(db => db.Articles.Where(d => d.Slug == deleteCmd.Slug).SingleOrDefaultAsync()); - Assert.Null(deleted); - } + } + ); + + var article = await ArticleHelpers.CreateArticle(this, createArticleCmd); + var dbArticle = await ExecuteDbContextAsync( + db => + db.Articles + .Include(a => a.ArticleTags) + .Where(d => d.Slug == article.Slug) + .SingleOrDefaultAsync() + ) ?? throw new InvalidOperationException(); + + var articleId = dbArticle.ArticleId; + var slug = dbArticle.Slug; + + // create article comment + var createCommentCmd = new Conduit.Features.Comments.Create.Command( + new(new Conduit.Features.Comments.Create.CommentData("article comment")), + slug?? throw new InvalidOperationException() + ); + + var comment = await CommentHelpers.CreateComment( + this, + createCommentCmd, + UserHelpers.DefaultUserName + ); + + // delete article with comment + var deleteCmd = new Delete.Command(slug); + + var dbContext = GetDbContext(); + + var articleDeleteHandler = new Delete.QueryHandler(dbContext); + await articleDeleteHandler.Handle(deleteCmd, new System.Threading.CancellationToken()); + + var deleted = await ExecuteDbContextAsync( + db => db.Articles.Where(d => d.Slug == deleteCmd.Slug).SingleOrDefaultAsync() + ); + Assert.Null(deleted); } } diff --git a/tests/Conduit.IntegrationTests/Features/Articles/EditTests.cs b/tests/Conduit.IntegrationTests/Features/Articles/EditTests.cs index 3ba77dea..018850c6 100644 --- a/tests/Conduit.IntegrationTests/Features/Articles/EditTests.cs +++ b/tests/Conduit.IntegrationTests/Features/Articles/EditTests.cs @@ -1,45 +1,55 @@ +using System; using System.Linq; using System.Threading.Tasks; using Conduit.Features.Articles; using Xunit; -namespace Conduit.IntegrationTests.Features.Articles +namespace Conduit.IntegrationTests.Features.Articles; + +public class EditTests : SliceFixture { - public class EditTests : SliceFixture + [Fact] + public async Task Expect_Edit_Article() { - [Fact] - public async Task Expect_Edit_Article() - { - var createCommand = new Create.Command(new Create.ArticleData() + var createCommand = new Create.Command( + new Create.ArticleData() { Title = "Test article dsergiu77", Description = "Description of the test article", Body = "Body of the test article", TagList = new string[] { "tag1", "tag2" } - }); + } + ); - var createdArticle = await ArticleHelpers.CreateArticle(this, createCommand); + var createdArticle = await ArticleHelpers.CreateArticle(this, createCommand); - var command = new Edit.Command(new(new Edit.ArticleData() - { - Title = "Updated " + createdArticle.Title, - Description = "Updated" + createdArticle.Description, - Body = "Updated" + createdArticle.Body, - }), createdArticle.Slug); - // remove the first tag and add a new tag - command.Model.Article.TagList = new string[] { createdArticle.TagList[1], "tag3" }; + var command = new Edit.Command( + new( + new Edit.ArticleData() + { + Title = "Updated " + createdArticle.Title, + Description = "Updated" + createdArticle.Description, + Body = "Updated" + createdArticle.Body, + } + ), + createdArticle.Slug ?? throw new InvalidOperationException() + ); + // remove the first tag and add a new tag + command.Model.Article.TagList = new string[] { createdArticle.TagList[1], "tag3" }; - var dbContext = GetDbContext(); + var dbContext = GetDbContext(); - var articleEditHandler = new Edit.Handler(dbContext); - var edited = await articleEditHandler.Handle(command, new System.Threading.CancellationToken()); + var articleEditHandler = new Edit.Handler(dbContext); + var edited = await articleEditHandler.Handle( + command, + new System.Threading.CancellationToken() + ); - Assert.NotNull(edited); - Assert.Equal(edited.Article.Title, command.Model.Article.Title); - Assert.Equal(edited.Article.TagList.Count(), command.Model.Article.TagList.Count()); - // use assert Contains because we do not know the order in which the tags are saved/retrieved - Assert.Contains(edited.Article.TagList[0], command.Model.Article.TagList); - Assert.Contains(edited.Article.TagList[1], command.Model.Article.TagList); - } + Assert.NotNull(edited); + Assert.Equal(edited.Article.Title, command.Model.Article.Title); + Assert.Equal(edited.Article.TagList.Count(), command.Model.Article.TagList.Count()); + // use assert Contains because we do not know the order in which the tags are saved/retrieved + Assert.Contains(edited.Article.TagList[0], command.Model.Article.TagList); + Assert.Contains(edited.Article.TagList[1], command.Model.Article.TagList); } } diff --git a/tests/Conduit.IntegrationTests/Features/Comments/CommentHelpers.cs b/tests/Conduit.IntegrationTests/Features/Comments/CommentHelpers.cs index c75fea65..4b2fd8ec 100644 --- a/tests/Conduit.IntegrationTests/Features/Comments/CommentHelpers.cs +++ b/tests/Conduit.IntegrationTests/Features/Comments/CommentHelpers.cs @@ -1,47 +1,75 @@ using System.Linq; +using System.Net; using System.Threading.Tasks; +using Conduit.Domain; using Conduit.Features.Comments; +using Conduit.Infrastructure.Errors; using Conduit.IntegrationTests.Features.Users; using Microsoft.EntityFrameworkCore; -namespace Conduit.IntegrationTests.Features.Comments +namespace Conduit.IntegrationTests.Features.Comments; + +public static class CommentHelpers { - public static class CommentHelpers + /// + /// creates an article comment based on the given Create command. + /// Creates a default user if parameter userName is empty. + /// + /// + /// + /// + /// + public static async Task CreateComment( + SliceFixture fixture, + Create.Command command, + string userName + ) { - /// - /// creates an article comment based on the given Create command. - /// Creates a default user if parameter userName is empty. - /// - /// - /// - /// - /// - public static async Task CreateComment(SliceFixture fixture, Create.Command command, string userName) + if (string.IsNullOrWhiteSpace(userName)) { - if (string.IsNullOrWhiteSpace(userName)) + var user = await UserHelpers.CreateDefaultUser(fixture); + + if (user.Username is null) { - var user = await UserHelpers.CreateDefaultUser(fixture); - userName = user.Username; + throw new RestException(HttpStatusCode.BadRequest); } - var dbContext = fixture.GetDbContext(); - var currentAccessor = new StubCurrentUserAccessor(userName); + userName = user.Username; + } - var commentCreateHandler = new Create.Handler(dbContext, currentAccessor); - var created = await commentCreateHandler.Handle(command, new System.Threading.CancellationToken()); + var dbContext = fixture.GetDbContext(); + var currentAccessor = new StubCurrentUserAccessor(userName); - var dbArticleWithComments = await fixture.ExecuteDbContextAsync( - db => db.Articles - .Include(a => a.Comments).Include(a => a.Author) + var commentCreateHandler = new Create.Handler(dbContext, currentAccessor); + var created = await commentCreateHandler.Handle( + command, + new System.Threading.CancellationToken() + ); + + var dbArticleWithComments = await fixture.ExecuteDbContextAsync( + db => + db.Articles + .Include(a => a.Comments) + .Include(a => a.Author) .Where(a => a.Slug == command.Slug) .SingleOrDefaultAsync() - ); + ); - var dbComment = dbArticleWithComments.Comments - .Where(c => c.ArticleId == dbArticleWithComments.ArticleId && c.Author == dbArticleWithComments.Author) - .FirstOrDefault(); + if (dbArticleWithComments is null) + { + throw new RestException(HttpStatusCode.NotFound, new {Article = Constants.NOT_FOUND}); + } + + var dbComment = dbArticleWithComments.Comments.FirstOrDefault(c => + c.ArticleId == dbArticleWithComments.ArticleId + && c.Author == dbArticleWithComments.Author +); - return dbComment; + if (dbComment is null) + { + throw new RestException(HttpStatusCode.NotFound, new {Article = Constants.NOT_FOUND}); } + + return dbComment; } } diff --git a/tests/Conduit.IntegrationTests/Features/Users/CreateTests.cs b/tests/Conduit.IntegrationTests/Features/Users/CreateTests.cs index 06fdcb18..bd3b0c90 100644 --- a/tests/Conduit.IntegrationTests/Features/Users/CreateTests.cs +++ b/tests/Conduit.IntegrationTests/Features/Users/CreateTests.cs @@ -5,26 +5,29 @@ using Microsoft.EntityFrameworkCore; using Xunit; -namespace Conduit.IntegrationTests.Features.Users +namespace Conduit.IntegrationTests.Features.Users; + +public class CreateTests : SliceFixture { - public class CreateTests : SliceFixture + [Fact] + public async Task Expect_Create_User() { - [Fact] - public async Task Expect_Create_User() - { - var command = new Create.Command(new Create.UserData() + var command = new Create.Command( + new Create.UserData() { Email = "email", Password = "password", Username = "username" - }); + } + ); - await SendAsync(command); + await SendAsync(command); - var created = await ExecuteDbContextAsync(db => db.Persons.Where(d => d.Email == command.User.Email).SingleOrDefaultAsync()); + var created = await ExecuteDbContextAsync( + db => db.Persons.Where(d => d.Email == command.User.Email).SingleOrDefaultAsync() + ); - Assert.NotNull(created); - Assert.Equal(created.Hash, await new PasswordHasher().Hash("password", created.Salt)); - } + Assert.NotNull(created); + Assert.Equal(created.Hash, await new PasswordHasher().Hash("password", created.Salt)); } -} +} \ No newline at end of file diff --git a/tests/Conduit.IntegrationTests/Features/Users/LoginTests.cs b/tests/Conduit.IntegrationTests/Features/Users/LoginTests.cs index 00f60f20..2783ed70 100644 --- a/tests/Conduit.IntegrationTests/Features/Users/LoginTests.cs +++ b/tests/Conduit.IntegrationTests/Features/Users/LoginTests.cs @@ -5,35 +5,32 @@ using Conduit.Infrastructure.Security; using Xunit; -namespace Conduit.IntegrationTests.Features.Users +namespace Conduit.IntegrationTests.Features.Users; + +public class LoginTests : SliceFixture { - public class LoginTests : SliceFixture + [Fact] + public async Task Expect_Login() { - [Fact] - public async Task Expect_Login() + var salt = Guid.NewGuid().ToByteArray(); + var person = new Person { - var salt = Guid.NewGuid().ToByteArray(); - var person = new Person - { - Username = "username", - Email = "email", - Hash = await new PasswordHasher().Hash("password", salt), - Salt = salt - }; - await InsertAsync(person); + Username = "username", + Email = "email", + Hash = await new PasswordHasher().Hash("password", salt), + Salt = salt + }; + await InsertAsync(person); - var command = new Login.Command(new Login.UserData() - { - Email = "email", - Password = "password" - }); + var command = new Login.Command( + new Login.UserData() { Email = "email", Password = "password" } + ); - var user = await SendAsync(command); + var user = await SendAsync(command); - Assert.NotNull(user?.User); - Assert.Equal(user.User.Email, command.User.Email); - Assert.Equal("username", user.User.Username); - Assert.NotNull(user.User.Token); - } + Assert.NotNull(user?.User); + Assert.Equal(user.User.Email, command.User.Email); + Assert.Equal("username", user.User.Username); + Assert.NotNull(user.User.Token); } -} +} \ No newline at end of file diff --git a/tests/Conduit.IntegrationTests/Features/Users/UserHelpers.cs b/tests/Conduit.IntegrationTests/Features/Users/UserHelpers.cs index 1194cb58..5529a2fa 100644 --- a/tests/Conduit.IntegrationTests/Features/Users/UserHelpers.cs +++ b/tests/Conduit.IntegrationTests/Features/Users/UserHelpers.cs @@ -1,28 +1,29 @@ using System.Threading.Tasks; using Conduit.Features.Users; -namespace Conduit.IntegrationTests.Features.Users +namespace Conduit.IntegrationTests.Features.Users; + +public static class UserHelpers { - public static class UserHelpers - { - public static readonly string DefaultUserName = "username"; + public static readonly string DefaultUserName = "username"; - /// - /// creates a default user to be used in different tests - /// - /// - /// - public static async Task CreateDefaultUser(SliceFixture fixture) - { - var command = new Create.Command(new Create.UserData() + /// + /// creates a default user to be used in different tests + /// + /// + /// + public static async Task CreateDefaultUser(SliceFixture fixture) + { + var command = new Create.Command( + new Create.UserData() { Email = "email", Password = "password", Username = DefaultUserName - }); + } + ); - var commandResult = await fixture.SendAsync(command); - return commandResult.User; - } + var commandResult = await fixture.SendAsync(command); + return commandResult.User; } -} +} \ No newline at end of file diff --git a/tests/Conduit.IntegrationTests/SliceFixture.cs b/tests/Conduit.IntegrationTests/SliceFixture.cs index 5df2dd8c..11edea01 100644 --- a/tests/Conduit.IntegrationTests/SliceFixture.cs +++ b/tests/Conduit.IntegrationTests/SliceFixture.cs @@ -6,82 +6,82 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -namespace Conduit.IntegrationTests +namespace Conduit.IntegrationTests; + +public class SliceFixture : IDisposable { - public class SliceFixture : IDisposable - { - //private static readonly IConfiguration CONFIG; + //private static readonly IConfiguration CONFIG; - private readonly IServiceScopeFactory _scopeFactory; - private readonly ServiceProvider _provider; - private readonly string _dbName = Guid.NewGuid() + ".db"; + private readonly IServiceScopeFactory _scopeFactory; + private readonly ServiceProvider _provider; + private readonly string _dbName = Guid.NewGuid() + ".db"; - //static SliceFixture() => CONFIG = new ConfigurationBuilder() - // .AddEnvironmentVariables() - // .Build(); + //static SliceFixture() => CONFIG = new ConfigurationBuilder() + // .AddEnvironmentVariables() + // .Build(); - public SliceFixture() - { - var startup = new Startup(); - var services = new ServiceCollection(); + public SliceFixture() + { + var startup = new Startup(); + var services = new ServiceCollection(); - var builder = new DbContextOptionsBuilder(); - builder.UseInMemoryDatabase(_dbName); - services.AddSingleton(new ConduitContext(builder.Options)); + var builder = new DbContextOptionsBuilder(); + builder.UseInMemoryDatabase(_dbName); + services.AddSingleton(new ConduitContext(builder.Options)); - startup.ConfigureServices(services); + startup.ConfigureServices(services); - _provider = services.BuildServiceProvider(); + _provider = services.BuildServiceProvider(); - GetDbContext().Database.EnsureCreated(); - _scopeFactory = _provider.GetService(); - } + GetDbContext().Database.EnsureCreated(); + _scopeFactory = _provider.GetRequiredService(); + } - public ConduitContext GetDbContext() => _provider.GetRequiredService(); + public ConduitContext GetDbContext() => _provider.GetRequiredService(); - public void Dispose() => File.Delete(_dbName); + public void Dispose() => File.Delete(_dbName); - public async Task ExecuteScopeAsync(Func action) - { - using (var scope = _scopeFactory.CreateScope()) - { - await action(scope.ServiceProvider); - } - } + public async Task ExecuteScopeAsync(Func action) + { + using var scope = _scopeFactory.CreateScope(); + await action(scope.ServiceProvider); + } - public async Task ExecuteScopeAsync(Func> action) - { - using (var scope = _scopeFactory.CreateScope()) - { - return await action(scope.ServiceProvider); - } - } + public async Task ExecuteScopeAsync(Func> action) + { + using var scope = _scopeFactory.CreateScope(); + return await action(scope.ServiceProvider); + } - public Task SendAsync(IRequest request) => ExecuteScopeAsync(sp => - { - var mediator = sp.GetService(); + public Task SendAsync(IRequest request) => + ExecuteScopeAsync(sp => + { + var mediator = sp.GetRequiredService(); - return mediator.Send(request); - }); + return mediator.Send(request); + }); - public Task SendAsync(IRequest request) => ExecuteScopeAsync(sp => - { - var mediator = sp.GetService(); + public Task SendAsync(IRequest request) => + ExecuteScopeAsync(sp => + { + var mediator = sp.GetRequiredService(); - return mediator.Send(request); - }); + return mediator.Send(request); + }); - public Task ExecuteDbContextAsync(Func action) => ExecuteScopeAsync(sp => action(sp.GetService())); + public Task ExecuteDbContextAsync(Func action) => + ExecuteScopeAsync(sp => action(sp.GetRequiredService())); - public Task ExecuteDbContextAsync(Func> action) => ExecuteScopeAsync(sp => action(sp.GetService())); + public Task ExecuteDbContextAsync(Func> action) => + ExecuteScopeAsync(sp => action(sp.GetRequiredService())); - public Task InsertAsync(params object[] entities) => ExecuteDbContextAsync(db => - { - foreach (var entity in entities) - { - db.Add(entity); - } - return db.SaveChangesAsync(); - }); - } + public Task InsertAsync(params object[] entities) => + ExecuteDbContextAsync(db => + { + foreach (var entity in entities) + { + db.Add(entity); + } + return db.SaveChangesAsync(); + }); } diff --git a/tests/Conduit.IntegrationTests/StubCurrentUserAccessor.cs b/tests/Conduit.IntegrationTests/StubCurrentUserAccessor.cs index ce3f5b2a..6cb2ef1c 100644 --- a/tests/Conduit.IntegrationTests/StubCurrentUserAccessor.cs +++ b/tests/Conduit.IntegrationTests/StubCurrentUserAccessor.cs @@ -1,17 +1,16 @@ using Conduit.Infrastructure; -namespace Conduit.IntegrationTests +namespace Conduit.IntegrationTests; + +public class StubCurrentUserAccessor : ICurrentUserAccessor { - public class StubCurrentUserAccessor : ICurrentUserAccessor - { - private readonly string _currentUserName; + private readonly string _currentUserName; - /// - /// stub the ICurrentUserAccessor with a given userName to be used in tests - /// - /// - public StubCurrentUserAccessor(string userName) => _currentUserName = userName; + /// + /// stub the ICurrentUserAccessor with a given userName to be used in tests + /// + /// + public StubCurrentUserAccessor(string userName) => _currentUserName = userName; - public string GetCurrentUsername() => _currentUserName; - } -} + public string GetCurrentUsername() => _currentUserName; +} \ No newline at end of file diff --git a/tests/Conduit.IntegrationTests/packages.lock.json b/tests/Conduit.IntegrationTests/packages.lock.json new file mode 100644 index 00000000..a2956a28 --- /dev/null +++ b/tests/Conduit.IntegrationTests/packages.lock.json @@ -0,0 +1,1596 @@ +{ + "version": 2, + "dependencies": { + "net7.0": { + "Microsoft.NET.Test.Sdk": { + "type": "Direct", + "requested": "[17.7.2, )", + "resolved": "17.7.2", + "contentHash": "WOSF/GUYcnrLGkdvCbXDRig2rShtBwfQc5l7IxQE6PZI3CeXAqF1SpyzwlGA5vw+MdEAXs3niy+ZkGBBWna6tw==", + "dependencies": { + "Microsoft.CodeCoverage": "17.7.2", + "Microsoft.TestPlatform.TestHost": "17.7.2" + } + }, + "xunit": { + "type": "Direct", + "requested": "[2.5.0, )", + "resolved": "2.5.0", + "contentHash": "f2V5wuAdoaq0mRTt9UBmPbVex9HcwFYn+y7WaKUz5Xpakcrv7lhtQWBJUWNY4N3Z+o+atDBLyAALM1QWx04C6Q==", + "dependencies": { + "xunit.analyzers": "1.2.0", + "xunit.assert": "2.5.0", + "xunit.core": "[2.5.0]" + } + }, + "xunit.runner.visualstudio": { + "type": "Direct", + "requested": "[2.5.0, )", + "resolved": "2.5.0", + "contentHash": "+Gp9vuC2431yPyKB15YrOTxCuEAErBQUTIs6CquumX1F073UaPHGW0VE/XVJLMh9W4sXdz3TBkcHdFWZrRn2Hw==" + }, + "Azure.Core": { + "type": "Transitive", + "resolved": "1.24.0", + "contentHash": "+/qI1j2oU1S4/nvxb2k/wDsol00iGf1AyJX5g3epV7eOpQEP/2xcgh/cxgKMeFgn3U2fmgSiBnQZdkV+l5y0Uw==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "1.1.1", + "System.Diagnostics.DiagnosticSource": "4.6.0", + "System.Memory.Data": "1.0.2", + "System.Numerics.Vectors": "4.5.0", + "System.Text.Encodings.Web": "4.7.2", + "System.Text.Json": "4.7.2", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Azure.Identity": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "EycyMsb6rD2PK9P0SyibFfEhvWWttdrYhyPF4f41uzdB/44yQlV+2Wehxyg489Rj6gbPvSPgbKq0xsHJBhipZA==", + "dependencies": { + "Azure.Core": "1.24.0", + "Microsoft.Identity.Client": "4.39.0", + "Microsoft.Identity.Client.Extensions.Msal": "2.19.3", + "System.Memory": "4.5.4", + "System.Security.Cryptography.ProtectedData": "4.7.0", + "System.Text.Json": "4.7.2", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "FluentValidation": { + "type": "Transitive", + "resolved": "11.5.1", + "contentHash": "0h1Q5lNOLLyYTWMJmyNoMqhY4CBRvvUWvJP1R4F2CnmmzuWwvB0A8aVmw5+lOuwYnwUwCRrdeMLbc81F38ahNQ==" + }, + "FluentValidation.DependencyInjectionExtensions": { + "type": "Transitive", + "resolved": "11.5.1", + "contentHash": "iWM0LS1MDYX06pcjMEQKqHirl2zkjHlNV23mEJSoR1IZI7KQmTa0RcTtGEJpj5+iHvBCfrzP2mYKM4FtRKVb+A==", + "dependencies": { + "FluentValidation": "11.5.1", + "Microsoft.Extensions.Dependencyinjection.Abstractions": "2.1.0" + } + }, + "MediatR.Contracts": { + "type": "Transitive", + "resolved": "2.0.1", + "contentHash": "FYv95bNT4UwcNA+G/J1oX5OpRiSUxteXaUt2BJbRSdRNiIUNbggJF69wy6mnk2wYToaanpdXZdCwVylt96MpwQ==" + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "1.1.1", + "contentHash": "yuvf07qFWFqtK3P/MRkEKLhn5r2UbSpVueRziSqj0yJQIKFwG1pq9mOayK3zE5qZCTs0CbrwL9M6R8VwqyGy2w==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.7.2", + "contentHash": "ntbkwIqwszkfCRjxVZOyEQiHauiYsY9NtYjw9ASsoxDSiG8YtV6AGcOAwrAk3TZv2UOq4MrpX+3MYEeMHSb03w==" + }, + "Microsoft.CSharp": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" + }, + "Microsoft.Data.SqlClient": { + "type": "Transitive", + "resolved": "5.0.2", + "contentHash": "mxcYU9I5TLzUegLVXiTtOE89RXN3GafL1Y+ExIbXvivvQtxplI4wxOgsiZGO4TZC18OJqui7mLVmiYpdFFImRQ==", + "dependencies": { + "Azure.Identity": "1.6.0", + "Microsoft.Data.SqlClient.SNI.runtime": "5.0.1", + "Microsoft.Identity.Client": "4.45.0", + "Microsoft.IdentityModel.JsonWebTokens": "6.21.0", + "Microsoft.IdentityModel.Protocols.OpenIdConnect": "6.21.0", + "Microsoft.SqlServer.Server": "1.0.0", + "Microsoft.Win32.Registry": "5.0.0", + "System.Buffers": "4.5.1", + "System.Configuration.ConfigurationManager": "5.0.0", + "System.Diagnostics.DiagnosticSource": "5.0.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime.Caching": "5.0.0", + "System.Security.Cryptography.Cng": "5.0.0", + "System.Security.Principal.Windows": "5.0.0", + "System.Text.Encoding.CodePages": "5.0.0", + "System.Text.Encodings.Web": "4.7.2" + } + }, + "Microsoft.Data.SqlClient.SNI.runtime": { + "type": "Transitive", + "resolved": "5.0.1", + "contentHash": "y0X5MxiNdbITJYoafJ2ruaX6hqO0twpCGR/ipiDOe85JKLU8WL4TuAQfDe5qtt3bND5Je26HnrarLSAMMnVTNg==" + }, + "Microsoft.Data.Sqlite.Core": { + "type": "Transitive", + "resolved": "7.0.10", + "contentHash": "SA6LOen490YTforjaNfVDClYpwiG3LmHJM8objq6KwIIFP0fq9Eulhbu6zBl6AduMefQqUA3pllrFFVQpwPoqw==", + "dependencies": { + "SQLitePCLRaw.core": "2.1.4" + } + }, + "Microsoft.EntityFrameworkCore": { + "type": "Transitive", + "resolved": "7.0.10", + "contentHash": "24NbXJqJ/x8u88/agqeb1pLdAF9+9StDLA36+P/3g5xsJPOaB2GxXn7epR8dWpZTgHsNZ7cvBMxBgfFmF+xZlg==", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "7.0.10", + "Microsoft.EntityFrameworkCore.Analyzers": "7.0.10", + "Microsoft.Extensions.Caching.Memory": "7.0.0", + "Microsoft.Extensions.DependencyInjection": "7.0.0", + "Microsoft.Extensions.Logging": "7.0.0" + } + }, + "Microsoft.EntityFrameworkCore.Abstractions": { + "type": "Transitive", + "resolved": "7.0.10", + "contentHash": "Z/lDWmGLiT9uNQrp6UXTKZxofSmAKQCiKOz98FDscTbfAGgBXE3DTTqRsPMc8HFIVVSNANSiFRz3JyLg07HN9Q==" + }, + "Microsoft.EntityFrameworkCore.Analyzers": { + "type": "Transitive", + "resolved": "7.0.10", + "contentHash": "+8NVNpyJTzW6nNh/7RGfldf+mbeboVcn+X1tD8kMBCEJswuy3RqM/qecEEfOfTcWLliZExPMaHwOwtHO6RMpdA==" + }, + "Microsoft.EntityFrameworkCore.Relational": { + "type": "Transitive", + "resolved": "7.0.10", + "contentHash": "PO2QB2Du+pW210UHmepYR12bk+ZOZJCiNkA7zEAxWs+vzvrRAMsUPlDlfgX2LXE7NBsnb0uvZp7a1/qqKf3fRQ==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "7.0.10", + "Microsoft.Extensions.Configuration.Abstractions": "7.0.0" + } + }, + "Microsoft.EntityFrameworkCore.Sqlite.Core": { + "type": "Transitive", + "resolved": "7.0.10", + "contentHash": "9ujvh/RXXyXmSsh0yY3zI7S+E+ILzndLVsDkigXGkDPVrDqp7/ynqElj0Xb/e4qWHbg/ogoRe5rvUs0jzfl+xg==", + "dependencies": { + "Microsoft.Data.Sqlite.Core": "7.0.10", + "Microsoft.EntityFrameworkCore.Relational": "7.0.10", + "Microsoft.Extensions.DependencyModel": "7.0.0" + } + }, + "Microsoft.Extensions.ApiDescription.Server": { + "type": "Transitive", + "resolved": "6.0.5", + "contentHash": "Ckb5EDBUNJdFWyajfXzUIMRkhf52fHZOQuuZg/oiu8y7zDCVwD0iHhew6MnThjHmevanpxL3f5ci2TtHQEN6bw==" + }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "IeimUd0TNbhB4ded3AbgBLQv2SnsiVugDyGV1MvspQFVlA07nDC7Zul7kcwH5jWN3JiTcp/ySE83AIJo8yfKjg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.Caching.Memory": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "xpidBs2KCE2gw1JrD0quHE72kvCaI3xFql5/Peb2GRtUuZX+dYPoK/NTdVMiM67Svym0M0Df9A3xyU0FbMQhHw==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "7.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Logging.Abstractions": "7.0.0", + "Microsoft.Extensions.Options": "7.0.0", + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "f34u2eaqIjNO9YLHBz8rozVZ+TcFiFs0F3r7nUJd7FRkVSxk8u4OpoK226mi49MwexHOR2ibP9MFvRUaLilcQQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "elNeOmkeX3eDVG6pYVeV82p29hr+UKDaBhrZyWvWLw/EVZSYEkZlQdkp0V39k/Xehs2Qa0mvoCvkVj3eQxNQ1Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "h3j/QfmFN4S0w4C2A6X7arXij/M/OVw3uQHSOFxnND4DyAzO1F9eMX7Eti7lU/OkSthEE0WzRsfT/Dmx86jzCw==" + }, + "Microsoft.Extensions.DependencyModel": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "oONNYd71J3LzkWc4fUHl3SvMfiQMYUCo/mDHDEu76hYYxdhdrPYv6fvGv9nnKVyhE9P0h20AU8RZB5OOWQcAXg==", + "dependencies": { + "System.Text.Encodings.Web": "7.0.0", + "System.Text.Json": "7.0.0" + } + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection": "7.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Logging.Abstractions": "7.0.0", + "Microsoft.Extensions.Options": "7.0.0" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "kmn78+LPVMOWeITUjIlfxUPDsI0R6G0RkeAMBmQxAJ7vBJn4q2dTva7pWi65ceN5vPGjJ9q/Uae2WKgvfktJAw==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "lP1yBnTTU42cKpMozuafbvNtQ7QcBjr/CcK3bYOGEMH55Fjt+iecXjT6chR7vbgCMqy3PG3aNQSZgo/EuY/9qQ==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", + "Microsoft.Extensions.Primitives": "7.0.0" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q==" + }, + "Microsoft.Identity.Client": { + "type": "Transitive", + "resolved": "4.45.0", + "contentHash": "ircobISCLWbtE5eEoLKU+ldfZ8O41vg4lcy38KRj/znH17jvBiAl8oxcyNp89CsuqE3onxIpn21Ca7riyDDrRw==", + "dependencies": { + "Microsoft.IdentityModel.Abstractions": "6.18.0" + } + }, + "Microsoft.Identity.Client.Extensions.Msal": { + "type": "Transitive", + "resolved": "2.19.3", + "contentHash": "zVVZjn8aW7W79rC1crioDgdOwaFTQorsSO6RgVlDDjc7MvbEGz071wSNrjVhzR0CdQn6Sefx7Abf1o7vasmrLg==", + "dependencies": { + "Microsoft.Identity.Client": "4.38.0", + "System.Security.Cryptography.ProtectedData": "4.5.0" + } + }, + "Microsoft.IdentityModel.Abstractions": { + "type": "Transitive", + "resolved": "6.21.0", + "contentHash": "XeE6LQtD719Qs2IG7HDi1TSw9LIkDbJ33xFiOBoHbApVw/8GpIBCbW+t7RwOjErUDyXZvjhZliwRkkLb8Z1uzg==" + }, + "Microsoft.IdentityModel.JsonWebTokens": { + "type": "Transitive", + "resolved": "6.21.0", + "contentHash": "d3h1/BaMeylKTkdP6XwRCxuOoDJZ44V9xaXr6gl5QxmpnZGdoK3bySo3OQN8ehRLJHShb94ElLUvoXyglQtgAw==", + "dependencies": { + "Microsoft.IdentityModel.Tokens": "6.21.0" + } + }, + "Microsoft.IdentityModel.Logging": { + "type": "Transitive", + "resolved": "6.21.0", + "contentHash": "tuEhHIQwvBEhMf8I50hy8FHmRSUkffDFP5EdLsSDV4qRcl2wvOPkQxYqEzWkh+ytW6sbdJGEXElGhmhDfAxAKg==", + "dependencies": { + "Microsoft.IdentityModel.Abstractions": "6.21.0" + } + }, + "Microsoft.IdentityModel.Protocols": { + "type": "Transitive", + "resolved": "6.21.0", + "contentHash": "0FqY5cTLQKtHrClzHEI+QxJl8OBT2vUiEQQB7UKk832JDiJJmetzYZ3AdSrPjN/3l3nkhByeWzXnhrX0JbifKg==", + "dependencies": { + "Microsoft.IdentityModel.Logging": "6.21.0", + "Microsoft.IdentityModel.Tokens": "6.21.0" + } + }, + "Microsoft.IdentityModel.Protocols.OpenIdConnect": { + "type": "Transitive", + "resolved": "6.21.0", + "contentHash": "vtSKL7n6EnAsLyxmiviusm6LKrblT2ndnNqN6rvVq6iIHAnPCK9E2DkDx6h1Jrpy1cvbp40r0cnTg23nhEAGTA==", + "dependencies": { + "Microsoft.IdentityModel.Protocols": "6.21.0", + "System.IdentityModel.Tokens.Jwt": "6.21.0" + } + }, + "Microsoft.IdentityModel.Tokens": { + "type": "Transitive", + "resolved": "6.21.0", + "contentHash": "AAEHZvZyb597a+QJSmtxH3n2P1nIJGpZ4Q89GTenknRx6T6zyfzf592yW/jA5e8EHN4tNMjjXHQaYWEq5+L05w==", + "dependencies": { + "Microsoft.CSharp": "4.5.0", + "Microsoft.IdentityModel.Logging": "6.21.0", + "System.Security.Cryptography.Cng": "4.5.0" + } + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" + }, + "Microsoft.NETCore.Targets": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" + }, + "Microsoft.OpenApi": { + "type": "Transitive", + "resolved": "1.2.3", + "contentHash": "Nug3rO+7Kl5/SBAadzSMAVgqDlfGjJZ0GenQrLywJ84XGKO0uRqkunz5Wyl0SDwcR71bAATXvSdbdzPrYRYKGw==" + }, + "Microsoft.SqlServer.Server": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "N4KeF3cpcm1PUHym1RmakkzfkEv3GRMyofVv40uXsQhCQeglr2OHNcUk2WOG51AKpGO8ynGpo9M/kFXSzghwug==" + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.7.2", + "contentHash": "aHzQWgDMVBnk39HhQVmn06w+YxzF1h2V5/M4WgrNQAn7q97GR4Si3vLRTDlmJo9nK/Nknce+H4tXx4gqOKyLeg==", + "dependencies": { + "NuGet.Frameworks": "6.5.0", + "System.Reflection.Metadata": "1.6.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.7.2", + "contentHash": "pv9yVD7IKPLJV28zYjLsWFiM3j506I2ye+6NquG8vsbm/gR7lgyig8IgY6Vo57VMvGaAKwtUECzcj+C5tH271Q==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.7.2", + "Newtonsoft.Json": "13.0.1" + } + }, + "Microsoft.Win32.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "Bh6blKG8VAKvXiLe2L+sEsn62nc1Ij34MrNxepD2OCrS5cpCwQa9MeLyhVQPQ/R4Wlzwuy6wMK8hLb11QPDRsQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0" + } + }, + "NETStandard.Library": { + "type": "Transitive", + "resolved": "1.6.1", + "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.Win32.Primitives": "4.3.0", + "System.AppContext": "4.3.0", + "System.Collections": "4.3.0", + "System.Collections.Concurrent": "4.3.0", + "System.Console": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tools": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Calendars": "4.3.0", + "System.IO": "4.3.0", + "System.IO.Compression": "4.3.0", + "System.IO.Compression.ZipFile": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Linq": "4.3.0", + "System.Linq.Expressions": "4.3.0", + "System.Net.Http": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Net.Sockets": "4.3.0", + "System.ObjectModel": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Security.Cryptography.X509Certificates": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encoding.Extensions": "4.3.0", + "System.Text.RegularExpressions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "System.Threading.Timer": "4.3.0", + "System.Xml.ReaderWriter": "4.3.0", + "System.Xml.XDocument": "4.3.0" + } + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, + "NuGet.Frameworks": { + "type": "Transitive", + "resolved": "6.5.0", + "contentHash": "QWINE2x3MbTODsWT1Gh71GaGb5icBz4chS8VYvTgsBnsi8esgN6wtHhydd7fvToWECYGq7T4cgBBDiKD/363fg==" + }, + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==" + }, + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==" + }, + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==" + }, + "runtime.native.System": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.IO.Compression": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.Security.Cryptography.Apple": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", + "dependencies": { + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" + } + }, + "runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", + "dependencies": { + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==" + }, + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==" + }, + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" + }, + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==" + }, + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==" + }, + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==" + }, + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==" + }, + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==" + }, + "SQLitePCLRaw.bundle_e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==", + "dependencies": { + "SQLitePCLRaw.lib.e_sqlite3": "2.1.4", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.4" + } + }, + "SQLitePCLRaw.core": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==", + "dependencies": { + "System.Memory": "4.5.3" + } + }, + "SQLitePCLRaw.lib.e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg==" + }, + "SQLitePCLRaw.provider.e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.4", + "contentHash": "CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==", + "dependencies": { + "SQLitePCLRaw.core": "2.1.4" + } + }, + "Swashbuckle.AspNetCore.Swagger": { + "type": "Transitive", + "resolved": "6.5.0", + "contentHash": "XWmCmqyFmoItXKFsQSwQbEAsjDKcxlNf1l+/Ki42hcb6LjKL8m5Db69OTvz5vLonMSRntYO1XLqz0OP+n3vKnA==", + "dependencies": { + "Microsoft.OpenApi": "1.2.3" + } + }, + "Swashbuckle.AspNetCore.SwaggerGen": { + "type": "Transitive", + "resolved": "6.5.0", + "contentHash": "Y/qW8Qdg9OEs7V013tt+94OdPxbRdbhcEbw4NiwGvf4YBcfhL/y7qp/Mjv/cENsQ2L3NqJ2AOu94weBy/h4KvA==", + "dependencies": { + "Swashbuckle.AspNetCore.Swagger": "6.5.0" + } + }, + "Swashbuckle.AspNetCore.SwaggerUI": { + "type": "Transitive", + "resolved": "6.5.0", + "contentHash": "OvbvxX+wL8skxTBttcBsVxdh73Fag4xwqEU2edh4JMn7Ws/xJHnY/JB1e9RoCb6XpDxUF3hD9A0Z1lEUx40Pfw==" + }, + "System.AppContext": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.Collections": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Collections.Concurrent": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "aM7cbfEfVNlEEOj3DsZP+2g9NRwbkyiAv2isQEzw7pnkDg9ekCU2m1cdJLM02Uq691OaCS91tooaxcEn8d0q5w==", + "dependencies": { + "System.Security.Cryptography.ProtectedData": "5.0.0", + "System.Security.Permissions": "5.0.0" + } + }, + "System.Console": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.Diagnostics.Debug": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "tCQTzPsGZh/A9LhhA6zrqCRV4hOHsK90/G7q3Khxmn6tnB1PuNU0cRaKANP2AWcF9bn0zsuOoZOSrHuJk6oNBA==" + }, + "System.Diagnostics.Tools": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Diagnostics.Tracing": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "SztFwAnpfKC8+sEKXAFxCBWhKQaEd97EiOL7oZJZP56zbqnLpmxACWA8aGseaUExciuEAUuR9dY8f7HkTRAdnw==", + "dependencies": { + "Microsoft.Win32.SystemEvents": "5.0.0" + } + }, + "System.Formats.Asn1": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "MTvUIktmemNB+El0Fgw9egyqT9AYSIk6DTJeoDSpc3GIHxHCMo8COqkWT1mptX5tZ1SlQ6HJZ0OsSvMth1c12w==" + }, + "System.Globalization": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization.Calendars": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Globalization.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.InteropServices": "4.3.0" + } + }, + "System.IdentityModel.Tokens.Jwt": { + "type": "Transitive", + "resolved": "6.21.0", + "contentHash": "JRD8AuypBE+2zYxT3dMJomQVsPYsCqlyZhWel3J1d5nzQokSRyTueF+Q4ID3Jcu6zSZKuzOdJ1MLTkbQsDqcvQ==", + "dependencies": { + "Microsoft.IdentityModel.JsonWebTokens": "6.21.0", + "Microsoft.IdentityModel.Tokens": "6.21.0" + } + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.IO.Compression": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Buffers": "4.3.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.IO.Compression": "4.3.0" + } + }, + "System.IO.Compression.ZipFile": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==", + "dependencies": { + "System.Buffers": "4.3.0", + "System.IO": "4.3.0", + "System.IO.Compression": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.IO.FileSystem": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.IO.FileSystem.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.Linq": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0" + } + }, + "System.Linq.Expressions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Linq": "4.3.0", + "System.ObjectModel": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Emit.Lightweight": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Reflection.TypeExtensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==" + }, + "System.Memory.Data": { + "type": "Transitive", + "resolved": "1.0.2", + "contentHash": "JGkzeqgBsiZwKJZ1IxPNsDFZDhUvuEdX8L8BDC8N3KOj+6zMcNU28CNN59TpZE/VJYy9cP+5M+sbxtWJx3/xtw==", + "dependencies": { + "System.Text.Encodings.Web": "4.7.2", + "System.Text.Json": "4.6.0" + } + }, + "System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.DiagnosticSource": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Extensions": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.OpenSsl": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Security.Cryptography.X509Certificates": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.Net.Http": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Net.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Net.Sockets": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + }, + "System.ObjectModel": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Emit": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", + "dependencies": { + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Emit.ILGeneration": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Emit.Lightweight": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Emit.ILGeneration": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Reflection.TypeExtensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Resources.ResourceManager": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "System.Runtime.Caching": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "30D6MkO8WF9jVGWZIP0hmCN8l9BTY4LCsAzLIe4xFSXzs+AjDotR7DpSmj27pFskDURzUvqYYY0ikModgBTxWw==", + "dependencies": { + "System.Configuration.ConfigurationManager": "5.0.0" + } + }, + "System.Runtime.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.Handles": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime.InteropServices": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Reflection": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, + "System.Runtime.InteropServices.RuntimeInformation": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==", + "dependencies": { + "System.Reflection": "4.3.0", + "System.Reflection.Extensions": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Threading": "4.3.0", + "runtime.native.System": "4.3.0" + } + }, + "System.Runtime.Numerics": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", + "dependencies": { + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0" + } + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Cryptography.Algorithms": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.Apple": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Cng": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "jIMXsKn94T9JY7PvPq/tMfqa6GAaHpElRDpmG+SuL+D3+sTw2M8VhnibKnN8Tq+4JqbPJ/f+BwtLeDMEnzAvRg==", + "dependencies": { + "System.Formats.Asn1": "5.0.0" + } + }, + "System.Security.Cryptography.Csp": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Security.Cryptography.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Collections.Concurrent": "4.3.0", + "System.Linq": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", + "dependencies": { + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", + "dependencies": { + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "HGxMSAFAPLNoxBvSfW08vHde0F9uh7BjASwu6JF9JnXuEPhCY3YUqURn0+bQV/4UWeaqymmrHWV+Aw9riQCtCA==" + }, + "System.Security.Cryptography.X509Certificates": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Calendars": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Cng": "4.3.0", + "System.Security.Cryptography.Csp": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.OpenSsl": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.Net.Http": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "uE8juAhEkp7KDBCdjDIE3H9R1HJuEHqeqX8nLX9gmYKWwsqk3T5qZlPx8qle5DPKimC/Fy3AFTdV7HamgCh9qQ==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Windows.Extensions": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0" + } + }, + "System.Text.Encoding.Extensions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "OP6umVGxc0Z0MvZQBVigj4/U31Pw72ITihDWP9WiWDm+q5aoe0GaJivsfYGq53o6dxH7DcXWiCTl7+0o2CGdmg==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "DaGSsVqKsn/ia6RG8frjwmJonfos0srquhw09TlT8KRw5I43E+4gs+/bZj4K0vShJ5H9imCuXupb4RmS+dBy3w==", + "dependencies": { + "System.Text.Encodings.Web": "7.0.0" + } + }, + "System.Text.RegularExpressions": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", + "dependencies": { + "System.Runtime": "4.3.0" + } + }, + "System.Threading": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", + "dependencies": { + "System.Runtime": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" + }, + "System.Threading.Timer": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "c1ho9WU9ZxMZawML+ssPKZfdnrg/OjR3pe0m9v8230z3acqphwvPJqzAkH54xRYm5ntZHGG1EPP3sux9H3qSPg==", + "dependencies": { + "System.Drawing.Common": "5.0.0" + } + }, + "System.Xml.ReaderWriter": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Text.Encoding.Extensions": "4.3.0", + "System.Text.RegularExpressions": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "System.Threading.Tasks.Extensions": "4.3.0" + } + }, + "System.Xml.XDocument": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tools": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Xml.ReaderWriter": "4.3.0" + } + }, + "xunit.abstractions": { + "type": "Transitive", + "resolved": "2.0.3", + "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg==" + }, + "xunit.analyzers": { + "type": "Transitive", + "resolved": "1.2.0", + "contentHash": "d3dehV/DASLRlR8stWQmbPPjfYC2tct50Evav+OlsJMkfFqkhYvzO1k0s81lk0px8O0knZU/FqC8SqbXOtn+hw==" + }, + "xunit.assert": { + "type": "Transitive", + "resolved": "2.5.0", + "contentHash": "wN84pKX5jzfpgJ0bB6arrCA/oelBeYLCpnQ9Wj5xGEVPydKzVSDY5tEatFLHE/rO0+0RC+I4H5igGE118jRh1w==", + "dependencies": { + "NETStandard.Library": "1.6.1" + } + }, + "xunit.core": { + "type": "Transitive", + "resolved": "2.5.0", + "contentHash": "dnV0Mn2s1C0y2m33AylQyMkEyhBQsL4R0302kwSGiEGuY3JwzEmhTa9pnghyMRPliYSs4fXfkEAP+5bKXryGFg==", + "dependencies": { + "xunit.extensibility.core": "[2.5.0]", + "xunit.extensibility.execution": "[2.5.0]" + } + }, + "xunit.extensibility.core": { + "type": "Transitive", + "resolved": "2.5.0", + "contentHash": "xRm6NIV3i7I+LkjsAJ91Xz2fxJm/oMEi2CYq1G5HlGTgcK1Zo2wNbLO6nKX1VG5FZzXibSdoLwr/MofVvh3mFA==", + "dependencies": { + "NETStandard.Library": "1.6.1", + "xunit.abstractions": "2.0.3" + } + }, + "xunit.extensibility.execution": { + "type": "Transitive", + "resolved": "2.5.0", + "contentHash": "7+v2Bvp+1ew1iMGQVb1glICi8jcNdHbRUX6Ru0dmJBViGdjiS7kyqcX2VxleQhFbKNi+WF0an7/TeTXD283RlQ==", + "dependencies": { + "NETStandard.Library": "1.6.1", + "xunit.extensibility.core": "[2.5.0]" + } + }, + "conduit": { + "type": "Project", + "dependencies": { + "AutoMapper": "[12.0.1, )", + "AutoMapper.Extensions.Microsoft.DependencyInjection": "[12.0.1, )", + "FluentValidation.AspNetCore": "[11.3.0, )", + "MediatR": "[12.1.1, )", + "Microsoft.AspNetCore.Authentication.JwtBearer": "[7.0.10, )", + "Microsoft.EntityFrameworkCore.InMemory": "[7.0.10, )", + "Microsoft.EntityFrameworkCore.SqlServer": "[7.0.10, )", + "Microsoft.EntityFrameworkCore.Sqlite": "[7.0.10, )", + "Serilog": "[3.0.1, )", + "Serilog.Extensions.Logging": "[7.0.0, )", + "Serilog.Sinks.Console": "[4.1.0, )", + "Swashbuckle.AspNetCore": "[6.5.0, )" + } + }, + "AutoMapper": { + "type": "CentralTransitive", + "requested": "[12.0.1, )", + "resolved": "12.0.1", + "contentHash": "hvV62vl6Hp/WfQ24yzo3Co9+OPl8wH8hApwVtgWpiAynVJkUcs7xvehnSftawL8Pe8FrPffBRM3hwzLQqWDNjA==", + "dependencies": { + "Microsoft.CSharp": "4.7.0" + } + }, + "AutoMapper.Extensions.Microsoft.DependencyInjection": { + "type": "CentralTransitive", + "requested": "[12.0.1, )", + "resolved": "12.0.1", + "contentHash": "+g/K+Vpe3gGMKGzjslMOdqNlkikScDjWfVvmWTayrDHaG/n2pPmFBMa+jKX1r/h6BDGFdkyRjAuhFE3ykW+r1g==", + "dependencies": { + "AutoMapper": "[12.0.1]", + "Microsoft.Extensions.Options": "6.0.0" + } + }, + "FluentValidation.AspNetCore": { + "type": "CentralTransitive", + "requested": "[11.3.0, )", + "resolved": "11.3.0", + "contentHash": "jtFVgKnDFySyBlPS8bZbTKEEwJZnn11rXXJ2SQnjDhZ56rQqybBg9Joq4crRLz3y0QR8WoOq4iE4piV81w/Djg==", + "dependencies": { + "FluentValidation": "11.5.1", + "FluentValidation.DependencyInjectionExtensions": "11.5.1" + } + }, + "MediatR": { + "type": "CentralTransitive", + "requested": "[12.1.1, )", + "resolved": "12.1.1", + "contentHash": "1AbwzzeS6gn4NdcO6A9LfKS5TXXgAiUQM3J18dREHa7O7TrdCXJ5dNFeRBpzPZY7UWl5Kby+n9pWrPJe3SDiMA==", + "dependencies": { + "MediatR.Contracts": "[2.0.1, 3.0.0)", + "Microsoft.Extensions.DependencyInjection.Abstractions": "6.0.0" + } + }, + "Microsoft.AspNetCore.Authentication.JwtBearer": { + "type": "CentralTransitive", + "requested": "[7.0.10, )", + "resolved": "7.0.10", + "contentHash": "8/GWtq034kzrDapCHYbAnnJXIYAJzFcJoXP5OtlbjIWBQBLxEbstRv3N4DJAvlcGUXxy0DmmNVDiSlFAY8HpWw==", + "dependencies": { + "Microsoft.IdentityModel.Protocols.OpenIdConnect": "6.15.1" + } + }, + "Microsoft.EntityFrameworkCore.InMemory": { + "type": "CentralTransitive", + "requested": "[7.0.10, )", + "resolved": "7.0.10", + "contentHash": "WeUjWx80ZVQVXkHuEIO4kOCKUHo6C4RdB1bJZLSngLWqwrQrMTCZE90IGBz0330GXmXKbL4LyJO/rXO/w7cRNA==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "7.0.10" + } + }, + "Microsoft.EntityFrameworkCore.Sqlite": { + "type": "CentralTransitive", + "requested": "[7.0.10, )", + "resolved": "7.0.10", + "contentHash": "gGOZWOvpRDC2bQAUNwpYzjYRTFbSJKNK60KOnvrstvvKKeM/4dVo7i0dxIvZaHpUuIRnlDybBrSK253/BBsfhg==", + "dependencies": { + "Microsoft.EntityFrameworkCore.Sqlite.Core": "7.0.10", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.4" + } + }, + "Microsoft.EntityFrameworkCore.SqlServer": { + "type": "CentralTransitive", + "requested": "[7.0.10, )", + "resolved": "7.0.10", + "contentHash": "qRMzze1QbKa2vhlhjTxwjhJUMCI4qVDFWQCRq9Pdgf9mC+xLz0ZT/4ALEa0oZRicGzPZAqIIhKrHNAjgD4GsAQ==", + "dependencies": { + "Microsoft.Data.SqlClient": "5.0.2", + "Microsoft.EntityFrameworkCore.Relational": "7.0.10" + } + }, + "Serilog": { + "type": "CentralTransitive", + "requested": "[3.0.1, )", + "resolved": "3.0.1", + "contentHash": "E4UmOQ++eNJax1laE+lws7E3zbhKgHsGJbO7ra0yE5smUh+5FfUPIKKBxM3MO1tK4sgpQke6/pLReDxIc/ggNw==" + }, + "Serilog.Extensions.Logging": { + "type": "CentralTransitive", + "requested": "[7.0.0, )", + "resolved": "7.0.0", + "contentHash": "9faU0zNQqU7I6soVhLUMYaGNpgWv6cKlKb2S5AnS8gXxzW/em5Ladm/6FMrWTnX41cdbdGPOWNAo6adi4WaJ6A==", + "dependencies": { + "Microsoft.Extensions.Logging": "7.0.0", + "Serilog": "2.12.0" + } + }, + "Serilog.Sinks.Console": { + "type": "CentralTransitive", + "requested": "[4.1.0, )", + "resolved": "4.1.0", + "contentHash": "K6N5q+5fetjnJPvCmkWOpJ/V8IEIoMIB1s86OzBrbxwTyHxdx3pmz4H+8+O/Dc/ftUX12DM1aynx/dDowkwzqg==", + "dependencies": { + "Serilog": "2.10.0" + } + }, + "Swashbuckle.AspNetCore": { + "type": "CentralTransitive", + "requested": "[6.5.0, )", + "resolved": "6.5.0", + "contentHash": "FK05XokgjgwlCI6wCT+D4/abtQkL1X1/B9Oas6uIwHFmYrIO9WUD5aLC9IzMs9GnHfUXOtXZ2S43gN1mhs5+aA==", + "dependencies": { + "Microsoft.Extensions.ApiDescription.Server": "6.0.5", + "Swashbuckle.AspNetCore.Swagger": "6.5.0", + "Swashbuckle.AspNetCore.SwaggerGen": "6.5.0", + "Swashbuckle.AspNetCore.SwaggerUI": "6.5.0" + } + } + } + } +} \ No newline at end of file