🚚 Rename activity in sphere to timeline
In order to leave the activity keyword for pass service user activity
This commit is contained in:
@@ -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"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
|
);
|
||||||
|
post.Awards.AddRange(
|
||||||
|
proto.Awards.Select(a => new SnPostAward
|
||||||
{
|
{
|
||||||
Id = Guid.Parse(a.Id), PostId = Guid.Parse(a.PostId), AccountId = Guid.Parse(a.AccountId),
|
Id = Guid.Parse(a.Id),
|
||||||
Amount = (decimal)a.Amount, Attitude = (PostReactionAttitude)((int)a.Attitude - 1),
|
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,
|
Message = string.IsNullOrEmpty(a.Message) ? null : a.Message,
|
||||||
CreatedAt = Instant.FromDateTimeOffset(a.CreatedAt.ToDateTimeOffset()),
|
CreatedAt = Instant.FromDateTimeOffset(a.CreatedAt.ToDateTimeOffset()),
|
||||||
UpdatedAt = Instant.FromDateTimeOffset(a.UpdatedAt.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,
|
||||||
}
|
}
|
||||||
|
|||||||
41
DysonNetwork.Shared/Models/Timeline.cs
Normal file
41
DysonNetwork.Shared/Models/Timeline.cs
Normal 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",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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()
|
||||||
|
.AddJsonOptions(options =>
|
||||||
{
|
{
|
||||||
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals;
|
options.JsonSerializerOptions.NumberHandling =
|
||||||
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
|
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) =>
|
options.DataAnnotationLocalizerProvider = (type, factory) =>
|
||||||
factory.Create(typeof(SharedResource));
|
factory.Create(typeof(SharedResource));
|
||||||
}).ConfigureApplicationPartManager(opts =>
|
})
|
||||||
|
.ConfigureApplicationPartManager(opts =>
|
||||||
{
|
{
|
||||||
var mockingPart = opts.ApplicationParts.FirstOrDefault(a => a.Name == "DysonNetwork.Pass");
|
var mockingPart = opts.ApplicationParts.FirstOrDefault(a =>
|
||||||
|
a.Name == "DysonNetwork.Pass"
|
||||||
|
);
|
||||||
if (mockingPart != null)
|
if (mockingPart != null)
|
||||||
opts.ApplicationParts.Remove(mockingPart);
|
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;
|
||||||
@@ -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>();
|
||||||
|
|||||||
@@ -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)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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",
|
||||||
@@ -23,3 +23,4 @@ public class DiscoveryActivity(List<DiscoveryItem> items) : IActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
public record DiscoveryItem(string Type, object Data);
|
public record DiscoveryItem(string Type, object Data);
|
||||||
|
|
||||||
@@ -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,7 +20,8 @@ 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
|
||||||
@@ -31,12 +32,13 @@ public class ActivityService(
|
|||||||
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);
|
||||||
Reference in New Issue
Block a user