♻️ Refactored activities
This commit is contained in:
parent
39533cced3
commit
b8341734df
@ -13,7 +13,6 @@ namespace DysonNetwork.Sphere.Account;
|
|||||||
|
|
||||||
public class AccountEventService(
|
public class AccountEventService(
|
||||||
AppDatabase db,
|
AppDatabase db,
|
||||||
ActivityService act,
|
|
||||||
WebSocketService ws,
|
WebSocketService ws,
|
||||||
ICacheService cache,
|
ICacheService cache,
|
||||||
PaymentService payment,
|
PaymentService payment,
|
||||||
@ -86,13 +85,6 @@ public class AccountEventService(
|
|||||||
db.AccountStatuses.Add(status);
|
db.AccountStatuses.Add(status);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
await act.CreateActivity(
|
|
||||||
user,
|
|
||||||
"accounts.status",
|
|
||||||
$"account.statuses/{status.Id}",
|
|
||||||
ActivityVisibility.Friends
|
|
||||||
);
|
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,13 +212,6 @@ public class AccountEventService(
|
|||||||
db.AccountCheckInResults.Add(result);
|
db.AccountCheckInResults.Add(result);
|
||||||
await db.SaveChangesAsync(); // Don't forget to save changes to the database
|
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
|
// The lock will be automatically released by the await using statement
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,37 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Activity;
|
namespace DysonNetwork.Sphere.Activity;
|
||||||
|
|
||||||
public enum ActivityVisibility
|
public interface IActivity
|
||||||
{
|
{
|
||||||
Public,
|
public Activity ToActivity();
|
||||||
Friends,
|
|
||||||
Selected
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[NotMapped]
|
||||||
public class Activity : ModelBase
|
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(1024)] public string Type { get; set; } = null!;
|
||||||
[MaxLength(4096)] public string ResourceIdentifier { 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 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 object? Data { get; set; }
|
||||||
public Account.Account Account { get; set; } = null!;
|
|
||||||
|
|
||||||
[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 DysonNetwork.Sphere.Account;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Activity;
|
namespace DysonNetwork.Sphere.Activity;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Activity is a universal feed that contains multiple kinds of data. Personalized and generated dynamically.
|
||||||
|
/// </summary>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/activities")]
|
[Route("/activities")]
|
||||||
public class ActivityController(
|
public class ActivityController(
|
||||||
AppDatabase db,
|
ActivityService acts
|
||||||
ActivityReaderService reader,
|
) : ControllerBase
|
||||||
RelationshipService rels) : 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]
|
[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);
|
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
|
||||||
var currentUser = currentUserValue as Account.Account;
|
if (currentUserValue is not Account.Account currentUser)
|
||||||
var userFriends = currentUser is null ? [] : await rels.ListAccountFriends(currentUser);
|
return Ok(await acts.GetActivitiesForAnyone(take, cursorTimestamp));
|
||||||
|
|
||||||
var totalCount = await db.Activities
|
return Ok(await acts.GetActivities(take, cursorTimestamp, currentUser));
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,173 +1,62 @@
|
|||||||
|
using DysonNetwork.Sphere.Account;
|
||||||
using DysonNetwork.Sphere.Post;
|
using DysonNetwork.Sphere.Post;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Activity;
|
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,
|
public async Task<List<Activity>> GetActivitiesForAnyone(int take, Instant cursor)
|
||||||
List<Guid> userFriends)
|
|
||||||
{
|
{
|
||||||
if (input.Count == 0) return input;
|
var activities = new List<Activity>();
|
||||||
|
|
||||||
|
// Crunching up 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)
|
||||||
|
.Where(p => p.CreatedAt > cursor)
|
||||||
|
.FilterWithVisibility(null, [], isListing: true)
|
||||||
|
.Take(take)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
// Formatting data
|
||||||
|
foreach (var post in posts)
|
||||||
|
activities.Add(post.ToActivity());
|
||||||
|
|
||||||
var postsId = input
|
return activities;
|
||||||
.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))
|
|
||||||
.Include(e => e.ForwardedPost)
|
|
||||||
.Include(e => e.Categories)
|
|
||||||
.Include(e => e.Tags)
|
|
||||||
.FilterWithVisibility(currentUser, userFriends)
|
|
||||||
.ToListAsync();
|
|
||||||
posts = PostService.TruncatePostContent(posts);
|
|
||||||
posts = await ps.LoadPublishers(posts);
|
|
||||||
|
|
||||||
var reactionMaps = await ps.GetPostReactionMapBatch(postsId);
|
|
||||||
foreach (var post in posts)
|
|
||||||
post.ReactionsCount =
|
|
||||||
reactionMaps.TryGetValue(post.Id, out var count) ? count : new Dictionary<string, int>();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var statusesId = input
|
|
||||||
.Where(e => e.ResourceIdentifier.StartsWith("account.statuses/"))
|
|
||||||
.Select(e => Guid.Parse(e.ResourceIdentifier.Split("/").Last()))
|
|
||||||
.Distinct()
|
|
||||||
.ToList();
|
|
||||||
if (statusesId.Count > 0)
|
|
||||||
{
|
|
||||||
var statuses = await db.AccountStatuses.Where(e => statusesId.Contains(e.Id))
|
|
||||||
.Include(e => e.Account)
|
|
||||||
.Include(e => e.Account.Profile)
|
|
||||||
.ToListAsync();
|
|
||||||
var statusesDict = statuses.ToDictionary(p => p.Id);
|
|
||||||
|
|
||||||
foreach (var item in input)
|
|
||||||
{
|
|
||||||
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 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 async Task<List<Activity>> GetActivities(int take, Instant cursor, Account.Account currentUser)
|
||||||
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
|
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();
|
||||||
|
|
||||||
|
// Formatting data
|
||||||
|
foreach (var post in posts)
|
||||||
|
activities.Add(post.ToActivity());
|
||||||
|
|
||||||
|
if (activities.Count == 0)
|
||||||
{
|
{
|
||||||
Type = type,
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
ResourceIdentifier = identifier,
|
activities.Add(Activity.Empty());
|
||||||
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(
|
return activities;
|
||||||
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()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -50,8 +50,6 @@ public class AppDatabase(
|
|||||||
public DbSet<Storage.CloudFile> Files { get; set; }
|
public DbSet<Storage.CloudFile> Files { get; set; }
|
||||||
public DbSet<Storage.CloudFileReference> FileReferences { 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<Publisher.Publisher> Publishers { get; set; }
|
||||||
public DbSet<PublisherMember> PublisherMembers { get; set; }
|
public DbSet<PublisherMember> PublisherMembers { get; set; }
|
||||||
public DbSet<PublisherSubscription> PublisherSubscriptions { 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);
|
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 =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@ -2795,18 +2737,6 @@ namespace DysonNetwork.Sphere.Migrations
|
|||||||
b.Navigation("Account");
|
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 =>
|
modelBuilder.Entity("DysonNetwork.Sphere.Auth.Challenge", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
b.HasOne("DysonNetwork.Sphere.Account.Account", "Account")
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
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.Sphere.Activity;
|
||||||
using DysonNetwork.Sphere.Storage;
|
using DysonNetwork.Sphere.Storage;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using NpgsqlTypes;
|
using NpgsqlTypes;
|
||||||
@ -22,7 +23,7 @@ public enum PostVisibility
|
|||||||
Private
|
Private
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Post : ModelBase, IIdentifiedResource
|
public class Post : ModelBase, IIdentifiedResource, IActivity
|
||||||
{
|
{
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
[MaxLength(1024)] public string? Title { get; set; }
|
[MaxLength(1024)] public string? Title { get; set; }
|
||||||
@ -48,7 +49,7 @@ public class Post : ModelBase, IIdentifiedResource
|
|||||||
public Post? RepliedPost { get; set; }
|
public Post? RepliedPost { get; set; }
|
||||||
public Guid? ForwardedPostId { get; set; }
|
public Guid? ForwardedPostId { get; set; }
|
||||||
public Post? ForwardedPost { get; set; }
|
public Post? ForwardedPost { get; set; }
|
||||||
|
|
||||||
// Outdated fields, keep for backward compability
|
// Outdated fields, keep for backward compability
|
||||||
public ICollection<CloudFile> OutdatedAttachments { get; set; } = new List<CloudFile>();
|
public ICollection<CloudFile> OutdatedAttachments { get; set; } = new List<CloudFile>();
|
||||||
[Column(TypeName = "jsonb")] public List<CloudFileReferenceObject> Attachments { get; set; } = [];
|
[Column(TypeName = "jsonb")] public List<CloudFileReferenceObject> Attachments { get; set; } = [];
|
||||||
@ -67,6 +68,20 @@ public class Post : ModelBase, IIdentifiedResource
|
|||||||
[NotMapped] public bool IsTruncated = false;
|
[NotMapped] public bool IsTruncated = false;
|
||||||
|
|
||||||
public string ResourceIdentifier => $"post/{Id}";
|
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
|
public class PostTag : ModelBase
|
||||||
|
@ -13,7 +13,6 @@ public class PostService(
|
|||||||
AppDatabase db,
|
AppDatabase db,
|
||||||
FileService fs,
|
FileService fs,
|
||||||
FileReferenceService fileRefService,
|
FileReferenceService fileRefService,
|
||||||
ActivityService act,
|
|
||||||
IStringLocalizer<NotificationResource> localizer,
|
IStringLocalizer<NotificationResource> localizer,
|
||||||
NotificationService nty,
|
NotificationService nty,
|
||||||
IServiceScopeFactory factory
|
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)
|
if (post.PublishedAt is not null && post.PublishedAt.Value.ToDateTimeUtc() <= DateTime.UtcNow)
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
@ -341,8 +338,12 @@ public class PostService(
|
|||||||
|
|
||||||
public static class PostQueryExtensions
|
public static class PostQueryExtensions
|
||||||
{
|
{
|
||||||
public static IQueryable<Post> FilterWithVisibility(this IQueryable<Post> source, Account.Account? currentUser,
|
public static IQueryable<Post> FilterWithVisibility(
|
||||||
List<Guid> userFriends, bool isListing = false)
|
this IQueryable<Post> source,
|
||||||
|
Account.Account? currentUser,
|
||||||
|
List<Guid> userFriends,
|
||||||
|
bool isListing = false
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var now = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
var now = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||||
|
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Net;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.RateLimiting;
|
using System.Threading.RateLimiting;
|
||||||
using DysonNetwork.Sphere;
|
using DysonNetwork.Sphere;
|
||||||
@ -22,23 +20,18 @@ using DysonNetwork.Sphere.Storage;
|
|||||||
using DysonNetwork.Sphere.Storage.Handlers;
|
using DysonNetwork.Sphere.Storage.Handlers;
|
||||||
using DysonNetwork.Sphere.Wallet;
|
using DysonNetwork.Sphere.Wallet;
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.RateLimiting;
|
using Microsoft.AspNetCore.RateLimiting;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
using NodaTime.Serialization.SystemTextJson;
|
using NodaTime.Serialization.SystemTextJson;
|
||||||
using OpenTelemetry.Metrics;
|
using OpenTelemetry.Metrics;
|
||||||
using OpenTelemetry.Trace;
|
using OpenTelemetry.Trace;
|
||||||
using Prometheus;
|
using Prometheus;
|
||||||
using Prometheus.DotNetRuntime;
|
|
||||||
using Prometheus.SystemMetrics;
|
using Prometheus.SystemMetrics;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
using StackExchange.Redis;
|
using StackExchange.Redis;
|
||||||
using tusdotnet;
|
using tusdotnet;
|
||||||
using tusdotnet.Models;
|
|
||||||
using tusdotnet.Models.Configuration;
|
|
||||||
using tusdotnet.Stores;
|
using tusdotnet.Stores;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
@ -213,7 +206,6 @@ builder.Services.AddScoped<FileReferenceMigrationService>();
|
|||||||
builder.Services.AddScoped<PublisherService>();
|
builder.Services.AddScoped<PublisherService>();
|
||||||
builder.Services.AddScoped<PublisherSubscriptionService>();
|
builder.Services.AddScoped<PublisherSubscriptionService>();
|
||||||
builder.Services.AddScoped<ActivityService>();
|
builder.Services.AddScoped<ActivityService>();
|
||||||
builder.Services.AddScoped<ActivityReaderService>();
|
|
||||||
builder.Services.AddScoped<PostService>();
|
builder.Services.AddScoped<PostService>();
|
||||||
builder.Services.AddScoped<RealmService>();
|
builder.Services.AddScoped<RealmService>();
|
||||||
builder.Services.AddScoped<ChatRoomService>();
|
builder.Services.AddScoped<ChatRoomService>();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user