✨ Provide real user and posts data for the thinking
This commit is contained in:
@@ -14,7 +14,7 @@ builder.ConfigureAppKestrel(builder.Configuration);
|
|||||||
builder.Services.AddAppServices(builder.Configuration);
|
builder.Services.AddAppServices(builder.Configuration);
|
||||||
builder.Services.AddAppAuthentication();
|
builder.Services.AddAppAuthentication();
|
||||||
builder.Services.AddDysonAuth();
|
builder.Services.AddDysonAuth();
|
||||||
builder.Services.AddPublisherService();
|
builder.Services.AddSphereService();
|
||||||
builder.Services.AddAccountService();
|
builder.Services.AddAccountService();
|
||||||
builder.Services.AddDriveService();
|
builder.Services.AddDriveService();
|
||||||
|
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ public class FileController(
|
|||||||
|
|
||||||
public class MarkFileRequest
|
public class MarkFileRequest
|
||||||
{
|
{
|
||||||
public List<ContentSensitiveMark>? SensitiveMarks { get; set; }
|
public List<Shared.Models.ContentSensitiveMark>? SensitiveMarks { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
@@ -298,7 +298,7 @@ public class FileController(
|
|||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public Dictionary<string, object?>? UserMeta { get; set; }
|
public Dictionary<string, object?>? UserMeta { get; set; }
|
||||||
public Dictionary<string, object?>? FileMeta { get; set; }
|
public Dictionary<string, object?>? FileMeta { get; set; }
|
||||||
public List<ContentSensitiveMark>? SensitiveMarks { get; set; }
|
public List<Shared.Models.ContentSensitiveMark>? SensitiveMarks { get; set; }
|
||||||
public Guid PoolId { get; set; }
|
public Guid PoolId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,9 +122,9 @@ var routes = specialRoutes.Concat(apiRoutes).Concat(swaggerRoutes).ToArray();
|
|||||||
var clusters = serviceNames.Select(serviceName => new ClusterConfig
|
var clusters = serviceNames.Select(serviceName => new ClusterConfig
|
||||||
{
|
{
|
||||||
ClusterId = serviceName,
|
ClusterId = serviceName,
|
||||||
HealthCheck = new()
|
HealthCheck = new HealthCheckConfig
|
||||||
{
|
{
|
||||||
Active = new()
|
Active = new ActiveHealthCheckConfig
|
||||||
{
|
{
|
||||||
Enabled = true,
|
Enabled = true,
|
||||||
Interval = TimeSpan.FromSeconds(10),
|
Interval = TimeSpan.FromSeconds(10),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using DysonNetwork.Insight;
|
using DysonNetwork.Insight;
|
||||||
using DysonNetwork.Insight.Startup;
|
using DysonNetwork.Insight.Startup;
|
||||||
using DysonNetwork.Shared.Http;
|
using DysonNetwork.Shared.Http;
|
||||||
|
using DysonNetwork.Shared.Registry;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
@@ -10,10 +11,13 @@ builder.AddServiceDefaults();
|
|||||||
builder.ConfigureAppKestrel(builder.Configuration);
|
builder.ConfigureAppKestrel(builder.Configuration);
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
builder.Services.AddAppServices(builder.Configuration);
|
builder.Services.AddAppServices();
|
||||||
builder.Services.AddAppAuthentication();
|
builder.Services.AddAppAuthentication();
|
||||||
builder.Services.AddAppFlushHandlers();
|
builder.Services.AddAppFlushHandlers();
|
||||||
builder.Services.AddAppBusinessServices();
|
builder.Services.AddAppBusinessServices();
|
||||||
|
|
||||||
|
builder.Services.AddAccountService();
|
||||||
|
builder.Services.AddSphereService();
|
||||||
builder.Services.AddThinkingServices(builder.Configuration);
|
builder.Services.AddThinkingServices(builder.Configuration);
|
||||||
|
|
||||||
builder.AddSwaggerManifest(
|
builder.AddSwaggerManifest(
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace DysonNetwork.Insight.Startup;
|
|||||||
|
|
||||||
public static class ServiceCollectionExtensions
|
public static class ServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration)
|
public static IServiceCollection AddAppServices(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddDbContext<AppDatabase>();
|
services.AddDbContext<AppDatabase>();
|
||||||
services.AddSingleton<IClock>(SystemClock.Instance);
|
services.AddSingleton<IClock>(SystemClock.Instance);
|
||||||
@@ -65,8 +65,7 @@ public static class ServiceCollectionExtensions
|
|||||||
|
|
||||||
public static IServiceCollection AddThinkingServices(this IServiceCollection services, IConfiguration configuration)
|
public static IServiceCollection AddThinkingServices(this IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
var thinkingProvider = new ThinkingProvider(configuration);
|
services.AddSingleton<ThinkingProvider>();
|
||||||
services.AddSingleton(thinkingProvider);
|
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,34 @@
|
|||||||
|
using DysonNetwork.Shared.Proto;
|
||||||
using Microsoft.SemanticKernel;
|
using Microsoft.SemanticKernel;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace DysonNetwork.Insight.Thinking;
|
namespace DysonNetwork.Insight.Thinking;
|
||||||
|
|
||||||
public class ThinkingProvider
|
public class ThinkingProvider
|
||||||
{
|
{
|
||||||
public readonly Kernel Kernel;
|
private readonly Kernel _kernel;
|
||||||
public readonly string? ModelProviderType;
|
private readonly PostService.PostServiceClient _postClient;
|
||||||
public readonly string? ModelDefault;
|
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");
|
var cfg = configuration.GetSection("Thinking");
|
||||||
ModelProviderType = cfg.GetValue<string>("Provider")?.ToLower();
|
ModelProviderType = cfg.GetValue<string>("Provider")?.ToLower();
|
||||||
@@ -26,23 +46,37 @@ public class ThinkingProvider
|
|||||||
throw new IndexOutOfRangeException("Unknown thinking provider: " + ModelProviderType);
|
throw new IndexOutOfRangeException("Unknown thinking provider: " + ModelProviderType);
|
||||||
}
|
}
|
||||||
|
|
||||||
Kernel = builder.Build();
|
return builder.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeHelperFunctions()
|
||||||
|
{
|
||||||
// Add Solar Network tools plugin
|
// Add Solar Network tools plugin
|
||||||
Kernel.ImportPluginFromFunctions("helper_functions", [
|
_kernel.ImportPluginFromFunctions("helper_functions", [
|
||||||
KernelFunctionFactory.CreateFromMethod(async (string userId) =>
|
KernelFunctionFactory.CreateFromMethod(async (string userId) =>
|
||||||
{
|
{
|
||||||
// MOCK: simulate fetching user profile
|
var request = new GetAccountRequest { Id = userId };
|
||||||
await Task.Delay(100);
|
var response = await _accountClient.GetAccountAsync(request);
|
||||||
return $"{{\"userId\":\"{userId}\",\"name\":\"MockUser\",\"bio\":\"Loves music and tech.\"}}";
|
return JsonSerializer.Serialize(response, GrpcTypeHelper.SerializerOptions);
|
||||||
}, "get_user_profile", "Get a user profile from the Solar Network."),
|
}, "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
|
var request = new GetPostRequest { Id = postId };
|
||||||
await Task.Delay(200);
|
var response = await _postClient.GetPostAsync(request);
|
||||||
return
|
return JsonSerializer.Serialize(response, GrpcTypeHelper.SerializerOptions);
|
||||||
$"[{{\"postId\":\"p1\",\"topic\":\"{topic}\",\"content\":\"Mock post content 1.\"}}, {{\"postId\":\"p2\",\"topic\":\"{topic}\",\"content\":\"Mock post content 2.\"}}]";
|
}, "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.")
|
}, "get_recent_posts", "Get recent posts from the Solar Network.")
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
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 NodaTime;
|
using NodaTime;
|
||||||
using NpgsqlTypes;
|
using NpgsqlTypes;
|
||||||
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
|
||||||
namespace DysonNetwork.Shared.Models;
|
namespace DysonNetwork.Shared.Models;
|
||||||
|
|
||||||
@@ -85,6 +87,107 @@ public class SnPost : ModelBase, IIdentifiedResource, IActivity
|
|||||||
|
|
||||||
public string ResourceIdentifier => $"post:{Id}";
|
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()
|
public SnActivity ToActivity()
|
||||||
{
|
{
|
||||||
return new SnActivity()
|
return new SnActivity()
|
||||||
@@ -108,6 +211,30 @@ public class SnPostTag : ModelBase
|
|||||||
[JsonIgnore] public List<SnPost> Posts { get; set; } = new List<SnPost>();
|
[JsonIgnore] public List<SnPost> Posts { get; set; } = new List<SnPost>();
|
||||||
|
|
||||||
[NotMapped] public int? Usage { get; set; }
|
[NotMapped] public int? Usage { get; set; }
|
||||||
|
|
||||||
|
public PostTag ToProtoValue()
|
||||||
|
{
|
||||||
|
return new PostTag
|
||||||
|
{
|
||||||
|
Id = Id.ToString(),
|
||||||
|
Slug = Slug,
|
||||||
|
Name = Name ?? string.Empty,
|
||||||
|
CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()),
|
||||||
|
UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SnPostTag FromProtoValue(PostTag proto)
|
||||||
|
{
|
||||||
|
return new SnPostTag
|
||||||
|
{
|
||||||
|
Id = Guid.Parse(proto.Id),
|
||||||
|
Slug = proto.Slug,
|
||||||
|
Name = proto.Name != string.Empty ? proto.Name : null,
|
||||||
|
CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()),
|
||||||
|
UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset())
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SnPostCategory : ModelBase
|
public class SnPostCategory : ModelBase
|
||||||
@@ -118,6 +245,30 @@ public class SnPostCategory : ModelBase
|
|||||||
[JsonIgnore] public List<SnPost> Posts { get; set; } = new List<SnPost>();
|
[JsonIgnore] public List<SnPost> Posts { get; set; } = new List<SnPost>();
|
||||||
|
|
||||||
[NotMapped] public int? Usage { get; set; }
|
[NotMapped] public int? Usage { get; set; }
|
||||||
|
|
||||||
|
public PostCategory ToProtoValue()
|
||||||
|
{
|
||||||
|
return new PostCategory
|
||||||
|
{
|
||||||
|
Id = Id.ToString(),
|
||||||
|
Slug = Slug,
|
||||||
|
Name = Name ?? string.Empty,
|
||||||
|
CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()),
|
||||||
|
UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SnPostCategory FromProtoValue(PostCategory proto)
|
||||||
|
{
|
||||||
|
return new SnPostCategory
|
||||||
|
{
|
||||||
|
Id = Guid.Parse(proto.Id),
|
||||||
|
Slug = proto.Slug,
|
||||||
|
Name = proto.Name != string.Empty ? proto.Name : null,
|
||||||
|
CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()),
|
||||||
|
UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset())
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SnPostCategorySubscription : ModelBase
|
public class SnPostCategorySubscription : ModelBase
|
||||||
@@ -150,6 +301,23 @@ public class SnPostFeaturedRecord : ModelBase
|
|||||||
[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; }
|
||||||
|
|
||||||
|
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
|
public enum PostReactionAttitude
|
||||||
@@ -169,6 +337,40 @@ public class SnPostReaction : ModelBase
|
|||||||
[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()
|
||||||
|
{
|
||||||
|
var proto = new PostReaction
|
||||||
|
{
|
||||||
|
Id = Id.ToString(),
|
||||||
|
Symbol = Symbol,
|
||||||
|
Attitude = (Proto.PostReactionAttitude)((int)Attitude + 1),
|
||||||
|
PostId = PostId.ToString(),
|
||||||
|
AccountId = AccountId.ToString(),
|
||||||
|
CreatedAt = Timestamp.FromDateTimeOffset(CreatedAt.ToDateTimeOffset()),
|
||||||
|
UpdatedAt = Timestamp.FromDateTimeOffset(UpdatedAt.ToDateTimeOffset())
|
||||||
|
};
|
||||||
|
if (Account != null)
|
||||||
|
{
|
||||||
|
proto.Account = Account.ToProtoValue();
|
||||||
|
}
|
||||||
|
return proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SnPostReaction FromProtoValue(Proto.PostReaction proto)
|
||||||
|
{
|
||||||
|
return new SnPostReaction
|
||||||
|
{
|
||||||
|
Id = Guid.Parse(proto.Id),
|
||||||
|
Symbol = proto.Symbol,
|
||||||
|
Attitude = (PostReactionAttitude)((int)proto.Attitude - 1),
|
||||||
|
PostId = Guid.Parse(proto.PostId),
|
||||||
|
AccountId = Guid.Parse(proto.AccountId),
|
||||||
|
Account = proto.Account != null ? SnAccount.FromProtoValue(proto.Account) : null,
|
||||||
|
CreatedAt = Instant.FromDateTimeOffset(proto.CreatedAt.ToDateTimeOffset()),
|
||||||
|
UpdatedAt = Instant.FromDateTimeOffset(proto.UpdatedAt.ToDateTimeOffset())
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SnPostAward : ModelBase
|
public class SnPostAward : ModelBase
|
||||||
@@ -181,6 +383,25 @@ public class SnPostAward : ModelBase
|
|||||||
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 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>
|
/// <summary>
|
||||||
@@ -193,6 +414,30 @@ public class PostEmbedView
|
|||||||
public string Uri { get; set; } = null!;
|
public string Uri { get; set; } = null!;
|
||||||
public double? AspectRatio { get; set; }
|
public double? AspectRatio { get; set; }
|
||||||
public PostEmbedViewRenderer Renderer { get; set; } = PostEmbedViewRenderer.WebView;
|
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
|
public enum PostEmbedViewRenderer
|
||||||
|
|||||||
283
DysonNetwork.Shared/Proto/post.proto
Normal file
283
DysonNetwork.Shared/Proto/post.proto
Normal 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';
|
||||||
@@ -59,7 +59,7 @@ public static class ServiceInjectionHelper
|
|||||||
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
|
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
|
||||||
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
|
{ ServerCertificateCustomValidationCallback = (_, _, _, _) => true }
|
||||||
);
|
);
|
||||||
|
|
||||||
services
|
services
|
||||||
.AddGrpcClient<RealmService.RealmServiceClient>(o => o.Address = new Uri("https://_grpc.pass"))
|
.AddGrpcClient<RealmService.RealmServiceClient>(o => o.Address = new Uri("https://_grpc.pass"))
|
||||||
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
|
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
|
||||||
@@ -69,7 +69,7 @@ public static class ServiceInjectionHelper
|
|||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddDriveService(this IServiceCollection services)
|
public static IServiceCollection AddDriveService(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddGrpcClient<FileService.FileServiceClient>(o => o.Address = new Uri("https://_grpc.drive"))
|
services.AddGrpcClient<FileService.FileServiceClient>(o => o.Address = new Uri("https://_grpc.drive"))
|
||||||
@@ -86,8 +86,14 @@ public static class ServiceInjectionHelper
|
|||||||
return services;
|
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
|
services
|
||||||
.AddGrpcClient<PublisherService.PublisherServiceClient>(o => o.Address = new Uri("https://_grpc.sphere"))
|
.AddGrpcClient<PublisherService.PublisherServiceClient>(o => o.Address = new Uri("https://_grpc.sphere"))
|
||||||
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
|
.ConfigurePrimaryHttpMessageHandler(_ => new HttpClientHandler()
|
||||||
@@ -107,4 +113,4 @@ public static class ServiceInjectionHelper
|
|||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ namespace DysonNetwork.Sphere.Activity;
|
|||||||
public class ActivityService(
|
public class ActivityService(
|
||||||
AppDatabase db,
|
AppDatabase db,
|
||||||
Publisher.PublisherService pub,
|
Publisher.PublisherService pub,
|
||||||
PostService ps,
|
Post.PostService ps,
|
||||||
RemoteRealmService rs,
|
RemoteRealmService rs,
|
||||||
DiscoveryService ds,
|
DiscoveryService ds,
|
||||||
AccountService.AccountServiceClient accounts
|
AccountService.AccountServiceClient accounts
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ public class PostController(
|
|||||||
if (realm != null)
|
if (realm != null)
|
||||||
query = query.Where(p => p.RealmId == realm.Id);
|
query = query.Where(p => p.RealmId == realm.Id);
|
||||||
if (type != null)
|
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 })
|
if (categories is { Count: > 0 })
|
||||||
query = query.Where(p => p.Categories.Any(c => categories.Contains(c.Slug)));
|
query = query.Where(p => p.Categories.Any(c => categories.Contains(c.Slug)));
|
||||||
if (tags is { Count: > 0 })
|
if (tags is { Count: > 0 })
|
||||||
@@ -139,10 +139,10 @@ public class PostController(
|
|||||||
switch (pinned)
|
switch (pinned)
|
||||||
{
|
{
|
||||||
case true when realm != null:
|
case true when realm != null:
|
||||||
query = query.Where(p => p.PinMode == PostPinMode.RealmPage);
|
query = query.Where(p => p.PinMode == Shared.Models.PostPinMode.RealmPage);
|
||||||
break;
|
break;
|
||||||
case true when publisher != null:
|
case true when publisher != null:
|
||||||
query = query.Where(p => p.PinMode == PostPinMode.PublisherPage);
|
query = query.Where(p => p.PinMode == Shared.Models.PostPinMode.PublisherPage);
|
||||||
break;
|
break;
|
||||||
case true:
|
case true:
|
||||||
return BadRequest(
|
return BadRequest(
|
||||||
@@ -360,7 +360,7 @@ public class PostController(
|
|||||||
|
|
||||||
var now = SystemClock.Instance.GetCurrentInstant();
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
var posts = await db.Posts
|
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)
|
.OrderByDescending(p => p.CreatedAt)
|
||||||
.FilterWithVisibility(currentUser, userFriends, userPublishers)
|
.FilterWithVisibility(currentUser, userFriends, userPublishers)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
@@ -425,9 +425,9 @@ public class PostController(
|
|||||||
[MaxLength(4096)] public string? Description { get; set; }
|
[MaxLength(4096)] public string? Description { get; set; }
|
||||||
[MaxLength(1024)] public string? Slug { get; set; }
|
[MaxLength(1024)] public string? Slug { get; set; }
|
||||||
public string? Content { get; set; }
|
public string? Content { get; set; }
|
||||||
public PostVisibility? Visibility { get; set; } = PostVisibility.Public;
|
public Shared.Models.PostVisibility? Visibility { get; set; } = Shared.Models.PostVisibility.Public;
|
||||||
public PostType? Type { get; set; }
|
public Shared.Models.PostType? Type { get; set; }
|
||||||
public PostEmbedView? EmbedView { get; set; }
|
public Shared.Models.PostEmbedView? EmbedView { get; set; }
|
||||||
[MaxLength(16)] public List<string>? Tags { get; set; }
|
[MaxLength(16)] public List<string>? Tags { get; set; }
|
||||||
[MaxLength(8)] public List<string>? Categories { get; set; }
|
[MaxLength(8)] public List<string>? Categories { get; set; }
|
||||||
[MaxLength(32)] public List<string>? Attachments { get; set; }
|
[MaxLength(32)] public List<string>? Attachments { get; set; }
|
||||||
@@ -477,9 +477,9 @@ public class PostController(
|
|||||||
Description = request.Description,
|
Description = request.Description,
|
||||||
Slug = request.Slug,
|
Slug = request.Slug,
|
||||||
Content = request.Content,
|
Content = request.Content,
|
||||||
Visibility = request.Visibility ?? PostVisibility.Public,
|
Visibility = request.Visibility ?? Shared.Models.PostVisibility.Public,
|
||||||
PublishedAt = request.PublishedAt,
|
PublishedAt = request.PublishedAt,
|
||||||
Type = request.Type ?? PostType.Moment,
|
Type = request.Type ?? Shared.Models.PostType.Moment,
|
||||||
Meta = request.Meta,
|
Meta = request.Meta,
|
||||||
EmbedView = request.EmbedView,
|
EmbedView = request.EmbedView,
|
||||||
Publisher = publisher,
|
Publisher = publisher,
|
||||||
@@ -565,7 +565,7 @@ public class PostController(
|
|||||||
public class PostReactionRequest
|
public class PostReactionRequest
|
||||||
{
|
{
|
||||||
[MaxLength(256)] public string Symbol { get; set; } = null!;
|
[MaxLength(256)] public string Symbol { get; set; } = null!;
|
||||||
public PostReactionAttitude Attitude { get; set; }
|
public Shared.Models.PostReactionAttitude Attitude { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly List<string> ReactionsAllowedDefault =
|
public static readonly List<string> ReactionsAllowedDefault =
|
||||||
@@ -638,7 +638,7 @@ public class PostController(
|
|||||||
public class PostAwardRequest
|
public class PostAwardRequest
|
||||||
{
|
{
|
||||||
public decimal Amount { get; set; }
|
public decimal Amount { get; set; }
|
||||||
public PostReactionAttitude Attitude { get; set; }
|
public Shared.Models.PostReactionAttitude Attitude { get; set; }
|
||||||
[MaxLength(4096)] public string? Message { get; set; }
|
[MaxLength(4096)] public string? Message { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -671,7 +671,7 @@ public class PostController(
|
|||||||
public async Task<ActionResult<PostAwardResponse>> AwardPost(Guid id, [FromBody] PostAwardRequest request)
|
public async Task<ActionResult<PostAwardResponse>> AwardPost(Guid id, [FromBody] PostAwardRequest request)
|
||||||
{
|
{
|
||||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
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");
|
return BadRequest("You cannot create a neutral post award");
|
||||||
|
|
||||||
var friendsResponse =
|
var friendsResponse =
|
||||||
@@ -714,7 +714,7 @@ public class PostController(
|
|||||||
|
|
||||||
public class PostPinRequest
|
public class PostPinRequest
|
||||||
{
|
{
|
||||||
[Required] public PostPinMode Mode { get; set; }
|
[Required] public Shared.Models.PostPinMode Mode { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{id:guid}/pin")]
|
[HttpPost("{id:guid}/pin")]
|
||||||
@@ -734,7 +734,7 @@ public class PostController(
|
|||||||
if (!await pub.IsMemberWithRole(post.PublisherId, accountId, PublisherMemberRole.Editor))
|
if (!await pub.IsMemberWithRole(post.PublisherId, accountId, PublisherMemberRole.Editor))
|
||||||
return StatusCode(403, "You are not an editor of this publisher");
|
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<int> { RealmMemberRole.Moderator }))
|
if (!await rs.IsMemberWithRole(post.RealmId.Value, accountId, new List<int> { RealmMemberRole.Moderator }))
|
||||||
return StatusCode(403, "You are not a moderator of this realm");
|
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))
|
if (!await pub.IsMemberWithRole(post.PublisherId, accountId, PublisherMemberRole.Editor))
|
||||||
return StatusCode(403, "You are not an editor of this publisher");
|
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<int> { RealmMemberRole.Moderator }))
|
if (!await rs.IsMemberWithRole(post.RealmId.Value, accountId, new List<int> { RealmMemberRole.Moderator }))
|
||||||
return StatusCode(403, "You are not a moderator of this realm");
|
return StatusCode(403, "You are not a moderator of this realm");
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ public partial class PostService(
|
|||||||
|
|
||||||
public async Task<SnPost> PreviewPostLinkAsync(SnPost item)
|
public async Task<SnPost> 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
|
// Find all URLs in the content
|
||||||
var matches = GetLinkRegex().Matches(item.Content);
|
var matches = GetLinkRegex().Matches(item.Content);
|
||||||
@@ -420,12 +420,12 @@ public partial class PostService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SnPost> PinPostAsync(SnPost post, Account currentUser, PostPinMode pinMode)
|
public async Task<SnPost> PinPostAsync(SnPost post, Account currentUser, Shared.Models.PostPinMode pinMode)
|
||||||
{
|
{
|
||||||
var accountId = Guid.Parse(currentUser.Id);
|
var accountId = Guid.Parse(currentUser.Id);
|
||||||
if (post.RepliedPostId != null)
|
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.");
|
throw new InvalidOperationException("Replies can only be pinned in the reply page.");
|
||||||
if (post.RepliedPost == null) throw new ArgumentNullException(nameof(post.RepliedPost));
|
if (post.RepliedPost == null) throw new ArgumentNullException(nameof(post.RepliedPost));
|
||||||
|
|
||||||
@@ -516,11 +516,11 @@ public partial class PostService(
|
|||||||
|
|
||||||
switch (reaction.Attitude)
|
switch (reaction.Attitude)
|
||||||
{
|
{
|
||||||
case PostReactionAttitude.Positive:
|
case Shared.Models.PostReactionAttitude.Positive:
|
||||||
if (isRemoving) post.Upvotes--;
|
if (isRemoving) post.Upvotes--;
|
||||||
else post.Upvotes++;
|
else post.Upvotes++;
|
||||||
break;
|
break;
|
||||||
case PostReactionAttitude.Negative:
|
case Shared.Models.PostReactionAttitude.Negative:
|
||||||
if (isRemoving) post.Downvotes--;
|
if (isRemoving) post.Downvotes--;
|
||||||
else post.Downvotes++;
|
else post.Downvotes++;
|
||||||
break;
|
break;
|
||||||
@@ -771,7 +771,7 @@ public partial class PostService(
|
|||||||
if (currentUser is null)
|
if (currentUser is null)
|
||||||
{
|
{
|
||||||
// Anonymous user can only view public posts that are published
|
// 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
|
// Check publication status - either published or user is member
|
||||||
@@ -781,10 +781,10 @@ public partial class PostService(
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Check visibility
|
// Check visibility
|
||||||
if (post.Visibility == PostVisibility.Private && !isMember)
|
if (post.Visibility == Shared.Models.PostVisibility.Private && !isMember)
|
||||||
return false;
|
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))
|
!(post.Publisher.AccountId.HasValue && userFriends.Contains(post.Publisher.AccountId.Value) || isMember))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -843,7 +843,7 @@ public partial class PostService(
|
|||||||
var periodEnd = today.InUtc().Date.AtStartOfDayInZone(DateTimeZone.Utc).ToInstant();
|
var periodEnd = today.InUtc().Date.AtStartOfDayInZone(DateTimeZone.Utc).ToInstant();
|
||||||
|
|
||||||
var postsInPeriod = await db.Posts
|
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)
|
.Where(e => e.CreatedAt >= periodStart && e.CreatedAt < periodEnd)
|
||||||
.Select(e => e.Id)
|
.Select(e => e.Id)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
@@ -854,7 +854,7 @@ public partial class PostService(
|
|||||||
.Select(e => new
|
.Select(e => new
|
||||||
{
|
{
|
||||||
PostId = e.Key,
|
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);
|
.ToDictionaryAsync(e => e.PostId, e => e.Score);
|
||||||
|
|
||||||
@@ -928,7 +928,7 @@ public partial class PostService(
|
|||||||
Guid postId,
|
Guid postId,
|
||||||
Guid accountId,
|
Guid accountId,
|
||||||
decimal amount,
|
decimal amount,
|
||||||
PostReactionAttitude attitude,
|
Shared.Models.PostReactionAttitude attitude,
|
||||||
string? message
|
string? message
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@@ -947,7 +947,7 @@ public partial class PostService(
|
|||||||
db.PostAwards.Add(award);
|
db.PostAwards.Add(award);
|
||||||
await db.SaveChangesAsync();
|
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)
|
await db.Posts.Where(p => p.Id == postId)
|
||||||
.ExecuteUpdateAsync(s => s.SetProperty(p => p.AwardedScore, p => p.AwardedScore + delta));
|
.ExecuteUpdateAsync(s => s.SetProperty(p => p.AwardedScore, p => p.AwardedScore + delta));
|
||||||
@@ -1017,20 +1017,20 @@ public static class PostQueryExtensions
|
|||||||
source = isListing switch
|
source = isListing switch
|
||||||
{
|
{
|
||||||
true when currentUser is not null => source.Where(e =>
|
true when currentUser is not null => source.Where(e =>
|
||||||
e.Visibility != PostVisibility.Unlisted || publishersId.Contains(e.PublisherId)),
|
e.Visibility != Shared.Models.PostVisibility.Unlisted || publishersId.Contains(e.PublisherId)),
|
||||||
true => source.Where(e => e.Visibility != PostVisibility.Unlisted),
|
true => source.Where(e => e.Visibility != Shared.Models.PostVisibility.Unlisted),
|
||||||
_ => source
|
_ => source
|
||||||
};
|
};
|
||||||
|
|
||||||
if (currentUser is null)
|
if (currentUser is null)
|
||||||
return source
|
return source
|
||||||
.Where(e => e.PublishedAt != null && now >= e.PublishedAt)
|
.Where(e => e.PublishedAt != null && now >= e.PublishedAt)
|
||||||
.Where(e => e.Visibility == PostVisibility.Public);
|
.Where(e => e.Visibility == Shared.Models.PostVisibility.Public);
|
||||||
|
|
||||||
return source
|
return source
|
||||||
.Where(e => (e.PublishedAt != null && now >= e.PublishedAt) || publishersId.Contains(e.PublisherId))
|
.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 != Shared.Models.PostVisibility.Private || publishersId.Contains(e.PublisherId))
|
||||||
.Where(e => e.Visibility != PostVisibility.Friends ||
|
.Where(e => e.Visibility != Shared.Models.PostVisibility.Friends ||
|
||||||
(e.Publisher.AccountId != null && userFriends.Contains(e.Publisher.AccountId.Value)) ||
|
(e.Publisher.AccountId != null && userFriends.Contains(e.Publisher.AccountId.Value)) ||
|
||||||
publishersId.Contains(e.PublisherId));
|
publishersId.Contains(e.PublisherId));
|
||||||
}
|
}
|
||||||
|
|||||||
252
DysonNetwork.Sphere/Post/PostServiceGrpc.cs
Normal file
252
DysonNetwork.Sphere/Post/PostServiceGrpc.cs
Normal file
@@ -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<Shared.Proto.Post> 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<GetPostBatchResponse> 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<SearchPostsResponse> 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<ListPostsResponse> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ using DysonNetwork.Shared.Proto;
|
|||||||
using DysonNetwork.Shared.Registry;
|
using DysonNetwork.Shared.Registry;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
using PublisherMemberRole = DysonNetwork.Shared.Models.PublisherMemberRole;
|
||||||
using PublisherType = DysonNetwork.Shared.Models.PublisherType;
|
using PublisherType = DysonNetwork.Shared.Models.PublisherType;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Publisher;
|
namespace DysonNetwork.Sphere.Publisher;
|
||||||
@@ -161,7 +162,7 @@ public class PublisherService(
|
|||||||
{
|
{
|
||||||
var publisher = new SnPublisher
|
var publisher = new SnPublisher
|
||||||
{
|
{
|
||||||
Type = Shared.Models.PublisherType.Individual,
|
Type = PublisherType.Individual,
|
||||||
Name = name ?? account.Name,
|
Name = name ?? account.Name,
|
||||||
Nick = nick ?? account.Nick,
|
Nick = nick ?? account.Nick,
|
||||||
Bio = bio ?? account.Profile.Bio,
|
Bio = bio ?? account.Profile.Bio,
|
||||||
@@ -177,7 +178,7 @@ public class PublisherService(
|
|||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
AccountId = Guid.Parse(account.Id),
|
AccountId = Guid.Parse(account.Id),
|
||||||
Role = Shared.Models.PublisherMemberRole.Owner,
|
Role = PublisherMemberRole.Owner,
|
||||||
JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow)
|
JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -214,7 +215,7 @@ public class PublisherService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SnPublisher> CreateOrganizationPublisher(
|
public async Task<SnPublisher> CreateOrganizationPublisher(
|
||||||
Shared.Models.SnRealm realm,
|
SnRealm realm,
|
||||||
Account account,
|
Account account,
|
||||||
string? name,
|
string? name,
|
||||||
string? nick,
|
string? nick,
|
||||||
@@ -225,7 +226,7 @@ public class PublisherService(
|
|||||||
{
|
{
|
||||||
var publisher = new SnPublisher
|
var publisher = new SnPublisher
|
||||||
{
|
{
|
||||||
Type = Shared.Models.PublisherType.Organizational,
|
Type = PublisherType.Organizational,
|
||||||
Name = name ?? realm.Slug,
|
Name = name ?? realm.Slug,
|
||||||
Nick = nick ?? realm.Name,
|
Nick = nick ?? realm.Name,
|
||||||
Bio = bio ?? realm.Description,
|
Bio = bio ?? realm.Description,
|
||||||
@@ -237,7 +238,7 @@ public class PublisherService(
|
|||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
AccountId = Guid.Parse(account.Id),
|
AccountId = Guid.Parse(account.Id),
|
||||||
Role = Shared.Models.PublisherMemberRole.Owner,
|
Role = PublisherMemberRole.Owner,
|
||||||
JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow)
|
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 postsCount = await db.Posts.Where(e => e.Publisher.Id == publisher.Id).CountAsync();
|
||||||
var postsUpvotes = await db.PostReactions
|
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();
|
.CountAsync();
|
||||||
var postsDownvotes = await db.PostReactions
|
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();
|
.CountAsync();
|
||||||
|
|
||||||
var stickerPacksId = await db.StickerPacks.Where(e => e.Publisher.Id == publisher.Id).Select(e => e.Id)
|
var stickerPacksId = await db.StickerPacks.Where(e => e.Publisher.Id == publisher.Id).Select(e => e.Id)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace DysonNetwork.Sphere.Publisher;
|
|||||||
|
|
||||||
public class PublisherSubscriptionService(
|
public class PublisherSubscriptionService(
|
||||||
AppDatabase db,
|
AppDatabase db,
|
||||||
PostService ps,
|
Post.PostService ps,
|
||||||
IStringLocalizer<NotificationResource> localizer,
|
IStringLocalizer<NotificationResource> localizer,
|
||||||
ICacheService cache,
|
ICacheService cache,
|
||||||
RingService.RingServiceClient pusher,
|
RingService.RingServiceClient pusher,
|
||||||
@@ -54,7 +54,7 @@ public class PublisherSubscriptionService(
|
|||||||
{
|
{
|
||||||
if (post.RepliedPostId is not null)
|
if (post.RepliedPostId is not null)
|
||||||
return 0;
|
return 0;
|
||||||
if (post.Visibility != PostVisibility.Public)
|
if (post.Visibility != Shared.Models.PostVisibility.Public)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
// Create notification data
|
// Create notification data
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using DysonNetwork.Shared.Auth;
|
using DysonNetwork.Shared.Auth;
|
||||||
using DysonNetwork.Shared.Http;
|
using DysonNetwork.Shared.Http;
|
||||||
|
using DysonNetwork.Sphere.Post;
|
||||||
using DysonNetwork.Sphere.Publisher;
|
using DysonNetwork.Sphere.Publisher;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Startup;
|
namespace DysonNetwork.Sphere.Startup;
|
||||||
@@ -20,6 +21,7 @@ public static class ApplicationConfiguration
|
|||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
// Map gRPC services
|
// Map gRPC services
|
||||||
|
app.MapGrpcService<PostServiceGrpc>();
|
||||||
app.MapGrpcService<PublisherServiceGrpc>();
|
app.MapGrpcService<PublisherServiceGrpc>();
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public class PaymentOrderAwardMeta
|
|||||||
[JsonPropertyName("account_id")] public Guid AccountId { get; set; }
|
[JsonPropertyName("account_id")] public Guid AccountId { get; set; }
|
||||||
[JsonPropertyName("post_id")] public Guid PostId { get; set; }
|
[JsonPropertyName("post_id")] public Guid PostId { get; set; }
|
||||||
[JsonPropertyName("amount")] public string Amount { get; set; } = null!;
|
[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; }
|
[JsonPropertyName("message")] public string? Message { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ public class BroadcastEventHandler(
|
|||||||
logger.LogInformation("Handling post award order: {OrderId}", evt.OrderId);
|
logger.LogInformation("Handling post award order: {OrderId}", evt.OrderId);
|
||||||
|
|
||||||
await using var scope = serviceProvider.CreateAsyncScope();
|
await using var scope = serviceProvider.CreateAsyncScope();
|
||||||
var ps = scope.ServiceProvider.GetRequiredService<PostService>();
|
var ps = scope.ServiceProvider.GetRequiredService<Post.PostService>();
|
||||||
|
|
||||||
var amountNum = decimal.Parse(meta.Amount);
|
var amountNum = decimal.Parse(meta.Amount);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user