Compare commits
3 Commits
3b679d6134
...
3ee5e5367d
Author | SHA1 | Date | |
---|---|---|---|
|
3ee5e5367d | ||
|
85fef30c7f | ||
|
e8d8dcbb2d |
@@ -55,6 +55,7 @@ public class AppDatabase(
|
|||||||
|
|
||||||
public DbSet<WebReader.WebArticle> WebArticles { get; set; } = null!;
|
public DbSet<WebReader.WebArticle> WebArticles { get; set; } = null!;
|
||||||
public DbSet<WebReader.WebFeed> WebFeeds { get; set; } = null!;
|
public DbSet<WebReader.WebFeed> WebFeeds { get; set; } = null!;
|
||||||
|
public DbSet<WebReader.WebFeedSubscription> WebFeedSubscriptions { get; set; } = null!;
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
|
2002
DysonNetwork.Sphere/Migrations/20250820060654_AddWebFeedSubscription.Designer.cs
generated
Normal file
2002
DysonNetwork.Sphere/Migrations/20250820060654_AddWebFeedSubscription.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddWebFeedSubscription : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "web_feed_subscriptions",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
feed_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||||
|
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_web_feed_subscriptions", x => x.id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_web_feed_subscriptions_web_feeds_feed_id",
|
||||||
|
column: x => x.feed_id,
|
||||||
|
principalTable: "web_feeds",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_web_feed_subscriptions_feed_id",
|
||||||
|
table: "web_feed_subscriptions",
|
||||||
|
column: "feed_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "web_feed_subscriptions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1458,6 +1458,42 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.ToTable("web_feeds", (string)null);
|
b.ToTable("web_feeds", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.WebReader.WebFeedSubscription", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
b.Property<Guid>("AccountId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("account_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("created_at");
|
||||||
|
|
||||||
|
b.Property<Instant?>("DeletedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("deleted_at");
|
||||||
|
|
||||||
|
b.Property<Guid>("FeedId")
|
||||||
|
.HasColumnType("uuid")
|
||||||
|
.HasColumnName("feed_id");
|
||||||
|
|
||||||
|
b.Property<Instant>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone")
|
||||||
|
.HasColumnName("updated_at");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_web_feed_subscriptions");
|
||||||
|
|
||||||
|
b.HasIndex("FeedId")
|
||||||
|
.HasDatabaseName("ix_web_feed_subscriptions_feed_id");
|
||||||
|
|
||||||
|
b.ToTable("web_feed_subscriptions", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("PostPostCategory", b =>
|
modelBuilder.Entity("PostPostCategory", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("CategoriesId")
|
b.Property<Guid>("CategoriesId")
|
||||||
@@ -1808,7 +1844,7 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPackOwnership", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPackOwnership", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack")
|
b.HasOne("DysonNetwork.Sphere.Sticker.StickerPack", "Pack")
|
||||||
.WithMany()
|
.WithMany("Ownerships")
|
||||||
.HasForeignKey("PackId")
|
.HasForeignKey("PackId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -1841,6 +1877,18 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Publisher");
|
b.Navigation("Publisher");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DysonNetwork.Sphere.WebReader.WebFeedSubscription", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DysonNetwork.Sphere.WebReader.WebFeed", "Feed")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("FeedId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("fk_web_feed_subscriptions_web_feeds_feed_id");
|
||||||
|
|
||||||
|
b.Navigation("Feed");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("PostPostCategory", b =>
|
modelBuilder.Entity("PostPostCategory", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null)
|
b.HasOne("DysonNetwork.Sphere.Post.PostCategory", null)
|
||||||
@@ -1936,6 +1984,8 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Sticker.StickerPack", b =>
|
||||||
{
|
{
|
||||||
|
b.Navigation("Ownerships");
|
||||||
|
|
||||||
b.Navigation("Stickers");
|
b.Navigation("Stickers");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -32,7 +32,8 @@ public class StickerPack : ModelBase
|
|||||||
[MaxLength(4096)] public string Description { get; set; } = string.Empty;
|
[MaxLength(4096)] public string Description { get; set; } = string.Empty;
|
||||||
[MaxLength(128)] public string Prefix { get; set; } = null!;
|
[MaxLength(128)] public string Prefix { get; set; } = null!;
|
||||||
|
|
||||||
public List<Sticker> Stickers { get; set; } = new();
|
public List<Sticker> Stickers { get; set; } = [];
|
||||||
|
[JsonIgnore] public List<StickerPackOwnership> Ownerships { get; set; } = [];
|
||||||
|
|
||||||
public Guid PublisherId { get; set; }
|
public Guid PublisherId { get; set; }
|
||||||
public Publisher.Publisher Publisher { get; set; } = null!;
|
public Publisher.Publisher Publisher { get; set; } = null!;
|
||||||
|
@@ -10,7 +10,12 @@ namespace DysonNetwork.Sphere.Sticker;
|
|||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/api/stickers")]
|
[Route("/api/stickers")]
|
||||||
public class StickerController(AppDatabase db, StickerService st, FileService.FileServiceClient files) : ControllerBase
|
public class StickerController(
|
||||||
|
AppDatabase db,
|
||||||
|
StickerService st,
|
||||||
|
Publisher.PublisherService ps,
|
||||||
|
FileService.FileServiceClient files
|
||||||
|
) : ControllerBase
|
||||||
{
|
{
|
||||||
private async Task<IActionResult> _CheckStickerPackPermissions(
|
private async Task<IActionResult> _CheckStickerPackPermissions(
|
||||||
Guid packId,
|
Guid packId,
|
||||||
@@ -26,12 +31,8 @@ public class StickerController(AppDatabase db, StickerService st, FileService.Fi
|
|||||||
return NotFound("Sticker pack not found");
|
return NotFound("Sticker pack not found");
|
||||||
|
|
||||||
var accountId = Guid.Parse(currentUser.Id);
|
var accountId = Guid.Parse(currentUser.Id);
|
||||||
var member = await db.PublisherMembers
|
if (!await ps.IsMemberWithRole(accountId, pack.PublisherId, requiredRole))
|
||||||
.FirstOrDefaultAsync(m => m.AccountId == accountId && m.PublisherId == pack.PublisherId);
|
|
||||||
if (member is null)
|
|
||||||
return StatusCode(403, "You are not a member of this publisher");
|
return StatusCode(403, "You are not a member of this publisher");
|
||||||
if (member.Role < requiredRole)
|
|
||||||
return StatusCode(403, $"You need to be at least a {requiredRole} to perform this action");
|
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
@@ -40,19 +41,38 @@ public class StickerController(AppDatabase db, StickerService st, FileService.Fi
|
|||||||
public async Task<ActionResult<List<StickerPack>>> ListStickerPacks(
|
public async Task<ActionResult<List<StickerPack>>> ListStickerPacks(
|
||||||
[FromQuery] int offset = 0,
|
[FromQuery] int offset = 0,
|
||||||
[FromQuery] int take = 20,
|
[FromQuery] int take = 20,
|
||||||
[FromQuery] string? pubName = null
|
[FromQuery(Name = "pub")] string? pubName = null,
|
||||||
|
[FromQuery(Name = "order")] string? order = null,
|
||||||
|
[FromQuery(Name = "query")] string? query = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Publisher.Publisher? publisher = null;
|
Publisher.Publisher? publisher = null;
|
||||||
if (pubName is not null)
|
if (pubName is not null)
|
||||||
publisher = await db.Publishers.FirstOrDefaultAsync(p => p.Name == pubName);
|
publisher = await db.Publishers.FirstOrDefaultAsync(p => p.Name == pubName);
|
||||||
|
|
||||||
var totalCount = await db.StickerPacks
|
var queryable = db.StickerPacks
|
||||||
.If(publisher is not null, q => q.Where(f => f.PublisherId == publisher!.Id))
|
.If(publisher is not null, q => q.Where(f => f.PublisherId == publisher!.Id));
|
||||||
|
|
||||||
|
if (order is not null)
|
||||||
|
{
|
||||||
|
queryable = order switch
|
||||||
|
{
|
||||||
|
"usage" => queryable.OrderByDescending(p => p.Ownerships.Count),
|
||||||
|
_ => queryable.OrderByDescending(p => p.CreatedAt)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(query))
|
||||||
|
{
|
||||||
|
queryable = queryable.Where(p =>
|
||||||
|
EF.Functions.ILike(p.Name, $"%{query}%") ||
|
||||||
|
EF.Functions.ILike(p.Description, $"%{query}%")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalCount = await queryable
|
||||||
.CountAsync();
|
.CountAsync();
|
||||||
var packs = await db.StickerPacks
|
var packs = await queryable
|
||||||
.If(publisher is not null, q => q.Where(f => f.PublisherId == publisher!.Id))
|
|
||||||
.OrderByDescending(e => e.CreatedAt)
|
|
||||||
.Skip(offset)
|
.Skip(offset)
|
||||||
.Take(take)
|
.Take(take)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
@@ -240,7 +260,8 @@ public class StickerController(AppDatabase db, StickerService st, FileService.Fi
|
|||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
|
|
||||||
var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, Publisher.PublisherMemberRole.Editor);
|
var permissionCheck =
|
||||||
|
await _CheckStickerPackPermissions(packId, currentUser, Publisher.PublisherMemberRole.Editor);
|
||||||
if (permissionCheck is not OkResult)
|
if (permissionCheck is not OkResult)
|
||||||
return permissionCheck;
|
return permissionCheck;
|
||||||
|
|
||||||
@@ -275,7 +296,8 @@ public class StickerController(AppDatabase db, StickerService st, FileService.Fi
|
|||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
|
|
||||||
var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, Publisher.PublisherMemberRole.Editor);
|
var permissionCheck =
|
||||||
|
await _CheckStickerPackPermissions(packId, currentUser, Publisher.PublisherMemberRole.Editor);
|
||||||
if (permissionCheck is not OkResult)
|
if (permissionCheck is not OkResult)
|
||||||
return permissionCheck;
|
return permissionCheck;
|
||||||
|
|
||||||
@@ -305,7 +327,8 @@ public class StickerController(AppDatabase db, StickerService st, FileService.Fi
|
|||||||
if (request.ImageId is null)
|
if (request.ImageId is null)
|
||||||
return BadRequest("Image is required.");
|
return BadRequest("Image is required.");
|
||||||
|
|
||||||
var permissionCheck = await _CheckStickerPackPermissions(packId, currentUser, Publisher.PublisherMemberRole.Editor);
|
var permissionCheck =
|
||||||
|
await _CheckStickerPackPermissions(packId, currentUser, Publisher.PublisherMemberRole.Editor);
|
||||||
if (permissionCheck is not OkResult)
|
if (permissionCheck is not OkResult)
|
||||||
return permissionCheck;
|
return permissionCheck;
|
||||||
|
|
||||||
@@ -396,4 +419,4 @@ public class StickerController(AppDatabase db, StickerService st, FileService.Fi
|
|||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using DysonNetwork.Pass.Account;
|
||||||
using DysonNetwork.Shared.Data;
|
using DysonNetwork.Shared.Data;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.WebReader;
|
namespace DysonNetwork.Sphere.WebReader;
|
||||||
@@ -44,4 +45,14 @@ public class WebFeed : ModelBase
|
|||||||
public Publisher.Publisher Publisher { get; set; } = null!;
|
public Publisher.Publisher Publisher { get; set; } = null!;
|
||||||
|
|
||||||
[JsonIgnore] public ICollection<WebArticle> Articles { get; set; } = new List<WebArticle>();
|
[JsonIgnore] public ICollection<WebArticle> Articles { get; set; } = new List<WebArticle>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class WebFeedSubscription : ModelBase
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
public Guid FeedId { get; set; }
|
||||||
|
public WebFeed Feed { get; set; } = null!;
|
||||||
|
public Guid AccountId { get; set; }
|
||||||
|
[NotMapped] public Account Account { get; set; } = null!;
|
||||||
}
|
}
|
132
DysonNetwork.Sphere/WebReader/WebFeedPublicController.cs
Normal file
132
DysonNetwork.Sphere/WebReader/WebFeedPublicController.cs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
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
|
||||||
|
) : 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("me")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IActionResult> GetMySubscriptions(
|
||||||
|
[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
|
||||||
|
.Skip(offset)
|
||||||
|
.Take(take)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
Response.Headers["X-Total"] = totalCount.ToString();
|
||||||
|
return Ok(subscriptions);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user