262 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			262 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using DysonNetwork.Shared.Proto;
 | |
| using Microsoft.AspNetCore.Authorization;
 | |
| using Microsoft.AspNetCore.Mvc;
 | |
| using Microsoft.EntityFrameworkCore;
 | |
| 
 | |
| namespace DysonNetwork.Sphere.WebReader;
 | |
| 
 | |
| [ApiController]
 | |
| [Route("/api/feeds")]
 | |
| public class WebFeedPublicController(
 | |
|     AppDatabase db,
 | |
|     WebFeedService webFeed
 | |
| ) : ControllerBase
 | |
| {
 | |
|     /// <summary>
 | |
|     /// Subscribe to a web feed
 | |
|     /// </summary>
 | |
|     /// <param name="feedId">The ID of the feed to subscribe to</param>
 | |
|     /// <returns>Subscription details</returns>
 | |
|     [HttpPost("{feedId:guid}/subscribe")]
 | |
|     [Authorize]
 | |
|     public async Task<IActionResult> Subscribe(Guid feedId)
 | |
|     {
 | |
|         if (HttpContext.Items["CurrentUser"] is not Account currentUser)
 | |
|             return Unauthorized();
 | |
| 
 | |
|         var accountId = Guid.Parse(currentUser.Id);
 | |
| 
 | |
|         // Check if feed exists
 | |
|         var feed = await db.WebFeeds.FindAsync(feedId);
 | |
|         if (feed == null)
 | |
|             return NotFound("Feed not found");
 | |
| 
 | |
|         // Check if already subscribed
 | |
|         var existingSubscription = await db.WebFeedSubscriptions
 | |
|             .FirstOrDefaultAsync(s => s.FeedId == feedId && s.AccountId == accountId);
 | |
| 
 | |
|         if (existingSubscription != null)
 | |
|             return Ok(existingSubscription);
 | |
| 
 | |
|         // Create new subscription
 | |
|         var subscription = new WebFeedSubscription
 | |
|         {
 | |
|             FeedId = feedId,
 | |
|             AccountId = accountId
 | |
|         };
 | |
| 
 | |
|         db.WebFeedSubscriptions.Add(subscription);
 | |
|         await db.SaveChangesAsync();
 | |
| 
 | |
|         return CreatedAtAction(nameof(GetSubscriptionStatus), new { feedId }, subscription);
 | |
|     }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Unsubscribe from a web feed
 | |
|     /// </summary>
 | |
|     /// <param name="feedId">The ID of the feed to unsubscribe from</param>
 | |
|     [HttpDelete("{feedId:guid}/subscribe")]
 | |
|     [Authorize]
 | |
|     public async Task<IActionResult> Unsubscribe(Guid feedId)
 | |
|     {
 | |
|         if (HttpContext.Items["CurrentUser"] is not Account currentUser)
 | |
|             return Unauthorized();
 | |
| 
 | |
|         var accountId = Guid.Parse(currentUser.Id);
 | |
| 
 | |
|         var subscription = await db.WebFeedSubscriptions
 | |
|             .FirstOrDefaultAsync(s => s.FeedId == feedId && s.AccountId == accountId);
 | |
| 
 | |
|         if (subscription == null)
 | |
|             return NoContent();
 | |
| 
 | |
|         db.WebFeedSubscriptions.Remove(subscription);
 | |
|         await db.SaveChangesAsync();
 | |
| 
 | |
|         return NoContent();
 | |
|     }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Get subscription status for the current user and a specific feed
 | |
|     /// </summary>
 | |
|     /// <param name="feedId">The ID of the feed to check</param>
 | |
|     /// <returns>Subscription status</returns>
 | |
|     [HttpGet("{feedId:guid}/subscription")]
 | |
|     [Authorize]
 | |
|     public async Task<ActionResult<WebFeedSubscription>> GetSubscriptionStatus(Guid feedId)
 | |
|     {
 | |
|         if (HttpContext.Items["CurrentUser"] is not Account currentUser)
 | |
|             return Unauthorized();
 | |
| 
 | |
|         var accountId = Guid.Parse(currentUser.Id);
 | |
| 
 | |
|         var subscription = await db.WebFeedSubscriptions
 | |
|             .Where(s => s.FeedId == feedId && s.AccountId == accountId)
 | |
|             .FirstOrDefaultAsync();
 | |
|         if (subscription is null)
 | |
|             return NotFound();
 | |
| 
 | |
|         return Ok(subscription);
 | |
|     }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// List all feeds the current user is subscribed to
 | |
|     /// </summary>
 | |
|     /// <returns>List of subscribed feeds</returns>
 | |
|     [HttpGet("subscribed")]
 | |
|     [Authorize]
 | |
|     public async Task<ActionResult<WebFeed>> GetSubscribedFeeds(
 | |
|         [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 query = db.WebFeedSubscriptions
 | |
|             .Where(s => s.AccountId == accountId)
 | |
|             .Include(s => s.Feed)
 | |
|             .ThenInclude(f => f.Publisher)
 | |
|             .OrderByDescending(s => s.CreatedAt);
 | |
| 
 | |
|         var totalCount = await query.CountAsync();
 | |
|         var subscriptions = await query
 | |
|             .Select(q => q.Feed)
 | |
|             .Skip(offset)
 | |
|             .Take(take)
 | |
|             .ToListAsync();
 | |
| 
 | |
|         Response.Headers["X-Total"] = totalCount.ToString();
 | |
|         return Ok(subscriptions);
 | |
|     }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Get articles from subscribed feeds
 | |
|     /// </summary>
 | |
|     [HttpGet]
 | |
|     [Authorize]
 | |
|     public async Task<ActionResult<WebFeed>> 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);
 | |
|     }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Get feed metadata by ID (public endpoint)
 | |
|     /// </summary>
 | |
|     /// <param name="feedId">The ID of the feed</param>
 | |
|     /// <returns>Feed metadata</returns>
 | |
|     [AllowAnonymous]
 | |
|     [HttpGet("{feedId:guid}")]
 | |
|     public async Task<ActionResult<WebFeed>> GetFeedById(Guid feedId)
 | |
|     {
 | |
|         var feed = await webFeed.GetFeedAsync(feedId);
 | |
|         if (feed == null)
 | |
|             return NotFound();
 | |
| 
 | |
|         return Ok(feed);
 | |
|     }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Get articles from a specific feed (public endpoint)
 | |
|     /// </summary>
 | |
|     /// <param name="feedId">The ID of the feed</param>
 | |
|     /// <param name="offset">Number of articles to skip</param>
 | |
|     /// <param name="take">Maximum number of articles to return</param>
 | |
|     /// <returns>List of articles from the feed</returns>
 | |
|     [AllowAnonymous]
 | |
|     [HttpGet("{feedId:guid}/articles")]
 | |
|     public async Task<ActionResult<WebArticle>> 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);
 | |
|     }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// Explore available web feeds
 | |
|     /// </summary>
 | |
|     [HttpGet("explore")]
 | |
|     [Authorize]
 | |
|     public async Task<ActionResult<WebFeed>> 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);
 | |
| 
 | |
|         var feedsQuery = db.WebFeeds
 | |
|             .Include(f => f.Publisher)
 | |
|             .OrderByDescending(f => f.CreatedAt)
 | |
|             .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))
 | |
|             );
 | |
|         }
 | |
| 
 | |
|         var totalCount = await feedsQuery.CountAsync();
 | |
|         var feeds = await feedsQuery
 | |
|             .Skip(offset)
 | |
|             .Take(take)
 | |
|             .ToListAsync();
 | |
| 
 | |
|         Response.Headers["X-Total"] = totalCount.ToString();
 | |
|         return Ok(feeds);
 | |
|     }
 | |
| } |