diff --git a/DysonNetwork.Sphere/WebReader/WebFeedController.cs b/DysonNetwork.Sphere/WebReader/WebFeedController.cs index ff22075..881f419 100644 --- a/DysonNetwork.Sphere/WebReader/WebFeedController.cs +++ b/DysonNetwork.Sphere/WebReader/WebFeedController.cs @@ -1,6 +1,5 @@ using System.ComponentModel.DataAnnotations; using DysonNetwork.Shared.Proto; -using DysonNetwork.Sphere.Publisher; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/DysonNetwork.Sphere/WebReader/WebFeedPublicController.cs b/DysonNetwork.Sphere/WebReader/WebFeedPublicController.cs index 157c63b..a0e10a0 100644 --- a/DysonNetwork.Sphere/WebReader/WebFeedPublicController.cs +++ b/DysonNetwork.Sphere/WebReader/WebFeedPublicController.cs @@ -8,7 +8,8 @@ namespace DysonNetwork.Sphere.WebReader; [ApiController] [Route("/api/feeds")] public class WebFeedPublicController( - AppDatabase db + AppDatabase db, + WebFeedService webFeed ) : ControllerBase { /// @@ -102,9 +103,9 @@ public class WebFeedPublicController( /// List all feeds the current user is subscribed to /// /// List of subscribed feeds - [HttpGet("me")] + [HttpGet("subscribed")] [Authorize] - public async Task GetMySubscriptions( + public async Task> GetSubscribedFeeds( [FromQuery] int offset = 0, [FromQuery] int take = 20 ) @@ -129,4 +130,141 @@ public class WebFeedPublicController( Response.Headers["X-Total"] = totalCount.ToString(); return Ok(subscriptions); } + + /// + /// Get articles from subscribed feeds + /// + [HttpGet] + [Authorize] + public async Task> GetWebFeedArticles( + [FromQuery] int offset = 0, + [FromQuery] int take = 20 + ) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) + return Unauthorized(); + + var accountId = Guid.Parse(currentUser.Id); + + var subscribedFeedIds = await db.WebFeedSubscriptions + .Where(s => s.AccountId == accountId) + .Select(s => s.FeedId) + .ToListAsync(); + + var query = db.WebFeeds + .Where(f => subscribedFeedIds.Contains(f.Id)) + .Include(f => f.Publisher) + .OrderByDescending(f => f.CreatedAt); + + var totalCount = await query.CountAsync(); + var feeds = await query + .Skip(offset) + .Take(take) + .ToListAsync(); + + Response.Headers["X-Total"] = totalCount.ToString(); + return Ok(feeds); + } + + /// + /// Get feed metadata by ID (public endpoint) + /// + /// The ID of the feed + /// Feed metadata + [AllowAnonymous] + [HttpGet("{feedId:guid}")] + public async Task> GetFeedById(Guid feedId) + { + var feed = await webFeed.GetFeedAsync(feedId); + if (feed == null) + return NotFound(); + + return Ok(feed); + } + + /// + /// Get articles from a specific feed (public endpoint) + /// + /// The ID of the feed + /// Number of articles to skip + /// Maximum number of articles to return + /// List of articles from the feed + [AllowAnonymous] + [HttpGet("{feedId:guid}/articles")] + public async Task> GetFeedArticles( + [FromRoute] Guid feedId, + [FromQuery] int offset = 0, + [FromQuery] int take = 20 + ) + { + // Check if feed exists + var feedExists = await db.WebFeeds.AnyAsync(f => f.Id == feedId); + if (!feedExists) + return NotFound("Feed not found"); + + var query = db.WebArticles + .Where(a => a.FeedId == feedId) + .OrderByDescending(a => a.CreatedAt) + .Include(a => a.Feed) + .ThenInclude(f => f.Publisher); + + var totalCount = await query.CountAsync(); + var articles = await query + .Skip(offset) + .Take(take) + .ToListAsync(); + + Response.Headers["X-Total"] = totalCount.ToString(); + return Ok(articles); + } + + /// + /// Explore available web feeds + /// + [HttpGet("explore")] + [Authorize] + public async Task> ExploreFeeds( + [FromQuery] int offset = 0, + [FromQuery] int take = 20, + [FromQuery] string? query = null + ) + { + if (HttpContext.Items["CurrentUser"] is not Account currentUser) + return Unauthorized(); + + var accountId = Guid.Parse(currentUser.Id); + + // Get IDs of already subscribed feeds + var subscribedFeedIds = await db.WebFeedSubscriptions + .Where(s => s.AccountId == accountId) + .Select(s => s.FeedId) + .ToListAsync(); + + var feedsQuery = db.WebFeeds + .Include(f => f.Publisher) + .Where(f => !subscribedFeedIds.Contains(f.Id)) + .AsQueryable(); + + // Apply search filter if query is provided + if (!string.IsNullOrWhiteSpace(query)) + { + var searchTerm = $"%{query}%"; + feedsQuery = feedsQuery.Where(f => + EF.Functions.ILike(f.Title, searchTerm) || + (f.Description != null && EF.Functions.ILike(f.Description, searchTerm)) + ); + } + + // Order by most recently created first + feedsQuery = feedsQuery.OrderByDescending(f => f.CreatedAt); + + var totalCount = await feedsQuery.CountAsync(); + var feeds = await feedsQuery + .Skip(offset) + .Take(take) + .ToListAsync(); + + Response.Headers["X-Total"] = totalCount.ToString(); + return Ok(feeds); + } } \ No newline at end of file diff --git a/DysonNetwork.Sphere/WebReader/WebFeedService.cs b/DysonNetwork.Sphere/WebReader/WebFeedService.cs index 8af8a26..4100304 100644 --- a/DysonNetwork.Sphere/WebReader/WebFeedService.cs +++ b/DysonNetwork.Sphere/WebReader/WebFeedService.cs @@ -31,7 +31,10 @@ public class WebFeedService( public async Task GetFeedAsync(Guid id, Guid? publisherId = null) { - var query = database.WebFeeds.Where(a => a.Id == id).AsQueryable(); + var query = database.WebFeeds + .Include(a => a.Publisher) + .Where(a => a.Id == id) + .AsQueryable(); if (publisherId.HasValue) query = query.Where(a => a.PublisherId == publisherId.Value); return await query.FirstOrDefaultAsync();