From fbd7b1c7d3981c06aa9324b2173c58edef77bcf6 Mon Sep 17 00:00:00 2001 From: the1mason Date: Mon, 21 Oct 2024 21:49:51 +0500 Subject: [PATCH] Added blog list for edit --- .../Repositories/Blogs/IBlogMetaRepository.cs | 2 + src/FastBlog.Core/FastBlog.Core.csproj | 1 + src/FastBlog.Core/Models/Blogs/BlogFilter.cs | 3 + .../Repositories/BlogMetaRepository.cs | 68 +++++++++- src/FastBlog.Core/Services/BlogService.cs | 22 ++++ .../Controllers/BlogsController.cs | 71 +++++++--- src/FastBlog.Web/Models/EditBlog.cs | 4 +- src/FastBlog.Web/Program.cs | 3 - src/FastBlog.Web/Views/Blogs/Index.cshtml | 26 +--- src/FastBlog.Web/Views/Blogs/List.cshtml | 123 ++++++++++++++++++ src/FastBlog.Web/Views/Blogs/Preview.cshtml | 2 +- .../Views/Blogs/Unpublished.cshtml | 22 ++++ .../{BlogContent.cshtml => Content.cshtml} | 0 .../Views/Shared/Blogs/ManageCard.cshtml | 23 ++++ src/FastBlog.Web/Views/Shared/_Layout.cshtml | 2 +- src/FastBlog.Web/wwwroot/css/site.css | 20 +-- 16 files changed, 334 insertions(+), 58 deletions(-) create mode 100644 src/FastBlog.Core/Models/Blogs/BlogFilter.cs create mode 100644 src/FastBlog.Web/Views/Blogs/List.cshtml create mode 100644 src/FastBlog.Web/Views/Blogs/Unpublished.cshtml rename src/FastBlog.Web/Views/Shared/Blogs/{BlogContent.cshtml => Content.cshtml} (100%) create mode 100644 src/FastBlog.Web/Views/Shared/Blogs/ManageCard.cshtml diff --git a/src/FastBlog.Core/Abstractions/Repositories/Blogs/IBlogMetaRepository.cs b/src/FastBlog.Core/Abstractions/Repositories/Blogs/IBlogMetaRepository.cs index 49fc381..f5713a7 100644 --- a/src/FastBlog.Core/Abstractions/Repositories/Blogs/IBlogMetaRepository.cs +++ b/src/FastBlog.Core/Abstractions/Repositories/Blogs/IBlogMetaRepository.cs @@ -5,9 +5,11 @@ namespace FastBlog.Core.Abstractions.Repositories.Blogs; public interface IBlogMetaRepository { + Task GetPublished(string? slug); Task Get(string? slug); Task GetForEdit(int id); Task Delete(int id); Task> Add(BlogMeta meta); Task> Update(BlogMeta meta); + Task> List(BlogFilter filter, PagedRequest request); } \ No newline at end of file diff --git a/src/FastBlog.Core/FastBlog.Core.csproj b/src/FastBlog.Core/FastBlog.Core.csproj index 1ff124c..d1b890f 100644 --- a/src/FastBlog.Core/FastBlog.Core.csproj +++ b/src/FastBlog.Core/FastBlog.Core.csproj @@ -17,6 +17,7 @@ + diff --git a/src/FastBlog.Core/Models/Blogs/BlogFilter.cs b/src/FastBlog.Core/Models/Blogs/BlogFilter.cs new file mode 100644 index 0000000..cac29b9 --- /dev/null +++ b/src/FastBlog.Core/Models/Blogs/BlogFilter.cs @@ -0,0 +1,3 @@ +namespace FastBlog.Core.Models.Blogs; + +public sealed record BlogFilter(DateTime? From, DateTime? To, string? Search); \ No newline at end of file diff --git a/src/FastBlog.Core/Repositories/BlogMetaRepository.cs b/src/FastBlog.Core/Repositories/BlogMetaRepository.cs index c7961f9..8d9c990 100644 --- a/src/FastBlog.Core/Repositories/BlogMetaRepository.cs +++ b/src/FastBlog.Core/Repositories/BlogMetaRepository.cs @@ -1,4 +1,5 @@ -using Dapper; +using System.Text; +using Dapper; using FastBlog.Core.Abstractions.Repositories.Blogs; using FastBlog.Core.Db; using FastBlog.Core.Models; @@ -9,7 +10,7 @@ namespace FastBlog.Core.Repositories; public sealed class BlogMetaRepository(SqliteConnectionFactory connectionFactory) : IBlogMetaRepository { - public async Task Get(string? slug) + public async Task GetPublished(string? slug) { using var connection = connectionFactory.Create(); @@ -29,6 +30,27 @@ public sealed class BlogMetaRepository(SqliteConnectionFactory connectionFactory return await connection.QueryFirstOrDefaultAsync(sqlNull); return await connection.QueryFirstOrDefaultAsync(sql, new { slug }); } + + public async Task Get(string? slug) + { + using var connection = connectionFactory.Create(); + + const string sqlNull = """ + select * from Blogs + where slug is null + """; + + const string sql = """ + select * from Blogs + where (slug = @slug or id = cast(@slug as integer)) + """; + + if (slug is null) + return await connection.QueryFirstOrDefaultAsync(sqlNull); + + return await connection.QueryFirstOrDefaultAsync(sql, new { slug }); + } + public async Task GetForEdit(int id) { @@ -98,4 +120,46 @@ public sealed class BlogMetaRepository(SqliteConnectionFactory connectionFactory throw; } } + + public async Task> List(BlogFilter filter, PagedRequest request) + { + using var connection = connectionFactory.Create(); + + var parameters = new DynamicParameters(); + + parameters.Add("@Amount", request.Amount); + parameters.Add("@Offset", request.Offset); + + var sql = new StringBuilder(""" + select COUNT(*) from Files; + select * from Blogs "" + """); + + if (filter.To.HasValue) + { + sql.AppendLine("where CreatedAt >= @To"); + parameters.Add("@To", filter.To.Value); + } + + if (filter.From.HasValue) + { + sql.AppendLine("where CreatedAt <= @From"); + parameters.Add("@From", filter.From.Value); + } + + if (filter.Search is not null) + { + sql.AppendLine("where Title like @Search"); + parameters.Add("@Search", $"%{filter.Search}%"); + } + + sql.AppendLine("order by CreatedAt desc"); + sql.AppendLine("limit @Amount offset @Offset;"); + + await using var multi = await connection.QueryMultipleAsync(sql.ToString(), parameters); + var total = await multi.ReadFirstAsync(); + var result = (await multi.ReadAsync()).ToArray(); + + return PagedResponse.Create(total, request.Amount, request.Offset, result); + } } \ No newline at end of file diff --git a/src/FastBlog.Core/Services/BlogService.cs b/src/FastBlog.Core/Services/BlogService.cs index 7442748..98c75a8 100644 --- a/src/FastBlog.Core/Services/BlogService.cs +++ b/src/FastBlog.Core/Services/BlogService.cs @@ -6,6 +6,26 @@ namespace FastBlog.Core.Services; public sealed class BlogService(IBlogMetaRepository metaRepository, IBlogFileRepository blogFileRepository) { + public async Task GetPublished(string? slug) + { + slug = slug?.ToLower(); + var metaResult = await metaRepository.GetPublished(slug); + if (metaResult is null) + return null; + var sourceResult = await blogFileRepository.Read(metaResult.SourceLocation); + + if (sourceResult is null) + { + throw new FileNotFoundException("File with metadata cannot be found"); + } + + return new Blog + { + Metadata = metaResult, + Text = sourceResult + }; + } + public async Task Get(string? slug) { slug = slug?.ToLower(); @@ -25,6 +45,8 @@ public sealed class BlogService(IBlogMetaRepository metaRepository, IBlogFileRep Text = sourceResult }; } + + public Task> ListMetas(BlogFilter filter, PagedRequest request) => metaRepository.List(filter, request); public async Task GetForEdit(int id) { diff --git a/src/FastBlog.Web/Controllers/BlogsController.cs b/src/FastBlog.Web/Controllers/BlogsController.cs index 03a5360..e46736b 100644 --- a/src/FastBlog.Web/Controllers/BlogsController.cs +++ b/src/FastBlog.Web/Controllers/BlogsController.cs @@ -1,5 +1,4 @@ -using System.Diagnostics; -using FastBlog.Core.Abstractions.Repositories.Blogs; +using FastBlog.Core.Models; using FastBlog.Core.Models.Blogs; using FastBlog.Core.Services; using FastBlog.Web.Models; @@ -18,7 +17,7 @@ public class BlogsController(BlogService service) : Controller if (string.IsNullOrWhiteSpace(slug)) slug = null; - var blog = await service.Get(slug); + var blog = await service.GetPublished(slug); if (blog is null) return NotFound(); @@ -30,7 +29,7 @@ public class BlogsController(BlogService service) : Controller [Route("")] public async Task Index() { - var blog = await service.Get(null); + var blog = await service.GetPublished(null); if (blog is null) return NotFound(); @@ -38,6 +37,40 @@ public class BlogsController(BlogService service) : Controller return View(blog); } + + [HttpGet] + [Route("unpublished/{*slug}")] + public async Task Unpublished(string? slug) + { + if (string.IsNullOrWhiteSpace(slug)) + slug = null; + + var blog = await service.Get(slug); + + if (blog is null) + return NotFound(); + + return View(blog); + } + + [HttpGet("list/edit")] + public async Task List( + [FromQuery(Name = "amount")] int amount = 25, + [FromQuery(Name = "offset")] int offset = 0, + [FromQuery(Name = "query")] string? query = null, + [FromQuery(Name = "from")] DateTime? from = null, + [FromQuery(Name = "to")] DateTime? to = null) + { + + if(amount is < 1 or > 100) + return BadRequest("Amount must be between 1 and 100"); + + if(offset < 0) + return BadRequest("Offset must be greater than 0"); + + return View(await service.ListMetas(new BlogFilter(from,to, query), new PagedRequest(amount, offset))); + } + [HttpGet] [Route("edit/{id:int?}")] public async ValueTask Edit(int? id) @@ -48,21 +81,21 @@ public class BlogsController(BlogService service) : Controller return View( new EditBlog { - Text = "# My new blog", - Title = "Blog from " + DateTime.UtcNow.ToString("g"), - SourceLocation = $"{date:yyyy-MM-dd-HH-mm}_blog.md", - CreatedAt = date, - ModifiedAt = date, - FullWidth = false, - ShowDetails = true, - Slug = "blog-" + date.ToString("yyyy-MM-dd-HH-mm"), - Visible = false + Text = "# My new blog", + Title = "Blog from " + DateTime.UtcNow.ToString("g"), + SourceLocation = $"{date:yyyy-MM-dd-HH-mm}_blog.md", + CreatedAt = date, + ModifiedAt = date, + FullWidth = false, + ShowDetails = true, + Slug = "blog-" + date.ToString("yyyy-MM-dd-HH-mm"), + Visible = false } ); } - + var blog = await service.GetForEdit(id.Value); - + if (blog is null) return NotFound(); @@ -118,7 +151,7 @@ public class BlogsController(BlogService service) : Controller return Redirect($"/blogs/{editBlog.Slug}"); } - + [HttpPost] [Route("preview")] public IActionResult Preview([FromForm] EditBlog editBlog) @@ -141,16 +174,16 @@ public class BlogsController(BlogService service) : Controller } }); } - + [HttpDelete] [Route("{id:int}")] public async Task Delete(int id) { var result = await service.Delete(id); - + if (!result) return NotFound(); - + HttpContext.Response.Headers.Append("Hx-Redirect", "/"); return Ok(); } diff --git a/src/FastBlog.Web/Models/EditBlog.cs b/src/FastBlog.Web/Models/EditBlog.cs index aab8636..7b7603c 100644 --- a/src/FastBlog.Web/Models/EditBlog.cs +++ b/src/FastBlog.Web/Models/EditBlog.cs @@ -1,4 +1,6 @@ -namespace FastBlog.Web.Models; +using System; + +namespace FastBlog.Web.Models; public sealed class EditBlog { diff --git a/src/FastBlog.Web/Program.cs b/src/FastBlog.Web/Program.cs index 8f9fddd..9e1bb97 100644 --- a/src/FastBlog.Web/Program.cs +++ b/src/FastBlog.Web/Program.cs @@ -3,7 +3,6 @@ using FastBlog.Web; var builder = WebApplication.CreateBuilder(args); -// Add services to the container. if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == Environments.Development) builder.Services.AddControllersWithViews().AddRazorRuntimeCompilation(); else @@ -14,11 +13,9 @@ builder.Services.Configure(builder.Configuration.GetSection("Dis var app = builder.Build(); -// Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. } app.UseHttpsRedirection(); diff --git a/src/FastBlog.Web/Views/Blogs/Index.cshtml b/src/FastBlog.Web/Views/Blogs/Index.cshtml index 913e619..8984ef1 100644 --- a/src/FastBlog.Web/Views/Blogs/Index.cshtml +++ b/src/FastBlog.Web/Views/Blogs/Index.cshtml @@ -4,31 +4,11 @@ ViewBag.Title = Model.Metadata.Title; } -@await Html.PartialAsync("Blogs/BlogContent", Model) +@await Html.PartialAsync("Blogs/Content", Model)

-
-
-

Editor's controls

-
- -
- -
- -
-
- -
-
+ @await Html.PartialAsync("Blogs/ManageCard", Model.Metadata) + \ No newline at end of file diff --git a/src/FastBlog.Web/Views/Blogs/List.cshtml b/src/FastBlog.Web/Views/Blogs/List.cshtml new file mode 100644 index 0000000..2ff6fd9 --- /dev/null +++ b/src/FastBlog.Web/Views/Blogs/List.cshtml @@ -0,0 +1,123 @@ +@using FastBlog.Web.Helpers +@model FastBlog.Core.Models.PagedResponse + + +@{ + ViewBag.Title = "Blogs"; +} + +
+ + +
+
+
+ + + + +
+ +

List of Blogs

+
+ + + @if (Model.Data.Length is 0) + { +

No blogs have been found

+ } + +
+
+

Name

+
+
+

Published

+
+
+

Visible

+
+
+

Actions

+
+
+ + @foreach (var blog in Model.Data) + { +
+
+

+ @if (blog.CreatedAt < DateTime.UtcNow) + { + @blog.Title + } + else + { + @blog.Title + } +

+
+
+

+ @if (blog.CreatedAt < DateTime.UtcNow) + { + @blog.CreatedAt.ToString("yy-MM-dd HH:mm:ss") + ; + } + else + { + @blog.CreatedAt.ToString("yy-MM-dd HH:mm:ss") + ; + } +

+
+
+

+ + @if (blog.Visible) + { + Yes + } + else + { + No + } + +

+
+ +
+
+ } + +
+
+ + +
+
+
+
\ No newline at end of file diff --git a/src/FastBlog.Web/Views/Blogs/Preview.cshtml b/src/FastBlog.Web/Views/Blogs/Preview.cshtml index 81e6df4..a6b411e 100644 --- a/src/FastBlog.Web/Views/Blogs/Preview.cshtml +++ b/src/FastBlog.Web/Views/Blogs/Preview.cshtml @@ -1,6 +1,6 @@ @model FastBlog.Core.Models.Blogs.Blog -@await Html.PartialAsync("Blogs/BlogContent", Model) +@await Html.PartialAsync("Blogs/Content", Model)
diff --git a/src/FastBlog.Web/Views/Blogs/Unpublished.cshtml b/src/FastBlog.Web/Views/Blogs/Unpublished.cshtml new file mode 100644 index 0000000..c2a127a --- /dev/null +++ b/src/FastBlog.Web/Views/Blogs/Unpublished.cshtml @@ -0,0 +1,22 @@ +@model FastBlog.Core.Models.Blogs.Blog + +@{ + ViewBag.Title = Model.Metadata.Title; +} + +@if(DateTime.UtcNow < Model.Metadata.CreatedAt) +{ +
+

Unpublished blog

+

This blog would not be visible and accessible until @Model.Metadata.CreatedAt.ToString("g")

+
+} + +@await Html.PartialAsync("Blogs/Content", Model) + +
+ +
+
+ @await Html.PartialAsync("Blogs/ManageCard", Model.Metadata) +
\ No newline at end of file diff --git a/src/FastBlog.Web/Views/Shared/Blogs/BlogContent.cshtml b/src/FastBlog.Web/Views/Shared/Blogs/Content.cshtml similarity index 100% rename from src/FastBlog.Web/Views/Shared/Blogs/BlogContent.cshtml rename to src/FastBlog.Web/Views/Shared/Blogs/Content.cshtml diff --git a/src/FastBlog.Web/Views/Shared/Blogs/ManageCard.cshtml b/src/FastBlog.Web/Views/Shared/Blogs/ManageCard.cshtml new file mode 100644 index 0000000..b6f183c --- /dev/null +++ b/src/FastBlog.Web/Views/Shared/Blogs/ManageCard.cshtml @@ -0,0 +1,23 @@ +@model FastBlog.Core.Models.Blogs.BlogMeta + +
+
+

Editor's controls

+
+ +
+ +
+ +
+
+ +
\ No newline at end of file diff --git a/src/FastBlog.Web/Views/Shared/_Layout.cshtml b/src/FastBlog.Web/Views/Shared/_Layout.cshtml index f18d69a..58c426e 100644 --- a/src/FastBlog.Web/Views/Shared/_Layout.cshtml +++ b/src/FastBlog.Web/Views/Shared/_Layout.cshtml @@ -30,7 +30,7 @@ return;