Compare commits
	
		
			2 Commits
		
	
	
		
			1024721e0e
			...
			c3095f2a9b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c3095f2a9b | |||
| 7656a8b298 | 
@@ -158,9 +158,18 @@ public class NotificationService
 | 
			
		||||
            var accounts = await _db.Accounts.ToListAsync();
 | 
			
		||||
            var notifications = accounts.Select(x =>
 | 
			
		||||
            {
 | 
			
		||||
                notification.Account = x;
 | 
			
		||||
                notification.AccountId = x.Id;
 | 
			
		||||
                return notification;
 | 
			
		||||
                var newNotification = new Notification
 | 
			
		||||
                {
 | 
			
		||||
                    Topic = notification.Topic,
 | 
			
		||||
                    Title = notification.Title,
 | 
			
		||||
                    Subtitle = notification.Subtitle,
 | 
			
		||||
                    Content = notification.Content,
 | 
			
		||||
                    Meta = notification.Meta,
 | 
			
		||||
                    Priority = notification.Priority,
 | 
			
		||||
                    Account = x,
 | 
			
		||||
                    AccountId = x.Id
 | 
			
		||||
                };
 | 
			
		||||
                return newNotification;
 | 
			
		||||
            }).ToList();
 | 
			
		||||
            await _db.BulkInsertAsync(notifications);
 | 
			
		||||
        }
 | 
			
		||||
@@ -183,9 +192,18 @@ public class NotificationService
 | 
			
		||||
        {
 | 
			
		||||
            var notifications = accounts.Select(x =>
 | 
			
		||||
            {
 | 
			
		||||
                notification.Account = x;
 | 
			
		||||
                notification.AccountId = x.Id;
 | 
			
		||||
                return notification;
 | 
			
		||||
                var newNotification = new Notification
 | 
			
		||||
                {
 | 
			
		||||
                    Topic = notification.Topic,
 | 
			
		||||
                    Title = notification.Title,
 | 
			
		||||
                    Subtitle = notification.Subtitle,
 | 
			
		||||
                    Content = notification.Content,
 | 
			
		||||
                    Meta = notification.Meta,
 | 
			
		||||
                    Priority = notification.Priority,
 | 
			
		||||
                    Account = x,
 | 
			
		||||
                    AccountId = x.Id
 | 
			
		||||
                };
 | 
			
		||||
                return newNotification;
 | 
			
		||||
            }).ToList();
 | 
			
		||||
            await _db.BulkInsertAsync(notifications);
 | 
			
		||||
        }
 | 
			
		||||
@@ -222,16 +240,22 @@ public class NotificationService
 | 
			
		||||
                            notification.Content ?? string.Empty).Trim();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    await _fcm.SendAsync(new
 | 
			
		||||
                    await _fcm.SendAsync(new Dictionary<string, object>
 | 
			
		||||
                    {
 | 
			
		||||
                        message = new
 | 
			
		||||
                        ["message"] = new Dictionary<string, object>
 | 
			
		||||
                        {
 | 
			
		||||
                            token = subscription.DeviceToken,
 | 
			
		||||
                            notification = new
 | 
			
		||||
                            ["token"] = subscription.DeviceToken,
 | 
			
		||||
                            ["notification"] = new Dictionary<string, object> 
 | 
			
		||||
                            {
 | 
			
		||||
                                title = notification.Title ?? string.Empty, body
 | 
			
		||||
                                ["title"] = notification.Title ?? string.Empty,
 | 
			
		||||
                                ["body"] = body
 | 
			
		||||
                            },
 | 
			
		||||
                            data = notification.Meta ?? new Dictionary<string, object>()
 | 
			
		||||
                            ["data"] = new Dictionary<string, object>
 | 
			
		||||
                            {
 | 
			
		||||
                                ["id"] = notification.Id,
 | 
			
		||||
                                ["topic"] = notification.Topic,
 | 
			
		||||
                                ["meta"] = notification.Meta ?? new Dictionary<string, object>()
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    break;
 | 
			
		||||
@@ -242,6 +266,7 @@ public class NotificationService
 | 
			
		||||
 | 
			
		||||
                    await _apns.SendAsync(new Dictionary<string, object>
 | 
			
		||||
                        {
 | 
			
		||||
                            ["topic"] = notification.Topic,
 | 
			
		||||
                            ["aps"] = new Dictionary<string, object>
 | 
			
		||||
                            {
 | 
			
		||||
                                ["alert"] = new Dictionary<string, object>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ using DysonNetwork.Sphere.Permission;
 | 
			
		||||
using DysonNetwork.Sphere.Publisher;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Design;
 | 
			
		||||
using Microsoft.EntityFrameworkCore.Query;
 | 
			
		||||
using NodaTime;
 | 
			
		||||
using Npgsql;
 | 
			
		||||
using Quartz;
 | 
			
		||||
@@ -49,7 +50,7 @@ public class AppDatabase(
 | 
			
		||||
    public DbSet<PublisherMember> PublisherMembers { get; set; }
 | 
			
		||||
    public DbSet<PublisherSubscription> PublisherSubscriptions { get; set; }
 | 
			
		||||
    public DbSet<PublisherFeature> PublisherFeatures { get; set; }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public DbSet<Post.Post> Posts { get; set; }
 | 
			
		||||
    public DbSet<Post.PostReaction> PostReactions { get; set; }
 | 
			
		||||
    public DbSet<Post.PostTag> PostTags { get; set; }
 | 
			
		||||
@@ -64,10 +65,10 @@ public class AppDatabase(
 | 
			
		||||
    public DbSet<Chat.Message> ChatMessages { get; set; }
 | 
			
		||||
    public DbSet<Chat.RealtimeCall> ChatRealtimeCall { get; set; }
 | 
			
		||||
    public DbSet<Chat.MessageReaction> ChatReactions { get; set; }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public DbSet<Sticker.Sticker> Stickers { get; set; }
 | 
			
		||||
    public DbSet<Sticker.StickerPack> StickerPacks { get; set; }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public DbSet<Wallet.Wallet> Wallets { get; set; }
 | 
			
		||||
    public DbSet<Wallet.WalletPocket> WalletPockets { get; set; }
 | 
			
		||||
    public DbSet<Wallet.Order> PaymentOrders { get; set; }
 | 
			
		||||
@@ -75,14 +76,14 @@ public class AppDatabase(
 | 
			
		||||
 | 
			
		||||
    public DbSet<Developer.CustomApp> CustomApps { get; set; }
 | 
			
		||||
    public DbSet<Developer.CustomAppSecret> CustomAppSecrets { get; set; }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
 | 
			
		||||
    {
 | 
			
		||||
        var dataSourceBuilder = new NpgsqlDataSourceBuilder(configuration.GetConnectionString("App"));
 | 
			
		||||
        dataSourceBuilder.EnableDynamicJson();
 | 
			
		||||
        dataSourceBuilder.UseNetTopologySuite();
 | 
			
		||||
        dataSourceBuilder.UseNodaTime();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if (configuration.GetValue<bool>("Debug"))
 | 
			
		||||
            optionsBuilder.EnableSensitiveDataLogging();
 | 
			
		||||
 | 
			
		||||
@@ -104,20 +105,20 @@ public class AppDatabase(
 | 
			
		||||
                {
 | 
			
		||||
                    Key = "default",
 | 
			
		||||
                    Nodes = new List<string>
 | 
			
		||||
                    {
 | 
			
		||||
                        "posts.create",
 | 
			
		||||
                        "posts.react", 
 | 
			
		||||
                        "publishers.create",
 | 
			
		||||
                        "files.create",
 | 
			
		||||
                        "chat.create",
 | 
			
		||||
                        "chat.messages.create",
 | 
			
		||||
                        "chat.realtime.create",
 | 
			
		||||
                        "accounts.statuses.create",
 | 
			
		||||
                        "accounts.statuses.update",
 | 
			
		||||
                        "stickers.packs.create",
 | 
			
		||||
                        "stickers.create"
 | 
			
		||||
                    }.Select(permission => 
 | 
			
		||||
                        PermissionService.NewPermissionNode("group:default", "global", permission, true))
 | 
			
		||||
                        {
 | 
			
		||||
                            "posts.create",
 | 
			
		||||
                            "posts.react",
 | 
			
		||||
                            "publishers.create",
 | 
			
		||||
                            "files.create",
 | 
			
		||||
                            "chat.create",
 | 
			
		||||
                            "chat.messages.create",
 | 
			
		||||
                            "chat.realtime.create",
 | 
			
		||||
                            "accounts.statuses.create",
 | 
			
		||||
                            "accounts.statuses.update",
 | 
			
		||||
                            "stickers.packs.create",
 | 
			
		||||
                            "stickers.create"
 | 
			
		||||
                        }.Select(permission =>
 | 
			
		||||
                            PermissionService.NewPermissionNode("group:default", "global", permission, true))
 | 
			
		||||
                        .ToList()
 | 
			
		||||
                });
 | 
			
		||||
                await context.SaveChangesAsync(cancellationToken);
 | 
			
		||||
@@ -359,4 +360,36 @@ public class AppDatabaseFactory : IDesignTimeDbContextFactory<AppDatabase>
 | 
			
		||||
        var optionsBuilder = new DbContextOptionsBuilder<AppDatabase>();
 | 
			
		||||
        return new AppDatabase(optionsBuilder.Options, configuration);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public static class OptionalQueryExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static IQueryable<T> If<T>(
 | 
			
		||||
        this IQueryable<T> source,
 | 
			
		||||
        bool condition,
 | 
			
		||||
        Func<IQueryable<T>, IQueryable<T>> transform
 | 
			
		||||
    )
 | 
			
		||||
    {
 | 
			
		||||
        return condition ? transform(source) : source;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public static IQueryable<T> If<T, TP>(
 | 
			
		||||
        this IIncludableQueryable<T, TP> source,
 | 
			
		||||
        bool condition,
 | 
			
		||||
        Func<IIncludableQueryable<T, TP>, IQueryable<T>> transform
 | 
			
		||||
    )
 | 
			
		||||
        where T : class
 | 
			
		||||
    {
 | 
			
		||||
        return condition ? transform(source) : source;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static IQueryable<T> If<T, TP>(
 | 
			
		||||
        this IIncludableQueryable<T, IEnumerable<TP>> source,
 | 
			
		||||
        bool condition,
 | 
			
		||||
        Func<IIncludableQueryable<T, IEnumerable<TP>>, IQueryable<T>> transform
 | 
			
		||||
    )
 | 
			
		||||
        where T : class
 | 
			
		||||
    {
 | 
			
		||||
        return condition ? transform(source) : source;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -66,14 +66,13 @@ public class ChatService(
 | 
			
		||||
        var scopedCrs = scope.ServiceProvider.GetRequiredService<ChatRoomService>();
 | 
			
		||||
 | 
			
		||||
        var roomSubject = room.Realm is not null ? $"{room.Name}, {room.Realm.Name}" : room.Name;
 | 
			
		||||
        var tasks = new List<Task>();
 | 
			
		||||
 | 
			
		||||
        var members = await scopedCrs.ListRoomMembers(room.Id);
 | 
			
		||||
 | 
			
		||||
        var notification = new Notification
 | 
			
		||||
        {
 | 
			
		||||
            Topic = "messages.new",
 | 
			
		||||
            Title = $"{sender.Nick ?? sender.Account?.Nick ?? "Unknown"} ({roomSubject})",
 | 
			
		||||
            Title = $"{sender.Nick ?? sender.Account.Nick ?? "Unknown"} ({roomSubject})",
 | 
			
		||||
            Content = !string.IsNullOrEmpty(message.Content)
 | 
			
		||||
                ? message.Content[..Math.Min(message.Content.Length, 100)]
 | 
			
		||||
                : "<attachments>",
 | 
			
		||||
@@ -106,9 +105,8 @@ public class ChatService(
 | 
			
		||||
        logger.LogInformation($"Trying to deliver message to {accountsToNotify.Count} accounts...");
 | 
			
		||||
        // Only send notifications if there are accounts to notify
 | 
			
		||||
        if (accountsToNotify.Count > 0)
 | 
			
		||||
            tasks.Add(scopedNty.SendNotificationBatch(notification, accountsToNotify, save: false));
 | 
			
		||||
            await scopedNty.SendNotificationBatch(notification, accountsToNotify, save: false);
 | 
			
		||||
 | 
			
		||||
        await Task.WhenAll(tasks);
 | 
			
		||||
        logger.LogInformation($"Delivered message to {accountsToNotify.Count} accounts.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ public class PostController(
 | 
			
		||||
            .FilterWithVisibility(currentUser, userFriends, isListing: true)
 | 
			
		||||
            .CountAsync();
 | 
			
		||||
        var posts = await query
 | 
			
		||||
            .Include(e => e.Publisher)
 | 
			
		||||
            .Include(e => e.RepliedPost)
 | 
			
		||||
            .Include(e => e.ThreadedPost)
 | 
			
		||||
            .Include(e => e.ForwardedPost)
 | 
			
		||||
            .Include(e => e.Attachments)
 | 
			
		||||
@@ -53,6 +53,7 @@ public class PostController(
 | 
			
		||||
            .Take(take)
 | 
			
		||||
            .ToListAsync();
 | 
			
		||||
        posts = PostService.TruncatePostContent(posts);
 | 
			
		||||
        posts = await ps.LoadPublishers(posts);
 | 
			
		||||
 | 
			
		||||
        var postsId = posts.Select(e => e.Id).ToList();
 | 
			
		||||
        var reactionMaps = await ps.GetPostReactionMapBatch(postsId);
 | 
			
		||||
@@ -106,7 +107,6 @@ public class PostController(
 | 
			
		||||
            .CountAsync();
 | 
			
		||||
        var posts = await db.Posts
 | 
			
		||||
            .Where(e => e.RepliedPostId == id)
 | 
			
		||||
            .Include(e => e.Publisher)
 | 
			
		||||
            .Include(e => e.ThreadedPost)
 | 
			
		||||
            .Include(e => e.ForwardedPost)
 | 
			
		||||
            .Include(e => e.Attachments)
 | 
			
		||||
@@ -118,6 +118,7 @@ public class PostController(
 | 
			
		||||
            .Take(take)
 | 
			
		||||
            .ToListAsync();
 | 
			
		||||
        posts = PostService.TruncatePostContent(posts);
 | 
			
		||||
        posts = await ps.LoadPublishers(posts);
 | 
			
		||||
 | 
			
		||||
        var postsId = posts.Select(e => e.Id).ToList();
 | 
			
		||||
        var reactionMaps = await ps.GetPostReactionMapBatch(postsId);
 | 
			
		||||
 
 | 
			
		||||
@@ -271,6 +271,47 @@ public class PostService(
 | 
			
		||||
                    )
 | 
			
		||||
            );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<List<Post>> LoadPublishers(List<Post> posts)
 | 
			
		||||
    {
 | 
			
		||||
        var publisherIds = posts
 | 
			
		||||
            .Where(e => e.Publisher.AccountId != null)
 | 
			
		||||
            .SelectMany<Post, Guid?>(e =>
 | 
			
		||||
            [
 | 
			
		||||
                e.Publisher.Id,
 | 
			
		||||
                e.RepliedPost?.Publisher.Id,
 | 
			
		||||
                e.ForwardedPost?.Publisher.Id,
 | 
			
		||||
                e.ThreadedPost?.Publisher.Id
 | 
			
		||||
            ])
 | 
			
		||||
            .Where(e => e != null)
 | 
			
		||||
            .Distinct()
 | 
			
		||||
            .ToList();
 | 
			
		||||
        if (publisherIds.Count == 0) return posts;
 | 
			
		||||
 | 
			
		||||
        var publishers = await db.Publishers
 | 
			
		||||
            .Where(e => e.AccountId != null && publisherIds.Contains(e.AccountId.Value))
 | 
			
		||||
            .ToDictionaryAsync(e => e.AccountId!.Value);
 | 
			
		||||
 | 
			
		||||
        foreach (var post in posts)
 | 
			
		||||
        {
 | 
			
		||||
            if (publishers.TryGetValue(post.Publisher.Id, out var publisher))
 | 
			
		||||
                post.Publisher = publisher;
 | 
			
		||||
 | 
			
		||||
            if (post.RepliedPost?.Publisher.Id != null &&
 | 
			
		||||
                publishers.TryGetValue(post.RepliedPost.Publisher.Id, out var repliedPublisher))
 | 
			
		||||
                post.RepliedPost.Publisher = repliedPublisher;
 | 
			
		||||
 | 
			
		||||
            if (post.ForwardedPost?.Publisher.Id != null && 
 | 
			
		||||
                publishers.TryGetValue(post.ForwardedPost.Publisher.Id, out var forwardedPublisher))
 | 
			
		||||
                post.ForwardedPost.Publisher = forwardedPublisher;
 | 
			
		||||
 | 
			
		||||
            if (post.ThreadedPost?.Publisher.Id != null &&
 | 
			
		||||
                publishers.TryGetValue(post.ThreadedPost.Publisher.Id, out var threadedPublisher))
 | 
			
		||||
                post.ThreadedPost.Publisher = threadedPublisher;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return posts;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public static class PostQueryExtensions
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@
 | 
			
		||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEntityFrameworkQueryableExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcb0587797ea44bd6915ede69888c6766291038_003F55_003F277f2d4c_003FEntityFrameworkQueryableExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
			
		||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEntityFrameworkServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F4a28847852ee9ba45fd3107526c0a749a733bd4f4ebf33aa3c9a59737a3f758_003FEntityFrameworkServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
			
		||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnumerable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F832399abc13b45b6bdbabfa022e4a28487e00_003F7f_003F7aece4dd_003FEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
			
		||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnumerable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fadcd336f9cde4e71998a851d7eb945bb87e00_003F0c_003F96dc130e_003FEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
			
		||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEvents_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003F20_003F86914b63_003FEvents_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
			
		||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F109293935a4844d5aa1610150b96edcde55000_003Fb7_003F8b7e5594_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
			
		||||
	<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExceptionDispatchInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fbf_003F44af6d95_003FExceptionDispatchInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user