♻️ Centralized data models (wip)
This commit is contained in:
@@ -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
|
||||
}
|
@@ -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);
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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
|
||||
)
|
||||
{
|
||||
|
Reference in New Issue
Block a user