From fed2c02c631a4e48827f6979e407264f20ddd51e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 19 Sep 2026 20:21:37 +0000 Subject: [PATCH 1/2] refactor: introduce typed repositories Co-authored-by: linkdotnet <26365461+linkdotnet@users.noreply.github.com> --- .../Persistence/IPersistenceHealthCheck.cs | 9 +++ .../MongoDB/MongoDbPersistenceHealthCheck.cs | 25 +++++++ .../RavenDb/RavenDbPersistenceHealthCheck.cs | 23 +++++++ .../Sql/SqlPersistenceHealthCheck.cs | 23 +++++++ .../Controller/RssFeedController.cs | 6 +- .../DatabaseHealthCheck.cs | 5 +- .../Features/AboutMe/Components/Profile.razor | 12 ++-- .../AboutMe/Components/Skill/SkillTable.razor | 12 ++-- .../AboutMe/Components/Talk/Talks.razor | 12 ++-- .../Components/AddTemplateDialog.razor | 6 +- .../Components/CreateNewBlogPost.razor | 8 +-- .../Admin/BlogPostEditor/CreateBlogPost.razor | 4 +- .../Services/BlogPostVersionService.cs | 8 +-- .../BlogPostEditor/UpdateBlogPostPage.razor | 6 +- .../Admin/BrokenLinks/BrokenLinksPage.razor | 8 +-- .../Services/BrokenLinkCheckerJob.cs | 18 ++--- .../Components/VisitCountPerPage.razor | 14 ++-- .../DraftBlogPost/DraftBlogPostPage.razor | 4 +- .../Admin/ShortCodes/ShortCodesPage.razor | 6 +- .../Admin/Sitemap/Services/SitemapService.cs | 6 +- .../Features/Archive/ArchivePage.razor | 6 +- .../Features/BlogPostPublisher.cs | 7 +- .../Features/Bookmarks/Bookmarks.razor | 6 +- .../Features/DummyData/DummyDataExtensions.cs | 28 ++++++++ .../Features/DummyData/DummyDataSeeder.cs | 6 +- .../Features/Home/Index.razor | 6 +- .../Repositories/AboutMeRepository.cs | 34 ++++++++++ .../Repositories/AnalyticsRepository.cs | 50 ++++++++++++++ .../Repositories/BlogPostRepository.cs | 37 +++++++++++ .../BlogPostTemplateRepository.cs | 15 +++++ .../Repositories/BrokenLinkRepository.cs | 19 ++++++ .../Repositories/CachedBlogPostRepository.cs | 53 +++++++++++++++ .../Repositories/IAboutMeRepository.cs | 27 ++++++++ .../Repositories/IAnalyticsRepository.cs | 40 ++++++++++++ .../Repositories/IBlogPostRepository.cs | 35 ++++++++++ .../IBlogPostTemplateRepository.cs | 15 +++++ .../Repositories/IBrokenLinkRepository.cs | 14 ++++ .../Repositories/IShortCodeRepository.cs | 15 +++++ .../ISimilarBlogPostRepository.cs | 14 ++++ .../Repositories/ShortCodeRepository.cs | 15 +++++ .../Repositories/SimilarBlogPostRepository.cs | 19 ++++++ .../Features/Search/SearchPage.razor | 4 +- .../SearchByTag/SearchByTagPage.razor | 4 +- .../Features/Services/Tags/TagQueryService.cs | 6 +- .../Features/Services/UserRecordService.cs | 10 +-- .../Components/BlogPostAdminActions.razor | 4 +- .../Components/SimilarBlogPostSection.razor | 8 +-- .../ShowBlogPost/ShowBlogPostPage.razor | 6 +- .../Features/SimilarBlogPostJob.cs | 17 +++-- .../Features/TransformBlogPostRecordsJob.cs | 26 ++++---- .../MongoDBRegistrationExtensions.cs | 1 + .../RavenDbRegistrationExtensions.cs | 1 + .../SqlRegistrationExtensions.cs | 4 ++ .../StorageProviderExtensions.cs | 31 ++++----- .../Web/Controller/RssFeedControllerTests.cs | 9 +-- .../Features/AboutMe/Components/TalksTests.cs | 4 +- .../BlogPostVersionServiceTests.cs | 3 +- .../CreateNewBlogPostPageTests.cs | 12 +++- .../BlogPostEditor/UpdateBlogPostPageTests.cs | 16 +++-- .../BrokenLinks/BrokenLinkCheckerJobTests.cs | 5 +- .../DraftBlogPost/DraftBlogPostPageTests.cs | 2 + .../Admin/ShortCodes/ShortCodesPageTests.cs | 5 +- .../Web/Features/Archive/ArchivePageTests.cs | 7 +- .../Web/Features/BlogPostPublisherTests.cs | 3 +- .../Web/Features/Bookmarks/BookmarksTests.cs | 6 +- .../Web/Features/Home/IndexTests.cs | 4 +- .../Web/Features/Search/SearchPageTests.cs | 2 + .../SearchByTag/SearchByTagPageTests.cs | 3 + .../Components/SimilarBlogPostSectionTests.cs | 4 +- .../ShowBlogPost/ShowBlogPostPageTests.cs | 6 +- .../Web/Features/SimilarBlogPostJobTests.cs | 7 +- .../TransformBlogPostRecordsJobTests.cs | 6 +- .../Shared/Admin/BlogPostAdminActionsTests.cs | 5 +- .../Web/Shared/ProfileTests.cs | 2 + .../Shared/Services/SitemapServiceTests.cs | 3 +- .../Web/Shared/Skills/SkillTableTests.cs | 6 ++ .../RepositoryRegistrationExtensions.cs | 65 +++++++++++++++++++ .../RepositoryTestExtensions.cs | 34 ++++++++++ .../Components/CreateNewBlogPostTests.cs | 5 +- .../Admin/BrokenLinks/BrokenLinksPageTests.cs | 5 +- .../SearchByTag/SearchByTagPageTests.cs | 4 +- .../Services/Tags/TagQueryServiceTests.cs | 6 +- .../Services/UserRecordServiceTests.cs | 12 ++-- .../ShowBlogPost/ShowBlogPostPageTests.cs | 14 ++++ 84 files changed, 876 insertions(+), 187 deletions(-) create mode 100644 src/LinkDotNet.Blog.Infrastructure/Persistence/IPersistenceHealthCheck.cs create mode 100644 src/LinkDotNet.Blog.Infrastructure/Persistence/MongoDB/MongoDbPersistenceHealthCheck.cs create mode 100644 src/LinkDotNet.Blog.Infrastructure/Persistence/RavenDb/RavenDbPersistenceHealthCheck.cs create mode 100644 src/LinkDotNet.Blog.Infrastructure/Persistence/Sql/SqlPersistenceHealthCheck.cs create mode 100644 src/LinkDotNet.Blog.Web/Features/Repositories/AboutMeRepository.cs create mode 100644 src/LinkDotNet.Blog.Web/Features/Repositories/AnalyticsRepository.cs create mode 100644 src/LinkDotNet.Blog.Web/Features/Repositories/BlogPostRepository.cs create mode 100644 src/LinkDotNet.Blog.Web/Features/Repositories/BlogPostTemplateRepository.cs create mode 100644 src/LinkDotNet.Blog.Web/Features/Repositories/BrokenLinkRepository.cs create mode 100644 src/LinkDotNet.Blog.Web/Features/Repositories/CachedBlogPostRepository.cs create mode 100644 src/LinkDotNet.Blog.Web/Features/Repositories/IAboutMeRepository.cs create mode 100644 src/LinkDotNet.Blog.Web/Features/Repositories/IAnalyticsRepository.cs create mode 100644 src/LinkDotNet.Blog.Web/Features/Repositories/IBlogPostRepository.cs create mode 100644 src/LinkDotNet.Blog.Web/Features/Repositories/IBlogPostTemplateRepository.cs create mode 100644 src/LinkDotNet.Blog.Web/Features/Repositories/IBrokenLinkRepository.cs create mode 100644 src/LinkDotNet.Blog.Web/Features/Repositories/IShortCodeRepository.cs create mode 100644 src/LinkDotNet.Blog.Web/Features/Repositories/ISimilarBlogPostRepository.cs create mode 100644 src/LinkDotNet.Blog.Web/Features/Repositories/ShortCodeRepository.cs create mode 100644 src/LinkDotNet.Blog.Web/Features/Repositories/SimilarBlogPostRepository.cs create mode 100644 tests/LinkDotNet.Blog.TestUtilities/RepositoryRegistrationExtensions.cs diff --git a/src/LinkDotNet.Blog.Infrastructure/Persistence/IPersistenceHealthCheck.cs b/src/LinkDotNet.Blog.Infrastructure/Persistence/IPersistenceHealthCheck.cs new file mode 100644 index 000000000..f0c8064c6 --- /dev/null +++ b/src/LinkDotNet.Blog.Infrastructure/Persistence/IPersistenceHealthCheck.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace LinkDotNet.Blog.Infrastructure.Persistence; + +public interface IPersistenceHealthCheck +{ + ValueTask PerformHealthCheckAsync(); +} diff --git a/src/LinkDotNet.Blog.Infrastructure/Persistence/MongoDB/MongoDbPersistenceHealthCheck.cs b/src/LinkDotNet.Blog.Infrastructure/Persistence/MongoDB/MongoDbPersistenceHealthCheck.cs new file mode 100644 index 000000000..ad2d2ae2d --- /dev/null +++ b/src/LinkDotNet.Blog.Infrastructure/Persistence/MongoDB/MongoDbPersistenceHealthCheck.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using MongoDB.Bson; +using MongoDB.Driver; + +namespace LinkDotNet.Blog.Infrastructure.Persistence.MongoDB; + +public sealed class MongoDbPersistenceHealthCheck(IMongoDatabase database) : IPersistenceHealthCheck +{ + public async ValueTask PerformHealthCheckAsync() + { + try + { + var command = new BsonDocument("ping", 1); + await database.RunCommandAsync(command); + + return HealthCheckResult.Healthy("A healthy result."); + } + catch (Exception ex) + { + return HealthCheckResult.Unhealthy(exception: ex); + } + } +} diff --git a/src/LinkDotNet.Blog.Infrastructure/Persistence/RavenDb/RavenDbPersistenceHealthCheck.cs b/src/LinkDotNet.Blog.Infrastructure/Persistence/RavenDb/RavenDbPersistenceHealthCheck.cs new file mode 100644 index 000000000..59ed88efd --- /dev/null +++ b/src/LinkDotNet.Blog.Infrastructure/Persistence/RavenDb/RavenDbPersistenceHealthCheck.cs @@ -0,0 +1,23 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Raven.Client.Documents; + +namespace LinkDotNet.Blog.Infrastructure.Persistence.RavenDb; + +public sealed class RavenDbPersistenceHealthCheck(IDocumentStore documentStore) : IPersistenceHealthCheck +{ + public async ValueTask PerformHealthCheckAsync() + { + try + { + using var session = documentStore.OpenAsyncSession(); + await session.Query().FirstOrDefaultAsync(); + return HealthCheckResult.Healthy(); + } + catch (Exception e) + { + return HealthCheckResult.Unhealthy(exception: e); + } + } +} diff --git a/src/LinkDotNet.Blog.Infrastructure/Persistence/Sql/SqlPersistenceHealthCheck.cs b/src/LinkDotNet.Blog.Infrastructure/Persistence/Sql/SqlPersistenceHealthCheck.cs new file mode 100644 index 000000000..b8725bb88 --- /dev/null +++ b/src/LinkDotNet.Blog.Infrastructure/Persistence/Sql/SqlPersistenceHealthCheck.cs @@ -0,0 +1,23 @@ +using System; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace LinkDotNet.Blog.Infrastructure.Persistence.Sql; + +public sealed class SqlPersistenceHealthCheck(IDbContextFactory dbContextFactory) : IPersistenceHealthCheck +{ + public async ValueTask PerformHealthCheckAsync() + { + try + { + await using var db = await dbContextFactory.CreateDbContextAsync(); + await db.Database.ExecuteSqlRawAsync("SELECT 1"); + return HealthCheckResult.Healthy(); + } + catch (Exception exc) + { + return HealthCheckResult.Unhealthy(exception: exc); + } + } +} diff --git a/src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs b/src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs index a4f9e00e3..a4f0a5886 100644 --- a/src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs +++ b/src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs @@ -9,8 +9,8 @@ using System.Xml; using System.Xml.Linq; using LinkDotNet.Blog.Domain; -using LinkDotNet.Blog.Infrastructure.Persistence; using LinkDotNet.Blog.Web.Features; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.RateLimiting; using Microsoft.Extensions.Options; @@ -26,12 +26,12 @@ public sealed class RssFeedController : ControllerBase private readonly string description; private readonly string blogName; private readonly int blogPostsPerPage; - private readonly IRepository blogPostRepository; + private readonly IBlogPostRepository blogPostRepository; public RssFeedController( IOptions introductionConfiguration, IOptions applicationConfiguration, - IRepository blogPostRepository) + IBlogPostRepository blogPostRepository) { ArgumentNullException.ThrowIfNull(introductionConfiguration); ArgumentNullException.ThrowIfNull(applicationConfiguration); diff --git a/src/LinkDotNet.Blog.Web/DatabaseHealthCheck.cs b/src/LinkDotNet.Blog.Web/DatabaseHealthCheck.cs index dae348389..068356a28 100644 --- a/src/LinkDotNet.Blog.Web/DatabaseHealthCheck.cs +++ b/src/LinkDotNet.Blog.Web/DatabaseHealthCheck.cs @@ -1,6 +1,5 @@ using System.Threading; using System.Threading.Tasks; -using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.Infrastructure.Persistence; using Microsoft.Extensions.Diagnostics.HealthChecks; @@ -8,9 +7,9 @@ namespace LinkDotNet.Blog.Web; public class DatabaseHealthCheck : IHealthCheck { - private readonly IRepository repository; + private readonly IPersistenceHealthCheck repository; - public DatabaseHealthCheck(IRepository repository) + public DatabaseHealthCheck(IPersistenceHealthCheck repository) { this.repository = repository; } diff --git a/src/LinkDotNet.Blog.Web/Features/AboutMe/Components/Profile.razor b/src/LinkDotNet.Blog.Web/Features/AboutMe/Components/Profile.razor index eac7c1859..b8597a2c7 100644 --- a/src/LinkDotNet.Blog.Web/Features/AboutMe/Components/Profile.razor +++ b/src/LinkDotNet.Blog.Web/Features/AboutMe/Components/Profile.razor @@ -1,8 +1,8 @@ @using LinkDotNet.Blog.Domain -@using LinkDotNet.Blog.Infrastructure.Persistence +@using LinkDotNet.Blog.Web.Features.Repositories @using LinkDotNet.Blog.Web.Features.Services -@inject IRepository Repository +@inject IAboutMeRepository Repository @inject ISortOrderCalculator SortOrderCalculator
@@ -60,7 +60,7 @@ protected override async Task OnInitializedAsync() { - profileInformationEntries = (await Repository.GetAllAsync(orderBy: d => d.SortOrder, descending: false)).ToList(); + profileInformationEntries = (await Repository.GetProfileInformationEntriesAsync()).ToList(); } private void ShowDeleteDialog(string key) @@ -73,7 +73,7 @@ { var entryToDelete = profileInformationEntries.Single(p => p.Content == currentDeleteKey); profileInformationEntries.Remove(entryToDelete); - await Repository.DeleteAsync(entryToDelete.Id); + await Repository.DeleteProfileInformationEntryAsync(entryToDelete.Id); } private async Task AddValue(string toAdd) @@ -82,7 +82,7 @@ var newEntry = ProfileInformationEntry.Create(toAdd, sortOrder); profileInformationEntries.Add(newEntry); - await Repository.StoreAsync(newEntry); + await Repository.StoreProfileInformationEntryAsync(newEntry); } private int GetSortOrder() @@ -104,7 +104,7 @@ var newSortOrder = SortOrderCalculator.GetSortOrder(dropTarget, profileInformationEntries); currentDragItem.SortOrder = newSortOrder; - await Repository.StoreAsync(currentDragItem); + await Repository.StoreProfileInformationEntryAsync(currentDragItem); currentDragItem = null; profileInformationEntries.Sort((a, b) => a.SortOrder.CompareTo(b.SortOrder)); StateHasChanged(); diff --git a/src/LinkDotNet.Blog.Web/Features/AboutMe/Components/Skill/SkillTable.razor b/src/LinkDotNet.Blog.Web/Features/AboutMe/Components/Skill/SkillTable.razor index 047a0254e..52156a1e8 100644 --- a/src/LinkDotNet.Blog.Web/Features/AboutMe/Components/Skill/SkillTable.razor +++ b/src/LinkDotNet.Blog.Web/Features/AboutMe/Components/Skill/SkillTable.razor @@ -1,6 +1,6 @@ @using LinkDotNet.Blog.Domain -@using LinkDotNet.Blog.Infrastructure.Persistence -@inject IRepository Repository +@using LinkDotNet.Blog.Web.Features.Repositories +@inject IAboutMeRepository Repository
@if (ShowAdminActions) { @@ -66,19 +66,19 @@ protected override async Task OnInitializedAsync() { - skills = (await Repository.GetAllAsync()).ToList(); + skills = (await Repository.GetSkillsAsync()).ToList(); } private async Task AddSkill(Skill skillToAdd) { skills.Add(skillToAdd); - await Repository.StoreAsync(skillToAdd); + await Repository.StoreSkillAsync(skillToAdd); } private async Task DeleteSkill(Skill skill) { skills.Remove(skill); - await Repository.DeleteAsync(skill.Id); + await Repository.DeleteSkillAsync(skill.Id); } private async Task HandleDrop(ProficiencyLevel proficiencyLevel) @@ -89,7 +89,7 @@ } currentDragItem.SetProficiencyLevel(proficiencyLevel); - await Repository.StoreAsync(currentDragItem); + await Repository.StoreSkillAsync(currentDragItem); currentDragItem = null; } diff --git a/src/LinkDotNet.Blog.Web/Features/AboutMe/Components/Talk/Talks.razor b/src/LinkDotNet.Blog.Web/Features/AboutMe/Components/Talk/Talks.razor index 4090f9daf..41f8fbb04 100644 --- a/src/LinkDotNet.Blog.Web/Features/AboutMe/Components/Talk/Talks.razor +++ b/src/LinkDotNet.Blog.Web/Features/AboutMe/Components/Talk/Talks.razor @@ -1,6 +1,6 @@ -@using LinkDotNet.Blog.Domain -@using LinkDotNet.Blog.Infrastructure.Persistence -@inject IRepository Repository +@using LinkDotNet.Blog.Domain +@using LinkDotNet.Blog.Web.Features.Repositories +@inject IAboutMeRepository Repository @if (talks.Any() || ShowAdminActions) { @@ -27,18 +27,18 @@ protected override async Task OnInitializedAsync() { - talks = (await Repository.GetAllAsync(orderBy: t => t.PublishedDate, descending: true)).ToList(); + talks = (await Repository.GetTalksAsync()).ToList(); } private async Task DeleteTalk(Talk talk) { talks.Remove(talk); - await Repository.DeleteAsync(talk.Id); + await Repository.DeleteTalkAsync(talk.Id); } private async Task StoreTalk(Talk talk) { talks.Add(talk); - await Repository.StoreAsync(talk); + await Repository.StoreTalkAsync(talk); } } diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/Components/AddTemplateDialog.razor b/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/Components/AddTemplateDialog.razor index 7521a397a..fb834137a 100644 --- a/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/Components/AddTemplateDialog.razor +++ b/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/Components/AddTemplateDialog.razor @@ -1,8 +1,8 @@ -@using LinkDotNet.Blog.Domain +@using LinkDotNet.Blog.Domain @using LinkDotNet.Blog.Infrastructure -@using LinkDotNet.Blog.Infrastructure.Persistence +@using LinkDotNet.Blog.Web.Features.Repositories @inject IToastService ToastService -@inject IRepository TemplateRepository +@inject IBlogPostTemplateRepository TemplateRepository @inject IJSRuntime JSRuntime diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/Components/CreateNewBlogPost.razor b/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/Components/CreateNewBlogPost.razor index 0914d07a0..43810db54 100644 --- a/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/Components/CreateNewBlogPost.razor +++ b/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/Components/CreateNewBlogPost.razor @@ -1,14 +1,14 @@ -@using LinkDotNet.Blog.Domain +@using LinkDotNet.Blog.Domain @using LinkDotNet.Blog.Infrastructure -@using LinkDotNet.Blog.Infrastructure.Persistence +@using LinkDotNet.Blog.Web.Features.Repositories @using LinkDotNet.Blog.Web.Features.Admin.BlogPostEditor.Services @using LinkDotNet.Blog.Web.Features.Services @using NCronJob @inject IJSRuntime JSRuntime @inject ICacheInvalidator CacheInvalidator @inject IInstantJobRegistry InstantJobRegistry -@inject IRepository ShortCodeRepository -@inject IRepository TemplateRepository +@inject IShortCodeRepository ShortCodeRepository +@inject IBlogPostTemplateRepository TemplateRepository @inject IToastService ToastService @inject IOptions AppConfiguration @inject ICurrentUserService CurrentUserService diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/CreateBlogPost.razor b/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/CreateBlogPost.razor index 26e190b47..9fbd265cc 100644 --- a/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/CreateBlogPost.razor +++ b/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/CreateBlogPost.razor @@ -1,9 +1,9 @@ @page "/create" @attribute [Authorize] @using LinkDotNet.Blog.Domain -@using LinkDotNet.Blog.Infrastructure.Persistence @using LinkDotNet.Blog.Web.Features.Admin.BlogPostEditor.Components -@inject IRepository BlogPostRepository +@using LinkDotNet.Blog.Web.Features.Repositories +@inject IBlogPostRepository BlogPostRepository @inject IToastService ToastService diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/Services/BlogPostVersionService.cs b/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/Services/BlogPostVersionService.cs index 57728abbe..953c7ff0e 100644 --- a/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/Services/BlogPostVersionService.cs +++ b/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/Services/BlogPostVersionService.cs @@ -1,9 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using LinkDotNet.Blog.Domain; -using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Infrastructure.Persistence.Sql; using Microsoft.EntityFrameworkCore; @@ -12,11 +12,11 @@ namespace LinkDotNet.Blog.Web.Features.Admin.BlogPostEditor.Services; public sealed class BlogPostVersionService : IBlogPostVersionService { private readonly IDbContextFactory dbContextFactory; - private readonly IRepository blogPostRepository; + private readonly IBlogPostRepository blogPostRepository; public BlogPostVersionService( IDbContextFactory dbContextFactory, - IRepository blogPostRepository) + IBlogPostRepository blogPostRepository) { this.dbContextFactory = dbContextFactory; this.blogPostRepository = blogPostRepository; diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/UpdateBlogPostPage.razor b/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/UpdateBlogPostPage.razor index 3ca9fdd50..31a941cb4 100644 --- a/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/UpdateBlogPostPage.razor +++ b/src/LinkDotNet.Blog.Web/Features/Admin/BlogPostEditor/UpdateBlogPostPage.razor @@ -1,10 +1,10 @@ -@page "/update/{blogPostId}" +@page "/update/{blogPostId}" @attribute [Authorize] @using LinkDotNet.Blog.Domain -@using LinkDotNet.Blog.Infrastructure.Persistence @using LinkDotNet.Blog.Web.Features.Admin.BlogPostEditor.Components @using LinkDotNet.Blog.Web.Features.Admin.BlogPostEditor.Services -@inject IRepository BlogPostRepository +@using LinkDotNet.Blog.Web.Features.Repositories +@inject IBlogPostRepository BlogPostRepository @inject IBlogPostVersionService BlogPostVersionService @inject IToastService ToastService diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/BrokenLinks/BrokenLinksPage.razor b/src/LinkDotNet.Blog.Web/Features/Admin/BrokenLinks/BrokenLinksPage.razor index a2b8a11cf..928ec38aa 100644 --- a/src/LinkDotNet.Blog.Web/Features/Admin/BrokenLinks/BrokenLinksPage.razor +++ b/src/LinkDotNet.Blog.Web/Features/Admin/BrokenLinks/BrokenLinksPage.razor @@ -1,7 +1,7 @@ -@page "/broken-links" +@page "/broken-links" @using LinkDotNet.Blog.Domain -@using LinkDotNet.Blog.Infrastructure.Persistence -@inject IRepository BrokenLinkRepository +@using LinkDotNet.Blog.Web.Features.Repositories +@inject IBrokenLinkRepository BrokenLinkRepository @inject IOptions ApplicationConfiguration @attribute [Authorize] @@ -57,7 +57,7 @@ { if (ApplicationConfiguration.Value.EnableBrokenLinkChecker) { - brokenLinks = (await BrokenLinkRepository.GetAllAsync(orderBy: b => b.BlogPostTitle, descending: false)).ToArray(); + brokenLinks = (await BrokenLinkRepository.GetAllAsync()).ToArray(); } } } diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/BrokenLinks/Services/BrokenLinkCheckerJob.cs b/src/LinkDotNet.Blog.Web/Features/Admin/BrokenLinks/Services/BrokenLinkCheckerJob.cs index cd9681f99..b13d73db4 100644 --- a/src/LinkDotNet.Blog.Web/Features/Admin/BrokenLinks/Services/BrokenLinkCheckerJob.cs +++ b/src/LinkDotNet.Blog.Web/Features/Admin/BrokenLinks/Services/BrokenLinkCheckerJob.cs @@ -1,10 +1,11 @@ -using System; +using System; using System.Collections.Concurrent; using System.Linq; using System.Threading; using System.Threading.Tasks; using LinkDotNet.Blog.Domain; -using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.Web.Features.Repositories; +using LinkDotNet.Blog.Infrastructure; using NCronJob; namespace LinkDotNet.Blog.Web.Features.Admin.BrokenLinks.Services; @@ -13,14 +14,14 @@ public sealed class BrokenLinkCheckerJob : IJob { private const int MaxParallelRequests = 5; - private readonly IRepository blogPostRepository; - private readonly IRepository brokenLinkRepository; + private readonly IBlogPostRepository blogPostRepository; + private readonly IBrokenLinkRepository brokenLinkRepository; private readonly ILinkChecker linkChecker; private readonly TimeProvider timeProvider; public BrokenLinkCheckerJob( - IRepository blogPostRepository, - IRepository brokenLinkRepository, + IBlogPostRepository blogPostRepository, + IBrokenLinkRepository brokenLinkRepository, ILinkChecker linkChecker, TimeProvider timeProvider) { @@ -58,9 +59,8 @@ await Parallel.ForEachAsync(blogPostsByUrl, parallelOptions, async (postsWithUrl } }); - var previousIds = await brokenLinkRepository.GetAllByProjectionAsync(b => b.Id); - await brokenLinkRepository.DeleteBulkAsync(previousIds); - await brokenLinkRepository.StoreBulkAsync(brokenLinks.ToArray()); + var previousIds = await brokenLinkRepository.GetAllAsync(); + await brokenLinkRepository.ReplaceAllAsync(previousIds.Select(link => link.Id).ToArray(), brokenLinks.ToArray()); } private sealed record BlogPostLinks(string Id, string Title, string Content); diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/Dashboard/Components/VisitCountPerPage.razor b/src/LinkDotNet.Blog.Web/Features/Admin/Dashboard/Components/VisitCountPerPage.razor index 0b3ee9979..d2953d205 100644 --- a/src/LinkDotNet.Blog.Web/Features/Admin/Dashboard/Components/VisitCountPerPage.razor +++ b/src/LinkDotNet.Blog.Web/Features/Admin/Dashboard/Components/VisitCountPerPage.razor @@ -1,12 +1,12 @@ @using System.Linq.Expressions @using LinkDotNet.Blog.Domain @using LinkDotNet.Blog.Infrastructure -@using LinkDotNet.Blog.Infrastructure.Persistence +@using LinkDotNet.Blog.Web.Features.Repositories @using System.Collections.Immutable @using System.Diagnostics @using Microsoft.Extensions.Logging -@inject IRepository BlogPostRepository -@inject IRepository BlogPostRecordRepository +@inject IBlogPostRepository BlogPostRepository +@inject IAnalyticsRepository AnalyticsRepository @inject ILogger Logger;
@@ -60,7 +60,7 @@ } private static async ValueTask> GetRelevantBlogPostsAsync( - IRepository blogPostRepository, + IBlogPostRepository blogPostRepository, Filter filter) { Expression>? blogPostFilter = filter.EndDate.HasValue @@ -74,10 +74,10 @@ } private static async Task> GetRelevantBlogPostRecords( - IRepository blogPostRecordRepository, + IAnalyticsRepository analyticsRepository, Filter filter) { - return await blogPostRecordRepository.GetGroupedByAsync( + return await analyticsRepository.GetGroupedBlogPostRecordsAsync( r => r.BlogPostId, g => new BlogPostClickAggregate { BlogPostId = g.Key, ClickCount = g.Sum(x => x.Clicks) }, filter: r => (!filter.StartDate.HasValue || r.DateClicked >= filter.StartDate) @@ -89,7 +89,7 @@ var stopwatch = Stopwatch.StartNew(); var blogPosts = await GetRelevantBlogPostsAsync(BlogPostRepository, filter); - var clickAggregates = await GetRelevantBlogPostRecords(BlogPostRecordRepository, filter); + var clickAggregates = await GetRelevantBlogPostRecords(AnalyticsRepository, filter); var time = stopwatch.ElapsedMilliseconds; Logger.LogDebug("Retrieving data took {ElapsedMilliseconds}ms", time); diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/DraftBlogPost/DraftBlogPostPage.razor b/src/LinkDotNet.Blog.Web/Features/Admin/DraftBlogPost/DraftBlogPostPage.razor index 1829d3f55..138a7d4c2 100644 --- a/src/LinkDotNet.Blog.Web/Features/Admin/DraftBlogPost/DraftBlogPostPage.razor +++ b/src/LinkDotNet.Blog.Web/Features/Admin/DraftBlogPost/DraftBlogPostPage.razor @@ -1,9 +1,9 @@ @page "/draft" @using LinkDotNet.Blog.Domain @using LinkDotNet.Blog.Infrastructure -@using LinkDotNet.Blog.Infrastructure.Persistence +@using LinkDotNet.Blog.Web.Features.Repositories @attribute [Authorize] -@inject IRepository BlogPostRepository +@inject IBlogPostRepository BlogPostRepository Drafts diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/ShortCodes/ShortCodesPage.razor b/src/LinkDotNet.Blog.Web/Features/Admin/ShortCodes/ShortCodesPage.razor index f088f773c..f19385f6b 100644 --- a/src/LinkDotNet.Blog.Web/Features/Admin/ShortCodes/ShortCodesPage.razor +++ b/src/LinkDotNet.Blog.Web/Features/Admin/ShortCodes/ShortCodesPage.razor @@ -1,9 +1,9 @@ -@page "/short-codes" +@page "/short-codes" @using System.ComponentModel.DataAnnotations @using LinkDotNet.Blog.Domain -@using LinkDotNet.Blog.Infrastructure.Persistence +@using LinkDotNet.Blog.Web.Features.Repositories -@inject IRepository ShortCodeRepository +@inject IShortCodeRepository ShortCodeRepository @inject IToastService ToastService Shortcodes diff --git a/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/SitemapService.cs b/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/SitemapService.cs index f4771057b..e0355599b 100644 --- a/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/SitemapService.cs +++ b/src/LinkDotNet.Blog.Web/Features/Admin/Sitemap/Services/SitemapService.cs @@ -5,15 +5,15 @@ using System.Linq; using System.Threading.Tasks; using LinkDotNet.Blog.Domain; -using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.Web.Features.Repositories; namespace LinkDotNet.Blog.Web.Features.Admin.Sitemap.Services; public sealed class SitemapService : ISitemapService { - private readonly IRepository repository; + private readonly IBlogPostRepository repository; - public SitemapService(IRepository repository) + public SitemapService(IBlogPostRepository repository) { this.repository = repository; } diff --git a/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor b/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor index 07f069b6b..bd58f1704 100644 --- a/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor +++ b/src/LinkDotNet.Blog.Web/Features/Archive/ArchivePage.razor @@ -1,9 +1,9 @@ -@page "/archive" -@using LinkDotNet.Blog.Infrastructure.Persistence +@page "/archive" +@using LinkDotNet.Blog.Web.Features.Repositories @using LinkDotNet.Blog.Domain @using System.Collections.Immutable @using System.Globalization -@inject IRepository Repository +@inject IBlogPostRepository Repository @inject IOptions AppConfiguration Archive @AppConfiguration.Value.BlogName diff --git a/src/LinkDotNet.Blog.Web/Features/BlogPostPublisher.cs b/src/LinkDotNet.Blog.Web/Features/BlogPostPublisher.cs index 664bf0c11..27bdea7c5 100644 --- a/src/LinkDotNet.Blog.Web/Features/BlogPostPublisher.cs +++ b/src/LinkDotNet.Blog.Web/Features/BlogPostPublisher.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.Infrastructure; using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.Services; using NCronJob; using Microsoft.Extensions.Logging; @@ -13,12 +14,12 @@ namespace LinkDotNet.Blog.Web.Features; public sealed partial class BlogPostPublisher : IJob { private readonly ILogger logger; - private readonly IRepository repository; + private readonly IBlogPostRepository repository; private readonly ICacheInvalidator cacheInvalidator; private readonly TimeProvider timeProvider; public BlogPostPublisher( - IRepository repository, + IBlogPostRepository repository, ICacheInvalidator cacheInvalidator, TimeProvider timeProvider, ILogger logger) diff --git a/src/LinkDotNet.Blog.Web/Features/Bookmarks/Bookmarks.razor b/src/LinkDotNet.Blog.Web/Features/Bookmarks/Bookmarks.razor index 8ffcef82b..d5c217ce0 100644 --- a/src/LinkDotNet.Blog.Web/Features/Bookmarks/Bookmarks.razor +++ b/src/LinkDotNet.Blog.Web/Features/Bookmarks/Bookmarks.razor @@ -1,8 +1,8 @@ -@page "/bookmarks" +@page "/bookmarks" @using LinkDotNet.Blog.Domain -@using LinkDotNet.Blog.Infrastructure.Persistence +@using LinkDotNet.Blog.Web.Features.Repositories @inject IBookmarkService BookmarkService -@inject IRepository BlogPostRepository; +@inject IBlogPostRepository BlogPostRepository;

Bookmarks

diff --git a/src/LinkDotNet.Blog.Web/Features/DummyData/DummyDataExtensions.cs b/src/LinkDotNet.Blog.Web/Features/DummyData/DummyDataExtensions.cs index cc86c2444..87b65cb6f 100644 --- a/src/LinkDotNet.Blog.Web/Features/DummyData/DummyDataExtensions.cs +++ b/src/LinkDotNet.Blog.Web/Features/DummyData/DummyDataExtensions.cs @@ -1,5 +1,6 @@ using LinkDotNet.Blog.Infrastructure.Persistence; using LinkDotNet.Blog.Infrastructure.Persistence.Sql; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using System; @@ -23,6 +24,14 @@ public static void UseDummyData(this IServiceCollection services, DummyDataOptio services.Remove(descriptor); } + RemoveIfRegistered(services); + RemoveIfRegistered(services); + RemoveIfRegistered(services); + RemoveIfRegistered(services); + RemoveIfRegistered(services); + RemoveIfRegistered(services); + RemoveIfRegistered(services); + var dummyDataOptions = options ?? new DummyDataOptions(); services.AddPooledDbContextFactory(builder => @@ -35,8 +44,27 @@ public static void UseDummyData(this IServiceCollection services, DummyDataOptio }); services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); + services.AddScoped(); + services.AddScoped(provider => new CachedBlogPostRepository( + provider.GetRequiredService(), + provider.GetRequiredService())); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.AddSingleton(dummyDataOptions); services.AddHostedService(); } + + private static void RemoveIfRegistered(IServiceCollection services) + { + var descriptors = services.Where(d => d.ServiceType == typeof(TService)).ToList(); + foreach (var descriptor in descriptors) + { + services.Remove(descriptor); + } + } } diff --git a/src/LinkDotNet.Blog.Web/Features/DummyData/DummyDataSeeder.cs b/src/LinkDotNet.Blog.Web/Features/DummyData/DummyDataSeeder.cs index 21cd6ebbf..9096b1fdd 100644 --- a/src/LinkDotNet.Blog.Web/Features/DummyData/DummyDataSeeder.cs +++ b/src/LinkDotNet.Blog.Web/Features/DummyData/DummyDataSeeder.cs @@ -1,5 +1,5 @@ -using LinkDotNet.Blog.Domain; -using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System; @@ -24,7 +24,7 @@ public DummyDataSeeder(IServiceProvider serviceProvider, DummyDataOptions option public async Task StartAsync(CancellationToken cancellationToken) { using var scope = serviceProvider.CreateScope(); - var repository = scope.ServiceProvider.GetRequiredService>(); + var repository = scope.ServiceProvider.GetRequiredService(); var blogPosts = GenerateDummyBlogPosts(options.NumberOfBlogPosts); await repository.StoreBulkAsync(blogPosts); diff --git a/src/LinkDotNet.Blog.Web/Features/Home/Index.razor b/src/LinkDotNet.Blog.Web/Features/Home/Index.razor index 959900a08..5832513e9 100644 --- a/src/LinkDotNet.Blog.Web/Features/Home/Index.razor +++ b/src/LinkDotNet.Blog.Web/Features/Home/Index.razor @@ -1,13 +1,13 @@ -@page "/" +@page "/" @page "/{page:int}" @using Markdig @using LinkDotNet.Blog.Domain @using LinkDotNet.Blog.Infrastructure -@using LinkDotNet.Blog.Infrastructure.Persistence +@using LinkDotNet.Blog.Web.Features.Repositories @using LinkDotNet.Blog.Web.Features.Home.Components @using ZiggyCreatures.Caching.Fusion @inject IFusionCache FusionCache -@inject IRepository BlogPostRepository +@inject IBlogPostRepository BlogPostRepository @inject IOptions Introduction @inject IOptions AppConfiguration @inject NavigationManager NavigationManager diff --git a/src/LinkDotNet.Blog.Web/Features/Repositories/AboutMeRepository.cs b/src/LinkDotNet.Blog.Web/Features/Repositories/AboutMeRepository.cs new file mode 100644 index 000000000..e8a374adf --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Features/Repositories/AboutMeRepository.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure; +using LinkDotNet.Blog.Infrastructure.Persistence; + +namespace LinkDotNet.Blog.Web.Features.Repositories; + +public sealed class AboutMeRepository( + IRepository profileInformationEntryRepository, + IRepository skillRepository, + IRepository talkRepository) : IAboutMeRepository +{ + public ValueTask> GetProfileInformationEntriesAsync() => + profileInformationEntryRepository.GetAllAsync(orderBy: entry => entry.SortOrder, descending: false); + + public ValueTask StoreProfileInformationEntryAsync(ProfileInformationEntry entry) => + profileInformationEntryRepository.StoreAsync(entry); + + public ValueTask DeleteProfileInformationEntryAsync(string id) => + profileInformationEntryRepository.DeleteAsync(id); + + public ValueTask> GetSkillsAsync() => skillRepository.GetAllAsync(); + + public ValueTask StoreSkillAsync(Skill skill) => skillRepository.StoreAsync(skill); + + public ValueTask DeleteSkillAsync(string id) => skillRepository.DeleteAsync(id); + + public ValueTask> GetTalksAsync() => + talkRepository.GetAllAsync(orderBy: talk => talk.PublishedDate, descending: true); + + public ValueTask StoreTalkAsync(Talk talk) => talkRepository.StoreAsync(talk); + + public ValueTask DeleteTalkAsync(string id) => talkRepository.DeleteAsync(id); +} diff --git a/src/LinkDotNet.Blog.Web/Features/Repositories/AnalyticsRepository.cs b/src/LinkDotNet.Blog.Web/Features/Repositories/AnalyticsRepository.cs new file mode 100644 index 000000000..1c870a7c7 --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Features/Repositories/AnalyticsRepository.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure; +using LinkDotNet.Blog.Infrastructure.Persistence; + +namespace LinkDotNet.Blog.Web.Features.Repositories; + +public sealed class AnalyticsRepository( + IRepository userRecordRepository, + IRepository blogPostRecordRepository) : IAnalyticsRepository +{ + public ValueTask StoreUserRecordAsync(UserRecord record) => userRecordRepository.StoreAsync(record); + + public ValueTask> GetUserRecordsAsync( + Expression>? filter = null, + Expression>? orderBy = null, + bool descending = true, + int page = 1, + int pageSize = int.MaxValue) => + userRecordRepository.GetAllAsync(filter, orderBy, descending, page, pageSize); + + public ValueTask> GetBlogPostRecordsAsync( + Expression>? filter = null, + Expression>? orderBy = null, + bool descending = true, + int page = 1, + int pageSize = int.MaxValue) => + blogPostRecordRepository.GetAllAsync(filter, orderBy, descending, page, pageSize); + + public ValueTask> GetGroupedBlogPostRecordsAsync( + Expression> keySelector, + Expression, TResult>> resultSelector, + Expression>? filter = null) => + blogPostRecordRepository.GetGroupedByAsync(keySelector, resultSelector, filter); + + public ValueTask DeleteUserRecordsAsync(IReadOnlyCollection ids) => + userRecordRepository.DeleteBulkAsync(ids); + + public async ValueTask ReplaceBlogPostRecordsAsync( + IReadOnlyCollection idsToDelete, + IReadOnlyCollection recordsToStore) + { + await blogPostRecordRepository.DeleteBulkAsync(idsToDelete); + await blogPostRecordRepository.StoreBulkAsync(recordsToStore); + } +} diff --git a/src/LinkDotNet.Blog.Web/Features/Repositories/BlogPostRepository.cs b/src/LinkDotNet.Blog.Web/Features/Repositories/BlogPostRepository.cs new file mode 100644 index 000000000..b27323076 --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Features/Repositories/BlogPostRepository.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Threading.Tasks; +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure; +using LinkDotNet.Blog.Infrastructure.Persistence; + +namespace LinkDotNet.Blog.Web.Features.Repositories; + +public sealed class BlogPostRepository(IRepository repository) : IBlogPostRepository +{ + public ValueTask GetByIdAsync(string id) => repository.GetByIdAsync(id); + + public ValueTask> GetAllAsync( + Expression>? filter = null, + Expression>? orderBy = null, + bool descending = true, + int page = 1, + int pageSize = int.MaxValue) => + repository.GetAllAsync(filter, orderBy, descending, page, pageSize); + + public ValueTask> GetAllByProjectionAsync( + Expression> selector, + Expression>? filter = null, + Expression>? orderBy = null, + bool descending = true, + int page = 1, + int pageSize = int.MaxValue) => + repository.GetAllByProjectionAsync(selector, filter, orderBy, descending, page, pageSize); + + public ValueTask StoreAsync(BlogPost entity) => repository.StoreAsync(entity); + + public ValueTask StoreBulkAsync(IReadOnlyCollection blogPosts) => repository.StoreBulkAsync(blogPosts); + + public ValueTask DeleteAsync(string id) => repository.DeleteAsync(id); +} diff --git a/src/LinkDotNet.Blog.Web/Features/Repositories/BlogPostTemplateRepository.cs b/src/LinkDotNet.Blog.Web/Features/Repositories/BlogPostTemplateRepository.cs new file mode 100644 index 000000000..10aad0878 --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Features/Repositories/BlogPostTemplateRepository.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure; +using LinkDotNet.Blog.Infrastructure.Persistence; + +namespace LinkDotNet.Blog.Web.Features.Repositories; + +public sealed class BlogPostTemplateRepository(IRepository repository) : IBlogPostTemplateRepository +{ + public ValueTask> GetAllAsync() => repository.GetAllAsync(); + + public ValueTask StoreAsync(BlogPostTemplate blogPostTemplate) => repository.StoreAsync(blogPostTemplate); + + public ValueTask DeleteAsync(string id) => repository.DeleteAsync(id); +} diff --git a/src/LinkDotNet.Blog.Web/Features/Repositories/BrokenLinkRepository.cs b/src/LinkDotNet.Blog.Web/Features/Repositories/BrokenLinkRepository.cs new file mode 100644 index 000000000..ad79e3d5c --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Features/Repositories/BrokenLinkRepository.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure; +using LinkDotNet.Blog.Infrastructure.Persistence; + +namespace LinkDotNet.Blog.Web.Features.Repositories; + +public sealed class BrokenLinkRepository(IRepository repository) : IBrokenLinkRepository +{ + public ValueTask> GetAllAsync() => + repository.GetAllAsync(orderBy: brokenLink => brokenLink.BlogPostTitle, descending: false); + + public async ValueTask ReplaceAllAsync(IReadOnlyCollection previousIds, BrokenLink[] brokenLinks) + { + await repository.DeleteBulkAsync(previousIds); + await repository.StoreBulkAsync(brokenLinks); + } +} diff --git a/src/LinkDotNet.Blog.Web/Features/Repositories/CachedBlogPostRepository.cs b/src/LinkDotNet.Blog.Web/Features/Repositories/CachedBlogPostRepository.cs new file mode 100644 index 000000000..f6108b96c --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Features/Repositories/CachedBlogPostRepository.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Threading.Tasks; +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure; +using LinkDotNet.Blog.Infrastructure.Persistence; +using ZiggyCreatures.Caching.Fusion; + +namespace LinkDotNet.Blog.Web.Features.Repositories; + +public sealed class CachedBlogPostRepository(IBlogPostRepository repository, IFusionCache fusionCache) : IBlogPostRepository +{ + public async ValueTask GetByIdAsync(string id) => await fusionCache.GetOrSetAsync(id, async _ => + await repository.GetByIdAsync(id), TimeSpan.FromDays(7)); + + public ValueTask> GetAllAsync( + Expression>? filter = null, + Expression>? orderBy = null, + bool descending = true, + int page = 1, + int pageSize = int.MaxValue) => + repository.GetAllAsync(filter, orderBy, descending, page, pageSize); + + public ValueTask> GetAllByProjectionAsync( + Expression> selector, + Expression>? filter = null, + Expression>? orderBy = null, + bool descending = true, + int page = 1, + int pageSize = int.MaxValue) => + repository.GetAllByProjectionAsync(selector, filter, orderBy, descending, page, pageSize); + + public async ValueTask StoreAsync(BlogPost entity) + { + ArgumentNullException.ThrowIfNull(entity); + + await repository.StoreAsync(entity); + + if (!string.IsNullOrEmpty(entity.Id)) + { + await fusionCache.RemoveAsync(entity.Id); + } + } + + public ValueTask StoreBulkAsync(IReadOnlyCollection blogPosts) => repository.StoreBulkAsync(blogPosts); + + public async ValueTask DeleteAsync(string id) + { + await repository.DeleteAsync(id); + await fusionCache.RemoveAsync(id); + } +} diff --git a/src/LinkDotNet.Blog.Web/Features/Repositories/IAboutMeRepository.cs b/src/LinkDotNet.Blog.Web/Features/Repositories/IAboutMeRepository.cs new file mode 100644 index 000000000..65faa6229 --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Features/Repositories/IAboutMeRepository.cs @@ -0,0 +1,27 @@ +using System.Threading.Tasks; +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure; +using LinkDotNet.Blog.Infrastructure.Persistence; + +namespace LinkDotNet.Blog.Web.Features.Repositories; + +public interface IAboutMeRepository +{ + ValueTask> GetProfileInformationEntriesAsync(); + + ValueTask StoreProfileInformationEntryAsync(ProfileInformationEntry entry); + + ValueTask DeleteProfileInformationEntryAsync(string id); + + ValueTask> GetSkillsAsync(); + + ValueTask StoreSkillAsync(Skill skill); + + ValueTask DeleteSkillAsync(string id); + + ValueTask> GetTalksAsync(); + + ValueTask StoreTalkAsync(Talk talk); + + ValueTask DeleteTalkAsync(string id); +} diff --git a/src/LinkDotNet.Blog.Web/Features/Repositories/IAnalyticsRepository.cs b/src/LinkDotNet.Blog.Web/Features/Repositories/IAnalyticsRepository.cs new file mode 100644 index 000000000..f9852d704 --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Features/Repositories/IAnalyticsRepository.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure; +using LinkDotNet.Blog.Infrastructure.Persistence; + +namespace LinkDotNet.Blog.Web.Features.Repositories; + +public interface IAnalyticsRepository +{ + ValueTask StoreUserRecordAsync(UserRecord record); + + ValueTask> GetUserRecordsAsync( + Expression>? filter = null, + Expression>? orderBy = null, + bool descending = true, + int page = 1, + int pageSize = int.MaxValue); + + ValueTask> GetBlogPostRecordsAsync( + Expression>? filter = null, + Expression>? orderBy = null, + bool descending = true, + int page = 1, + int pageSize = int.MaxValue); + + ValueTask> GetGroupedBlogPostRecordsAsync( + Expression> keySelector, + Expression, TResult>> resultSelector, + Expression>? filter = null); + + ValueTask DeleteUserRecordsAsync(IReadOnlyCollection ids); + + ValueTask ReplaceBlogPostRecordsAsync( + IReadOnlyCollection idsToDelete, + IReadOnlyCollection recordsToStore); +} diff --git a/src/LinkDotNet.Blog.Web/Features/Repositories/IBlogPostRepository.cs b/src/LinkDotNet.Blog.Web/Features/Repositories/IBlogPostRepository.cs new file mode 100644 index 000000000..8e6ee3a70 --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Features/Repositories/IBlogPostRepository.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Threading.Tasks; +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure; +using LinkDotNet.Blog.Infrastructure.Persistence; + +namespace LinkDotNet.Blog.Web.Features.Repositories; + +public interface IBlogPostRepository +{ + ValueTask GetByIdAsync(string id); + + ValueTask> GetAllAsync( + Expression>? filter = null, + Expression>? orderBy = null, + bool descending = true, + int page = 1, + int pageSize = int.MaxValue); + + ValueTask> GetAllByProjectionAsync( + Expression> selector, + Expression>? filter = null, + Expression>? orderBy = null, + bool descending = true, + int page = 1, + int pageSize = int.MaxValue); + + ValueTask StoreAsync(BlogPost entity); + + ValueTask StoreBulkAsync(IReadOnlyCollection blogPosts); + + ValueTask DeleteAsync(string id); +} diff --git a/src/LinkDotNet.Blog.Web/Features/Repositories/IBlogPostTemplateRepository.cs b/src/LinkDotNet.Blog.Web/Features/Repositories/IBlogPostTemplateRepository.cs new file mode 100644 index 000000000..5bdb7ff96 --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Features/Repositories/IBlogPostTemplateRepository.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure; +using LinkDotNet.Blog.Infrastructure.Persistence; + +namespace LinkDotNet.Blog.Web.Features.Repositories; + +public interface IBlogPostTemplateRepository +{ + ValueTask> GetAllAsync(); + + ValueTask StoreAsync(BlogPostTemplate blogPostTemplate); + + ValueTask DeleteAsync(string id); +} diff --git a/src/LinkDotNet.Blog.Web/Features/Repositories/IBrokenLinkRepository.cs b/src/LinkDotNet.Blog.Web/Features/Repositories/IBrokenLinkRepository.cs new file mode 100644 index 000000000..15a11fcdc --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Features/Repositories/IBrokenLinkRepository.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure; +using LinkDotNet.Blog.Infrastructure.Persistence; + +namespace LinkDotNet.Blog.Web.Features.Repositories; + +public interface IBrokenLinkRepository +{ + ValueTask> GetAllAsync(); + + ValueTask ReplaceAllAsync(IReadOnlyCollection previousIds, BrokenLink[] brokenLinks); +} diff --git a/src/LinkDotNet.Blog.Web/Features/Repositories/IShortCodeRepository.cs b/src/LinkDotNet.Blog.Web/Features/Repositories/IShortCodeRepository.cs new file mode 100644 index 000000000..fc7270f6b --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Features/Repositories/IShortCodeRepository.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure; +using LinkDotNet.Blog.Infrastructure.Persistence; + +namespace LinkDotNet.Blog.Web.Features.Repositories; + +public interface IShortCodeRepository +{ + ValueTask> GetAllAsync(); + + ValueTask StoreAsync(ShortCode shortCode); + + ValueTask DeleteAsync(string id); +} diff --git a/src/LinkDotNet.Blog.Web/Features/Repositories/ISimilarBlogPostRepository.cs b/src/LinkDotNet.Blog.Web/Features/Repositories/ISimilarBlogPostRepository.cs new file mode 100644 index 000000000..70ae670d2 --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Features/Repositories/ISimilarBlogPostRepository.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LinkDotNet.Blog.Domain; + +namespace LinkDotNet.Blog.Web.Features.Repositories; + +public interface ISimilarBlogPostRepository +{ + ValueTask GetByIdAsync(string id); + + ValueTask> GetAllIdsAsync(); + + ValueTask ReplaceAllAsync(IReadOnlyCollection idsToDelete, IReadOnlyCollection similarBlogPosts); +} diff --git a/src/LinkDotNet.Blog.Web/Features/Repositories/ShortCodeRepository.cs b/src/LinkDotNet.Blog.Web/Features/Repositories/ShortCodeRepository.cs new file mode 100644 index 000000000..b891fd253 --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Features/Repositories/ShortCodeRepository.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure; +using LinkDotNet.Blog.Infrastructure.Persistence; + +namespace LinkDotNet.Blog.Web.Features.Repositories; + +public sealed class ShortCodeRepository(IRepository repository) : IShortCodeRepository +{ + public ValueTask> GetAllAsync() => repository.GetAllAsync(); + + public ValueTask StoreAsync(ShortCode shortCode) => repository.StoreAsync(shortCode); + + public ValueTask DeleteAsync(string id) => repository.DeleteAsync(id); +} diff --git a/src/LinkDotNet.Blog.Web/Features/Repositories/SimilarBlogPostRepository.cs b/src/LinkDotNet.Blog.Web/Features/Repositories/SimilarBlogPostRepository.cs new file mode 100644 index 000000000..2dd79daa6 --- /dev/null +++ b/src/LinkDotNet.Blog.Web/Features/Repositories/SimilarBlogPostRepository.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure.Persistence; + +namespace LinkDotNet.Blog.Web.Features.Repositories; + +public sealed class SimilarBlogPostRepository(IRepository repository) : ISimilarBlogPostRepository +{ + public ValueTask GetByIdAsync(string id) => repository.GetByIdAsync(id); + + public async ValueTask> GetAllIdsAsync() => await repository.GetAllByProjectionAsync(s => s.Id); + + public async ValueTask ReplaceAllAsync(IReadOnlyCollection idsToDelete, IReadOnlyCollection similarBlogPosts) + { + await repository.DeleteBulkAsync(idsToDelete); + await repository.StoreBulkAsync(similarBlogPosts); + } +} diff --git a/src/LinkDotNet.Blog.Web/Features/Search/SearchPage.razor b/src/LinkDotNet.Blog.Web/Features/Search/SearchPage.razor index a615325c2..a7093f8d0 100644 --- a/src/LinkDotNet.Blog.Web/Features/Search/SearchPage.razor +++ b/src/LinkDotNet.Blog.Web/Features/Search/SearchPage.razor @@ -1,8 +1,8 @@ @page "/search/{searchTerm}" @using LinkDotNet.Blog.Domain @using LinkDotNet.Blog.Infrastructure -@using LinkDotNet.Blog.Infrastructure.Persistence -@inject IRepository BlogPostRepository +@using LinkDotNet.Blog.Web.Features.Repositories +@inject IBlogPostRepository BlogPostRepository

Results for: @SearchTerm

diff --git a/src/LinkDotNet.Blog.Web/Features/SearchByTag/SearchByTagPage.razor b/src/LinkDotNet.Blog.Web/Features/SearchByTag/SearchByTagPage.razor index d5f83eaa2..3fb370cce 100644 --- a/src/LinkDotNet.Blog.Web/Features/SearchByTag/SearchByTagPage.razor +++ b/src/LinkDotNet.Blog.Web/Features/SearchByTag/SearchByTagPage.razor @@ -1,9 +1,9 @@ @page "/searchByTag/{tag}" @using LinkDotNet.Blog.Domain @using LinkDotNet.Blog.Infrastructure -@using LinkDotNet.Blog.Infrastructure.Persistence +@using LinkDotNet.Blog.Web.Features.Repositories @using LinkDotNet.Blog.Web.Features.Components -@inject IRepository BlogPostRepository +@inject IBlogPostRepository BlogPostRepository Search for tag: @Tag @* Tag pages are thin listing pages that search engines crawl and then decline to diff --git a/src/LinkDotNet.Blog.Web/Features/Services/Tags/TagQueryService.cs b/src/LinkDotNet.Blog.Web/Features/Services/Tags/TagQueryService.cs index 963bc4b91..5b5b09702 100644 --- a/src/LinkDotNet.Blog.Web/Features/Services/Tags/TagQueryService.cs +++ b/src/LinkDotNet.Blog.Web/Features/Services/Tags/TagQueryService.cs @@ -1,5 +1,5 @@ -using LinkDotNet.Blog.Domain; -using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; @@ -10,7 +10,7 @@ namespace LinkDotNet.Blog.Web.Features.Services.Tags; public sealed class TagQueryService( - IRepository blogPostRepository, + IBlogPostRepository blogPostRepository, IFusionCache fusionCache, IOptions appConfiguration) : ITagQueryService { diff --git a/src/LinkDotNet.Blog.Web/Features/Services/UserRecordService.cs b/src/LinkDotNet.Blog.Web/Features/Services/UserRecordService.cs index d129df38e..b7e6b5c23 100644 --- a/src/LinkDotNet.Blog.Web/Features/Services/UserRecordService.cs +++ b/src/LinkDotNet.Blog.Web/Features/Services/UserRecordService.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; using LinkDotNet.Blog.Domain; -using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.Logging; @@ -10,20 +10,20 @@ namespace LinkDotNet.Blog.Web.Features.Services; public sealed partial class UserRecordService : IUserRecordService { - private readonly IRepository userRecordRepository; + private readonly IAnalyticsRepository analyticsRepository; private readonly NavigationManager navigationManager; private readonly AuthenticationStateProvider authenticationStateProvider; private readonly TimeProvider timeProvider; private readonly ILogger logger; public UserRecordService( - IRepository userRecordRepository, + IAnalyticsRepository analyticsRepository, NavigationManager navigationManager, AuthenticationStateProvider authenticationStateProvider, TimeProvider timeProvider, ILogger logger) { - this.userRecordRepository = userRecordRepository; + this.analyticsRepository = analyticsRepository; this.navigationManager = navigationManager; this.authenticationStateProvider = authenticationStateProvider; this.timeProvider = timeProvider; @@ -58,7 +58,7 @@ private async ValueTask GetAndStoreUserRecordAsync() UrlClicked = url, }; - await userRecordRepository.StoreAsync(record); + await analyticsRepository.StoreUserRecordAsync(record); } private string GetClickedUrl() diff --git a/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/Components/BlogPostAdminActions.razor b/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/Components/BlogPostAdminActions.razor index 5c4422e32..7d93e951c 100644 --- a/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/Components/BlogPostAdminActions.razor +++ b/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/Components/BlogPostAdminActions.razor @@ -1,10 +1,10 @@ @using LinkDotNet.Blog.Domain -@using LinkDotNet.Blog.Infrastructure.Persistence +@using LinkDotNet.Blog.Web.Features.Repositories @using LinkDotNet.Blog.Web.Features.Services.Tags @using NCronJob @inject NavigationManager NavigationManager @inject IToastService ToastService -@inject IRepository BlogPostRepository +@inject IBlogPostRepository BlogPostRepository @inject IInstantJobRegistry InstantJobRegistry @inject ITagQueryService TagQueryService diff --git a/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/Components/SimilarBlogPostSection.razor b/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/Components/SimilarBlogPostSection.razor index ef076be7f..bdab6009e 100644 --- a/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/Components/SimilarBlogPostSection.razor +++ b/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/Components/SimilarBlogPostSection.razor @@ -1,8 +1,8 @@ -@using LinkDotNet.Blog.Domain -@using LinkDotNet.Blog.Infrastructure.Persistence +@using LinkDotNet.Blog.Domain +@using LinkDotNet.Blog.Web.Features.Repositories @using LinkDotNet.Blog.Web.Features -@inject IRepository BlogPostRepository -@inject IRepository SimilarBlogPostJobRepository +@inject IBlogPostRepository BlogPostRepository +@inject ISimilarBlogPostRepository SimilarBlogPostJobRepository @if (similarBlogPosts.Count > 0) { diff --git a/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor b/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor index b388e6741..fd12684e4 100644 --- a/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor +++ b/src/LinkDotNet.Blog.Web/Features/ShowBlogPost/ShowBlogPostPage.razor @@ -2,14 +2,14 @@ @using System.Text @using Markdig @using LinkDotNet.Blog.Domain -@using LinkDotNet.Blog.Infrastructure.Persistence +@using LinkDotNet.Blog.Web.Features.Repositories @using LinkDotNet.Blog.Web.Features.Bookmarks @using LinkDotNet.Blog.Web.Features.Services @using LinkDotNet.Blog.Web.Features.ShowBlogPost.Components @using LinkDotNet.Blog.Web.Features.SupportMe.Components @using LinkDotNet.Blog.Web.Features.Bookmarks.Components -@inject IRepository BlogPostRepository -@inject IRepository ShortCodeRepository +@inject IBlogPostRepository BlogPostRepository +@inject IShortCodeRepository ShortCodeRepository @inject IJSRuntime JsRuntime @inject NavigationManager NavigationManager @inject IUserRecordService UserRecordService diff --git a/src/LinkDotNet.Blog.Web/Features/SimilarBlogPostJob.cs b/src/LinkDotNet.Blog.Web/Features/SimilarBlogPostJob.cs index e461123b5..1dcf0c7b0 100644 --- a/src/LinkDotNet.Blog.Web/Features/SimilarBlogPostJob.cs +++ b/src/LinkDotNet.Blog.Web/Features/SimilarBlogPostJob.cs @@ -1,23 +1,23 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using NCronJob; using LinkDotNet.Blog.Domain; -using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.Services.Similiarity; namespace LinkDotNet.Blog.Web.Features; public class SimilarBlogPostJob : IJob { - private readonly IRepository blogPostRepository; - private readonly IRepository similarBlogPostRepository; + private readonly IBlogPostRepository blogPostRepository; + private readonly ISimilarBlogPostRepository similarBlogPostRepository; public SimilarBlogPostJob( - IRepository blogPostRepository, - IRepository similarBlogPostRepository) + IBlogPostRepository blogPostRepository, + ISimilarBlogPostRepository similarBlogPostRepository) { this.blogPostRepository = blogPostRepository; this.similarBlogPostRepository = similarBlogPostRepository; @@ -40,9 +40,8 @@ public async Task RunAsync(IJobExecutionContext context, CancellationToken token var documents = blogPosts.Select(bp => TextProcessor.TokenizeAndNormalize([bp.Title, bp.ShortDescription, ..bp.Tags])).ToList(); var similarities = blogPosts.Select(bp => GetSimilarityForBlogPost(bp, documents, blogPosts)).ToArray(); - var ids = await similarBlogPostRepository.GetAllByProjectionAsync(s => s.Id); - await similarBlogPostRepository.DeleteBulkAsync(ids); - await similarBlogPostRepository.StoreBulkAsync(similarities); + var ids = await similarBlogPostRepository.GetAllIdsAsync(); + await similarBlogPostRepository.ReplaceAllAsync(ids, similarities); } diff --git a/src/LinkDotNet.Blog.Web/Features/TransformBlogPostRecordsJob.cs b/src/LinkDotNet.Blog.Web/Features/TransformBlogPostRecordsJob.cs index 855f6e71e..04d7d10d2 100644 --- a/src/LinkDotNet.Blog.Web/Features/TransformBlogPostRecordsJob.cs +++ b/src/LinkDotNet.Blog.Web/Features/TransformBlogPostRecordsJob.cs @@ -1,31 +1,28 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using LinkDotNet.Blog.Domain; -using LinkDotNet.Blog.Infrastructure.Persistence; using NCronJob; using Microsoft.Extensions.Logging; +using LinkDotNet.Blog.Web.Features.Repositories; namespace LinkDotNet.Blog.Web.Features; public sealed partial class TransformBlogPostRecordsJob : IJob { - private readonly IRepository blogPostRepository; - private readonly IRepository userRecordRepository; - private readonly IRepository blogPostRecordRepository; + private readonly IBlogPostRepository blogPostRepository; + private readonly IAnalyticsRepository analyticsRepository; private readonly ILogger logger; public TransformBlogPostRecordsJob( - IRepository blogPostRepository, - IRepository userRecordRepository, - IRepository blogPostRecordRepository, + IBlogPostRepository blogPostRepository, + IAnalyticsRepository analyticsRepository, ILogger logger) { this.blogPostRepository = blogPostRepository; - this.userRecordRepository = userRecordRepository; - this.blogPostRecordRepository = blogPostRecordRepository; + this.analyticsRepository = analyticsRepository; this.logger = logger; } @@ -78,7 +75,7 @@ private static string GetBlogPostId(UserRecord userRecord) private async Task TransformRecordsAsync() { var blogPosts = await blogPostRepository.GetAllAsync(); - var userRecords = await userRecordRepository.GetAllAsync( + var userRecords = await analyticsRepository.GetUserRecordsAsync( filter: r => r.UrlClicked.StartsWith("blogPost/")); var newBlogPostRecords = GetBlogPostRecords(blogPosts, userRecords); @@ -88,15 +85,14 @@ private async Task TransformRecordsAsync() } var earliestDate = newBlogPostRecords.MinBy(r => r.DateClicked)?.DateClicked ?? DateOnly.MinValue; - var oldBlogPostRecords = await blogPostRecordRepository.GetAllAsync(f => f.DateClicked >= earliestDate); + var oldBlogPostRecords = await analyticsRepository.GetBlogPostRecordsAsync(f => f.DateClicked >= earliestDate); var mergedRecords = MergeRecords(oldBlogPostRecords, newBlogPostRecords); - await blogPostRecordRepository.DeleteBulkAsync(oldBlogPostRecords.Select(o => o.Id).ToArray()); - await blogPostRecordRepository.StoreBulkAsync(mergedRecords.ToArray()); + await analyticsRepository.ReplaceBlogPostRecordsAsync(oldBlogPostRecords.Select(o => o.Id).ToArray(), mergedRecords.ToArray()); LogDeletingUserRecords(userRecords.Count); - await userRecordRepository.DeleteBulkAsync(userRecords.Select(u => u.Id).ToArray()); + await analyticsRepository.DeleteUserRecordsAsync(userRecords.Select(u => u.Id).ToArray()); LogDeletedUserRecords(); } diff --git a/src/LinkDotNet.Blog.Web/RegistrationExtensions/MongoDBRegistrationExtensions.cs b/src/LinkDotNet.Blog.Web/RegistrationExtensions/MongoDBRegistrationExtensions.cs index 74b6f3a32..bb2b04796 100644 --- a/src/LinkDotNet.Blog.Web/RegistrationExtensions/MongoDBRegistrationExtensions.cs +++ b/src/LinkDotNet.Blog.Web/RegistrationExtensions/MongoDBRegistrationExtensions.cs @@ -19,5 +19,6 @@ public static void UseMongoDBAsStorageProvider(this IServiceCollection services) return MongoDBConnectionProvider.Create(connectionString, databaseName); }); services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); + services.AddScoped(); } } diff --git a/src/LinkDotNet.Blog.Web/RegistrationExtensions/RavenDbRegistrationExtensions.cs b/src/LinkDotNet.Blog.Web/RegistrationExtensions/RavenDbRegistrationExtensions.cs index 1cee30ca9..97a23a9bc 100644 --- a/src/LinkDotNet.Blog.Web/RegistrationExtensions/RavenDbRegistrationExtensions.cs +++ b/src/LinkDotNet.Blog.Web/RegistrationExtensions/RavenDbRegistrationExtensions.cs @@ -19,5 +19,6 @@ public static void UseRavenDbAsStorageProvider(this IServiceCollection services) return RavenDbConnectionProvider.Create(connectionString, databaseName); }); services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); + services.AddScoped(); } } diff --git a/src/LinkDotNet.Blog.Web/RegistrationExtensions/SqlRegistrationExtensions.cs b/src/LinkDotNet.Blog.Web/RegistrationExtensions/SqlRegistrationExtensions.cs index 33158d921..ea85aa52d 100644 --- a/src/LinkDotNet.Blog.Web/RegistrationExtensions/SqlRegistrationExtensions.cs +++ b/src/LinkDotNet.Blog.Web/RegistrationExtensions/SqlRegistrationExtensions.cs @@ -25,6 +25,7 @@ public static void UseSqlAsStorageProvider(this IServiceCollection services) }); services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); + services.AddScoped(); } public static void UseSqliteAsStorageProvider(this IServiceCollection services) @@ -43,6 +44,7 @@ public static void UseSqliteAsStorageProvider(this IServiceCollection services) ; }); services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); + services.AddScoped(); } public static void UseMySqlAsStorageProvider(this IServiceCollection services) @@ -65,6 +67,7 @@ public static void UseMySqlAsStorageProvider(this IServiceCollection services) ; }); services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); + services.AddScoped(); } public static void UsePostgreSqlAsStorageProvider(this IServiceCollection services) @@ -83,5 +86,6 @@ public static void UsePostgreSqlAsStorageProvider(this IServiceCollection servic ; }); services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); + services.AddScoped(); } } diff --git a/src/LinkDotNet.Blog.Web/RegistrationExtensions/StorageProviderExtensions.cs b/src/LinkDotNet.Blog.Web/RegistrationExtensions/StorageProviderExtensions.cs index cbe30cf76..060f2e1e6 100644 --- a/src/LinkDotNet.Blog.Web/RegistrationExtensions/StorageProviderExtensions.cs +++ b/src/LinkDotNet.Blog.Web/RegistrationExtensions/StorageProviderExtensions.cs @@ -1,5 +1,6 @@ using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System; @@ -30,28 +31,22 @@ public static IServiceCollection AddStorageProvider(this IServiceCollection serv onRavenDb: services.UseRavenDbAsStorageProvider ); - if (persistenceProvider.IsSql()) - { - services.RegisterCachedRepository>(); - } - else if (persistenceProvider.IsRavenDb()) - { - services.RegisterCachedRepository>(); - } - else if (persistenceProvider.IsMongoDB()) - { - services.RegisterCachedRepository>(); - } + services.RegisterTypedRepositories(); return services; } - private static void RegisterCachedRepository(this IServiceCollection services) - where TRepo : class, IRepository + private static void RegisterTypedRepositories(this IServiceCollection services) { - services.AddScoped(); - services.AddScoped>(provider => new CachedRepository( - provider.GetRequiredService(), - provider.GetRequiredService())); + services.AddScoped(); + services.AddScoped(provider => new CachedBlogPostRepository( + provider.GetRequiredService(), + provider.GetRequiredService())); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); } } diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Controller/RssFeedControllerTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Controller/RssFeedControllerTests.cs index a780490ec..8b81586e7 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Controller/RssFeedControllerTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Controller/RssFeedControllerTests.cs @@ -4,6 +4,7 @@ using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.TestUtilities; using LinkDotNet.Blog.Web.Controller; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; @@ -48,7 +49,7 @@ public async Task ShouldCreateRssFeed() .Build(); await Repository.StoreAsync(blogPost1); await Repository.StoreAsync(blogPost2); - var cut = new RssFeedController(introductionConfig, config, Repository) + var cut = new RssFeedController(introductionConfig, config, new BlogPostRepository(Repository)) { ControllerContext = controllerContext, }; @@ -126,7 +127,7 @@ public async Task ShouldReturnFullContentIfRequested() .Build(); await Repository.StoreAsync(blogPost1); await Repository.StoreAsync(blogPost2); - var cut = new RssFeedController(introductionConfig, config, Repository) + var cut = new RssFeedController(introductionConfig, config, new BlogPostRepository(Repository)) { ControllerContext = controllerContext, }; @@ -204,7 +205,7 @@ public async Task ShouldReturnNPostsIfRequested() .Build(); await Repository.StoreAsync(blogPost1); await Repository.StoreAsync(blogPost2); - var cut = new RssFeedController(introductionConfig, config, Repository) + var cut = new RssFeedController(introductionConfig, config, new BlogPostRepository(Repository)) { ControllerContext = controllerContext, }; @@ -272,7 +273,7 @@ public async Task ShouldRespectBlogPostsPerPage() .Build(); await Repository.StoreAsync(blogPost1); await Repository.StoreAsync(blogPost2); - var cut = new RssFeedController(introductionConfig, config, Repository) + var cut = new RssFeedController(introductionConfig, config, new BlogPostRepository(Repository)) { ControllerContext = controllerContext, }; diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/AboutMe/Components/TalksTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/AboutMe/Components/TalksTests.cs index 542a996df..f71693e62 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/AboutMe/Components/TalksTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/AboutMe/Components/TalksTests.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Threading.Tasks; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.TestUtilities; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.TestUtilities.Fakes; using LinkDotNet.Blog.Web.Features.AboutMe.Components.Talk; using LinkDotNet.Blog.Web.Features.Components; @@ -18,6 +19,7 @@ public sealed class TalksTests : SqlDatabaseTestBase, IDisposable public TalksTests() { ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(); ctx.ComponentFactories.Add(); } diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/BlogPostEditor/BlogPostVersionServiceTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/BlogPostEditor/BlogPostVersionServiceTests.cs index ad8c7cc1e..0bd1cee4a 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/BlogPostEditor/BlogPostVersionServiceTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/BlogPostEditor/BlogPostVersionServiceTests.cs @@ -4,6 +4,7 @@ using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.TestUtilities; using LinkDotNet.Blog.Web.Features.Admin.BlogPostEditor.Services; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.EntityFrameworkCore; using TestContext = Xunit.TestContext; @@ -15,7 +16,7 @@ public class BlogPostVersionServiceTests : SqlDatabaseTestBase public BlogPostVersionServiceTests() { - sut = new BlogPostVersionService(DbContextFactory, Repository); + sut = new BlogPostVersionService(DbContextFactory, new BlogPostRepository(Repository)); } [Fact] diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/BlogPostEditor/CreateNewBlogPostPageTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/BlogPostEditor/CreateNewBlogPostPageTests.cs index fca62887a..004709174 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/BlogPostEditor/CreateNewBlogPostPageTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/BlogPostEditor/CreateNewBlogPostPageTests.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Blazored.Toast.Services; @@ -14,6 +14,7 @@ using LinkDotNet.Blog.Web.Features.Components; using LinkDotNet.Blog.Web.Features.Services; using LinkDotNet.Blog.Web.Features.Services.Tags; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -34,6 +35,7 @@ public async Task ShouldSaveBlogPostOnSave() ctx.JSInterop.SetupVoid("hljs.highlightAll"); ctx.AddAuthorization().SetAuthorized("some username"); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); ctx.Services.AddScoped(_ => toastService); ctx.Services.AddScoped(_ => Substitute.For()); ctx.Services.AddScoped(_ => instantRegistry); @@ -43,10 +45,12 @@ public async Task ShouldSaveBlogPostOnSave() var shortCodeRepository = Substitute.For>(); shortCodeRepository.GetAllAsync().Returns(PagedList.Empty); ctx.Services.AddScoped(_ => shortCodeRepository); + ctx.Services.AddScoped(_ => new ShortCodeRepository(shortCodeRepository)); var templateRepository = Substitute.For>(); templateRepository.GetAllAsync().Returns(PagedList.Empty); ctx.Services.AddScoped(_ => templateRepository); + ctx.Services.AddScoped(_ => new BlogPostTemplateRepository(templateRepository)); var currentUserService = Substitute.For(); currentUserService.GetDisplayNameAsync().Returns("Test Author"); @@ -88,6 +92,7 @@ public async Task ShouldSaveAuthorNameAsNullWhenMultiAuthorModeIsDisabled() ctx.JSInterop.SetupVoid("hljs.highlightAll"); ctx.AddAuthorization().SetAuthorized("some username"); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); ctx.Services.AddScoped(_ => toastService); ctx.Services.AddScoped(_ => Substitute.For()); ctx.Services.AddScoped(_ => instantRegistry); @@ -97,10 +102,12 @@ public async Task ShouldSaveAuthorNameAsNullWhenMultiAuthorModeIsDisabled() var shortCodeRepository = Substitute.For>(); shortCodeRepository.GetAllAsync().Returns(PagedList.Empty); ctx.Services.AddScoped(_ => shortCodeRepository); + ctx.Services.AddScoped(_ => new ShortCodeRepository(shortCodeRepository)); var templateRepository = Substitute.For>(); templateRepository.GetAllAsync().Returns(PagedList.Empty); ctx.Services.AddScoped(_ => templateRepository); + ctx.Services.AddScoped(_ => new BlogPostTemplateRepository(templateRepository)); var currentUserService = Substitute.For(); currentUserService.GetDisplayNameAsync().Returns("Test Author"); @@ -137,6 +144,7 @@ public async Task ShouldLoadTemplate() ctx.JSInterop.SetupVoid("hljs.highlightAll"); ctx.AddAuthorization().SetAuthorized("some username"); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); ctx.Services.AddScoped(_ => toastService); ctx.Services.AddScoped(_ => Substitute.For()); ctx.Services.AddScoped(_ => Substitute.For()); @@ -146,11 +154,13 @@ public async Task ShouldLoadTemplate() var shortCodeRepository = Substitute.For>(); shortCodeRepository.GetAllAsync().Returns(PagedList.Empty); ctx.Services.AddScoped(_ => shortCodeRepository); + ctx.Services.AddScoped(_ => new ShortCodeRepository(shortCodeRepository)); var templateRepository = Substitute.For>(); var template = BlogPostTemplate.Create("My Template", "Title", "Short", "Content"); templateRepository.GetAllAsync().Returns(new PagedList([template], 1, 1, 1)); ctx.Services.AddScoped(_ => templateRepository); + ctx.Services.AddScoped(_ => new BlogPostTemplateRepository(templateRepository)); var currentUserService = Substitute.For(); ctx.Services.AddScoped(_ => currentUserService); diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/BlogPostEditor/UpdateBlogPostPageTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/BlogPostEditor/UpdateBlogPostPageTests.cs index 10b1ab627..7bea543c8 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/BlogPostEditor/UpdateBlogPostPageTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/BlogPostEditor/UpdateBlogPostPageTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Threading; using System.Threading.Tasks; using Blazored.Toast.Services; @@ -15,6 +15,7 @@ using LinkDotNet.Blog.Web.Features.Components; using LinkDotNet.Blog.Web.Features.Services; using LinkDotNet.Blog.Web.Features.Services.Tags; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.AspNetCore.Components; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -39,17 +40,20 @@ public async Task ShouldSaveBlogPostOnSave() await Repository.StoreAsync(blogPost); ctx.AddAuthorization().SetAuthorized("some username"); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); ctx.Services.AddScoped(_ => toastService); ctx.Services.AddScoped(_ => instantRegistry); - ctx.Services.AddScoped(_ => new BlogPostVersionService(DbContextFactory, Repository)); + ctx.Services.AddScoped(_ => new BlogPostVersionService(DbContextFactory, new BlogPostRepository(Repository))); ctx.Services.AddScoped(_ => EmptyTagQueryService()); var shortCodeRepository = Substitute.For>(); shortCodeRepository.GetAllAsync().Returns(PagedList.Empty); ctx.Services.AddScoped(_ => shortCodeRepository); + ctx.Services.AddScoped(_ => new ShortCodeRepository(shortCodeRepository)); var templateRepository = Substitute.For>(); templateRepository.GetAllAsync().Returns(PagedList.Empty); ctx.Services.AddScoped(_ => templateRepository); + ctx.Services.AddScoped(_ => new BlogPostTemplateRepository(templateRepository)); var currentUserService = Substitute.For(); currentUserService.GetDisplayNameAsync().Returns("Test Author"); @@ -95,17 +99,20 @@ public async Task ShouldSaveAuthorNameAsNullWhenMultiAuthorModeIsDisabled() await Repository.StoreAsync(blogPost); ctx.AddAuthorization().SetAuthorized("some username"); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); ctx.Services.AddScoped(_ => toastService); ctx.Services.AddScoped(_ => instantRegistry); - ctx.Services.AddScoped(_ => new BlogPostVersionService(DbContextFactory, Repository)); + ctx.Services.AddScoped(_ => new BlogPostVersionService(DbContextFactory, new BlogPostRepository(Repository))); ctx.Services.AddScoped(_ => EmptyTagQueryService()); var shortCodeRepository = Substitute.For>(); shortCodeRepository.GetAllAsync().Returns(PagedList.Empty); ctx.Services.AddScoped(_ => shortCodeRepository); + ctx.Services.AddScoped(_ => new ShortCodeRepository(shortCodeRepository)); var templateRepository = Substitute.For>(); templateRepository.GetAllAsync().Returns(PagedList.Empty); ctx.Services.AddScoped(_ => templateRepository); + ctx.Services.AddScoped(_ => new BlogPostTemplateRepository(templateRepository)); var currentUserService = Substitute.For(); currentUserService.GetDisplayNameAsync().Returns("Test Author"); @@ -141,9 +148,10 @@ public void ShouldThrowWhenNoIdProvided() ctx.ComponentFactories.Add(); ctx.AddAuthorization().SetAuthorized("some username"); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); ctx.Services.AddScoped(_ => Substitute.For()); ctx.Services.AddScoped(_ => Substitute.For()); - ctx.Services.AddScoped(_ => new BlogPostVersionService(DbContextFactory, Repository)); + ctx.Services.AddScoped(_ => new BlogPostVersionService(DbContextFactory, new BlogPostRepository(Repository))); var currentUserService = Substitute.For(); currentUserService.GetDisplayNameAsync().Returns("Test Author"); diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/BrokenLinks/BrokenLinkCheckerJobTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/BrokenLinks/BrokenLinkCheckerJobTests.cs index 02fed700b..d8083dc0e 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/BrokenLinks/BrokenLinkCheckerJobTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/BrokenLinks/BrokenLinkCheckerJobTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -6,6 +6,7 @@ using LinkDotNet.Blog.Infrastructure.Persistence.Sql; using LinkDotNet.Blog.TestUtilities; using LinkDotNet.Blog.Web.Features.Admin.BrokenLinks.Services; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.Extensions.Logging; using NCronJob; @@ -87,5 +88,5 @@ public async Task ShouldReplacePreviousResults() (await brokenLinkRepository.GetAllAsync()).ShouldBeEmpty(); } - private BrokenLinkCheckerJob CreateSut() => new(Repository, brokenLinkRepository, linkChecker, timeProvider); + private BrokenLinkCheckerJob CreateSut() => new(new BlogPostRepository(Repository), new BrokenLinkRepository(brokenLinkRepository), linkChecker, timeProvider); } diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/DraftBlogPost/DraftBlogPostPageTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/DraftBlogPost/DraftBlogPostPageTests.cs index 84c1cedbb..64da5ae30 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/DraftBlogPost/DraftBlogPostPageTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/DraftBlogPost/DraftBlogPostPageTests.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.TestUtilities; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.Admin.DraftBlogPost; using LinkDotNet.Blog.Web.Features.Bookmarks; using LinkDotNet.Blog.Web.Features.Components; @@ -20,6 +21,7 @@ public async Task ShouldOnlyShowPublishedPosts() using var ctx = new BunitContext(); ctx.JSInterop.Mode = JSRuntimeMode.Loose; ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); ctx.Services.AddScoped(_ => Substitute.For()); var cut = ctx.Render(); cut.WaitForElement(".blog-card"); diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/ShortCodes/ShortCodesPageTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/ShortCodes/ShortCodesPageTests.cs index 913333731..83900b98e 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/ShortCodes/ShortCodesPageTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/ShortCodes/ShortCodesPageTests.cs @@ -1,10 +1,11 @@ -using System.Linq; +using System.Linq; using System.Threading.Tasks; using Blazored.Toast.Services; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.TestUtilities.Fakes; using LinkDotNet.Blog.Web.Features.Admin.ShortCodes; using LinkDotNet.Blog.Web.Features.Components; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.Extensions.DependencyInjection; namespace LinkDotNet.Blog.IntegrationTests.Web.Features.Admin.ShortCodes; @@ -16,6 +17,7 @@ public async Task ShouldShowShortCodes() { await using var ctx = new BunitContext(); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new ShortCodeRepository(Repository)); ctx.Services.AddScoped(_ => Substitute.For()); ctx.ComponentFactories.Add(); var cut = ctx.Render(); @@ -37,6 +39,7 @@ public async Task ShouldUpdateShortCode() var shortCode = ShortCode.Create("# Text", "ShortName"); await Repository.StoreAsync(shortCode); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new ShortCodeRepository(Repository)); ctx.Services.AddScoped(_ => Substitute.For()); ctx.ComponentFactories.Add(); var cut = ctx.Render(); diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Archive/ArchivePageTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Archive/ArchivePageTests.cs index f8ab82768..4ea081905 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Archive/ArchivePageTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Archive/ArchivePageTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -7,6 +7,7 @@ using LinkDotNet.Blog.Infrastructure; using LinkDotNet.Blog.Infrastructure.Persistence; using LinkDotNet.Blog.TestUtilities; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.Archive; using LinkDotNet.Blog.Web.Features.Components; using Microsoft.Extensions.DependencyInjection; @@ -21,6 +22,7 @@ public async Task ShouldDisplayAllBlogPosts() { using var ctx = new BunitContext(); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); await Repository.StoreAsync(CreateBlogPost(new DateTime(2021, 1, 1), "Blog Post 1")); await Repository.StoreAsync(CreateBlogPost(new DateTime(2021, 2, 1), "Blog Post 2")); await Repository.StoreAsync(CreateBlogPost(new DateTime(2022, 1, 1), "Blog Post 3")); @@ -44,6 +46,7 @@ public async Task ShouldOnlyShowPublishedBlogPosts() { using var ctx = new BunitContext(); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); var publishedBlogPost = new BlogPostBuilder().WithUpdatedDate(new DateTime(2022, 1, 1)).IsPublished().Build(); var unPublishedBlogPost = new BlogPostBuilder().WithUpdatedDate(new DateTime(2022, 1, 1)).IsPublished(false).Build(); await Repository.StoreAsync(publishedBlogPost); @@ -60,6 +63,7 @@ public async Task ShouldShowTotalAmountOfBlogPosts() { using var ctx = new BunitContext(); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); await Repository.StoreAsync(CreateBlogPost(new DateTime(2021, 1, 1), "Blog Post 1")); await Repository.StoreAsync(CreateBlogPost(new DateTime(2021, 2, 1), "Blog Post 2")); @@ -85,6 +89,7 @@ public void ShouldSetOgData() { using var ctx = new BunitContext(); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); var cut = ctx.Render(); diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/BlogPostPublisherTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/BlogPostPublisherTests.cs index a23e41aef..117c485b0 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/BlogPostPublisherTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/BlogPostPublisherTests.cs @@ -4,6 +4,7 @@ using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.TestUtilities; using LinkDotNet.Blog.Web.Features; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.Services; using Microsoft.Extensions.Logging; using NCronJob; @@ -19,7 +20,7 @@ public BlogPostPublisherTests() { cacheInvalidator = Substitute.For(); - sut = new BlogPostPublisher(Repository, cacheInvalidator, TimeProvider.System, Substitute.For>()); + sut = new BlogPostPublisher(new BlogPostRepository(Repository), cacheInvalidator, TimeProvider.System, Substitute.For>()); } [Fact] diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Bookmarks/BookmarksTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Bookmarks/BookmarksTests.cs index 487a419ef..11ca41f13 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Bookmarks/BookmarksTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Bookmarks/BookmarksTests.cs @@ -1,7 +1,8 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.TestUtilities; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.Bookmarks; using LinkDotNet.Blog.Web.Features.Bookmarks.Components; using LinkDotNet.Blog.Web.Features.Components; @@ -23,6 +24,7 @@ public async Task ShouldOnlyDisplayBookmarkedPosts() await Repository.StoreAsync(nonBookmarkedBlogPost); bookmarkService.GetBookmarkedPostIds().Returns(new List { bookmarkedBlogPost.Id }); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); ctx.Services.AddScoped(_ => bookmarkService); // Act @@ -47,6 +49,7 @@ public async Task ShouldRemoveBookmarkWhenButtonIsClicked() bookmarkService.GetBookmarkedPostIds().Returns(new List { bookmarkedBlogPost.Id }); bookmarkService.IsBookmarked(bookmarkedBlogPost.Id).Returns(true); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); ctx.Services.AddScoped(_ => bookmarkService); // Act @@ -69,6 +72,7 @@ public void ShouldDisplayMessageWhenNoBookmarksExist() var bookmarkService = Substitute.For(); bookmarkService.GetBookmarkedPostIds().Returns(new List()); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); ctx.Services.AddScoped(_ => bookmarkService); // Act diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Home/IndexTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Home/IndexTests.cs index b10faab0b..ad00c312f 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Home/IndexTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Home/IndexTests.cs @@ -1,8 +1,9 @@ -using System.Linq; +using System.Linq; using System.Threading.Tasks; using Bunit.Extensions.WaitForHelpers; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.TestUtilities; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web; using LinkDotNet.Blog.Web.Features.Bookmarks; using LinkDotNet.Blog.Web.Features.Components; @@ -222,6 +223,7 @@ private async Task CreatePublishedBlogPosts(int amount) private void RegisterComponents(BunitContext ctx, string? profilePictureUri = null, bool useMultiAuthorMode = false) { ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); ctx.Services.AddScoped(_ => Options.Create(CreateSampleAppConfiguration(profilePictureUri, useMultiAuthorMode).ApplicationConfiguration)); ctx.Services.AddScoped(_ => Options.Create(CreateSampleAppConfiguration(profilePictureUri, useMultiAuthorMode).Introduction)); ctx.Services.AddScoped(_ => Substitute.For()); diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Search/SearchPageTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Search/SearchPageTests.cs index 1d085f13e..7976f0d93 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Search/SearchPageTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Search/SearchPageTests.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.TestUtilities; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.Bookmarks; using LinkDotNet.Blog.Web.Features.Components; using LinkDotNet.Blog.Web.Features.Search; @@ -59,6 +60,7 @@ public async Task ShouldUnescapeQuery() private void RegisterServices(BunitContext ctx) { ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); ctx.Services.AddScoped(_ => Substitute.For()); } } diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/SearchByTag/SearchByTagPageTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/SearchByTag/SearchByTagPageTests.cs index 5628328e6..77f0b41bf 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/SearchByTag/SearchByTagPageTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/SearchByTag/SearchByTagPageTests.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.TestUtilities; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.Bookmarks; using LinkDotNet.Blog.Web.Features.SearchByTag; using Microsoft.AspNetCore.Components; @@ -48,6 +49,7 @@ public void ShouldSetTitleToTag() { using var ctx = new BunitContext(); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); ctx.ComponentFactories.Add(); var cut = ctx.Render(p => p.Add(s => s.Tag, "Tag")); @@ -67,6 +69,7 @@ private async Task AddBlogPostWithTagAsync(string tag, bool isPublished = true) private void RegisterServices(BunitContext ctx) { ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); ctx.Services.AddScoped(_ => Substitute.For()); } diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/ShowBlogPost/Components/SimilarBlogPostSectionTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/ShowBlogPost/Components/SimilarBlogPostSectionTests.cs index e273f7ffe..eccc6e681 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/ShowBlogPost/Components/SimilarBlogPostSectionTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/ShowBlogPost/Components/SimilarBlogPostSectionTests.cs @@ -1,8 +1,9 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.Infrastructure.Persistence; using LinkDotNet.Blog.Infrastructure.Persistence.Sql; using LinkDotNet.Blog.TestUtilities; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.ShowBlogPost.Components; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -32,6 +33,7 @@ public async Task ShouldShowSimilarBlogPosts() context.Services.AddScoped>(_ => new Repository(DbContextFactory, Substitute.For>>())); context.Services.AddScoped(_ => Repository); + context.Services.AddScoped(_ => new BlogPostRepository(Repository)); var cut = context.Render(p => p.Add(s => s.BlogPost, blogPost1)); diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/ShowBlogPost/ShowBlogPostPageTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/ShowBlogPost/ShowBlogPostPageTests.cs index 375dea42a..9cbd4e326 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/ShowBlogPost/ShowBlogPostPageTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/ShowBlogPost/ShowBlogPostPageTests.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Blazored.Toast.Services; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.Infrastructure; @@ -10,6 +10,7 @@ using LinkDotNet.Blog.Web.Features.Services.Tags; using LinkDotNet.Blog.Web.Features.ShowBlogPost; using LinkDotNet.Blog.Web.Features.ShowBlogPost.Components; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using Microsoft.EntityFrameworkCore; @@ -131,6 +132,7 @@ public async Task ShortCodesShouldBeReplacedByTheirContent() var returnValues = new PagedList([shortCode], 1, 1, 1); shortCodesRepository.GetAllAsync().Returns(returnValues); ctx.Services.AddScoped(_ => shortCodesRepository); + ctx.Services.AddScoped(_ => new ShortCodeRepository(shortCodesRepository)); var blogPost = new BlogPostBuilder().WithContent("This is a [[ONE]] shortcode").IsPublished().Build(); await Repository.StoreAsync(blogPost); @@ -212,6 +214,7 @@ public async Task ShouldRenderHeadingAnchorsWithFullPageUrl() private void RegisterComponents(BunitContext ctx, ILocalStorageService? localStorageService = null, bool useMultiAuthorMode = false) { ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(_ => new BlogPostRepository(Repository)); ctx.Services.AddScoped(_ => localStorageService ?? Substitute.For()); ctx.Services.AddScoped(_ => Substitute.For()); ctx.Services.AddScoped(_ => Substitute.For()); @@ -221,6 +224,7 @@ private void RegisterComponents(BunitContext ctx, ILocalStorageService? localSto var shortCodeRepository = Substitute.For>(); shortCodeRepository.GetAllAsync().Returns(PagedList.Empty); ctx.Services.AddScoped(_ => shortCodeRepository); + ctx.Services.AddScoped(_ => new ShortCodeRepository(shortCodeRepository)); ctx.Services.AddScoped(_ => Substitute.For()); } } diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/SimilarBlogPostJobTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/SimilarBlogPostJobTests.cs index 3c362cf7f..100bc55a2 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/SimilarBlogPostJobTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/SimilarBlogPostJobTests.cs @@ -1,9 +1,10 @@ -using System.Threading; +using System.Threading; using System.Threading.Tasks; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.Infrastructure.Persistence.Sql; using LinkDotNet.Blog.TestUtilities; using LinkDotNet.Blog.Web.Features; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.Extensions.Logging; using NCronJob; @@ -29,7 +30,7 @@ public async Task ShouldCalculateSimilarBlogPosts() await Repository.StoreAsync(blogPost2); await Repository.StoreAsync(blogPost3); - var job = new SimilarBlogPostJob(Repository, similarBlogPostRepository); + var job = new SimilarBlogPostJob(new BlogPostRepository(Repository), new SimilarBlogPostRepository(similarBlogPostRepository)); var context = Substitute.For(); context.Parameter.Returns(true); await job.RunAsync(context, CancellationToken.None); @@ -48,7 +49,7 @@ public async Task ShouldNotCalculateWhenNotTriggeredAsInstantJob() await Repository.StoreAsync(blogPost2); await Repository.StoreAsync(blogPost3); - var job = new SimilarBlogPostJob(Repository, similarBlogPostRepository); + var job = new SimilarBlogPostJob(new BlogPostRepository(Repository), new SimilarBlogPostRepository(similarBlogPostRepository)); await job.RunAsync(Substitute.For(), CancellationToken.None); var similarBlogPosts = await similarBlogPostRepository.GetAllAsync(); diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/TransformBlogPostRecordsJobTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/TransformBlogPostRecordsJobTests.cs index e8ee47c46..035cd2c03 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/TransformBlogPostRecordsJobTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/TransformBlogPostRecordsJobTests.cs @@ -6,6 +6,7 @@ using LinkDotNet.Blog.Infrastructure.Persistence.Sql; using LinkDotNet.Blog.TestUtilities; using LinkDotNet.Blog.Web.Features; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.Extensions.Logging; using NCronJob; using TestContext = Xunit.TestContext; @@ -26,9 +27,8 @@ public TransformBlogPostRecordsJobTests() new Repository(DbContextFactory, Substitute.For>>()); sut = new TransformBlogPostRecordsJob( - Repository, - userRecordRepository, - blogPostRecordRepository, + new BlogPostRepository(Repository), + new AnalyticsRepository(userRecordRepository, blogPostRecordRepository), Substitute.For>()); } diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Admin/BlogPostAdminActionsTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Admin/BlogPostAdminActionsTests.cs index 09acb1430..52fd0808e 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Admin/BlogPostAdminActionsTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Admin/BlogPostAdminActionsTests.cs @@ -3,6 +3,7 @@ using Blazored.Toast.Services; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.Services.Tags; using LinkDotNet.Blog.Web.Features.ShowBlogPost.Components; using Microsoft.Extensions.DependencyInjection; @@ -16,7 +17,9 @@ public class BlogPostAdminActionsTests : BunitContext public BlogPostAdminActionsTests() { - Services.AddSingleton(Substitute.For>()); + var repositoryMock = Substitute.For>(); + Services.AddSingleton(repositoryMock); + Services.AddScoped(_ => new BlogPostRepository(repositoryMock)); Services.AddSingleton(Substitute.For()); Services.AddSingleton(Substitute.For()); tagQueryService.ClearTagCacheAsync().Returns(Task.CompletedTask); diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/ProfileTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/ProfileTests.cs index 344233673..419bc46af 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/ProfileTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/ProfileTests.cs @@ -5,6 +5,7 @@ using LinkDotNet.Blog.Infrastructure; using LinkDotNet.Blog.Infrastructure.Persistence; using LinkDotNet.Blog.TestUtilities; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.AboutMe.Components; using LinkDotNet.Blog.Web.Features.Components; using LinkDotNet.Blog.Web.Features.Services; @@ -166,6 +167,7 @@ private static void SetupGetAll( var repoMock = Substitute.For>(); var calcMock = Substitute.For(); Services.AddScoped(_ => repoMock); + Services.AddScoped(); Services.AddScoped(_ => calcMock); repoMock.GetAllAsync( Arg.Any>>(), diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Services/SitemapServiceTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Services/SitemapServiceTests.cs index 2a06ac8c8..21e066cba 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Services/SitemapServiceTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Services/SitemapServiceTests.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.TestUtilities; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.Admin.Sitemap.Services; namespace LinkDotNet.Blog.IntegrationTests.Web.Shared.Services; @@ -11,7 +12,7 @@ public sealed class SitemapServiceTests : SqlDatabaseTestBase private readonly SitemapService sut; public SitemapServiceTests() - => sut = new SitemapService(Repository); + => sut = new SitemapService(new BlogPostRepository(Repository)); [Fact] public async Task ShouldSaveSitemapUrlInCorrectFormat() diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Skills/SkillTableTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Skills/SkillTableTests.cs index 76049a479..04277a6d4 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Skills/SkillTableTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Skills/SkillTableTests.cs @@ -4,6 +4,7 @@ using Blazored.Toast.Services; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.TestUtilities; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.AboutMe.Components.Skill; using Microsoft.Extensions.DependencyInjection; using TestContext = Xunit.TestContext; @@ -19,6 +20,7 @@ public async Task ShouldDeleteItem() using var ctx = new BunitContext(); await Repository.StoreAsync(skill); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(); ctx.Services.AddScoped(_ => Substitute.For()); var cut = ctx.Render(p => p.Add(s => s.ShowAdminActions, true)); @@ -35,6 +37,7 @@ public async Task ShouldAddSkill() { using var ctx = new BunitContext(); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(); ctx.Services.AddScoped(_ => Substitute.For()); var cut = ctx.Render(p => p.Add(s => s.ShowAdminActions, true)); @@ -63,6 +66,7 @@ public async Task ShouldNotAllowToEditSkillTagsWhenNotAdmin() var skill = new SkillBuilder().Build(); await Repository.StoreAsync(skill); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(); ctx.Services.AddScoped(_ => Substitute.For()); var cut = ctx.Render(p => @@ -79,6 +83,7 @@ public async Task ShouldUpdateProficiencyWhenSkillTagDragged() await DbContext.AddAsync(skill, TestContext.Current.CancellationToken); await DbContext.SaveChangesAsync(TestContext.Current.CancellationToken); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(); ctx.Services.AddScoped(_ => Substitute.For()); var cut = ctx.Render(p => p.Add(s => s.ShowAdminActions, true)); @@ -100,6 +105,7 @@ public async Task ShouldStayOnSameProficiencyWhenDroppedOnSameProficiencyLevel() await DbContext.AddAsync(skill, TestContext.Current.CancellationToken); await DbContext.SaveChangesAsync(TestContext.Current.CancellationToken); ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped(); ctx.Services.AddScoped(_ => Substitute.For()); var cut = ctx.Render(p => p.Add(s => s.ShowAdminActions, true)); diff --git a/tests/LinkDotNet.Blog.TestUtilities/RepositoryRegistrationExtensions.cs b/tests/LinkDotNet.Blog.TestUtilities/RepositoryRegistrationExtensions.cs new file mode 100644 index 000000000..5bccbe77a --- /dev/null +++ b/tests/LinkDotNet.Blog.TestUtilities/RepositoryRegistrationExtensions.cs @@ -0,0 +1,65 @@ +using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.Web.Features.Repositories; +using Microsoft.Extensions.DependencyInjection; + +namespace LinkDotNet.Blog.TestUtilities; + +public static class RepositoryRegistrationExtensions +{ + public static IServiceCollection RegisterBlogPostRepository(this IServiceCollection services, IRepository repository) + { + services.AddScoped(_ => repository); + services.AddScoped(_ => new BlogPostRepository(repository)); + return services; + } + + public static IServiceCollection RegisterShortCodeRepository(this IServiceCollection services, IRepository repository) + { + services.AddScoped(_ => repository); + services.AddScoped(_ => new ShortCodeRepository(repository)); + return services; + } + + public static IServiceCollection RegisterBlogPostTemplateRepository(this IServiceCollection services, IRepository repository) + { + services.AddScoped(_ => repository); + services.AddScoped(_ => new BlogPostTemplateRepository(repository)); + return services; + } + + public static IServiceCollection RegisterBrokenLinkRepository(this IServiceCollection services, IRepository repository) + { + services.AddScoped(_ => repository); + services.AddScoped(_ => new BrokenLinkRepository(repository)); + return services; + } + + public static IServiceCollection RegisterSimilarBlogPostRepository(this IServiceCollection services, IRepository repository) + { + services.AddScoped(_ => repository); + services.AddScoped(_ => new SimilarBlogPostRepository(repository)); + return services; + } + + public static IServiceCollection RegisterProfileInformationEntryRepository(this IServiceCollection services, IRepository repository) + { + services.AddScoped(_ => repository); + services.AddScoped(); + return services; + } + + public static IServiceCollection RegisterSkillRepository(this IServiceCollection services, IRepository repository) + { + services.AddScoped(_ => repository); + services.AddScoped(); + return services; + } + + public static IServiceCollection RegisterTalkRepository(this IServiceCollection services, IRepository repository) + { + services.AddScoped(_ => repository); + services.AddScoped(); + return services; + } +} diff --git a/tests/LinkDotNet.Blog.UnitTests/RepositoryTestExtensions.cs b/tests/LinkDotNet.Blog.UnitTests/RepositoryTestExtensions.cs index da5d1e061..aa7070fff 100644 --- a/tests/LinkDotNet.Blog.UnitTests/RepositoryTestExtensions.cs +++ b/tests/LinkDotNet.Blog.UnitTests/RepositoryTestExtensions.cs @@ -3,6 +3,7 @@ using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.Infrastructure; using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.TestUtilities; using Microsoft.Extensions.DependencyInjection; namespace LinkDotNet.Blog.UnitTests; @@ -22,5 +23,38 @@ public static void RegisterRepositoryWithEmptyReturn(this IServiceColle .Returns(new PagedList([], 0, 1, 1)); collection.AddScoped(_ => repositoryMock); + + if (typeof(TEntity) == typeof(BlogPost)) + { + collection.RegisterBlogPostRepository((IRepository)repositoryMock); + } + else if (typeof(TEntity) == typeof(ShortCode)) + { + collection.RegisterShortCodeRepository((IRepository)repositoryMock); + } + else if (typeof(TEntity) == typeof(BlogPostTemplate)) + { + collection.RegisterBlogPostTemplateRepository((IRepository)repositoryMock); + } + else if (typeof(TEntity) == typeof(BrokenLink)) + { + collection.RegisterBrokenLinkRepository((IRepository)repositoryMock); + } + else if (typeof(TEntity) == typeof(SimilarBlogPost)) + { + collection.RegisterSimilarBlogPostRepository((IRepository)repositoryMock); + } + else if (typeof(TEntity) == typeof(ProfileInformationEntry)) + { + collection.RegisterProfileInformationEntryRepository((IRepository)repositoryMock); + } + else if (typeof(TEntity) == typeof(Skill)) + { + collection.RegisterSkillRepository((IRepository)repositoryMock); + } + else if (typeof(TEntity) == typeof(Talk)) + { + collection.RegisterTalkRepository((IRepository)repositoryMock); + } } } diff --git a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/BlogPostEditor/Components/CreateNewBlogPostTests.cs b/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/BlogPostEditor/Components/CreateNewBlogPostTests.cs index a4d2578c1..beb1efa0b 100644 --- a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/BlogPostEditor/Components/CreateNewBlogPostTests.cs +++ b/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/BlogPostEditor/Components/CreateNewBlogPostTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Threading.Tasks; using AngleSharp.Html.Dom; @@ -11,6 +11,7 @@ using LinkDotNet.Blog.Web; using LinkDotNet.Blog.Web.Features.Admin.BlogPostEditor.Components; using LinkDotNet.Blog.Web.Features.Admin.BlogPostEditor.Services; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.Components; using LinkDotNet.Blog.Web.Features.Services; using LinkDotNet.Blog.Web.Features.Services.Tags; @@ -32,10 +33,12 @@ public CreateNewBlogPostTests() var shortCodeRepository = Substitute.For>(); shortCodeRepository.GetAllAsync().Returns(PagedList.Empty); Services.AddScoped(_ => shortCodeRepository); + Services.AddScoped(_ => new ShortCodeRepository(shortCodeRepository)); var templateRepository = Substitute.For>(); templateRepository.GetAllAsync().Returns(PagedList.Empty); Services.AddScoped(_ => templateRepository); + Services.AddScoped(_ => new BlogPostTemplateRepository(templateRepository)); JSInterop.SetupVoid("hljs.highlightAll"); ComponentFactories.Add(); diff --git a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/BrokenLinks/BrokenLinksPageTests.cs b/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/BrokenLinks/BrokenLinksPageTests.cs index 366264f43..829b6e200 100644 --- a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/BrokenLinks/BrokenLinksPageTests.cs +++ b/tests/LinkDotNet.Blog.UnitTests/Web/Features/Admin/BrokenLinks/BrokenLinksPageTests.cs @@ -1,9 +1,10 @@ -using System; +using System; using System.Linq; using System.Linq.Expressions; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.Infrastructure; using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.TestUtilities; using LinkDotNet.Blog.Web.Features.Admin.BrokenLinks; using Microsoft.Extensions.DependencyInjection; @@ -17,7 +18,7 @@ public class BrokenLinksPageTests : BunitContext public BrokenLinksPageTests() { - Services.AddScoped(_ => repository); + Services.RegisterBrokenLinkRepository(repository); } [Fact] diff --git a/tests/LinkDotNet.Blog.UnitTests/Web/Features/SearchByTag/SearchByTagPageTests.cs b/tests/LinkDotNet.Blog.UnitTests/Web/Features/SearchByTag/SearchByTagPageTests.cs index fa2fe4a80..c787a5f66 100644 --- a/tests/LinkDotNet.Blog.UnitTests/Web/Features/SearchByTag/SearchByTagPageTests.cs +++ b/tests/LinkDotNet.Blog.UnitTests/Web/Features/SearchByTag/SearchByTagPageTests.cs @@ -1,6 +1,7 @@ -using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.Infrastructure; using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.Components; using LinkDotNet.Blog.Web.Features.SearchByTag; using Microsoft.Extensions.DependencyInjection; @@ -17,6 +18,7 @@ public void ShouldNotIndexTagPages() var repositoryMock = Substitute.For>(); repositoryMock.GetAllAsync().ReturnsForAnyArgs(PagedList.Empty); Services.AddScoped(_ => repositoryMock); + Services.AddScoped(_ => new BlogPostRepository(repositoryMock)); var cut = Render(p => p.Add(s => s.Tag, "C%23")); diff --git a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Services/Tags/TagQueryServiceTests.cs b/tests/LinkDotNet.Blog.UnitTests/Web/Features/Services/Tags/TagQueryServiceTests.cs index 5a66548d0..8fb0db754 100644 --- a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Services/Tags/TagQueryServiceTests.cs +++ b/tests/LinkDotNet.Blog.UnitTests/Web/Features/Services/Tags/TagQueryServiceTests.cs @@ -1,8 +1,8 @@ using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.Infrastructure; -using LinkDotNet.Blog.Infrastructure.Persistence; using LinkDotNet.Blog.TestUtilities; using LinkDotNet.Blog.Web; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.Services.Tags; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; @@ -20,13 +20,13 @@ namespace LinkDotNet.Blog.UnitTests.Web.Features.Services.Tags; public sealed class TagQueryServiceTests { - private readonly IRepository repository; + private readonly IBlogPostRepository repository; private readonly TagQueryService tagQueryService; private readonly IFusionCache fusionCache; public TagQueryServiceTests() { - repository = Substitute.For>(); + repository = Substitute.For(); fusionCache = new FusionCache( new FusionCacheOptions(), diff --git a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Services/UserRecordServiceTests.cs b/tests/LinkDotNet.Blog.UnitTests/Web/Features/Services/UserRecordServiceTests.cs index 748fd29f0..c3112e42e 100644 --- a/tests/LinkDotNet.Blog.UnitTests/Web/Features/Services/UserRecordServiceTests.cs +++ b/tests/LinkDotNet.Blog.UnitTests/Web/Features/Services/UserRecordServiceTests.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; using LinkDotNet.Blog.Domain; -using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.Services; using Microsoft.Extensions.Logging; @@ -9,14 +9,14 @@ namespace LinkDotNet.Blog.UnitTests.Web.Features.Services; public class UserRecordServiceTests : BunitContext { - private readonly IRepository repositoryMock; + private readonly IAnalyticsRepository repositoryMock; private readonly BunitNavigationManager fakeNavigationManager; private readonly BunitAuthenticationStateProvider fakeAuthenticationStateProvider; private readonly UserRecordService sut; public UserRecordServiceTests() { - repositoryMock = Substitute.For>(); + repositoryMock = Substitute.For(); fakeNavigationManager = new BunitNavigationManager(this); fakeAuthenticationStateProvider = new BunitAuthenticationStateProvider(); sut = new UserRecordService( @@ -33,7 +33,7 @@ public async Task ShouldStoreInformation() const string url = "http://localhost/subpart"; fakeNavigationManager.NavigateTo(url); UserRecord? recordToDb = null; - repositoryMock.When(r => r.StoreAsync(Arg.Any())) + repositoryMock.When(r => r.StoreUserRecordAsync(Arg.Any())) .Do(call => recordToDb = call.Arg()); await sut.StoreUserRecordAsync(); @@ -50,7 +50,7 @@ public async Task ShouldNotStoreForAdmin() await sut.StoreUserRecordAsync(); - await repositoryMock.Received(0).StoreAsync(Arg.Any()); + await repositoryMock.Received(0).StoreUserRecordAsync(Arg.Any()); } [InlineData("http://localhost/blogPost/12?q=3", "blogPost/12")] @@ -62,7 +62,7 @@ public async Task ShouldRemoveQueryStringIfPresent(string url, string expectedRe { fakeNavigationManager.NavigateTo(url); UserRecord? recordToDb = null; - repositoryMock.When(r => r.StoreAsync(Arg.Any())) + repositoryMock.When(r => r.StoreUserRecordAsync(Arg.Any())) .Do(call => recordToDb = call.Arg()); await sut.StoreUserRecordAsync(); diff --git a/tests/LinkDotNet.Blog.UnitTests/Web/Features/ShowBlogPost/ShowBlogPostPageTests.cs b/tests/LinkDotNet.Blog.UnitTests/Web/Features/ShowBlogPost/ShowBlogPostPageTests.cs index 68afbee1d..32a62224f 100644 --- a/tests/LinkDotNet.Blog.UnitTests/Web/Features/ShowBlogPost/ShowBlogPostPageTests.cs +++ b/tests/LinkDotNet.Blog.UnitTests/Web/Features/ShowBlogPost/ShowBlogPostPageTests.cs @@ -11,6 +11,7 @@ using LinkDotNet.Blog.Web.Features.Services.Tags; using LinkDotNet.Blog.Web.Features.ShowBlogPost; using LinkDotNet.Blog.Web.Features.ShowBlogPost.Components; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using Microsoft.Extensions.DependencyInjection; @@ -29,6 +30,7 @@ public ShowBlogPostPageTests() var shortCodeRepository = Substitute.For>(); shortCodeRepository.GetAllAsync().Returns(PagedList.Empty); Services.AddScoped(_ => shortCodeRepository); + Services.AddScoped(_ => new ShortCodeRepository(shortCodeRepository)); Services.AddScoped(_ => Substitute.For()); Services.AddScoped(_ => Substitute.For()); Services.AddScoped(_ => Substitute.For()); @@ -47,6 +49,7 @@ public void ShouldShowLoadingAnimation() const string blogPostId = "2"; var repositoryMock = Substitute.For>(); Services.AddScoped(_ => repositoryMock); + Services.AddScoped(_ => new BlogPostRepository(repositoryMock)); repositoryMock.GetByIdAsync(blogPostId)! .Returns(new ValueTask(Task.Run(async () => { @@ -69,6 +72,7 @@ public void ShouldTrackVisitOnlyOncePerPageView() var repositoryMock = Substitute.For>(); repositoryMock.GetByIdAsync("1").Returns(new BlogPostBuilder().Build()); Services.AddScoped(_ => repositoryMock); + Services.AddScoped(_ => new BlogPostRepository(repositoryMock)); var cut = Render( p => p.Add(s => s.BlogPostId, "1")); @@ -83,6 +87,7 @@ public void ShouldSetTitleToTag() var blogPost = new BlogPostBuilder().WithTitle("Title").Build(); repositoryMock.GetByIdAsync("1").Returns(blogPost); Services.AddScoped(_ => repositoryMock); + Services.AddScoped(_ => new BlogPostRepository(repositoryMock)); var cut = Render( p => p.Add(s => s.BlogPostId, "1")); @@ -104,6 +109,7 @@ public void ShouldUseFallbackAsOgDataIfAvailable(string preview, string? fallbac .Build(); repositoryMock.GetByIdAsync("1").Returns(blogPost); Services.AddScoped(_ => repositoryMock); + Services.AddScoped(_ => new BlogPostRepository(repositoryMock)); var cut = Render( p => p.Add(s => s.BlogPostId, "1")); @@ -120,6 +126,7 @@ public void ShowTagWithLinksWhenAvailable() .Build(); repositoryMock.GetByIdAsync("1").Returns(blogPost); Services.AddScoped(_ => repositoryMock); + Services.AddScoped(_ => new BlogPostRepository(repositoryMock)); var cut = Render( p => p.Add(s => s.BlogPostId, "1")); @@ -137,6 +144,7 @@ public void ShowNotShowTagsWhenNotSet() .Build(); repositoryMock.GetByIdAsync("1").Returns(blogPost); Services.AddScoped(_ => repositoryMock); + Services.AddScoped(_ => new BlogPostRepository(repositoryMock)); var cut = Render( p => p.Add(s => s.BlogPostId, "1")); @@ -157,6 +165,7 @@ public void ShowReadingIndicatorWhenEnabled(bool isEnabled) .Build(); repositoryMock.GetByIdAsync("1").Returns(blogPost); Services.AddScoped(_ => repositoryMock); + Services.AddScoped(_ => new BlogPostRepository(repositoryMock)); Services.AddScoped(_ => Options.Create(appConfiguration)); var cut = Render( @@ -175,6 +184,7 @@ public void ShouldSetCanoncialUrlOfOgDataWithoutSlug() blogPost.Id = "1"; repositoryMock.GetByIdAsync("1").Returns(blogPost); Services.AddScoped(_ => repositoryMock); + Services.AddScoped(_ => new BlogPostRepository(repositoryMock)); var cut = Render( p => p.Add(s => s.BlogPostId, "1")); @@ -194,6 +204,7 @@ public void ShouldRenderStructuredDataIntoPageBody() blogPost.Id = "1"; repositoryMock.GetByIdAsync("1").Returns(blogPost); Services.AddScoped(_ => repositoryMock); + Services.AddScoped(_ => new BlogPostRepository(repositoryMock)); var cut = Render( p => p.Add(s => s.BlogPostId, "1")); @@ -212,6 +223,7 @@ public void ShouldShowAuthorNameWhenUseMultiAuthorModeIsTrue() blogPost.Id = "1"; repositoryMock.GetByIdAsync("1").Returns(blogPost); Services.AddScoped(_ => repositoryMock); + Services.AddScoped(_ => new BlogPostRepository(repositoryMock)); Services.AddScoped(_ => Options.Create(new ApplicationConfigurationBuilder().WithUseMultiAuthorMode(true).Build())); var cut = Render( @@ -231,6 +243,7 @@ public void ShouldNotShowAuthorNameWhenUseMultiAuthorModeIsFalse() blogPost.Id = "1"; repositoryMock.GetByIdAsync("1").Returns(blogPost); Services.AddScoped(_ => repositoryMock); + Services.AddScoped(_ => new BlogPostRepository(repositoryMock)); Services.AddScoped(_ => Options.Create(new ApplicationConfigurationBuilder().WithUseMultiAuthorMode(false).Build())); var cut = Render( @@ -248,6 +261,7 @@ public void ShouldNotShowAuthorNameWhenAuthorNameIsNull() blogPost.Id = "1"; repositoryMock.GetByIdAsync("1").Returns(blogPost); Services.AddScoped(_ => repositoryMock); + Services.AddScoped(_ => new BlogPostRepository(repositoryMock)); Services.AddScoped(_ => Options.Create(new ApplicationConfigurationBuilder().WithUseMultiAuthorMode(true).Build())); var cut = Render( From 4ed06af4e0bc339189e2d6a7b8fa42f812e2bd33 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 19 Sep 2026 20:25:28 +0000 Subject: [PATCH 2/2] test: finish typed repository migration coverage Co-authored-by: linkdotnet <26365461+linkdotnet@users.noreply.github.com> --- .../Features/AboutMe/Components/TalksTests.cs | 9 +++++- .../Components/VisitCountPerPageTests.cs | 11 +++++-- .../Web/Features/Archive/ArchivePageTests.cs | 1 + .../Components/SimilarBlogPostSectionTests.cs | 5 ++-- .../StorageProviderExtensionsTests.cs | 5 ++-- .../Shared/Admin/BlogPostAdminActionsTests.cs | 2 ++ .../Web/Shared/ProfileTests.cs | 4 +++ .../Web/Shared/Skills/SkillTableTests.cs | 30 ++++++++++++------- 8 files changed, 49 insertions(+), 18 deletions(-) diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/AboutMe/Components/TalksTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/AboutMe/Components/TalksTests.cs index f71693e62..58c7994b9 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/AboutMe/Components/TalksTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/AboutMe/Components/TalksTests.cs @@ -1,6 +1,8 @@ using System; using System.Threading.Tasks; using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.Infrastructure.Persistence.Sql; using LinkDotNet.Blog.TestUtilities; using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.TestUtilities.Fakes; @@ -8,6 +10,7 @@ using LinkDotNet.Blog.Web.Features.Components; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using TestContext = Xunit.TestContext; namespace LinkDotNet.Blog.IntegrationTests.Web.Features.AboutMe.Components; @@ -18,7 +21,11 @@ public sealed class TalksTests : SqlDatabaseTestBase, IDisposable public TalksTests() { - ctx.Services.AddScoped(_ => Repository); + ctx.Services.AddScoped>(_ => Repository); + ctx.Services.AddScoped>(_ => + new Repository(DbContextFactory, Substitute.For>>())); + ctx.Services.AddScoped>(_ => + new Repository(DbContextFactory, Substitute.For>>())); ctx.Services.AddScoped(); ctx.ComponentFactories.Add(); } diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/Dashboard/Components/VisitCountPerPageTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/Dashboard/Components/VisitCountPerPageTests.cs index 483333d7e..930c2085f 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/Dashboard/Components/VisitCountPerPageTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Admin/Dashboard/Components/VisitCountPerPageTests.cs @@ -7,6 +7,7 @@ using LinkDotNet.Blog.Infrastructure.Persistence.Sql; using LinkDotNet.Blog.TestUtilities; using LinkDotNet.Blog.Web.Features.Admin.Dashboard.Components; +using LinkDotNet.Blog.Web.Features.Repositories; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -169,8 +170,14 @@ public async Task GivenDeletedBlogPostWhenEncounteringBlogPostRecordThenNoError( private void RegisterRepositories(BunitContext ctx) { - ctx.Services.AddScoped>(_ => new Repository(DbContextFactory, Substitute.For>>())); - ctx.Services.AddScoped>(_ => new Repository(DbContextFactory, Substitute.For>>())); + var blogPostRepository = new Repository(DbContextFactory, Substitute.For>>()); + var blogPostRecordRepository = new Repository(DbContextFactory, Substitute.For>>()); + var userRecordRepository = new Repository(DbContextFactory, Substitute.For>>()); + ctx.Services.AddScoped>(_ => blogPostRepository); + ctx.Services.AddScoped>(_ => blogPostRecordRepository); + ctx.Services.AddScoped>(_ => userRecordRepository); + ctx.Services.AddScoped(_ => new BlogPostRepository(blogPostRepository)); + ctx.Services.AddScoped(_ => new AnalyticsRepository(userRecordRepository, blogPostRecordRepository)); } private async Task SaveBlogPostArticleClicked(string blogPostId, int count) diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Archive/ArchivePageTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Archive/ArchivePageTests.cs index 4ea081905..b2deb6a85 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Archive/ArchivePageTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/Archive/ArchivePageTests.cs @@ -78,6 +78,7 @@ public void ShouldShowLoading() { using var ctx = new BunitContext(); ctx.Services.AddScoped>(_ => new SlowRepository()); + ctx.Services.AddScoped(_ => new BlogPostRepository(new SlowRepository())); var cut = ctx.Render(); diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/ShowBlogPost/Components/SimilarBlogPostSectionTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/ShowBlogPost/Components/SimilarBlogPostSectionTests.cs index eccc6e681..f3b1d621a 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/ShowBlogPost/Components/SimilarBlogPostSectionTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Features/ShowBlogPost/Components/SimilarBlogPostSectionTests.cs @@ -30,8 +30,9 @@ public async Task ShouldShowSimilarBlogPosts() await DbContext.SimilarBlogPosts.AddAsync(similarBlogPost1, TestContext.Current.CancellationToken); await DbContext.SaveChangesAsync(TestContext.Current.CancellationToken); await using var context = new BunitContext(); - context.Services.AddScoped>(_ => - new Repository(DbContextFactory, Substitute.For>>())); + var similarBlogPostRepository = new Repository(DbContextFactory, Substitute.For>>()); + context.Services.AddScoped>(_ => similarBlogPostRepository); + context.Services.AddScoped(_ => new SimilarBlogPostRepository(similarBlogPostRepository)); context.Services.AddScoped(_ => Repository); context.Services.AddScoped(_ => new BlogPostRepository(Repository)); diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/RegistrationExtensions/StorageProviderExtensionsTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/RegistrationExtensions/StorageProviderExtensionsTests.cs index cabc1e403..069836373 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/RegistrationExtensions/StorageProviderExtensionsTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/RegistrationExtensions/StorageProviderExtensionsTests.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.Linq; using LinkDotNet.Blog.Domain; using LinkDotNet.Blog.Infrastructure.Persistence; using LinkDotNet.Blog.TestUtilities; +using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.RegistrationExtensions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -57,7 +58,7 @@ public void ShouldHaveCacheRepositoryOnlyForBlogPosts() collection.AddStorageProvider(config); var serviceProvider = collection.BuildServiceProvider(); - serviceProvider.GetService>().ShouldBeOfType>(); + serviceProvider.GetService().ShouldBeOfType(); serviceProvider.GetService>().ShouldNotBeOfType>(); } } diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Admin/BlogPostAdminActionsTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Admin/BlogPostAdminActionsTests.cs index 52fd0808e..084a707e7 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Admin/BlogPostAdminActionsTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Admin/BlogPostAdminActionsTests.cs @@ -33,6 +33,7 @@ public async Task ShouldDeleteBlogPostWhenOkClicked() const string blogPostId = "2"; var repositoryMock = Substitute.For>(); Services.AddSingleton(repositoryMock); + Services.AddScoped(_ => new BlogPostRepository(repositoryMock)); var cut = Render(s => s.Add(p => p.BlogPostId, blogPostId)); await cut.Find("#delete-blogpost").ClickAsync(); @@ -71,6 +72,7 @@ public async Task ShouldNotDeleteBlogPostWhenCancelClicked() var repositoryMock = Substitute.For>(); Services.AddSingleton(repositoryMock); + Services.AddScoped(_ => new BlogPostRepository(repositoryMock)); var cut = Render(s => s.Add(p => p.BlogPostId, blogPostId)); await cut.Find("#delete-blogpost").ClickAsync(); diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/ProfileTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/ProfileTests.cs index 419bc46af..23d82b394 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/ProfileTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/ProfileTests.cs @@ -165,8 +165,12 @@ private static void SetupGetAll( private (IRepository repoMock, ISortOrderCalculator calcMock) RegisterServices() { var repoMock = Substitute.For>(); + var skillRepository = Substitute.For>(); + var talkRepository = Substitute.For>(); var calcMock = Substitute.For(); Services.AddScoped(_ => repoMock); + Services.AddScoped(_ => skillRepository); + Services.AddScoped(_ => talkRepository); Services.AddScoped(); Services.AddScoped(_ => calcMock); repoMock.GetAllAsync( diff --git a/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Skills/SkillTableTests.cs b/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Skills/SkillTableTests.cs index 04277a6d4..b3c6aed7c 100644 --- a/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Skills/SkillTableTests.cs +++ b/tests/LinkDotNet.Blog.IntegrationTests/Web/Shared/Skills/SkillTableTests.cs @@ -3,10 +3,13 @@ using AngleSharp.Dom; using Blazored.Toast.Services; using LinkDotNet.Blog.Domain; +using LinkDotNet.Blog.Infrastructure.Persistence; +using LinkDotNet.Blog.Infrastructure.Persistence.Sql; using LinkDotNet.Blog.TestUtilities; using LinkDotNet.Blog.Web.Features.Repositories; using LinkDotNet.Blog.Web.Features.AboutMe.Components.Skill; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using TestContext = Xunit.TestContext; namespace LinkDotNet.Blog.IntegrationTests.Web.Shared.Skills; @@ -19,8 +22,7 @@ public async Task ShouldDeleteItem() var skill = new SkillBuilder().WithSkillName("C#").Build(); using var ctx = new BunitContext(); await Repository.StoreAsync(skill); - ctx.Services.AddScoped(_ => Repository); - ctx.Services.AddScoped(); + RegisterAboutMeRepository(ctx); ctx.Services.AddScoped(_ => Substitute.For()); var cut = ctx.Render(p => p.Add(s => s.ShowAdminActions, true)); @@ -36,8 +38,7 @@ public async Task ShouldDeleteItem() public async Task ShouldAddSkill() { using var ctx = new BunitContext(); - ctx.Services.AddScoped(_ => Repository); - ctx.Services.AddScoped(); + RegisterAboutMeRepository(ctx); ctx.Services.AddScoped(_ => Substitute.For()); var cut = ctx.Render(p => p.Add(s => s.ShowAdminActions, true)); @@ -65,8 +66,7 @@ public async Task ShouldNotAllowToEditSkillTagsWhenNotAdmin() using var ctx = new BunitContext(); var skill = new SkillBuilder().Build(); await Repository.StoreAsync(skill); - ctx.Services.AddScoped(_ => Repository); - ctx.Services.AddScoped(); + RegisterAboutMeRepository(ctx); ctx.Services.AddScoped(_ => Substitute.For()); var cut = ctx.Render(p => @@ -82,8 +82,7 @@ public async Task ShouldUpdateProficiencyWhenSkillTagDragged() var skill = new SkillBuilder().WithProficiencyLevel(ProficiencyLevel.Familiar).Build(); await DbContext.AddAsync(skill, TestContext.Current.CancellationToken); await DbContext.SaveChangesAsync(TestContext.Current.CancellationToken); - ctx.Services.AddScoped(_ => Repository); - ctx.Services.AddScoped(); + RegisterAboutMeRepository(ctx); ctx.Services.AddScoped(_ => Substitute.For()); var cut = ctx.Render(p => p.Add(s => s.ShowAdminActions, true)); @@ -104,8 +103,7 @@ public async Task ShouldStayOnSameProficiencyWhenDroppedOnSameProficiencyLevel() var skill = new SkillBuilder().WithProficiencyLevel(ProficiencyLevel.Familiar).Build(); await DbContext.AddAsync(skill, TestContext.Current.CancellationToken); await DbContext.SaveChangesAsync(TestContext.Current.CancellationToken); - ctx.Services.AddScoped(_ => Repository); - ctx.Services.AddScoped(); + RegisterAboutMeRepository(ctx); ctx.Services.AddScoped(_ => Substitute.For()); var cut = ctx.Render(p => p.Add(s => s.ShowAdminActions, true)); @@ -118,4 +116,14 @@ public async Task ShouldStayOnSameProficiencyWhenDroppedOnSameProficiencyLevel() skillFromDb.ShouldNotBeNull(); skillFromDb.ProficiencyLevel.ShouldBe(ProficiencyLevel.Familiar); } -} \ No newline at end of file + + private void RegisterAboutMeRepository(BunitContext ctx) + { + ctx.Services.AddScoped>(_ => Repository); + ctx.Services.AddScoped>(_ => + new Repository(DbContextFactory, Substitute.For>>())); + ctx.Services.AddScoped>(_ => + new Repository(DbContextFactory, Substitute.For>>())); + ctx.Services.AddScoped(); + } +}