diff --git a/DysonNetwork.Develop/Program.cs b/DysonNetwork.Develop/Program.cs index f735267..d9a1104 100644 --- a/DysonNetwork.Develop/Program.cs +++ b/DysonNetwork.Develop/Program.cs @@ -14,7 +14,7 @@ builder.ConfigureAppKestrel(builder.Configuration); builder.Services.AddAppServices(builder.Configuration); builder.Services.AddAppAuthentication(); builder.Services.AddDysonAuth(); -builder.Services.AddPublisherService(); +builder.Services.AddSphereService(); builder.Services.AddAccountService(); builder.Services.AddDriveService(); diff --git a/DysonNetwork.Drive/Storage/FileController.cs b/DysonNetwork.Drive/Storage/FileController.cs index 51b6c84..ac43f4a 100644 --- a/DysonNetwork.Drive/Storage/FileController.cs +++ b/DysonNetwork.Drive/Storage/FileController.cs @@ -187,7 +187,7 @@ public class FileController( public class MarkFileRequest { - public List? SensitiveMarks { get; set; } + public List? SensitiveMarks { get; set; } } [Authorize] @@ -298,7 +298,7 @@ public class FileController( public string? Description { get; set; } public Dictionary? UserMeta { get; set; } public Dictionary? FileMeta { get; set; } - public List? SensitiveMarks { get; set; } + public List? SensitiveMarks { get; set; } public Guid PoolId { get; set; } } diff --git a/DysonNetwork.Gateway/Program.cs b/DysonNetwork.Gateway/Program.cs index 8350f0f..5087db8 100644 --- a/DysonNetwork.Gateway/Program.cs +++ b/DysonNetwork.Gateway/Program.cs @@ -122,9 +122,9 @@ var routes = specialRoutes.Concat(apiRoutes).Concat(swaggerRoutes).ToArray(); var clusters = serviceNames.Select(serviceName => new ClusterConfig { ClusterId = serviceName, - HealthCheck = new() + HealthCheck = new HealthCheckConfig { - Active = new() + Active = new ActiveHealthCheckConfig { Enabled = true, Interval = TimeSpan.FromSeconds(10), diff --git a/DysonNetwork.Insight/Program.cs b/DysonNetwork.Insight/Program.cs index 279477a..083edcc 100644 --- a/DysonNetwork.Insight/Program.cs +++ b/DysonNetwork.Insight/Program.cs @@ -1,6 +1,7 @@ using DysonNetwork.Insight; using DysonNetwork.Insight.Startup; using DysonNetwork.Shared.Http; +using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); @@ -10,10 +11,13 @@ builder.AddServiceDefaults(); builder.ConfigureAppKestrel(builder.Configuration); builder.Services.AddControllers(); -builder.Services.AddAppServices(builder.Configuration); +builder.Services.AddAppServices(); builder.Services.AddAppAuthentication(); builder.Services.AddAppFlushHandlers(); builder.Services.AddAppBusinessServices(); + +builder.Services.AddAccountService(); +builder.Services.AddSphereService(); builder.Services.AddThinkingServices(builder.Configuration); builder.AddSwaggerManifest( diff --git a/DysonNetwork.Insight/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Insight/Startup/ServiceCollectionExtensions.cs index f41c761..8d3af57 100644 --- a/DysonNetwork.Insight/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Insight/Startup/ServiceCollectionExtensions.cs @@ -10,7 +10,7 @@ namespace DysonNetwork.Insight.Startup; public static class ServiceCollectionExtensions { - public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration) + public static IServiceCollection AddAppServices(this IServiceCollection services) { services.AddDbContext(); services.AddSingleton(SystemClock.Instance); @@ -65,8 +65,7 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddThinkingServices(this IServiceCollection services, IConfiguration configuration) { - var thinkingProvider = new ThinkingProvider(configuration); - services.AddSingleton(thinkingProvider); + services.AddSingleton(); return services; } diff --git a/DysonNetwork.Insight/Thinking/ThinkingProvider.cs b/DysonNetwork.Insight/Thinking/ThinkingProvider.cs index 4831ff8..4239b12 100644 --- a/DysonNetwork.Insight/Thinking/ThinkingProvider.cs +++ b/DysonNetwork.Insight/Thinking/ThinkingProvider.cs @@ -1,14 +1,34 @@ +using DysonNetwork.Shared.Proto; using Microsoft.SemanticKernel; +using Microsoft.Extensions.Configuration; +using System.Text.Json; namespace DysonNetwork.Insight.Thinking; public class ThinkingProvider { - public readonly Kernel Kernel; - public readonly string? ModelProviderType; - public readonly string? ModelDefault; + private readonly Kernel _kernel; + private readonly PostService.PostServiceClient _postClient; + private readonly AccountService.AccountServiceClient _accountClient; - public ThinkingProvider(IConfiguration configuration) + public Kernel Kernel => _kernel; + public string? ModelProviderType { get; private set; } + public string? ModelDefault { get; private set; } + + public ThinkingProvider( + IConfiguration configuration, + PostService.PostServiceClient postClient, + AccountService.AccountServiceClient accountClient + ) + { + _postClient = postClient; + _accountClient = accountClient; + + _kernel = InitializeThinkingProvider(configuration); + InitializeHelperFunctions(); + } + + private Kernel InitializeThinkingProvider(IConfiguration configuration) { var cfg = configuration.GetSection("Thinking"); ModelProviderType = cfg.GetValue("Provider")?.ToLower(); @@ -26,23 +46,37 @@ public class ThinkingProvider throw new IndexOutOfRangeException("Unknown thinking provider: " + ModelProviderType); } - Kernel = builder.Build(); + return builder.Build(); + } + private void InitializeHelperFunctions() + { // Add Solar Network tools plugin - Kernel.ImportPluginFromFunctions("helper_functions", [ + _kernel.ImportPluginFromFunctions("helper_functions", [ KernelFunctionFactory.CreateFromMethod(async (string userId) => { - // MOCK: simulate fetching user profile - await Task.Delay(100); - return $"{{\"userId\":\"{userId}\",\"name\":\"MockUser\",\"bio\":\"Loves music and tech.\"}}"; + var request = new GetAccountRequest { Id = userId }; + var response = await _accountClient.GetAccountAsync(request); + return JsonSerializer.Serialize(response, GrpcTypeHelper.SerializerOptions); }, "get_user_profile", "Get a user profile from the Solar Network."), - KernelFunctionFactory.CreateFromMethod(async (string topic) => + KernelFunctionFactory.CreateFromMethod(async (string postId) => { - // MOCK: simulate fetching recent posts - await Task.Delay(200); - return - $"[{{\"postId\":\"p1\",\"topic\":\"{topic}\",\"content\":\"Mock post content 1.\"}}, {{\"postId\":\"p2\",\"topic\":\"{topic}\",\"content\":\"Mock post content 2.\"}}]"; + var request = new GetPostRequest { Id = postId }; + var response = await _postClient.GetPostAsync(request); + return JsonSerializer.Serialize(response, GrpcTypeHelper.SerializerOptions); + }, "get_post", "Get a single post by ID from the Solar Network."), + KernelFunctionFactory.CreateFromMethod(async (string query) => + { + var request = new SearchPostsRequest { Query = query, PageSize = 10 }; + var response = await _postClient.SearchPostsAsync(request); + return JsonSerializer.Serialize(response.Posts, GrpcTypeHelper.SerializerOptions); + }, "search_posts", "Search posts by query from the Solar Network."), + KernelFunctionFactory.CreateFromMethod(async () => + { + var request = new ListPostsRequest { PageSize = 10 }; + var response = await _postClient.ListPostsAsync(request); + return JsonSerializer.Serialize(response.Posts, GrpcTypeHelper.SerializerOptions); }, "get_recent_posts", "Get recent posts from the Solar Network.") ]); } -} \ No newline at end of file +} diff --git a/DysonNetwork.Shared/Models/Post.cs b/DysonNetwork.Shared/Models/Post.cs index f1014db..8c04ec6 100644 --- a/DysonNetwork.Shared/Models/Post.cs +++ b/DysonNetwork.Shared/Models/Post.cs @@ -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() }, + RepliesCount = RepliesCount, + ReactionsMade = { ReactionsMade ?? new Dictionary() }, + RepliedGone = RepliedGone, + ForwardedGone = ForwardedGone, + PublisherId = PublisherId.ToString(), + Publisher = Publisher.ToProto(), + CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), + UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) + }; + + if (EditedAt.HasValue) + { + proto.EditedAt = Timestamp.FromDateTimeOffset(EditedAt.Value.ToDateTimeOffset()); + } + + if (PublishedAt.HasValue) + { + proto.PublishedAt = Timestamp.FromDateTimeOffset(PublishedAt.Value.ToDateTimeOffset()); + } + + if (Content != null) + { + proto.Content = Content; + } + + if (PinMode.HasValue) + { + proto.PinMode = (Proto.PostPinMode)((int)PinMode.Value); + } + + if (Meta != null) + { + proto.Meta = GrpcTypeHelper.ConvertObjectToByteString(Meta); + } + + if (SensitiveMarks != null) + { + proto.SensitiveMarks = GrpcTypeHelper.ConvertObjectToByteString(SensitiveMarks); + } + + if (EmbedView != null) + { + proto.EmbedView = EmbedView.ToProtoValue(); + } + + if (RepliedPostId.HasValue) + { + proto.RepliedPostId = RepliedPostId.Value.ToString(); + if (RepliedPost != null) + { + proto.RepliedPost = RepliedPost.ToProtoValue(); + } + } + + if (ForwardedPostId.HasValue) + { + proto.ForwardedPostId = ForwardedPostId.Value.ToString(); + if (ForwardedPost != null) + { + proto.ForwardedPost = ForwardedPost.ToProtoValue(); + } + } + + if (RealmId.HasValue) + { + proto.RealmId = RealmId.Value.ToString(); + if (Realm != null) + { + proto.Realm = Realm.ToProtoValue(); + } + } + + proto.Attachments.AddRange(Attachments.Select(a => a.ToProtoValue())); + proto.Awards.AddRange(Awards.Select(a => a.ToProto())); + proto.Reactions.AddRange(Reactions.Select(r => r.ToProtoValue())); + proto.Tags.AddRange(Tags.Select(t => t.ToProtoValue())); + proto.Categories.AddRange(Categories.Select(c => c.ToProtoValue())); + proto.FeaturedRecords.AddRange(FeaturedRecords.Select(f => f.ToProtoValue())); + + if (DeletedAt.HasValue) + proto.DeletedAt = Timestamp.FromDateTimeOffset(DeletedAt.Value.ToDateTimeOffset()); + + return proto; + } + public SnActivity ToActivity() { return new SnActivity() @@ -108,6 +211,30 @@ public class SnPostTag : ModelBase [JsonIgnore] public List Posts { get; set; } = new List(); [NotMapped] public int? Usage { get; set; } + + public PostTag ToProtoValue() + { + return new PostTag + { + Id = Id.ToString(), + Slug = Slug, + Name = Name ?? string.Empty, + CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), + UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) + }; + } + + public static SnPostTag FromProtoValue(PostTag proto) + { + return new SnPostTag + { + Id = Guid.Parse(proto.Id), + Slug = proto.Slug, + Name = proto.Name != string.Empty ? proto.Name : null, + CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()), + UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()) + }; + } } public class SnPostCategory : ModelBase @@ -118,6 +245,30 @@ public class SnPostCategory : ModelBase [JsonIgnore] public List Posts { get; set; } = new List(); [NotMapped] public int? Usage { get; set; } + + public PostCategory ToProtoValue() + { + return new PostCategory + { + Id = Id.ToString(), + Slug = Slug, + Name = Name ?? string.Empty, + CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()), + UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset()) + }; + } + + public static SnPostCategory FromProtoValue(PostCategory proto) + { + return new SnPostCategory + { + Id = Guid.Parse(proto.Id), + Slug = proto.Slug, + Name = proto.Name != string.Empty ? proto.Name : null, + CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()), + UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset()) + }; + } } public class SnPostCategorySubscription : ModelBase @@ -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; + } } /// @@ -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 diff --git a/DysonNetwork.Shared/Proto/post.proto b/DysonNetwork.Shared/Proto/post.proto new file mode 100644 index 0000000..dec34f7 --- /dev/null +++ b/DysonNetwork.Shared/Proto/post.proto @@ -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 + optional bytes sensitive_marks = 12; // List + 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 reactions_count = 19; // Dictionary + int32 replies_count = 20; + map reactions_made = 21; // Dictionary + + 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 + + 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'; diff --git a/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs b/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs index 6b13a8c..0ac7628 100644 --- a/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs +++ b/DysonNetwork.Shared/Registry/ServiceInjectionHelper.cs @@ -59,7 +59,7 @@ public static class ServiceInjectionHelper .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } ); - + services .AddGrpcClient(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(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(o => o.Address = new Uri("https://_grpc.sphere")) + .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() + { ServerCertificateCustomValidationCallback = (_, _, _, _) => true } + ); + services .AddGrpcClient(o => o.Address = new Uri("https://_grpc.sphere")) .ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler() @@ -107,4 +113,4 @@ public static class ServiceInjectionHelper return services; } -} +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Activity/ActivityService.cs b/DysonNetwork.Sphere/Activity/ActivityService.cs index 08ab828..be922e9 100644 --- a/DysonNetwork.Sphere/Activity/ActivityService.cs +++ b/DysonNetwork.Sphere/Activity/ActivityService.cs @@ -12,7 +12,7 @@ namespace DysonNetwork.Sphere.Activity; public class ActivityService( AppDatabase db, Publisher.PublisherService pub, - PostService ps, + Post.PostService ps, RemoteRealmService rs, DiscoveryService ds, AccountService.AccountServiceClient accounts diff --git a/DysonNetwork.Sphere/Post/PostController.cs b/DysonNetwork.Sphere/Post/PostController.cs index 01e916e..7c9bc4c 100644 --- a/DysonNetwork.Sphere/Post/PostController.cs +++ b/DysonNetwork.Sphere/Post/PostController.cs @@ -125,7 +125,7 @@ public class PostController( if (realm != null) query = query.Where(p => p.RealmId == realm.Id); if (type != null) - query = query.Where(p => p.Type == (PostType)type); + query = query.Where(p => p.Type == (Shared.Models.PostType)type); if (categories is { Count: > 0 }) query = query.Where(p => p.Categories.Any(c => categories.Contains(c.Slug))); if (tags is { Count: > 0 }) @@ -139,10 +139,10 @@ public class PostController( switch (pinned) { case true when realm != null: - query = query.Where(p => p.PinMode == PostPinMode.RealmPage); + query = query.Where(p => p.PinMode == Shared.Models.PostPinMode.RealmPage); break; case true when publisher != null: - query = query.Where(p => p.PinMode == PostPinMode.PublisherPage); + query = query.Where(p => p.PinMode == Shared.Models.PostPinMode.PublisherPage); break; case true: return BadRequest( @@ -360,7 +360,7 @@ public class PostController( var now = SystemClock.Instance.GetCurrentInstant(); var posts = await db.Posts - .Where(e => e.RepliedPostId == id && e.PinMode == PostPinMode.ReplyPage) + .Where(e => e.RepliedPostId == id && e.PinMode == Shared.Models.PostPinMode.ReplyPage) .OrderByDescending(p => p.CreatedAt) .FilterWithVisibility(currentUser, userFriends, userPublishers) .ToListAsync(); @@ -425,9 +425,9 @@ public class PostController( [MaxLength(4096)] public string? Description { get; set; } [MaxLength(1024)] public string? Slug { get; set; } public string? Content { get; set; } - public PostVisibility? Visibility { get; set; } = PostVisibility.Public; - public PostType? Type { get; set; } - public PostEmbedView? EmbedView { get; set; } + public Shared.Models.PostVisibility? Visibility { get; set; } = Shared.Models.PostVisibility.Public; + public Shared.Models.PostType? Type { get; set; } + public Shared.Models.PostEmbedView? EmbedView { get; set; } [MaxLength(16)] public List? Tags { get; set; } [MaxLength(8)] public List? Categories { get; set; } [MaxLength(32)] public List? Attachments { get; set; } @@ -477,9 +477,9 @@ public class PostController( Description = request.Description, Slug = request.Slug, Content = request.Content, - Visibility = request.Visibility ?? PostVisibility.Public, + Visibility = request.Visibility ?? Shared.Models.PostVisibility.Public, PublishedAt = request.PublishedAt, - Type = request.Type ?? PostType.Moment, + Type = request.Type ?? Shared.Models.PostType.Moment, Meta = request.Meta, EmbedView = request.EmbedView, Publisher = publisher, @@ -565,7 +565,7 @@ public class PostController( public class PostReactionRequest { [MaxLength(256)] public string Symbol { get; set; } = null!; - public PostReactionAttitude Attitude { get; set; } + public Shared.Models.PostReactionAttitude Attitude { get; set; } } public static readonly List ReactionsAllowedDefault = @@ -638,7 +638,7 @@ public class PostController( public class PostAwardRequest { public decimal Amount { get; set; } - public PostReactionAttitude Attitude { get; set; } + public Shared.Models.PostReactionAttitude Attitude { get; set; } [MaxLength(4096)] public string? Message { get; set; } } @@ -671,7 +671,7 @@ public class PostController( public async Task> AwardPost(Guid id, [FromBody] PostAwardRequest request) { if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized(); - if (request.Attitude == PostReactionAttitude.Neutral) + if (request.Attitude == Shared.Models.PostReactionAttitude.Neutral) return BadRequest("You cannot create a neutral post award"); var friendsResponse = @@ -714,7 +714,7 @@ public class PostController( public class PostPinRequest { - [Required] public PostPinMode Mode { get; set; } + [Required] public Shared.Models.PostPinMode Mode { get; set; } } [HttpPost("{id:guid}/pin")] @@ -734,7 +734,7 @@ public class PostController( if (!await pub.IsMemberWithRole(post.PublisherId, accountId, PublisherMemberRole.Editor)) return StatusCode(403, "You are not an editor of this publisher"); - if (request.Mode == PostPinMode.RealmPage && post.RealmId != null) + if (request.Mode == Shared.Models.PostPinMode.RealmPage && post.RealmId != null) { if (!await rs.IsMemberWithRole(post.RealmId.Value, accountId, new List { RealmMemberRole.Moderator })) return StatusCode(403, "You are not a moderator of this realm"); @@ -782,7 +782,7 @@ public class PostController( if (!await pub.IsMemberWithRole(post.PublisherId, accountId, PublisherMemberRole.Editor)) return StatusCode(403, "You are not an editor of this publisher"); - if (post is { PinMode: PostPinMode.RealmPage, RealmId: not null }) + if (post is { PinMode: Shared.Models.PostPinMode.RealmPage, RealmId: not null }) { if (!await rs.IsMemberWithRole(post.RealmId.Value, accountId, new List { RealmMemberRole.Moderator })) return StatusCode(403, "You are not a moderator of this realm"); diff --git a/DysonNetwork.Sphere/Post/PostService.cs b/DysonNetwork.Sphere/Post/PostService.cs index b31868b..31c6519 100644 --- a/DysonNetwork.Sphere/Post/PostService.cs +++ b/DysonNetwork.Sphere/Post/PostService.cs @@ -290,7 +290,7 @@ public partial class PostService( public async Task PreviewPostLinkAsync(SnPost item) { - if (item.Type != PostType.Moment || string.IsNullOrEmpty(item.Content)) return item; + if (item.Type != Shared.Models.PostType.Moment || string.IsNullOrEmpty(item.Content)) return item; // Find all URLs in the content var matches = GetLinkRegex().Matches(item.Content); @@ -420,12 +420,12 @@ public partial class PostService( } } - public async Task PinPostAsync(SnPost post, Account currentUser, PostPinMode pinMode) + public async Task PinPostAsync(SnPost post, Account currentUser, Shared.Models.PostPinMode pinMode) { var accountId = Guid.Parse(currentUser.Id); if (post.RepliedPostId != null) { - if (pinMode != PostPinMode.ReplyPage) + if (pinMode != Shared.Models.PostPinMode.ReplyPage) throw new InvalidOperationException("Replies can only be pinned in the reply page."); if (post.RepliedPost == null) throw new ArgumentNullException(nameof(post.RepliedPost)); @@ -516,11 +516,11 @@ public partial class PostService( switch (reaction.Attitude) { - case PostReactionAttitude.Positive: + case Shared.Models.PostReactionAttitude.Positive: if (isRemoving) post.Upvotes--; else post.Upvotes++; break; - case PostReactionAttitude.Negative: + case Shared.Models.PostReactionAttitude.Negative: if (isRemoving) post.Downvotes--; else post.Downvotes++; break; @@ -771,7 +771,7 @@ public partial class PostService( if (currentUser is null) { // Anonymous user can only view public posts that are published - return post.PublishedAt != null && now >= post.PublishedAt && post.Visibility == PostVisibility.Public; + return post.PublishedAt != null && now >= post.PublishedAt && post.Visibility == Shared.Models.PostVisibility.Public; } // Check publication status - either published or user is member @@ -781,10 +781,10 @@ public partial class PostService( return false; // Check visibility - if (post.Visibility == PostVisibility.Private && !isMember) + if (post.Visibility == Shared.Models.PostVisibility.Private && !isMember) return false; - if (post.Visibility == PostVisibility.Friends && + if (post.Visibility == Shared.Models.PostVisibility.Friends && !(post.Publisher.AccountId.HasValue && userFriends.Contains(post.Publisher.AccountId.Value) || isMember)) return false; @@ -843,7 +843,7 @@ public partial class PostService( var periodEnd = today.InUtc().Date.AtStartOfDayInZone(DateTimeZone.Utc).ToInstant(); var postsInPeriod = await db.Posts - .Where(e => e.Visibility == PostVisibility.Public) + .Where(e => e.Visibility == Shared.Models.PostVisibility.Public) .Where(e => e.CreatedAt >= periodStart && e.CreatedAt < periodEnd) .Select(e => e.Id) .ToListAsync(); @@ -854,7 +854,7 @@ public partial class PostService( .Select(e => new { PostId = e.Key, - Score = e.Sum(r => r.Attitude == PostReactionAttitude.Positive ? 1 : -1) + Score = e.Sum(r => r.Attitude == Shared.Models.PostReactionAttitude.Positive ? 1 : -1) }) .ToDictionaryAsync(e => e.PostId, e => e.Score); @@ -928,7 +928,7 @@ public partial class PostService( Guid postId, Guid accountId, decimal amount, - PostReactionAttitude attitude, + Shared.Models.PostReactionAttitude attitude, string? message ) { @@ -947,7 +947,7 @@ public partial class PostService( db.PostAwards.Add(award); await db.SaveChangesAsync(); - var delta = award.Attitude == PostReactionAttitude.Positive ? amount : -amount; + var delta = award.Attitude == Shared.Models.PostReactionAttitude.Positive ? amount : -amount; await db.Posts.Where(p => p.Id == postId) .ExecuteUpdateAsync(s => s.SetProperty(p => p.AwardedScore, p => p.AwardedScore + delta)); @@ -1017,20 +1017,20 @@ public static class PostQueryExtensions source = isListing switch { true when currentUser is not null => source.Where(e => - e.Visibility != PostVisibility.Unlisted || publishersId.Contains(e.PublisherId)), - true => source.Where(e => e.Visibility != PostVisibility.Unlisted), + e.Visibility != Shared.Models.PostVisibility.Unlisted || publishersId.Contains(e.PublisherId)), + true => source.Where(e => e.Visibility != Shared.Models.PostVisibility.Unlisted), _ => source }; if (currentUser is null) return source .Where(e => e.PublishedAt != null && now >= e.PublishedAt) - .Where(e => e.Visibility == PostVisibility.Public); + .Where(e => e.Visibility == Shared.Models.PostVisibility.Public); return source .Where(e => (e.PublishedAt != null && now >= e.PublishedAt) || publishersId.Contains(e.PublisherId)) - .Where(e => e.Visibility != PostVisibility.Private || publishersId.Contains(e.PublisherId)) - .Where(e => e.Visibility != PostVisibility.Friends || + .Where(e => e.Visibility != Shared.Models.PostVisibility.Private || publishersId.Contains(e.PublisherId)) + .Where(e => e.Visibility != Shared.Models.PostVisibility.Friends || (e.Publisher.AccountId != null && userFriends.Contains(e.Publisher.AccountId.Value)) || publishersId.Contains(e.PublisherId)); } diff --git a/DysonNetwork.Sphere/Post/PostServiceGrpc.cs b/DysonNetwork.Sphere/Post/PostServiceGrpc.cs new file mode 100644 index 0000000..b37abbb --- /dev/null +++ b/DysonNetwork.Sphere/Post/PostServiceGrpc.cs @@ -0,0 +1,252 @@ +using DysonNetwork.Shared.Proto; +using DysonNetwork.Shared.Models; +using Grpc.Core; +using Microsoft.EntityFrameworkCore; + +namespace DysonNetwork.Sphere.Post; + +public class PostServiceGrpc(AppDatabase db, PostService ps) : Shared.Proto.PostService.PostServiceBase +{ + public override async Task GetPost(GetPostRequest request, ServerCallContext context) + { + if (!Guid.TryParse(request.Id, out var id)) + throw new RpcException(new Status(StatusCode.InvalidArgument, "invalid post id")); + + var post = await db.Posts + .Include(p => p.Publisher) + .Include(p => p.Tags) + .Include(p => p.Categories) + .Include(p => p.RepliedPost) + .Include(p => p.ForwardedPost) + .Include(p => p.FeaturedRecords) + .FilterWithVisibility(null, [], []) + .FirstOrDefaultAsync(p => p.Id == id); + + if (post == null) throw new RpcException(new Status(StatusCode.NotFound, "post not found")); + + post = await ps.LoadPostInfo(post); + + return post.ToProtoValue(); + } + + public override async Task GetPostBatch(GetPostBatchRequest request, ServerCallContext context) + { + var ids = request.Ids + .Where(s => !string.IsNullOrWhiteSpace(s) && Guid.TryParse(s, out _)) + .Select(Guid.Parse) + .ToList(); + + if (ids.Count == 0) return new GetPostBatchResponse(); + + var posts = await db.Posts + .Include(p => p.Publisher) + .Include(p => p.Tags) + .Include(p => p.Categories) + .Include(p => p.RepliedPost) + .Include(p => p.ForwardedPost) + .Include(p => p.FeaturedRecords) + .Include(p => p.Awards) + .Where(p => ids.Contains(p.Id)) + .FilterWithVisibility(null, [], []) + .ToListAsync(); + + posts = await ps.LoadPostInfo(posts, null); + + var resp = new GetPostBatchResponse(); + resp.Posts.AddRange(posts.Select(p => p.ToProtoValue())); + return resp; + } + + public override async Task SearchPosts(SearchPostsRequest request, ServerCallContext context) + { + var query = db.Posts + .Include(p => p.Publisher) + .Include(p => p.Tags) + .Include(p => p.Categories) + .Include(p => p.RepliedPost) + .Include(p => p.ForwardedPost) + .Include(p => p.Attachments) + .Include(p => p.Awards) + .Include(p => p.Reactions) + .Include(p => p.FeaturedRecords) + .Where(p => p.DeletedAt == null) // Only active posts + .AsQueryable(); + + if (!string.IsNullOrWhiteSpace(request.Query)) + { + // Simple search, assuming full-text search or title/content contains + query = query.Where(p => + EF.Functions.ILike(p.Title, $"%{request.Query}%") || + EF.Functions.ILike(p.Content, $"%{request.Query}%") || + EF.Functions.ILike(p.Description, $"%{request.Query}%")); + } + + if (!string.IsNullOrWhiteSpace(request.PublisherId) && Guid.TryParse(request.PublisherId, out var pid)) + { + query = query.Where(p => p.PublisherId == pid); + } + + if (!string.IsNullOrWhiteSpace(request.RealmId) && Guid.TryParse(request.RealmId, out var rid)) + { + query = query.Where(p => p.RealmId == rid); + } + + query = query.FilterWithVisibility(null, [], []); + + var totalSize = await query.CountAsync(); + + // Apply pagination + var pageSize = request.PageSize > 0 ? request.PageSize : 20; + var pageToken = request.PageToken; + var offset = string.IsNullOrEmpty(pageToken) ? 0 : int.Parse(pageToken); + + var posts = await query + .OrderByDescending(p => p.PublishedAt ?? p.CreatedAt) + .Skip(offset) + .Take(pageSize) + .ToListAsync(); + + posts = await ps.LoadPostInfo(posts, null, true); + + var nextToken = offset + pageSize < totalSize ? (offset + pageSize).ToString() : string.Empty; + + var resp = new SearchPostsResponse(); + resp.Posts.AddRange(posts.Select(p => p.ToProtoValue())); + resp.NextPageToken = nextToken; + resp.TotalSize = totalSize; + + return resp; + } + + public override async Task ListPosts(ListPostsRequest request, ServerCallContext context) + { + var query = db.Posts + .Include(p => p.Publisher) + .Include(p => p.Tags) + .Include(p => p.Categories) + .Include(p => p.RepliedPost) + .Include(p => p.ForwardedPost) + .Include(p => p.Awards) + .Include(p => p.FeaturedRecords) + .Where(p => p.DeletedAt == null) + .AsQueryable(); + + if (!string.IsNullOrWhiteSpace(request.PublisherId) && Guid.TryParse(request.PublisherId, out var pid)) + { + query = query.Where(p => p.PublisherId == pid); + } + + if (!string.IsNullOrWhiteSpace(request.RealmId) && Guid.TryParse(request.RealmId, out var rid)) + { + query = query.Where(p => p.RealmId == rid); + } + + if (request.Categories.Count > 0) + { + query = query.Where(p => p.Categories.Any(c => request.Categories.Contains(c.Slug))); + } + + if (request.Tags.Count > 0) + { + query = query.Where(p => p.Tags.Any(c => request.Tags.Contains(c.Slug))); + } + + // TODO: Add types filtering when proto is regenerated + // if (request.Types.Count > 0) + // { + // var types = request.Types.Select(t => (Shared.Models.PostType)t).Distinct(); + // query = query.Where(p => types.Contains(p.Type)); + // } + + if (request.OnlyMedia) + { + query = query.Where(e => e.Attachments.Count > 0); + } + + // Pinned filtering + switch (request.Pinned) + { + case Shared.Proto.PostPinMode.RealmPage when !string.IsNullOrWhiteSpace(request.RealmId): + query = query.Where(p => p.PinMode == Shared.Models.PostPinMode.RealmPage); + break; + case Shared.Proto.PostPinMode.PublisherPage when !string.IsNullOrWhiteSpace(request.PublisherId): + query = query.Where(p => p.PinMode == Shared.Models.PostPinMode.PublisherPage); + break; + case Shared.Proto.PostPinMode.ReplyPage: + query = query.Where(p => p.PinMode == Shared.Models.PostPinMode.ReplyPage); + break; + default: + if (request.Pinned != null) + { + // Specific pinned mode but conditions not met, or unknown mode + query = query.Where(p => p.PinMode == (Shared.Models.PostPinMode)request.Pinned); + } + else + { + query = query.Where(p => p.PinMode == null); + } + break; + } + + // Include/exclude replies + if (request.IncludeReplies) + { + // Include both root and reply posts + } + else + { + // Exclude reply posts, only root posts + query = query.Where(e => e.RepliedPostId == null); + } + + // TODO: Time range filtering when proto fields are available + // if (request.After != null) + // { + // var afterTime = request.After.ToDateTimeOffset(); + // query = query.Where(p => (p.CreatedAt >= afterTime) || (p.PublishedAt >= afterTime)); + // } + + // if (request.Before != null) + // { + // var beforeTime = request.Before.ToDateTimeOffset(); + // query = query.Where(p => (p.CreatedAt <= beforeTime) || (p.PublishedAt <= beforeTime)); + // } + + // TODO: Query text search when proto field is available + // if (!string.IsNullOrWhiteSpace(request.Query)) + // { + // query = query.Where(p => + // EF.Functions.ILike(p.Title, $"%{request.Query}%") || + // EF.Functions.ILike(p.Content, $"%{request.Query}%") || + // EF.Functions.ILike(p.Description, $"%{request.Query}%")); + // } + + // Visibility filter (simplified for grpc - no user context) + query = query.FilterWithVisibility(null, [], []); + + var totalSize = await query.CountAsync(); + + var pageSize = request.PageSize > 0 ? request.PageSize : 20; + var pageToken = request.PageToken; + var offset = string.IsNullOrEmpty(pageToken) ? 0 : int.Parse(pageToken); + + // Ordering - TODO: Add shuffle when proto field is available + var orderedQuery = query.OrderByDescending(e => e.PublishedAt ?? e.CreatedAt); + + var posts = await orderedQuery + .Skip(offset) + .Take(pageSize) + .ToListAsync(); + + posts = await ps.LoadPostInfo(posts, null, true); + + var nextToken = offset + pageSize < totalSize ? (offset + pageSize).ToString() : string.Empty; + + var resp = new ListPostsResponse(); + resp.Posts.AddRange(posts.Select(p => p.ToProtoValue())); + resp.NextPageToken = nextToken; + resp.TotalSize = totalSize; + + return resp; + } +} diff --git a/DysonNetwork.Sphere/Publisher/PublisherService.cs b/DysonNetwork.Sphere/Publisher/PublisherService.cs index 22cb479..51fdd06 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherService.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherService.cs @@ -4,6 +4,7 @@ using DysonNetwork.Shared.Proto; using DysonNetwork.Shared.Registry; using Microsoft.EntityFrameworkCore; using NodaTime; +using PublisherMemberRole = DysonNetwork.Shared.Models.PublisherMemberRole; using PublisherType = DysonNetwork.Shared.Models.PublisherType; namespace DysonNetwork.Sphere.Publisher; @@ -161,7 +162,7 @@ public class PublisherService( { var publisher = new SnPublisher { - Type = Shared.Models.PublisherType.Individual, + Type = PublisherType.Individual, Name = name ?? account.Name, Nick = nick ?? account.Nick, Bio = bio ?? account.Profile.Bio, @@ -177,7 +178,7 @@ public class PublisherService( new() { AccountId = Guid.Parse(account.Id), - Role = Shared.Models.PublisherMemberRole.Owner, + Role = PublisherMemberRole.Owner, JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow) } ] @@ -214,7 +215,7 @@ public class PublisherService( } public async Task CreateOrganizationPublisher( - Shared.Models.SnRealm realm, + SnRealm realm, Account account, string? name, string? nick, @@ -225,7 +226,7 @@ public class PublisherService( { var publisher = new SnPublisher { - Type = Shared.Models.PublisherType.Organizational, + Type = PublisherType.Organizational, Name = name ?? realm.Slug, Nick = nick ?? realm.Name, Bio = bio ?? realm.Description, @@ -237,7 +238,7 @@ public class PublisherService( new() { AccountId = Guid.Parse(account.Id), - Role = Shared.Models.PublisherMemberRole.Owner, + Role = PublisherMemberRole.Owner, JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow) } } @@ -299,10 +300,10 @@ public class PublisherService( var postsCount = await db.Posts.Where(e => e.Publisher.Id == publisher.Id).CountAsync(); var postsUpvotes = await db.PostReactions - .Where(r => r.Post.Publisher.Id == publisher.Id && r.Attitude == PostReactionAttitude.Positive) + .Where(r => r.Post.Publisher.Id == publisher.Id && r.Attitude == Shared.Models.PostReactionAttitude.Positive) .CountAsync(); var postsDownvotes = await db.PostReactions - .Where(r => r.Post.Publisher.Id == publisher.Id && r.Attitude == PostReactionAttitude.Negative) + .Where(r => r.Post.Publisher.Id == publisher.Id && r.Attitude == Shared.Models.PostReactionAttitude.Negative) .CountAsync(); var stickerPacksId = await db.StickerPacks.Where(e => e.Publisher.Id == publisher.Id).Select(e => e.Id) diff --git a/DysonNetwork.Sphere/Publisher/PublisherSubscriptionService.cs b/DysonNetwork.Sphere/Publisher/PublisherSubscriptionService.cs index 636da92..bfb33ec 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherSubscriptionService.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherSubscriptionService.cs @@ -11,7 +11,7 @@ namespace DysonNetwork.Sphere.Publisher; public class PublisherSubscriptionService( AppDatabase db, - PostService ps, + Post.PostService ps, IStringLocalizer localizer, ICacheService cache, RingService.RingServiceClient pusher, @@ -54,7 +54,7 @@ public class PublisherSubscriptionService( { if (post.RepliedPostId is not null) return 0; - if (post.Visibility != PostVisibility.Public) + if (post.Visibility != Shared.Models.PostVisibility.Public) return 0; // Create notification data diff --git a/DysonNetwork.Sphere/Startup/ApplicationConfiguration.cs b/DysonNetwork.Sphere/Startup/ApplicationConfiguration.cs index 121606d..45558df 100644 --- a/DysonNetwork.Sphere/Startup/ApplicationConfiguration.cs +++ b/DysonNetwork.Sphere/Startup/ApplicationConfiguration.cs @@ -1,5 +1,6 @@ using DysonNetwork.Shared.Auth; using DysonNetwork.Shared.Http; +using DysonNetwork.Sphere.Post; using DysonNetwork.Sphere.Publisher; namespace DysonNetwork.Sphere.Startup; @@ -20,6 +21,7 @@ public static class ApplicationConfiguration app.MapControllers(); // Map gRPC services + app.MapGrpcService(); app.MapGrpcService(); return app; diff --git a/DysonNetwork.Sphere/Startup/BroadcastEventHandler.cs b/DysonNetwork.Sphere/Startup/BroadcastEventHandler.cs index 34169c4..c0d940c 100644 --- a/DysonNetwork.Sphere/Startup/BroadcastEventHandler.cs +++ b/DysonNetwork.Sphere/Startup/BroadcastEventHandler.cs @@ -24,7 +24,7 @@ public class PaymentOrderAwardMeta [JsonPropertyName("account_id")] public Guid AccountId { get; set; } [JsonPropertyName("post_id")] public Guid PostId { get; set; } [JsonPropertyName("amount")] public string Amount { get; set; } = null!; - [JsonPropertyName("attitude")] public PostReactionAttitude Attitude { get; set; } + [JsonPropertyName("attitude")] public Shared.Models.PostReactionAttitude Attitude { get; set; } [JsonPropertyName("message")] public string? Message { get; set; } } @@ -82,7 +82,7 @@ public class BroadcastEventHandler( logger.LogInformation("Handling post award order: {OrderId}", evt.OrderId); await using var scope = serviceProvider.CreateAsyncScope(); - var ps = scope.ServiceProvider.GetRequiredService(); + var ps = scope.ServiceProvider.GetRequiredService(); var amountNum = decimal.Parse(meta.Amount);