Skip to content

Use OPEN_JSON for persisting tags #252

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 5 additions & 20 deletions src/LinkDotNet.Blog.Domain/BlogPost.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

namespace LinkDotNet.Blog.Domain;
Expand All @@ -24,15 +25,15 @@ private BlogPost()

public DateTime? ScheduledPublishDate { get; private set; }

public ICollection<Tag> Tags { get; private set; }
public IReadOnlyCollection<string> Tags { get; private set; }

public bool IsPublished { get; private set; }

public int Likes { get; set; }

public bool IsScheduled => ScheduledPublishDate is not null;

public string TagsAsString => Tags is null ? string.Empty : string.Join(", ", Tags.Select(t => t.Content));
public string TagsAsString => Tags is null ? string.Empty : string.Join(", ", Tags);

public static BlogPost Create(
string title,
Expand Down Expand Up @@ -62,7 +63,7 @@ public static BlogPost Create(
PreviewImageUrl = previewImageUrl,
PreviewImageUrlFallback = previewImageUrlFallback,
IsPublished = isPublished,
Tags = tags?.Select(Tag.Create).ToList(),
Tags = tags?.Select(t => t.Trim()).ToImmutableArray(),
};

return blogPost;
Expand All @@ -89,22 +90,6 @@ public void Update(BlogPost from)
PreviewImageUrl = from.PreviewImageUrl;
PreviewImageUrlFallback = from.PreviewImageUrlFallback;
IsPublished = from.IsPublished;
ReplaceTags(from.Tags);
}

private void ReplaceTags(IEnumerable<Tag> tags)
{
Tags?.Clear();
if (Tags == null || tags == null)
{
Tags = tags?.ToList();
}
else
{
foreach (var tag in tags)
{
Tags.Add(tag);
}
}
Tags = from.Tags;
}
}
24 changes: 0 additions & 24 deletions src/LinkDotNet.Blog.Domain/Tag.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ public BlogDbContext(DbContextOptions options)

public DbSet<BlogPost> BlogPosts { get; set; }

public DbSet<Tag> Tags { get; set; }

public DbSet<ProfileInformationEntry> ProfileInformationEntries { get; set; }

public DbSet<Skill> Skills { get; set; }
Expand All @@ -27,7 +25,6 @@ public BlogDbContext(DbContextOptions options)
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new BlogPostConfiguration());
modelBuilder.ApplyConfiguration(new TagsConfiguration());
modelBuilder.ApplyConfiguration(new ProfileInformationEntryConfiguration());
modelBuilder.ApplyConfiguration(new SkillConfiguration());
modelBuilder.ApplyConfiguration(new UserRecordConfiguration());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,14 @@ public void Configure(EntityTypeBuilder<BlogPost> builder)
{
builder.HasKey(c => c.Id);
builder.Property(c => c.Id).ValueGeneratedOnAdd();

builder.HasMany(t => t.Tags)
.WithOne()
.OnDelete(DeleteBehavior.Cascade);
builder.Navigation(x => x.Tags).AutoInclude();
builder.Property(x => x.Title).HasMaxLength(256).IsRequired();
builder.Property(x => x.PreviewImageUrl).HasMaxLength(1024).IsRequired();
builder.Property(x => x.PreviewImageUrlFallback).HasMaxLength(1024);
builder.Property(x => x.Content).IsRequired();
builder.Property(x => x.ShortDescription).IsRequired();
builder.Property(x => x.Likes).IsRequired();
builder.Property(x => x.IsPublished).IsRequired();
builder.Property(x => x.Tags).HasMaxLength(2096);

builder.HasIndex(x => new { x.IsPublished, x.UpdatedDate })
.HasDatabaseName("IX_BlogPosts_IsPublished_UpdatedDate")
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public async ValueTask StoreAsync(TEntity entity)

await blogDbContext.SaveChangesAsync();
}

public async ValueTask DeleteAsync(string id)
{
var entityToDelete = await GetByIdAsync(id);
Expand Down
10 changes: 5 additions & 5 deletions src/LinkDotNet.Blog.Web/Controller/RssFeedController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace LinkDotNet.Blog.Web.Controller;

public sealed class RssFeedController : ControllerBase
{
private static readonly XmlWriterSettings Settings = CreateXmlWriterSettings();
private readonly AppConfiguration appConfiguration;
private readonly IRepository<BlogPost> blogPostRepository;

Expand Down Expand Up @@ -45,8 +46,7 @@ public async Task<IActionResult> GetRssFeed()

private static async Task WriteRssInfoToStreamAsync(Stream stream, SyndicationFeed feed)
{
var settings = CreateXmlWriterSettings();
await using var xmlWriter = XmlWriter.Create(stream, settings);
await using var xmlWriter = XmlWriter.Create(stream, Settings);
var rssFormatter = new Rss20FeedFormatter(feed, false);
rssFormatter.WriteTo(xmlWriter);
await xmlWriter.FlushAsync();
Expand Down Expand Up @@ -86,9 +86,9 @@ private static SyndicationItem CreateSyndicationItemFromBlogPost(string url, Blo

private static void AddCategories(ICollection<SyndicationCategory> categories, BlogPostRssInfo blogPost)
{
foreach (var tag in blogPost.Tags ?? Array.Empty<Tag>())
foreach (var tag in blogPost.Tags ?? Array.Empty<string>())
{
categories.Add(new SyndicationCategory(tag.Content));
categories.Add(new SyndicationCategory(tag));
}
}

Expand All @@ -107,5 +107,5 @@ private sealed record BlogPostRssInfo(
string ShortDescription,
DateTime UpdatedDate,
string PreviewImageUrl,
ICollection<Tag> Tags);
IReadOnlyCollection<string> Tags);
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ private IEnumerable<SitemapUrl> CreateUrlsForTags(IEnumerable<BlogPost> blogPost
{
return blogPosts
.SelectMany(b => b.Tags)
.Select(t => t.Content)
.Distinct()
.Select(t => new SitemapUrl
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
{
<li class="tags me-4">
<ul>
@foreach (var tag in BlogPost.Tags.Select(t => t.Content))
@foreach (var tag in BlogPost.Tags)
{
<li><a class="goto-tag" href="/searchByTag/@(Uri.EscapeDataString(tag))">@tag</a></li>
}
Expand Down
2 changes: 1 addition & 1 deletion src/LinkDotNet.Blog.Web/Features/Search/SearchPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
{
var term = Uri.UnescapeDataString(SearchTerm);
blogPosts = await BlogPostRepository.GetAllAsync(t => t.IsPublished && (t.Title.Contains(term)
|| t.Tags.Any(tag => tag.Content == term)),
|| t.Tags.Any(tag => tag == term)),
b => b.UpdatedDate);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
{
Tag = Uri.UnescapeDataString(Tag);
blogPosts = await BlogPostRepository.GetAllAsync(
b => b.IsPublished && b.Tags.Any(t => t.Content == Tag),
b => b.IsPublished && b.Tags.Any(t => t == Tag),
b => b.UpdatedDate);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ else
@if (BlogPost.Tags != null && BlogPost.Tags.Any())
{
<span class="blogpost-tag d-inline-block">
@foreach (var tag in BlogPost.Tags.Select(t => t.Content))
@foreach (var tag in BlogPost.Tags)
{
<a class="goto-tag ms-1" href="/searchByTag/@(Uri.EscapeDataString(tag))">@tag</a>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ public async Task ShouldNotCacheWhenDifferentQueries()
await Repository.StoreAsync(bp3);
var searchTerm = "tag 1";
var sut = new CachedRepository<BlogPost>(Repository, new MemoryCache(new MemoryCacheOptions()));
await sut.GetAllAsync(f => f.Tags.Any(t => t.Content == searchTerm));
await sut.GetAllAsync(f => f.Tags.Any(t => t == searchTerm));
searchTerm = "tag 2";

var allWithTag2 = await sut.GetAllAsync(f => f.Tags.Any(t => t.Content == searchTerm));
var allWithTag2 = await sut.GetAllAsync(f => f.Tags.Any(t => t == searchTerm));

allWithTag2.Count.Should().Be(1);
allWithTag2.Single().Tags.Single().Content.Should().Be("tag 2");
allWithTag2.Single().Tags.Single().Should().Be("tag 2");
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public async Task ShouldLoadBlogPost()
blogPostFromRepo.PreviewImageUrl.Should().Be("url");
blogPostFromRepo.IsPublished.Should().BeTrue();
blogPostFromRepo.Tags.Should().HaveCount(2);
var tagContent = blogPostFromRepo.Tags.Select(t => t.Content).ToList();
var tagContent = blogPostFromRepo.Tags;
tagContent.Should().Contain(new[] { "Tag 1", "Tag 2" });
}

Expand Down Expand Up @@ -96,7 +96,7 @@ public async Task ShouldSaveBlogPost()
blogPostFromContext.IsPublished.Should().BeTrue();
blogPostFromContext.PreviewImageUrl.Should().Be("url");
blogPostFromContext.Tags.Should().HaveCount(2);
var tagContent = blogPostFromContext.Tags.Select(t => t.Content).ToList();
var tagContent = blogPostFromContext.Tags;
tagContent.Should().Contain(new[] { "Tag 1", "Tag 2" });
}

Expand All @@ -117,7 +117,7 @@ public async Task ShouldGetAllBlogPosts()
blogPostFromRepo.PreviewImageUrl.Should().Be("url");
blogPostFromRepo.IsPublished.Should().BeTrue();
blogPostFromRepo.Tags.Should().HaveCount(2);
var tagContent = blogPostFromRepo.Tags.Select(t => t.Content).ToList();
var tagContent = blogPostFromRepo.Tags;
tagContent.Should().Contain(new[] { "Tag 1", "Tag 2" });
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System.Linq;
using System.Threading.Tasks;
using LinkDotNet.Blog.Domain;
using LinkDotNet.Blog.Infrastructure.Persistence;
using LinkDotNet.Blog.TestUtilities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;

namespace LinkDotNet.Blog.IntegrationTests.Infrastructure.Persistence.Sql;

Expand All @@ -24,7 +26,7 @@ public async Task ShouldLoadBlogPost()
blogPostFromRepo.PreviewImageUrl.Should().Be("url");
blogPostFromRepo.IsPublished.Should().BeTrue();
blogPostFromRepo.Tags.Should().HaveCount(2);
var tagContent = blogPostFromRepo.Tags.Select(t => t.Content).ToList();
var tagContent = blogPostFromRepo.Tags;
tagContent.Should().Contain(new[] { "Tag 1", "Tag 2" });
}

Expand All @@ -35,15 +37,15 @@ public async Task ShouldSaveBlogPost()

await Repository.StoreAsync(blogPost);

var blogPostFromContext = await DbContext.BlogPosts.Include(b => b.Tags).AsNoTracking().SingleOrDefaultAsync(s => s.Id == blogPost.Id);
var blogPostFromContext = await DbContext.BlogPosts.AsNoTracking().SingleOrDefaultAsync(s => s.Id == blogPost.Id);
blogPostFromContext.Should().NotBeNull();
blogPostFromContext.Title.Should().Be("Title");
blogPostFromContext.ShortDescription.Should().Be("Subtitle");
blogPostFromContext.Content.Should().Be("Content");
blogPostFromContext.IsPublished.Should().BeTrue();
blogPostFromContext.PreviewImageUrl.Should().Be("url");
blogPostFromContext.Tags.Should().HaveCount(2);
var tagContent = blogPostFromContext.Tags.Select(t => t.Content).ToList();
var tagContent = blogPostFromContext.Tags;
tagContent.Should().Contain(new[] { "Tag 1", "Tag 2" });
}

Expand All @@ -65,7 +67,7 @@ public async Task ShouldGetAllBlogPosts()
blogPostFromRepo.PreviewImageUrl.Should().Be("url");
blogPostFromRepo.IsPublished.Should().BeTrue();
blogPostFromRepo.Tags.Should().HaveCount(2);
var tagContent = blogPostFromRepo.Tags.Select(t => t.Content).ToList();
var tagContent = blogPostFromRepo.Tags;
tagContent.Should().Contain(new[] { "Tag 1", "Tag 2" });
}

Expand Down Expand Up @@ -116,4 +118,20 @@ public async Task ShouldDelete()

(await DbContext.BlogPosts.AsNoTracking().AnyAsync(b => b.Id == blogPost.Id)).Should().BeFalse();
}

[Fact]
public async Task GivenBlogPostWithTags_WhenLoadingAndDeleting_ThenShouldBeUpdated()
{
var bp = new BlogPostBuilder().WithTags("tag 1").Build();
var sut = new CachedRepository<BlogPost>(Repository, new MemoryCache(new MemoryCacheOptions()));
await sut.StoreAsync(bp);
var updateBp = new BlogPostBuilder().WithTags("tag 2").Build();
var bpFromCache = await sut.GetByIdAsync(bp.Id);
bpFromCache.Update(updateBp);
await sut.StoreAsync(bpFromCache);

var bpFromDb = await sut.GetByIdAsync(bp.Id);

bpFromDb.Tags.Single().Should().Be("tag 2");
}
}
8 changes: 4 additions & 4 deletions tests/LinkDotNet.Blog.UnitTests/Domain/BlogPostTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,16 @@ public void ShouldUpdateTagsWhenExisting()
blogPostToUpdate.Update(blogPost);

blogPostToUpdate.Tags.Should().HaveCount(1);
blogPostToUpdate.Tags.Single().Content.Should().Be("tag 2");
blogPostToUpdate.Tags.Single().Should().Be("tag 2");
}

[Fact]
public void ShouldTrimWhitespacesFromTags()
{
var blogPost = BlogPost.Create("Title", "Sub", "Content", "Preview", false, tags: new[] { " Tag 1", " Tag 2 ", });

blogPost.Tags.Select(t => t.Content).Should().Contain("Tag 1");
blogPost.Tags.Select(t => t.Content).Should().Contain("Tag 2");
blogPost.Tags.Should().Contain("Tag 1");
blogPost.Tags.Should().Contain("Tag 2");
}

[Fact]
Expand All @@ -67,7 +67,7 @@ public void ShouldNotDeleteTagsWhenSameReference()
bp.Update(bp);

bp.Tags.Should().HaveCount(1);
bp.Tags.Single().Content.Should().Be("tag 1");
bp.Tags.Single().Should().Be("tag 1");
}

[Fact]
Expand Down
26 changes: 0 additions & 26 deletions tests/LinkDotNet.Blog.UnitTests/Domain/TagTests.cs

This file was deleted.

Loading