Use new API for blog
This commit is contained in:
parent
e389b2404a
commit
25c320bf6b
17 changed files with 356 additions and 314 deletions
|
@ -23,6 +23,8 @@ steps:
|
||||||
from_secret: GitDomain
|
from_secret: GitDomain
|
||||||
GitToken:
|
GitToken:
|
||||||
from_secret: GitToken
|
from_secret: GitToken
|
||||||
|
BlogEndpoint:
|
||||||
|
from_secret: BlogEndpoint
|
||||||
commands:
|
commands:
|
||||||
- curl -sL https://deb.nodesource.com/setup_12.x | bash -
|
- curl -sL https://deb.nodesource.com/setup_12.x | bash -
|
||||||
- apt-get install -y nodejs
|
- apt-get install -y nodejs
|
||||||
|
@ -31,6 +33,7 @@ steps:
|
||||||
- sed -i "s/<DatabaseConnectionString>/$ConnectionString/g" output/appsettings.json
|
- sed -i "s/<DatabaseConnectionString>/$ConnectionString/g" output/appsettings.json
|
||||||
- sed -i "s/<GitDomain>/$GitDomain/g" output/appsettings.json
|
- sed -i "s/<GitDomain>/$GitDomain/g" output/appsettings.json
|
||||||
- sed -i "s/<GitToken>/$GitToken/g" output/appsettings.json
|
- sed -i "s/<GitToken>/$GitToken/g" output/appsettings.json
|
||||||
|
- sed -i "s/<BlogEndpoint>/$BlogEndpoint/g" output/appsettings.json
|
||||||
- cp Infrastructure/website.service output/
|
- cp Infrastructure/website.service output/
|
||||||
- cp -r ./output/* /output
|
- cp -r ./output/* /output
|
||||||
- name: restart service
|
- name: restart service
|
||||||
|
|
249
Website.Tests/Data/BlogApiTests.cs
Normal file
249
Website.Tests/Data/BlogApiTests.cs
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Website.Data;
|
||||||
|
using Website.Models;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Website.Tests.Data {
|
||||||
|
public class BlogApiTests {
|
||||||
|
[Fact]
|
||||||
|
public async Task GetPostByUrlAsync_WithUrl_ReturnsBlogPost() {
|
||||||
|
const string json = @"{""id"":1,""title"":""title"",""content"":""content"",""timestamp"":""2020-04-10T13:00:42"",""draft"":""draft"",""url"":""url"",""userId"":0}";
|
||||||
|
var httpClient = new HttpClientBuilder()
|
||||||
|
.WithMethod(HttpMethod.Get)
|
||||||
|
.WithUrl("/get/test")
|
||||||
|
.WithResponse(json)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var expectation = new BlogPost {
|
||||||
|
Id = 1,
|
||||||
|
Title = "title",
|
||||||
|
Content = "content",
|
||||||
|
Draft = "draft",
|
||||||
|
Timestamp = new DateTime(2020, 04, 10, 13, 00, 42),
|
||||||
|
Url = "url",
|
||||||
|
UserId = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
var api = new BlogApi(httpClient);
|
||||||
|
(await api.GetPostByUrlAsync("test")).Should().BeEquivalentTo(expectation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetLatestPostsAsync_WithNoParameters_ReturnsBlogPostCollection() {
|
||||||
|
const string json = @"[{""id"":1,""title"":""title"",""content"":""content"",""timestamp"":""2020-04-10T13:00:42"",""draft"":""draft"",""url"":""url"",""userId"":0}]";
|
||||||
|
var httpClient = new HttpClientBuilder()
|
||||||
|
.WithMethod(HttpMethod.Get)
|
||||||
|
.WithUrl("/getlatestposts")
|
||||||
|
.WithResponse(json)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var expectation = new[] {
|
||||||
|
new BlogPost {
|
||||||
|
Id = 1,
|
||||||
|
Title = "title",
|
||||||
|
Content = "content",
|
||||||
|
Draft = "draft",
|
||||||
|
Timestamp = new DateTime(2020, 04, 10, 13, 00, 42),
|
||||||
|
Url = "url",
|
||||||
|
UserId = 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var api = new BlogApi(httpClient);
|
||||||
|
(await api.GetLatestPostsAsync()).Should().BeEquivalentTo(expectation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetLatestPostsAsync_WithCount_ReturnsBlogPostCollection() {
|
||||||
|
const string json = @"[{""id"":1,""title"":""title"",""content"":""content"",""timestamp"":""2020-04-10T13:00:42"",""draft"":""draft"",""url"":""url"",""userId"":0}]";
|
||||||
|
var httpClient = new HttpClientBuilder()
|
||||||
|
.WithMethod(HttpMethod.Get)
|
||||||
|
.WithUrl("/getlatestposts?count=1")
|
||||||
|
.WithResponse(json)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var expectation = new[] {
|
||||||
|
new BlogPost {
|
||||||
|
Id = 1,
|
||||||
|
Title = "title",
|
||||||
|
Content = "content",
|
||||||
|
Draft = "draft",
|
||||||
|
Timestamp = new DateTime(2020, 04, 10, 13, 00, 42),
|
||||||
|
Url = "url",
|
||||||
|
UserId = 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var api = new BlogApi(httpClient);
|
||||||
|
(await api.GetLatestPostsAsync(1)).Should().BeEquivalentTo(expectation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetLatestPostsAsync_WithCountAndOffset_ReturnsBlogPostCollection() {
|
||||||
|
const string json = @"[{""id"":1,""title"":""title"",""content"":""content"",""timestamp"":""2020-04-10T13:00:42"",""draft"":""draft"",""url"":""url"",""userId"":0}]";
|
||||||
|
var httpClient = new HttpClientBuilder()
|
||||||
|
.WithMethod(HttpMethod.Get)
|
||||||
|
.WithUrl("/getlatestposts")
|
||||||
|
.WithQueryString("count", "1")
|
||||||
|
.WithQueryString("offset", "1")
|
||||||
|
.WithResponse(json)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var expectation = new[] {
|
||||||
|
new BlogPost {
|
||||||
|
Id = 1,
|
||||||
|
Title = "title",
|
||||||
|
Content = "content",
|
||||||
|
Draft = "draft",
|
||||||
|
Timestamp = new DateTime(2020, 04, 10, 13, 00, 42),
|
||||||
|
Url = "url",
|
||||||
|
UserId = 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var api = new BlogApi(httpClient);
|
||||||
|
(await api.GetLatestPostsAsync(1, 1)).Should().BeEquivalentTo(expectation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetLatestPostAsync_ReturnsBlogPost() {
|
||||||
|
const string json = @"{""id"":1,""title"":""title"",""content"":""content"",""timestamp"":""2020-04-10T13:00:42"",""draft"":""draft"",""url"":""url"",""userId"":0}";
|
||||||
|
var httpClient = new HttpClientBuilder()
|
||||||
|
.WithMethod(HttpMethod.Get)
|
||||||
|
.WithUrl("/getlatestpost")
|
||||||
|
.WithResponse(json)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var expectation = new BlogPost {
|
||||||
|
Id = 1,
|
||||||
|
Title = "title",
|
||||||
|
Content = "content",
|
||||||
|
Draft = "draft",
|
||||||
|
Timestamp = new DateTime(2020, 04, 10, 13, 00, 42),
|
||||||
|
Url = "url",
|
||||||
|
UserId = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
var api = new BlogApi(httpClient);
|
||||||
|
(await api.GetLatestPostAsync()).Should().BeEquivalentTo(expectation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetCountAsync_ReturnsCount() {
|
||||||
|
const string json = @"23";
|
||||||
|
var httpClient = new HttpClientBuilder()
|
||||||
|
.WithMethod(HttpMethod.Get)
|
||||||
|
.WithUrl("/getcount")
|
||||||
|
.WithResponse(json)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var api = new BlogApi(httpClient);
|
||||||
|
(await api.GetCountAsync()).Should().Be(23);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetPostByIdAsync_WithUrl_ReturnsBlogPost() {
|
||||||
|
const string json = @"{""id"":1,""title"":""title"",""content"":""content"",""timestamp"":""2020-04-10T13:00:42"",""draft"":""draft"",""url"":""url"",""userId"":0}";
|
||||||
|
var httpClient = new HttpClientBuilder()
|
||||||
|
.WithMethod(HttpMethod.Get)
|
||||||
|
.WithUrl("/get/1")
|
||||||
|
.WithResponse(json)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var expectation = new BlogPost {
|
||||||
|
Id = 1,
|
||||||
|
Title = "title",
|
||||||
|
Content = "content",
|
||||||
|
Draft = "draft",
|
||||||
|
Timestamp = new DateTime(2020, 04, 10, 13, 00, 42),
|
||||||
|
Url = "url",
|
||||||
|
UserId = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
var api = new BlogApi(httpClient);
|
||||||
|
(await api.GetPostByIdAsync(1)).Should().BeEquivalentTo(expectation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SavePost_WithPost_ReturnsNothing() {
|
||||||
|
const string requestJson = @"{""Id"":1,""Title"":""title"",""Content"":""content""}";
|
||||||
|
const string responseJson = @"{""id"":1,""title"":""title"",""content"":""content"",""timestamp"":""2020-04-10T13:00:42"",""draft"":""draft"",""url"":""url"",""userId"":0}";
|
||||||
|
var httpClient = new HttpClientBuilder()
|
||||||
|
.WithMethod(HttpMethod.Post)
|
||||||
|
.WithUrl("/savepost")
|
||||||
|
.WithPostBody(requestJson)
|
||||||
|
.WithResponse(responseJson)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var post = new BlogPostSubmission {Id = 1, Title = "title", Content = "content"};
|
||||||
|
|
||||||
|
var expectation = new BlogPost {
|
||||||
|
Id = 1,
|
||||||
|
Title = "title",
|
||||||
|
Content = "content",
|
||||||
|
Draft = "draft",
|
||||||
|
Timestamp = new DateTime(2020, 04, 10, 13, 00, 42),
|
||||||
|
Url = "url",
|
||||||
|
UserId = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
var api = new BlogApi(httpClient);
|
||||||
|
(await api.SavePost(post)).Should().BeEquivalentTo(expectation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAllPostsAsync_ReturnsBlogPostCollection() {
|
||||||
|
const string json =
|
||||||
|
@"[{""id"":1,""title"":""title"",""content"":""content"",""timestamp"":""2020-04-10T13:00:42"",""draft"":""draft"",""url"":""url"",""userId"":0}]";
|
||||||
|
var httpClient = new HttpClientBuilder()
|
||||||
|
.WithMethod(HttpMethod.Get)
|
||||||
|
.WithUrl("/getallposts")
|
||||||
|
.WithResponse(json)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var expectation = new[] {
|
||||||
|
new BlogPost {
|
||||||
|
Id = 1,
|
||||||
|
Title = "title",
|
||||||
|
Content = "content",
|
||||||
|
Draft = "draft",
|
||||||
|
Timestamp = new DateTime(2020, 04, 10, 13, 00, 42),
|
||||||
|
Url = "url",
|
||||||
|
UserId = 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var api = new BlogApi(httpClient);
|
||||||
|
(await api.GetAllPostsAsync()).Should().BeEquivalentTo(expectation);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeletePost_WithId_ReturnsNothing() {
|
||||||
|
var httpClient = new HttpClientBuilder()
|
||||||
|
.WithMethod(HttpMethod.Post)
|
||||||
|
.WithUrl("/deletepost")
|
||||||
|
.WithPostBody("1")
|
||||||
|
.Build(out var mockHttpMessageHandler);
|
||||||
|
|
||||||
|
var api = new BlogApi(httpClient);
|
||||||
|
await api.DeletePostAsync(1);
|
||||||
|
mockHttpMessageHandler.VerifyNoOutstandingExpectation();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task PublishPost_WithId_ReturnsNothing() {
|
||||||
|
var httpClient = new HttpClientBuilder()
|
||||||
|
.WithMethod(HttpMethod.Post)
|
||||||
|
.WithUrl("/publishpost")
|
||||||
|
.WithPostBody("1")
|
||||||
|
.Build(out var mockHttpMessageHandler);
|
||||||
|
|
||||||
|
var api = new BlogApi(httpClient);
|
||||||
|
await api.PublishPostAsync(1);
|
||||||
|
mockHttpMessageHandler.VerifyNoOutstandingExpectation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,55 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Dapper;
|
|
||||||
using FluentAssertions;
|
|
||||||
using NSubstitute;
|
|
||||||
using NSubstitute.ExceptionExtensions;
|
|
||||||
using Website.Data;
|
|
||||||
using Website.Data.States;
|
|
||||||
using Website.Models;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Website.Tests.Data
|
|
||||||
{
|
|
||||||
public class BlogRepositoryTests
|
|
||||||
{
|
|
||||||
// [Fact]
|
|
||||||
// public void GetPostAsync_WithValidId_ReturnsBlogPost() {
|
|
||||||
// var blogPostState = new BlogPostState
|
|
||||||
// {
|
|
||||||
// Post_Id = 1,
|
|
||||||
// Post_Title = "title",
|
|
||||||
// Post_Content = "content",
|
|
||||||
// Post_Timestamp = new DateTime(),
|
|
||||||
// Post_Deleted = false,
|
|
||||||
// Post_Draft = "draft",
|
|
||||||
// Post_Url = "url",
|
|
||||||
// User_Id = 1
|
|
||||||
// };
|
|
||||||
// var connection = Substitute.For<IDbConnection>();
|
|
||||||
// connection.QueryAsync(null, null, (Func<object[], BlogPostState>)null, null, null, false, null, null, null).ReturnsForAnyArgs(new[]{ blogPostState });
|
|
||||||
|
|
||||||
// var provider = Substitute.For<IDatabaseProvider>();
|
|
||||||
// provider.NewConnection().Returns(connection);
|
|
||||||
|
|
||||||
// var repo = new BlogRepository(provider);
|
|
||||||
// var post = repo.GetPostAsync(1);
|
|
||||||
// var expected = new BlogPost
|
|
||||||
// {
|
|
||||||
// Id = 1,
|
|
||||||
// Title = "title",
|
|
||||||
// Content = "content",
|
|
||||||
// Timestamp = new DateTime(),
|
|
||||||
// Draft = "draft",
|
|
||||||
// Url = "url",
|
|
||||||
// UserId = 1
|
|
||||||
// }; ;
|
|
||||||
// post.Should().BeEquivalentTo(expected);
|
|
||||||
// }
|
|
||||||
|
|
||||||
//public async Task GetPostAsync_WithInvalidId_ReturnsNull()
|
|
||||||
//public async Task GetPostAsync_WithDeletedPost_ReturnsNull()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
using FluentAssertions;
|
|
||||||
using Website.Models;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Website.Tests.Models {
|
|
||||||
public class BlogPostTests {
|
|
||||||
[Fact]
|
|
||||||
public void UpdateTitle_WithNewTitle_UpdatesTitlePropertyAndRegeneratesUrl() {
|
|
||||||
var post = new BlogPost();
|
|
||||||
post.UpdateTitle("new title");
|
|
||||||
|
|
||||||
post.Title.Should().Be("new title");
|
|
||||||
post.Url.Should().Be("new-title");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData("new:title", "new-title")]
|
|
||||||
[InlineData("new: title", "new-title")]
|
|
||||||
[InlineData("new$title", "new-title")]
|
|
||||||
[InlineData("new TITle", "new-title")]
|
|
||||||
public void UpdateTitle_WithUnsafeCharsInTitle_RegeneratesSafeUrl(string title, string url) {
|
|
||||||
var post = new BlogPost();
|
|
||||||
post.UpdateTitle(title);
|
|
||||||
post.Url.Should().Be(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void UpdateDraft_WithContent_UpdatesDraftProperty() {
|
|
||||||
var post = new BlogPost();
|
|
||||||
post.UpdateDraft("content");
|
|
||||||
post.Draft.Should().Be("content");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Publish_SetsContentToDraft() {
|
|
||||||
var post = new BlogPost();
|
|
||||||
post.UpdateDraft("content");
|
|
||||||
post.Publish();
|
|
||||||
post.Content.Should().Be("content");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Website.Data.States;
|
|
||||||
using Website.Models;
|
using Website.Models;
|
||||||
using Website.ViewModels;
|
using Website.ViewModels;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
@ -10,20 +9,14 @@ namespace Website.Tests.VIewModels
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Constructor_WithContentOver1000Characters_LimitsContentTo1000Chars() {
|
public void Constructor_WithContentOver1000Characters_LimitsContentTo1000Chars() {
|
||||||
var state = new BlogPostState {
|
var post = new BlogPost {Content = new string('a', 1001)};
|
||||||
Post_Content = new string('a', 1001)
|
|
||||||
};
|
|
||||||
var post = new BlogPost(state);
|
|
||||||
var vm = new BlogPostSnippetViewModel(post);
|
var vm = new BlogPostSnippetViewModel(post);
|
||||||
vm.Content.Length.Should().Be(1000);
|
vm.Content.Length.Should().Be(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Constructor_WithContentUnder1000Characters_ContentIsIdenticalLength() {
|
public void Constructor_WithContentUnder1000Characters_ContentIsIdenticalLength() {
|
||||||
var state = new BlogPostState {
|
var post = new BlogPost{Content = new string('a', 900)};
|
||||||
Post_Content = new string('a', 900)
|
|
||||||
};
|
|
||||||
var post = new BlogPost(state);
|
|
||||||
var vm = new BlogPostSnippetViewModel(post);
|
var vm = new BlogPostSnippetViewModel(post);
|
||||||
vm.Content.Length.Should().Be(900);
|
vm.Content.Length.Should().Be(900);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,53 +1,46 @@
|
||||||
using System;
|
using System;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Website.Data.States;
|
|
||||||
using Website.Models;
|
using Website.Models;
|
||||||
using Website.ViewModels;
|
using Website.ViewModels;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Website.Tests.VIewModels
|
namespace Website.Tests.VIewModels {
|
||||||
{
|
public class BlogPostViewModelTests {
|
||||||
public class BlogPostViewModelTests
|
|
||||||
{
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ContentHtml_WithMarkdownContent_ReturnsHtml() {
|
public void ContentHtml_WithMarkdownContent_ReturnsHtml() {
|
||||||
var state = new BlogPostState {
|
var post = new BlogPost {
|
||||||
Post_Content="# header"
|
Content = "# header"
|
||||||
};
|
};
|
||||||
var post = new BlogPost(state);
|
|
||||||
var vm = new BlogPostViewModel(post, false);
|
var vm = new BlogPostViewModel(post, false);
|
||||||
vm.ContentHtml.Should().Be("<h1>header</h1>");
|
vm.ContentHtml.Should().Be("<h1>header</h1>");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Timestamp_OnThe1st_IsFriendlyString() {
|
public void Timestamp_OnThe1st_IsFriendlyString() {
|
||||||
var state = new BlogPostState {
|
var post = new BlogPost {
|
||||||
Post_Content = "",
|
Content = "",
|
||||||
Post_Timestamp = new DateTime(2018, 10, 01, 15, 1, 25)
|
Timestamp = new DateTime(2018, 10, 01, 15, 1, 25)
|
||||||
};
|
};
|
||||||
var post = new BlogPost(state);
|
|
||||||
var vm = new BlogPostViewModel(post, false);
|
var vm = new BlogPostViewModel(post, false);
|
||||||
vm.Timestamp.Should().Be("Monday the 1st of October 2018");
|
vm.Timestamp.Should().Be("Monday the 1st of October 2018");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Timestamp_OnThe2nd_IsFriendlyString() {
|
public void Timestamp_OnThe2nd_IsFriendlyString() {
|
||||||
var state = new BlogPostState {
|
var post = new BlogPost {
|
||||||
Post_Content = "",
|
Content = "",
|
||||||
Post_Timestamp = new DateTime(2018, 10, 02, 15, 1, 25)
|
Timestamp = new DateTime(2018, 10, 02, 15, 1, 25)
|
||||||
};
|
};
|
||||||
var post = new BlogPost(state);
|
|
||||||
var vm = new BlogPostViewModel(post, false);
|
var vm = new BlogPostViewModel(post, false);
|
||||||
vm.Timestamp.Should().Be("Tuesday the 2nd of October 2018");
|
vm.Timestamp.Should().Be("Tuesday the 2nd of October 2018");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Timestamp_OnThe3rd_IsFriendlyString() {
|
public void Timestamp_OnThe3rd_IsFriendlyString() {
|
||||||
var state = new BlogPostState {
|
var post = new BlogPost {
|
||||||
Post_Content = "",
|
Content = "",
|
||||||
Post_Timestamp = new DateTime(2018, 10, 03, 15, 1, 25)
|
Timestamp = new DateTime(2018, 10, 03, 15, 1, 25)
|
||||||
};
|
};
|
||||||
var post = new BlogPost(state);
|
|
||||||
var vm = new BlogPostViewModel(post, false);
|
var vm = new BlogPostViewModel(post, false);
|
||||||
vm.Timestamp.Should().Be("Wednesday the 3rd of October 2018");
|
vm.Timestamp.Should().Be("Wednesday the 3rd of October 2018");
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,21 +10,21 @@ using Website.ViewModels;
|
||||||
namespace Website.Controllers {
|
namespace Website.Controllers {
|
||||||
public class BlogController:Controller {
|
public class BlogController:Controller {
|
||||||
private const int MaxPostsPerPage = 10;
|
private const int MaxPostsPerPage = 10;
|
||||||
private readonly BlogRepository _repo;
|
private readonly IBlogApi _api;
|
||||||
|
|
||||||
public BlogController(BlogRepository repo) => _repo = repo;
|
public BlogController(IBlogApi api) => _api = api;
|
||||||
|
|
||||||
public async Task<IActionResult> Page(int page) {
|
public async Task<IActionResult> Page(int page) {
|
||||||
var offset = (page - 1) * MaxPostsPerPage;
|
var offset = (page - 1) * MaxPostsPerPage;
|
||||||
var posts = (await _repo.GetLatestPostsAsync(MaxPostsPerPage, offset)).Select(p => new BlogPostSnippetViewModel(p));
|
var posts = (await _api.GetLatestPostsAsync(MaxPostsPerPage, offset)).Select(p => new BlogPostSnippetViewModel(p));
|
||||||
var maxPages = (await _repo.GetCountAsync()) / MaxPostsPerPage;
|
var maxPages = (await _api.GetCountAsync()) / MaxPostsPerPage;
|
||||||
var model = new BlogViewModel(posts, page, maxPages);
|
var model = new BlogViewModel(posts, page, maxPages);
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> Entry(string url, bool preview = false) {
|
public async Task<IActionResult> Entry(string url, bool preview = false) {
|
||||||
try {
|
try {
|
||||||
var post = await _repo.GetPostByUrlAsync(url);
|
var post = await _api.GetPostByUrlAsync(url);
|
||||||
|
|
||||||
if (!preview && string.IsNullOrEmpty(post.Content))
|
if (!preview && string.IsNullOrEmpty(post.Content))
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
@ -42,7 +42,7 @@ namespace Website.Controllers {
|
||||||
return View();
|
return View();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var post = await _repo.GetPostByIdAsync(id.Value);
|
var post = await _api.GetPostByIdAsync(id.Value);
|
||||||
var model = new BlogPostSubmission {
|
var model = new BlogPostSubmission {
|
||||||
Id = post.Id,
|
Id = post.Id,
|
||||||
Title = post.Title,
|
Title = post.Title,
|
||||||
|
@ -57,35 +57,28 @@ namespace Website.Controllers {
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> Save(BlogPostSubmission submission) {
|
public async Task<IActionResult> Save(BlogPostSubmission submission) {
|
||||||
var post = submission.Id.HasValue ? await _repo.GetPostByIdAsync(submission.Id.Value) : new BlogPost();
|
var savedPost = await _api.SavePost(submission);
|
||||||
|
|
||||||
post.UpdateTitle(submission.Title);
|
|
||||||
post.UpdateDraft(submission.Content);
|
|
||||||
|
|
||||||
var savedPost = await _repo.SavePost(post);
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(Edit), new { savedPost.Id });
|
return RedirectToAction(nameof(Edit), new { savedPost.Id });
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<IActionResult> Manage() {
|
public async Task<IActionResult> Manage() {
|
||||||
var posts = await _repo.GetAllPostsAsync();
|
var posts = await _api.GetAllPostsAsync();
|
||||||
var models = posts.OrderByDescending(post => post.Timestamp).Select(post => new BlogPostViewModel(post, false));
|
var models = posts.OrderByDescending(post => post.Timestamp).Select(post => new BlogPostViewModel(post, false));
|
||||||
return View(models);
|
return View(models);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<IActionResult> Publish(int id) {
|
public async Task<IActionResult> Publish(int id) {
|
||||||
var post = await _repo.GetPostByIdAsync(id);
|
await _api.PublishPostAsync(id);
|
||||||
post.Publish();
|
|
||||||
await _repo.SavePost(post);
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(Manage));
|
return RedirectToAction(nameof(Manage));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<IActionResult> Delete(int id) {
|
public async Task<IActionResult> Delete(int id) {
|
||||||
await _repo.DeletePostAsync(id);
|
await _api.DeletePostAsync(id);
|
||||||
|
|
||||||
return RedirectToAction(nameof(Manage));
|
return RedirectToAction(nameof(Manage));
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,16 @@ using System.Linq;
|
||||||
|
|
||||||
namespace Website.Controllers {
|
namespace Website.Controllers {
|
||||||
public class HomeController : Controller {
|
public class HomeController : Controller {
|
||||||
private readonly BlogRepository _blogRepo;
|
private readonly IBlogApi _blogApi;
|
||||||
private readonly GitServerApi _api;
|
private readonly GitServerApi _api;
|
||||||
|
|
||||||
public HomeController(BlogRepository blogRepo, GitServerApi api) {
|
public HomeController(IBlogApi blogApi, GitServerApi api) {
|
||||||
_api = api;
|
_api = api;
|
||||||
_blogRepo = blogRepo;
|
_blogApi = blogApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> Index() {
|
public async Task<IActionResult> Index() {
|
||||||
var post = await _blogRepo.GetLatestPostAsync();
|
var post = await _blogApi.GetLatestPostAsync();
|
||||||
var repo = (await _api.GetRepositories()).First();
|
var repo = (await _api.GetRepositories()).First();
|
||||||
var branch = (await _api.GetBranches(repo.Name)).First();
|
var branch = (await _api.GetBranches(repo.Name)).First();
|
||||||
var commit = await _api.GetCommit(repo.Name, branch.Commit.Id);
|
var commit = await _api.GetCommit(repo.Name, branch.Commit.Id);
|
||||||
|
|
22
Website/Data/BlogApi.cs
Normal file
22
Website/Data/BlogApi.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Website.Models;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Website.Data
|
||||||
|
{
|
||||||
|
public class BlogApi : ApiClient, IBlogApi {
|
||||||
|
public BlogApi(HttpClient client) : base(client) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<BlogPost> GetPostByUrlAsync(string url) => await Get<BlogPost>("/get/" + url);
|
||||||
|
public async Task<IEnumerable<BlogPost>> GetLatestPostsAsync(int count = 0, int offset = 0) => await Get<IEnumerable<BlogPost>>("/getlatestposts", new{count, offset});
|
||||||
|
public async Task<BlogPost> GetLatestPostAsync() => await Get<BlogPost>("/getlatestpost");
|
||||||
|
public async Task<int> GetCountAsync() => await Get<int>("/getcount");
|
||||||
|
public async Task<BlogPost> GetPostByIdAsync(int id) => await Get<BlogPost>("/get/" + id);
|
||||||
|
public async Task<BlogPost> SavePost(BlogPostSubmission post) => await Post<BlogPost>("/savepost", post);
|
||||||
|
public async Task<IEnumerable<BlogPost>> GetAllPostsAsync() => await Get<IEnumerable<BlogPost>>("/getallposts");
|
||||||
|
public async Task DeletePostAsync(int id) => await Post<object>("/deletepost", id);
|
||||||
|
public async Task PublishPostAsync(int id) => await Post<object>("/publishpost", id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,87 +0,0 @@
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Linq;
|
|
||||||
using Dapper;
|
|
||||||
using Website.Models;
|
|
||||||
using Website.Data.States;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Website.Data
|
|
||||||
{
|
|
||||||
public class BlogRepository
|
|
||||||
{
|
|
||||||
private readonly IDatabaseProvider _dbProvider;
|
|
||||||
|
|
||||||
public BlogRepository(IDatabaseProvider dbProvider) => _dbProvider = dbProvider;
|
|
||||||
|
|
||||||
public async Task<BlogPost> GetPostByUrlAsync(string url) {
|
|
||||||
const string query = "SELECT * FROM blog_posts WHERE post_url=@url AND post_deleted=0";
|
|
||||||
using (var connection = _dbProvider.NewConnection()) {
|
|
||||||
connection.Open();
|
|
||||||
var result = await connection.QueryAsync<BlogPostState>(query, new{url});
|
|
||||||
return new BlogPost(result.First());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<BlogPost>> GetLatestPostsAsync(int limit, int offset = 0) {
|
|
||||||
const string query = "SELECT * FROM blog_posts WHERE post_content<>'' AND post_deleted=0 ORDER BY post_timestamp DESC LIMIT @offset,@limit";
|
|
||||||
using (var connection = _dbProvider.NewConnection()) {
|
|
||||||
connection.Open();
|
|
||||||
var results = await connection.QueryAsync<BlogPostState>(query, new{limit, offset});
|
|
||||||
return results.Select(result => new BlogPost(result));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<BlogPost> GetLatestPostAsync() => (await GetLatestPostsAsync(1)).First();
|
|
||||||
|
|
||||||
public async Task<int> GetCountAsync()
|
|
||||||
{
|
|
||||||
var query="SELECT COUNT(*) FROM blog_posts WHERE post_content<>'' AND post_deleted=0";
|
|
||||||
using(var connection = _dbProvider.NewConnection()) {
|
|
||||||
connection.Open();
|
|
||||||
var result = await connection.QueryAsync<int>(query);
|
|
||||||
return result.First();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<BlogPost> GetPostByIdAsync(int id) {
|
|
||||||
const string query = "SELECT * FROM blog_posts WHERE post_id=@id AND post_deleted=0";
|
|
||||||
using (var connection = _dbProvider.NewConnection()) {
|
|
||||||
connection.Open();
|
|
||||||
var result = await connection.QueryAsync<BlogPostState>(query, new {id});
|
|
||||||
return new BlogPost(result.First());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<BlogPost> SavePost(BlogPost post) {
|
|
||||||
const string newPostQuery = "INSERT INTO blog_posts (post_title, post_content, post_draft, post_url) VALUES (@title, @content, @draft, @url); SELECT CAST(LAST_INSERT_ID() as int)";
|
|
||||||
const string updatePostQuery = "UPDATE blog_posts SET post_id = @id, post_title = @title, post_content = @content, post_draft = @draft, post_url = @url WHERE post_id = @id ";
|
|
||||||
|
|
||||||
var newPost = post.Id == 0;
|
|
||||||
|
|
||||||
using (var connection = _dbProvider.NewConnection()) {
|
|
||||||
connection.Open();
|
|
||||||
var result = await connection.QueryAsync<int>(newPost ? newPostQuery : updatePostQuery, post);
|
|
||||||
return newPost ? await GetPostByIdAsync(result.Single()) : post;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<BlogPost>> GetAllPostsAsync() {
|
|
||||||
const string query = "SELECT * FROM blog_posts WHERE post_deleted=0";
|
|
||||||
|
|
||||||
using (var connection = _dbProvider.NewConnection()) {
|
|
||||||
connection.Open();
|
|
||||||
var result = await connection.QueryAsync<BlogPostState>(query);
|
|
||||||
return result.Select(state => new BlogPost(state));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task DeletePostAsync(int id) {
|
|
||||||
const string query = "UPDATE blog_posts SET post_deleted=1 WHERE post_id=@id";
|
|
||||||
|
|
||||||
using (var connection = _dbProvider.NewConnection()) {
|
|
||||||
connection.Open();
|
|
||||||
await connection.ExecuteAsync(query, new {id});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
17
Website/Data/IBlogApi.cs
Normal file
17
Website/Data/IBlogApi.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Website.Models;
|
||||||
|
|
||||||
|
namespace Website.Data {
|
||||||
|
public interface IBlogApi {
|
||||||
|
Task<BlogPost> GetPostByUrlAsync(string url);
|
||||||
|
Task<IEnumerable<BlogPost>> GetLatestPostsAsync(int count = 0, int offset = 0);
|
||||||
|
Task<BlogPost> GetLatestPostAsync();
|
||||||
|
Task<int> GetCountAsync();
|
||||||
|
Task<BlogPost> GetPostByIdAsync(int id);
|
||||||
|
Task<BlogPost> SavePost(BlogPostSubmission post);
|
||||||
|
Task<IEnumerable<BlogPost>> GetAllPostsAsync();
|
||||||
|
Task DeletePostAsync(int id);
|
||||||
|
Task PublishPostAsync(int id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +0,0 @@
|
||||||
using System;
|
|
||||||
using Dapper.Contrib.Extensions;
|
|
||||||
|
|
||||||
namespace Website.Data.States
|
|
||||||
{
|
|
||||||
[Table("blog_posts")]
|
|
||||||
public class BlogPostState
|
|
||||||
{
|
|
||||||
[Key]
|
|
||||||
public int Post_Id { get; set; }
|
|
||||||
public string Post_Title { get; set; }
|
|
||||||
public string Post_Content { get; set; }
|
|
||||||
public DateTime Post_Timestamp { get; set; }
|
|
||||||
public string Post_Draft { get; set; }
|
|
||||||
public string Post_Url { get; set; }
|
|
||||||
public bool Post_Deleted{ get; set; }
|
|
||||||
public int User_Id { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Website.Data.States;
|
|
||||||
|
|
||||||
namespace Website.Models
|
namespace Website.Models
|
||||||
{
|
{
|
||||||
public class BlogPost
|
public class BlogPost
|
||||||
{
|
{
|
||||||
public BlogPost() {
|
public int Id { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
}
|
public string Content { get; set; }
|
||||||
|
public DateTime Timestamp { get; set; }
|
||||||
public BlogPost(BlogPostState state)
|
public string Draft { get; set; }
|
||||||
{
|
public string Url { get; set; }
|
||||||
Id = state.Post_Id;
|
public int UserId { get; set; }
|
||||||
Title = state.Post_Title;
|
|
||||||
Content = state.Post_Content;
|
|
||||||
Timestamp = state.Post_Timestamp;
|
|
||||||
Draft = state.Post_Draft;
|
|
||||||
Url = state.Post_Url;
|
|
||||||
UserId = state.User_Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Id { get; private 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; }
|
|
||||||
|
|
||||||
private void GenerateUrl() {
|
|
||||||
Url = Regex.Replace(Title, @"[^a-zA-Z0-9\.]+", "-").ToLower();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateTitle(string title) {
|
|
||||||
Title = title;
|
|
||||||
GenerateUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateDraft(string content) => Draft = content;
|
|
||||||
public void Publish() => Content = Draft;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Net.Http;
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
@ -43,9 +44,10 @@ namespace Website
|
||||||
|
|
||||||
private void RegisterRepositories(IServiceCollection services) =>
|
private void RegisterRepositories(IServiceCollection services) =>
|
||||||
services.AddSingleton<IDatabaseProvider, MySQLDatabaseProvider>()
|
services.AddSingleton<IDatabaseProvider, MySQLDatabaseProvider>()
|
||||||
.AddSingleton<BlogRepository, BlogRepository>()
|
.AddSingleton<IBlogApi, BlogApi>()
|
||||||
.AddSingleton<UserRepository, UserRepository>()
|
.AddSingleton<UserRepository, UserRepository>()
|
||||||
.AddSingleton(new GitServerApi(new HttpClient(), Configuration["gitDomain"], Configuration["gitToken"]));
|
.AddSingleton(new GitServerApi(new HttpClient(), Configuration["gitDomain"], Configuration["gitToken"]))
|
||||||
|
.AddHttpClient<IBlogApi, BlogApi>(client => client.BaseAddress = new Uri(Configuration["blogApiEndpoint"]));
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
|
|
|
@ -4,9 +4,9 @@ using Website.Data;
|
||||||
|
|
||||||
namespace Website.ViewComponents {
|
namespace Website.ViewComponents {
|
||||||
public class BlogNavigationViewComponent:ViewComponent {
|
public class BlogNavigationViewComponent:ViewComponent {
|
||||||
private readonly BlogRepository _repo;
|
private readonly IBlogApi _repo;
|
||||||
|
|
||||||
public BlogNavigationViewComponent(BlogRepository repo) {
|
public BlogNavigationViewComponent(IBlogApi repo) {
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,5 +10,6 @@
|
||||||
"database": "Server=localhost;User ID=user;Password=pass;Database=db"
|
"database": "Server=localhost;User ID=user;Password=pass;Database=db"
|
||||||
},
|
},
|
||||||
"gitDomain": "",
|
"gitDomain": "",
|
||||||
"gitToken": ""
|
"gitToken": "",
|
||||||
|
"blogApiEndpoint": ""
|
||||||
}
|
}
|
|
@ -9,6 +9,7 @@
|
||||||
},
|
},
|
||||||
"gitDomain": "<GitDomain>",
|
"gitDomain": "<GitDomain>",
|
||||||
"gitToken": "<GitToken>",
|
"gitToken": "<GitToken>",
|
||||||
|
"blogApiEndpoint": "<BlogEndpoint>",
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"Kestrel": {
|
"Kestrel": {
|
||||||
"EndPoints": {
|
"EndPoints": {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue