[backend/asp] Make services runtime-discoverable

This commit is contained in:
Laura Hausmann 2024-11-17 04:20:27 +01:00
parent 705e061f74
commit 4356a47b9d
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
56 changed files with 255 additions and 213 deletions

View file

@ -1,9 +1,11 @@
using AngleSharp.Io; using AngleSharp.Io;
using Iceshrimp.Backend.Core.Extensions;
using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Matching;
namespace Iceshrimp.Backend.Components.PublicPreview.Attributes; namespace Iceshrimp.Backend.Components.PublicPreview.Attributes;
public class PublicPreviewRouteMatcher : MatcherPolicy, IEndpointSelectorPolicy public class PublicPreviewRouteMatcher : MatcherPolicy, IEndpointSelectorPolicy, ISingletonService,
IService<MatcherPolicy>
{ {
public override int Order => 99999; // That's ActionConstraintMatcherPolicy - 1 public override int Order => 99999; // That's ActionConstraintMatcherPolicy - 1

View file

@ -1,5 +1,6 @@
using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion; using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
using Iceshrimp.Parsing; using Iceshrimp.Parsing;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@ -7,7 +8,7 @@ using Microsoft.Extensions.Options;
namespace Iceshrimp.Backend.Components.PublicPreview.Renderers; namespace Iceshrimp.Backend.Components.PublicPreview.Renderers;
public class MfmRenderer(IOptions<Config.InstanceSection> config) public class MfmRenderer(IOptions<Config.InstanceSection> config) : ISingletonService
{ {
private readonly MfmConverter _converter = new(config); private readonly MfmConverter _converter = new(config);

View file

@ -14,7 +14,7 @@ public class NoteRenderer(
MfmRenderer mfm, MfmRenderer mfm,
IOptions<Config.InstanceSection> instance, IOptions<Config.InstanceSection> instance,
IOptionsSnapshot<Config.SecuritySection> security IOptionsSnapshot<Config.SecuritySection> security
) ) : IScopedService
{ {
public async Task<PreviewNote?> RenderOne(Note? note) public async Task<PreviewNote?> RenderOne(Note? note)
{ {

View file

@ -13,7 +13,7 @@ public class UserRenderer(
MfmRenderer mfm, MfmRenderer mfm,
IOptions<Config.InstanceSection> instance, IOptions<Config.InstanceSection> instance,
IOptionsSnapshot<Config.SecuritySection> security IOptionsSnapshot<Config.SecuritySection> security
) ) : IScopedService
{ {
public async Task<PreviewUser?> RenderOne(User? user) public async Task<PreviewUser?> RenderOne(User? user)
{ {

View file

@ -29,7 +29,7 @@ public class ActivityPubController(
ActivityPub.NoteRenderer noteRenderer, ActivityPub.NoteRenderer noteRenderer,
ActivityPub.UserRenderer userRenderer, ActivityPub.UserRenderer userRenderer,
IOptions<Config.InstanceSection> config IOptions<Config.InstanceSection> config
) : ControllerBase ) : ControllerBase, IScopedService
{ {
[HttpGet("/notes/{id}")] [HttpGet("/notes/{id}")]
[AuthorizedFetch] [AuthorizedFetch]

View file

@ -20,7 +20,7 @@ public class NoteRenderer(
MfmConverter mfmConverter, MfmConverter mfmConverter,
DatabaseContext db, DatabaseContext db,
EmojiService emojiSvc EmojiService emojiSvc
) ) : IScopedService
{ {
private static readonly FilterResultEntity InaccessibleFilter = new() private static readonly FilterResultEntity InaccessibleFilter = new()
{ {

View file

@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Controllers.Mastodon.Renderers; namespace Iceshrimp.Backend.Controllers.Mastodon.Renderers;
public class NotificationRenderer(DatabaseContext db, NoteRenderer noteRenderer, UserRenderer userRenderer) public class NotificationRenderer(DatabaseContext db, NoteRenderer noteRenderer, UserRenderer userRenderer) : IScopedService
{ {
public async Task<NotificationEntity> RenderAsync( public async Task<NotificationEntity> RenderAsync(
Notification notification, User user, bool isPleroma, List<AccountEntity>? accounts = null, Notification notification, User user, bool isPleroma, List<AccountEntity>? accounts = null,

View file

@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Controllers.Mastodon.Renderers; namespace Iceshrimp.Backend.Controllers.Mastodon.Renderers;
public class PollRenderer(DatabaseContext db) public class PollRenderer(DatabaseContext db) : IScopedService
{ {
public async Task<PollEntity> RenderAsync(Poll poll, User? user, PollRendererDto? data = null) public async Task<PollEntity> RenderAsync(Poll poll, User? user, PollRendererDto? data = null)
{ {

View file

@ -14,7 +14,7 @@ public class UserRenderer(
IOptionsSnapshot<Config.SecuritySection> security, IOptionsSnapshot<Config.SecuritySection> security,
MfmConverter mfmConverter, MfmConverter mfmConverter,
DatabaseContext db DatabaseContext db
) ) : IScopedService
{ {
private readonly string _transparent = $"https://{config.Value.WebDomain}/assets/transparent.png"; private readonly string _transparent = $"https://{config.Value.WebDomain}/assets/transparent.png";

View file

@ -15,7 +15,7 @@ public class NoteRenderer(
DatabaseContext db, DatabaseContext db,
EmojiService emojiSvc, EmojiService emojiSvc,
IOptions<Config.InstanceSection> config IOptions<Config.InstanceSection> config
) ) : IScopedService
{ {
public async Task<NoteResponse> RenderOne( public async Task<NoteResponse> RenderOne(
Note note, User? user, Filter.FilterContext? filterContext = null, NoteRendererDto? data = null Note note, User? user, Filter.FilterContext? filterContext = null, NoteRendererDto? data = null

View file

@ -5,7 +5,7 @@ using static Iceshrimp.Shared.Schemas.Web.NotificationResponse;
namespace Iceshrimp.Backend.Controllers.Web.Renderers; namespace Iceshrimp.Backend.Controllers.Web.Renderers;
public class NotificationRenderer(UserRenderer userRenderer, NoteRenderer noteRenderer) public class NotificationRenderer(UserRenderer userRenderer, NoteRenderer noteRenderer) : IScopedService
{ {
private static NotificationResponse Render(Notification notification, NotificationRendererDto data) private static NotificationResponse Render(Notification notification, NotificationRendererDto data)
{ {

View file

@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Controllers.Web.Renderers; namespace Iceshrimp.Backend.Controllers.Web.Renderers;
public class UserProfileRenderer(DatabaseContext db) public class UserProfileRenderer(DatabaseContext db) : IScopedService
{ {
public async Task<UserProfileResponse> RenderOne(User user, User? localUser, UserRendererDto? data = null) public async Task<UserProfileResponse> RenderOne(User user, User? localUser, UserRendererDto? data = null)
{ {

View file

@ -1,13 +1,14 @@
using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Shared.Schemas.Web; using Iceshrimp.Shared.Schemas.Web;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Iceshrimp.Backend.Controllers.Web.Renderers; namespace Iceshrimp.Backend.Controllers.Web.Renderers;
public class UserRenderer(IOptions<Config.InstanceSection> config, DatabaseContext db) public class UserRenderer(IOptions<Config.InstanceSection> config, DatabaseContext db) : IScopedService
{ {
private UserResponse Render(User user, UserRendererDto data) private UserResponse Render(User user, UserRendererDto data)
{ {

View file

@ -3,19 +3,10 @@ using System.Reflection;
using System.Threading.RateLimiting; using System.Threading.RateLimiting;
using System.Xml.Linq; using System.Xml.Linq;
using Iceshrimp.AssemblyUtils; using Iceshrimp.AssemblyUtils;
using Iceshrimp.Backend.Components.PublicPreview.Attributes;
using Iceshrimp.Backend.Components.PublicPreview.Renderers;
using Iceshrimp.Backend.Controllers.Federation;
using Iceshrimp.Backend.Controllers.Mastodon.Renderers;
using Iceshrimp.Backend.Controllers.Web.Renderers;
using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Federation.WebFinger;
using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Helpers;
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Services;
using Iceshrimp.Backend.Core.Services.ImageProcessing;
using Iceshrimp.Backend.SignalR.Authentication; using Iceshrimp.Backend.SignalR.Authentication;
using Iceshrimp.Shared.Configuration; using Iceshrimp.Shared.Configuration;
using Iceshrimp.Shared.Schemas.Web; using Iceshrimp.Shared.Schemas.Web;
@ -33,9 +24,6 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using NoteRenderer = Iceshrimp.Backend.Controllers.Web.Renderers.NoteRenderer;
using NotificationRenderer = Iceshrimp.Backend.Controllers.Web.Renderers.NotificationRenderer;
using UserRenderer = Iceshrimp.Backend.Controllers.Web.Renderers.UserRenderer;
namespace Iceshrimp.Backend.Core.Extensions; namespace Iceshrimp.Backend.Core.Extensions;
@ -43,91 +31,40 @@ public static class ServiceExtensions
{ {
public static void AddServices(this IServiceCollection services, IConfiguration configuration) public static void AddServices(this IServiceCollection services, IConfiguration configuration)
{ {
// Transient = instantiated per request and class var config = configuration.Get<Config>() ?? throw new Exception("Failed to read storage config section");
// Scoped = instantiated per request var serviceTypes = PluginLoader
services .Assemblies.Prepend(Assembly.GetExecutingAssembly())
.AddScoped<ActivityPub.ActivityRenderer>() .SelectMany(AssemblyLoader.GetImplementationsOfInterface<IService>)
.AddScoped<ActivityPub.UserRenderer>() .OrderBy(type => type.GetInterfaceProperty<IService, int?>(nameof(IService.Priority)) ?? 0)
.AddScoped<ActivityPub.NoteRenderer>() .ToArray();
.AddScoped<ActivityPub.UserResolver>()
.AddScoped<ActivityPub.ObjectResolver>()
.AddScoped<ActivityPub.MentionsResolver>()
.AddScoped<ActivityPub.ActivityDeliverService>()
.AddScoped<ActivityPub.FederationControlService>()
.AddScoped<ActivityPub.ActivityHandlerService>()
.AddScoped<ActivityPub.ActivityFetcherService>()
.AddScoped<UserService>()
.AddScoped<NoteService>()
.AddScoped<EmojiService>()
.AddScoped<EmojiImportService>()
.AddScoped<WebFingerService>()
.AddScoped<SystemUserService>()
.AddScoped<DriveService>()
.AddScoped<NotificationService>()
.AddScoped<DatabaseMaintenanceService>()
.AddScoped<BiteService>()
.AddScoped<ImportExportService>()
.AddScoped<UserProfileMentionsResolver>()
.AddScoped<Controllers.Mastodon.Renderers.UserRenderer>()
.AddScoped<Controllers.Mastodon.Renderers.NoteRenderer>()
.AddScoped<Controllers.Mastodon.Renderers.NotificationRenderer>()
.AddScoped<PollRenderer>()
.AddScoped<PollService>()
.AddScoped<NoteRenderer>()
.AddScoped<UserRenderer>()
.AddScoped<NotificationRenderer>()
.AddScoped<ActivityPubController>()
.AddScoped<FollowupTaskService>()
.AddScoped<InstanceService>()
.AddScoped<MfmConverter>()
.AddScoped<UserProfileRenderer>()
.AddScoped<CacheService>()
.AddScoped<MetaService>()
.AddScoped<StorageMaintenanceService>()
.AddScoped<RelayService>()
.AddScoped<Components.PublicPreview.Renderers.UserRenderer>()
.AddScoped<Components.PublicPreview.Renderers.NoteRenderer>();
// Singleton = instantiated once across application lifetime foreach (var type in serviceTypes)
services
.AddSingleton<HttpClient, CustomHttpClient>()
.AddSingleton<HttpRequestService>()
.AddSingleton<CronService>()
.AddSingleton<QueueService>()
.AddSingleton<ObjectStorageService>()
.AddSingleton<EventService>()
.AddSingleton<PushService>()
.AddSingleton<StreamingService>()
.AddSingleton<ImageProcessor>()
.AddSingleton<RazorViewRenderService>()
.AddSingleton<MfmRenderer>()
.AddSingleton<MatcherPolicy, PublicPreviewRouteMatcher>()
.AddSingleton<PolicyService>();
var config = configuration.GetSection("Storage").Get<Config.StorageSection>() ??
throw new Exception("Failed to read storage config section");
switch (config.MediaProcessing.ImageProcessor)
{ {
case Enums.ImageProcessor.LibVips: if (type.GetInterfaceProperty<IService, ServiceLifetime?>(nameof(IService.Lifetime)) is not { } lifetime)
services.AddSingleton<IImageProcessor, VipsProcessor>(); continue;
services.AddSingleton<IImageProcessor, ImageSharpProcessor>();
break; if (type.GetInterface(nameof(IConditionalService)) != null)
case Enums.ImageProcessor.ImageSharp: if (type.CallInterfaceMethod(nameof(IConditionalService.Predicate), config) is not true)
services.AddSingleton<IImageProcessor, ImageSharpProcessor>(); continue;
break;
case Enums.ImageProcessor.None: var serviceType = type.GetInterfaceProperty<IService, Type>(nameof(IService.ServiceType)) ?? type;
break; services.Add(new ServiceDescriptor(serviceType, type, lifetime));
default:
throw new ArgumentOutOfRangeException();
} }
// Hosted services = long running background tasks var hostedServiceTypes = PluginLoader
// Note: These need to be added as a singleton as well to ensure data consistency .Assemblies.Prepend(Assembly.GetExecutingAssembly())
services.AddHostedService<CronService>(provider => provider.GetRequiredService<CronService>()); .SelectMany(AssemblyLoader.GetImplementationsOfInterface<IHostedService>)
services.AddHostedService<QueueService>(provider => provider.GetRequiredService<QueueService>()); .ToArray();
services.AddHostedService<PushService>(provider => provider.GetRequiredService<PushService>());
foreach (var type in hostedServiceTypes)
{
if (type.GetInterface(nameof(IService)) == null)
services.Add(new ServiceDescriptor(type, type, ServiceLifetime.Singleton));
services.Add(new ServiceDescriptor(typeof(IHostedService), provider => provider.GetRequiredService(type),
ServiceLifetime.Singleton));
}
} }
public static void AddMiddleware(this IServiceCollection services) public static void AddMiddleware(this IServiceCollection services)
@ -413,6 +350,51 @@ public static partial class HttpContextExtensions
ctx.Items.TryGetValue(CacheKey, out var s) && s is true; ctx.Items.TryGetValue(CacheKey, out var s) && s is true;
} }
public interface IService
{
// This should be abstract instead of virtual but the runtime team said https://github.com/dotnet/runtime/issues/79331
public static virtual ServiceLifetime Lifetime => throw new Exception("Missing IService.Lifetime override");
public static virtual Type? ServiceType => null;
public static virtual int Priority => 0;
}
/// <summary>
/// Instantiated per request and class
/// </summary>
public interface ITransientService : IService
{
static ServiceLifetime IService.Lifetime => ServiceLifetime.Transient;
}
/// <summary>
/// Instantiated per request
/// </summary>
public interface IScopedService : IService
{
static ServiceLifetime IService.Lifetime => ServiceLifetime.Scoped;
}
/// <summary>
/// Instantiated once across application lifetime
/// </summary>
public interface ISingletonService : IService
{
static ServiceLifetime IService.Lifetime => ServiceLifetime.Singleton;
}
public interface IService<TService> : IService
{
static Type IService.ServiceType => typeof(TService);
}
public interface IConditionalService : IService
{
// This should be abstract instead of virtual but the runtime team said https://github.com/dotnet/runtime/issues/79331
public static virtual bool Predicate(Config ctx) =>
throw new Exception("Missing IConditionalService.Predicate override");
}
#region AsyncDataProtection handlers #region AsyncDataProtection handlers
/// <summary> /// <summary>

View file

@ -1,5 +1,6 @@
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Federation.ActivityStreams; using Iceshrimp.Backend.Core.Federation.ActivityStreams;
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types; using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
using Iceshrimp.Backend.Core.Queues; using Iceshrimp.Backend.Core.Queues;
@ -13,7 +14,7 @@ public class ActivityDeliverService(
ILogger<ActivityDeliverService> logger, ILogger<ActivityDeliverService> logger,
QueueService queueService, QueueService queueService,
DatabaseContext db DatabaseContext db
) ) : IScopedService
{ {
public async Task DeliverToFollowersAsync(ASActivity activity, User actor, IEnumerable<User> recipients) public async Task DeliverToFollowersAsync(ASActivity activity, User actor, IEnumerable<User> recipients)
{ {

View file

@ -23,7 +23,7 @@ public class ActivityFetcherService(
DatabaseContext db, DatabaseContext db,
ILogger<ActivityFetcherService> logger, ILogger<ActivityFetcherService> logger,
FederationControlService fedCtrlSvc FederationControlService fedCtrlSvc
) ) : IScopedService
{ {
private static readonly IReadOnlyCollection<string> AcceptableActivityTypes = private static readonly IReadOnlyCollection<string> AcceptableActivityTypes =
[ [

View file

@ -29,7 +29,7 @@ public class ActivityHandlerService(
EmojiService emojiSvc, EmojiService emojiSvc,
EventService eventSvc, EventService eventSvc,
RelayService relaySvc RelayService relaySvc
) ) : IScopedService
{ {
public async Task PerformActivityAsync(ASActivity activity, string? inboxUserId, string? authenticatedUserId) public async Task PerformActivityAsync(ASActivity activity, string? inboxUserId, string? authenticatedUserId)
{ {

View file

@ -12,7 +12,7 @@ public class ActivityRenderer(
IOptions<Config.InstanceSection> config, IOptions<Config.InstanceSection> config,
UserRenderer userRenderer, UserRenderer userRenderer,
NoteRenderer noteRenderer NoteRenderer noteRenderer
) ) : IScopedService
{ {
private string GenerateActivityId() => private string GenerateActivityId() =>
$"https://{config.Value.WebDomain}/activities/ephemeral/{Guid.NewGuid().ToStringLower()}"; $"https://{config.Value.WebDomain}/activities/ephemeral/{Guid.NewGuid().ToStringLower()}";

View file

@ -13,7 +13,7 @@ public class FederationControlService(
IOptionsSnapshot<Config.SecuritySection> options, IOptionsSnapshot<Config.SecuritySection> options,
IOptions<Config.InstanceSection> instance, IOptions<Config.InstanceSection> instance,
DatabaseContext db DatabaseContext db
) ) : IScopedService
{ {
//TODO: we need some level of caching here //TODO: we need some level of caching here
public async Task<bool> ShouldBlockAsync(params string?[] hosts) public async Task<bool> ShouldBlockAsync(params string?[] hosts)

View file

@ -16,9 +16,7 @@ using SplitDomainMapping = IReadOnlyDictionary<(string usernameLower, string web
/// Resolves mentions into their canonical form. This is required for handling split domain mentions correctly, as it /// Resolves mentions into their canonical form. This is required for handling split domain mentions correctly, as it
/// cannot be guaranteed that remote instances handle split domain users correctly. /// cannot be guaranteed that remote instances handle split domain users correctly.
/// </summary> /// </summary>
public class MentionsResolver( public class MentionsResolver(IOptions<Config.InstanceSection> config) : ISingletonService
IOptions<Config.InstanceSection> config
)
{ {
public string ResolveMentions( public string ResolveMentions(
string mfm, string? host, string mfm, string? host,

View file

@ -10,7 +10,11 @@ using Microsoft.Extensions.Options;
namespace Iceshrimp.Backend.Core.Federation.ActivityPub; namespace Iceshrimp.Backend.Core.Federation.ActivityPub;
public class NoteRenderer(IOptions<Config.InstanceSection> config, MfmConverter mfmConverter, DatabaseContext db) public class NoteRenderer(
IOptions<Config.InstanceSection> config,
MfmConverter mfmConverter,
DatabaseContext db
) : IScopedService
{ {
/// <summary> /// <summary>
/// This function is meant for compacting a note into the @id form as specified in ActivityStreams /// This function is meant for compacting a note into the @id form as specified in ActivityStreams

View file

@ -1,6 +1,7 @@
using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types; using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Middleware;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -14,7 +15,7 @@ public class ObjectResolver(
DatabaseContext db, DatabaseContext db,
FederationControlService federationCtrl, FederationControlService federationCtrl,
IOptions<Config.InstanceSection> config IOptions<Config.InstanceSection> config
) ) : IScopedService
{ {
public async Task<ASObject?> ResolveObject( public async Task<ASObject?> ResolveObject(
ASObjectBase baseObj, string? actorUri = null, int recurse = 5, bool force = false, User? user = null ASObjectBase baseObj, string? actorUri = null, int recurse = 5, bool force = false, User? user = null

View file

@ -2,6 +2,7 @@ using AngleSharp.Text;
using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types; using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion; using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -9,7 +10,11 @@ using Microsoft.Extensions.Options;
namespace Iceshrimp.Backend.Core.Federation.ActivityPub; namespace Iceshrimp.Backend.Core.Federation.ActivityPub;
public class UserRenderer(IOptions<Config.InstanceSection> config, DatabaseContext db, MfmConverter mfmConverter) public class UserRenderer(
IOptions<Config.InstanceSection> config,
DatabaseContext db,
MfmConverter mfmConverter
) : IScopedService
{ {
/// <summary> /// <summary>
/// This function is meant for compacting an actor into the @id form as specified in ActivityStreams /// This function is meant for compacting an actor into the @id form as specified in ActivityStreams

View file

@ -3,6 +3,7 @@ using AsyncKeyedLock;
using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Federation.WebFinger; using Iceshrimp.Backend.Core.Federation.WebFinger;
using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Helpers;
using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Middleware;
@ -20,7 +21,7 @@ public class UserResolver(
ActivityFetcherService fetchSvc, ActivityFetcherService fetchSvc,
IOptions<Config.InstanceSection> config, IOptions<Config.InstanceSection> config,
DatabaseContext db DatabaseContext db
) ) : IScopedService
{ {
private static readonly AsyncKeyedLocker<string> KeyedLocker = new(o => private static readonly AsyncKeyedLocker<string> KeyedLocker = new(o =>
{ {

View file

@ -4,6 +4,7 @@ using System.Text.Encodings.Web;
using System.Xml; using System.Xml;
using System.Xml.Serialization; using System.Xml.Serialization;
using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Middleware;
using Iceshrimp.Backend.Core.Services; using Iceshrimp.Backend.Core.Services;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -27,7 +28,7 @@ public class WebFingerService(
HttpRequestService httpRqSvc, HttpRequestService httpRqSvc,
IHostApplicationLifetime appLifetime, IHostApplicationLifetime appLifetime,
IOptions<Config.InstanceSection> config IOptions<Config.InstanceSection> config
) ) : ISingletonService
{ {
private static readonly ImmutableArray<string> Accept = private static readonly ImmutableArray<string> Accept =
[ [

View file

@ -16,7 +16,9 @@ using HtmlParser = AngleSharp.Html.Parser.HtmlParser;
namespace Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion; namespace Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
public class MfmConverter(IOptions<Config.InstanceSection> config) public class MfmConverter(
IOptions<Config.InstanceSection> config
) : IScopedService // <- this is intentional, see property below
{ {
public bool SupportsHtmlFormatting { private get; set; } = true; public bool SupportsHtmlFormatting { private get; set; } = true;

View file

@ -1,79 +1,88 @@
using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Helpers;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
public class BiteService(DatabaseContext db, ActivityPub.ActivityRenderer activityRenderer, ActivityPub.ActivityDeliverService deliverSvc, NotificationService notificationSvc, IOptions<Config.InstanceSection> config) public class BiteService(
DatabaseContext db,
ActivityPub.ActivityRenderer activityRenderer,
ActivityPub.ActivityDeliverService deliverSvc,
NotificationService notificationSvc,
IOptions<Config.InstanceSection> config
) : IScopedService
{ {
public async Task BiteAsync(User user, Bite target) public async Task BiteAsync(User user, Bite target)
{ {
var bite = new Bite var bite = new Bite
{ {
Id = IdHelpers.GenerateSnowflakeId(), Id = IdHelpers.GenerateSnowflakeId(),
CreatedAt = DateTime.UtcNow, CreatedAt = DateTime.UtcNow,
User = user, User = user,
TargetBite = target TargetBite = target
}; };
bite.Uri = bite.GetPublicUri(config.Value); bite.Uri = bite.GetPublicUri(config.Value);
await db.Bites.AddAsync(bite); await db.Bites.AddAsync(bite);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
if (target.UserHost != null) if (target.UserHost != null)
{ {
var activity = activityRenderer.RenderBite(bite, target.Uri ?? target.GetPublicUri(config.Value), target.User); var activity =
await deliverSvc.DeliverToAsync(activity, user, target.User); activityRenderer.RenderBite(bite, target.Uri ?? target.GetPublicUri(config.Value), target.User);
} await deliverSvc.DeliverToAsync(activity, user, target.User);
}
await notificationSvc.GenerateBiteNotification(bite); await notificationSvc.GenerateBiteNotification(bite);
} }
public async Task BiteAsync(User user, Note target) public async Task BiteAsync(User user, Note target)
{ {
var bite = new Bite var bite = new Bite
{ {
Id = IdHelpers.GenerateSnowflakeId(), Id = IdHelpers.GenerateSnowflakeId(),
CreatedAt = DateTime.UtcNow, CreatedAt = DateTime.UtcNow,
User = user, User = user,
TargetNote = target TargetNote = target
}; };
bite.Uri = bite.GetPublicUri(config.Value); bite.Uri = bite.GetPublicUri(config.Value);
await db.Bites.AddAsync(bite); await db.Bites.AddAsync(bite);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
if (target.UserHost != null) if (target.UserHost != null)
{ {
var activity = activityRenderer.RenderBite(bite, target.Uri ?? target.GetPublicUri(config.Value), target.User); var activity =
await deliverSvc.DeliverToAsync(activity, user, target.User); activityRenderer.RenderBite(bite, target.Uri ?? target.GetPublicUri(config.Value), target.User);
} await deliverSvc.DeliverToAsync(activity, user, target.User);
}
await notificationSvc.GenerateBiteNotification(bite); await notificationSvc.GenerateBiteNotification(bite);
} }
public async Task BiteAsync(User user, User target) public async Task BiteAsync(User user, User target)
{ {
var bite = new Bite var bite = new Bite
{ {
Id = IdHelpers.GenerateSnowflakeId(), Id = IdHelpers.GenerateSnowflakeId(),
CreatedAt = DateTime.UtcNow, CreatedAt = DateTime.UtcNow,
User = user, User = user,
TargetUser = target TargetUser = target
}; };
bite.Uri = bite.GetPublicUri(config.Value); bite.Uri = bite.GetPublicUri(config.Value);
await db.Bites.AddAsync(bite); await db.Bites.AddAsync(bite);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
if (target.Host != null) if (target.Host != null)
{ {
var activity = activityRenderer.RenderBite(bite, target.Uri ?? target.GetPublicUri(config.Value), target); var activity = activityRenderer.RenderBite(bite, target.Uri ?? target.GetPublicUri(config.Value), target);
await deliverSvc.DeliverToAsync(activity, user, target); await deliverSvc.DeliverToAsync(activity, user, target);
} }
await notificationSvc.GenerateBiteNotification(bite); await notificationSvc.GenerateBiteNotification(bite);
} }
} }

View file

@ -3,12 +3,12 @@ using System.Text.Json.Serialization;
using AsyncKeyedLock; using AsyncKeyedLock;
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
//TODO: named caches (with prefix) public class CacheService([FromKeyedServices("cache")] DatabaseContext db) : IScopedService
public class CacheService([FromKeyedServices("cache")] DatabaseContext db)
{ {
private static readonly AsyncKeyedLocker<string> KeyedLocker = new(o => private static readonly AsyncKeyedLocker<string> KeyedLocker = new(o =>
{ {

View file

@ -7,7 +7,7 @@ using Microsoft.Extensions.Options;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
public class CustomHttpClient : HttpClient public class CustomHttpClient : HttpClient, IService<HttpClient>, ISingletonService
{ {
private static readonly FastFallback FastFallbackHandler = new(); private static readonly FastFallback FastFallbackHandler = new();

View file

@ -1,9 +1,10 @@
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Extensions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
public class DatabaseMaintenanceService(DatabaseContext db) public class DatabaseMaintenanceService(DatabaseContext db) : IScopedService
{ {
public async Task RecomputeNoteCountersAsync() public async Task RecomputeNoteCountersAsync()
{ {

View file

@ -27,7 +27,7 @@ public class DriveService(
QueueService queueSvc, QueueService queueSvc,
ILogger<DriveService> logger, ILogger<DriveService> logger,
ImageProcessor imageProcessor ImageProcessor imageProcessor
) ) : IScopedService
{ {
public async Task<DriveFile?> StoreFile( public async Task<DriveFile?> StoreFile(
string? uri, User user, bool sensitive, string? description = null, string? mimeType = null, string? uri, User user, bool sensitive, string? description = null, string? mimeType = null,

View file

@ -1,6 +1,7 @@
using System.IO.Compression; using System.IO.Compression;
using System.Net; using System.Net;
using System.Text.Json; using System.Text.Json;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Middleware;
using Microsoft.AspNetCore.StaticFiles; using Microsoft.AspNetCore.StaticFiles;
@ -30,7 +31,7 @@ public record EmojiZip(EmojiZipMeta Metadata, ZipArchive Archive);
public class EmojiImportService( public class EmojiImportService(
EmojiService emojiSvc, EmojiService emojiSvc,
ILogger<EmojiImportService> logger ILogger<EmojiImportService> logger
) ) : IScopedService
{ {
public static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web); public static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);

View file

@ -18,7 +18,7 @@ public partial class EmojiService(
DriveService driveSvc, DriveService driveSvc,
SystemUserService sysUserSvc, SystemUserService sysUserSvc,
IOptions<Config.InstanceSection> config IOptions<Config.InstanceSection> config
) ) : IScopedService
{ {
private static readonly AsyncKeyedLocker<string> KeyedLocker = new(o => private static readonly AsyncKeyedLocker<string> KeyedLocker = new(o =>
{ {

View file

@ -1,9 +1,10 @@
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Events; using Iceshrimp.Backend.Core.Events;
using Iceshrimp.Backend.Core.Extensions;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
public class EventService public class EventService : ISingletonService
{ {
public event EventHandler<Note>? NotePublished; public event EventHandler<Note>? NotePublished;
public event EventHandler<Note>? NoteUpdated; public event EventHandler<Note>? NoteUpdated;

View file

@ -1,6 +1,11 @@
using Iceshrimp.Backend.Core.Extensions;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
public class FollowupTaskService(IServiceScopeFactory serviceScopeFactory, ILogger<FollowupTaskService> logger) public class FollowupTaskService(
IServiceScopeFactory serviceScopeFactory,
ILogger<FollowupTaskService> logger
) : IScopedService
{ {
public bool IsBackgroundWorker { get; private set; } public bool IsBackgroundWorker { get; private set; }

View file

@ -3,12 +3,13 @@ using System.Net.Http.Headers;
using System.Security.Cryptography; using System.Security.Cryptography;
using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Federation.Cryptography; using Iceshrimp.Backend.Core.Federation.Cryptography;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
public class HttpRequestService(IOptions<Config.InstanceSection> options) public class HttpRequestService(IOptions<Config.InstanceSection> options) : ISingletonService
{ {
private static HttpRequestMessage GenerateRequest( private static HttpRequestMessage GenerateRequest(
string url, HttpMethod method, string url, HttpMethod method,

View file

@ -2,13 +2,14 @@ using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Helpers;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using static Iceshrimp.Backend.Core.Services.ImageProcessing.ImageVersion; using static Iceshrimp.Backend.Core.Services.ImageProcessing.ImageVersion;
namespace Iceshrimp.Backend.Core.Services.ImageProcessing; namespace Iceshrimp.Backend.Core.Services.ImageProcessing;
public class ImageProcessor public class ImageProcessor : ISingletonService
{ {
private readonly IOptionsMonitor<Config.StorageSection> _config; private readonly IOptionsMonitor<Config.StorageSection> _config;
//TODO: support stripping of exif/icc metadata (without re-encoding) //TODO: support stripping of exif/icc metadata (without re-encoding)

View file

@ -1,5 +1,6 @@
using CommunityToolkit.HighPerformance; using CommunityToolkit.HighPerformance;
using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Helpers;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
@ -13,7 +14,8 @@ using ImageSharpConfig = SixLabors.ImageSharp.Configuration;
namespace Iceshrimp.Backend.Core.Services.ImageProcessing; namespace Iceshrimp.Backend.Core.Services.ImageProcessing;
public class ImageSharpProcessor : ImageProcessorBase, IImageProcessor public class ImageSharpProcessor : ImageProcessorBase, IImageProcessor,
ISingletonService, IConditionalService, IService<IImageProcessor>
{ {
private readonly ILogger<ImageSharpProcessor> _logger; private readonly ILogger<ImageSharpProcessor> _logger;
private readonly ImageSharpConfig _sharpConfig; private readonly ImageSharpConfig _sharpConfig;
@ -22,6 +24,9 @@ public class ImageSharpProcessor : ImageProcessorBase, IImageProcessor
public bool CanIdentify => true; public bool CanIdentify => true;
public bool CanGenerateBlurhash => true; public bool CanGenerateBlurhash => true;
public static bool Predicate(Config ctx) =>
ctx.Storage.MediaProcessing.ImageProcessor is Enums.ImageProcessor.ImageSharp or Enums.ImageProcessor.LibVips;
public ImageSharpProcessor( public ImageSharpProcessor(
ILogger<ImageSharpProcessor> logger, IOptions<Config.StorageSection> config ILogger<ImageSharpProcessor> logger, IOptions<Config.StorageSection> config
) : base("ImageSharp", 1) ) : base("ImageSharp", 1)

View file

@ -1,19 +1,28 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using CommunityToolkit.HighPerformance; using CommunityToolkit.HighPerformance;
using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Helpers;
using NetVips; using NetVips;
using Iceshrimp.MimeTypes; using Iceshrimp.MimeTypes;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using Enums = NetVips.Enums;
namespace Iceshrimp.Backend.Core.Services.ImageProcessing; namespace Iceshrimp.Backend.Core.Services.ImageProcessing;
public class VipsProcessor : ImageProcessorBase, IImageProcessor public class VipsProcessor : ImageProcessorBase, IImageProcessor,
ISingletonService, IConditionalService, IService<IImageProcessor>
{ {
private readonly ILogger<VipsProcessor> _logger; private readonly ILogger<VipsProcessor> _logger;
public bool CanIdentify => true; public bool CanIdentify => true;
public bool CanGenerateBlurhash => true; public bool CanGenerateBlurhash => true;
static int IService.Priority => -1;
public static bool Predicate(Config ctx) =>
ctx.Storage.MediaProcessing.ImageProcessor == Configuration.Enums.ImageProcessor.LibVips;
public VipsProcessor(ILogger<VipsProcessor> logger) : base("LibVips", 0) public VipsProcessor(ILogger<VipsProcessor> logger) : base("LibVips", 0)
{ {
_logger = logger; _logger = logger;

View file

@ -15,7 +15,7 @@ public class ImportExportService(
CacheService cacheSvc, CacheService cacheSvc,
UserService userSvc, UserService userSvc,
ActivityPub.UserResolver userResolver ActivityPub.UserResolver userResolver
) ) : IScopedService
{ {
public async Task<string> ExportFollowingAsync(User user) public async Task<string> ExportFollowingAsync(User user)
{ {

View file

@ -8,7 +8,11 @@ using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
public class InstanceService(DatabaseContext db, HttpClient httpClient, ILogger<InstanceService> logger) public class InstanceService(
DatabaseContext db,
HttpClient httpClient,
ILogger<InstanceService> logger
) : IScopedService
{ {
private static readonly AsyncKeyedLocker<string> KeyedLocker = new(o => private static readonly AsyncKeyedLocker<string> KeyedLocker = new(o =>
{ {

View file

@ -1,10 +1,11 @@
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
public class MetaService([FromKeyedServices("cache")] DatabaseContext db) public class MetaService([FromKeyedServices("cache")] DatabaseContext db) : IScopedService
{ {
public async Task<T> Get<T>(Meta<T> meta) => meta.ConvertGet(await Fetch(meta.Key)); public async Task<T> Get<T>(Meta<T> meta) => meta.ConvertGet(await Fetch(meta.Key));

View file

@ -19,8 +19,6 @@ using static Iceshrimp.Parsing.MfmNodeTypes;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor",
Justification = "We need IOptionsSnapshot for config hot reload")]
public class NoteService( public class NoteService(
ILogger<NoteService> logger, ILogger<NoteService> logger,
DatabaseContext db, DatabaseContext db,
@ -43,7 +41,7 @@ public class NoteService(
PollService pollSvc, PollService pollSvc,
ActivityPub.FederationControlService fedCtrlSvc, ActivityPub.FederationControlService fedCtrlSvc,
PolicyService policySvc PolicyService policySvc
) ) : IScopedService
{ {
private const int DefaultRecursionLimit = 100; private const int DefaultRecursionLimit = 100;

View file

@ -8,10 +8,9 @@ using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
public class NotificationService( public class NotificationService(
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor")]
DatabaseContext db, DatabaseContext db,
EventService eventSvc EventService eventSvc
) ) : IScopedService
{ {
public async Task GenerateMentionNotifications(Note note, IReadOnlyCollection<string> mentionedLocalUserIds) public async Task GenerateMentionNotifications(Note note, IReadOnlyCollection<string> mentionedLocalUserIds)
{ {

View file

@ -3,6 +3,7 @@ using System.Net.Http.Headers;
using System.Text; using System.Text;
using Carbon.Storage; using Carbon.Storage;
using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Helpers;
using Iceshrimp.ObjectStorage.Core.Models; using Iceshrimp.ObjectStorage.Core.Models;
using Iceshrimp.ObjectStorage.Core.Security; using Iceshrimp.ObjectStorage.Core.Security;
@ -11,7 +12,7 @@ using Microsoft.Extensions.Options;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
public class ObjectStorageService(IOptions<Config.StorageSection> config, HttpClient httpClient) public class ObjectStorageService(IOptions<Config.StorageSection> config, HttpClient httpClient) : ISingletonService
{ {
private readonly string? _accessUrl = config.Value.ObjectStorage?.AccessUrl; private readonly string? _accessUrl = config.Value.ObjectStorage?.AccessUrl;

View file

@ -4,6 +4,7 @@ using System.Text.Json;
using Iceshrimp.AssemblyUtils; using Iceshrimp.AssemblyUtils;
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types; using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Helpers;
using Iceshrimp.Shared.Configuration; using Iceshrimp.Shared.Configuration;
@ -11,7 +12,7 @@ using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
public class PolicyService(IServiceScopeFactory scopeFactory) public class PolicyService(IServiceScopeFactory scopeFactory) : ISingletonService
{ {
private bool _initialized; private bool _initialized;
private IRejectPolicy[] _rejectPolicies = []; private IRejectPolicy[] _rejectPolicies = [];

View file

@ -1,5 +1,6 @@
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
@ -9,7 +10,7 @@ public class PollService(
ActivityPub.ActivityRenderer activityRenderer, ActivityPub.ActivityRenderer activityRenderer,
ActivityPub.UserRenderer userRenderer, ActivityPub.UserRenderer userRenderer,
ActivityPub.ActivityDeliverService deliverSvc ActivityPub.ActivityDeliverService deliverSvc
) ) : IScopedService
{ {
public async Task RegisterPollVote(PollVote pollVote, Poll poll, Note note, bool updateVotersCount = true) public async Task RegisterPollVote(PollVote pollVote, Poll poll, Note note, bool updateVotersCount = true)
{ {

View file

@ -1,5 +1,6 @@
using System.Diagnostics; using System.Diagnostics;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using Iceshrimp.Backend.Core.Extensions;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
@ -15,7 +16,7 @@ public class RazorViewRenderService(
ITempDataProvider tempDataProvider, ITempDataProvider tempDataProvider,
IHttpContextAccessor httpContextAccessor, IHttpContextAccessor httpContextAccessor,
IRazorPageActivator activator IRazorPageActivator activator
) ) : ISingletonService
{ {
private async Task RenderAsync<T>(string path, T model, TextWriter writer) where T : PageModel private async Task RenderAsync<T>(string path, T model, TextWriter writer) where T : PageModel
{ {

View file

@ -1,5 +1,6 @@
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Helpers;
using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Middleware;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -12,7 +13,7 @@ public class RelayService(
ActivityPub.ActivityRenderer activityRenderer, ActivityPub.ActivityRenderer activityRenderer,
ActivityPub.ActivityDeliverService deliverSvc, ActivityPub.ActivityDeliverService deliverSvc,
ActivityPub.UserRenderer userRenderer ActivityPub.UserRenderer userRenderer
) ) : IScopedService
{ {
public async Task SubscribeToRelay(string uri) public async Task SubscribeToRelay(string uri)
{ {

View file

@ -16,7 +16,7 @@ public class StorageMaintenanceService(
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor")] [SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor")]
IOptionsSnapshot<Config.StorageSection> options, IOptionsSnapshot<Config.StorageSection> options,
ILogger<StorageMaintenanceService> logger ILogger<StorageMaintenanceService> logger
) ) : IScopedService
{ {
public async Task MigrateLocalFiles(bool purge) public async Task MigrateLocalFiles(bool purge)
{ {

View file

@ -1,6 +1,7 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using Iceshrimp.Backend.Controllers.Web.Renderers; using Iceshrimp.Backend.Controllers.Web.Renderers;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.SignalR; using Iceshrimp.Backend.SignalR;
using Iceshrimp.Backend.SignalR.Helpers; using Iceshrimp.Backend.SignalR.Helpers;
using Iceshrimp.Shared.Schemas.SignalR; using Iceshrimp.Shared.Schemas.SignalR;
@ -9,7 +10,7 @@ using Microsoft.AspNetCore.SignalR;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
public sealed class StreamingService public sealed class StreamingService : ISingletonService
{ {
private readonly ConcurrentDictionary<string, StreamingConnectionAggregate> _connections = []; private readonly ConcurrentDictionary<string, StreamingConnectionAggregate> _connections = [];
private readonly EventService _eventSvc; private readonly EventService _eventSvc;

View file

@ -2,12 +2,13 @@ using System.Security.Cryptography;
using AsyncKeyedLock; using AsyncKeyedLock;
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Helpers;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
public class SystemUserService(ILogger<SystemUserService> logger, DatabaseContext db) public class SystemUserService(ILogger<SystemUserService> logger, DatabaseContext db) : IScopedService
{ {
private static readonly AsyncKeyedLocker<string> KeyedLocker = new(o => private static readonly AsyncKeyedLocker<string> KeyedLocker = new(o =>
{ {

View file

@ -14,7 +14,10 @@ namespace Iceshrimp.Backend.Core.Services;
using MentionTuple = (List<Note.MentionedUser> mentions, using MentionTuple = (List<Note.MentionedUser> mentions,
Dictionary<(string usernameLower, string webDomain), string> splitDomainMapping); Dictionary<(string usernameLower, string webDomain), string> splitDomainMapping);
public class UserProfileMentionsResolver(ActivityPub.UserResolver userResolver, IOptions<Config.InstanceSection> config) public class UserProfileMentionsResolver(
ActivityPub.UserResolver userResolver,
IOptions<Config.InstanceSection> config
) : IScopedService
{ {
public async Task<MentionTuple> ResolveMentions(ASActor actor, string? host) public async Task<MentionTuple> ResolveMentions(ASActor actor, string? host)
{ {

View file

@ -22,7 +22,6 @@ using static Iceshrimp.Parsing.MfmNodeTypes;
namespace Iceshrimp.Backend.Core.Services; namespace Iceshrimp.Backend.Core.Services;
public class UserService( public class UserService(
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor")]
IOptionsSnapshot<Config.SecuritySection> security, IOptionsSnapshot<Config.SecuritySection> security,
IOptions<Config.InstanceSection> instance, IOptions<Config.InstanceSection> instance,
ILogger<UserService> logger, ILogger<UserService> logger,
@ -40,7 +39,7 @@ public class UserService(
EventService eventSvc, EventService eventSvc,
WebFingerService webFingerSvc, WebFingerService webFingerSvc,
ActivityPub.FederationControlService fedCtrlSvc ActivityPub.FederationControlService fedCtrlSvc
) ) : IScopedService
{ {
private static readonly AsyncKeyedLocker<string> KeyedLocker = new(o => private static readonly AsyncKeyedLocker<string> KeyedLocker = new(o =>
{ {

View file

@ -47,7 +47,7 @@
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" /> <PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="System.Text.Json" Version="9.0.0" /> <PackageReference Include="System.Text.Json" Version="9.0.0" />
<PackageReference Include="Ulid" Version="1.3.4" /> <PackageReference Include="Ulid" Version="1.3.4" />
<PackageReference Include="Iceshrimp.AssemblyUtils" Version="1.0.2" /> <PackageReference Include="Iceshrimp.AssemblyUtils" Version="1.0.3" />
<PackageReference Include="Iceshrimp.MimeTypes" Version="1.0.1" /> <PackageReference Include="Iceshrimp.MimeTypes" Version="1.0.1" />
<PackageReference Include="Iceshrimp.WebPush" Version="2.1.0" /> <PackageReference Include="Iceshrimp.WebPush" Version="2.1.0" />
<PackageReference Include="NetVips" Version="3.0.0" /> <PackageReference Include="NetVips" Version="3.0.0" />

View file

@ -38,7 +38,6 @@ builder.Services.AddRazorPages();
builder.Services.AddRazorComponents(); builder.Services.AddRazorComponents();
builder.Services.AddAntiforgery(o => o.Cookie.Name = "CSRF-Token"); builder.Services.AddAntiforgery(o => o.Cookie.Name = "CSRF-Token");
builder.Services.AddMiddleware(); builder.Services.AddMiddleware();
builder.Services.AddServices(builder.Configuration); builder.Services.AddServices(builder.Configuration);
builder.Services.ConfigureServices(builder.Configuration); builder.Services.ConfigureServices(builder.Configuration);