feat(backend): initial data export support

obviously it's missing things that haven't been added yet
This commit is contained in:
sam 2024-12-02 18:06:19 +01:00
parent f0ae648492
commit 903be2709c
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
15 changed files with 502 additions and 24 deletions

View file

@ -0,0 +1,47 @@
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace Foxnouns.Backend.Services;
public class DataCleanupService(
DatabaseContext db,
IClock clock,
ILogger logger,
ObjectStorageService objectStorageService
)
{
private readonly ILogger _logger = logger.ForContext<DataCleanupService>();
public async Task InvokeAsync(CancellationToken ct = default)
{
_logger.Information("Cleaning up expired data exports");
await CleanExportsAsync(ct);
}
private async Task CleanExportsAsync(CancellationToken ct = default)
{
var minExpiredId = Snowflake.FromInstant(clock.GetCurrentInstant() - DataExport.Expiration);
var exports = await db.DataExports.Where(d => d.Id < minExpiredId).ToListAsync(ct);
if (exports.Count == 0)
return;
_logger.Debug("There are {Count} expired exports", exports.Count);
foreach (var export in exports)
{
_logger.Debug("Deleting export {ExportId}", export.Id);
await objectStorageService.RemoveObjectAsync(
ExportPath(export.UserId, export.Filename),
ct
);
db.Remove(export);
}
await db.SaveChangesAsync(ct);
}
private static string ExportPath(Snowflake userId, string b64) =>
$"data-exports/{userId}/{b64}.zip";
}

View file

@ -72,7 +72,7 @@ public class MemberRendererService(DatabaseContext db, Config config)
renderUnlisted ? member.Unlisted : null
);
private string? AvatarUrlFor(Member member) =>
public string? AvatarUrlFor(Member member) =>
member.Avatar != null
? $"{config.MediaBaseUrl}/members/{member.Id}/avatars/{member.Avatar}.webp"
: null;

View file

@ -62,7 +62,7 @@ public class BackgroundMetricsCollectionService(
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
while (await timer.WaitForNextTickAsync(ct))
{
_logger.Debug("Collecting metrics");
_logger.Debug("Collecting metrics manually");
await metricsCollectionService.CollectMetricsAsync(ct);
}
}

View file

@ -48,4 +48,13 @@ public class ObjectStorageService(ILogger logger, Config config, IMinioClient mi
ct
);
}
public async Task GetObjectAsync(string path, CancellationToken ct = default)
{
var stream = new MemoryStream();
var resp = await minioClient.GetObjectAsync(
new GetObjectArgs().WithBucket(config.Storage.Bucket).WithObject(path),
ct
);
}
}

View file

@ -8,10 +8,7 @@ public class PeriodicTasksService(ILogger logger, IServiceProvider services) : B
{
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1));
while (await timer.WaitForNextTickAsync(ct))
{
_logger.Debug("Collecting metrics");
await RunPeriodicTasksAsync(ct);
}
}
private async Task RunPeriodicTasksAsync(CancellationToken ct)
@ -20,7 +17,10 @@ public class PeriodicTasksService(ILogger logger, IServiceProvider services) : B
await using var scope = services.CreateAsyncScope();
var keyCacheSvc = scope.ServiceProvider.GetRequiredService<KeyCacheService>();
await keyCacheSvc.DeleteExpiredKeysAsync(ct);
var keyCacheService = scope.ServiceProvider.GetRequiredService<KeyCacheService>();
var dataCleanupService = scope.ServiceProvider.GetRequiredService<DataCleanupService>();
await keyCacheService.DeleteExpiredKeysAsync(ct);
await dataCleanupService.InvokeAsync(ct);
}
}

View file

@ -22,12 +22,31 @@ public class UserRendererService(
bool renderAuthMethods = false,
string? overrideSid = null,
CancellationToken ct = default
) =>
await RenderUserInnerAsync(
user,
selfUser != null && selfUser.Id == user.Id,
token?.Scopes ?? [],
renderMembers,
renderAuthMethods,
overrideSid,
ct
);
public async Task<UserResponse> RenderUserInnerAsync(
User user,
bool isSelfUser,
string[] scopes,
bool renderMembers = true,
bool renderAuthMethods = false,
string? overrideSid = null,
CancellationToken ct = default
)
{
var isSelfUser = selfUser?.Id == user.Id;
var tokenCanReadHiddenMembers = token.HasScope("member.read") && isSelfUser;
var tokenHidden = token.HasScope("user.read_hidden") && isSelfUser;
var tokenPrivileged = token.HasScope("user.read_privileged") && isSelfUser;
scopes = scopes.ExpandScopes();
var tokenCanReadHiddenMembers = scopes.Contains("member.read") && isSelfUser;
var tokenHidden = scopes.Contains("user.read_hidden") && isSelfUser;
var tokenPrivileged = scopes.Contains("user.read_privileged") && isSelfUser;
renderMembers = renderMembers && (!user.ListHidden || tokenCanReadHiddenMembers);
renderAuthMethods = renderAuthMethods && tokenPrivileged;
@ -105,12 +124,12 @@ public class UserRendererService(
user.CustomPreferences
);
private string? AvatarUrlFor(User user) =>
public string? AvatarUrlFor(User user) =>
user.Avatar != null
? $"{config.MediaBaseUrl}/users/{user.Id}/avatars/{user.Avatar}.webp"
: null;
private string ImageUrlFor(PrideFlag flag) => $"{config.MediaBaseUrl}/flags/{flag.Hash}.webp";
public string ImageUrlFor(PrideFlag flag) => $"{config.MediaBaseUrl}/flags/{flag.Hash}.webp";
public record UserResponse(
Snowflake Id,