Compare commits
	
		
			No commits in common. "f00f5b400e78d9d458315a2bebefdbdc45c8203f" and "bba322bd22d39ab8f06c629cb7eb0baaaf7dcb8c" have entirely different histories.
		
	
	
		
			f00f5b400e
			...
			bba322bd22
		
	
		
					 18 changed files with 135 additions and 289 deletions
				
			
		| 
						 | 
				
			
			@ -21,10 +21,8 @@ using Foxnouns.Backend.Services;
 | 
			
		|||
using Foxnouns.Backend.Services.Auth;
 | 
			
		||||
using Foxnouns.Backend.Services.V1;
 | 
			
		||||
using Microsoft.EntityFrameworkCore;
 | 
			
		||||
using Microsoft.Extensions.Http.Resilience;
 | 
			
		||||
using Minio;
 | 
			
		||||
using NodaTime;
 | 
			
		||||
using Polly;
 | 
			
		||||
using Prometheus;
 | 
			
		||||
using Serilog;
 | 
			
		||||
using Serilog.Events;
 | 
			
		||||
| 
						 | 
				
			
			@ -102,40 +100,6 @@ public static class WebApplicationExtensions
 | 
			
		|||
        builder.Host.ConfigureServices(
 | 
			
		||||
            (ctx, services) =>
 | 
			
		||||
            {
 | 
			
		||||
                // create a single HTTP client for all requests.
 | 
			
		||||
                // it's also configured with a retry mechanism, so that requests aren't immediately lost to the void if they fail
 | 
			
		||||
                services.AddSingleton<HttpClient>(_ =>
 | 
			
		||||
                {
 | 
			
		||||
                    // ReSharper disable once SuggestVarOrType_Elsewhere
 | 
			
		||||
                    var retryPipeline = new ResiliencePipelineBuilder<HttpResponseMessage>()
 | 
			
		||||
                        .AddRetry(
 | 
			
		||||
                            new HttpRetryStrategyOptions
 | 
			
		||||
                            {
 | 
			
		||||
                                BackoffType = DelayBackoffType.Linear,
 | 
			
		||||
                                MaxRetryAttempts = 3,
 | 
			
		||||
                            }
 | 
			
		||||
                        )
 | 
			
		||||
                        .Build();
 | 
			
		||||
 | 
			
		||||
                    var resilienceHandler = new ResilienceHandler(retryPipeline)
 | 
			
		||||
                    {
 | 
			
		||||
                        InnerHandler = new SocketsHttpHandler
 | 
			
		||||
                        {
 | 
			
		||||
                            PooledConnectionLifetime = TimeSpan.FromMinutes(15),
 | 
			
		||||
                        },
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    var client = new HttpClient(resilienceHandler);
 | 
			
		||||
                    client.DefaultRequestHeaders.Remove("User-Agent");
 | 
			
		||||
                    client.DefaultRequestHeaders.Remove("Accept");
 | 
			
		||||
                    client.DefaultRequestHeaders.Add(
 | 
			
		||||
                        "User-Agent",
 | 
			
		||||
                        $"pronouns.cc/{BuildInfo.Version}"
 | 
			
		||||
                    );
 | 
			
		||||
                    client.DefaultRequestHeaders.Add("Accept", "application/json");
 | 
			
		||||
                    return client;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                services
 | 
			
		||||
                    .AddQueue()
 | 
			
		||||
                    .AddSmtpMailer(ctx.Configuration)
 | 
			
		||||
| 
						 | 
				
			
			@ -196,6 +160,9 @@ public static class WebApplicationExtensions
 | 
			
		|||
 | 
			
		||||
    public static async Task Initialize(this WebApplication app, string[] args)
 | 
			
		||||
    {
 | 
			
		||||
        // Read version information from .version in the repository root
 | 
			
		||||
        await BuildInfo.ReadBuildInfo();
 | 
			
		||||
 | 
			
		||||
        app.Services.ConfigureQueue()
 | 
			
		||||
            .LogQueuedTaskProgress(app.Services.GetRequiredService<ILogger<IQueue>>());
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,13 +25,12 @@
 | 
			
		|||
            <PrivateAssets>all</PrivateAssets>
 | 
			
		||||
        </PackageReference>
 | 
			
		||||
        <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.2"/>
 | 
			
		||||
        <PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.2.0"/>
 | 
			
		||||
        <PackageReference Include="MimeKit" Version="4.10.0"/>
 | 
			
		||||
        <PackageReference Include="Minio" Version="6.0.4"/>
 | 
			
		||||
        <PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
 | 
			
		||||
        <PackageReference Include="NodaTime" Version="3.2.1"/>
 | 
			
		||||
        <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4"/>
 | 
			
		||||
        <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4"/>
 | 
			
		||||
        <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
 | 
			
		||||
        <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4" />
 | 
			
		||||
        <PackageReference Include="Npgsql.Json.NET" Version="9.0.3"/>
 | 
			
		||||
        <PackageReference Include="prometheus-net" Version="8.2.1"/>
 | 
			
		||||
        <PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1"/>
 | 
			
		||||
| 
						 | 
				
			
			@ -39,14 +38,14 @@
 | 
			
		|||
            <PrivateAssets>all</PrivateAssets>
 | 
			
		||||
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
 | 
			
		||||
        </PackageReference>
 | 
			
		||||
        <PackageReference Include="Scalar.AspNetCore" Version="2.0.26"/>
 | 
			
		||||
        <PackageReference Include="Sentry.AspNetCore" Version="5.3.0"/>
 | 
			
		||||
        <PackageReference Include="Scalar.AspNetCore" Version="2.0.26" />
 | 
			
		||||
        <PackageReference Include="Sentry.AspNetCore" Version="5.3.0" />
 | 
			
		||||
        <PackageReference Include="Serilog" Version="4.2.0"/>
 | 
			
		||||
        <PackageReference Include="Serilog.AspNetCore" Version="9.0.0"/>
 | 
			
		||||
        <PackageReference Include="Serilog.Sinks.Console" Version="6.0.0"/>
 | 
			
		||||
        <PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0"/>
 | 
			
		||||
        <PackageReference Include="SixLabors.ImageSharp" Version="3.1.7"/>
 | 
			
		||||
        <PackageReference Include="StackExchange.Redis" Version="2.8.31"/>
 | 
			
		||||
        <PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
 | 
			
		||||
        <PackageReference Include="StackExchange.Redis" Version="2.8.31" />
 | 
			
		||||
        <PackageReference Include="System.Text.Json" Version="9.0.2"/>
 | 
			
		||||
        <PackageReference Include="System.Text.RegularExpressions" Version="4.3.1"/>
 | 
			
		||||
        <PackageReference Include="Yort.Xid.Net" Version="2.0.1"/>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,7 +27,6 @@ using NodaTime.Text;
 | 
			
		|||
namespace Foxnouns.Backend.Jobs;
 | 
			
		||||
 | 
			
		||||
public class CreateDataExportJob(
 | 
			
		||||
    HttpClient client,
 | 
			
		||||
    DatabaseContext db,
 | 
			
		||||
    IClock clock,
 | 
			
		||||
    UserRendererService userRenderer,
 | 
			
		||||
| 
						 | 
				
			
			@ -37,6 +36,7 @@ public class CreateDataExportJob(
 | 
			
		|||
    ILogger logger
 | 
			
		||||
)
 | 
			
		||||
{
 | 
			
		||||
    private static readonly HttpClient Client = new();
 | 
			
		||||
    private readonly ILogger _logger = logger.ForContext<CreateDataExportJob>();
 | 
			
		||||
 | 
			
		||||
    public static void Enqueue(Snowflake userId)
 | 
			
		||||
| 
						 | 
				
			
			@ -201,7 +201,7 @@ public class CreateDataExportJob(
 | 
			
		|||
        if (s3Path == null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        HttpResponseMessage resp = await client.GetAsync(s3Path);
 | 
			
		||||
        HttpResponseMessage resp = await Client.GetAsync(s3Path);
 | 
			
		||||
        if (resp.StatusCode != HttpStatusCode.OK)
 | 
			
		||||
        {
 | 
			
		||||
            _logger.Warning("S3 path {S3Path} returned a non-200 status, not saving file", s3Path);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,9 +34,6 @@ Config config = builder.AddConfiguration();
 | 
			
		|||
 | 
			
		||||
builder.AddSerilog();
 | 
			
		||||
 | 
			
		||||
// Read version information from .version in the repository root
 | 
			
		||||
await BuildInfo.ReadBuildInfo();
 | 
			
		||||
 | 
			
		||||
builder
 | 
			
		||||
    .WebHost.UseSentry(opts =>
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -71,9 +68,11 @@ builder
 | 
			
		|||
    })
 | 
			
		||||
    .ConfigureApiBehaviorOptions(options =>
 | 
			
		||||
    {
 | 
			
		||||
        options.InvalidModelStateResponseFactory = actionContext => new BadRequestObjectResult(
 | 
			
		||||
            new ApiError.AspBadRequest("Bad request", actionContext.ModelState).ToJson()
 | 
			
		||||
        );
 | 
			
		||||
        // the type isn't needed but without it, rider keeps complaining for no reason (it compiles just fine)
 | 
			
		||||
        options.InvalidModelStateResponseFactory = (ActionContext actionContext) =>
 | 
			
		||||
            new BadRequestObjectResult(
 | 
			
		||||
                new ApiError.AspBadRequest("Bad request", actionContext.ModelState).ToJson()
 | 
			
		||||
            );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
builder
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,20 +25,20 @@ namespace Foxnouns.Backend.Services.Auth;
 | 
			
		|||
public partial class FediverseAuthService
 | 
			
		||||
{
 | 
			
		||||
    private string MastodonRedirectUri(string instance) =>
 | 
			
		||||
        $"{config.BaseUrl}/auth/callback/mastodon/{instance}";
 | 
			
		||||
        $"{_config.BaseUrl}/auth/callback/mastodon/{instance}";
 | 
			
		||||
 | 
			
		||||
    private async Task<FediverseApplication> CreateMastodonApplicationAsync(
 | 
			
		||||
        string instance,
 | 
			
		||||
        Snowflake? existingAppId = null
 | 
			
		||||
    )
 | 
			
		||||
    {
 | 
			
		||||
        HttpResponseMessage resp = await client.PostAsJsonAsync(
 | 
			
		||||
        HttpResponseMessage resp = await _client.PostAsJsonAsync(
 | 
			
		||||
            $"https://{instance}/api/v1/apps",
 | 
			
		||||
            new CreateMastodonApplicationRequest(
 | 
			
		||||
                $"pronouns.cc (+{config.BaseUrl})",
 | 
			
		||||
                $"pronouns.cc (+{_config.BaseUrl})",
 | 
			
		||||
                MastodonRedirectUri(instance),
 | 
			
		||||
                "read read:accounts",
 | 
			
		||||
                config.BaseUrl
 | 
			
		||||
                _config.BaseUrl
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
        resp.EnsureSuccessStatusCode();
 | 
			
		||||
| 
						 | 
				
			
			@ -58,19 +58,19 @@ public partial class FediverseAuthService
 | 
			
		|||
        {
 | 
			
		||||
            app = new FediverseApplication
 | 
			
		||||
            {
 | 
			
		||||
                Id = existingAppId ?? snowflakeGenerator.GenerateSnowflake(),
 | 
			
		||||
                Id = existingAppId ?? _snowflakeGenerator.GenerateSnowflake(),
 | 
			
		||||
                ClientId = mastodonApp.ClientId,
 | 
			
		||||
                ClientSecret = mastodonApp.ClientSecret,
 | 
			
		||||
                Domain = instance,
 | 
			
		||||
                InstanceType = FediverseInstanceType.MastodonApi,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            db.Add(app);
 | 
			
		||||
            _db.Add(app);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            app =
 | 
			
		||||
                await db.FediverseApplications.FindAsync(existingAppId)
 | 
			
		||||
                await _db.FediverseApplications.FindAsync(existingAppId)
 | 
			
		||||
                ?? throw new FoxnounsError($"Existing app with ID {existingAppId} was null");
 | 
			
		||||
 | 
			
		||||
            app.ClientId = mastodonApp.ClientId;
 | 
			
		||||
| 
						 | 
				
			
			@ -78,7 +78,7 @@ public partial class FediverseAuthService
 | 
			
		|||
            app.InstanceType = FediverseInstanceType.MastodonApi;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await db.SaveChangesAsync();
 | 
			
		||||
        await _db.SaveChangesAsync();
 | 
			
		||||
 | 
			
		||||
        return app;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -90,9 +90,9 @@ public partial class FediverseAuthService
 | 
			
		|||
    )
 | 
			
		||||
    {
 | 
			
		||||
        if (state != null)
 | 
			
		||||
            await keyCacheService.ValidateAuthStateAsync(state);
 | 
			
		||||
            await _keyCacheService.ValidateAuthStateAsync(state);
 | 
			
		||||
 | 
			
		||||
        HttpResponseMessage tokenResp = await client.PostAsync(
 | 
			
		||||
        HttpResponseMessage tokenResp = await _client.PostAsync(
 | 
			
		||||
            MastodonTokenUri(app.Domain),
 | 
			
		||||
            new FormUrlEncodedContent(
 | 
			
		||||
                new Dictionary<string, string>
 | 
			
		||||
| 
						 | 
				
			
			@ -123,7 +123,7 @@ public partial class FediverseAuthService
 | 
			
		|||
        var req = new HttpRequestMessage(HttpMethod.Get, MastodonCurrentUserUri(app.Domain));
 | 
			
		||||
        req.Headers.Add("Authorization", $"Bearer {token}");
 | 
			
		||||
 | 
			
		||||
        HttpResponseMessage currentUserResp = await client.SendAsync(req);
 | 
			
		||||
        HttpResponseMessage currentUserResp = await _client.SendAsync(req);
 | 
			
		||||
        currentUserResp.EnsureSuccessStatusCode();
 | 
			
		||||
        FediverseUser? user = await currentUserResp.Content.ReadFromJsonAsync<FediverseUser>();
 | 
			
		||||
        if (user == null)
 | 
			
		||||
| 
						 | 
				
			
			@ -151,7 +151,7 @@ public partial class FediverseAuthService
 | 
			
		|||
            app = await CreateMastodonApplicationAsync(app.Domain, app.Id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        state ??= HttpUtility.UrlEncode(await keyCacheService.GenerateAuthStateAsync());
 | 
			
		||||
        state ??= HttpUtility.UrlEncode(await _keyCacheService.GenerateAuthStateAsync());
 | 
			
		||||
 | 
			
		||||
        return $"https://{app.Domain}/oauth/authorize?response_type=code"
 | 
			
		||||
            + $"&client_id={app.ClientId}"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,11 +34,11 @@ public partial class FediverseAuthService
 | 
			
		|||
        Snowflake? existingAppId = null
 | 
			
		||||
    )
 | 
			
		||||
    {
 | 
			
		||||
        HttpResponseMessage resp = await client.PostAsJsonAsync(
 | 
			
		||||
        HttpResponseMessage resp = await _client.PostAsJsonAsync(
 | 
			
		||||
            MisskeyAppUri(instance),
 | 
			
		||||
            new CreateMisskeyApplicationRequest(
 | 
			
		||||
                $"pronouns.cc (+{config.BaseUrl})",
 | 
			
		||||
                $"pronouns.cc on {config.BaseUrl}",
 | 
			
		||||
                $"pronouns.cc (+{_config.BaseUrl})",
 | 
			
		||||
                $"pronouns.cc on {_config.BaseUrl}",
 | 
			
		||||
                ["read:account"],
 | 
			
		||||
                MastodonRedirectUri(instance)
 | 
			
		||||
            )
 | 
			
		||||
| 
						 | 
				
			
			@ -60,19 +60,19 @@ public partial class FediverseAuthService
 | 
			
		|||
        {
 | 
			
		||||
            app = new FediverseApplication
 | 
			
		||||
            {
 | 
			
		||||
                Id = existingAppId ?? snowflakeGenerator.GenerateSnowflake(),
 | 
			
		||||
                Id = existingAppId ?? _snowflakeGenerator.GenerateSnowflake(),
 | 
			
		||||
                ClientId = misskeyApp.Id,
 | 
			
		||||
                ClientSecret = misskeyApp.Secret,
 | 
			
		||||
                Domain = instance,
 | 
			
		||||
                InstanceType = FediverseInstanceType.MisskeyApi,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            db.Add(app);
 | 
			
		||||
            _db.Add(app);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            app =
 | 
			
		||||
                await db.FediverseApplications.FindAsync(existingAppId)
 | 
			
		||||
                await _db.FediverseApplications.FindAsync(existingAppId)
 | 
			
		||||
                ?? throw new FoxnounsError($"Existing app with ID {existingAppId} was null");
 | 
			
		||||
 | 
			
		||||
            app.ClientId = misskeyApp.Id;
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +80,7 @@ public partial class FediverseAuthService
 | 
			
		|||
            app.InstanceType = FediverseInstanceType.MisskeyApi;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await db.SaveChangesAsync();
 | 
			
		||||
        await _db.SaveChangesAsync();
 | 
			
		||||
 | 
			
		||||
        return app;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -96,7 +96,7 @@ public partial class FediverseAuthService
 | 
			
		|||
 | 
			
		||||
    private async Task<FediverseUser> GetMisskeyUserAsync(FediverseApplication app, string code)
 | 
			
		||||
    {
 | 
			
		||||
        HttpResponseMessage resp = await client.PostAsJsonAsync(
 | 
			
		||||
        HttpResponseMessage resp = await _client.PostAsJsonAsync(
 | 
			
		||||
            MisskeyTokenUri(app.Domain),
 | 
			
		||||
            new GetMisskeySessionUserKeyRequest(app.ClientSecret, code)
 | 
			
		||||
        );
 | 
			
		||||
| 
						 | 
				
			
			@ -130,7 +130,7 @@ public partial class FediverseAuthService
 | 
			
		|||
            app = await CreateMisskeyApplicationAsync(app.Domain, app.Id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        HttpResponseMessage resp = await client.PostAsJsonAsync(
 | 
			
		||||
        HttpResponseMessage resp = await _client.PostAsJsonAsync(
 | 
			
		||||
            MisskeyGenerateSessionUri(app.Domain),
 | 
			
		||||
            new CreateMisskeySessionUriRequest(app.ClientSecret)
 | 
			
		||||
        );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,17 +19,37 @@ using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
 | 
			
		|||
 | 
			
		||||
namespace Foxnouns.Backend.Services.Auth;
 | 
			
		||||
 | 
			
		||||
public partial class FediverseAuthService(
 | 
			
		||||
    ILogger logger,
 | 
			
		||||
    Config config,
 | 
			
		||||
    DatabaseContext db,
 | 
			
		||||
    HttpClient client,
 | 
			
		||||
    KeyCacheService keyCacheService,
 | 
			
		||||
    ISnowflakeGenerator snowflakeGenerator
 | 
			
		||||
)
 | 
			
		||||
public partial class FediverseAuthService
 | 
			
		||||
{
 | 
			
		||||
    private const string NodeInfoRel = "http://nodeinfo.diaspora.software/ns/schema/2.0";
 | 
			
		||||
    private readonly ILogger _logger = logger.ForContext<FediverseAuthService>();
 | 
			
		||||
 | 
			
		||||
    private readonly HttpClient _client;
 | 
			
		||||
    private readonly ILogger _logger;
 | 
			
		||||
    private readonly Config _config;
 | 
			
		||||
    private readonly DatabaseContext _db;
 | 
			
		||||
    private readonly KeyCacheService _keyCacheService;
 | 
			
		||||
    private readonly ISnowflakeGenerator _snowflakeGenerator;
 | 
			
		||||
 | 
			
		||||
    public FediverseAuthService(
 | 
			
		||||
        ILogger logger,
 | 
			
		||||
        Config config,
 | 
			
		||||
        DatabaseContext db,
 | 
			
		||||
        KeyCacheService keyCacheService,
 | 
			
		||||
        ISnowflakeGenerator snowflakeGenerator
 | 
			
		||||
    )
 | 
			
		||||
    {
 | 
			
		||||
        _logger = logger.ForContext<FediverseAuthService>();
 | 
			
		||||
        _config = config;
 | 
			
		||||
        _db = db;
 | 
			
		||||
        _keyCacheService = keyCacheService;
 | 
			
		||||
        _snowflakeGenerator = snowflakeGenerator;
 | 
			
		||||
 | 
			
		||||
        _client = new HttpClient();
 | 
			
		||||
        _client.DefaultRequestHeaders.Remove("User-Agent");
 | 
			
		||||
        _client.DefaultRequestHeaders.Remove("Accept");
 | 
			
		||||
        _client.DefaultRequestHeaders.Add("User-Agent", $"pronouns.cc/{BuildInfo.Version}");
 | 
			
		||||
        _client.DefaultRequestHeaders.Add("Accept", "application/json");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<string> GenerateAuthUrlAsync(
 | 
			
		||||
        string instance,
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +70,7 @@ public partial class FediverseAuthService(
 | 
			
		|||
 | 
			
		||||
    public async Task<FediverseApplication> GetApplicationAsync(string instance)
 | 
			
		||||
    {
 | 
			
		||||
        FediverseApplication? app = await db.FediverseApplications.FirstOrDefaultAsync(a =>
 | 
			
		||||
        FediverseApplication? app = await _db.FediverseApplications.FirstOrDefaultAsync(a =>
 | 
			
		||||
            a.Domain == instance
 | 
			
		||||
        );
 | 
			
		||||
        if (app != null)
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +92,7 @@ public partial class FediverseAuthService(
 | 
			
		|||
    {
 | 
			
		||||
        _logger.Debug("Requesting software name for fediverse instance {Instance}", instance);
 | 
			
		||||
 | 
			
		||||
        HttpResponseMessage wellKnownResp = await client.GetAsync(
 | 
			
		||||
        HttpResponseMessage wellKnownResp = await _client.GetAsync(
 | 
			
		||||
            new Uri($"https://{instance}/.well-known/nodeinfo")
 | 
			
		||||
        );
 | 
			
		||||
        wellKnownResp.EnsureSuccessStatusCode();
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +107,7 @@ public partial class FediverseAuthService(
 | 
			
		|||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        HttpResponseMessage nodeInfoResp = await client.GetAsync(nodeInfoUrl);
 | 
			
		||||
        HttpResponseMessage nodeInfoResp = await _client.GetAsync(nodeInfoUrl);
 | 
			
		||||
        nodeInfoResp.EnsureSuccessStatusCode();
 | 
			
		||||
 | 
			
		||||
        PartialNodeInfo? nodeInfo = await nodeInfoResp.Content.ReadFromJsonAsync<PartialNodeInfo>();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,7 +27,7 @@ public partial class RemoteAuthService
 | 
			
		|||
    )
 | 
			
		||||
    {
 | 
			
		||||
        var redirectUri = $"{config.BaseUrl}/auth/callback/discord";
 | 
			
		||||
        HttpResponseMessage resp = await client.PostAsync(
 | 
			
		||||
        HttpResponseMessage resp = await _httpClient.PostAsync(
 | 
			
		||||
            _discordTokenUri,
 | 
			
		||||
            new FormUrlEncodedContent(
 | 
			
		||||
                new Dictionary<string, string>
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +59,7 @@ public partial class RemoteAuthService
 | 
			
		|||
        var req = new HttpRequestMessage(HttpMethod.Get, _discordUserUri);
 | 
			
		||||
        req.Headers.Add("Authorization", $"{token.TokenType} {token.AccessToken}");
 | 
			
		||||
 | 
			
		||||
        HttpResponseMessage resp2 = await client.SendAsync(req, ct);
 | 
			
		||||
        HttpResponseMessage resp2 = await _httpClient.SendAsync(req, ct);
 | 
			
		||||
        resp2.EnsureSuccessStatusCode();
 | 
			
		||||
        DiscordUserResponse? user = await resp2.Content.ReadFromJsonAsync<DiscordUserResponse>(ct);
 | 
			
		||||
        if (user == null)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,7 +28,7 @@ public partial class RemoteAuthService
 | 
			
		|||
    )
 | 
			
		||||
    {
 | 
			
		||||
        var redirectUri = $"{config.BaseUrl}/auth/callback/google";
 | 
			
		||||
        HttpResponseMessage resp = await client.PostAsync(
 | 
			
		||||
        HttpResponseMessage resp = await _httpClient.PostAsync(
 | 
			
		||||
            _googleTokenUri,
 | 
			
		||||
            new FormUrlEncodedContent(
 | 
			
		||||
                new Dictionary<string, string>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,7 +29,7 @@ public partial class RemoteAuthService
 | 
			
		|||
    )
 | 
			
		||||
    {
 | 
			
		||||
        var redirectUri = $"{config.BaseUrl}/auth/callback/tumblr";
 | 
			
		||||
        HttpResponseMessage resp = await client.PostAsync(
 | 
			
		||||
        HttpResponseMessage resp = await _httpClient.PostAsync(
 | 
			
		||||
            _tumblrTokenUri,
 | 
			
		||||
            new FormUrlEncodedContent(
 | 
			
		||||
                new Dictionary<string, string>
 | 
			
		||||
| 
						 | 
				
			
			@ -62,7 +62,7 @@ public partial class RemoteAuthService
 | 
			
		|||
        var req = new HttpRequestMessage(HttpMethod.Get, _tumblrUserUri);
 | 
			
		||||
        req.Headers.Add("Authorization", $"Bearer {token.AccessToken}");
 | 
			
		||||
 | 
			
		||||
        HttpResponseMessage resp2 = await client.SendAsync(req, ct);
 | 
			
		||||
        HttpResponseMessage resp2 = await _httpClient.SendAsync(req, ct);
 | 
			
		||||
        if (!resp2.IsSuccessStatusCode)
 | 
			
		||||
        {
 | 
			
		||||
            string respBody = await resp2.Content.ReadAsStringAsync(ct);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,6 @@ using Microsoft.EntityFrameworkCore;
 | 
			
		|||
namespace Foxnouns.Backend.Services.Auth;
 | 
			
		||||
 | 
			
		||||
public partial class RemoteAuthService(
 | 
			
		||||
    HttpClient client,
 | 
			
		||||
    Config config,
 | 
			
		||||
    ILogger logger,
 | 
			
		||||
    DatabaseContext db,
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +32,7 @@ public partial class RemoteAuthService(
 | 
			
		|||
)
 | 
			
		||||
{
 | 
			
		||||
    private readonly ILogger _logger = logger.ForContext<RemoteAuthService>();
 | 
			
		||||
    private readonly HttpClient _httpClient = new();
 | 
			
		||||
 | 
			
		||||
    public record RemoteUser(string Id, string Username);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,6 +39,8 @@ public class KeyCacheService(Config config)
 | 
			
		|||
    public async Task DeleteKeyAsync(string key) =>
 | 
			
		||||
        await Multiplexer.GetDatabase().KeyDeleteAsync(key);
 | 
			
		||||
 | 
			
		||||
    public Task DeleteExpiredKeysAsync(CancellationToken ct) => Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
    public async Task SetKeyAsync<T>(string key, T obj, Duration expiresAt)
 | 
			
		||||
        where T : class
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,9 +33,11 @@ public class PeriodicTasksService(ILogger logger, IServiceProvider services) : B
 | 
			
		|||
 | 
			
		||||
        // The type is literally written on the same line, we can just use `var`
 | 
			
		||||
        // ReSharper disable SuggestVarOrType_SimpleTypes
 | 
			
		||||
        var keyCacheService = scope.ServiceProvider.GetRequiredService<KeyCacheService>();
 | 
			
		||||
        var dataCleanupService = scope.ServiceProvider.GetRequiredService<DataCleanupService>();
 | 
			
		||||
        // ReSharper restore SuggestVarOrType_SimpleTypes
 | 
			
		||||
 | 
			
		||||
        await keyCacheService.DeleteExpiredKeysAsync(ct);
 | 
			
		||||
        await dataCleanupService.InvokeAsync(ct);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,7 +31,6 @@ public partial class ValidationService
 | 
			
		|||
        "settings",
 | 
			
		||||
        "pronouns.cc",
 | 
			
		||||
        "pronounscc",
 | 
			
		||||
        "null",
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    private static readonly string[] InvalidMemberNames =
 | 
			
		||||
| 
						 | 
				
			
			@ -39,10 +38,8 @@ public partial class ValidationService
 | 
			
		|||
        // these break routing outright
 | 
			
		||||
        ".",
 | 
			
		||||
        "..",
 | 
			
		||||
        // TODO: remove this? i'm not sure if /@[username]/edit will redirect to settings
 | 
			
		||||
        // the user edit page lives at `/@{username}/edit`, so a member named "edit" would be inaccessible
 | 
			
		||||
        "edit",
 | 
			
		||||
        // this breaks the frontend, somehow
 | 
			
		||||
        "null",
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public ValidationError? ValidateUsername(string username)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -155,18 +155,6 @@
 | 
			
		|||
          "Microsoft.Extensions.Primitives": "9.0.2"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.Http.Resilience": {
 | 
			
		||||
        "type": "Direct",
 | 
			
		||||
        "requested": "[9.2.0, )",
 | 
			
		||||
        "resolved": "9.2.0",
 | 
			
		||||
        "contentHash": "Km+YyCuk1IaeOsAzPDygtgsUOh3Fi89hpA18si0tFJmpSBf9aKzP9ffV5j7YOoVDvRWirpumXAPQzk1inBsvKw==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Binder": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Http.Diagnostics": "9.2.0",
 | 
			
		||||
          "Microsoft.Extensions.ObjectPool": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Resilience": "9.2.0"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "MimeKit": {
 | 
			
		||||
        "type": "Direct",
 | 
			
		||||
        "requested": "[4.10.0, )",
 | 
			
		||||
| 
						 | 
				
			
			@ -549,16 +537,6 @@
 | 
			
		|||
          "Microsoft.Extensions.Logging": "9.0.2"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.AmbientMetadata.Application": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.2.0",
 | 
			
		||||
        "contentHash": "GMCX3zybUB22aAADjYPXrWhhd1HNMkcY5EcFAJnXy/4k5pPpJ6TS4VRl37xfrtosNyzbpO2SI7pd2Q5PvggSdg==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.Configuration": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Hosting.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.2"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.Caching.Abstractions": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.0.2",
 | 
			
		||||
| 
						 | 
				
			
			@ -567,22 +545,13 @@
 | 
			
		|||
          "Microsoft.Extensions.Primitives": "9.0.2"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.Compliance.Abstractions": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.2.0",
 | 
			
		||||
        "contentHash": "Te+N4xphDlGIS90lKJMZyezFiMWKLAtYV2/M8gGJG4thH6xyC7LWhMzgz2+tWMehxwZlBUq2D9DvVpjKBZFTPQ==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.ObjectPool": "9.0.2"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.Configuration": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.0.2",
 | 
			
		||||
        "contentHash": "EBZW+u96tApIvNtjymXEIS44tH0I/jNwABHo4c33AchWOiDWCq2rL3klpnIo+xGrxoVGJzPDISV6hZ+a9C9SzQ==",
 | 
			
		||||
        "resolved": "9.0.0",
 | 
			
		||||
        "contentHash": "YIMO9T3JL8MeEXgVozKt2v79hquo/EFtnY0vgxmLnUvk1Rei/halI7kOWZL2RBeV9FMGzgM9LZA8CVaNwFMaNA==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Primitives": "9.0.2"
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Abstractions": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Primitives": "9.0.0"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.Configuration.Abstractions": {
 | 
			
		||||
| 
						 | 
				
			
			@ -595,10 +564,10 @@
 | 
			
		|||
      },
 | 
			
		||||
      "Microsoft.Extensions.Configuration.Binder": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.0.2",
 | 
			
		||||
        "contentHash": "krJ04xR0aPXrOf5dkNASg6aJjsdzexvsMRL6UNOUjiTzqBvRr95sJ1owoKEm89bSONQCfZNhHrAFV9ahDqIPIw==",
 | 
			
		||||
        "resolved": "9.0.0",
 | 
			
		||||
        "contentHash": "RiScL99DcyngY9zJA2ROrri7Br8tn5N4hP4YNvGdTN/bvg1A3dwvDOxHnNZ3Im7x2SJ5i4LkX1uPiR/MfSFBLQ==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Abstractions": "9.0.2"
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Abstractions": "9.0.0"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.DependencyInjection": {
 | 
			
		||||
| 
						 | 
				
			
			@ -614,14 +583,6 @@
 | 
			
		|||
        "resolved": "9.0.2",
 | 
			
		||||
        "contentHash": "MNe7GSTBf3jQx5vYrXF0NZvn6l7hUKF6J54ENfAgCO8y6xjN1XUmKKWG464LP2ye6QqDiA1dkaWEZBYnhoZzjg=="
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.DependencyInjection.AutoActivation": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.2.0",
 | 
			
		||||
        "contentHash": "WcwfTpl3IcPcaahTVEaJwMUg1eWog1SkIA6jQZZFqMXiMX9/tVkhNB6yzUQmBdGWdlWDDRKpOmK7T7x1Uu05pQ==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.Hosting.Abstractions": "9.0.2"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.DependencyModel": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.0.2",
 | 
			
		||||
| 
						 | 
				
			
			@ -629,74 +590,54 @@
 | 
			
		|||
      },
 | 
			
		||||
      "Microsoft.Extensions.Diagnostics": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.0.2",
 | 
			
		||||
        "contentHash": "kwFWk6DPaj1Roc0CExRv+TTwjsiERZA730jQIPlwCcS5tMaCAQtaGfwAK0z8CMFpVTiT+MgKXpd/P50qVCuIgg==",
 | 
			
		||||
        "resolved": "9.0.0",
 | 
			
		||||
        "contentHash": "0CF9ZrNw5RAlRfbZuVIvzzhP8QeWqHiUmMBU/2H7Nmit8/vwP3/SbHeEctth7D4Gz2fBnEbokPc1NU8/j/1ZLw==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.Configuration": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.2"
 | 
			
		||||
          "Microsoft.Extensions.Configuration": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.0"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.Diagnostics.Abstractions": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.0.2",
 | 
			
		||||
        "contentHash": "kFwIZEC/37cwKuEm/nXvjF7A/Myz9O7c7P9Csgz6AOiiDE62zdOG5Bu7VkROu1oMYaX0wgijPJ5LqVt6+JKjVg==",
 | 
			
		||||
        "resolved": "9.0.0",
 | 
			
		||||
        "contentHash": "1K8P7XzuzX8W8pmXcZjcrqS6x5eSSdvhQohmcpgiQNY/HlDAlnrhR9dvlURfFz428A+RTCJpUyB+aKTA6AgVcQ==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Options": "9.0.2"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.Diagnostics.ExceptionSummarization": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.2.0",
 | 
			
		||||
        "contentHash": "et5JevHsLv1w1O1Zhb6LiUfai/nmDRzIHnbrZJdzLsIbbMCKTZpeHuANYIppAD//n12KvgOne05j4cu0GhG9gw==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2"
 | 
			
		||||
          "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Options": "9.0.0"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.FileProviders.Abstractions": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.0.2",
 | 
			
		||||
        "contentHash": "IcOBmTlr2jySswU+3x8c3ql87FRwTVPQgVKaV5AXzPT5u0VItfNU8SMbESpdSp5STwxT/1R99WYszgHWsVkzhg==",
 | 
			
		||||
        "resolved": "9.0.0",
 | 
			
		||||
        "contentHash": "uK439QzYR0q2emLVtYzwyK3x+T5bTY4yWsd/k/ZUS9LR6Sflp8MIdhGXW8kQCd86dQD4tLqvcbLkku8qHY263Q==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.Primitives": "9.0.2"
 | 
			
		||||
          "Microsoft.Extensions.Primitives": "9.0.0"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.Hosting.Abstractions": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.0.2",
 | 
			
		||||
        "contentHash": "PvjZW6CMdZbPbOwKsQXYN5VPtIWZQqdTRuBPZiW3skhU3hymB17XSlLVC4uaBbDZU+/3eHG3p80y+MzZxZqR7Q==",
 | 
			
		||||
        "resolved": "9.0.0",
 | 
			
		||||
        "contentHash": "yUKJgu81ExjvqbNWqZKshBbLntZMbMVz/P7Way2SBx7bMqA08Mfdc9O7hWDKAiSp+zPUGT6LKcSCQIPeDK+CCw==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.FileProviders.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Logging.Abstractions": "9.0.2"
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Abstractions": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.FileProviders.Abstractions": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Logging.Abstractions": "9.0.0"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.Http": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.0.2",
 | 
			
		||||
        "contentHash": "34+kcwxPZr3Owk9eZx268+gqGNB8G/8Y96gZHomxam0IOH08FhPBjPrLWDtKdVn4+sVUUJnJMpECSTJi4XXCcg==",
 | 
			
		||||
        "resolved": "9.0.0",
 | 
			
		||||
        "contentHash": "DqI4q54U4hH7bIAq9M5a/hl5Odr/KBAoaZ0dcT4OgutD8dook34CbkvAfAIzkMVjYXiL+E5ul9etwwqiX4PHGw==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Diagnostics": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Logging": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Logging.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Options": "9.0.2"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.Http.Diagnostics": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.2.0",
 | 
			
		||||
        "contentHash": "Eeup1LuD5hVk5SsKAuX1D7I9sF380MjrNG10IaaauRLOmrRg8rq2TA8PYTXVBXf3MLkZ6m2xpBqRbZdxf8ygkg==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.DependencyInjection.AutoActivation": "9.2.0",
 | 
			
		||||
          "Microsoft.Extensions.Http": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Telemetry": "9.2.0",
 | 
			
		||||
          "System.IO.Pipelines": "9.0.2"
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Abstractions": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Diagnostics": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Logging": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Logging.Abstractions": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Options": "9.0.0"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.Logging": {
 | 
			
		||||
| 
						 | 
				
			
			@ -719,23 +660,23 @@
 | 
			
		|||
      },
 | 
			
		||||
      "Microsoft.Extensions.Logging.Configuration": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.0.2",
 | 
			
		||||
        "contentHash": "pnwYZE7U6d3Y6iMVqADOAUUMMBGYAQPsT3fMwVr/V1Wdpe5DuVGFcViZavUthSJ5724NmelIl1cYy+kRfKfRPQ==",
 | 
			
		||||
        "resolved": "9.0.0",
 | 
			
		||||
        "contentHash": "H05HiqaNmg6GjH34ocYE9Wm1twm3Oz2aXZko8GTwGBzM7op2brpAA8pJ5yyD1OpS1mXUtModBYOlcZ/wXeWsSg==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.Configuration": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Binder": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Logging": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Logging.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Options": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.2"
 | 
			
		||||
          "Microsoft.Extensions.Configuration": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Abstractions": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Binder": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Logging": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Logging.Abstractions": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Options": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.0"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.ObjectPool": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.0.2",
 | 
			
		||||
        "contentHash": "nWx7uY6lfkmtpyC2dGc0IxtrZZs/LnLCQHw3YYQucbqWj8a27U/dZ+eh72O3ZiolqLzzLkVzoC+w/M8dZwxRTw=="
 | 
			
		||||
        "resolved": "7.0.0",
 | 
			
		||||
        "contentHash": "udvKco0sAVgYGTBnHUb0tY9JQzJ/nPDiv/8PIyz69wl1AibeCDZOLVVI+6156dPfHmJH7ws5oUJRiW4ZmAvuuA=="
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.Options": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
| 
						 | 
				
			
			@ -748,14 +689,14 @@
 | 
			
		|||
      },
 | 
			
		||||
      "Microsoft.Extensions.Options.ConfigurationExtensions": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.0.2",
 | 
			
		||||
        "contentHash": "OPm1NXdMg4Kb4Kz+YHdbBQfekh7MqQZ7liZ5dYUd+IbJakinv9Fl7Ck6Strbgs0a6E76UGbP/jHR532K/7/feQ==",
 | 
			
		||||
        "resolved": "9.0.0",
 | 
			
		||||
        "contentHash": "Ob3FXsXkcSMQmGZi7qP07EQ39kZpSBlTcAZLbJLdI4FIf0Jug8biv2HTavWmnTirchctPlq9bl/26CXtQRguzA==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Binder": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Options": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Primitives": "9.0.2"
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Abstractions": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Configuration.Binder": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Options": "9.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Primitives": "9.0.0"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.Primitives": {
 | 
			
		||||
| 
						 | 
				
			
			@ -763,42 +704,6 @@
 | 
			
		|||
        "resolved": "9.0.2",
 | 
			
		||||
        "contentHash": "puBMtKe/wLuYa7H6docBkLlfec+h8L35DXqsDKKJgW0WY5oCwJ3cBJKcDaZchv6knAyqOMfsl6VUbaR++E5LXA=="
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.Resilience": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.2.0",
 | 
			
		||||
        "contentHash": "dyaM+Jeznh/i21bOrrRs3xceFfn0571EOjOq95dRXmL1rHDLC4ExhACJ2xipRBP6g1AgRNqmryi+hMrVWWgmlg==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.Diagnostics": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Diagnostics.ExceptionSummarization": "9.2.0",
 | 
			
		||||
          "Microsoft.Extensions.Options.ConfigurationExtensions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Telemetry.Abstractions": "9.2.0",
 | 
			
		||||
          "Polly.Extensions": "8.4.2",
 | 
			
		||||
          "Polly.RateLimiting": "8.4.2"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.Telemetry": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.2.0",
 | 
			
		||||
        "contentHash": "4+bw7W4RrAMrND9TxonnSmzJOdXiPxljoda8OPJiReIN607mKCc0t0Mf28sHNsTujO1XQw28wsI0poxeeQxohw==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.AmbientMetadata.Application": "9.2.0",
 | 
			
		||||
          "Microsoft.Extensions.DependencyInjection.AutoActivation": "9.2.0",
 | 
			
		||||
          "Microsoft.Extensions.Logging.Configuration": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.ObjectPool": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Telemetry.Abstractions": "9.2.0"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.Extensions.Telemetry.Abstractions": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.2.0",
 | 
			
		||||
        "contentHash": "kEl+5G3RqS20XaEhHh/nOugcjKEK+rgVtMJra1iuwNzdzQXElelf3vu8TugcT7rIZ/T4T76EKW1OX/fmlxz4hw==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.Compliance.Abstractions": "9.2.0",
 | 
			
		||||
          "Microsoft.Extensions.Logging.Abstractions": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.ObjectPool": "9.0.2",
 | 
			
		||||
          "Microsoft.Extensions.Options": "9.0.2"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Microsoft.NETCore.Platforms": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "1.1.1",
 | 
			
		||||
| 
						 | 
				
			
			@ -855,30 +760,6 @@
 | 
			
		|||
          "System.IO.Pipelines": "5.0.1"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Polly.Core": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "8.4.2",
 | 
			
		||||
        "contentHash": "BpE2I6HBYYA5tF0Vn4eoQOGYTYIK1BlF5EXVgkWGn3mqUUjbXAr13J6fZVbp7Q3epRR8yshacBMlsHMhpOiV3g=="
 | 
			
		||||
      },
 | 
			
		||||
      "Polly.Extensions": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "8.4.2",
 | 
			
		||||
        "contentHash": "GZ9vRVmR0jV2JtZavt+pGUsQ1O1cuRKG7R7VOZI6ZDy9y6RNPvRvXK1tuS4ffUrv8L0FTea59oEuQzgS0R7zSA==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Microsoft.Extensions.Logging.Abstractions": "8.0.0",
 | 
			
		||||
          "Microsoft.Extensions.Options": "8.0.0",
 | 
			
		||||
          "Polly.Core": "8.4.2"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Polly.RateLimiting": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "8.4.2",
 | 
			
		||||
        "contentHash": "ehTImQ/eUyO07VYW2WvwSmU9rRH200SKJ/3jku9rOkyWE0A2JxNFmAVms8dSn49QLSjmjFRRSgfNyOgr/2PSmA==",
 | 
			
		||||
        "dependencies": {
 | 
			
		||||
          "Polly.Core": "8.4.2",
 | 
			
		||||
          "System.Threading.RateLimiting": "8.0.0"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "Sentry": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "5.3.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -1020,8 +901,8 @@
 | 
			
		|||
      },
 | 
			
		||||
      "System.IO.Pipelines": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "9.0.2",
 | 
			
		||||
        "contentHash": "UIBaK7c/A3FyQxmX/747xw4rCUkm1BhNiVU617U5jweNJssNjLJkPUGhBsrlDG0BpKWCYKsncD+Kqpy4KmvZZQ=="
 | 
			
		||||
        "resolved": "7.0.0",
 | 
			
		||||
        "contentHash": "jRn6JYnNPW6xgQazROBLSfpdoczRw694vO5kKvMcNnpXuolEixUyw6IBuBs2Y2mlSX/LdLvyyWmfXhaI3ND1Yg=="
 | 
			
		||||
      },
 | 
			
		||||
      "System.Reactive": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
| 
						 | 
				
			
			@ -1059,11 +940,6 @@
 | 
			
		|||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "7.0.0",
 | 
			
		||||
        "contentHash": "qmeeYNROMsONF6ndEZcIQ+VxR4Q/TX/7uIVLJqtwIWL7dDWeh0l1UIqgo4wYyjG//5lUNhwkLDSFl+pAWO6oiA=="
 | 
			
		||||
      },
 | 
			
		||||
      "System.Threading.RateLimiting": {
 | 
			
		||||
        "type": "Transitive",
 | 
			
		||||
        "resolved": "8.0.0",
 | 
			
		||||
        "contentHash": "7mu9v0QDv66ar3DpGSZHg9NuNcxDaaAcnMULuZlaTpP9+hwXhrxNGsF5GmLkSHxFdb5bBc1TzeujsRgTrPWi+Q=="
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,7 +21,6 @@
 | 
			
		|||
		"@types/markdown-it": "^14.1.2",
 | 
			
		||||
		"@types/sanitize-html": "^2.13.0",
 | 
			
		||||
		"bootstrap": "^5.3.3",
 | 
			
		||||
		"dotenv": "^16.4.7",
 | 
			
		||||
		"eslint": "^9.17.0",
 | 
			
		||||
		"eslint-config-prettier": "^9.1.0",
 | 
			
		||||
		"eslint-plugin-svelte": "^2.46.1",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										3
									
								
								Foxnouns.Frontend/pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								Foxnouns.Frontend/pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -72,9 +72,6 @@ importers:
 | 
			
		|||
      bootstrap:
 | 
			
		||||
        specifier: ^5.3.3
 | 
			
		||||
        version: 5.3.3(@popperjs/core@2.11.8)
 | 
			
		||||
      dotenv:
 | 
			
		||||
        specifier: ^16.4.7
 | 
			
		||||
        version: 16.4.7
 | 
			
		||||
      eslint:
 | 
			
		||||
        specifier: ^9.17.0
 | 
			
		||||
        version: 9.17.0
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,14 +1,5 @@
 | 
			
		|||
import adapter from "@sveltejs/adapter-node";
 | 
			
		||||
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
 | 
			
		||||
import * as path from "node:path";
 | 
			
		||||
 | 
			
		||||
import { config as dotenv } from "dotenv";
 | 
			
		||||
dotenv({
 | 
			
		||||
	path: [path.resolve(process.cwd(), ".env"), path.resolve(process.cwd(), ".env.local")],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
console.log(process.env.NODE_ENV);
 | 
			
		||||
const isProd = process.env.NODE_ENV === "production";
 | 
			
		||||
 | 
			
		||||
/** @type {import('@sveltejs/kit').Config} */
 | 
			
		||||
const config = {
 | 
			
		||||
| 
						 | 
				
			
			@ -30,9 +21,6 @@ const config = {
 | 
			
		|||
			// we only disable it during development, during building NODE_ENV == production
 | 
			
		||||
			checkOrigin: process.env.NODE_ENV !== "development",
 | 
			
		||||
		},
 | 
			
		||||
		paths: {
 | 
			
		||||
			assets: isProd ? process.env.PRIVATE_ASSETS_PREFIX || "" : "",
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue