From ca5be5a01c8d55a57216ca05f3e29b7766942208 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 29 Jun 2025 22:02:26 +0800 Subject: [PATCH] :recycle: Refined web feed APIs --- .../Connection/WebReader/WebFeedController.cs | 137 +++++++++++++----- .../Connection/WebReader/WebFeedService.cs | 66 ++++++--- 2 files changed, 145 insertions(+), 58 deletions(-) diff --git a/DysonNetwork.Sphere/Connection/WebReader/WebFeedController.cs b/DysonNetwork.Sphere/Connection/WebReader/WebFeedController.cs index 0359197..56ff646 100644 --- a/DysonNetwork.Sphere/Connection/WebReader/WebFeedController.cs +++ b/DysonNetwork.Sphere/Connection/WebReader/WebFeedController.cs @@ -1,62 +1,123 @@ using System.ComponentModel.DataAnnotations; -using DysonNetwork.Sphere.Permission; +using DysonNetwork.Sphere.Publisher; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; namespace DysonNetwork.Sphere.Connection.WebReader; [Authorize] [ApiController] -[Route("/feeds")] -public class WebFeedController(WebFeedService webFeedService, AppDatabase database) : ControllerBase +[Route("/publishers/{pubName}/feeds")] +public class WebFeedController(WebFeedService webFeed, PublisherService ps) : ControllerBase { - public class CreateWebFeedRequest + public record WebFeedRequest( + [MaxLength(8192)] string? Url, + [MaxLength(4096)] string? Title, + [MaxLength(8192)] string? Description + ); + + [HttpGet] + public async Task ListFeeds([FromRoute] string pubName) { - [Required] - [MaxLength(8192)] - public required string Url { get; set; } - - [Required] - [MaxLength(4096)] - public required string Title { get; set; } - - [MaxLength(8192)] - public string? Description { get; set; } + var publisher = await ps.GetPublisherByName(pubName); + if (publisher is null) return NotFound(); + var feeds = await webFeed.GetFeedsByPublisherAsync(publisher.Id); + return Ok(feeds); } - - [HttpPost] - public async Task CreateWebFeed([FromBody] CreateWebFeedRequest request) + [HttpGet("{id:guid}")] + public async Task GetFeed([FromRoute] string pubName, Guid id) { - var feed = await webFeedService.CreateWebFeedAsync(request, User); + var publisher = await ps.GetPublisherByName(pubName); + if (publisher is null) return NotFound(); + + var feed = await webFeed.GetFeedAsync(id, publisherId: publisher.Id); + if (feed == null) + return NotFound(); + return Ok(feed); } - - [HttpPost("scrape/{feedId}")] - [RequiredPermission("maintenance", "web-feeds")] - public async Task ScrapeFeed(Guid feedId) + + [HttpPost] + [Authorize] + public async Task CreateWebFeed([FromRoute] string pubName, [FromBody] WebFeedRequest request) { - var feed = await database.Set().FindAsync(feedId); + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + + if (string.IsNullOrWhiteSpace(request.Url) || string.IsNullOrWhiteSpace(request.Title)) + return BadRequest("Url and title are required"); + + var publisher = await ps.GetPublisherByName(pubName); + if (publisher is null) return NotFound(); + + if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor)) + return StatusCode(403, "You must be an editor of the publisher to create a web feed"); + + var feed = await webFeed.CreateWebFeedAsync(publisher, request); + return Ok(feed); + } + + [HttpPatch("{id:guid}")] + [Authorize] + public async Task UpdateFeed([FromRoute] string pubName, Guid id, [FromBody] WebFeedRequest request) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + + var publisher = await ps.GetPublisherByName(pubName); + if (publisher is null) return NotFound(); + + if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor)) + return StatusCode(403, "You must be an editor of the publisher to update a web feed"); + + var feed = await webFeed.GetFeedAsync(id, publisherId: publisher.Id); + if (feed == null) + return NotFound(); + + feed = await webFeed.UpdateFeedAsync(feed, request); + return Ok(feed); + } + + [HttpDelete("{id:guid}")] + [Authorize] + public async Task DeleteFeed([FromRoute] string pubName, Guid id) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + + var publisher = await ps.GetPublisherByName(pubName); + if (publisher is null) return NotFound(); + + if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor)) + return StatusCode(403, "You must be an editor of the publisher to delete a web feed"); + + var feed = await webFeed.GetFeedAsync(id, publisherId: publisher.Id); + if (feed == null) + return NotFound(); + + var result = await webFeed.DeleteFeedAsync(id); + if (!result) + return NotFound(); + return NoContent(); + } + + [HttpPost("{id:guid}/scrap")] + [Authorize] + public async Task Scrap([FromRoute] string pubName, Guid id) + { + if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized(); + + var publisher = await ps.GetPublisherByName(pubName); + if (publisher is null) return NotFound(); + + if (!await ps.IsMemberWithRole(publisher.Id, currentUser.Id, PublisherMemberRole.Editor)) + return StatusCode(403, "You must be an editor of the publisher to scrape a web feed"); + + var feed = await webFeed.GetFeedAsync(id, publisherId: publisher.Id); if (feed == null) { return NotFound(); } - await webFeedService.ScrapeFeedAsync(feed); - return Ok(); - } - - [HttpPost("scrape-all")] - [RequiredPermission("maintenance", "web-feeds")] - public async Task ScrapeAllFeeds() - { - var feeds = await database.Set().ToListAsync(); - foreach (var feed in feeds) - { - await webFeedService.ScrapeFeedAsync(feed); - } - + await webFeed.ScrapeFeedAsync(feed); return Ok(); } } diff --git a/DysonNetwork.Sphere/Connection/WebReader/WebFeedService.cs b/DysonNetwork.Sphere/Connection/WebReader/WebFeedService.cs index 4c2a2c1..70cc273 100644 --- a/DysonNetwork.Sphere/Connection/WebReader/WebFeedService.cs +++ b/DysonNetwork.Sphere/Connection/WebReader/WebFeedService.cs @@ -1,9 +1,6 @@ -using System.Security.Claims; using System.ServiceModel.Syndication; using System.Xml; -using DysonNetwork.Sphere.Account; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; namespace DysonNetwork.Sphere.Connection.WebReader; @@ -11,30 +8,17 @@ public class WebFeedService( AppDatabase database, IHttpClientFactory httpClientFactory, ILogger logger, - AccountService accountService, WebReaderService webReaderService ) { - public async Task CreateWebFeedAsync(WebFeedController.CreateWebFeedRequest request, - ClaimsPrincipal claims) + public async Task CreateWebFeedAsync(Publisher.Publisher publisher, WebFeedController.WebFeedRequest request) { - if (claims.Identity?.Name == null) - { - throw new UnauthorizedAccessException(); - } - - var account = await accountService.LookupAccount(claims.Identity.Name); - if (account == null) - { - throw new UnauthorizedAccessException(); - } - var feed = new WebFeed { - Url = request.Url, - Title = request.Title, + Url = request.Url!, + Title = request.Title!, Description = request.Description, - PublisherId = account.Id, + PublisherId = publisher.Id, }; database.Set().Add(feed); @@ -43,6 +27,48 @@ public class WebFeedService( return feed; } + public async Task GetFeedAsync(Guid id, Guid? publisherId = null) + { + var query = database.WebFeeds.Where(a => a.Id == id).AsQueryable(); + if (publisherId.HasValue) + query = query.Where(a => a.PublisherId == publisherId.Value); + return await query.FirstOrDefaultAsync(); + } + + public async Task> GetFeedsByPublisherAsync(Guid publisherId) + { + return await database.WebFeeds.Where(a => a.PublisherId == publisherId).ToListAsync(); + } + + public async Task UpdateFeedAsync(WebFeed feed, WebFeedController.WebFeedRequest request) + { + if (request.Url is not null) + feed.Url = request.Url; + if (request.Title is not null) + feed.Title = request.Title; + if (request.Description is not null) + feed.Description = request.Description; + + database.Update(feed); + await database.SaveChangesAsync(); + + return feed; + } + + public async Task DeleteFeedAsync(Guid id) + { + var feed = await database.WebFeeds.FindAsync(id); + if (feed == null) + { + return false; + } + + database.WebFeeds.Remove(feed); + await database.SaveChangesAsync(); + + return true; + } + public async Task ScrapeFeedAsync(WebFeed feed, CancellationToken cancellationToken = default) { var httpClient = httpClientFactory.CreateClient();