🚚 Rename activity in sphere to timeline

In order to leave the activity keyword for pass service user activity
This commit is contained in:
2025-10-30 21:46:24 +08:00
parent ab23f87a66
commit 0b65bf8dd7
7 changed files with 323 additions and 208 deletions

View File

@@ -1,34 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using NodaTime;
namespace DysonNetwork.Shared.Models;
public interface IActivity
{
public SnActivity ToActivity();
}
[NotMapped]
public class SnActivity : ModelBase
{
public Guid Id { get; set; }
[MaxLength(1024)] public string Type { get; set; } = null!;
[MaxLength(4096)] public string ResourceIdentifier { get; set; } = null!;
[Column(TypeName = "jsonb")] public Dictionary<string, object> Meta { get; set; } = new();
public object? Data { get; set; }
public static SnActivity Empty()
{
var now = SystemClock.Instance.GetCurrentInstant();
return new SnActivity
{
CreatedAt = now,
UpdatedAt = now,
Id = Guid.NewGuid(),
Type = "empty",
ResourceIdentifier = "none"
};
}
}

View File

@@ -2,16 +2,15 @@ using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Proto;
using NodaTime;
using NpgsqlTypes;
using Google.Protobuf.WellKnownTypes; using Google.Protobuf.WellKnownTypes;
using NodaTime;
namespace DysonNetwork.Shared.Models; namespace DysonNetwork.Shared.Models;
public enum PostType public enum PostType
{ {
Moment, Moment,
Article Article,
} }
public enum PostVisibility public enum PostVisibility
@@ -19,7 +18,7 @@ public enum PostVisibility
Public, Public,
Friends, Friends,
Unlisted, Unlisted,
Private Private,
} }
public enum PostPinMode public enum PostPinMode
@@ -29,12 +28,18 @@ public enum PostPinMode
ReplyPage, ReplyPage,
} }
public class SnPost : ModelBase, IIdentifiedResource, IActivity public class SnPost : ModelBase, IIdentifiedResource, ITimelineEvent
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
[MaxLength(1024)] public string? Title { get; set; }
[MaxLength(4096)] public string? Description { get; set; } [MaxLength(1024)]
[MaxLength(1024)] public string? Slug { get; set; } 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? EditedAt { get; set; }
public Instant? PublishedAt { get; set; } public Instant? PublishedAt { get; set; }
public PostVisibility Visibility { get; set; } = PostVisibility.Public; public PostVisibility Visibility { get; set; } = PostVisibility.Public;
@@ -44,18 +49,30 @@ public class SnPost : ModelBase, IIdentifiedResource, IActivity
public PostType Type { get; set; } public PostType Type { get; set; }
public PostPinMode? PinMode { 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")]
[Column(TypeName = "jsonb")] public PostEmbedView? EmbedView { get; set; } 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 ViewsUnique { get; set; }
public int ViewsTotal { get; set; } public int ViewsTotal { get; set; }
public int Upvotes { get; set; } public int Upvotes { get; set; }
public int Downvotes { get; set; } public int Downvotes { get; set; }
public decimal AwardedScore { get; set; } public decimal AwardedScore { get; set; }
[NotMapped] public Dictionary<string, int> ReactionsCount { get; set; } = new();
[NotMapped] public int RepliesCount { get; set; } [NotMapped]
[NotMapped] public Dictionary<string, bool>? ReactionsMade { get; set; } 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 RepliedGone { get; set; }
public bool ForwardedGone { get; set; } public bool ForwardedGone { get; set; }
@@ -66,22 +83,32 @@ public class SnPost : ModelBase, IIdentifiedResource, IActivity
public SnPost? ForwardedPost { get; set; } public SnPost? ForwardedPost { get; set; }
public Guid? RealmId { get; set; } public Guid? RealmId { get; set; }
[NotMapped] public SnRealm? Realm { get; set; }
[Column(TypeName = "jsonb")] public List<SnCloudFileReferenceObject> Attachments { get; set; } = []; [NotMapped]
public SnRealm? Realm { get; set; }
[Column(TypeName = "jsonb")]
public List<SnCloudFileReferenceObject> Attachments { get; set; } = [];
public Guid PublisherId { get; set; } public Guid PublisherId { get; set; }
public SnPublisher Publisher { get; set; } = null!; public SnPublisher Publisher { get; set; } = null!;
public List<SnPostAward> Awards { get; set; } = []; public List<SnPostAward> Awards { get; set; } = [];
[JsonIgnore] public List<SnPostReaction> Reactions { get; set; } = [];
[JsonIgnore]
public List<SnPostReaction> Reactions { get; set; } = [];
public List<SnPostTag> Tags { get; set; } = []; public List<SnPostTag> Tags { get; set; } = [];
public List<SnPostCategory> Categories { get; set; } = []; public List<SnPostCategory> Categories { get; set; } = [];
[JsonIgnore] public List<SnPostCollection> Collections { get; set; } = [];
[JsonIgnore]
public List<SnPostCollection> Collections { get; set; } = [];
public List<SnPostFeaturedRecord> FeaturedRecords { get; set; } = []; public List<SnPostFeaturedRecord> FeaturedRecords { get; set; } = [];
[JsonIgnore] public bool Empty => Content == null && Attachments.Count == 0 && ForwardedPostId == null; [JsonIgnore]
[NotMapped] public bool IsTruncated { get; set; } = false; public bool Empty => Content == null && Attachments.Count == 0 && ForwardedPostId == null;
[NotMapped]
public bool IsTruncated { get; set; } = false;
public string ResourceIdentifier => $"post:{Id}"; public string ResourceIdentifier => $"post:{Id}";
@@ -108,7 +135,7 @@ public class SnPost : ModelBase, IIdentifiedResource, IActivity
PublisherId = PublisherId.ToString(), PublisherId = PublisherId.ToString(),
Publisher = Publisher.ToProtoValue(), Publisher = Publisher.ToProtoValue(),
CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()),
UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()),
}; };
if (EditedAt.HasValue) if (EditedAt.HasValue)
@@ -195,7 +222,7 @@ public class SnPost : ModelBase, IIdentifiedResource, IActivity
PublisherId = Guid.Parse(proto.PublisherId), PublisherId = Guid.Parse(proto.PublisherId),
Publisher = SnPublisher.FromProtoValue(proto.Publisher), Publisher = SnPublisher.FromProtoValue(proto.Publisher),
CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()), CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()),
UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()) UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()),
}; };
if (proto.EditedAt is not null) if (proto.EditedAt is not null)
@@ -211,11 +238,14 @@ public class SnPost : ModelBase, IIdentifiedResource, IActivity
post.PinMode = (PostPinMode)(proto.PinMode - 1); post.PinMode = (PostPinMode)(proto.PinMode - 1);
if (proto.Meta != null) if (proto.Meta != null)
post.Meta = GrpcTypeHelper.ConvertByteStringToObject<Dictionary<string, object>>(proto.Meta); post.Meta = GrpcTypeHelper.ConvertByteStringToObject<Dictionary<string, object>>(
proto.Meta
);
if (proto.SensitiveMarks != null) if (proto.SensitiveMarks != null)
post.SensitiveMarks = post.SensitiveMarks = GrpcTypeHelper.ConvertByteStringToObject<
GrpcTypeHelper.ConvertByteStringToObject<List<ContentSensitiveMark>>(proto.SensitiveMarks); List<ContentSensitiveMark>
>(proto.SensitiveMarks);
if (proto.EmbedView is not null) if (proto.EmbedView is not null)
post.EmbedView = PostEmbedView.FromProtoValue(proto.EmbedView); post.EmbedView = PostEmbedView.FromProtoValue(proto.EmbedView);
@@ -241,19 +271,28 @@ public class SnPost : ModelBase, IIdentifiedResource, IActivity
post.Realm = SnRealm.FromProtoValue(proto.Realm); post.Realm = SnRealm.FromProtoValue(proto.Realm);
} }
post.Attachments.AddRange(proto.Attachments.Select(SnCloudFileReferenceObject.FromProtoValue)); post.Attachments.AddRange(
post.Awards.AddRange(proto.Awards.Select(a => new SnPostAward proto.Attachments.Select(SnCloudFileReferenceObject.FromProtoValue)
{ );
Id = Guid.Parse(a.Id), PostId = Guid.Parse(a.PostId), AccountId = Guid.Parse(a.AccountId), post.Awards.AddRange(
Amount = (decimal)a.Amount, Attitude = (PostReactionAttitude)((int)a.Attitude - 1), proto.Awards.Select(a => new SnPostAward
Message = string.IsNullOrEmpty(a.Message) ? null : a.Message, {
CreatedAt = Instant.FromDateTimeOffset(a.CreatedAt.ToDateTimeOffset()), Id = Guid.Parse(a.Id),
UpdatedAt = Instant.FromDateTimeOffset(a.UpdatedAt.ToDateTimeOffset()) PostId = Guid.Parse(a.PostId),
})); AccountId = Guid.Parse(a.AccountId),
Amount = (decimal)a.Amount,
Attitude = (PostReactionAttitude)((int)a.Attitude - 1),
Message = string.IsNullOrEmpty(a.Message) ? null : a.Message,
CreatedAt = Instant.FromDateTimeOffset(a.CreatedAt.ToDateTimeOffset()),
UpdatedAt = Instant.FromDateTimeOffset(a.UpdatedAt.ToDateTimeOffset()),
})
);
post.Reactions.AddRange(proto.Reactions.Select(SnPostReaction.FromProtoValue)); post.Reactions.AddRange(proto.Reactions.Select(SnPostReaction.FromProtoValue));
post.Tags.AddRange(proto.Tags.Select(SnPostTag.FromProtoValue)); post.Tags.AddRange(proto.Tags.Select(SnPostTag.FromProtoValue));
post.Categories.AddRange(proto.Categories.Select(SnPostCategory.FromProtoValue)); post.Categories.AddRange(proto.Categories.Select(SnPostCategory.FromProtoValue));
post.FeaturedRecords.AddRange(proto.FeaturedRecords.Select(SnPostFeaturedRecord.FromProtoValue)); post.FeaturedRecords.AddRange(
proto.FeaturedRecords.Select(SnPostFeaturedRecord.FromProtoValue)
);
if (proto.DeletedAt is not null) if (proto.DeletedAt is not null)
post.DeletedAt = Instant.FromDateTimeOffset(proto.DeletedAt.ToDateTimeOffset()); post.DeletedAt = Instant.FromDateTimeOffset(proto.DeletedAt.ToDateTimeOffset());
@@ -261,9 +300,9 @@ public class SnPost : ModelBase, IIdentifiedResource, IActivity
return post; return post;
} }
public SnActivity ToActivity() public SnTimelineEvent ToActivity()
{ {
return new SnActivity() return new SnTimelineEvent()
{ {
CreatedAt = PublishedAt ?? CreatedAt, CreatedAt = PublishedAt ?? CreatedAt,
UpdatedAt = UpdatedAt, UpdatedAt = UpdatedAt,
@@ -271,7 +310,7 @@ public class SnPost : ModelBase, IIdentifiedResource, IActivity
Id = Id, Id = Id,
Type = RepliedPostId is null ? "posts.new" : "posts.new.replies", Type = RepliedPostId is null ? "posts.new" : "posts.new.replies",
ResourceIdentifier = ResourceIdentifier, ResourceIdentifier = ResourceIdentifier,
Data = this Data = this,
}; };
} }
} }
@@ -279,11 +318,18 @@ public class SnPost : ModelBase, IIdentifiedResource, IActivity
public class SnPostTag : ModelBase public class SnPostTag : ModelBase
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
[MaxLength(128)] public string Slug { get; set; } = null!;
[MaxLength(256)] public string? Name { get; set; }
[JsonIgnore] public List<SnPost> Posts { get; set; } = new List<SnPost>();
[NotMapped] public int? Usage { get; set; } [MaxLength(128)]
public string Slug { get; set; } = null!;
[MaxLength(256)]
public string? Name { get; set; }
[JsonIgnore]
public List<SnPost> Posts { get; set; } = new List<SnPost>();
[NotMapped]
public int? Usage { get; set; }
public PostTag ToProtoValue() public PostTag ToProtoValue()
{ {
@@ -293,7 +339,7 @@ public class SnPostTag : ModelBase
Slug = Slug, Slug = Slug,
Name = Name ?? string.Empty, Name = Name ?? string.Empty,
CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()),
UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()),
}; };
} }
@@ -305,7 +351,7 @@ public class SnPostTag : ModelBase
Slug = proto.Slug, Slug = proto.Slug,
Name = proto.Name != string.Empty ? proto.Name : null, Name = proto.Name != string.Empty ? proto.Name : null,
CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()), CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()),
UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()) UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()),
}; };
} }
} }
@@ -313,11 +359,18 @@ public class SnPostTag : ModelBase
public class SnPostCategory : ModelBase public class SnPostCategory : ModelBase
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
[MaxLength(128)] public string Slug { get; set; } = null!;
[MaxLength(256)] public string? Name { get; set; }
[JsonIgnore] public List<SnPost> Posts { get; set; } = new List<SnPost>();
[NotMapped] public int? Usage { get; set; } [MaxLength(128)]
public string Slug { get; set; } = null!;
[MaxLength(256)]
public string? Name { get; set; }
[JsonIgnore]
public List<SnPost> Posts { get; set; } = new List<SnPost>();
[NotMapped]
public int? Usage { get; set; }
public PostCategory ToProtoValue() public PostCategory ToProtoValue()
{ {
@@ -327,7 +380,7 @@ public class SnPostCategory : ModelBase
Slug = Slug, Slug = Slug,
Name = Name ?? string.Empty, Name = Name ?? string.Empty,
CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()),
UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()),
}; };
} }
@@ -339,7 +392,7 @@ public class SnPostCategory : ModelBase
Slug = proto.Slug, Slug = proto.Slug,
Name = proto.Name != string.Empty ? proto.Name : null, Name = proto.Name != string.Empty ? proto.Name : null,
CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()), CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()),
UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()) UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()),
}; };
} }
} }
@@ -358,9 +411,15 @@ public class SnPostCategorySubscription : ModelBase
public class SnPostCollection : ModelBase public class SnPostCollection : ModelBase
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
[MaxLength(128)] public string Slug { get; set; } = null!;
[MaxLength(256)] public string? Name { get; set; } [MaxLength(128)]
[MaxLength(4096)] public string? Description { get; set; } 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 SnPublisher Publisher { get; set; } = null!;
@@ -371,7 +430,9 @@ public class SnPostFeaturedRecord : ModelBase
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public Guid PostId { get; set; } public Guid PostId { get; set; }
[JsonIgnore] public SnPost Post { get; set; } = null!;
[JsonIgnore]
public SnPost Post { get; set; } = null!;
public Instant? FeaturedAt { get; set; } public Instant? FeaturedAt { get; set; }
public int SocialCredits { get; set; } public int SocialCredits { get; set; }
@@ -383,7 +444,7 @@ public class SnPostFeaturedRecord : ModelBase
PostId = PostId.ToString(), PostId = PostId.ToString(),
SocialCredits = SocialCredits, SocialCredits = SocialCredits,
CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()),
UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()),
}; };
if (FeaturedAt.HasValue) if (FeaturedAt.HasValue)
{ {
@@ -402,7 +463,10 @@ public class SnPostFeaturedRecord : ModelBase
SocialCredits = proto.SocialCredits, SocialCredits = proto.SocialCredits,
CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()), CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()),
UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()), UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()),
FeaturedAt = proto.FeaturedAt != null ? Instant.FromDateTimeOffset(proto.FeaturedAt.ToDateTimeOffset()) : null FeaturedAt =
proto.FeaturedAt != null
? Instant.FromDateTimeOffset(proto.FeaturedAt.ToDateTimeOffset())
: null,
}; };
} }
} }
@@ -417,13 +481,19 @@ public enum PostReactionAttitude
public class SnPostReaction : ModelBase public class SnPostReaction : ModelBase
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
[MaxLength(256)] public string Symbol { get; set; } = null!;
[MaxLength(256)]
public string Symbol { get; set; } = null!;
public PostReactionAttitude Attitude { get; set; } public PostReactionAttitude Attitude { get; set; }
public Guid PostId { get; set; } public Guid PostId { get; set; }
[JsonIgnore] public SnPost Post { get; set; } = null!;
[JsonIgnore]
public SnPost Post { get; set; } = null!;
public Guid AccountId { get; set; } public Guid AccountId { get; set; }
[NotMapped] public SnAccount? Account { get; set; }
[NotMapped]
public SnAccount? Account { get; set; }
public PostReaction ToProtoValue() public PostReaction ToProtoValue()
{ {
@@ -435,7 +505,7 @@ public class SnPostReaction : ModelBase
PostId = PostId.ToString(), PostId = PostId.ToString(),
AccountId = AccountId.ToString(), AccountId = AccountId.ToString(),
CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()),
UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()),
}; };
if (Account != null) if (Account != null)
{ {
@@ -456,7 +526,7 @@ public class SnPostReaction : ModelBase
AccountId = Guid.Parse(proto.AccountId), AccountId = Guid.Parse(proto.AccountId),
Account = proto.Account != null ? SnAccount.FromProtoValue(proto.Account) : null, Account = proto.Account != null ? SnAccount.FromProtoValue(proto.Account) : null,
CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()), CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()),
UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()) UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()),
}; };
} }
} }
@@ -466,10 +536,14 @@ public class SnPostAward : ModelBase
public Guid Id { get; set; } public Guid Id { get; set; }
public decimal Amount { get; set; } public decimal Amount { get; set; }
public PostReactionAttitude Attitude { get; set; } public PostReactionAttitude Attitude { get; set; }
[MaxLength(4096)] public string? Message { get; set; }
[MaxLength(4096)]
public string? Message { get; set; }
public Guid PostId { get; set; } public Guid PostId { get; set; }
[JsonIgnore] public SnPost Post { get; set; } = null!;
[JsonIgnore]
public SnPost Post { get; set; } = null!;
public Guid AccountId { get; set; } public Guid AccountId { get; set; }
public PostAward ToProtoValue() public PostAward ToProtoValue()
@@ -482,7 +556,7 @@ public class SnPostAward : ModelBase
PostId = PostId.ToString(), PostId = PostId.ToString(),
AccountId = AccountId.ToString(), AccountId = AccountId.ToString(),
CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()),
UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()),
}; };
if (Message != null) if (Message != null)
proto.Message = Message; proto.Message = Message;
@@ -506,7 +580,7 @@ public class PostEmbedView
var proto = new Proto.PostEmbedView var proto = new Proto.PostEmbedView
{ {
Uri = Uri, Uri = Uri,
Renderer = (Proto.PostEmbedViewRenderer)(int)Renderer Renderer = (Proto.PostEmbedViewRenderer)(int)Renderer,
}; };
if (AspectRatio.HasValue) if (AspectRatio.HasValue)
{ {
@@ -522,12 +596,12 @@ public class PostEmbedView
{ {
Uri = proto.Uri, Uri = proto.Uri,
AspectRatio = proto.HasAspectRatio ? proto.AspectRatio : null, AspectRatio = proto.HasAspectRatio ? proto.AspectRatio : null,
Renderer = (PostEmbedViewRenderer)((int)proto.Renderer - 1) Renderer = (PostEmbedViewRenderer)((int)proto.Renderer - 1),
}; };
} }
} }
public enum PostEmbedViewRenderer public enum PostEmbedViewRenderer
{ {
WebView WebView,
} }

View File

@@ -0,0 +1,41 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using NodaTime;
namespace DysonNetwork.Shared.Models;
public interface ITimelineEvent
{
public SnTimelineEvent ToActivity();
}
[NotMapped]
public class SnTimelineEvent : ModelBase
{
public Guid Id { get; set; }
[MaxLength(1024)]
public string Type { get; set; } = null!;
[MaxLength(4096)]
public string ResourceIdentifier { get; set; } = null!;
[Column(TypeName = "jsonb")]
public Dictionary<string, object> Meta { get; set; } = new();
public object? Data { get; set; }
public static SnTimelineEvent Empty()
{
var now = SystemClock.Instance.GetCurrentInstant();
return new SnTimelineEvent
{
CreatedAt = now,
UpdatedAt = now,
Id = Guid.NewGuid(),
Type = "empty",
ResourceIdentifier = "none",
};
}
}

View File

@@ -1,23 +1,23 @@
using System.Globalization; using System.Globalization;
using DysonNetwork.Sphere.Activity;
using DysonNetwork.Sphere.Chat;
using DysonNetwork.Sphere.Chat.Realtime;
using DysonNetwork.Sphere.Localization;
using DysonNetwork.Sphere.Post;
using DysonNetwork.Sphere.Publisher;
using DysonNetwork.Sphere.Sticker;
using NodaTime;
using NodaTime.Serialization.SystemTextJson;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using DysonNetwork.Shared.Cache; using DysonNetwork.Shared.Cache;
using DysonNetwork.Shared.GeoIp; using DysonNetwork.Shared.GeoIp;
using DysonNetwork.Shared.Registry; using DysonNetwork.Shared.Registry;
using DysonNetwork.Sphere.Autocompletion; using DysonNetwork.Sphere.Autocompletion;
using DysonNetwork.Sphere.WebReader; using DysonNetwork.Sphere.Chat;
using DysonNetwork.Sphere.Chat.Realtime;
using DysonNetwork.Sphere.Discovery; using DysonNetwork.Sphere.Discovery;
using DysonNetwork.Sphere.Localization;
using DysonNetwork.Sphere.Poll; using DysonNetwork.Sphere.Poll;
using DysonNetwork.Sphere.Post;
using DysonNetwork.Sphere.Publisher;
using DysonNetwork.Sphere.Sticker;
using DysonNetwork.Sphere.Timeline;
using DysonNetwork.Sphere.Translation; using DysonNetwork.Sphere.Translation;
using DysonNetwork.Sphere.WebReader;
using NodaTime;
using NodaTime.Serialization.SystemTextJson;
namespace DysonNetwork.Sphere.Startup; namespace DysonNetwork.Sphere.Startup;
@@ -34,34 +34,41 @@ public static class ServiceCollectionExtensions
services.AddHttpClient(); services.AddHttpClient();
services.AddControllers().AddJsonOptions(options => services
{ .AddControllers()
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals; .AddJsonOptions(options =>
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; {
options.JsonSerializerOptions.NumberHandling =
JsonNumberHandling.AllowNamedFloatingPointLiterals;
options.JsonSerializerOptions.PropertyNamingPolicy =
JsonNamingPolicy.SnakeCaseLower;
options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
}).AddDataAnnotationsLocalization(options => })
{ .AddDataAnnotationsLocalization(options =>
options.DataAnnotationLocalizerProvider = (type, factory) => {
factory.Create(typeof(SharedResource)); options.DataAnnotationLocalizerProvider = (type, factory) =>
}).ConfigureApplicationPartManager(opts => factory.Create(typeof(SharedResource));
{ })
var mockingPart = opts.ApplicationParts.FirstOrDefault(a => a.Name == "DysonNetwork.Pass"); .ConfigureApplicationPartManager(opts =>
if (mockingPart != null) {
opts.ApplicationParts.Remove(mockingPart); var mockingPart = opts.ApplicationParts.FirstOrDefault(a =>
}); a.Name == "DysonNetwork.Pass"
);
if (mockingPart != null)
opts.ApplicationParts.Remove(mockingPart);
});
services.AddRazorPages(); services.AddRazorPages();
services.AddGrpc(options => { options.EnableDetailedErrors = true; }); services.AddGrpc(options =>
{
options.EnableDetailedErrors = true;
});
services.AddGrpcReflection(); services.AddGrpcReflection();
services.Configure<RequestLocalizationOptions>(options => services.Configure<RequestLocalizationOptions>(options =>
{ {
var supportedCultures = new[] var supportedCultures = new[] { new CultureInfo("en-US"), new CultureInfo("zh-Hans") };
{
new CultureInfo("en-US"),
new CultureInfo("zh-Hans"),
};
options.SupportedCultures = supportedCultures; options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures; options.SupportedUICultures = supportedCultures;
@@ -71,7 +78,7 @@ public static class ServiceCollectionExtensions
return services; return services;
} }
public static IServiceCollection AddAppAuthentication(this IServiceCollection services) public static IServiceCollection AddAppAuthentication(this IServiceCollection services)
{ {
services.AddAuthorization(); services.AddAuthorization();
@@ -86,14 +93,16 @@ public static class ServiceCollectionExtensions
return services; return services;
} }
public static IServiceCollection AddAppBusinessServices(this IServiceCollection services, public static IServiceCollection AddAppBusinessServices(
IConfiguration configuration) this IServiceCollection services,
IConfiguration configuration
)
{ {
services.Configure<GeoIpOptions>(configuration.GetSection("GeoIP")); services.Configure<GeoIpOptions>(configuration.GetSection("GeoIP"));
services.AddScoped<GeoIpService>(); services.AddScoped<GeoIpService>();
services.AddScoped<PublisherService>(); services.AddScoped<PublisherService>();
services.AddScoped<PublisherSubscriptionService>(); services.AddScoped<PublisherSubscriptionService>();
services.AddScoped<ActivityService>(); services.AddScoped<TimelineService>();
services.AddScoped<PostService>(); services.AddScoped<PostService>();
services.AddScoped<ChatRoomService>(); services.AddScoped<ChatRoomService>();
services.AddScoped<ChatService>(); services.AddScoped<ChatService>();

View File

@@ -4,16 +4,14 @@ using Microsoft.AspNetCore.Mvc;
using NodaTime; using NodaTime;
using NodaTime.Text; using NodaTime.Text;
namespace DysonNetwork.Sphere.Activity; namespace DysonNetwork.Sphere.Timeline;
/// <summary> /// <summary>
/// Activity is a universal feed that contains multiple kinds of data. Personalized and generated dynamically. /// Activity is a universal feed that contains multiple kinds of data. Personalized and generated dynamically.
/// </summary> /// </summary>
[ApiController] [ApiController]
[Route("/api/activities")] [Route("/api/timeline")]
public class ActivityController( public class ActivityController(TimelineService acts) : ControllerBase
ActivityService acts
) : ControllerBase
{ {
/// <summary> /// <summary>
/// Listing the activities for the user, users may be logged in or not to use this API. /// Listing the activities for the user, users may be logged in or not to use this API.
@@ -24,7 +22,7 @@ public class ActivityController(
/// Besides, when users are logged in, it will also mix the other kinds of data and who're plying to them. /// Besides, when users are logged in, it will also mix the other kinds of data and who're plying to them.
/// </summary> /// </summary>
[HttpGet] [HttpGet]
public async Task<ActionResult<List<SnActivity>>> ListActivities( public async Task<ActionResult<List<SnTimelineEvent>>> ListEvents(
[FromQuery] string? cursor, [FromQuery] string? cursor,
[FromQuery] string? filter, [FromQuery] string? filter,
[FromQuery] int take = 20, [FromQuery] int take = 20,
@@ -48,7 +46,9 @@ public class ActivityController(
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
return currentUserValue is not Account currentUser return currentUserValue is not Account currentUser
? Ok(await acts.GetActivitiesForAnyone(take, cursorTimestamp, debugIncludeSet)) ? Ok(await acts.ListEventsForAnyone(take, cursorTimestamp, debugIncludeSet))
: Ok(await acts.GetActivities(take, cursorTimestamp, currentUser, filter, debugIncludeSet)); : Ok(
await acts.ListEvents(take, cursorTimestamp, currentUser, filter, debugIncludeSet)
);
} }
} }

View File

@@ -1,16 +1,16 @@
using DysonNetwork.Shared.Models; using DysonNetwork.Shared.Models;
using NodaTime; using NodaTime;
namespace DysonNetwork.Sphere.Activity; namespace DysonNetwork.Sphere.Timeline;
public class DiscoveryActivity(List<DiscoveryItem> items) : IActivity public class TimelineDiscoveryEvent(List<DiscoveryItem> items) : ITimelineEvent
{ {
public List<DiscoveryItem> Items { get; set; } = items; public List<DiscoveryItem> Items { get; set; } = items;
public SnActivity ToActivity() public SnTimelineEvent ToActivity()
{ {
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();
return new SnActivity return new SnTimelineEvent
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Type = "discovery", Type = "discovery",
@@ -22,4 +22,5 @@ public class DiscoveryActivity(List<DiscoveryItem> items) : IActivity
} }
} }
public record DiscoveryItem(string Type, object Data); public record DiscoveryItem(string Type, object Data);

View File

@@ -7,9 +7,9 @@ using DysonNetwork.Sphere.WebReader;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NodaTime; using NodaTime;
namespace DysonNetwork.Sphere.Activity; namespace DysonNetwork.Sphere.Timeline;
public class ActivityService( public class TimelineService(
AppDatabase db, AppDatabase db,
Publisher.PublisherService pub, Publisher.PublisherService pub,
Post.PostService ps, Post.PostService ps,
@@ -20,23 +20,25 @@ public class ActivityService(
{ {
private static double CalculateHotRank(SnPost post, Instant now) private static double CalculateHotRank(SnPost post, Instant now)
{ {
var performanceScore = post.Upvotes - post.Downvotes + post.RepliesCount + (int)post.AwardedScore / 10; var performanceScore =
post.Upvotes - post.Downvotes + post.RepliesCount + (int)post.AwardedScore / 10;
var postTime = post.PublishedAt ?? post.CreatedAt; var postTime = post.PublishedAt ?? post.CreatedAt;
var timeScore = (now - postTime).TotalMinutes; var timeScore = (now - postTime).TotalMinutes;
// Add 1 to score to prevent negative results for posts with more downvotes than upvotes // Add 1 to score to prevent negative results for posts with more downvotes than upvotes
// Time dominates ranking, performance adjusts within similar timeframes. // Time dominates ranking, performance adjusts within similar timeframes.
var performanceWeight = performanceScore + 5; var performanceWeight = performanceScore + 5;
// Normalize time influence since average post interval ~60 minutes // Normalize time influence since average post interval ~60 minutes
var normalizedTime = timeScore / 60.0; var normalizedTime = timeScore / 60.0;
return performanceWeight / Math.Pow(normalizedTime + 1.0, 1.2); return performanceWeight / Math.Pow(normalizedTime + 1.0, 1.2);
} }
public async Task<List<SnActivity>> GetActivitiesForAnyone( public async Task<List<SnTimelineEvent>> ListEventsForAnyone(
int take, int take,
Instant? cursor, Instant? cursor,
HashSet<string>? debugInclude = null) HashSet<string>? debugInclude = null
)
{ {
var activities = new List<SnActivity>(); var activities = new List<SnTimelineEvent>();
debugInclude ??= new HashSet<string>(); debugInclude ??= new HashSet<string>();
// Get and process posts // Get and process posts
@@ -51,7 +53,7 @@ public class ActivityService(
await LoadPostsRealmsAsync(posts, rs); await LoadPostsRealmsAsync(posts, rs);
posts = RankPosts(posts, take); posts = RankPosts(posts, take);
var interleaved = new List<SnActivity>(); var interleaved = new List<SnTimelineEvent>();
var random = new Random(); var random = new Random();
foreach (var post in posts) foreach (var post in posts)
{ {
@@ -69,26 +71,26 @@ public class ActivityService(
activities.AddRange(interleaved); activities.AddRange(interleaved);
if (activities.Count == 0) if (activities.Count == 0)
activities.Add(SnActivity.Empty()); activities.Add(SnTimelineEvent.Empty());
return activities; return activities;
} }
public async Task<List<SnActivity>> GetActivities( public async Task<List<SnTimelineEvent>> ListEvents(
int take, int take,
Instant? cursor, Instant? cursor,
Account currentUser, Account currentUser,
string? filter = null, string? filter = null,
HashSet<string>? debugInclude = null) HashSet<string>? debugInclude = null
)
{ {
var activities = new List<SnActivity>(); var activities = new List<SnTimelineEvent>();
debugInclude ??= new HashSet<string>(); debugInclude ??= new HashSet<string>();
// Get user's friends and publishers // Get user's friends and publishers
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest var friendsResponse = await accounts.ListFriendsAsync(
{ new ListRelationshipSimpleRequest { AccountId = currentUser.Id }
AccountId = currentUser.Id );
});
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));
@@ -107,20 +109,18 @@ public class ActivityService(
currentUser, currentUser,
userFriends, userFriends,
filter is null ? userPublishers : [], filter is null ? userPublishers : [],
isListing: true) isListing: true
)
.Take(take * 5); .Take(take * 5);
// Get, process and rank posts // Get, process and rank posts
var posts = await GetAndProcessPosts( var posts = await GetAndProcessPosts(postsQuery, currentUser, trackViews: true);
postsQuery,
currentUser,
trackViews: true);
await LoadPostsRealmsAsync(posts, rs); await LoadPostsRealmsAsync(posts, rs);
posts = RankPosts(posts, take); posts = RankPosts(posts, take);
var interleaved = new List<SnActivity>(); var interleaved = new List<SnTimelineEvent>();
var random = new Random(); var random = new Random();
foreach (var post in posts) foreach (var post in posts)
{ {
@@ -137,15 +137,19 @@ public class ActivityService(
activities.AddRange(interleaved); activities.AddRange(interleaved);
if (activities.Count == 0) if (activities.Count == 0)
activities.Add(SnActivity.Empty()); activities.Add(SnTimelineEvent.Empty());
return activities; return activities;
} }
private async Task<SnActivity?> MaybeGetDiscoveryActivity(HashSet<string> debugInclude, Instant? cursor) private async Task<SnTimelineEvent?> MaybeGetDiscoveryActivity(
HashSet<string> debugInclude,
Instant? cursor
)
{ {
if (cursor != null) return null; if (cursor != null)
var options = new List<Func<Task<SnActivity?>>>(); return null;
var options = new List<Func<Task<SnTimelineEvent?>>>();
if (debugInclude.Contains("realms") || Random.Shared.NextDouble() < 0.2) if (debugInclude.Contains("realms") || Random.Shared.NextDouble() < 0.2)
options.Add(() => GetRealmDiscoveryActivity()); options.Add(() => GetRealmDiscoveryActivity());
if (debugInclude.Contains("publishers") || Random.Shared.NextDouble() < 0.2) if (debugInclude.Contains("publishers") || Random.Shared.NextDouble() < 0.2)
@@ -154,7 +158,8 @@ public class ActivityService(
options.Add(() => GetArticleDiscoveryActivity()); options.Add(() => GetArticleDiscoveryActivity());
if (debugInclude.Contains("shuffledPosts") || Random.Shared.NextDouble() < 0.2) if (debugInclude.Contains("shuffledPosts") || Random.Shared.NextDouble() < 0.2)
options.Add(() => GetShuffledPostsActivity()); options.Add(() => GetShuffledPostsActivity());
if (options.Count == 0) return null; if (options.Count == 0)
return null;
var random = new Random(); var random = new Random();
var pick = options[random.Next(options.Count)]; var pick = options[random.Next(options.Count)];
return await pick(); return await pick();
@@ -177,9 +182,7 @@ public class ActivityService(
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();
var recent = now.Minus(Duration.FromDays(7)); var recent = now.Minus(Duration.FromDays(7));
var posts = await db.Posts var posts = await db.Posts.Where(p => p.PublishedAt > recent).ToListAsync();
.Where(p => p.PublishedAt > recent)
.ToListAsync();
var publisherIds = posts.Select(p => p.PublisherId).Distinct().ToList(); var publisherIds = posts.Select(p => p.PublisherId).Distinct().ToList();
var publishers = await db.Publishers.Where(p => publisherIds.Contains(p.Id)).ToListAsync(); var publishers = await db.Publishers.Where(p => publisherIds.Contains(p.Id)).ToListAsync();
@@ -188,7 +191,7 @@ public class ActivityService(
.Select(p => new .Select(p => new
{ {
Publisher = p, Publisher = p,
Rank = CalculatePopularity(posts.Where(post => post.PublisherId == p.Id).ToList()) Rank = CalculatePopularity(posts.Where(post => post.PublisherId == p.Id).ToList()),
}) })
.OrderByDescending(x => x.Rank) .OrderByDescending(x => x.Rank)
.Select(x => x.Publisher) .Select(x => x.Publisher)
@@ -196,30 +199,33 @@ public class ActivityService(
.ToList(); .ToList();
} }
private async Task<SnActivity?> GetRealmDiscoveryActivity(int count = 5) private async Task<SnTimelineEvent?> GetRealmDiscoveryActivity(int count = 5)
{ {
var realms = await ds.GetCommunityRealmAsync(null, count, 0, true); var realms = await ds.GetCommunityRealmAsync(null, count, 0, true);
return realms.Count > 0 return realms.Count > 0
? new DiscoveryActivity(realms.Select(x => new DiscoveryItem("realm", x)).ToList()).ToActivity() ? new TimelineDiscoveryEvent(
realms.Select(x => new DiscoveryItem("realm", x)).ToList()
).ToActivity()
: null; : null;
} }
private async Task<SnActivity?> GetPublisherDiscoveryActivity(int count = 5) private async Task<SnTimelineEvent?> GetPublisherDiscoveryActivity(int count = 5)
{ {
var popularPublishers = await GetPopularPublishers(count); var popularPublishers = await GetPopularPublishers(count);
return popularPublishers.Count > 0 return popularPublishers.Count > 0
? new DiscoveryActivity(popularPublishers.Select(x => new DiscoveryItem("publisher", x)).ToList()) ? new TimelineDiscoveryEvent(
.ToActivity() popularPublishers.Select(x => new DiscoveryItem("publisher", x)).ToList()
).ToActivity()
: null; : null;
} }
private async Task<SnActivity?> GetShuffledPostsActivity(int count = 5) private async Task<SnTimelineEvent?> GetShuffledPostsActivity(int count = 5)
{ {
var publicRealms = await rs.GetPublicRealms(); var publicRealms = await rs.GetPublicRealms();
var publicRealmIds = publicRealms.Select(r => r.Id).ToList(); var publicRealmIds = publicRealms.Select(r => r.Id).ToList();
var postsQuery = db.Posts var postsQuery = db
.Include(p => p.Categories) .Posts.Include(p => p.Categories)
.Include(p => p.Tags) .Include(p => p.Tags)
.Where(p => p.RepliedPostId == null) .Where(p => p.RepliedPostId == null)
.Where(p => p.RealmId == null || publicRealmIds.Contains(p.RealmId.Value)) .Where(p => p.RealmId == null || publicRealmIds.Contains(p.RealmId.Value))
@@ -231,17 +237,22 @@ public class ActivityService(
return posts.Count == 0 return posts.Count == 0
? null ? null
: new DiscoveryActivity(posts.Select(x => new DiscoveryItem("post", x)).ToList()).ToActivity(); : new TimelineDiscoveryEvent(
posts.Select(x => new DiscoveryItem("post", x)).ToList()
).ToActivity();
} }
private async Task<SnActivity?> GetArticleDiscoveryActivity(int count = 5, int feedSampleSize = 10) private async Task<SnTimelineEvent?> GetArticleDiscoveryActivity(
int count = 5,
int feedSampleSize = 10
)
{ {
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();
var today = now.InZone(DateTimeZone.Utc).Date; var today = now.InZone(DateTimeZone.Utc).Date;
var todayBegin = today.AtStartOfDayInZone(DateTimeZone.Utc).ToInstant(); var todayBegin = today.AtStartOfDayInZone(DateTimeZone.Utc).ToInstant();
var todayEnd = today.PlusDays(1).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant(); var todayEnd = today.PlusDays(1).AtStartOfDayInZone(DateTimeZone.Utc).ToInstant();
var recentFeedIds = await db.WebArticles var recentFeedIds = await db
.Where(a => a.CreatedAt >= todayBegin && a.CreatedAt < todayEnd) .WebArticles.Where(a => a.CreatedAt >= todayBegin && a.CreatedAt < todayEnd)
.GroupBy(a => a.FeedId) .GroupBy(a => a.FeedId)
.OrderByDescending(g => g.Max(a => a.PublishedAt)) .OrderByDescending(g => g.Max(a => a.PublishedAt))
.Take(feedSampleSize) .Take(feedSampleSize)
@@ -253,26 +264,31 @@ public class ActivityService(
foreach (var feedId in recentFeedIds.OrderBy(_ => random.Next())) foreach (var feedId in recentFeedIds.OrderBy(_ => random.Next()))
{ {
var article = await db.WebArticles var article = await db
.Include(a => a.Feed) .WebArticles.Include(a => a.Feed)
.Where(a => a.FeedId == feedId) .Where(a => a.FeedId == feedId)
.OrderBy(_ => EF.Functions.Random()) .OrderBy(_ => EF.Functions.Random())
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (article == null) continue; if (article == null)
continue;
recentArticles.Add(article); recentArticles.Add(article);
if (recentArticles.Count >= count) break; if (recentArticles.Count >= count)
break;
} }
return recentArticles.Count > 0 return recentArticles.Count > 0
? new DiscoveryActivity(recentArticles.Select(x => new DiscoveryItem("article", x)).ToList()).ToActivity() ? new TimelineDiscoveryEvent(
recentArticles.Select(x => new DiscoveryItem("article", x)).ToList()
).ToActivity()
: null; : null;
} }
private async Task<List<SnPost>> GetAndProcessPosts( private async Task<List<SnPost>> GetAndProcessPosts(
IQueryable<SnPost> baseQuery, IQueryable<SnPost> baseQuery,
Account? currentUser = null, Account? currentUser = null,
bool trackViews = true) bool trackViews = true
)
{ {
var posts = await baseQuery.ToListAsync(); var posts = await baseQuery.ToListAsync();
posts = await ps.LoadPostInfo(posts, currentUser, true); posts = await ps.LoadPostInfo(posts, currentUser, true);
@@ -282,7 +298,10 @@ public class ActivityService(
foreach (var post in posts) foreach (var post in posts)
{ {
post.ReactionsCount = reactionMaps.GetValueOrDefault(post.Id, new Dictionary<string, int>()); post.ReactionsCount = reactionMaps.GetValueOrDefault(
post.Id,
new Dictionary<string, int>()
);
if (trackViews && currentUser != null) if (trackViews && currentUser != null)
{ {
@@ -299,8 +318,8 @@ public class ActivityService(
List<Guid>? userRealms = null List<Guid>? userRealms = null
) )
{ {
var query = db.Posts var query = db
.Include(e => e.RepliedPost) .Posts.Include(e => e.RepliedPost)
.Include(e => e.ForwardedPost) .Include(e => e.ForwardedPost)
.Include(e => e.Categories) .Include(e => e.Categories)
.Include(e => e.Tags) .Include(e => e.Tags)
@@ -319,8 +338,7 @@ public class ActivityService(
query = query.Where(p => p.RealmId == null); // Modify in caller query = query.Where(p => p.RealmId == null); // Modify in caller
} }
else else
query = query.Where(p => query = query.Where(p => p.RealmId == null || userRealms.Contains(p.RealmId.Value));
p.RealmId == null || userRealms.Contains(p.RealmId.Value));
return query; return query;
} }
@@ -328,7 +346,8 @@ public class ActivityService(
private async Task<List<Shared.Models.SnPublisher>?> GetFilteredPublishers( private async Task<List<Shared.Models.SnPublisher>?> GetFilteredPublishers(
string? filter, string? filter,
Account currentUser, Account currentUser,
List<Guid> userFriends) List<Guid> userFriends
)
{ {
return filter?.ToLower() switch return filter?.ToLower() switch
{ {
@@ -337,14 +356,19 @@ public class ActivityService(
.SelectMany(x => x.Value) .SelectMany(x => x.Value)
.DistinctBy(x => x.Id) .DistinctBy(x => x.Id)
.ToList(), .ToList(),
_ => null _ => null,
}; };
} }
private static async Task LoadPostsRealmsAsync(List<SnPost> posts, RemoteRealmService rs) private static async Task LoadPostsRealmsAsync(List<SnPost> posts, RemoteRealmService rs)
{ {
var postRealmIds = posts.Where(p => p.RealmId != null).Select(p => p.RealmId!.Value).Distinct().ToList(); var postRealmIds = posts
if (!postRealmIds.Any()) return; .Where(p => p.RealmId != null)
.Select(p => p.RealmId!.Value)
.Distinct()
.ToList();
if (!postRealmIds.Any())
return;
var realms = await rs.GetRealmBatch(postRealmIds.Select(id => id.ToString()).ToList()); var realms = await rs.GetRealmBatch(postRealmIds.Select(id => id.ToString()).ToList());
var realmDict = realms.ToDictionary(r => r.Id, r => r); var realmDict = realms.ToDictionary(r => r.Id, r => r);