diff --git a/Iceshrimp.Backend/Core/Extensions/DistributedCacheExtensions.cs b/Iceshrimp.Backend/Core/Extensions/DistributedCacheExtensions.cs new file mode 100644 index 00000000..a3dd6bde --- /dev/null +++ b/Iceshrimp.Backend/Core/Extensions/DistributedCacheExtensions.cs @@ -0,0 +1,38 @@ +using System.Text.Json; +using Microsoft.Extensions.Caching.Distributed; + +namespace Iceshrimp.Backend.Core.Extensions; + +public static class DistributedCacheExtensions { + //TODO: named caches, CacheService? + //TODO: thread-safe locks to prevent fetching data more than once + + public static async Task Get(this IDistributedCache cache, string key) { + var buffer = await cache.GetAsync(key); + if (buffer == null) return default; + + var stream = new MemoryStream(buffer); + var data = await JsonSerializer.DeserializeAsync(stream); + + return data != null ? (T)data : default; + } + + public static async Task Fetch(this IDistributedCache cache, string key, TimeSpan ttl, + Func> fetcher) { + var hit = await cache.Get(key); + if (hit != null) return hit; + + var fetched = await fetcher(); + await cache.Set(key, fetched, ttl); + return fetched; + } + + public static async Task Set(this IDistributedCache cache, string key, T data, TimeSpan ttl) { + using var ms = new MemoryStream(); + await JsonSerializer.SerializeAsync(ms, data); + var buffer = new Memory(); + _ = await ms.ReadAsync(buffer); + var options = new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = ttl }; + await cache.SetAsync(key, buffer.ToArray(), options); + } +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Services/UserService.cs b/Iceshrimp.Backend/Core/Services/UserService.cs index f6ee51c5..77e8ec37 100644 --- a/Iceshrimp.Backend/Core/Services/UserService.cs +++ b/Iceshrimp.Backend/Core/Services/UserService.cs @@ -9,6 +9,7 @@ using Iceshrimp.Backend.Core.Federation.ActivityPub; using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Middleware; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Options; namespace Iceshrimp.Backend.Core.Services; @@ -19,7 +20,9 @@ public class UserService( IOptions instance, ILogger logger, DatabaseContext db, - ActivityFetcherService fetchSvc) { + ActivityFetcherService fetchSvc, + IDistributedCache cache +) { private (string Username, string? Host) AcctToTuple(string acct) { if (!acct.StartsWith("acct:")) throw new GracefulException(HttpStatusCode.BadRequest, "Invalid query"); @@ -161,10 +164,13 @@ public class UserService( return await GetOrCreateSystemUser("relay.actor"); } - //TODO: cache in redis private async Task GetOrCreateSystemUser(string username) { - return await db.Users.FirstOrDefaultAsync(p => p.UsernameLower == username && p.Host == null) ?? - await CreateSystemUser(username); + return await cache.Fetch($"systemUser:{username}", TimeSpan.FromHours(24), async () => { + logger.LogTrace("GetOrCreateSystemUser delegate method called for user {username}", username); + return await db.Users.FirstOrDefaultAsync(p => p.UsernameLower == username && + p.Host == null) ?? + await CreateSystemUser(username); + }); } private async Task CreateSystemUser(string username) {