using DysonNetwork.Sphere.Post; using DysonNetwork.Sphere.Storage; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using NodaTime; namespace DysonNetwork.Sphere.Publisher; public class PublisherService(AppDatabase db, FileService fs, IMemoryCache cache) { public async Task CreateIndividualPublisher( Account.Account account, string? name, string? nick, string? bio, CloudFile? picture, CloudFile? background ) { var publisher = new Publisher { Type = PublisherType.Individual, Name = name ?? account.Name, Nick = nick ?? account.Nick, Bio = bio ?? account.Profile.Bio, Picture = picture ?? account.Profile.Picture, Background = background ?? account.Profile.Background, AccountId = account.Id, Members = new List { new() { AccountId = account.Id, Role = PublisherMemberRole.Owner, JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow) } } }; db.Publishers.Add(publisher); await db.SaveChangesAsync(); if (publisher.Picture is not null) await fs.MarkUsageAsync(publisher.Picture, 1); if (publisher.Background is not null) await fs.MarkUsageAsync(publisher.Background, 1); return publisher; } public async Task CreateOrganizationPublisher( Realm.Realm realm, Account.Account account, string? name, string? nick, string? bio, CloudFile? picture, CloudFile? background ) { var publisher = new Publisher { Type = PublisherType.Organizational, Name = name ?? realm.Slug, Nick = nick ?? realm.Name, Bio = bio ?? realm.Description, Picture = picture ?? realm.Picture, Background = background ?? realm.Background, RealmId = realm.Id, Members = new List { new() { AccountId = account.Id, Role = PublisherMemberRole.Owner, JoinedAt = Instant.FromDateTimeUtc(DateTime.UtcNow) } } }; db.Publishers.Add(publisher); await db.SaveChangesAsync(); if (publisher.Picture is not null) await fs.MarkUsageAsync(publisher.Picture, 1); if (publisher.Background is not null) await fs.MarkUsageAsync(publisher.Background, 1); return publisher; } public class PublisherStats { public int PostsCreated { get; set; } public int StickerPacksCreated { get; set; } public int StickersCreated { get; set; } public int UpvoteReceived { get; set; } public int DownvoteReceived { get; set; } public int SubscribersCount { get; set; } } private const string PublisherStatsCacheKey = "PublisherStats_{0}"; private const string PublisherFeatureCacheKey = "PublisherFeature_{0}_{1}"; public async Task GetPublisherStats(string name) { var cacheKey = string.Format(PublisherStatsCacheKey, name); if (cache.TryGetValue(cacheKey, out PublisherStats? stats)) return stats; var publisher = await db.Publishers.FirstOrDefaultAsync(e => e.Name == name); if (publisher is null) return null; var postsCount = await db.Posts.Where(e => e.Publisher.Id == publisher.Id).CountAsync(); var postsUpvotes = await db.PostReactions .Where(r => r.Post.Publisher.Id == publisher.Id && r.Attitude == PostReactionAttitude.Positive) .CountAsync(); var postsDownvotes = await db.PostReactions .Where(r => r.Post.Publisher.Id == publisher.Id && r.Attitude == PostReactionAttitude.Negative) .CountAsync(); var stickerPacksId = await db.StickerPacks.Where(e => e.Publisher.Id == publisher.Id).Select(e => e.Id) .ToListAsync(); var stickerPacksCount = stickerPacksId.Count; var stickersCount = await db.Stickers.Where(e => stickerPacksId.Contains(e.PackId)).CountAsync(); var subscribersCount = await db.PublisherSubscriptions.Where(e => e.PublisherId == publisher.Id).CountAsync(); stats = new PublisherStats { PostsCreated = postsCount, StickerPacksCreated = stickerPacksCount, StickersCreated = stickersCount, UpvoteReceived = postsUpvotes, DownvoteReceived = postsDownvotes, SubscribersCount = subscribersCount, }; cache.Set(cacheKey, stats, TimeSpan.FromMinutes(5)); return stats; } public async Task SetFeatureFlag(Guid publisherId, string flag) { var featureFlag = await db.PublisherFeatures .FirstOrDefaultAsync(f => f.PublisherId == publisherId && f.Flag == flag); if (featureFlag == null) { featureFlag = new PublisherFeature { PublisherId = publisherId, Flag = flag, }; db.PublisherFeatures.Add(featureFlag); } else { featureFlag.ExpiredAt = SystemClock.Instance.GetCurrentInstant(); } await db.SaveChangesAsync(); cache.Remove(string.Format(PublisherFeatureCacheKey, publisherId, flag)); } public async Task HasFeature(Guid publisherId, string flag) { var cacheKey = string.Format(PublisherFeatureCacheKey, publisherId, flag); if (cache.TryGetValue(cacheKey, out bool isEnabled)) return isEnabled; var now = SystemClock.Instance.GetCurrentInstant(); var featureFlag = await db.PublisherFeatures .FirstOrDefaultAsync(f => f.PublisherId == publisherId && f.Flag == flag && (f.ExpiredAt == null || f.ExpiredAt > now) ); if (featureFlag is not null) isEnabled = true; cache.Set(cacheKey, isEnabled, TimeSpan.FromMinutes(5)); return isEnabled; } public async Task IsMemberWithRole(Guid publisherId, Guid accountId, PublisherMemberRole requiredRole) { var member = await db.Publishers .Where(p => p.Id == publisherId) .SelectMany(p => p.Members) .FirstOrDefaultAsync(m => m.AccountId == accountId); return member != null && member.Role >= requiredRole; } }