From 2002b8b232833817a90b30a7da9b92ee0a20cd93 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sun, 29 Jun 2025 16:12:16 +0100 Subject: [PATCH] refactoring --- .../AuthorizationFailureException.cs | 17 ++ src/Yavsc.Server/Helpers/UserHelpers.cs | 2 +- src/Yavsc.Server/Services/BlogSpotService.cs | 162 ++++++++++++++++ .../Communicating/BlogspotController.cs | 178 +++++------------- src/Yavsc/Extensions/HostingExtensions.cs | 10 +- 5 files changed, 225 insertions(+), 144 deletions(-) create mode 100644 src/Yavsc.Server/Exceptions/AuthorizationFailureException.cs create mode 100644 src/Yavsc.Server/Services/BlogSpotService.cs diff --git a/src/Yavsc.Server/Exceptions/AuthorizationFailureException.cs b/src/Yavsc.Server/Exceptions/AuthorizationFailureException.cs new file mode 100644 index 00000000..a3c5105e --- /dev/null +++ b/src/Yavsc.Server/Exceptions/AuthorizationFailureException.cs @@ -0,0 +1,17 @@ +namespace Yavsc.Server.Exceptions; + +[Serializable] +public class AuthorizationFailureException : Exception +{ + public AuthorizationFailureException(Microsoft.AspNetCore.Authorization.AuthorizationResult auth) : base(auth?.Failure?.ToString()??auth?.ToString()??"AuthorizationResult failure") + { + } + + public AuthorizationFailureException(string? message) : base(message) + { + } + + public AuthorizationFailureException(string? message, Exception? innerException) : base(message, innerException) + { + } +} diff --git a/src/Yavsc.Server/Helpers/UserHelpers.cs b/src/Yavsc.Server/Helpers/UserHelpers.cs index 587bd29e..666d6ba8 100644 --- a/src/Yavsc.Server/Helpers/UserHelpers.cs +++ b/src/Yavsc.Server/Helpers/UserHelpers.cs @@ -24,7 +24,7 @@ namespace Yavsc.Helpers return user.Identity.IsAuthenticated; } - public static IEnumerable UserPosts(this ApplicationDbContext dbContext, string posterId, string readerId) + public static IEnumerable UserPosts(this ApplicationDbContext dbContext, string posterId, string? readerId) { if (readerId == null) { diff --git a/src/Yavsc.Server/Services/BlogSpotService.cs b/src/Yavsc.Server/Services/BlogSpotService.cs new file mode 100644 index 00000000..e12f228b --- /dev/null +++ b/src/Yavsc.Server/Services/BlogSpotService.cs @@ -0,0 +1,162 @@ + + +using System.Diagnostics; +using System.Security.Claims; +using Microsoft.AspNetCore.Authorization; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Differencing; +using Microsoft.EntityFrameworkCore; +using Org.BouncyCastle.Tls; +using Yavsc.Helpers; +using Yavsc.Models; +using Yavsc.Models.Blog; +using Yavsc.Server.Exceptions; +using Yavsc.ViewModels.Auth; +using Yavsc.ViewModels.Blog; + +public class BlogSpotService +{ + private readonly ApplicationDbContext _context; + private readonly IAuthorizationService _authorizationService; + + + public BlogSpotService(ApplicationDbContext context, + IAuthorizationService authorizationService) + { + _authorizationService = authorizationService; + _context = context; + } + + public BlogPost Create(string userId, BlogPostInputViewModel blogInput) + { + BlogPost post = new BlogPost + { + Title = blogInput.Title, + Content = blogInput.Content, + Photo = blogInput.Photo, + AuthorId = userId + }; + _context.BlogSpot.Add(post); + _context.SaveChanges(userId); + return post; + } + public async Task GetPostForEdition(ClaimsPrincipal user, long blogPostId) + { + var blog = await _context.BlogSpot.Include(x => x.Author).Include(x => x.ACL).SingleAsync(m => m.Id == blogPostId); + var auth = await _authorizationService.AuthorizeAsync(user, blog, new EditPermission()); + if (!auth.Succeeded) + { + throw new AuthorizationFailureException(auth); + } + return blog; + } + + public async Task Details(ClaimsPrincipal user, long blogPostId) + { + + BlogPost blog = await _context.BlogSpot + .Include(p => p.Author) + .Include(p => p.Tags) + .Include(p => p.Comments) + .Include(p => p.ACL) + .SingleAsync(m => m.Id == blogPostId); + if (blog == null) + { + return null; + } + var auth = await _authorizationService.AuthorizeAsync(user, blog, new ReadPermission()); + if (!auth.Succeeded) + { + throw new AuthorizationFailureException(auth); + } + foreach (var c in blog.Comments) + { + c.Author = _context.Users.First(u => u.Id == c.AuthorId); + } + return blog; + } + + public async Task Modify(ClaimsPrincipal user, BlogPostEditViewModel blogEdit) + { + var blog = _context.BlogSpot.SingleOrDefault(b => b.Id == blogEdit.Id); + Debug.Assert(blog != null); + var auth = await _authorizationService.AuthorizeAsync(user, blog, new EditPermission()); + if (!auth.Succeeded) + { + throw new AuthorizationFailureException(auth); + } + blog.Content = blogEdit.Content; + blog.Title = blogEdit.Title; + blog.Photo = blogEdit.Photo; + blog.ACL = blogEdit.ACL; + // saves the change + _context.Update(blog); + _context.SaveChanges(user.GetUserId()); + } + + public async Task>> IndexByTitle(ClaimsPrincipal user, string id, int skip = 0, int take = 25) + { + IEnumerable posts; + + if (user.Identity.IsAuthenticated) + { + string viewerId = user.GetUserId(); + long[] usercircles = await _context.Circle.Include(c => c.Members). + Where(c => c.Members.Any(m => m.MemberId == viewerId)) + .Select(c => c.Id).ToArrayAsync(); + + posts = _context.BlogSpot + .Include(b => b.Author) + .Include(p => p.ACL) + .Include(p => p.Tags) + .Include(p => p.Comments) + .Where(p => (p.ACL.Count == 0) + || (p.AuthorId == viewerId) + || (usercircles != null && p.ACL.Any(a => usercircles.Contains(a.CircleId))) + ); + } + else + { + posts = _context.blogspotPublications + .Include(p => p.BlogPost) + .Include(b => b.BlogPost.Author) + .Include(p => p.BlogPost.ACL) + .Include(p => p.BlogPost.Tags) + .Include(p => p.BlogPost.Comments) + .Where(p => p.BlogPost.ACL.Count == 0).Select(p => p.BlogPost).ToArray(); + } + + var data = posts.OrderByDescending(p => p.DateCreated); + var grouped = data.GroupBy(p => p.Title).Skip(skip).Take(take); + return grouped; + } + + public async Task Delete(ClaimsPrincipal user, long id) + { + var uid = user.GetUserId(); + BlogPost blog = _context.BlogSpot.Single(m => m.Id == id); + + _context.BlogSpot.Remove(blog); + _context.SaveChanges(user.GetUserId()); + } + + public async Task> UserPosts( + string posterName, + string? readerId, + int pageLen = 10, + int pageNum = 0) + { + string? posterId = (await _context.Users.SingleOrDefaultAsync(u => u.UserName == posterName))?.Id ?? null; + if (posterId == null) return Array.Empty(); + return _context.UserPosts(posterId, readerId); + } + + public object? ByTitle(string title) + { + return _context.BlogSpot.Include( + b => b.Author + ).Where(x => x.Title == title).OrderByDescending( + x => x.DateCreated + ).ToList(); + } +} diff --git a/src/Yavsc/Controllers/Communicating/BlogspotController.cs b/src/Yavsc/Controllers/Communicating/BlogspotController.cs index 2bc36456..25bfad77 100644 --- a/src/Yavsc/Controllers/Communicating/BlogspotController.cs +++ b/src/Yavsc/Controllers/Communicating/BlogspotController.cs @@ -1,4 +1,4 @@ -using System.Security.Claims; + using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Yavsc.Models; @@ -9,6 +9,7 @@ using Yavsc.Helpers; using Microsoft.Extensions.Options; using Microsoft.EntityFrameworkCore; using Yavsc.ViewModels.Blog; +using Yavsc.Server.Exceptions; // For more information on enabling Web API for empty projects, visit http://go.microsoft.com/fwlink/?LinkID=397860 @@ -21,112 +22,63 @@ namespace Yavsc.Controllers private readonly IAuthorizationService _authorizationService; readonly RequestLocalizationOptions _localisationOptions; + readonly BlogSpotService blogSpotService; public BlogspotController( ApplicationDbContext context, ILoggerFactory loggerFactory, IAuthorizationService authorizationService, - IOptions localisationOptions) + IOptions localisationOptions, + BlogSpotService blogSpotService) { _context = context; _logger = loggerFactory.CreateLogger(); _authorizationService = authorizationService; _localisationOptions = localisationOptions.Value; + this.blogSpotService = blogSpotService; } // GET: Blog [AllowAnonymous] - public async Task Index(string id, int skip=0, int take=25) + public async Task Index(string id, int skip = 0, int take = 25) { - if (!string.IsNullOrEmpty(id)) { - return View("UserPosts", await UserPosts(id)); - } - IEnumerable posts; - - if (User.Identity.IsAuthenticated) + if (!string.IsNullOrEmpty(id)) { - string viewerId = User.GetUserId(); - long[] usercircles = await _context.Circle.Include(c=>c.Members). - Where(c=>c.Members.Any(m=>m.MemberId == viewerId)) - .Select(c=>c.Id).ToArrayAsync(); - - posts = _context.BlogSpot - .Include(b => b.Author) - .Include(p=>p.ACL) - .Include(p=>p.Tags) - .Include(p=>p.Comments) - .Where(p =>(p.ACL.Count == 0) - || (p.AuthorId == viewerId) - || (usercircles != null && p.ACL.Any(a => usercircles.Contains(a.CircleId))) - ); + return View("UserPosts", + await blogSpotService.UserPosts(id, User.GetUserId(), + skip, take)); } - else - { - posts = _context.blogspotPublications - .Include(p=>p.BlogPost) - .Include(b => b.BlogPost.Author) - .Include(p=>p.BlogPost.ACL) - .Include(p=>p.BlogPost.Tags) - .Include(p=>p.BlogPost.Comments) - .Where(p => p.BlogPost.ACL.Count == 0 ).Select(p=>p.BlogPost).ToArray(); - } - - var data = posts.OrderByDescending( p=> p.DateCreated); - var grouped = data.GroupBy(p=> p.Title).Skip(skip).Take(take); - - return View(grouped); + var byTitle = await this.blogSpotService.IndexByTitle(User, id, skip, take); + return View(byTitle); } [Route("~/Title/{id?}")] [AllowAnonymous] public IActionResult Title(string id) { - var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); ViewData["Title"] = id; - return View("Title", _context.BlogSpot.Include( - b => b.Author - ).Where(x => x.Title == id && (x.AuthorId == uid )).OrderByDescending( - x => x.DateCreated - ).ToList()); + return View("Title", blogSpotService.ByTitle(id)); } - private async Task> UserPosts(string userName, int pageLen=10, int pageNum=0) + private async Task> UserPosts(string userName, int pageLen = 10, int pageNum = 0) { - string posterId = (await _context.Users.SingleOrDefaultAsync(u=>u.UserName == userName))?.Id ?? null ; - return _context.UserPosts(posterId, User.Identity.Name); + return await blogSpotService.UserPosts(userName, User.GetUserId(), pageLen, pageNum); + } // GET: Blog/Details/5 [AllowAnonymous] public async Task Details(long? id) { - if (id == null) - { - return NotFound(); - } + if (id == null) return this.NotFound(); - BlogPost blog = _context.BlogSpot - .Include(p => p.Author) - .Include(p => p.Tags) - .Include(p => p.Comments) - .Include(p => p.ACL) - .Single(m => m.Id == id); - if (blog == null) - { - return NotFound(); - } - if ( _authorizationService.AuthorizeAsync(User, blog, new ReadPermission()).IsFaulted) - { - return new ChallengeResult(); - } - foreach (var c in blog.Comments) { - c.Author = _context.Users.First(u=>u.Id==c.AuthorId); - } + var blog = await blogSpotService.Details(User, id.Value); ViewData["apicmtctlr"] = "/api/blogcomments"; ViewData["moderatoFlag"] = User.IsInRole(Constants.BlogModeratorGroupName); + return View(blog); } void SetLangItems() { - ViewBag.LangItems = _localisationOptions.SupportedUICultures.Select + ViewBag.LangItems = _localisationOptions.SupportedUICultures?.Select ( sc => new SelectListItem { Value = sc.IetfLanguageTag, Text = sc.NativeName, Selected = System.Globalization.CultureInfo.CurrentUICulture == sc } ); @@ -136,9 +88,10 @@ namespace Yavsc.Controllers [Authorize()] public IActionResult Create(string title) { - var result = new BlogPostInputViewModel{Title=title + var result = new BlogPostInputViewModel + { + Title = title }; - ViewData["PostTarget"]="Create"; SetLangItems(); return View(result); } @@ -149,20 +102,11 @@ namespace Yavsc.Controllers { if (ModelState.IsValid) { - BlogPost post = new BlogPost - { - Title = blogInput.Title, - Content = blogInput.Content, - Photo = blogInput.Photo, - AuthorId = User.GetUserId() - }; - _context.BlogSpot.Add(post); - _context.SaveChanges(User.GetUserId()); + BlogPost post = blogSpotService.Create(User.GetUserId(), + blogInput); return RedirectToAction("Index"); } - ModelState.AddModelError("Unknown","Invalid Blog posted ..."); - ViewData["PostTarget"]="Create"; - return View("Edit",blogInput); + return View("Edit", blogInput); } [Authorize()] @@ -173,26 +117,13 @@ namespace Yavsc.Controllers { return NotFound(); } - - ViewData["PostTarget"]="Edit"; - BlogPost blog = _context.BlogSpot.Include(x => x.Author).Include(x => x.ACL).Single(m => m.Id == id); - - if (blog == null) + try { - return NotFound(); - } - if (!_authorizationService.AuthorizeAsync(User, blog, new EditPermission()).IsFaulted) - { - ViewBag.ACL = _context.Circle.Where( - c=>c.OwnerId == blog.AuthorId) - .Select( - c => new SelectListItem - { - Text = c.Name, - Value = c.Id.ToString(), - Selected = blog.AuthorizeCircle(c.Id) - }  - ); + BlogPost blog = await blogSpotService.GetPostForEdition(User, id.Value); + if (blog == null) + { + return NotFound(); + } SetLangItems(); return View(new BlogPostEditViewModel { @@ -201,9 +132,10 @@ namespace Yavsc.Controllers Content = blog.Content, ACL = blog.ACL, Photo = blog.Photo - }); + }); + } - else + catch (AuthorizationFailureException) { return new ChallengeResult(); } @@ -211,65 +143,41 @@ namespace Yavsc.Controllers // POST: Blog/Edit/5 [HttpPost] - [ValidateAntiForgeryToken,Authorize()] + [ValidateAntiForgeryToken, Authorize()] public async Task Edit(BlogPostEditViewModel blogEdit) { if (ModelState.IsValid) { - var blog = _context.BlogSpot.SingleOrDefault(b=>b.Id == blogEdit.Id); - if (blog == null) { - ModelState.AddModelError("Id", "not found"); - return View(); - } - if (!(await _authorizationService.AuthorizeAsync(User, blog, new EditPermission())).Succeeded) { - ViewData["StatusMessage"] = "Accès restreint"; - return new ChallengeResult(); - } - blog.Content=blogEdit.Content; - blog.Title = blogEdit.Title; - blog.Photo = blogEdit.Photo; - blog.ACL = blogEdit.ACL; - // saves the change - _context.Update(blog); - _context.SaveChanges(User.GetUserId()); + await blogSpotService.Modify(User, blogEdit); ViewData["StatusMessage"] = "Post modified"; return RedirectToAction("Index"); } - ViewData["PostTarget"]="Edit"; return View(blogEdit); } // GET: Blog/Delete/5 - [ActionName("Delete"),Authorize()] - public IActionResult Delete(long? id) + [ActionName("Delete"), Authorize()] + public async Task Delete(long? id) { if (id == null) { return NotFound(); } - BlogPost blog = _context.BlogSpot.Include( - b => b.Author - ).Single(m => m.Id == id); + BlogPost blog = await blogSpotService.GetPostForEdition(User, id.Value); if (blog == null) { return NotFound(); } - return View(blog); } // POST: Blog/Delete/5 [HttpPost, ActionName("Delete"), Authorize("IsTheAuthor")] [ValidateAntiForgeryToken] - public IActionResult DeleteConfirmed(long id) + public async Task DeleteConfirmed(long id) { - var uid = User.GetUserId(); - BlogPost blog = _context.BlogSpot.Single(m => m.Id == id); - - _context.BlogSpot.Remove(blog); - _context.SaveChanges(User.GetUserId()); - + await blogSpotService.Delete(User, id); return RedirectToAction("Index"); } } diff --git a/src/Yavsc/Extensions/HostingExtensions.cs b/src/Yavsc/Extensions/HostingExtensions.cs index 27acbaee..b1189961 100644 --- a/src/Yavsc/Extensions/HostingExtensions.cs +++ b/src/Yavsc/Extensions/HostingExtensions.cs @@ -39,7 +39,6 @@ namespace Yavsc.Extensions; public static class HostingExtensions { - #region files config public static IApplicationBuilder ConfigureFileServerApp(this IApplicationBuilder app, bool enableDirectoryBrowsing = false) { @@ -91,16 +90,10 @@ public static class HostingExtensions return app; } - #endregion - - internal static WebApplication ConfigureWebAppServices(this WebApplicationBuilder builder) { IServiceCollection services = LoadConfiguration(builder); - //services.AddRazorPages(); - - services.AddSession(); // TODO .AddServerSideSessionStore() @@ -148,7 +141,8 @@ public static class HostingExtensions .AddTransient() .AddTransient() .AddTransient((sp) => new FileDataStore("googledatastore", false)) - .AddTransient(); + .AddTransient() + .AddTransient(); // TODO for SMS: services.AddTransient();