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();