Compare commits
3 Commits
8c748fd57a
...
915054fce0
Author | SHA1 | Date | |
---|---|---|---|
|
915054fce0 | ||
|
63653680ba | ||
|
84c4df6620 |
@@ -10,6 +10,8 @@ public abstract class ActionLogType
|
|||||||
public const string PostUpdate = "posts.update";
|
public const string PostUpdate = "posts.update";
|
||||||
public const string PostDelete = "posts.delete";
|
public const string PostDelete = "posts.delete";
|
||||||
public const string PostReact = "posts.react";
|
public const string PostReact = "posts.react";
|
||||||
|
public const string PostPin = "posts.pin";
|
||||||
|
public const string PostUnpin = "posts.unpin";
|
||||||
public const string MessageCreate = "messages.create";
|
public const string MessageCreate = "messages.create";
|
||||||
public const string MessageUpdate = "messages.update";
|
public const string MessageUpdate = "messages.update";
|
||||||
public const string MessageDelete = "messages.delete";
|
public const string MessageDelete = "messages.delete";
|
||||||
@@ -37,4 +39,4 @@ public abstract class ActionLogType
|
|||||||
public const string ChatroomLeave = "chatrooms.leave";
|
public const string ChatroomLeave = "chatrooms.leave";
|
||||||
public const string ChatroomKick = "chatrooms.kick";
|
public const string ChatroomKick = "chatrooms.kick";
|
||||||
public const string ChatroomAdjustRole = "chatrooms.role.edit";
|
public const string ChatroomAdjustRole = "chatrooms.role.edit";
|
||||||
}
|
}
|
||||||
|
2006
DysonNetwork.Sphere/Migrations/20250825045548_AddPostPin.Designer.cs
generated
Normal file
2006
DysonNetwork.Sphere/Migrations/20250825045548_AddPostPin.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
DysonNetwork.Sphere/Migrations/20250825045548_AddPostPin.cs
Normal file
28
DysonNetwork.Sphere/Migrations/20250825045548_AddPostPin.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DysonNetwork.Sphere.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddPostPin : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "pin_mode",
|
||||||
|
table: "posts",
|
||||||
|
type: "integer",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "pin_mode",
|
||||||
|
table: "posts");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -566,6 +566,10 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
.HasColumnType("jsonb")
|
.HasColumnType("jsonb")
|
||||||
.HasColumnName("meta");
|
.HasColumnName("meta");
|
||||||
|
|
||||||
|
b.Property<int?>("PinMode")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("pin_mode");
|
||||||
|
|
||||||
b.Property<Instant?>("PublishedAt")
|
b.Property<Instant?>("PublishedAt")
|
||||||
.HasColumnType("timestamp with time zone")
|
.HasColumnType("timestamp with time zone")
|
||||||
.HasColumnName("published_at");
|
.HasColumnName("published_at");
|
||||||
|
@@ -23,6 +23,13 @@ public enum PostVisibility
|
|||||||
Private
|
Private
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum PostPinMode
|
||||||
|
{
|
||||||
|
PublisherPage,
|
||||||
|
RealmPage,
|
||||||
|
ReplyPage,
|
||||||
|
}
|
||||||
|
|
||||||
public class Post : ModelBase, IIdentifiedResource, IActivity
|
public class Post : ModelBase, IIdentifiedResource, IActivity
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
@@ -37,6 +44,7 @@ public class Post : ModelBase, IIdentifiedResource, IActivity
|
|||||||
public string? Content { get; set; }
|
public string? Content { get; set; }
|
||||||
|
|
||||||
public PostType Type { get; set; }
|
public PostType Type { get; set; }
|
||||||
|
public PostPinMode? PinMode { get; set; }
|
||||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
|
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
|
||||||
[Column(TypeName = "jsonb")] public List<ContentSensitiveMark>? SensitiveMarks { get; set; } = [];
|
[Column(TypeName = "jsonb")] public List<ContentSensitiveMark>? SensitiveMarks { get; set; } = [];
|
||||||
|
|
||||||
@@ -97,7 +105,7 @@ public class PostTag : ModelBase
|
|||||||
[MaxLength(128)] public string Slug { get; set; } = null!;
|
[MaxLength(128)] public string Slug { get; set; } = null!;
|
||||||
[MaxLength(256)] public string? Name { get; set; }
|
[MaxLength(256)] public string? Name { get; set; }
|
||||||
[JsonIgnore] public ICollection<Post> Posts { get; set; } = new List<Post>();
|
[JsonIgnore] public ICollection<Post> Posts { get; set; } = new List<Post>();
|
||||||
|
|
||||||
[NotMapped] public int? Usage { get; set; }
|
[NotMapped] public int? Usage { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +115,7 @@ public class PostCategory : ModelBase
|
|||||||
[MaxLength(128)] public string Slug { get; set; } = null!;
|
[MaxLength(128)] public string Slug { get; set; } = null!;
|
||||||
[MaxLength(256)] public string? Name { get; set; }
|
[MaxLength(256)] public string? Name { get; set; }
|
||||||
[JsonIgnore] public ICollection<Post> Posts { get; set; } = new List<Post>();
|
[JsonIgnore] public ICollection<Post> Posts { get; set; } = new List<Post>();
|
||||||
|
|
||||||
[NotMapped] public int? Usage { get; set; }
|
[NotMapped] public int? Usage { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -81,7 +81,8 @@ public class PostController(
|
|||||||
[FromQuery(Name = "query")] string? queryTerm = null,
|
[FromQuery(Name = "query")] string? queryTerm = null,
|
||||||
[FromQuery(Name = "vector")] bool queryVector = false,
|
[FromQuery(Name = "vector")] bool queryVector = false,
|
||||||
[FromQuery(Name = "media")] bool onlyMedia = false,
|
[FromQuery(Name = "media")] bool onlyMedia = false,
|
||||||
[FromQuery(Name = "shuffle")] bool shuffle = false
|
[FromQuery(Name = "shuffle")] bool shuffle = false,
|
||||||
|
[FromQuery(Name = "pinned")] bool pinned = false
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
|
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
|
||||||
@@ -91,7 +92,7 @@ public class PostController(
|
|||||||
if (currentUser != null)
|
if (currentUser != null)
|
||||||
{
|
{
|
||||||
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
||||||
{ AccountId = currentUser.Id });
|
{ AccountId = currentUser.Id });
|
||||||
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +117,14 @@ public class PostController(
|
|||||||
query = query.Where(p => p.Tags.Any(c => tags.Contains(c.Slug)));
|
query = query.Where(p => p.Tags.Any(c => tags.Contains(c.Slug)));
|
||||||
if (onlyMedia)
|
if (onlyMedia)
|
||||||
query = query.Where(e => e.Attachments.Count > 0);
|
query = query.Where(e => e.Attachments.Count > 0);
|
||||||
|
|
||||||
|
if (pinned)
|
||||||
|
{
|
||||||
|
if (realm != null) query = query.Where(p => p.PinMode == PostPinMode.RealmPage);
|
||||||
|
else if (publisher != null) query = query.Where(p => p.PinMode == PostPinMode.PublisherPage);
|
||||||
|
else return BadRequest("You need pass extra realm or publisher params in order to filter with pinned posts.");
|
||||||
|
}
|
||||||
|
|
||||||
query = includeReplies switch
|
query = includeReplies switch
|
||||||
{
|
{
|
||||||
false => query.Where(e => e.RepliedPostId == null),
|
false => query.Where(e => e.RepliedPostId == null),
|
||||||
@@ -169,7 +177,7 @@ public class PostController(
|
|||||||
if (currentUser != null)
|
if (currentUser != null)
|
||||||
{
|
{
|
||||||
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
||||||
{ AccountId = currentUser.Id });
|
{ AccountId = currentUser.Id });
|
||||||
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,9 +196,6 @@ public class PostController(
|
|||||||
if (post is null) return NotFound();
|
if (post is null) return NotFound();
|
||||||
post = await ps.LoadPostInfo(post, currentUser);
|
post = await ps.LoadPostInfo(post, currentUser);
|
||||||
|
|
||||||
// Track view - use the account ID as viewer ID if user is logged in
|
|
||||||
await ps.IncreaseViewCount(post.Id, currentUser?.Id);
|
|
||||||
|
|
||||||
return Ok(post);
|
return Ok(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +208,7 @@ public class PostController(
|
|||||||
if (currentUser != null)
|
if (currentUser != null)
|
||||||
{
|
{
|
||||||
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
||||||
{ AccountId = currentUser.Id });
|
{ AccountId = currentUser.Id });
|
||||||
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,9 +227,6 @@ public class PostController(
|
|||||||
if (post is null) return NotFound();
|
if (post is null) return NotFound();
|
||||||
post = await ps.LoadPostInfo(post, currentUser);
|
post = await ps.LoadPostInfo(post, currentUser);
|
||||||
|
|
||||||
// Track view - use the account ID as viewer ID if user is logged in
|
|
||||||
await ps.IncreaseViewCount(post.Id, currentUser?.Id);
|
|
||||||
|
|
||||||
return Ok(post);
|
return Ok(post);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,7 +263,7 @@ public class PostController(
|
|||||||
if (currentUser != null)
|
if (currentUser != null)
|
||||||
{
|
{
|
||||||
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
||||||
{ AccountId = currentUser.Id });
|
{ AccountId = currentUser.Id });
|
||||||
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,12 +280,36 @@ public class PostController(
|
|||||||
.FilterWithVisibility(currentUser, userFriends, userPublishers)
|
.FilterWithVisibility(currentUser, userFriends, userPublishers)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (post is null) return NotFound();
|
if (post is null) return NotFound();
|
||||||
post = await ps.LoadPostInfo(post, currentUser);
|
post = await ps.LoadPostInfo(post, currentUser, true);
|
||||||
|
|
||||||
// Track view - use the account ID as viewer ID if user is logged in
|
return Ok(post);
|
||||||
await ps.IncreaseViewCount(post.Id, currentUser?.Id);
|
}
|
||||||
|
|
||||||
return await ps.LoadPostInfo(post);
|
[HttpGet("{id:guid}/replies/pinned")]
|
||||||
|
public async Task<ActionResult<List<Post>>> ListPinnedReplies(Guid id)
|
||||||
|
{
|
||||||
|
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
|
||||||
|
var currentUser = currentUserValue as Account;
|
||||||
|
List<Guid> userFriends = [];
|
||||||
|
if (currentUser != null)
|
||||||
|
{
|
||||||
|
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
||||||
|
{ AccountId = currentUser.Id });
|
||||||
|
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(Guid.Parse(currentUser.Id));
|
||||||
|
|
||||||
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
var posts = await db.Posts
|
||||||
|
.Where(e => e.RepliedPostId == id && e.PinMode == PostPinMode.ReplyPage)
|
||||||
|
.OrderByDescending(p => p.CreatedAt)
|
||||||
|
.FilterWithVisibility(currentUser, userFriends, userPublishers)
|
||||||
|
.ToListAsync();
|
||||||
|
if (posts is null) return NotFound();
|
||||||
|
posts = await ps.LoadPostInfo(posts, currentUser);
|
||||||
|
|
||||||
|
return Ok(posts);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id:guid}/replies")]
|
[HttpGet("{id:guid}/replies")]
|
||||||
@@ -297,7 +323,7 @@ public class PostController(
|
|||||||
if (currentUser != null)
|
if (currentUser != null)
|
||||||
{
|
{
|
||||||
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
||||||
{ AccountId = currentUser.Id });
|
{ AccountId = currentUser.Id });
|
||||||
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,7 +510,7 @@ public class PostController(
|
|||||||
|
|
||||||
var friendsResponse =
|
var friendsResponse =
|
||||||
await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
||||||
{ AccountId = currentUser.Id.ToString() });
|
{ AccountId = currentUser.Id.ToString() });
|
||||||
var userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
var userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
||||||
var userPublishers = await pub.GetUserPublishers(Guid.Parse(currentUser.Id));
|
var userPublishers = await pub.GetUserPublishers(Guid.Parse(currentUser.Id));
|
||||||
|
|
||||||
@@ -535,6 +561,88 @@ public class PostController(
|
|||||||
return Ok(reaction);
|
return Ok(reaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class PostPinRequest
|
||||||
|
{
|
||||||
|
[Required] public PostPinMode Mode { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{id:guid}/pin")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult<Post>> PinPost(Guid id, [FromBody] PostPinRequest request)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
|
var post = await db.Posts
|
||||||
|
.Where(e => e.Id == id)
|
||||||
|
.Include(e => e.Publisher)
|
||||||
|
.Include(e => e.RepliedPost)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (post is null) return NotFound();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ps.PinPostAsync(post, currentUser, request.Mode);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException err)
|
||||||
|
{
|
||||||
|
return BadRequest(err.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountId = Guid.Parse(currentUser.Id);
|
||||||
|
|
||||||
|
_ = als.CreateActionLogAsync(new CreateActionLogRequest
|
||||||
|
{
|
||||||
|
Action = ActionLogType.PostPin,
|
||||||
|
Meta =
|
||||||
|
{
|
||||||
|
{ "post_id", Google.Protobuf.WellKnownTypes.Value.ForString(post.Id.ToString()) },
|
||||||
|
{ "mode", Google.Protobuf.WellKnownTypes.Value.ForString(request.Mode.ToString()) }
|
||||||
|
},
|
||||||
|
AccountId = currentUser.Id.ToString(),
|
||||||
|
UserAgent = Request.Headers.UserAgent,
|
||||||
|
IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok(post);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id:guid}/pin")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult<Post>> UnpinPost(Guid id)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
|
||||||
|
var post = await db.Posts
|
||||||
|
.Where(e => e.Id == id)
|
||||||
|
.Include(e => e.Publisher)
|
||||||
|
.Include(e => e.RepliedPost)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (post is null) return NotFound();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ps.UnpinPostAsync(post, currentUser);
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException err)
|
||||||
|
{
|
||||||
|
return BadRequest(err.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = als.CreateActionLogAsync(new CreateActionLogRequest
|
||||||
|
{
|
||||||
|
Action = ActionLogType.PostUnpin,
|
||||||
|
Meta =
|
||||||
|
{
|
||||||
|
{ "post_id", Google.Protobuf.WellKnownTypes.Value.ForString(post.Id.ToString()) }
|
||||||
|
},
|
||||||
|
AccountId = currentUser.Id.ToString(),
|
||||||
|
UserAgent = Request.Headers.UserAgent,
|
||||||
|
IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok(post);
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPatch("{id:guid}")]
|
[HttpPatch("{id:guid}")]
|
||||||
public async Task<ActionResult<Post>> UpdatePost(
|
public async Task<ActionResult<Post>> UpdatePost(
|
||||||
Guid id,
|
Guid id,
|
||||||
@@ -679,4 +787,4 @@ public class PostController(
|
|||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -25,6 +25,7 @@ public partial class PostService(
|
|||||||
FileService.FileServiceClient files,
|
FileService.FileServiceClient files,
|
||||||
FileReferenceService.FileReferenceServiceClient fileRefs,
|
FileReferenceService.FileReferenceServiceClient fileRefs,
|
||||||
PollService polls,
|
PollService polls,
|
||||||
|
Publisher.PublisherService ps,
|
||||||
WebReaderService reader
|
WebReaderService reader
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@@ -418,6 +419,56 @@ public partial class PostService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Post> PinPostAsync(Post post, Account currentUser, PostPinMode pinMode)
|
||||||
|
{
|
||||||
|
var accountId = Guid.Parse(currentUser.Id);
|
||||||
|
if (post.RepliedPostId != null)
|
||||||
|
{
|
||||||
|
if (pinMode != PostPinMode.ReplyPage) throw new InvalidOperationException("Replies can only be pinned in the reply page.");
|
||||||
|
if (post.RepliedPost == null) throw new ArgumentNullException(nameof(post.RepliedPost));
|
||||||
|
|
||||||
|
if (!await ps.IsMemberWithRole(post.RepliedPost.PublisherId, accountId, Publisher.PublisherMemberRole.Editor))
|
||||||
|
throw new InvalidOperationException("Only editors of original post can pin replies.");
|
||||||
|
|
||||||
|
post.PinMode = pinMode;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!await ps.IsMemberWithRole(post.PublisherId, accountId, Publisher.PublisherMemberRole.Editor))
|
||||||
|
throw new InvalidOperationException("Only editors can pin replies.");
|
||||||
|
|
||||||
|
post.PinMode = pinMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Update(post);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
return post;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Post> UnpinPostAsync(Post post, Account currentUser)
|
||||||
|
{
|
||||||
|
var accountId = Guid.Parse(currentUser.Id);
|
||||||
|
if (post.RepliedPostId != null)
|
||||||
|
{
|
||||||
|
if (post.RepliedPost == null) throw new ArgumentNullException(nameof(post.RepliedPost));
|
||||||
|
|
||||||
|
if (!await ps.IsMemberWithRole(post.RepliedPost.PublisherId, accountId, Publisher.PublisherMemberRole.Editor))
|
||||||
|
throw new InvalidOperationException("Only editors of original post can unpin replies.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!await ps.IsMemberWithRole(post.PublisherId, accountId, Publisher.PublisherMemberRole.Editor))
|
||||||
|
throw new InvalidOperationException("Only editors can unpin posts.");
|
||||||
|
}
|
||||||
|
|
||||||
|
post.PinMode = null;
|
||||||
|
db.Update(post);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
return post;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculate the total number of votes for a post.
|
/// Calculate the total number of votes for a post.
|
||||||
/// This function helps you save the new reactions.
|
/// This function helps you save the new reactions.
|
||||||
@@ -770,7 +821,6 @@ public partial class PostService(
|
|||||||
var reactSocialPoints = await db.PostReactions
|
var reactSocialPoints = await db.PostReactions
|
||||||
.Include(e => e.Post)
|
.Include(e => e.Post)
|
||||||
.Where(e => e.Post.Visibility == PostVisibility.Public)
|
.Where(e => e.Post.Visibility == PostVisibility.Public)
|
||||||
.Where(e => e.CreatedAt >= periodStart && e.CreatedAt < periodEnd)
|
|
||||||
.Where(e => e.Post.CreatedAt >= periodStart && e.Post.CreatedAt < periodEnd)
|
.Where(e => e.Post.CreatedAt >= periodStart && e.Post.CreatedAt < periodEnd)
|
||||||
.GroupBy(e => e.PostId)
|
.GroupBy(e => e.PostId)
|
||||||
.Select(e => new
|
.Select(e => new
|
||||||
@@ -784,16 +834,27 @@ public partial class PostService(
|
|||||||
|
|
||||||
featuredIds = reactSocialPoints.Select(e => e.Key).ToList();
|
featuredIds = reactSocialPoints.Select(e => e.Key).ToList();
|
||||||
|
|
||||||
await cache.SetAsync(FeaturedPostCacheKey, featuredIds, TimeSpan.FromHours(24));
|
await cache.SetAsync(FeaturedPostCacheKey, featuredIds, TimeSpan.FromHours(4));
|
||||||
|
|
||||||
// Create featured record
|
// Create featured record
|
||||||
var records = reactSocialPoints.Select(e => new PostFeaturedRecord
|
var existingFeaturedPostIds = await db.PostFeaturedRecords
|
||||||
|
.Where(r => featuredIds.Contains(r.PostId))
|
||||||
|
.Select(r => r.PostId)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var records = reactSocialPoints
|
||||||
|
.Where(p => !existingFeaturedPostIds.Contains(p.Key))
|
||||||
|
.Select(e => new PostFeaturedRecord
|
||||||
|
{
|
||||||
|
PostId = e.Key,
|
||||||
|
SocialCredits = e.Value
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
if (records.Any())
|
||||||
{
|
{
|
||||||
PostId = e.Key,
|
db.PostFeaturedRecords.AddRange(records);
|
||||||
SocialCredits = e.Value
|
await db.SaveChangesAsync();
|
||||||
}).ToList();
|
}
|
||||||
db.PostFeaturedRecords.AddRange(records);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var posts = await db.Posts
|
var posts = await db.Posts
|
||||||
|
@@ -245,14 +245,14 @@ public class RealmController(
|
|||||||
members.Select(m => m.AccountId).ToList()
|
members.Select(m => m.AccountId).ToList()
|
||||||
);
|
);
|
||||||
|
|
||||||
members = members
|
members = members
|
||||||
.Select(m =>
|
.Select(m =>
|
||||||
{
|
{
|
||||||
m.Status = memberStatuses.TryGetValue(m.AccountId, out var s) ? s : null;
|
m.Status = memberStatuses.TryGetValue(m.AccountId, out var s) ? s : null;
|
||||||
return m;
|
return m;
|
||||||
})
|
})
|
||||||
.OrderByDescending(m => m.Status?.IsOnline ?? false)
|
.OrderByDescending(m => m.Status?.IsOnline ?? false)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var total = members.Count;
|
var total = members.Count;
|
||||||
Response.Headers.Append("X-Total", total.ToString());
|
Response.Headers.Append("X-Total", total.ToString());
|
||||||
@@ -260,7 +260,7 @@ public class RealmController(
|
|||||||
var result = members.Skip(offset).Take(take).ToList();
|
var result = members.Skip(offset).Take(take).ToList();
|
||||||
|
|
||||||
members = await rs.LoadMemberAccounts(result);
|
members = await rs.LoadMemberAccounts(result);
|
||||||
|
|
||||||
return Ok(members.Where(m => m.Account is not null).ToList());
|
return Ok(members.Where(m => m.Account is not null).ToList());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -742,4 +742,4 @@ public class RealmController(
|
|||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user