Skip to content

Commit

Permalink
Publish a post (#29)
Browse files Browse the repository at this point in the history
Publish a post #22

* Set publish date
* Move images/files to new folder, when slug changes (folder name is slug)
* Preview button, show unpublished the post for a logged in user
* move seed to sample app and add waffle gen
* Update prism version and new prism theme
  • Loading branch information
petervandenhout authored Aug 9, 2019
1 parent c113119 commit c740db8
Show file tree
Hide file tree
Showing 32 changed files with 1,592 additions and 1,160 deletions.
85 changes: 85 additions & 0 deletions samples/Opw.PineBlog.Sample/DatabaseSeed.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using Opw.PineBlog.Entities;
using Opw.PineBlog.EntityFrameworkCore;
using System;
using System.Linq;
using WaffleGenerator;

namespace Opw.PineBlog.Sample
{
internal class DatabaseSeed
{
private readonly BlogEntityDbContext _dbContext;

public DatabaseSeed(BlogEntityDbContext context)
{
_dbContext = context;
}

public void Run()
{
if (DateTime.UtcNow.Day % 2 == 0)
CreateAuthor("John Smith", "images/avatar-male.png");
else
CreateAuthor("Mary Smith", "images/avatar-female.png");

CreateBlogPosts();
}

void CreateAuthor(string name, string imagePath)
{
if (_dbContext.Authors.Count() > 0) return;

var email = "pineblog@example.com";
if (_dbContext.Authors.Count(a => a.UserName.Equals(email)) > 0) return;

_dbContext.Authors.Add(new Author
{
UserName = email,
Email = email,
DisplayName = name,
Avatar = imagePath,
Bio = WaffleEngine.Text(1, false),
});

_dbContext.SaveChanges();
}

void CreateBlogPosts()
{
if (_dbContext.Posts.Count() > 0) return;

var author = _dbContext.Authors.Single();

for(int i = 0; i < 5; i++)
{
var title = WaffleEngine.Title();
var post = new Post
{
AuthorId = author.Id,
Title = title,
Slug = title.ToSlug(),
Description = WaffleEngine.Text(1, false),
Published = DateTime.UtcNow.AddDays(-i * 10)
};

if (i % 2 == 0)
{
post.CoverUrl = "/images/woods.gif";
post.CoverCaption = "Battle background for the Misty Woods in the game Shadows of Adam by Tim Wendorf";
post.CoverLink = "http://pixeljoint.com/pixelart/94359.htm";
post.Content = $"## {WaffleEngine.Text(1, true)} {WaffleEngine.Text(1, false)} \n``` csharp\npublic class {{\n var myVar = \"Some value\";\n}}\n```\n ## {WaffleEngine.Text(1, true)} {WaffleEngine.Text(2, false)}";
post.Categories = "csharp,waffle,random";
}
else
{
post.Content = $"## {WaffleEngine.Text(1, true)} {WaffleEngine.Text(1, false)} ### {WaffleEngine.Text(1, true)} \n``` yaml\nYAML: YAML Ain't Markup Language\n```\n ## {WaffleEngine.Text(1, true)} {WaffleEngine.Text(2, false)}";
post.Categories = "yaml,waffle,random";
}

_dbContext.Posts.Add(post);
}

_dbContext.SaveChanges();
}
}
}
3 changes: 2 additions & 1 deletion samples/Opw.PineBlog.Sample/Opw.PineBlog.Sample.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
Expand All @@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
<PackageReference Include="WaffleGenerator" Version="4.0.2" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Opw.EntityFrameworkCore;
using Opw.PineBlog.EntityFrameworkCore;

namespace Opw.PineBlog.EntityFrameworkCore
namespace Opw.PineBlog.Sample
{
public static class ServiceProviderExtensions
{
Expand Down
4 changes: 2 additions & 2 deletions samples/Opw.PineBlog.Sample/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
},
"PineBlogOptions": {
"Title": "PineBlog",
"CoverUrl": "images/woods.gif",
"CoverUrl": "/images/woods.gif",
"CoverCaption": "Battle background for the Misty Woods in the game Shadows of Adam by Tim Wendorf",
"CoverLink": "http://pixeljoint.com/pixelart/94359.htm",
"ItemsPerPage": 2,
"CreateAndSeedDatabases": true,
"AzureStorageConnectionString": "UseDevelopmentStorage=true",
"AzureStorageBlobContainerName": "pineblog",
"CdnUrl": "http://127.0.0.1:10000/devstoreaccount1",
"FileBaseUrl": "http://127.0.0.1:10000/devstoreaccount1",
"CoverImagesPath": "covers"
},
"Logging": {
Expand Down
6 changes: 3 additions & 3 deletions src/Opw.PineBlog.Abstractions/PineBlogOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ public class PineBlogOptions
public string CoverLink { get; set; }

/// <summary>
/// The URL of the CDN where the images and other files are accessible.
/// Can also be the web host or a local host name if no CDN is used.
/// The URL of the location where the images and other files are stored.
/// Can be the web host, a CDN or a local host.
/// </summary>
public string CdnUrl { get; set; }
public string FileBaseUrl { get; set; }

/// <summary>
/// The path for the folder where the cover images are stored.
Expand Down
2 changes: 1 addition & 1 deletion src/Opw.PineBlog.Core/Files/FileUrlHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public string ReplaceUrlFormatWithBaseUrl(string s)
/// </summary>
public string GetBaseUrl()
{
var url = _blogOptions.Value.CdnUrl;
var url = _blogOptions.Value.FileBaseUrl;
if (!string.IsNullOrWhiteSpace(_blogOptions.Value.AzureStorageBlobContainerName))
url += "/" + _blogOptions.Value.AzureStorageBlobContainerName;

Expand Down
7 changes: 1 addition & 6 deletions src/Opw.PineBlog.Core/Posts/AddPostCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ public class AddPostCommand : IRequest<Result<Post>>, IEditPostCommand
/// </summary>
public string Title { get; set; }

/// <summary>
/// The slug for this post, until the post is published a temporary slug will be used.
/// </summary>
public string Slug { get; set; } = DateTime.UtcNow.Ticks.ToString();

/// <summary>
/// A short description for the post.
/// </summary>
Expand Down Expand Up @@ -97,7 +92,7 @@ public async Task<Result<Post>> Handle(AddPostCommand request, CancellationToken
{
AuthorId = author.Id,
Title = request.Title,
Slug = request.Slug,
Slug = request.Title.ToSlug(),
Description = request.Description,
Content = request.Content,
Categories = request.Categories,
Expand Down
2 changes: 0 additions & 2 deletions src/Opw.PineBlog.Core/Posts/EditPostCommandValidator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using FluentValidation;
using Opw.FluentValidation;

namespace Opw.PineBlog.Posts
{
Expand All @@ -15,7 +14,6 @@ public class EditPostCommandValidator<TRequest> : AbstractValidator<TRequest>
public EditPostCommandValidator()
{
RuleFor(c => c.Title).MaximumLength(160).NotEmpty();
RuleFor(c => c.Slug).IsSlug();
RuleFor(c => c.Description).MaximumLength(450);
RuleFor(c => c.Categories).MaximumLength(2000);
RuleFor(c => c.Content).NotEmpty();
Expand Down
5 changes: 0 additions & 5 deletions src/Opw.PineBlog.Core/Posts/IEditPostCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ public interface IEditPostCommand
/// </summary>
string Title { get; set; }

/// <summary>
/// The slug for this post.
/// </summary>
string Slug { get; set; }

/// <summary>
/// A short description for the post.
/// </summary>
Expand Down
59 changes: 59 additions & 0 deletions src/Opw.PineBlog.Core/Posts/PublishPostCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using Opw.HttpExceptions;
using Opw.PineBlog.Entities;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Opw.PineBlog.Posts
{
/// <summary>
/// Command that publishes a post.
/// </summary>
public class PublishPostCommand : IRequest<Result<Post>>
{
/// <summary>
/// The post id.
/// </summary>
public Guid Id { get; set; }

/// <summary>
/// Handler for the PublishPostCommand.
/// </summary>
public class Handler : IRequestHandler<PublishPostCommand, Result<Post>>
{
private readonly IBlogEntityDbContext _context;

/// <summary>
/// Implementation of PublishPostCommand.Handler.
/// </summary>
/// <param name="context">The blog entity context.</param>
public Handler(IBlogEntityDbContext context)
{
_context = context;
}

/// <summary>
/// Handle the PublishPostCommand request.
/// </summary>
/// <param name="request">The PublishPostCommand request.</param>
/// <param name="cancellationToken">A cancellation token.</param>
public async Task<Result<Post>> Handle(PublishPostCommand request, CancellationToken cancellationToken)
{
var entity = await _context.Posts.SingleOrDefaultAsync(e => e.Id.Equals(request.Id));
if (entity == null)
return Result<Post>.Fail(new NotFoundException<Post>($"Could not find post, id: \"{request.Id}\""));

entity.Published = DateTime.UtcNow;

_context.Posts.Update(entity);
var result = await _context.SaveChangesAsync(true, cancellationToken);
if (!result.IsSuccess)
return Result<Post>.Fail(result.Exception);

return Result<Post>.Success(entity);
}
}
}
}
19 changes: 19 additions & 0 deletions src/Opw.PineBlog.Core/Posts/PublishPostCommandValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using FluentValidation;
using Opw.FluentValidation;

namespace Opw.PineBlog.Posts
{
/// <summary>
/// Validator for the PublishPostCommand request.
/// </summary>
public class PublishPostCommandValidator : AbstractValidator<PublishPostCommand>
{
/// <summary>
/// Implementation of PublishPostCommandValidator.
/// </summary>
public PublishPostCommandValidator()
{
RuleFor(c => c.Id).IsRequiredGuid();
}
}
}
59 changes: 59 additions & 0 deletions src/Opw.PineBlog.Core/Posts/UnpublishPostCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using Opw.HttpExceptions;
using Opw.PineBlog.Entities;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Opw.PineBlog.Posts
{
/// <summary>
/// Command that unpublishes a post.
/// </summary>
public class UnpublishPostCommand : IRequest<Result<Post>>
{
/// <summary>
/// The post id.
/// </summary>
public Guid Id { get; set; }

/// <summary>
/// Handler for the UnpublishPostCommand.
/// </summary>
public class Handler : IRequestHandler<UnpublishPostCommand, Result<Post>>
{
private readonly IBlogEntityDbContext _context;

/// <summary>
/// Implementation of UnpublishPostCommand.Handler.
/// </summary>
/// <param name="context">The blog entity context.</param>
public Handler(IBlogEntityDbContext context)
{
_context = context;
}

/// <summary>
/// Handle the UnpublishPostCommand request.
/// </summary>
/// <param name="request">The UnpublishPostCommand request.</param>
/// <param name="cancellationToken">A cancellation token.</param>
public async Task<Result<Post>> Handle(UnpublishPostCommand request, CancellationToken cancellationToken)
{
var entity = await _context.Posts.SingleOrDefaultAsync(e => e.Id.Equals(request.Id));
if (entity == null)
return Result<Post>.Fail(new NotFoundException<Post>($"Could not find post, id: \"{request.Id}\""));

entity.Published = null;

_context.Posts.Update(entity);
var result = await _context.SaveChangesAsync(true, cancellationToken);
if (!result.IsSuccess)
return Result<Post>.Fail(result.Exception);

return Result<Post>.Success(entity);
}
}
}
}
19 changes: 19 additions & 0 deletions src/Opw.PineBlog.Core/Posts/UnpublishPostCommandValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using FluentValidation;
using Opw.FluentValidation;

namespace Opw.PineBlog.Posts
{
/// <summary>
/// Validator for the UnpublishPostCommand request.
/// </summary>
public class UnpublishPostCommandValidator : AbstractValidator<UnpublishPostCommand>
{
/// <summary>
/// Implementation of UnpublishPostCommandValidator.
/// </summary>
public UnpublishPostCommandValidator()
{
RuleFor(c => c.Id).IsRequiredGuid();
}
}
}
14 changes: 8 additions & 6 deletions src/Opw.PineBlog.Core/Posts/UpdatePostCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@ public class UpdatePostCommand : IRequest<Result<Post>>, IEditPostCommand
/// </summary>
public string Title { get; set; }

/// <summary>
/// The slug for this post, until the post is published a temporary slug will be used.
/// </summary>
public string Slug { get; set; }

/// <summary>
/// A short description for the post.
/// </summary>
Expand Down Expand Up @@ -94,8 +89,10 @@ public async Task<Result<Post>> Handle(UpdatePostCommand request, CancellationTo
if (entity == null)
return Result<Post>.Fail(new NotFoundException<Post>($"Could not find post, id: \"{request.Id}\""));

var oldSlug = entity.Slug;

entity.Title = request.Title;
entity.Slug = request.Slug;
entity.Slug = request.Title.ToSlug();
entity.Description = request.Description;
entity.Content = request.Content;
entity.Categories = request.Categories;
Expand All @@ -111,6 +108,11 @@ public async Task<Result<Post>> Handle(UpdatePostCommand request, CancellationTo
if (!result.IsSuccess)
return Result<Post>.Fail(result.Exception);

if (!oldSlug.Equals(entity.Slug))
{
// TODO: update folders
}

return Result<Post>.Success(entity);
}
}
Expand Down
Loading

0 comments on commit c740db8

Please sign in to comment.