Add code for using MongoDB
This commit is contained in:
parent
dd370e27cb
commit
313d668bfe
8 changed files with 218 additions and 1 deletions
|
@ -20,6 +20,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robware.Api.Blog.Tests", "R
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "..\build\_build.csproj", "{9F55FA01-FEC9-4326-8338-4EF014E127A4}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "..\build\_build.csproj", "{9F55FA01-FEC9-4326-8338-4EF014E127A4}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robware.Data.MongoDB", "Robware.Data.MongoDB\Robware.Data.MongoDB.csproj", "{D48723D5-053A-4654-B37D-CC2FA62625C3}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robware.Data.MongoDB.Tests", "Robware.Data.MongoDB.Tests\Robware.Data.MongoDB.Tests.csproj", "{E4361445-DD13-4DB3-A278-4198F4DEE00A}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -48,6 +52,14 @@ Global
|
||||||
{54DEC274-5F28-42FB-9B60-1E4BAF3D7BF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{54DEC274-5F28-42FB-9B60-1E4BAF3D7BF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{54DEC274-5F28-42FB-9B60-1E4BAF3D7BF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{54DEC274-5F28-42FB-9B60-1E4BAF3D7BF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{54DEC274-5F28-42FB-9B60-1E4BAF3D7BF9}.Release|Any CPU.Build.0 = Release|Any CPU
|
{54DEC274-5F28-42FB-9B60-1E4BAF3D7BF9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D48723D5-053A-4654-B37D-CC2FA62625C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D48723D5-053A-4654-B37D-CC2FA62625C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D48723D5-053A-4654-B37D-CC2FA62625C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D48723D5-053A-4654-B37D-CC2FA62625C3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E4361445-DD13-4DB3-A278-4198F4DEE00A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{E4361445-DD13-4DB3-A278-4198F4DEE00A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E4361445-DD13-4DB3-A278-4198F4DEE00A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E4361445-DD13-4DB3-A278-4198F4DEE00A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
@ -5,12 +5,14 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MongoDB.Driver" Version="2.10.4" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Robware.Blog\Robware.Blog.csproj" />
|
<ProjectReference Include="..\Robware.Blog\Robware.Blog.csproj" />
|
||||||
<ProjectReference Include="..\Robware.Data\Robware.Data.csproj" />
|
<ProjectReference Include="..\Robware.Data\Robware.Data.csproj" />
|
||||||
|
<ProjectReference Include="..\Robware.Data.MongoDB\Robware.Data.MongoDB.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Robware.Data {
|
namespace Robware.Blog {
|
||||||
public class ItemNotFoundException:Exception {
|
public class ItemNotFoundException:Exception {
|
||||||
public object Parameters { get; }
|
public object Parameters { get; }
|
||||||
|
|
54
src/Robware.Data.MongoDB.Tests/BlogRepositoryTests.cs
Normal file
54
src/Robware.Data.MongoDB.Tests/BlogRepositoryTests.cs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
using FluentAssertions;
|
||||||
|
using MongoDB.Driver;
|
||||||
|
using NSubstitute;
|
||||||
|
using Robware.Blog;
|
||||||
|
using Robware.Blog.Data.MongoDB.State;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Robware.Data.MongoDB.Tests {
|
||||||
|
public class BlogRepositoryTests {
|
||||||
|
[Fact]
|
||||||
|
public async Task SavePost_WithNewPost_GetsNewIdAndSetsTimestampAndSavesAsync() {
|
||||||
|
var collection = Substitute.For<IMongoCollection<BlogPostState>>();
|
||||||
|
collection.CountDocumentsAsync(Arg.Any<FilterDefinition<BlogPostState>>()).Returns(10);
|
||||||
|
var database = Substitute.For<IMongoDatabase>();
|
||||||
|
database.GetCollection<BlogPostState>("blog").Returns(collection);
|
||||||
|
|
||||||
|
var repo = new BlogRepository(database);
|
||||||
|
var post = new BlogPost();
|
||||||
|
await repo.SavePost(post);
|
||||||
|
post.Id.Should().Be(11);
|
||||||
|
post.Timestamp.Should().BeCloseTo(DateTime.Now);
|
||||||
|
await collection.Received(1).InsertOneAsync(Arg.Any<BlogPostState>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SavePost_WithExistingPost_DoesNotSetTimestampAndSavesAsync() {
|
||||||
|
var collection = Substitute.For<IMongoCollection<BlogPostState>>();
|
||||||
|
var database = Substitute.For<IMongoDatabase>();
|
||||||
|
database.GetCollection<BlogPostState>("blog").Returns(collection);
|
||||||
|
|
||||||
|
var repo = new BlogRepository(database);
|
||||||
|
var post = new BlogPost() {
|
||||||
|
Id = 1
|
||||||
|
};
|
||||||
|
await repo.SavePost(post);
|
||||||
|
post.Id.Should().Be(1);
|
||||||
|
post.Timestamp.Should().Be(new DateTime());
|
||||||
|
await collection.Received(1).InsertOneAsync(Arg.Any<BlogPostState>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetCountAsync_ReturnsCountFromDatabase() {
|
||||||
|
var collection = Substitute.For<IMongoCollection<BlogPostState>>();
|
||||||
|
collection.CountDocumentsAsync(Arg.Any<FilterDefinition<BlogPostState>>()).Returns(10);
|
||||||
|
var database = Substitute.For<IMongoDatabase>();
|
||||||
|
database.GetCollection<BlogPostState>("blog").Returns(collection);
|
||||||
|
|
||||||
|
var repo = new BlogRepository(database);
|
||||||
|
(await repo.GetCountAsync()).Should().Be(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FluentAssertions" Version="5.10.3" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||||
|
<PackageReference Include="MongoDB.Driver" Version="2.10.4" />
|
||||||
|
<PackageReference Include="NSubstitute" Version="4.2.2" />
|
||||||
|
<PackageReference Include="xunit" Version="2.4.0" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
|
||||||
|
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Robware.Data.MongoDB\Robware.Data.MongoDB.csproj" />
|
||||||
|
<ProjectReference Include="..\Robware.Blog\Robware.Blog.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
77
src/Robware.Data.MongoDB/BlogRepository.cs
Normal file
77
src/Robware.Data.MongoDB/BlogRepository.cs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MongoDB.Driver;
|
||||||
|
using Robware.Blog;
|
||||||
|
using Robware.Blog.Data.MongoDB.State;
|
||||||
|
|
||||||
|
namespace Robware.Data.MongoDB {
|
||||||
|
public class BlogRepository : IBlogRepository {
|
||||||
|
private readonly IMongoCollection<BlogPostState> _collection;
|
||||||
|
|
||||||
|
public BlogRepository(IMongoDatabase database) {
|
||||||
|
_collection = database.GetCollection<BlogPostState>("blog");
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlogPost MapStateToPost(BlogPostState state) =>
|
||||||
|
new BlogPost {
|
||||||
|
Id = state.Id,
|
||||||
|
Title = state.Title,
|
||||||
|
Content = state.Content,
|
||||||
|
Timestamp = state.Timestamp,
|
||||||
|
Draft = state.Draft,
|
||||||
|
Url = state.Url,
|
||||||
|
UserId = state.UserId,
|
||||||
|
};
|
||||||
|
|
||||||
|
private IEnumerable<BlogPost> MapStateToPost(IEnumerable<BlogPostState> states) => states.Select(MapStateToPost);
|
||||||
|
|
||||||
|
public async Task DeletePostAsync(int id) {
|
||||||
|
var update = Builders<BlogPostState>.Update.Set(nameof(BlogPostState.Deleted), true);
|
||||||
|
await _collection.UpdateOneAsync(post => post.Id == id, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<BlogPost>> GetAllPostsAsync() => MapStateToPost((await _collection.FindAsync(post => true)).ToEnumerable());
|
||||||
|
|
||||||
|
public async Task<int> GetCountAsync() => (int)await _collection.CountDocumentsAsync(post => !post.Deleted);
|
||||||
|
|
||||||
|
public async Task<BlogPost> GetLatestPostAsync() => (await GetLatestPostsAsync(1)).FirstOrDefault();
|
||||||
|
|
||||||
|
public async Task<IEnumerable<BlogPost>> GetLatestPostsAsync(int limit, int offset = 0) {
|
||||||
|
var filter = Builders<BlogPostState>.Filter.Eq(nameof(BlogPostState.Deleted), false);
|
||||||
|
var sort = Builders<BlogPostState>.Sort.Descending(nameof(BlogPostState.Timestamp));
|
||||||
|
var options = new FindOptions<BlogPostState> {
|
||||||
|
Sort = sort,
|
||||||
|
Limit = limit,
|
||||||
|
Skip = offset
|
||||||
|
};
|
||||||
|
var states = (await _collection.FindAsync<BlogPostState>(filter, options)).ToEnumerable();
|
||||||
|
return MapStateToPost(states);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<BlogPost> GetPostAsync(Expression<Func<BlogPostState, bool>> filter) {
|
||||||
|
var result = (await _collection.FindAsync(filter)).FirstOrDefault();
|
||||||
|
if (result == null)
|
||||||
|
throw new ItemNotFoundException(nameof(GetPostAsync), null);
|
||||||
|
return MapStateToPost(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<BlogPost> GetPostByIdAsync(int id) => await GetPostAsync(post => post.Id == id && !post.Deleted);
|
||||||
|
|
||||||
|
public async Task<BlogPost> GetPostByUrlAsync(string url) => await GetPostAsync(post => post.Url == url && !post.Deleted);
|
||||||
|
|
||||||
|
public async Task<BlogPost> SavePost(BlogPost post) {
|
||||||
|
if (post.Id == 0) {
|
||||||
|
post.Id = (await GetCountAsync()) + 1;
|
||||||
|
post.Timestamp = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mongoPost = new BlogPostState(post);
|
||||||
|
await _collection.InsertOneAsync(mongoPost);
|
||||||
|
|
||||||
|
return post;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
src/Robware.Data.MongoDB/Robware.Data.MongoDB.csproj
Normal file
15
src/Robware.Data.MongoDB/Robware.Data.MongoDB.csproj
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Robware.Blog\Robware.Blog.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MongoDB.Driver" Version="2.10.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
33
src/Robware.Data.MongoDB/State/BlogPostState.cs
Normal file
33
src/Robware.Data.MongoDB/State/BlogPostState.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
using MongoDB.Bson;
|
||||||
|
using MongoDB.Bson.Serialization.Attributes;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Robware.Blog.Data.MongoDB.State {
|
||||||
|
public class BlogPostState {
|
||||||
|
public BlogPostState()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlogPostState(BlogPost basePost) {
|
||||||
|
Id = basePost.Id;
|
||||||
|
Title = basePost.Title;
|
||||||
|
Content = basePost.Content;
|
||||||
|
Timestamp = basePost.Timestamp;
|
||||||
|
Draft = basePost.Draft;
|
||||||
|
Url = basePost.Url;
|
||||||
|
UserId = basePost.UserId;
|
||||||
|
Deleted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BsonId]
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Title { get; private set; }
|
||||||
|
public string Content { get; private set; }
|
||||||
|
public DateTime Timestamp { get; private set; }
|
||||||
|
public string Draft { get; private set; }
|
||||||
|
public string Url { get; private set; }
|
||||||
|
public int UserId { get; private set; }
|
||||||
|
public bool Deleted { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue