Provide real user and posts data for the thinking

This commit is contained in:
2025-10-25 17:58:58 +08:00
parent 40325c6df5
commit 93f7dfd379
17 changed files with 898 additions and 72 deletions

View File

@@ -1,8 +1,10 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json.Serialization;
using DysonNetwork.Shared.Proto;
using NodaTime;
using NpgsqlTypes;
using Google.Protobuf.WellKnownTypes;
namespace DysonNetwork.Shared.Models;
@@ -85,6 +87,107 @@ public class SnPost : ModelBase, IIdentifiedResource, IActivity
public string ResourceIdentifier => $"post:{Id}";
public Post ToProtoValue()
{
var proto = new Post
{
Id = Id.ToString(),
Title = Title ?? string.Empty,
Description = Description ?? string.Empty,
Slug = Slug ?? string.Empty,
Visibility = (Proto.PostVisibility)((int)Visibility + 1),
Type = (Proto.PostType)((int)Type + 1),
ViewsUnique = ViewsUnique,
ViewsTotal = ViewsTotal,
Upvotes = Upvotes,
Downvotes = Downvotes,
AwardedScore = (double)AwardedScore,
ReactionsCount = { ReactionsCount ?? new Dictionary<string, int>() },
RepliesCount = RepliesCount,
ReactionsMade = { ReactionsMade ?? new Dictionary<string, bool>() },
RepliedGone = RepliedGone,
ForwardedGone = ForwardedGone,
PublisherId = PublisherId.ToString(),
Publisher = Publisher.ToProto(),
CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()),
UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset())
};
if (EditedAt.HasValue)
{
proto.EditedAt = Timestamp.FromDateTimeOffset(EditedAt.Value.ToDateTimeOffset());
}
if (PublishedAt.HasValue)
{
proto.PublishedAt = Timestamp.FromDateTimeOffset(PublishedAt.Value.ToDateTimeOffset());
}
if (Content != null)
{
proto.Content = Content;
}
if (PinMode.HasValue)
{
proto.PinMode = (Proto.PostPinMode)((int)PinMode.Value);
}
if (Meta != null)
{
proto.Meta = GrpcTypeHelper.ConvertObjectToByteString(Meta);
}
if (SensitiveMarks != null)
{
proto.SensitiveMarks = GrpcTypeHelper.ConvertObjectToByteString(SensitiveMarks);
}
if (EmbedView != null)
{
proto.EmbedView = EmbedView.ToProtoValue();
}
if (RepliedPostId.HasValue)
{
proto.RepliedPostId = RepliedPostId.Value.ToString();
if (RepliedPost != null)
{
proto.RepliedPost = RepliedPost.ToProtoValue();
}
}
if (ForwardedPostId.HasValue)
{
proto.ForwardedPostId = ForwardedPostId.Value.ToString();
if (ForwardedPost != null)
{
proto.ForwardedPost = ForwardedPost.ToProtoValue();
}
}
if (RealmId.HasValue)
{
proto.RealmId = RealmId.Value.ToString();
if (Realm != null)
{
proto.Realm = Realm.ToProtoValue();
}
}
proto.Attachments.AddRange(Attachments.Select(a => a.ToProtoValue()));
proto.Awards.AddRange(Awards.Select(a => a.ToProto()));
proto.Reactions.AddRange(Reactions.Select(r => r.ToProtoValue()));
proto.Tags.AddRange(Tags.Select(t => t.ToProtoValue()));
proto.Categories.AddRange(Categories.Select(c => c.ToProtoValue()));
proto.FeaturedRecords.AddRange(FeaturedRecords.Select(f => f.ToProtoValue()));
if (DeletedAt.HasValue)
proto.DeletedAt = Timestamp.FromDateTimeOffset(DeletedAt.Value.ToDateTimeOffset());
return proto;
}
public SnActivity ToActivity()
{
return new SnActivity()
@@ -108,6 +211,30 @@ public class SnPostTag : ModelBase
[JsonIgnore] public List<SnPost> Posts { get; set; } = new List<SnPost>();
[NotMapped] public int? Usage { get; set; }
public PostTag ToProtoValue()
{
return new PostTag
{
Id = Id.ToString(),
Slug = Slug,
Name = Name ?? string.Empty,
CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()),
UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset())
};
}
public static SnPostTag FromProtoValue(PostTag proto)
{
return new SnPostTag
{
Id = Guid.Parse(proto.Id),
Slug = proto.Slug,
Name = proto.Name != string.Empty ? proto.Name : null,
CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()),
UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset())
};
}
}
public class SnPostCategory : ModelBase
@@ -118,6 +245,30 @@ public class SnPostCategory : ModelBase
[JsonIgnore] public List<SnPost> Posts { get; set; } = new List<SnPost>();
[NotMapped] public int? Usage { get; set; }
public PostCategory ToProtoValue()
{
return new PostCategory
{
Id = Id.ToString(),
Slug = Slug,
Name = Name ?? string.Empty,
CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()),
UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset())
};
}
public static SnPostCategory FromProtoValue(PostCategory proto)
{
return new SnPostCategory
{
Id = Guid.Parse(proto.Id),
Slug = proto.Slug,
Name = proto.Name != string.Empty ? proto.Name : null,
CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()),
UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset())
};
}
}
public class SnPostCategorySubscription : ModelBase
@@ -150,6 +301,23 @@ public class SnPostFeaturedRecord : ModelBase
[JsonIgnore] public SnPost Post { get; set; } = null!;
public Instant? FeaturedAt { get; set; }
public int SocialCredits { get; set; }
public PostFeaturedRecord ToProtoValue()
{
var proto = new PostFeaturedRecord
{
Id = Id.ToString(),
PostId = PostId.ToString(),
SocialCredits = SocialCredits,
CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()),
UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset())
};
if (FeaturedAt.HasValue)
{
proto.FeaturedAt = Timestamp.FromDateTimeOffset(FeaturedAt.Value.ToDateTimeOffset());
}
return proto;
}
}
public enum PostReactionAttitude
@@ -169,6 +337,40 @@ public class SnPostReaction : ModelBase
[JsonIgnore] public SnPost Post { get; set; } = null!;
public Guid AccountId { get; set; }
[NotMapped] public SnAccount? Account { get; set; }
public PostReaction ToProtoValue()
{
var proto = new PostReaction
{
Id = Id.ToString(),
Symbol = Symbol,
Attitude = (Proto.PostReactionAttitude)((int)Attitude + 1),
PostId = PostId.ToString(),
AccountId = AccountId.ToString(),
CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()),
UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset())
};
if (Account != null)
{
proto.Account = Account.ToProtoValue();
}
return proto;
}
public static SnPostReaction FromProtoValue(Proto.PostReaction proto)
{
return new SnPostReaction
{
Id = Guid.Parse(proto.Id),
Symbol = proto.Symbol,
Attitude = (PostReactionAttitude)((int)proto.Attitude - 1),
PostId = Guid.Parse(proto.PostId),
AccountId = Guid.Parse(proto.AccountId),
Account = proto.Account != null ? SnAccount.FromProtoValue(proto.Account) : null,
CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()),
UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset())
};
}
}
public class SnPostAward : ModelBase
@@ -181,6 +383,25 @@ public class SnPostAward : ModelBase
public Guid PostId { get; set; }
[JsonIgnore] public SnPost Post { get; set; } = null!;
public Guid AccountId { get; set; }
public PostAward ToProto()
{
var proto = new PostAward
{
Id = Id.ToString(),
Amount = (double)Amount,
Attitude = (Proto.PostReactionAttitude)((int)Attitude + 1),
PostId = PostId.ToString(),
AccountId = AccountId.ToString(),
CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()),
UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset())
};
if (Message != null)
{
proto.Message = Message;
}
return proto;
}
}
/// <summary>
@@ -193,6 +414,30 @@ public class PostEmbedView
public string Uri { get; set; } = null!;
public double? AspectRatio { get; set; }
public PostEmbedViewRenderer Renderer { get; set; } = PostEmbedViewRenderer.WebView;
public Proto.PostEmbedView ToProtoValue()
{
var proto = new Proto.PostEmbedView
{
Uri = Uri,
Renderer = (Proto.PostEmbedViewRenderer)(int)Renderer
};
if (AspectRatio.HasValue)
{
proto.AspectRatio = AspectRatio.Value;
}
return proto;
}
public static PostEmbedView FromProtoValue(Proto.PostEmbedView proto)
{
return new PostEmbedView
{
Uri = proto.Uri,
AspectRatio = proto.HasAspectRatio ? proto.AspectRatio : null,
Renderer = (PostEmbedViewRenderer)((int)proto.Renderer - 1)
};
}
}
public enum PostEmbedViewRenderer

View File

@@ -0,0 +1,283 @@
syntax = "proto3";
package proto;
option csharp_namespace = "DysonNetwork.Shared.Proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto";
import "google/protobuf/struct.proto";
import "file.proto";
import "realm.proto";
import "publisher.proto";
// Enums
enum PostType {
POST_TYPE_UNSPECIFIED = 0;
MOMENT = 1;
ARTICLE = 2;
}
enum PostVisibility {
VISIBILITY_UNSPECIFIED = 0;
PUBLIC = 1;
FRIENDS = 2;
UNLISTED = 3;
PRIVATE = 4;
}
enum PostPinMode {
PIN_MODE_UNSPECIFIED = 0;
PUBLISHER_PAGE = 1;
REALM_PAGE = 2;
REPLY_PAGE = 3;
}
enum ContentSensitiveMark {
SENSITIVE_MARK_UNSPECIFIED = 0;
LANGUAGE = 1;
SEXUAL_CONTENT = 2;
VIOLENCE = 3;
PROFANITY = 4;
HATE_SPEECH = 5;
RACISM = 6;
ADULT_CONTENT = 7;
DRUG_ABUSE = 8;
ALCOHOL_ABUSE = 9;
GAMBLING = 10;
SELF_HARM = 11;
CHILD_ABUSE = 12;
OTHER = 13;
}
enum PostReactionAttitude {
ATTITUDE_UNSPECIFIED = 0;
POST_ATTITUDE_POSITIVE = 1;
POST_ATTITUDE_NEUTRAL = 2;
POST_ATTITUDE_NEGATIVE = 3;
}
enum PostEmbedViewRenderer {
RENDERER_UNSPECIFIED = 0;
WEBVIEW = 1;
}
// Messages
message PostEmbedView {
string uri = 1;
optional double aspect_ratio = 2;
PostEmbedViewRenderer renderer = 3;
}
message Post {
string id = 1;
string title = 2;
string description = 3;
string slug = 4;
optional google.protobuf.Timestamp edited_at = 5;
optional google.protobuf.Timestamp published_at = 6;
PostVisibility visibility = 7;
optional string content = 8;
PostType type = 9;
optional PostPinMode pin_mode = 10;
optional bytes meta = 11; // Dictionary<string, object>
optional bytes sensitive_marks = 12; // List<ContentSensitiveMark>
optional PostEmbedView embed_view = 13;
int32 views_unique = 14;
int32 views_total = 15;
int32 upvotes = 16;
int32 downvotes = 17;
double awarded_score = 18;
// Not mapped fields: handled client-side
map<string, int32> reactions_count = 19; // Dictionary<string, int>
int32 replies_count = 20;
map<string, bool> reactions_made = 21; // Dictionary<string, bool>
bool replied_gone = 22;
bool forwarded_gone = 23;
optional string replied_post_id = 24;
optional Post replied_post = 25; // full if populated
optional string forwarded_post_id = 26;
optional Post forwarded_post = 27; // full if populated
optional string realm_id = 28;
optional Realm realm = 29; // full if populated
repeated CloudFile attachments = 30; // List<SnCloudFileReferenceObject>
string publisher_id = 31;
Publisher publisher = 32;
repeated PostAward awards = 33;
repeated PostReaction reactions = 34;
repeated PostTag tags = 35;
repeated PostCategory categories = 36;
repeated PostFeaturedRecord featured_records = 37;
// Added for ToActivity
google.protobuf.Timestamp created_at = 38;
google.protobuf.Timestamp updated_at = 39;
optional google.protobuf.Timestamp deleted_at = 40;
}
message PostTag {
string id = 1;
string slug = 2;
string name = 3;
google.protobuf.Timestamp created_at = 4;
google.protobuf.Timestamp updated_at = 5;
}
message PostCategory {
string id = 1;
string slug = 2;
string name = 3;
google.protobuf.Timestamp created_at = 4;
google.protobuf.Timestamp updated_at = 5;
}
message PostCategorySubscription {
string id = 1;
string account_id = 2;
optional string category_id = 3;
optional PostCategory category = 4;
optional string tag_id = 5;
optional PostTag tag = 6;
google.protobuf.Timestamp created_at = 7;
google.protobuf.Timestamp updated_at = 8;
}
message PostCollection {
string id = 1;
string slug = 2;
optional google.protobuf.StringValue name = 3;
optional google.protobuf.StringValue description = 4;
Publisher publisher = 5;
optional string publisher_id = 6; // for cases where full publisher not needed
repeated Post posts = 7;
google.protobuf.Timestamp created_at = 8;
google.protobuf.Timestamp updated_at = 9;
}
message PostFeaturedRecord {
string id = 1;
string post_id = 2;
optional google.protobuf.Timestamp featured_at = 3;
int32 social_credits = 4;
google.protobuf.Timestamp created_at = 5;
google.protobuf.Timestamp updated_at = 6;
}
message PostReaction {
string id = 1;
string symbol = 2;
PostReactionAttitude attitude = 3;
string post_id = 4;
string account_id = 5;
optional Account account = 6; // optional full account
google.protobuf.Timestamp created_at = 7;
google.protobuf.Timestamp updated_at = 8;
}
message PostAward {
string id = 1;
double amount = 2;
PostReactionAttitude attitude = 3;
optional google.protobuf.StringValue message = 4;
string post_id = 5;
string account_id = 6;
google.protobuf.Timestamp created_at = 7;
google.protobuf.Timestamp updated_at = 8;
}
// ====================================
// Request/Response Messages
// ====================================
message GetPostRequest {
string id = 1;
}
message GetPostBatchRequest {
repeated string ids = 1;
}
message GetPostBatchResponse {
repeated Post posts = 1;
}
message SearchPostsRequest {
string query = 1;
string publisher_id = 2;
string realm_id = 3;
int32 page_size = 4;
string page_token = 5;
string order_by = 6;
}
message SearchPostsResponse {
repeated Post posts = 1;
string next_page_token = 2;
int32 total_size = 3;
}
message ListPostsRequest {
string publisher_id = 1;
string realm_id = 2;
int32 page_size = 3;
string page_token = 4;
string order_by = 5;
repeated string categories = 6;
repeated string tags = 7;
string query = 8;
repeated PostType types = 9;
optional google.protobuf.Timestamp after = 10; // Filter posts created after this timestamp
optional google.protobuf.Timestamp before = 11; // Filter posts created before this timestamp
bool include_replies = 12; // Include reply posts
optional PostPinMode pinned = 13; // Filter by pinned mode (if present, null means not pinned)
bool only_media = 14; // Only return posts with attachments
bool shuffle = 15; // Random order
}
message ListPostsResponse {
repeated Post posts = 1;
string next_page_token = 2;
int32 total_size = 3;
}
// ====================================
// Service Definitions
// ====================================
service PostService {
// Get a single post by id
rpc GetPost(GetPostRequest) returns (Post);
// Get multiple posts by ids
rpc GetPostBatch(GetPostBatchRequest) returns (GetPostBatchResponse);
// Search posts
rpc SearchPosts(SearchPostsRequest) returns (SearchPostsResponse);
// List posts with filters
rpc ListPosts(ListPostsRequest) returns (ListPostsResponse);
}
import 'account.proto';

View File

@@ -59,7 +59,7 @@ public static class ServiceInjectionHelper
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
services
.AddGrpcClient<RealmService.RealmServiceClient>(o => o.Address = new Uri("https://_grpc.pass"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
@@ -69,7 +69,7 @@ public static class ServiceInjectionHelper
return services;
}
public static IServiceCollection AddDriveService(this IServiceCollection services)
{
services.AddGrpcClient<FileService.FileServiceClient>(o => o.Address = new Uri("https://_grpc.drive"))
@@ -86,8 +86,14 @@ public static class ServiceInjectionHelper
return services;
}
public static IServiceCollection AddPublisherService(this IServiceCollection services)
public static IServiceCollection AddSphereService(this IServiceCollection services)
{
services
.AddGrpcClient<PostService.PostServiceClient>(o => o.Address = new Uri("https://_grpc.sphere"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
);
services
.AddGrpcClient<PublisherService.PublisherServiceClient>(o => o.Address = new Uri("https://_grpc.sphere"))
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
@@ -107,4 +113,4 @@ public static class ServiceInjectionHelper
return services;
}
}
}