feat(backend): initial data export support
obviously it's missing things that haven't been added yet
This commit is contained in:
parent
f0ae648492
commit
903be2709c
15 changed files with 502 additions and 24 deletions
47
Foxnouns.Backend/Services/DataCleanupService.cs
Normal file
47
Foxnouns.Backend/Services/DataCleanupService.cs
Normal 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";
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue