forked from Kieran/void.cat
Decouple user upload store
This commit is contained in:
parent
1f484f243d
commit
cba4d5fc80
@ -68,8 +68,7 @@ namespace VoidCat.Controllers
|
|||||||
Name = filename,
|
Name = filename,
|
||||||
Description = Request.Headers.GetHeader("V-Description"),
|
Description = Request.Headers.GetHeader("V-Description"),
|
||||||
Digest = Request.Headers.GetHeader("V-Full-Digest"),
|
Digest = Request.Headers.GetHeader("V-Full-Digest"),
|
||||||
Size = (ulong?) Request.ContentLength ?? 0UL,
|
Size = (ulong?) Request.ContentLength ?? 0UL
|
||||||
Uploader = uid
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var digest = Request.Headers.GetHeader("V-Digest");
|
var digest = Request.Headers.GetHeader("V-Digest");
|
||||||
@ -80,13 +79,13 @@ namespace VoidCat.Controllers
|
|||||||
|
|
||||||
// save metadata
|
// save metadata
|
||||||
await _metadata.Set(vf.Id, vf.Metadata!);
|
await _metadata.Set(vf.Id, vf.Metadata!);
|
||||||
|
|
||||||
// attach file upload to user
|
// attach file upload to user
|
||||||
if (uid.HasValue)
|
if (uid.HasValue)
|
||||||
{
|
{
|
||||||
await _userUploads.AddFile(uid!.Value, vf);
|
await _userUploads.AddFile(uid!.Value, vf);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cli)
|
if (cli)
|
||||||
{
|
{
|
||||||
var urlBuilder = new UriBuilder(Request.IsHttps ? "https" : "http", Request.Host.Host,
|
var urlBuilder = new UriBuilder(Request.IsHttps ? "https" : "http", Request.Host.Host,
|
||||||
@ -137,7 +136,7 @@ namespace VoidCat.Controllers
|
|||||||
Id = gid,
|
Id = gid,
|
||||||
IsAppend = true
|
IsAppend = true
|
||||||
}, HttpContext.RequestAborted);
|
}, HttpContext.RequestAborted);
|
||||||
|
|
||||||
// update file size
|
// update file size
|
||||||
await _metadata.Set(vf.Id, vf.Metadata!);
|
await _metadata.Set(vf.Id, vf.Metadata!);
|
||||||
return UploadResult.Success(vf);
|
return UploadResult.Success(vf);
|
||||||
@ -155,9 +154,13 @@ namespace VoidCat.Controllers
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("{id}")]
|
[Route("{id}")]
|
||||||
public ValueTask<PublicVoidFile?> GetInfo([FromRoute] string id)
|
public async Task<IActionResult> GetInfo([FromRoute] string id)
|
||||||
{
|
{
|
||||||
return _fileInfo.Get(id.FromBase58Guid());
|
var fid = id.FromBase58Guid();
|
||||||
|
var uid = HttpContext.GetUserId();
|
||||||
|
var isOwner = uid.HasValue && await _userUploads.Uploader(fid) == uid;
|
||||||
|
|
||||||
|
return isOwner ? Json(await _fileInfo.GetPrivate(fid)) : Json(await _fileInfo.Get(fid));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -207,7 +210,7 @@ namespace VoidCat.Controllers
|
|||||||
var gid = id.FromBase58Guid();
|
var gid = id.FromBase58Guid();
|
||||||
var meta = await _metadata.Get<SecretVoidFileMeta>(gid);
|
var meta = await _metadata.Get<SecretVoidFileMeta>(gid);
|
||||||
if (meta == default) return NotFound();
|
if (meta == default) return NotFound();
|
||||||
if (!meta.CanEdit(req.EditSecret, HttpContext)) return Unauthorized();
|
if (!meta.CanEdit(req.EditSecret)) return Unauthorized();
|
||||||
|
|
||||||
if (req.Strike != default)
|
if (req.Strike != default)
|
||||||
{
|
{
|
||||||
@ -236,7 +239,7 @@ namespace VoidCat.Controllers
|
|||||||
var gid = id.FromBase58Guid();
|
var gid = id.FromBase58Guid();
|
||||||
var meta = await _metadata.Get<SecretVoidFileMeta>(gid);
|
var meta = await _metadata.Get<SecretVoidFileMeta>(gid);
|
||||||
if (meta == default) return NotFound();
|
if (meta == default) return NotFound();
|
||||||
if (!meta.CanEdit(fileMeta.EditSecret, HttpContext)) return Unauthorized();
|
if (!meta.CanEdit(fileMeta.EditSecret)) return Unauthorized();
|
||||||
|
|
||||||
await _metadata.Update(gid, fileMeta);
|
await _metadata.Update(gid, fileMeta);
|
||||||
return Ok();
|
return Ok();
|
||||||
|
@ -10,12 +10,14 @@ public class UserController : Controller
|
|||||||
private readonly IUserStore _store;
|
private readonly IUserStore _store;
|
||||||
private readonly IUserUploadsStore _userUploads;
|
private readonly IUserUploadsStore _userUploads;
|
||||||
private readonly IEmailVerification _emailVerification;
|
private readonly IEmailVerification _emailVerification;
|
||||||
|
private readonly IFileInfoManager _fileInfoManager;
|
||||||
|
|
||||||
public UserController(IUserStore store, IUserUploadsStore userUploads, IEmailVerification emailVerification)
|
public UserController(IUserStore store, IUserUploadsStore userUploads, IEmailVerification emailVerification, IFileInfoManager fileInfoManager)
|
||||||
{
|
{
|
||||||
_store = store;
|
_store = store;
|
||||||
_userUploads = userUploads;
|
_userUploads = userUploads;
|
||||||
_emailVerification = emailVerification;
|
_emailVerification = emailVerification;
|
||||||
|
_fileInfoManager = fileInfoManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -95,7 +97,15 @@ public class UserController : Controller
|
|||||||
!user.Flags.HasFlag(VoidUserFlags.PublicUploads)) return Forbid();
|
!user.Flags.HasFlag(VoidUserFlags.PublicUploads)) return Forbid();
|
||||||
|
|
||||||
var results = await _userUploads.ListFiles(id.FromBase58Guid(), request);
|
var results = await _userUploads.ListFiles(id.FromBase58Guid(), request);
|
||||||
return Json(await results.GetResults());
|
var files = await results.Results.ToListAsync();
|
||||||
|
var fileInfo = await Task.WhenAll(files.Select(a => _fileInfoManager.Get(a).AsTask()));
|
||||||
|
return Json(new RenderedResults<PublicVoidFile>()
|
||||||
|
{
|
||||||
|
PageSize = results.PageSize,
|
||||||
|
Page = results.Page,
|
||||||
|
TotalResults = results.TotalResults,
|
||||||
|
Results = fileInfo.Where(a => a != null).ToList()!
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -61,10 +61,9 @@ public static class Extensions
|
|||||||
return !string.IsNullOrEmpty(h.Value.ToString()) ? h.Value.ToString() : default;
|
return !string.IsNullOrEmpty(h.Value.ToString()) ? h.Value.ToString() : default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool CanEdit(this SecretVoidFileMeta file, Guid? editSecret, HttpContext context)
|
public static bool CanEdit(this SecretVoidFileMeta file, Guid? editSecret)
|
||||||
{
|
{
|
||||||
return file.EditSecret == editSecret
|
return file.EditSecret == editSecret;
|
||||||
|| file.Uploader == context.GetUserId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ToHex(this byte[] data)
|
public static string ToHex(this byte[] data)
|
||||||
|
@ -65,12 +65,6 @@ public record VoidFileMeta : IVoidFileMeta
|
|||||||
/// Url to download the file
|
/// Url to download the file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Uri? Url { get; set; }
|
public Uri? Url { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// User who uploaded the file
|
|
||||||
/// </summary>
|
|
||||||
[JsonConverter(typeof(Base58GuidConverter))]
|
|
||||||
public Guid? Uploader { get; init; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -158,9 +158,8 @@ services.AddCaptcha(voidSettings);
|
|||||||
// postgres
|
// postgres
|
||||||
if (!string.IsNullOrEmpty(voidSettings.Postgres))
|
if (!string.IsNullOrEmpty(voidSettings.Postgres))
|
||||||
{
|
{
|
||||||
services.AddScoped<OpenDatabase>();
|
services.AddSingleton<PostgresConnectionFactory>();
|
||||||
services.AddScoped((_) => new NpgsqlConnection(voidSettings.Postgres));
|
services.AddTransient<IDbConnection>(_ => new NpgsqlConnection(voidSettings.Postgres));
|
||||||
services.AddScoped<IDbConnection>((svc) => svc.GetRequiredService<NpgsqlConnection>());
|
|
||||||
|
|
||||||
// fluent migrations
|
// fluent migrations
|
||||||
services.AddTransient<IMigration, FluentMigrationRunner>();
|
services.AddTransient<IMigration, FluentMigrationRunner>();
|
||||||
@ -221,11 +220,6 @@ app.UseSwaggerUI();
|
|||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(voidSettings.Postgres))
|
|
||||||
{
|
|
||||||
app.UseMiddleware<OpenDatabase>();
|
|
||||||
}
|
|
||||||
|
|
||||||
app.UseEndpoints(ep =>
|
app.UseEndpoints(ep =>
|
||||||
{
|
{
|
||||||
ep.MapControllers();
|
ep.MapControllers();
|
||||||
|
@ -14,6 +14,13 @@ public interface IFileInfoManager
|
|||||||
/// <param name="id"></param>
|
/// <param name="id"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask<PublicVoidFile?> Get(Guid id);
|
ValueTask<PublicVoidFile?> Get(Guid id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get all private metadata for a single file
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
ValueTask<PrivateVoidFile?> GetPrivate(Guid id);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all metadata for multiple files
|
/// Get all metadata for multiple files
|
||||||
|
@ -2,8 +2,31 @@ using VoidCat.Model;
|
|||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mapping store to associate files to users
|
||||||
|
/// </summary>
|
||||||
public interface IUserUploadsStore
|
public interface IUserUploadsStore
|
||||||
{
|
{
|
||||||
ValueTask<PagedResult<PublicVoidFile>> ListFiles(Guid user, PagedRequest request);
|
/// <summary>
|
||||||
|
/// List all files for the user
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
ValueTask<PagedResult<Guid>> ListFiles(Guid user, PagedRequest request);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Assign a file upload to a user
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <param name="voidFile"></param>
|
||||||
|
/// <returns></returns>
|
||||||
ValueTask AddFile(Guid user, PrivateVoidFile voidFile);
|
ValueTask AddFile(Guid user, PrivateVoidFile voidFile);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the uploader of a single file
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
ValueTask<Guid?> Uploader(Guid file);
|
||||||
}
|
}
|
||||||
|
@ -40,8 +40,8 @@ public class DeleteUnverifiedAccounts : BackgroundService
|
|||||||
// ReSharper disable once UseCancellationTokenForIAsyncEnumerable
|
// ReSharper disable once UseCancellationTokenForIAsyncEnumerable
|
||||||
await foreach (var file in files.Results)
|
await foreach (var file in files.Results)
|
||||||
{
|
{
|
||||||
await fileStore.DeleteFile(file.Id);
|
await fileStore.DeleteFile(file);
|
||||||
await fileInfoManager.Delete(file.Id);
|
await fileInfoManager.Delete(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,40 +11,29 @@ public class FileInfoManager : IFileInfoManager
|
|||||||
private readonly IStatsReporter _statsReporter;
|
private readonly IStatsReporter _statsReporter;
|
||||||
private readonly IUserStore _userStore;
|
private readonly IUserStore _userStore;
|
||||||
private readonly IVirusScanStore _virusScanStore;
|
private readonly IVirusScanStore _virusScanStore;
|
||||||
|
private readonly IUserUploadsStore _userUploadsStore;
|
||||||
|
|
||||||
public FileInfoManager(IFileMetadataStore metadataStore, IPaywallStore paywallStore, IStatsReporter statsReporter,
|
public FileInfoManager(IFileMetadataStore metadataStore, IPaywallStore paywallStore, IStatsReporter statsReporter,
|
||||||
IUserStore userStore, IVirusScanStore virusScanStore)
|
IUserStore userStore, IVirusScanStore virusScanStore, IUserUploadsStore userUploadsStore)
|
||||||
{
|
{
|
||||||
_metadataStore = metadataStore;
|
_metadataStore = metadataStore;
|
||||||
_paywallStore = paywallStore;
|
_paywallStore = paywallStore;
|
||||||
_statsReporter = statsReporter;
|
_statsReporter = statsReporter;
|
||||||
_userStore = userStore;
|
_userStore = userStore;
|
||||||
_virusScanStore = virusScanStore;
|
_virusScanStore = virusScanStore;
|
||||||
|
_userUploadsStore = userUploadsStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<PublicVoidFile?> Get(Guid id)
|
public ValueTask<PublicVoidFile?> Get(Guid id)
|
||||||
{
|
{
|
||||||
var meta = _metadataStore.Get<VoidFileMeta>(id);
|
return Get<PublicVoidFile, VoidFileMeta>(id);
|
||||||
var paywall = _paywallStore.Get(id);
|
}
|
||||||
var bandwidth = _statsReporter.GetBandwidth(id);
|
|
||||||
var virusScan = _virusScanStore.Get(id);
|
|
||||||
await Task.WhenAll(meta.AsTask(), paywall.AsTask(), bandwidth.AsTask(), virusScan.AsTask());
|
|
||||||
|
|
||||||
if (meta.Result == default) return default;
|
/// <inheritdoc />
|
||||||
|
public ValueTask<PrivateVoidFile?> GetPrivate(Guid id)
|
||||||
var uploader = meta.Result?.Uploader;
|
{
|
||||||
var user = uploader.HasValue ? await _userStore.Get<PublicVoidUser>(uploader.Value) : null;
|
return Get<PrivateVoidFile, SecretVoidFileMeta>(id);
|
||||||
|
|
||||||
return new()
|
|
||||||
{
|
|
||||||
Id = id,
|
|
||||||
Metadata = meta.Result,
|
|
||||||
Paywall = paywall.Result,
|
|
||||||
Bandwidth = bandwidth.Result,
|
|
||||||
Uploader = user?.Flags.HasFlag(VoidUserFlags.PublicProfile) == true ? user : null,
|
|
||||||
VirusScan = virusScan.Result
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -71,4 +60,28 @@ public class FileInfoManager : IFileInfoManager
|
|||||||
await _statsReporter.Delete(id);
|
await _statsReporter.Delete(id);
|
||||||
await _virusScanStore.Delete(id);
|
await _virusScanStore.Delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async ValueTask<TFile?> Get<TFile, TMeta>(Guid id)
|
||||||
|
where TMeta : VoidFileMeta where TFile : VoidFile<TMeta>, new()
|
||||||
|
{
|
||||||
|
var meta = _metadataStore.Get<TMeta>(id);
|
||||||
|
var paywall = _paywallStore.Get(id);
|
||||||
|
var bandwidth = _statsReporter.GetBandwidth(id);
|
||||||
|
var virusScan = _virusScanStore.Get(id);
|
||||||
|
var uploader = _userUploadsStore.Uploader(id);
|
||||||
|
await Task.WhenAll(meta.AsTask(), paywall.AsTask(), bandwidth.AsTask(), virusScan.AsTask(), uploader.AsTask());
|
||||||
|
|
||||||
|
if (meta.Result == default) return default;
|
||||||
|
var user = uploader.Result.HasValue ? await _userStore.Get<PublicVoidUser>(uploader.Result.Value) : null;
|
||||||
|
|
||||||
|
return new TFile()
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Metadata = meta.Result,
|
||||||
|
Paywall = paywall.Result,
|
||||||
|
Bandwidth = bandwidth.Result,
|
||||||
|
Uploader = user?.Flags.HasFlag(VoidUserFlags.PublicProfile) == true ? user : null,
|
||||||
|
VirusScan = virusScan.Result
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,5 +1,4 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using Npgsql;
|
|
||||||
using VoidCat.Model;
|
using VoidCat.Model;
|
||||||
using VoidCat.Services.Abstractions;
|
using VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
@ -8,9 +7,9 @@ namespace VoidCat.Services.Files;
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public class PostgresFileMetadataStore : IFileMetadataStore
|
public class PostgresFileMetadataStore : IFileMetadataStore
|
||||||
{
|
{
|
||||||
private readonly NpgsqlConnection _connection;
|
private readonly PostgresConnectionFactory _connection;
|
||||||
|
|
||||||
public PostgresFileMetadataStore(NpgsqlConnection connection)
|
public PostgresFileMetadataStore(PostgresConnectionFactory connection)
|
||||||
{
|
{
|
||||||
_connection = connection;
|
_connection = connection;
|
||||||
}
|
}
|
||||||
@ -30,7 +29,8 @@ public class PostgresFileMetadataStore : IFileMetadataStore
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask Set(Guid id, SecretVoidFileMeta obj)
|
public async ValueTask Set(Guid id, SecretVoidFileMeta obj)
|
||||||
{
|
{
|
||||||
await _connection.ExecuteAsync(
|
await using var conn = await _connection.Get();
|
||||||
|
await conn.ExecuteAsync(
|
||||||
@"insert into
|
@"insert into
|
||||||
""Files""(""Id"", ""Name"", ""Size"", ""Uploaded"", ""Description"", ""MimeType"", ""Digest"", ""EditSecret"")
|
""Files""(""Id"", ""Name"", ""Size"", ""Uploaded"", ""Description"", ""MimeType"", ""Digest"", ""EditSecret"")
|
||||||
values(:id, :name, :size, :uploaded, :description, :mimeType, :digest, :editSecret)
|
values(:id, :name, :size, :uploaded, :description, :mimeType, :digest, :editSecret)
|
||||||
@ -50,20 +50,23 @@ on conflict (""Id"") do update set ""Name"" = :name, ""Description"" = :descript
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask Delete(Guid id)
|
public async ValueTask Delete(Guid id)
|
||||||
{
|
{
|
||||||
await _connection.ExecuteAsync("delete from \"Files\" where \"Id\" = :id", new {id});
|
await using var conn = await _connection.Get();
|
||||||
|
await conn.ExecuteAsync("delete from \"Files\" where \"Id\" = :id", new {id});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<TMeta?> Get<TMeta>(Guid id) where TMeta : VoidFileMeta
|
public async ValueTask<TMeta?> Get<TMeta>(Guid id) where TMeta : VoidFileMeta
|
||||||
{
|
{
|
||||||
return await _connection.QuerySingleOrDefaultAsync<TMeta?>(@"select * from ""Files"" where ""Id"" = :id",
|
await using var conn = await _connection.Get();
|
||||||
|
return await conn.QuerySingleOrDefaultAsync<TMeta?>(@"select * from ""Files"" where ""Id"" = :id",
|
||||||
new {id});
|
new {id});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<IReadOnlyList<TMeta>> Get<TMeta>(Guid[] ids) where TMeta : VoidFileMeta
|
public async ValueTask<IReadOnlyList<TMeta>> Get<TMeta>(Guid[] ids) where TMeta : VoidFileMeta
|
||||||
{
|
{
|
||||||
var ret = await _connection.QueryAsync<TMeta>("select * from \"Files\" where \"Id\" in :ids", new {ids});
|
await using var conn = await _connection.Get();
|
||||||
|
var ret = await conn.QueryAsync<TMeta>("select * from \"Files\" where \"Id\" in :ids", new {ids});
|
||||||
return ret.ToList();
|
return ret.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +86,8 @@ on conflict (""Id"") do update set ""Name"" = :name, ""Description"" = :descript
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<PagedResult<TMeta>> ListFiles<TMeta>(PagedRequest request) where TMeta : VoidFileMeta
|
public async ValueTask<PagedResult<TMeta>> ListFiles<TMeta>(PagedRequest request) where TMeta : VoidFileMeta
|
||||||
{
|
{
|
||||||
var count = await _connection.ExecuteScalarAsync<int>(@"select count(*) from ""Files""");
|
await using var conn = await _connection.Get();
|
||||||
|
var count = await conn.ExecuteScalarAsync<int>(@"select count(*) from ""Files""");
|
||||||
|
|
||||||
async IAsyncEnumerable<TMeta> Enumerate()
|
async IAsyncEnumerable<TMeta> Enumerate()
|
||||||
{
|
{
|
||||||
@ -94,8 +98,9 @@ on conflict (""Id"") do update set ""Name"" = :name, ""Description"" = :descript
|
|||||||
PagedSortBy.Size => "Size",
|
PagedSortBy.Size => "Size",
|
||||||
_ => "Id"
|
_ => "Id"
|
||||||
};
|
};
|
||||||
|
await using var iconn = await _connection.Get();
|
||||||
var orderDirection = request.SortOrder == PageSortOrder.Asc ? "asc" : "desc";
|
var orderDirection = request.SortOrder == PageSortOrder.Asc ? "asc" : "desc";
|
||||||
var results = await _connection.QueryAsync<TMeta>(
|
var results = await iconn.QueryAsync<TMeta>(
|
||||||
$"select * from \"Files\" order by \"{orderBy}\" {orderDirection} offset @offset limit @limit",
|
$"select * from \"Files\" order by \"{orderBy}\" {orderDirection} offset @offset limit @limit",
|
||||||
new {offset = request.PageSize * request.Page, limit = request.PageSize});
|
new {offset = request.PageSize * request.Page, limit = request.PageSize});
|
||||||
|
|
||||||
@ -117,7 +122,8 @@ on conflict (""Id"") do update set ""Name"" = :name, ""Description"" = :descript
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<IFileMetadataStore.StoreStats> Stats()
|
public async ValueTask<IFileMetadataStore.StoreStats> Stats()
|
||||||
{
|
{
|
||||||
var v = await _connection.QuerySingleAsync<(long Files, long Size)>(
|
await using var conn = await _connection.Get();
|
||||||
|
var v = await conn.QuerySingleAsync<(long Files, long Size)>(
|
||||||
@"select count(1) ""Files"", cast(sum(""Size"") as bigint) ""Size"" from ""Files""");
|
@"select count(1) ""Files"", cast(sum(""Size"") as bigint) ""Size"" from ""Files""");
|
||||||
return new(v.Files, (ulong) v.Size);
|
return new(v.Files, (ulong) v.Size);
|
||||||
}
|
}
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
using Npgsql;
|
|
||||||
|
|
||||||
public class OpenDatabase : IMiddleware
|
|
||||||
{
|
|
||||||
private readonly NpgsqlConnection _connection;
|
|
||||||
|
|
||||||
public OpenDatabase(NpgsqlConnection connection)
|
|
||||||
{
|
|
||||||
_connection = connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
|
||||||
{
|
|
||||||
await _connection.OpenAsync();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await next(context);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
await _connection.CloseAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
26
VoidCat/Services/PostgresConnectionFactory.cs
Normal file
26
VoidCat/Services/PostgresConnectionFactory.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using System.Data;
|
||||||
|
using Npgsql;
|
||||||
|
using VoidCat.Model;
|
||||||
|
|
||||||
|
namespace VoidCat.Services;
|
||||||
|
|
||||||
|
public sealed class PostgresConnectionFactory
|
||||||
|
{
|
||||||
|
private readonly VoidSettings _settings;
|
||||||
|
|
||||||
|
public PostgresConnectionFactory(VoidSettings settings)
|
||||||
|
{
|
||||||
|
_settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<NpgsqlConnection> Get()
|
||||||
|
{
|
||||||
|
var conn = new NpgsqlConnection(_settings.Postgres);
|
||||||
|
if (!conn.State.HasFlag(ConnectionState.Open))
|
||||||
|
{
|
||||||
|
await conn.OpenAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using Npgsql;
|
|
||||||
using VoidCat.Model;
|
using VoidCat.Model;
|
||||||
|
|
||||||
namespace VoidCat.Services.Users;
|
namespace VoidCat.Services.Users;
|
||||||
@ -7,10 +6,10 @@ namespace VoidCat.Services.Users;
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public class PostgresEmailVerification : BaseEmailVerification
|
public class PostgresEmailVerification : BaseEmailVerification
|
||||||
{
|
{
|
||||||
private readonly NpgsqlConnection _connection;
|
private readonly PostgresConnectionFactory _connection;
|
||||||
|
|
||||||
public PostgresEmailVerification(ILogger<BaseEmailVerification> logger, VoidSettings settings,
|
public PostgresEmailVerification(ILogger<BaseEmailVerification> logger, VoidSettings settings,
|
||||||
RazorPartialToStringRenderer renderer, NpgsqlConnection connection) : base(logger, settings, renderer)
|
RazorPartialToStringRenderer renderer, PostgresConnectionFactory connection) : base(logger, settings, renderer)
|
||||||
{
|
{
|
||||||
_connection = connection;
|
_connection = connection;
|
||||||
}
|
}
|
||||||
@ -18,7 +17,8 @@ public class PostgresEmailVerification : BaseEmailVerification
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override async ValueTask SaveToken(EmailVerificationCode code)
|
protected override async ValueTask SaveToken(EmailVerificationCode code)
|
||||||
{
|
{
|
||||||
await _connection.ExecuteAsync(
|
await using var conn = await _connection.Get();
|
||||||
|
await conn.ExecuteAsync(
|
||||||
@"insert into ""EmailVerification""(""User"", ""Code"", ""Expires"") values(:user, :code, :expires)",
|
@"insert into ""EmailVerification""(""User"", ""Code"", ""Expires"") values(:user, :code, :expires)",
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
@ -31,7 +31,8 @@ public class PostgresEmailVerification : BaseEmailVerification
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override async ValueTask<EmailVerificationCode?> GetToken(Guid user, Guid code)
|
protected override async ValueTask<EmailVerificationCode?> GetToken(Guid user, Guid code)
|
||||||
{
|
{
|
||||||
return await _connection.QuerySingleOrDefaultAsync<EmailVerificationCode>(
|
await using var conn = await _connection.Get();
|
||||||
|
return await conn.QuerySingleOrDefaultAsync<EmailVerificationCode>(
|
||||||
@"select * from ""EmailVerification"" where ""User"" = :user and ""Code"" = :code",
|
@"select * from ""EmailVerification"" where ""User"" = :user and ""Code"" = :code",
|
||||||
new {user, code});
|
new {user, code});
|
||||||
}
|
}
|
||||||
@ -39,7 +40,9 @@ public class PostgresEmailVerification : BaseEmailVerification
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override async ValueTask DeleteToken(Guid user, Guid code)
|
protected override async ValueTask DeleteToken(Guid user, Guid code)
|
||||||
{
|
{
|
||||||
await _connection.ExecuteAsync(@"delete from ""EmailVerification"" where ""User"" = :user and ""Code"" = :code",
|
await using var conn = await _connection.Get();
|
||||||
|
await conn.ExecuteAsync(
|
||||||
|
@"delete from ""EmailVerification"" where ""User"" = :user and ""Code"" = :code",
|
||||||
new {user, code});
|
new {user, code});
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,4 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using Npgsql;
|
|
||||||
using VoidCat.Model;
|
using VoidCat.Model;
|
||||||
using VoidCat.Services.Abstractions;
|
using VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
@ -8,9 +7,9 @@ namespace VoidCat.Services.Users;
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public class PostgresUserStore : IUserStore
|
public class PostgresUserStore : IUserStore
|
||||||
{
|
{
|
||||||
private readonly NpgsqlConnection _connection;
|
private readonly PostgresConnectionFactory _connection;
|
||||||
|
|
||||||
public PostgresUserStore(NpgsqlConnection connection)
|
public PostgresUserStore(PostgresConnectionFactory connection)
|
||||||
{
|
{
|
||||||
_connection = connection;
|
_connection = connection;
|
||||||
}
|
}
|
||||||
@ -30,7 +29,8 @@ public class PostgresUserStore : IUserStore
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask Set(Guid id, InternalVoidUser obj)
|
public async ValueTask Set(Guid id, InternalVoidUser obj)
|
||||||
{
|
{
|
||||||
await _connection.ExecuteAsync(
|
await using var conn = await _connection.Get();
|
||||||
|
await conn.ExecuteAsync(
|
||||||
@"insert into
|
@"insert into
|
||||||
""Users""(""Id"", ""Email"", ""Password"", ""LastLogin"", ""DisplayName"", ""Avatar"", ""Flags"")
|
""Users""(""Id"", ""Email"", ""Password"", ""LastLogin"", ""DisplayName"", ""Avatar"", ""Flags"")
|
||||||
values(:id, :email, :password, :lastLogin, :displayName, :avatar, :flags)",
|
values(:id, :email, :password, :lastLogin, :displayName, :avatar, :flags)",
|
||||||
@ -48,7 +48,8 @@ values(:id, :email, :password, :lastLogin, :displayName, :avatar, :flags)",
|
|||||||
{
|
{
|
||||||
foreach (var r in obj.Roles.Where(a => a != Roles.User))
|
foreach (var r in obj.Roles.Where(a => a != Roles.User))
|
||||||
{
|
{
|
||||||
await _connection.ExecuteAsync(@"insert into ""UserRoles""(""User"", ""Role"") values(:user, :role)",
|
await conn.ExecuteAsync(
|
||||||
|
@"insert into ""UserRoles""(""User"", ""Role"") values(:user, :role)",
|
||||||
new {user = obj.Id, role = r});
|
new {user = obj.Id, role = r});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,17 +58,20 @@ values(:id, :email, :password, :lastLogin, :displayName, :avatar, :flags)",
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask Delete(Guid id)
|
public async ValueTask Delete(Guid id)
|
||||||
{
|
{
|
||||||
await _connection.ExecuteAsync(@"delete from ""Users"" where ""Id"" = :id", new {id});
|
await using var conn = await _connection.Get();
|
||||||
|
await conn.ExecuteAsync(@"delete from ""Users"" where ""Id"" = :id", new {id});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<T?> Get<T>(Guid id) where T : VoidUser
|
public async ValueTask<T?> Get<T>(Guid id) where T : VoidUser
|
||||||
{
|
{
|
||||||
var user = await _connection.QuerySingleOrDefaultAsync<T?>(@"select * from ""Users"" where ""Id"" = :id",
|
await using var conn = await _connection.Get();
|
||||||
|
var user = await conn.QuerySingleOrDefaultAsync<T?>(@"select * from ""Users"" where ""Id"" = :id",
|
||||||
new {id});
|
new {id});
|
||||||
if (user != default)
|
if (user != default)
|
||||||
{
|
{
|
||||||
var roles = await _connection.QueryAsync<string>(@"select ""Role"" from ""UserRoles"" where ""User"" = :id",
|
var roles = await conn.QueryAsync<string>(
|
||||||
|
@"select ""Role"" from ""UserRoles"" where ""User"" = :id",
|
||||||
new {id});
|
new {id});
|
||||||
foreach (var r in roles)
|
foreach (var r in roles)
|
||||||
{
|
{
|
||||||
@ -81,7 +85,8 @@ values(:id, :email, :password, :lastLogin, :displayName, :avatar, :flags)",
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<Guid?> LookupUser(string email)
|
public async ValueTask<Guid?> LookupUser(string email)
|
||||||
{
|
{
|
||||||
return await _connection.QuerySingleOrDefaultAsync<Guid?>(
|
await using var conn = await _connection.Get();
|
||||||
|
return await conn.QuerySingleOrDefaultAsync<Guid?>(
|
||||||
@"select ""Id"" from ""Users"" where ""Email"" = :email",
|
@"select ""Id"" from ""Users"" where ""Email"" = :email",
|
||||||
new {email});
|
new {email});
|
||||||
}
|
}
|
||||||
@ -89,31 +94,34 @@ values(:id, :email, :password, :lastLogin, :displayName, :avatar, :flags)",
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<PagedResult<PrivateVoidUser>> ListUsers(PagedRequest request)
|
public async ValueTask<PagedResult<PrivateVoidUser>> ListUsers(PagedRequest request)
|
||||||
{
|
{
|
||||||
var orderBy = request.SortBy switch
|
await using var conn = await _connection.Get();
|
||||||
{
|
var totalUsers = await conn.ExecuteScalarAsync<int>(@"select count(*) from ""Users""");
|
||||||
PagedSortBy.Date => "Created",
|
|
||||||
PagedSortBy.Name => "DisplayName",
|
|
||||||
_ => "Id"
|
|
||||||
};
|
|
||||||
var sortBy = request.SortOrder switch
|
|
||||||
{
|
|
||||||
PageSortOrder.Dsc => "desc",
|
|
||||||
_ => "asc"
|
|
||||||
};
|
|
||||||
var totalUsers = await _connection.ExecuteScalarAsync<int>(@"select count(*) from ""Users""");
|
|
||||||
var users = await _connection.QueryAsync<PrivateVoidUser>(
|
|
||||||
$@"select * from ""Users"" order by ""{orderBy}"" {sortBy} offset :offset limit :limit",
|
|
||||||
new
|
|
||||||
{
|
|
||||||
offset = request.PageSize * request.Page,
|
|
||||||
limit = request.PageSize
|
|
||||||
});
|
|
||||||
|
|
||||||
async IAsyncEnumerable<PrivateVoidUser> Enumerate()
|
async IAsyncEnumerable<PrivateVoidUser> Enumerate()
|
||||||
{
|
{
|
||||||
foreach (var u in users ?? Enumerable.Empty<PrivateVoidUser>())
|
var orderBy = request.SortBy switch
|
||||||
{
|
{
|
||||||
yield return u;
|
PagedSortBy.Date => "Created",
|
||||||
|
PagedSortBy.Name => "DisplayName",
|
||||||
|
_ => "Id"
|
||||||
|
};
|
||||||
|
var sortBy = request.SortOrder switch
|
||||||
|
{
|
||||||
|
PageSortOrder.Dsc => "desc",
|
||||||
|
_ => "asc"
|
||||||
|
};
|
||||||
|
await using var iconn = await _connection.Get();
|
||||||
|
var users = await iconn.ExecuteReaderAsync(
|
||||||
|
$@"select * from ""Users"" order by ""{orderBy}"" {sortBy} offset :offset limit :limit",
|
||||||
|
new
|
||||||
|
{
|
||||||
|
offset = request.PageSize * request.Page,
|
||||||
|
limit = request.PageSize
|
||||||
|
});
|
||||||
|
var rowParser = users.GetRowParser<PrivateVoidUser>();
|
||||||
|
while (await users.ReadAsync())
|
||||||
|
{
|
||||||
|
yield return rowParser(users);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,8 +141,8 @@ values(:id, :email, :password, :lastLogin, :displayName, :avatar, :flags)",
|
|||||||
if (oldUser == null) return;
|
if (oldUser == null) return;
|
||||||
|
|
||||||
var emailFlag = oldUser.Flags.HasFlag(VoidUserFlags.EmailVerified) ? VoidUserFlags.EmailVerified : 0;
|
var emailFlag = oldUser.Flags.HasFlag(VoidUserFlags.EmailVerified) ? VoidUserFlags.EmailVerified : 0;
|
||||||
|
await using var conn = await _connection.Get();
|
||||||
await _connection.ExecuteAsync(
|
await conn.ExecuteAsync(
|
||||||
@"update ""Users"" set ""DisplayName"" = @displayName, ""Avatar"" = @avatar, ""Flags"" = :flags where ""Id"" = :id",
|
@"update ""Users"" set ""DisplayName"" = @displayName, ""Avatar"" = @avatar, ""Flags"" = :flags where ""Id"" = :id",
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
@ -148,7 +156,8 @@ values(:id, :email, :password, :lastLogin, :displayName, :avatar, :flags)",
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask UpdateLastLogin(Guid id, DateTime timestamp)
|
public async ValueTask UpdateLastLogin(Guid id, DateTime timestamp)
|
||||||
{
|
{
|
||||||
await _connection.ExecuteAsync(@"update ""Users"" set ""LastLogin"" = :timestamp where ""Id"" = :id",
|
await using var conn = await _connection.Get();
|
||||||
|
await conn.ExecuteAsync(@"update ""Users"" set ""LastLogin"" = :timestamp where ""Id"" = :id",
|
||||||
new {id, timestamp});
|
new {id, timestamp});
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,22 +1,20 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using Npgsql;
|
|
||||||
using VoidCat.Model;
|
using VoidCat.Model;
|
||||||
using VoidCat.Services.Abstractions;
|
using VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
namespace VoidCat.Services.Users;
|
namespace VoidCat.Services.Users;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public class PostgresUserUploadStore : IUserUploadsStore
|
public class PostgresUserUploadStore : IUserUploadsStore
|
||||||
{
|
{
|
||||||
private readonly NpgsqlConnection _connection;
|
private readonly PostgresConnectionFactory _connection;
|
||||||
private readonly IFileInfoManager _fileInfoManager;
|
|
||||||
|
|
||||||
public PostgresUserUploadStore(NpgsqlConnection connection, IFileInfoManager fileInfoManager)
|
public PostgresUserUploadStore(PostgresConnectionFactory connection)
|
||||||
{
|
{
|
||||||
_connection = connection;
|
_connection = connection;
|
||||||
_fileInfoManager = fileInfoManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<PagedResult<PublicVoidFile>> ListFiles(Guid user, PagedRequest request)
|
public async ValueTask<PagedResult<Guid>> ListFiles(Guid user, PagedRequest request)
|
||||||
{
|
{
|
||||||
var query = @"select {0}
|
var query = @"select {0}
|
||||||
from ""UserFiles"" uf, ""Files"" f
|
from ""UserFiles"" uf, ""Files"" f
|
||||||
@ -24,32 +22,31 @@ where uf.""User"" = :user
|
|||||||
and uf.""File"" = f.""Id""";
|
and uf.""File"" = f.""Id""";
|
||||||
var queryOrder = @"order by f.""{1}"" {2} limit :limit offset :offset";
|
var queryOrder = @"order by f.""{1}"" {2} limit :limit offset :offset";
|
||||||
|
|
||||||
var orderBy = request.SortBy switch
|
await using var conn = await _connection.Get();
|
||||||
{
|
var count = await conn.ExecuteScalarAsync<int>(string.Format(query, "count(*)"), new {user});
|
||||||
PagedSortBy.Name => "Name",
|
|
||||||
PagedSortBy.Date => "Uploaded",
|
|
||||||
PagedSortBy.Size => "Size",
|
|
||||||
_ => "Id"
|
|
||||||
};
|
|
||||||
var sortOrder = request.SortOrder switch
|
|
||||||
{
|
|
||||||
PageSortOrder.Dsc => "desc",
|
|
||||||
_ => "asc"
|
|
||||||
};
|
|
||||||
var count = await _connection.ExecuteScalarAsync<int>(string.Format(query, "count(*)"), new {user});
|
|
||||||
var files = await _connection.QueryAsync<Guid>(
|
|
||||||
string.Format(query + queryOrder, "uf.\"File\"", orderBy, sortOrder),
|
|
||||||
new {user, offset = request.Page * request.PageSize, limit = request.PageSize});
|
|
||||||
|
|
||||||
async IAsyncEnumerable<PublicVoidFile> EnumerateFiles()
|
async IAsyncEnumerable<Guid> EnumerateFiles()
|
||||||
{
|
{
|
||||||
foreach (var file in files ?? Enumerable.Empty<Guid>())
|
var orderBy = request.SortBy switch
|
||||||
{
|
{
|
||||||
var v = await _fileInfoManager.Get(file);
|
PagedSortBy.Name => "Name",
|
||||||
if (v != default)
|
PagedSortBy.Date => "Uploaded",
|
||||||
{
|
PagedSortBy.Size => "Size",
|
||||||
yield return v;
|
_ => "Id"
|
||||||
}
|
};
|
||||||
|
var sortOrder = request.SortOrder switch
|
||||||
|
{
|
||||||
|
PageSortOrder.Dsc => "desc",
|
||||||
|
_ => "asc"
|
||||||
|
};
|
||||||
|
await using var connInner = await _connection.Get();
|
||||||
|
var files = await connInner.ExecuteReaderAsync(
|
||||||
|
string.Format(query + queryOrder, "uf.\"File\"", orderBy, sortOrder),
|
||||||
|
new {user, offset = request.Page * request.PageSize, limit = request.PageSize});
|
||||||
|
var rowParser = files.GetRowParser<Guid>();
|
||||||
|
while (await files.ReadAsync())
|
||||||
|
{
|
||||||
|
yield return rowParser(files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,12 +59,22 @@ and uf.""File"" = f.""Id""";
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async ValueTask AddFile(Guid user, PrivateVoidFile voidFile)
|
public async ValueTask AddFile(Guid user, PrivateVoidFile voidFile)
|
||||||
{
|
{
|
||||||
await _connection.ExecuteAsync(@"insert into ""UserFiles""(""File"", ""User"") values(:file, :user)", new
|
await using var conn = await _connection.Get();
|
||||||
|
await conn.ExecuteAsync(@"insert into ""UserFiles""(""File"", ""User"") values(:file, :user)", new
|
||||||
{
|
{
|
||||||
file = voidFile.Id,
|
file = voidFile.Id,
|
||||||
user
|
user
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async ValueTask<Guid?> Uploader(Guid file)
|
||||||
|
{
|
||||||
|
await using var conn = await _connection.Get();
|
||||||
|
return await conn.ExecuteScalarAsync<Guid?>(
|
||||||
|
@"select ""User"" from ""UserFiles"" where ""File"" = :file", new {file});
|
||||||
|
}
|
||||||
}
|
}
|
@ -3,18 +3,18 @@ using VoidCat.Services.Abstractions;
|
|||||||
|
|
||||||
namespace VoidCat.Services.Users;
|
namespace VoidCat.Services.Users;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public class UserUploadStore : IUserUploadsStore
|
public class UserUploadStore : IUserUploadsStore
|
||||||
{
|
{
|
||||||
private readonly ICache _cache;
|
private readonly ICache _cache;
|
||||||
private readonly IFileInfoManager _fileInfo;
|
|
||||||
|
|
||||||
public UserUploadStore(ICache cache, IFileInfoManager fileInfo)
|
public UserUploadStore(ICache cache)
|
||||||
{
|
{
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
_fileInfo = fileInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<PagedResult<PublicVoidFile>> ListFiles(Guid user, PagedRequest request)
|
/// <inheritdoc />
|
||||||
|
public async ValueTask<PagedResult<Guid>> ListFiles(Guid user, PagedRequest request)
|
||||||
{
|
{
|
||||||
var ids = (await _cache.GetList(MapKey(user))).Select(Guid.Parse);
|
var ids = (await _cache.GetList(MapKey(user))).Select(Guid.Parse);
|
||||||
ids = (request.SortBy, request.SortOrder) switch
|
ids = (request.SortBy, request.SortOrder) switch
|
||||||
@ -24,15 +24,12 @@ public class UserUploadStore : IUserUploadsStore
|
|||||||
_ => ids
|
_ => ids
|
||||||
};
|
};
|
||||||
|
|
||||||
async IAsyncEnumerable<PublicVoidFile> EnumerateResults(IEnumerable<Guid> page)
|
var idsRendered = ids.ToList();
|
||||||
|
async IAsyncEnumerable<Guid> EnumerateResults(IEnumerable<Guid> page)
|
||||||
{
|
{
|
||||||
foreach (var guid in page)
|
foreach (var id in page)
|
||||||
{
|
{
|
||||||
var info = await _fileInfo.Get(guid);
|
yield return id;
|
||||||
if (info != default)
|
|
||||||
{
|
|
||||||
yield return info;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,15 +37,24 @@ public class UserUploadStore : IUserUploadsStore
|
|||||||
{
|
{
|
||||||
Page = request.Page,
|
Page = request.Page,
|
||||||
PageSize = request.PageSize,
|
PageSize = request.PageSize,
|
||||||
TotalResults = ids?.Count() ?? 0,
|
TotalResults = idsRendered.Count,
|
||||||
Results = EnumerateResults(ids.Skip(request.Page * request.PageSize).Take(request.PageSize))
|
Results = EnumerateResults(idsRendered.Skip(request.Page * request.PageSize).Take(request.PageSize))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask AddFile(Guid user, PrivateVoidFile voidFile)
|
/// <inheritdoc />
|
||||||
|
public async ValueTask AddFile(Guid user, PrivateVoidFile voidFile)
|
||||||
{
|
{
|
||||||
return _cache.AddToList(MapKey(user), voidFile.Id.ToString());
|
await _cache.AddToList(MapKey(user), voidFile.Id.ToString());
|
||||||
|
await _cache.Set(MapUploader(voidFile.Id), user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ValueTask<Guid?> Uploader(Guid file)
|
||||||
|
{
|
||||||
|
return _cache.Get<Guid?>(MapUploader(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string MapKey(Guid id) => $"user:{id}:uploads";
|
private static string MapKey(Guid id) => $"user:{id}:uploads";
|
||||||
|
private static string MapUploader(Guid file) => $"file:{file}:uploader";
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user