using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; using DysonNetwork.Shared.Proto; using NodaTime; using NpgsqlTypes; using Google.Protobuf.WellKnownTypes; namespace DysonNetwork.Shared.Models; public enum PostType { Moment, Article } public enum PostVisibility { Public, Friends, Unlisted, Private } public enum PostPinMode { PublisherPage, RealmPage, ReplyPage, } public class SnPost : 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? Meta { get; set; } [Column(TypeName = "jsonb")] public List? 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 ReactionsCount { get; set; } = new(); [NotMapped] public int RepliesCount { get; set; } [NotMapped] public Dictionary? ReactionsMade { get; set; } public bool RepliedGone { get; set; } public bool ForwardedGone { get; set; } public Guid? RepliedPostId { get; set; } public SnPost? RepliedPost { get; set; } public Guid? ForwardedPostId { get; set; } public SnPost? ForwardedPost { get; set; } public Guid? RealmId { get; set; } [NotMapped] public SnRealm? Realm { get; set; } [Column(TypeName = "jsonb")] public List Attachments { get; set; } = []; [JsonIgnore] public NpgsqlTsVector SearchVector { get; set; } = null!; public Guid PublisherId { get; set; } public SnPublisher Publisher { get; set; } = null!; public List Awards { get; set; } = null!; [JsonIgnore] public List Reactions { get; set; } = []; public List Tags { get; set; } = []; public List Categories { get; set; } = []; [JsonIgnore] public List Collections { get; set; } = []; public List FeaturedRecords { get; set; } = []; [JsonIgnore] public bool Empty => Content == null && Attachments.Count == 0 && ForwardedPostId == null; [NotMapped] public bool IsTruncated { get; set; } = false; public string ResourceIdentifier => $"post:{Id}"; public Post ToProtoValue() { var proto = new Post { Id = Id.ToString(), Title = Title ?? string.Empty, Description = Description ?? string.Empty, Slug = Slug ?? string.Empty, Visibility = (Proto.PostVisibility)((int)Visibility + 1), Type = (Proto.PostType)((int)Type + 1), ViewsUnique = ViewsUnique, ViewsTotal = ViewsTotal, Upvotes = Upvotes, Downvotes = Downvotes, AwardedScore = (double)AwardedScore, ReactionsCount = { ReactionsCount ?? new Dictionary() }, RepliesCount = RepliesCount, ReactionsMade = { ReactionsMade ?? new Dictionary() }, RepliedGone = RepliedGone, ForwardedGone = ForwardedGone, PublisherId = PublisherId.ToString(), Publisher = Publisher.ToProto(), CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) }; if (EditedAt.HasValue) { proto.EditedAt = Timestamp.FromDateTimeOffset(EditedAt.Value.ToDateTimeOffset()); } if (PublishedAt.HasValue) { proto.PublishedAt = Timestamp.FromDateTimeOffset(PublishedAt.Value.ToDateTimeOffset()); } if (Content != null) { proto.Content = Content; } if (PinMode.HasValue) { proto.PinMode = (Proto.PostPinMode)((int)PinMode.Value); } if (Meta != null) { proto.Meta = GrpcTypeHelper.ConvertObjectToByteString(Meta); } if (SensitiveMarks != null) { proto.SensitiveMarks = GrpcTypeHelper.ConvertObjectToByteString(SensitiveMarks); } if (EmbedView != null) { proto.EmbedView = EmbedView.ToProtoValue(); } if (RepliedPostId.HasValue) { proto.RepliedPostId = RepliedPostId.Value.ToString(); if (RepliedPost != null) { proto.RepliedPost = RepliedPost.ToProtoValue(); } } if (ForwardedPostId.HasValue) { proto.ForwardedPostId = ForwardedPostId.Value.ToString(); if (ForwardedPost != null) { proto.ForwardedPost = ForwardedPost.ToProtoValue(); } } if (RealmId.HasValue) { proto.RealmId = RealmId.Value.ToString(); if (Realm != null) { proto.Realm = Realm.ToProtoValue(); } } proto.Attachments.AddRange(Attachments.Select(a => a.ToProtoValue())); proto.Awards.AddRange(Awards.Select(a => a.ToProto())); proto.Reactions.AddRange(Reactions.Select(r => r.ToProtoValue())); proto.Tags.AddRange(Tags.Select(t => t.ToProtoValue())); proto.Categories.AddRange(Categories.Select(c => c.ToProtoValue())); proto.FeaturedRecords.AddRange(FeaturedRecords.Select(f => f.ToProtoValue())); if (DeletedAt.HasValue) proto.DeletedAt = Timestamp.FromDateTimeOffset(DeletedAt.Value.ToDateTimeOffset()); return proto; } public SnActivity ToActivity() { return new SnActivity() { CreatedAt = PublishedAt ?? CreatedAt, UpdatedAt = UpdatedAt, DeletedAt = DeletedAt, Id = Id, Type = RepliedPostId is null ? "posts.new" : "posts.new.replies", ResourceIdentifier = ResourceIdentifier, Data = this }; } } public class SnPostTag : ModelBase { public Guid Id { get; set; } [MaxLength(128)] public string Slug { get; set; } = null!; [MaxLength(256)] public string? Name { get; set; } [JsonIgnore] public List Posts { get; set; } = new List(); [NotMapped] public int? Usage { get; set; } public PostTag ToProtoValue() { return new PostTag { Id = Id.ToString(), Slug = Slug, Name = Name ?? string.Empty, CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) }; } public static SnPostTag FromProtoValue(PostTag proto) { return new SnPostTag { Id = Guid.Parse(proto.Id), Slug = proto.Slug, Name = proto.Name != string.Empty ? proto.Name : null, CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()), UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()) }; } } public class SnPostCategory : ModelBase { public Guid Id { get; set; } [MaxLength(128)] public string Slug { get; set; } = null!; [MaxLength(256)] public string? Name { get; set; } [JsonIgnore] public List Posts { get; set; } = new List(); [NotMapped] public int? Usage { get; set; } public PostCategory ToProtoValue() { return new PostCategory { Id = Id.ToString(), Slug = Slug, Name = Name ?? string.Empty, CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) }; } public static SnPostCategory FromProtoValue(PostCategory proto) { return new SnPostCategory { Id = Guid.Parse(proto.Id), Slug = proto.Slug, Name = proto.Name != string.Empty ? proto.Name : null, CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()), UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()) }; } } public class SnPostCategorySubscription : ModelBase { public Guid Id { get; set; } public Guid AccountId { get; set; } public Guid? CategoryId { get; set; } public SnPostCategory? Category { get; set; } public Guid? TagId { get; set; } public SnPostTag? Tag { get; set; } } public class SnPostCollection : 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 SnPublisher Publisher { get; set; } = null!; public List Posts { get; set; } = new List(); } public class SnPostFeaturedRecord : ModelBase { public Guid Id { get; set; } public Guid PostId { get; set; } [JsonIgnore] public SnPost Post { get; set; } = null!; public Instant? FeaturedAt { get; set; } public int SocialCredits { get; set; } public PostFeaturedRecord ToProtoValue() { var proto = new PostFeaturedRecord { Id = Id.ToString(), PostId = PostId.ToString(), SocialCredits = SocialCredits, CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) }; if (FeaturedAt.HasValue) { proto.FeaturedAt = Timestamp.FromDateTimeOffset(FeaturedAt.Value.ToDateTimeOffset()); } return proto; } } public enum PostReactionAttitude { Positive, Neutral, Negative, } public class SnPostReaction : 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 SnPost Post { get; set; } = null!; public Guid AccountId { get; set; } [NotMapped] public SnAccount? Account { get; set; } public PostReaction ToProtoValue() { var proto = new PostReaction { Id = Id.ToString(), Symbol = Symbol, Attitude = (Proto.PostReactionAttitude)((int)Attitude + 1), PostId = PostId.ToString(), AccountId = AccountId.ToString(), CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) }; if (Account != null) { proto.Account = Account.ToProtoValue(); } return proto; } public static SnPostReaction FromProtoValue(Proto.PostReaction proto) { return new SnPostReaction { Id = Guid.Parse(proto.Id), Symbol = proto.Symbol, Attitude = (PostReactionAttitude)((int)proto.Attitude - 1), PostId = Guid.Parse(proto.PostId), AccountId = Guid.Parse(proto.AccountId), Account = proto.Account != null ? SnAccount.FromProtoValue(proto.Account) : null, CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()), UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()) }; } } public class SnPostAward : 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 SnPost Post { get; set; } = null!; public Guid AccountId { get; set; } public PostAward ToProto() { var proto = new PostAward { Id = Id.ToString(), Amount = (double)Amount, Attitude = (Proto.PostReactionAttitude)((int)Attitude + 1), PostId = PostId.ToString(), AccountId = AccountId.ToString(), CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) }; if (Message != null) { proto.Message = Message; } return proto; } } /// /// This model is used to tell the client to render a WebView / iframe /// Usually external website and web pages /// Used as a JSON column /// public class PostEmbedView { public string Uri { get; set; } = null!; public double? AspectRatio { get; set; } public PostEmbedViewRenderer Renderer { get; set; } = PostEmbedViewRenderer.WebView; public Proto.PostEmbedView ToProtoValue() { var proto = new Proto.PostEmbedView { Uri = Uri, Renderer = (Proto.PostEmbedViewRenderer)(int)Renderer }; if (AspectRatio.HasValue) { proto.AspectRatio = AspectRatio.Value; } return proto; } public static PostEmbedView FromProtoValue(Proto.PostEmbedView proto) { return new PostEmbedView { Uri = proto.Uri, AspectRatio = proto.HasAspectRatio ? proto.AspectRatio : null, Renderer = (PostEmbedViewRenderer)((int)proto.Renderer - 1) }; } } public enum PostEmbedViewRenderer { WebView }