♻️ Refactored activities
This commit is contained in:
parent
39533cced3
commit
b8341734df
@ -13,7 +13,6 @@ namespace DysonNetwork.Sphere.Account;
|
||||
|
||||
public class AccountEventService(
|
||||
AppDatabase db,
|
||||
ActivityService act,
|
||||
WebSocketService ws,
|
||||
ICacheService cache,
|
||||
PaymentService payment,
|
||||
@ -86,13 +85,6 @@ public class AccountEventService(
|
||||
db.AccountStatuses.Add(status);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
await act.CreateActivity(
|
||||
user,
|
||||
"accounts.status",
|
||||
$"account.statuses/{status.Id}",
|
||||
ActivityVisibility.Friends
|
||||
);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@ -220,13 +212,6 @@ public class AccountEventService(
|
||||
db.AccountCheckInResults.Add(result);
|
||||
await db.SaveChangesAsync(); // Don't forget to save changes to the database
|
||||
|
||||
await act.CreateActivity(
|
||||
user,
|
||||
"accounts.check-in",
|
||||
$"account.check-in/{result.Id}",
|
||||
ActivityVisibility.Friends
|
||||
);
|
||||
|
||||
// The lock will be automatically released by the await using statement
|
||||
return result;
|
||||
}
|
||||
|
@ -1,26 +1,37 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Activity;
|
||||
|
||||
public enum ActivityVisibility
|
||||
public interface IActivity
|
||||
{
|
||||
Public,
|
||||
Friends,
|
||||
Selected
|
||||
public Activity ToActivity();
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
public class Activity : ModelBase
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(1024)] public string Type { get; set; } = null!;
|
||||
[MaxLength(4096)] public string ResourceIdentifier { get; set; } = null!;
|
||||
public ActivityVisibility Visibility { get; set; } = ActivityVisibility.Public;
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object> Meta { get; set; } = new();
|
||||
[Column(TypeName = "jsonb")] public ICollection<Guid> UsersVisible { get; set; } = new List<Guid>();
|
||||
|
||||
public Guid AccountId { get; set; }
|
||||
public Account.Account Account { get; set; } = null!;
|
||||
public object? Data { get; set; }
|
||||
|
||||
[NotMapped] public object? Data { get; set; }
|
||||
// Outdated fields, for backward compability
|
||||
public int Visibility => 0;
|
||||
|
||||
public static Activity Empty()
|
||||
{
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
return new Activity
|
||||
{
|
||||
CreatedAt = now,
|
||||
UpdatedAt = now,
|
||||
Id = Guid.NewGuid(),
|
||||
Type = "empty",
|
||||
ResourceIdentifier = "none"
|
||||
};
|
||||
}
|
||||
}
|
@ -1,38 +1,38 @@
|
||||
using DysonNetwork.Sphere.Account;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Activity;
|
||||
|
||||
/// <summary>
|
||||
/// Activity is a universal feed that contains multiple kinds of data. Personalized and generated dynamically.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("/activities")]
|
||||
public class ActivityController(
|
||||
AppDatabase db,
|
||||
ActivityReaderService reader,
|
||||
RelationshipService rels) : ControllerBase
|
||||
ActivityService acts
|
||||
) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Listing the activities for the user, users may be logged in or not to use this API.
|
||||
/// When the users are not logged in, this API will return the posts that are public.
|
||||
/// When the users are logged in,
|
||||
/// the API will personalize the user's experience
|
||||
/// by ranking up the people they like and the posts they like.
|
||||
/// Besides, when users are logged in, it will also mix the other kinds of data and who're plying to them.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<List<Activity>>> ListActivities([FromQuery] int offset, [FromQuery] int take = 20)
|
||||
public async Task<ActionResult<List<Activity>>> ListActivities([FromQuery] int? cursor, [FromQuery] int take = 20)
|
||||
{
|
||||
var cursorTimestamp = cursor is <= 1000
|
||||
? SystemClock.Instance.GetCurrentInstant()
|
||||
: Instant.FromUnixTimeMilliseconds(cursor!.Value);
|
||||
|
||||
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
|
||||
var currentUser = currentUserValue as Account.Account;
|
||||
var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser);
|
||||
if (currentUserValue is not Account.Account currentUser)
|
||||
return Ok(await acts.GetActivitiesForAnyone(take, cursorTimestamp));
|
||||
|
||||
var totalCount = await db.Activities
|
||||
.FilterWithVisibility(currentUser, userFriends)
|
||||
.CountAsync();
|
||||
var activities = await db.Activities
|
||||
.Include(e => e.Account)
|
||||
.Include(e => e.Account.Profile)
|
||||
.FilterWithVisibility(currentUser, userFriends)
|
||||
.OrderByDescending(e => e.CreatedAt)
|
||||
.Skip(offset)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
activities = await reader.LoadActivityData(activities, currentUser, userFriends);
|
||||
|
||||
Response.Headers["X-Total"] = totalCount.ToString();
|
||||
|
||||
return Ok(activities);
|
||||
return Ok(await acts.GetActivities(take, cursorTimestamp, currentUser));
|
||||
}
|
||||
}
|
@ -1,173 +1,62 @@
|
||||
using DysonNetwork.Sphere.Account;
|
||||
using DysonNetwork.Sphere.Post;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Activity;
|
||||
|
||||
public class ActivityReaderService(AppDatabase db, PostService ps)
|
||||
public class ActivityService(AppDatabase db, RelationshipService rels)
|
||||
{
|
||||
public async Task<List<Activity>> LoadActivityData(List<Activity> input, Account.Account? currentUser,
|
||||
List<Guid> userFriends)
|
||||
public async Task<List<Activity>> GetActivitiesForAnyone(int take, Instant cursor)
|
||||
{
|
||||
if (input.Count == 0) return input;
|
||||
var activities = new List<Activity>();
|
||||
|
||||
var postsId = input
|
||||
.Where(e => e.ResourceIdentifier.StartsWith("posts/"))
|
||||
.Select(e => Guid.Parse(e.ResourceIdentifier.Split("/").Last()))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
if (postsId.Count > 0)
|
||||
{
|
||||
var posts = await db.Posts.Where(e => postsId.Contains(e.Id))
|
||||
// Crunching up data
|
||||
var posts = await db.Posts
|
||||
.Include(e => e.RepliedPost)
|
||||
.Include(e => e.ForwardedPost)
|
||||
.Include(e => e.Categories)
|
||||
.Include(e => e.Tags)
|
||||
.FilterWithVisibility(currentUser, userFriends)
|
||||
.Where(e => e.RepliedPostId == null)
|
||||
.Where(p => p.CreatedAt > cursor)
|
||||
.FilterWithVisibility(null, [], isListing: true)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
posts = PostService.TruncatePostContent(posts);
|
||||
posts = await ps.LoadPublishers(posts);
|
||||
|
||||
var reactionMaps = await ps.GetPostReactionMapBatch(postsId);
|
||||
// Formatting data
|
||||
foreach (var post in posts)
|
||||
post.ReactionsCount =
|
||||
reactionMaps.TryGetValue(post.Id, out var count) ? count : new Dictionary<string, int>();
|
||||
activities.Add(post.ToActivity());
|
||||
|
||||
var postsDict = posts.ToDictionary(p => p.Id);
|
||||
|
||||
foreach (var item in input)
|
||||
{
|
||||
var resourceIdentifier = item.ResourceIdentifier;
|
||||
if (!resourceIdentifier.StartsWith("posts/")) continue;
|
||||
var postId = Guid.Parse(resourceIdentifier.Split("/").Last());
|
||||
if (postsDict.TryGetValue(postId, out var post) && item.Data is null)
|
||||
{
|
||||
item.Data = post;
|
||||
}
|
||||
}
|
||||
return activities;
|
||||
}
|
||||
|
||||
var statusesId = input
|
||||
.Where(e => e.ResourceIdentifier.StartsWith("account.statuses/"))
|
||||
.Select(e => Guid.Parse(e.ResourceIdentifier.Split("/").Last()))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
if (statusesId.Count > 0)
|
||||
public async Task<List<Activity>> GetActivities(int take, Instant cursor, Account.Account currentUser)
|
||||
{
|
||||
var statuses = await db.AccountStatuses.Where(e => statusesId.Contains(e.Id))
|
||||
.Include(e => e.Account)
|
||||
.Include(e => e.Account.Profile)
|
||||
var activities = new List<Activity>();
|
||||
var userFriends = await rels.ListAccountFriends(currentUser);
|
||||
|
||||
// Crunching data
|
||||
var posts = await db.Posts
|
||||
.Include(e => e.RepliedPost)
|
||||
.Include(e => e.ForwardedPost)
|
||||
.Include(e => e.Categories)
|
||||
.Include(e => e.Tags)
|
||||
.Where(e => e.RepliedPostId == null || e.RepliedPostId == currentUser.Id)
|
||||
.Where(p => p.CreatedAt > cursor)
|
||||
.FilterWithVisibility(currentUser, userFriends, isListing: true)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
var statusesDict = statuses.ToDictionary(p => p.Id);
|
||||
|
||||
foreach (var item in input)
|
||||
// Formatting data
|
||||
foreach (var post in posts)
|
||||
activities.Add(post.ToActivity());
|
||||
|
||||
if (activities.Count == 0)
|
||||
{
|
||||
var resourceIdentifier = item.ResourceIdentifier;
|
||||
if (!resourceIdentifier.StartsWith("account.statuses/")) continue;
|
||||
var statusId = Guid.Parse(resourceIdentifier.Split("/").Last());
|
||||
if (statusesDict.TryGetValue(statusId, out var status) && item.Data is null)
|
||||
{
|
||||
item.Data = status;
|
||||
}
|
||||
}
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
activities.Add(Activity.Empty());
|
||||
}
|
||||
|
||||
var checkInId = input
|
||||
.Where(e => e.ResourceIdentifier.StartsWith("account.check-in/"))
|
||||
.Select(e => Guid.Parse(e.ResourceIdentifier.Split("/").Last()))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
if (checkInId.Count > 0)
|
||||
{
|
||||
var checkIns = await db.AccountCheckInResults.Where(e => checkInId.Contains(e.Id))
|
||||
.Include(e => e.Account)
|
||||
.Include(e => e.Account.Profile)
|
||||
.ToListAsync();
|
||||
var checkInsDict = checkIns.ToDictionary(p => p.Id);
|
||||
|
||||
foreach (var item in input)
|
||||
{
|
||||
var resourceIdentifier = item.ResourceIdentifier;
|
||||
if (!resourceIdentifier.StartsWith("account.check-in/")) continue;
|
||||
var checkInResultId = Guid.Parse(resourceIdentifier.Split("/").Last());
|
||||
if (checkInsDict.TryGetValue(checkInResultId, out var checkIn) && item.Data is null)
|
||||
{
|
||||
item.Data = checkIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
public class ActivityService(AppDatabase db)
|
||||
{
|
||||
public async Task<Activity> CreateActivity(
|
||||
Account.Account user,
|
||||
string type,
|
||||
string identifier,
|
||||
ActivityVisibility visibility = ActivityVisibility.Public,
|
||||
List<Guid>? visibleUsers = null
|
||||
)
|
||||
{
|
||||
var activity = new Activity
|
||||
{
|
||||
Type = type,
|
||||
ResourceIdentifier = identifier,
|
||||
Visibility = visibility,
|
||||
AccountId = user.Id,
|
||||
UsersVisible = visibleUsers ?? []
|
||||
};
|
||||
|
||||
db.Activities.Add(activity);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return activity;
|
||||
}
|
||||
|
||||
public async Task CreateNewPostActivity(Account.Account user, Post.Post post)
|
||||
{
|
||||
if (post.Visibility is PostVisibility.Unlisted or PostVisibility.Private) return;
|
||||
|
||||
var identifier = $"posts/{post.Id}";
|
||||
if (post.RepliedPostId is not null)
|
||||
{
|
||||
var ogPost = await db.Posts
|
||||
.Where(e => e.Id == post.RepliedPostId)
|
||||
.Include(e => e.Publisher)
|
||||
.FirstOrDefaultAsync();
|
||||
if (ogPost?.Publisher.AccountId == null) return;
|
||||
await CreateActivity(
|
||||
user,
|
||||
"posts.new.replies",
|
||||
identifier,
|
||||
ActivityVisibility.Selected,
|
||||
[ogPost.Publisher.AccountId!.Value]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await CreateActivity(
|
||||
user,
|
||||
"posts.new",
|
||||
identifier,
|
||||
post.Visibility == PostVisibility.Friends ? ActivityVisibility.Friends : ActivityVisibility.Public
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ActivityQueryExtensions
|
||||
{
|
||||
public static IQueryable<Activity> FilterWithVisibility(this IQueryable<Activity> source,
|
||||
Account.Account? currentUser, List<Guid> userFriends)
|
||||
{
|
||||
if (currentUser is null)
|
||||
return source.Where(e => e.Visibility == ActivityVisibility.Public);
|
||||
|
||||
return source
|
||||
.Where(e => e.Visibility != ActivityVisibility.Friends ||
|
||||
userFriends.Contains(e.AccountId) ||
|
||||
e.AccountId == currentUser.Id)
|
||||
.Where(e => e.Visibility != ActivityVisibility.Selected ||
|
||||
EF.Functions.JsonExists(e.UsersVisible, currentUser.Id.ToString()));
|
||||
return activities;
|
||||
}
|
||||
}
|
@ -50,8 +50,6 @@ public class AppDatabase(
|
||||
public DbSet<Storage.CloudFile> Files { get; set; }
|
||||
public DbSet<Storage.CloudFileReference> FileReferences { get; set; }
|
||||
|
||||
public DbSet<Activity.Activity> Activities { get; set; }
|
||||
|
||||
public DbSet<Publisher.Publisher> Publishers { get; set; }
|
||||
public DbSet<PublisherMember> PublisherMembers { get; set; }
|
||||
public DbSet<PublisherSubscription> PublisherSubscriptions { get; set; }
|
||||
|
3344
DysonNetwork.Sphere/Migrations/20250608152205_RemoveActivities.Designer.cs
generated
Normal file
3344
DysonNetwork.Sphere/Migrations/20250608152205_RemoveActivities.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Sphere.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveActivities : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "activities");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "activities",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: false),
|
||||
resource_identifier = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
type = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
users_visible = table.Column<ICollection<Guid>>(type: "jsonb", nullable: false),
|
||||
visibility = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_activities", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_activities_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_activities_account_id",
|
||||
table: "activities",
|
||||
column: "account_id");
|
||||
}
|
||||
}
|
||||
}
|
@ -727,64 +727,6 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
b.ToTable("account_statuses", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Sphere.Activity.Activity", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Dictionary<string, object>>("Meta")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("meta");
|
||||
|
||||
b.Property<string>("ResourceIdentifier")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("resource_identifier");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<ICollection<Guid>>("UsersVisible")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("users_visible");
|
||||
|
||||
b.Property<int>("Visibility")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("visibility");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_activities");
|
||||
|
||||
b.HasIndex("AccountId")
|
||||
.HasDatabaseName("ix_activities_account_id");
|
||||
|
||||
b.ToTable("activities", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -2795,18 +2737,6 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
b.Navigation("Account");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Sphere.Activity.Activity", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
||||
.WithMany()
|
||||
.HasForeignKey("AccountId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_activities_accounts_account_id");
|
||||
|
||||
b.Navigation("Account");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Sphere.Activity;
|
||||
using DysonNetwork.Sphere.Storage;
|
||||
using NodaTime;
|
||||
using NpgsqlTypes;
|
||||
@ -22,7 +23,7 @@ public enum PostVisibility
|
||||
Private
|
||||
}
|
||||
|
||||
public class Post : ModelBase, IIdentifiedResource
|
||||
public class Post : ModelBase, IIdentifiedResource, IActivity
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
[MaxLength(1024)] public string? Title { get; set; }
|
||||
@ -67,6 +68,20 @@ public class Post : ModelBase, IIdentifiedResource
|
||||
[NotMapped] public bool IsTruncated = false;
|
||||
|
||||
public string ResourceIdentifier => $"post/{Id}";
|
||||
|
||||
public Activity.Activity ToActivity()
|
||||
{
|
||||
return new Activity.Activity()
|
||||
{
|
||||
CreatedAt = CreatedAt,
|
||||
UpdatedAt = UpdatedAt,
|
||||
DeletedAt = DeletedAt,
|
||||
Id = Id,
|
||||
Type = RepliedPostId is null ? "posts.new" : "posts.new.replies",
|
||||
ResourceIdentifier = ResourceIdentifier,
|
||||
Data = null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class PostTag : ModelBase
|
||||
|
@ -13,7 +13,6 @@ public class PostService(
|
||||
AppDatabase db,
|
||||
FileService fs,
|
||||
FileReferenceService fileRefService,
|
||||
ActivityService act,
|
||||
IStringLocalizer<NotificationResource> localizer,
|
||||
NotificationService nty,
|
||||
IServiceScopeFactory factory
|
||||
@ -107,8 +106,6 @@ public class PostService(
|
||||
}
|
||||
}
|
||||
|
||||
await act.CreateNewPostActivity(user, post);
|
||||
|
||||
if (post.PublishedAt is not null && post.PublishedAt.Value.ToDateTimeUtc() <= DateTime.UtcNow)
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
@ -341,8 +338,12 @@ public class PostService(
|
||||
|
||||
public static class PostQueryExtensions
|
||||
{
|
||||
public static IQueryable<Post> FilterWithVisibility(this IQueryable<Post> source, Account.Account? currentUser,
|
||||
List<Guid> userFriends, bool isListing = false)
|
||||
public static IQueryable<Post> FilterWithVisibility(
|
||||
this IQueryable<Post> source,
|
||||
Account.Account? currentUser,
|
||||
List<Guid> userFriends,
|
||||
bool isListing = false
|
||||
)
|
||||
{
|
||||
var now = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.RateLimiting;
|
||||
using DysonNetwork.Sphere;
|
||||
@ -22,23 +20,18 @@ using DysonNetwork.Sphere.Storage;
|
||||
using DysonNetwork.Sphere.Storage.Handlers;
|
||||
using DysonNetwork.Sphere.Wallet;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.SystemTextJson;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Trace;
|
||||
using Prometheus;
|
||||
using Prometheus.DotNetRuntime;
|
||||
using Prometheus.SystemMetrics;
|
||||
using Quartz;
|
||||
using StackExchange.Redis;
|
||||
using tusdotnet;
|
||||
using tusdotnet.Models;
|
||||
using tusdotnet.Models.Configuration;
|
||||
using tusdotnet.Stores;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
@ -213,7 +206,6 @@ builder.Services.AddScoped<FileReferenceMigrationService>();
|
||||
builder.Services.AddScoped<PublisherService>();
|
||||
builder.Services.AddScoped<PublisherSubscriptionService>();
|
||||
builder.Services.AddScoped<ActivityService>();
|
||||
builder.Services.AddScoped<ActivityReaderService>();
|
||||
builder.Services.AddScoped<PostService>();
|
||||
builder.Services.AddScoped<RealmService>();
|
||||
builder.Services.AddScoped<ChatRoomService>();
|
||||
|
Loading…
x
Reference in New Issue
Block a user