From f64955c69d91e578e580b409ba31ac4b3477da96 Mon Sep 17 00:00:00 2001 From: vnugent Date: Wed, 12 Jul 2023 01:28:23 -0400 Subject: Initial commit --- back-end/src/Endpoints/PostsEndpoint.cs | 271 ++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 back-end/src/Endpoints/PostsEndpoint.cs (limited to 'back-end/src/Endpoints/PostsEndpoint.cs') diff --git a/back-end/src/Endpoints/PostsEndpoint.cs b/back-end/src/Endpoints/PostsEndpoint.cs new file mode 100644 index 0000000..fe7a310 --- /dev/null +++ b/back-end/src/Endpoints/PostsEndpoint.cs @@ -0,0 +1,271 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: CMNext +* Package: Content.Publishing.Blog.Admin +* File: PostsEndpoint.cs +* +* CMNext is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License as +* published by the Free Software Foundation, either version 3 of the +* License, or (at your option) any later version. +* +* CMNext is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see https://www.gnu.org/licenses/. +*/ + +using System; +using System.Net; +using System.Threading.Tasks; +using Content.Publishing.Blog.Admin.Model; + +using FluentValidation; + +using VNLib.Plugins; +using VNLib.Plugins.Essentials; +using VNLib.Plugins.Essentials.Accounts; +using VNLib.Plugins.Essentials.Endpoints; +using VNLib.Plugins.Essentials.Extensions; +using VNLib.Plugins.Extensions.Loading; +using VNLib.Plugins.Extensions.Validation; + +namespace Content.Publishing.Blog.Admin.Endpoints +{ + + [ConfigurationName("post_endpoint")] + internal sealed class PostsEndpoint : ProtectedWebEndpoint + { + private static readonly IValidator PostValidator = BlogPost.GetValidator(); + + private readonly IBlogPostManager PostManager; + private readonly IChannelContextManager ContentManager; + + public PostsEndpoint(PluginBase plugin, IConfigScope config) + { + string? path = config["path"].GetString(); + + InitPathAndLog(path, plugin.Log); + + //Get post manager and context manager + PostManager = plugin.GetOrCreateSingleton(); + ContentManager = plugin.GetOrCreateSingleton(); + } + + protected override async ValueTask GetAsync(HttpEntity entity) + { + //Check for read permissions + if (!entity.Session.CanRead()) + { + WebMessage webm = new() + { + Result = "You do not have permission to read content" + }; + entity.CloseResponseJson(HttpStatusCode.Forbidden, webm); + return VfReturnType.VirtualSkip; + } + + //Try to get the blog id from the query + if (!entity.QueryArgs.TryGetNonEmptyValue("channel", out string? contextId)) + { + entity.CloseResponse(HttpStatusCode.BadRequest); + return VfReturnType.VirtualSkip; + } + + //Try to get the blog context from the id + IChannelContext? context = await ContentManager.GetChannelAsync(contextId, entity.EventCancellation); + if (context == null) + { + entity.CloseResponse(HttpStatusCode.NotFound); + return VfReturnType.VirtualSkip; + } + + //Try to get the post id from the query + if (entity.QueryArgs.TryGetNonEmptyValue("post", out string? postId)) + { + //Try to get single post + PostMeta? post = await PostManager.GetPostAsync(context, postId, entity.EventCancellation); + + if (post != null) + { + entity.CloseResponseJson(HttpStatusCode.OK, post); + } + else + { + entity.CloseResponse(HttpStatusCode.NotFound); + } + + return VfReturnType.VirtualSkip; + } + + //Get the post meta list + PostMeta[] posts = await PostManager.GetPostsAsync(context, entity.EventCancellation); + entity.CloseResponseJson(HttpStatusCode.OK, posts); + return VfReturnType.VirtualSkip; + } + + protected override async ValueTask PostAsync(HttpEntity entity) + { + ValErrWebMessage webm = new(); + + //Check for write permissions + if (webm.Assert(entity.Session.CanWrite() == true, "You do not have permission to publish posts")) + { + entity.CloseResponseJson(HttpStatusCode.Forbidden, webm); + return VfReturnType.VirtualSkip; + } + + if (!entity.QueryArgs.TryGetNonEmptyValue("channel", out string? contextId)) + { + webm.Result = "No blog channel was selected"; + entity.CloseResponseJson(HttpStatusCode.BadRequest, webm); + return VfReturnType.VirtualSkip; + } + + //Try to get the blog context from the id + IChannelContext? context = await ContentManager.GetChannelAsync(contextId, entity.EventCancellation); + + if (webm.Assert(context != null, "A blog with the given id does not exist")) + { + entity.CloseResponseJson(HttpStatusCode.BadRequest, webm); + return VfReturnType.VirtualSkip; + } + + //Get the post from the request body + BlogPost? post = await entity.GetJsonFromFileAsync(); + + if (webm.Assert(post != null, "Message body was empty")) + { + entity.CloseResponseJson(HttpStatusCode.BadRequest, webm); + return VfReturnType.VirtualSkip; + } + + //Validate post + if (!PostValidator.Validate(post, webm)) + { + entity.CloseResponse(webm); + return VfReturnType.VirtualSkip; + } + + //Publish post to the blog + await PostManager.PublishPostAsync(context, post, entity.EventCancellation); + + //Success + webm.Result = post; + webm.Success = true; + + //Return updated post to client + entity.CloseResponse(webm); + return VfReturnType.VirtualSkip; + } + + protected override async ValueTask PatchAsync(HttpEntity entity) + { + ValErrWebMessage webm = new(); + + //Check for write permissions + if (webm.Assert(entity.Session.CanWrite() == true, "You do not have permissions to update posts")) + { + entity.CloseResponseJson(HttpStatusCode.Forbidden, webm); + return VfReturnType.VirtualSkip; + } + + //Try to get the blog id from the query + if (!entity.QueryArgs.TryGetNonEmptyValue("channel", out string? contextId)) + { + webm.Result = "You must select a blog channel to update posts"; + entity.CloseResponseJson(HttpStatusCode.BadRequest, webm); + return VfReturnType.VirtualSkip; + } + + //Try to get the blog context from the id + IChannelContext? channel = await ContentManager.GetChannelAsync(contextId, entity.EventCancellation); + + if (webm.Assert(channel != null, "The channel you selected does not exist")) + { + entity.CloseResponseJson(HttpStatusCode.NotFound, webm); + return VfReturnType.VirtualSkip; + } + + //Get the blog post object + BlogPost? post = await entity.GetJsonFromFileAsync(); + + if (webm.Assert(post != null, "Message body was empty")) + { + entity.CloseResponse(webm); + return VfReturnType.VirtualSkip; + } + + //Validate post + if (!PostValidator.Validate(post, webm)) + { + entity.CloseResponse(webm); + return VfReturnType.VirtualSkip; + } + + //Update post against manager + bool result = await PostManager.UpdatePostAsync(channel, post, entity.EventCancellation); + + if (webm.Assert(result, "Failed to update post because it does not exist or the blog channel was not found")) + { + entity.CloseResponse(webm); + return VfReturnType.VirtualSkip; + } + + //Success + webm.Result = post; + webm.Success = true; + + entity.CloseResponse(webm); + return VfReturnType.VirtualSkip; + } + + protected override async ValueTask DeleteAsync(HttpEntity entity) + { + //Check for delete permissions + if (!entity.Session.CanDelete()) + { + WebMessage webm = new() + { + Result = "You do not have permission to delete content" + }; + entity.CloseResponseJson(HttpStatusCode.Forbidden, webm); + return VfReturnType.VirtualSkip; + } + + //Try to get the blog id from the query + if (!entity.QueryArgs.TryGetNonEmptyValue("channel", out string? contextId)) + { + entity.CloseResponse(HttpStatusCode.BadRequest); + return VfReturnType.VirtualSkip; + } + + //Try to get the blog context from the id + IChannelContext? context = await ContentManager.GetChannelAsync(contextId, entity.EventCancellation); + if (context == null) + { + entity.CloseResponse(HttpStatusCode.NotFound); + return VfReturnType.VirtualSkip; + } + + //Try to get the post id from the query + if (!entity.QueryArgs.TryGetNonEmptyValue("post", out string? postId)) + { + entity.CloseResponse(HttpStatusCode.NotFound); + return VfReturnType.VirtualSkip; + } + + //Delete post + await PostManager.DeletePostAsync(context, postId, entity.EventCancellation); + + //Success + entity.CloseResponse(HttpStatusCode.OK); + return VfReturnType.VirtualSkip; + } + + } +} -- cgit