♻️ Centralized data models (wip)

This commit is contained in:
2025-09-27 14:09:28 +08:00
parent 51b6f7309e
commit e70d8371f8
206 changed files with 1352 additions and 2128 deletions

View File

@@ -1,202 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using DysonNetwork.Shared.Data;
using DysonNetwork.Sphere.Activity;
using NodaTime;
using NpgsqlTypes;
namespace DysonNetwork.Sphere.Post;
public enum PostType
{
Moment,
Article
}
public enum PostVisibility
{
Public,
Friends,
Unlisted,
Private
}
public enum PostPinMode
{
PublisherPage,
RealmPage,
ReplyPage,
}
public class Post : ModelBase, IIdentifiedResource, IActivity
{
public Guid Id { get; set; }
[MaxLength(1024)] public string? Title { get; set; }
[MaxLength(4096)] public string? Description { get; set; }
[MaxLength(1024)] public string? Slug { get; set; }
public Instant? EditedAt { get; set; }
public Instant? PublishedAt { get; set; }
public PostVisibility Visibility { get; set; } = PostVisibility.Public;
// ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength
public string? Content { 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 List<ContentSensitiveMark>? SensitiveMarks { get; set; } = [];
[Column(TypeName = "jsonb")] public PostEmbedView? EmbedView { get; set; }
public int ViewsUnique { get; set; }
public int ViewsTotal { get; set; }
public int Upvotes { get; set; }
public int Downvotes { get; set; }
public decimal AwardedScore { get; set; }
[NotMapped] public Dictionary<string, int> ReactionsCount { get; set; } = new();
[NotMapped] public int RepliesCount { get; set; }
[NotMapped] public Dictionary<string, bool>? ReactionsMade { get; set; }
public bool RepliedGone { get; set; }
public bool ForwardedGone { get; set; }
public Guid? RepliedPostId { get; set; }
public Post? RepliedPost { get; set; }
public Guid? ForwardedPostId { get; set; }
public Post? ForwardedPost { get; set; }
public Guid? RealmId { get; set; }
public Realm.Realm? Realm { get; set; }
[Column(TypeName = "jsonb")] public List<CloudFileReferenceObject> Attachments { get; set; } = [];
[JsonIgnore] public NpgsqlTsVector SearchVector { get; set; } = null!;
public Guid PublisherId { get; set; }
public Publisher.Publisher Publisher { get; set; } = null!;
public ICollection<PostAward> Awards { get; set; } = null!;
[JsonIgnore] public ICollection<PostReaction> Reactions { get; set; } = new List<PostReaction>();
public ICollection<PostTag> Tags { get; set; } = new List<PostTag>();
public ICollection<PostCategory> Categories { get; set; } = new List<PostCategory>();
[JsonIgnore] public ICollection<PostCollection> Collections { get; set; } = new List<PostCollection>();
[JsonIgnore] public bool Empty => Content == null && Attachments.Count == 0 && ForwardedPostId == null;
[NotMapped] public bool IsTruncated { get; set; } = false;
public string ResourceIdentifier => $"post:{Id}";
public Activity.Activity ToActivity()
{
return new Activity.Activity()
{
CreatedAt = PublishedAt ?? CreatedAt,
UpdatedAt = UpdatedAt,
DeletedAt = DeletedAt,
Id = Id,
Type = RepliedPostId is null ? "posts.new" : "posts.new.replies",
ResourceIdentifier = ResourceIdentifier,
Data = this
};
}
}
public class PostTag : ModelBase
{
public Guid Id { get; set; }
[MaxLength(128)] public string Slug { get; set; } = null!;
[MaxLength(256)] public string? Name { get; set; }
[JsonIgnore] public ICollection<Post> Posts { get; set; } = new List<Post>();
[NotMapped] public int? Usage { get; set; }
}
public class PostCategory : ModelBase
{
public Guid Id { get; set; }
[MaxLength(128)] public string Slug { get; set; } = null!;
[MaxLength(256)] public string? Name { get; set; }
[JsonIgnore] public ICollection<Post> Posts { get; set; } = new List<Post>();
[NotMapped] public int? Usage { get; set; }
}
public class PostCategorySubscription : ModelBase
{
public Guid Id { get; set; }
public Guid AccountId { get; set; }
public Guid? CategoryId { get; set; }
public PostCategory? Category { get; set; }
public Guid? TagId { get; set; }
public PostTag? Tag { get; set; }
}
public class PostCollection : ModelBase
{
public Guid Id { get; set; }
[MaxLength(128)] public string Slug { get; set; } = null!;
[MaxLength(256)] public string? Name { get; set; }
[MaxLength(4096)] public string? Description { get; set; }
public Publisher.Publisher Publisher { get; set; } = null!;
public ICollection<Post> Posts { get; set; } = new List<Post>();
}
public class PostFeaturedRecord : ModelBase
{
public Guid Id { get; set; }
public Guid PostId { get; set; }
public Post Post { get; set; } = null!;
public Instant? FeaturedAt { get; set; }
public int SocialCredits { get; set; }
}
public enum PostReactionAttitude
{
Positive,
Neutral,
Negative,
}
public class PostReaction : ModelBase
{
public Guid Id { get; set; }
[MaxLength(256)] public string Symbol { get; set; } = null!;
public PostReactionAttitude Attitude { get; set; }
public Guid PostId { get; set; }
[JsonIgnore] public Post Post { get; set; } = null!;
public Guid AccountId { get; set; }
}
public class PostAward : ModelBase
{
public Guid Id { get; set; }
public decimal Amount { get; set; }
public PostReactionAttitude Attitude { get; set; }
[MaxLength(4096)] public string? Message { get; set; }
public Guid PostId { get; set; }
[JsonIgnore] public Post Post { get; set; } = null!;
public Guid AccountId { get; set; }
}
/// <summary>
/// This model is used to tell the client to render a WebView / iframe
/// Usually external website and web pages
/// Used as a JSON column
/// </summary>
public class PostEmbedView
{
public string Uri { get; set; } = null!;
public double? AspectRatio { get; set; }
public PostEmbedViewRenderer Renderer { get; set; } = PostEmbedViewRenderer.WebView;
}
public enum PostEmbedViewRenderer
{
WebView
}

View File

@@ -1,4 +1,5 @@
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -11,7 +12,7 @@ namespace DysonNetwork.Sphere.Post;
public class PostCategoryController(AppDatabase db) : ControllerBase
{
[HttpGet("categories")]
public async Task<ActionResult<List<PostCategory>>> ListCategories(
public async Task<ActionResult<List<SnPostCategory>>> ListCategories(
[FromQuery] string? query = null,
[FromQuery] int offset = 0,
[FromQuery] int take = 20,
@@ -59,7 +60,7 @@ public class PostCategoryController(AppDatabase db) : ControllerBase
}
[HttpGet("tags")]
public async Task<ActionResult<List<PostTag>>> ListTags(
public async Task<ActionResult<List<SnPostTag>>> ListTags(
[FromQuery] string? query = null,
[FromQuery] int offset = 0,
[FromQuery] int take = 20,
@@ -107,7 +108,7 @@ public class PostCategoryController(AppDatabase db) : ControllerBase
}
[HttpGet("categories/{slug}")]
public async Task<ActionResult<PostCategory>> GetCategory(string slug)
public async Task<ActionResult<SnPostCategory>> GetCategory(string slug)
{
var category = await db.PostCategories.FirstOrDefaultAsync(e => e.Slug == slug);
if (category is null)
@@ -116,7 +117,7 @@ public class PostCategoryController(AppDatabase db) : ControllerBase
}
[HttpGet("tags/{slug}")]
public async Task<ActionResult<PostTag>> GetTag(string slug)
public async Task<ActionResult<SnPostTag>> GetTag(string slug)
{
var tag = await db.PostTags.FirstOrDefaultAsync(e => e.Slug == slug);
if (tag is null)
@@ -126,7 +127,7 @@ public class PostCategoryController(AppDatabase db) : ControllerBase
[HttpPost("categories/{slug}/subscribe")]
[Authorize]
public async Task<ActionResult<PostCategorySubscription>> SubscribeCategory(string slug)
public async Task<ActionResult<SnPostCategorySubscription>> SubscribeCategory(string slug)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var accountId = Guid.Parse(currentUser.Id);
@@ -143,7 +144,7 @@ public class PostCategoryController(AppDatabase db) : ControllerBase
if (existingSubscription != null)
return Ok(existingSubscription);
var subscription = new PostCategorySubscription
var subscription = new SnPostCategorySubscription
{
AccountId = accountId,
CategoryId = category.Id
@@ -180,7 +181,7 @@ public class PostCategoryController(AppDatabase db) : ControllerBase
[HttpGet("categories/{slug}/subscription")]
[Authorize]
public async Task<ActionResult<PostCategorySubscription>> GetCategorySubscription(string slug)
public async Task<ActionResult<SnPostCategorySubscription>> GetCategorySubscription(string slug)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var accountId = Guid.Parse(currentUser.Id);
@@ -200,7 +201,7 @@ public class PostCategoryController(AppDatabase db) : ControllerBase
[HttpPost("tags/{slug}/subscribe")]
[Authorize]
public async Task<ActionResult<PostCategorySubscription>> SubscribeTag(string slug)
public async Task<ActionResult<SnPostCategorySubscription>> SubscribeTag(string slug)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var accountId = Guid.Parse(currentUser.Id);
@@ -219,7 +220,7 @@ public class PostCategoryController(AppDatabase db) : ControllerBase
return Ok(existingSubscription);
}
var subscription = new PostCategorySubscription
var subscription = new SnPostCategorySubscription
{
AccountId = accountId,
TagId = tag.Id
@@ -260,7 +261,7 @@ public class PostCategoryController(AppDatabase db) : ControllerBase
[HttpGet("tags/{slug}/subscription")]
[Authorize]
public async Task<ActionResult<PostCategorySubscription>> GetTagSubscription(string slug)
public async Task<ActionResult<SnPostCategorySubscription>> GetTagSubscription(string slug)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
var accountId = Guid.Parse(currentUser.Id);

View File

@@ -3,6 +3,7 @@ using System.Globalization;
using DysonNetwork.Shared.Auth;
using DysonNetwork.Shared.Content;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using DysonNetwork.Sphere.Poll;
using DysonNetwork.Sphere.Realm;
@@ -12,7 +13,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NodaTime;
using Swashbuckle.AspNetCore.Annotations;
using PublisherMemberRole = DysonNetwork.Sphere.Publisher.PublisherMemberRole;
using PublisherMemberRole = DysonNetwork.Shared.Models.PublisherMemberRole;
using PublisherService = DysonNetwork.Sphere.Publisher.PublisherService;
namespace DysonNetwork.Sphere.Post;
@@ -32,7 +33,7 @@ public class PostController(
: ControllerBase
{
[HttpGet("featured")]
public async Task<ActionResult<List<Post>>> ListFeaturedPosts()
public async Task<ActionResult<List<SnPost>>> ListFeaturedPosts()
{
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
var currentUser = currentUserValue as Account;
@@ -63,7 +64,7 @@ public class PostController(
/// </returns>
/// <response code="200">Returns the list of posts matching the criteria.</response>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List<Post>))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List<SnPost>))]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[SwaggerOperation(
Summary = "Retrieves a paginated list of posts",
@@ -72,9 +73,9 @@ public class PostController(
OperationId = "ListPosts",
Tags = ["Posts"]
)]
[SwaggerResponse(StatusCodes.Status200OK, "Successfully retrieved the list of posts", typeof(List<Post>))]
[SwaggerResponse(StatusCodes.Status200OK, "Successfully retrieved the list of posts", typeof(List<SnPost>))]
[SwaggerResponse(StatusCodes.Status400BadRequest, "Invalid request parameters")]
public async Task<ActionResult<List<Post>>> ListPosts(
public async Task<ActionResult<List<SnPost>>> ListPosts(
[FromQuery] int offset = 0,
[FromQuery] int take = 20,
[FromQuery(Name = "pub")] string? pubName = null,
@@ -189,7 +190,7 @@ public class PostController(
}
[HttpGet("{publisherName}/{slug}")]
public async Task<ActionResult<Post>> GetPost(string publisherName, string slug)
public async Task<ActionResult<SnPost>> GetPost(string publisherName, string slug)
{
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
var currentUser = currentUserValue as Account;
@@ -220,7 +221,7 @@ public class PostController(
}
[HttpGet("{id:guid}")]
public async Task<ActionResult<Post>> GetPost(Guid id)
public async Task<ActionResult<SnPost>> GetPost(Guid id)
{
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
var currentUser = currentUserValue as Account;
@@ -251,7 +252,7 @@ public class PostController(
}
[HttpGet("{id:guid}/reactions")]
public async Task<ActionResult<List<PostReaction>>> GetReactions(
public async Task<ActionResult<List<SnPostReaction>>> GetReactions(
Guid id,
[FromQuery] string? symbol = null,
[FromQuery] int offset = 0,
@@ -275,7 +276,7 @@ public class PostController(
}
[HttpGet("{id:guid}/replies/featured")]
public async Task<ActionResult<Post>> GetFeaturedReply(Guid id)
public async Task<ActionResult<SnPost>> GetFeaturedReply(Guid id)
{
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
var currentUser = currentUserValue as Account;
@@ -306,7 +307,7 @@ public class PostController(
}
[HttpGet("{id:guid}/replies/pinned")]
public async Task<ActionResult<List<Post>>> ListPinnedReplies(Guid id)
public async Task<ActionResult<List<SnPost>>> ListPinnedReplies(Guid id)
{
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
var currentUser = currentUserValue as Account;
@@ -332,7 +333,7 @@ public class PostController(
}
[HttpGet("{id:guid}/replies")]
public async Task<ActionResult<List<Post>>> ListReplies(Guid id, [FromQuery] int offset = 0,
public async Task<ActionResult<List<SnPost>>> ListReplies(Guid id, [FromQuery] int offset = 0,
[FromQuery] int take = 20)
{
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
@@ -403,7 +404,7 @@ public class PostController(
[HttpPost]
[RequiredPermission("global", "posts.create")]
public async Task<ActionResult<Post>> CreatePost(
public async Task<ActionResult<SnPost>> CreatePost(
[FromBody] PostRequest request,
[FromQuery(Name = "pub")] string? pubName
)
@@ -415,12 +416,12 @@ public class PostController(
var accountId = Guid.Parse(currentUser.Id);
Publisher.Publisher? publisher;
Shared.Models.SnPublisher? publisher;
if (pubName is null)
{
// Use the first personal publisher
publisher = await db.Publishers.FirstOrDefaultAsync(e =>
e.AccountId == accountId && e.Type == Publisher.PublisherType.Individual);
e.AccountId == accountId && e.Type == Shared.Models.PublisherType.Individual);
}
else
{
@@ -432,7 +433,7 @@ public class PostController(
if (publisher is null) return BadRequest("Publisher was not found.");
var post = new Post
var post = new SnPost
{
Title = request.Title,
Description = request.Description,
@@ -525,7 +526,7 @@ public class PostController(
[HttpPost("{id:guid}/reactions")]
[Authorize]
[RequiredPermission("global", "posts.react")]
public async Task<ActionResult<PostReaction>> ReactPost(Guid id, [FromBody] PostReactionRequest request)
public async Task<ActionResult<SnPostReaction>> ReactPost(Guid id, [FromBody] PostReactionRequest request)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
@@ -549,7 +550,7 @@ public class PostController(
.AnyAsync(r => r.PostId == post.Id &&
r.Symbol == request.Symbol &&
r.AccountId == accountId);
var reaction = new PostReaction
var reaction = new SnPostReaction
{
Symbol = request.Symbol,
Attitude = request.Attitude,
@@ -590,7 +591,7 @@ public class PostController(
}
[HttpGet("{id:guid}/awards")]
public async Task<ActionResult<PostAward>> GetPostAwards(Guid id, [FromQuery] int offset = 0,
public async Task<ActionResult<SnPostAward>> GetPostAwards(Guid id, [FromQuery] int offset = 0,
[FromQuery] int take = 20)
{
var queryable = db.PostAwards
@@ -666,7 +667,7 @@ public class PostController(
[HttpPost("{id:guid}/pin")]
[Authorize]
public async Task<ActionResult<Post>> PinPost(Guid id, [FromBody] PostPinRequest request)
public async Task<ActionResult<SnPost>> PinPost(Guid id, [FromBody] PostPinRequest request)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
@@ -714,7 +715,7 @@ public class PostController(
[HttpDelete("{id:guid}/pin")]
[Authorize]
public async Task<ActionResult<Post>> UnpinPost(Guid id)
public async Task<ActionResult<SnPost>> UnpinPost(Guid id)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
@@ -760,7 +761,7 @@ public class PostController(
}
[HttpPatch("{id:guid}")]
public async Task<ActionResult<Post>> UpdatePost(
public async Task<ActionResult<SnPost>> UpdatePost(
Guid id,
[FromBody] PostRequest request,
[FromQuery(Name = "pub")] string? pubName
@@ -780,14 +781,14 @@ public class PostController(
if (post is null) return NotFound();
var accountId = Guid.Parse(currentUser.Id);
if (!await pub.IsMemberWithRole(post.Publisher.Id, accountId, Publisher.PublisherMemberRole.Editor))
if (!await pub.IsMemberWithRole(post.Publisher.Id, accountId, PublisherMemberRole.Editor))
return StatusCode(403, "You need at least be an editor to edit this publisher's post.");
if (pubName is not null)
{
var publisher = await pub.GetPublisherByName(pubName);
if (publisher is null) return NotFound();
if (!await pub.IsMemberWithRole(publisher.Id, accountId, Publisher.PublisherMemberRole.Editor))
if (!await pub.IsMemberWithRole(publisher.Id, accountId, PublisherMemberRole.Editor))
return StatusCode(403, "You need at least be an editor to transfer this post to this publisher.");
post.PublisherId = publisher.Id;
post.Publisher = publisher;
@@ -879,7 +880,7 @@ public class PostController(
}
[HttpDelete("{id:guid}")]
public async Task<ActionResult<Post>> DeletePost(Guid id)
public async Task<ActionResult<SnPost>> DeletePost(Guid id)
{
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();

View File

@@ -3,7 +3,6 @@ using System.Text.RegularExpressions;
using AngleSharp.Common;
using DysonNetwork.Shared;
using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.Proto;
using DysonNetwork.Shared.Registry;
using DysonNetwork.Sphere.WebReader;
@@ -13,6 +12,7 @@ using DysonNetwork.Sphere.Publisher;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Localization;
using NodaTime;
using DysonNetwork.Shared.Models;
namespace DysonNetwork.Sphere.Post;
@@ -32,7 +32,7 @@ public partial class PostService(
{
private const string PostFileUsageIdentifier = "post";
private static List<Post> TruncatePostContent(List<Post> input)
private static List<SnPost> TruncatePostContent(List<SnPost> input)
{
const int maxLength = 256;
const int embedMaxLength = 80;
@@ -62,7 +62,7 @@ public partial class PostService(
return input;
}
public (string title, string content) ChopPostForNotification(Post post)
public (string title, string content) ChopPostForNotification(SnPost post)
{
var content = !string.IsNullOrEmpty(post.Description)
? post.Description?.Length >= 40 ? post.Description[..37] + "..." : post.Description
@@ -75,8 +75,8 @@ public partial class PostService(
return (title, content);
}
public async Task<Post> PostAsync(
Post post,
public async Task<SnPost> PostAsync(
SnPost post,
List<string>? attachments = null,
List<string>? tags = null,
List<string>? categories = null
@@ -101,7 +101,7 @@ public partial class PostService(
queryRequest.Ids.AddRange(attachments);
var queryResponse = await files.GetFileBatchAsync(queryRequest);
post.Attachments = queryResponse.Files.Select(CloudFileReferenceObject.FromProtoValue).ToList();
post.Attachments = queryResponse.Files.Select(SnCloudFileReferenceObject.FromProtoValue).ToList();
// Re-order the list to match the id list places
post.Attachments = attachments
.Select(id => post.Attachments.First(a => a.Id == id))
@@ -116,7 +116,7 @@ public partial class PostService(
var existingSlugs = existingTags.Select(t => t.Slug).ToHashSet();
var missingSlugs = tags.Where(slug => !existingSlugs.Contains(slug)).ToList();
var newTags = missingSlugs.Select(slug => new PostTag { Slug = slug }).ToList();
var newTags = missingSlugs.Select(slug => new SnPostTag { Slug = slug }).ToList();
if (newTags.Count > 0)
{
await db.PostTags.AddRangeAsync(newTags);
@@ -208,8 +208,8 @@ public partial class PostService(
return post;
}
public async Task<Post> UpdatePostAsync(
Post post,
public async Task<SnPost> UpdatePostAsync(
SnPost post,
List<string>? attachments = null,
List<string>? tags = null,
List<string>? categories = null,
@@ -248,7 +248,7 @@ public partial class PostService(
queryRequest.Ids.AddRange(attachments);
var queryResponse = await files.GetFileBatchAsync(queryRequest);
post.Attachments = queryResponse.Files.Select(CloudFileReferenceObject.FromProtoValue).ToList();
post.Attachments = queryResponse.Files.Select(SnCloudFileReferenceObject.FromProtoValue).ToList();
}
if (tags is not null)
@@ -259,7 +259,7 @@ public partial class PostService(
var existingSlugs = existingTags.Select(t => t.Slug).ToHashSet();
var missingSlugs = tags.Where(slug => !existingSlugs.Contains(slug)).ToList();
var newTags = missingSlugs.Select(slug => new PostTag { Slug = slug }).ToList();
var newTags = missingSlugs.Select(slug => new SnPostTag { Slug = slug }).ToList();
if (newTags.Count > 0)
{
await db.PostTags.AddRangeAsync(newTags);
@@ -288,7 +288,7 @@ public partial class PostService(
[GeneratedRegex(@"https?://(?!.*\.\w{1,6}(?:[#?]|$))[^\s]+", RegexOptions.IgnoreCase)]
private static partial Regex GetLinkRegex();
public async Task<Post> PreviewPostLinkAsync(Post item)
public async Task<SnPost> PreviewPostLinkAsync(SnPost item)
{
if (item.Type != PostType.Moment || string.IsNullOrEmpty(item.Content)) return item;
@@ -348,7 +348,7 @@ public partial class PostService(
/// This method is designed to be called from a background task
/// </summary>
/// <param name="post">The post to process link previews for</param>
private async Task ProcessPostLinkPreviewAsync(Post post)
private async Task ProcessPostLinkPreviewAsync(SnPost post)
{
try
{
@@ -387,7 +387,7 @@ public partial class PostService(
}
}
public async Task DeletePostAsync(Post post)
public async Task DeletePostAsync(SnPost post)
{
// Delete all file references for this post
await fileRefs.DeleteResourceReferencesAsync(
@@ -420,7 +420,7 @@ public partial class PostService(
}
}
public async Task<Post> PinPostAsync(Post post, Account currentUser, PostPinMode pinMode)
public async Task<SnPost> PinPostAsync(SnPost post, Account currentUser, PostPinMode pinMode)
{
var accountId = Guid.Parse(currentUser.Id);
if (post.RepliedPostId != null)
@@ -430,14 +430,14 @@ public partial class PostService(
if (post.RepliedPost == null) throw new ArgumentNullException(nameof(post.RepliedPost));
if (!await ps.IsMemberWithRole(post.RepliedPost.PublisherId, accountId,
Publisher.PublisherMemberRole.Editor))
Shared.Models.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))
if (!await ps.IsMemberWithRole(post.PublisherId, accountId, Shared.Models.PublisherMemberRole.Editor))
throw new InvalidOperationException("Only editors can pin replies.");
post.PinMode = pinMode;
@@ -449,7 +449,7 @@ public partial class PostService(
return post;
}
public async Task<Post> UnpinPostAsync(Post post, Account currentUser)
public async Task<SnPost> UnpinPostAsync(SnPost post, Account currentUser)
{
var accountId = Guid.Parse(currentUser.Id);
if (post.RepliedPostId != null)
@@ -457,12 +457,12 @@ public partial class PostService(
if (post.RepliedPost == null) throw new ArgumentNullException(nameof(post.RepliedPost));
if (!await ps.IsMemberWithRole(post.RepliedPost.PublisherId, accountId,
Publisher.PublisherMemberRole.Editor))
Shared.Models.PublisherMemberRole.Editor))
throw new InvalidOperationException("Only editors of original post can unpin replies.");
}
else
{
if (!await ps.IsMemberWithRole(post.PublisherId, accountId, Publisher.PublisherMemberRole.Editor))
if (!await ps.IsMemberWithRole(post.PublisherId, accountId, Shared.Models.PublisherMemberRole.Editor))
throw new InvalidOperationException("Only editors can unpin posts.");
}
@@ -484,14 +484,14 @@ public partial class PostService(
/// <param name="isSelfReact">Indicate this reaction is by the original post himself</param>
/// <param name="sender">The account that creates this reaction</param>
public async Task<bool> ModifyPostVotes(
Post post,
PostReaction reaction,
SnPost post,
SnPostReaction reaction,
Account sender,
bool isRemoving,
bool isSelfReact
)
{
var isExistingReaction = await db.Set<PostReaction>()
var isExistingReaction = await db.Set<SnPostReaction>()
.AnyAsync(r => r.PostId == post.Id && r.AccountId == reaction.AccountId);
if (isRemoving)
@@ -576,7 +576,7 @@ public partial class PostService(
public async Task<Dictionary<string, int>> GetPostReactionMap(Guid postId)
{
return await db.Set<PostReaction>()
return await db.Set<SnPostReaction>()
.Where(r => r.PostId == postId)
.GroupBy(r => r.Symbol)
.ToDictionaryAsync(
@@ -587,7 +587,7 @@ public partial class PostService(
public async Task<Dictionary<Guid, Dictionary<string, int>>> GetPostReactionMapBatch(List<Guid> postIds)
{
return await db.Set<PostReaction>()
return await db.Set<SnPostReaction>()
.Where(r => postIds.Contains(r.PostId))
.GroupBy(r => r.PostId)
.ToDictionaryAsync(
@@ -603,7 +603,7 @@ public partial class PostService(
public async Task<Dictionary<Guid, Dictionary<string, bool>>> GetPostReactionMadeMapBatch(List<Guid> postIds,
Guid accountId)
{
var reactions = await db.Set<PostReaction>()
var reactions = await db.Set<SnPostReaction>()
.Where(r => postIds.Contains(r.PostId) && r.AccountId == accountId)
.Select(r => new { r.PostId, r.Symbol })
.ToListAsync();
@@ -653,10 +653,10 @@ public partial class PostService(
});
}
public async Task<List<Post>> LoadPublishers(List<Post> posts)
public async Task<List<SnPost>> LoadPublishers(List<SnPost> posts)
{
var publisherIds = posts
.SelectMany<Post, Guid?>(e =>
.SelectMany<SnPost, Guid?>(e =>
[
e.PublisherId,
e.RepliedPost?.PublisherId,
@@ -688,7 +688,7 @@ public partial class PostService(
return posts;
}
public async Task<List<Post>> LoadInteractive(List<Post> posts, Account? currentUser = null)
public async Task<List<SnPost>> LoadInteractive(List<SnPost> posts, Account? currentUser = null)
{
if (posts.Count == 0) return posts;
@@ -738,7 +738,7 @@ public partial class PostService(
);
}
private async Task LoadPostEmbed(Post post, Account? currentUser)
private async Task LoadPostEmbed(SnPost post, Account? currentUser)
{
if (!post.Meta!.TryGetValue("embeds", out var value))
return;
@@ -778,8 +778,8 @@ public partial class PostService(
}
}
public async Task<List<Post>> LoadPostInfo(
List<Post> posts,
public async Task<List<SnPost>> LoadPostInfo(
List<SnPost> posts,
Account? currentUser = null,
bool truncate = false
)
@@ -801,7 +801,7 @@ public partial class PostService(
return posts;
}
public async Task<Post> LoadPostInfo(Post post, Account? currentUser = null, bool truncate = false)
public async Task<SnPost> LoadPostInfo(SnPost post, Account? currentUser = null, bool truncate = false)
{
// Convert single post to list, process it, then return the single post
var posts = await LoadPostInfo([post], currentUser, truncate);
@@ -810,7 +810,7 @@ public partial class PostService(
private const string FeaturedPostCacheKey = "posts:featured";
public async Task<List<Post>> ListFeaturedPostsAsync(Account? currentUser = null)
public async Task<List<SnPost>> ListFeaturedPostsAsync(Account? currentUser = null)
{
// Check cache first for featured post IDs
var featuredIds = await cache.GetAsync<List<Guid>>(FeaturedPostCacheKey);
@@ -877,7 +877,7 @@ public partial class PostService(
var records = reactSocialPoints
.Where(p => !existingFeaturedPostIds.Contains(p.Key))
.Select(e => new PostFeaturedRecord
.Select(e => new SnPostFeaturedRecord
{
PostId = e.Key,
SocialCredits = e.Value
@@ -904,7 +904,7 @@ public partial class PostService(
return posts;
}
public async Task<PostAward> AwardPost(
public async Task<SnPostAward> AwardPost(
Guid postId,
Guid accountId,
decimal amount,
@@ -915,7 +915,7 @@ public partial class PostService(
var post = await db.Posts.Where(p => p.Id == postId).FirstOrDefaultAsync();
if (post is null) throw new InvalidOperationException("Post not found");
var award = new PostAward
var award = new SnPostAward
{
Amount = amount,
Attitude = attitude,
@@ -983,11 +983,11 @@ public partial class PostService(
public static class PostQueryExtensions
{
public static IQueryable<Post> FilterWithVisibility(
this IQueryable<Post> source,
public static IQueryable<SnPost> FilterWithVisibility(
this IQueryable<SnPost> source,
Account? currentUser,
List<Guid> userFriends,
List<Publisher.Publisher> publishers,
List<Shared.Models.SnPublisher> publishers,
bool isListing = false
)
{