From cba4d5fc80085cff1f30a9e546f935dfcf2ab679 Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 10 Jun 2022 21:42:36 +0100 Subject: [PATCH] Decouple user upload store --- VoidCat/Controllers/UploadController.cs | 21 +++--- VoidCat/Controllers/UserController.cs | 14 +++- VoidCat/Model/Extensions.cs | 5 +- VoidCat/Model/VoidFileMeta.cs | 6 -- VoidCat/Program.cs | 10 +-- .../Services/Abstractions/IFileInfoManager.cs | 7 ++ .../Abstractions/IUserUploadsStore.cs | 25 ++++++- .../Background/DeleteUnverifiedAccounts.cs | 4 +- VoidCat/Services/Files/FileInfoManager.cs | 55 ++++++++------ .../Files/PostgresFileMetadataStore.cs | 26 ++++--- VoidCat/Services/OpenDatabase.cs | 24 ------ VoidCat/Services/PostgresConnectionFactory.cs | 26 +++++++ .../Users/PostgresEmailVerification.cs | 15 ++-- VoidCat/Services/Users/PostgresUserStore.cs | 75 +++++++++++-------- .../Services/Users/PostgresUserUploadStore.cs | 67 +++++++++-------- VoidCat/Services/Users/UserUploadStore.cs | 36 +++++---- 16 files changed, 246 insertions(+), 170 deletions(-) delete mode 100644 VoidCat/Services/OpenDatabase.cs create mode 100644 VoidCat/Services/PostgresConnectionFactory.cs diff --git a/VoidCat/Controllers/UploadController.cs b/VoidCat/Controllers/UploadController.cs index 398b0b3..444aa0f 100644 --- a/VoidCat/Controllers/UploadController.cs +++ b/VoidCat/Controllers/UploadController.cs @@ -68,8 +68,7 @@ namespace VoidCat.Controllers Name = filename, Description = Request.Headers.GetHeader("V-Description"), Digest = Request.Headers.GetHeader("V-Full-Digest"), - Size = (ulong?) Request.ContentLength ?? 0UL, - Uploader = uid + Size = (ulong?) Request.ContentLength ?? 0UL }; var digest = Request.Headers.GetHeader("V-Digest"); @@ -80,13 +79,13 @@ namespace VoidCat.Controllers // save metadata await _metadata.Set(vf.Id, vf.Metadata!); - + // attach file upload to user if (uid.HasValue) { await _userUploads.AddFile(uid!.Value, vf); } - + if (cli) { var urlBuilder = new UriBuilder(Request.IsHttps ? "https" : "http", Request.Host.Host, @@ -137,7 +136,7 @@ namespace VoidCat.Controllers Id = gid, IsAppend = true }, HttpContext.RequestAborted); - + // update file size await _metadata.Set(vf.Id, vf.Metadata!); return UploadResult.Success(vf); @@ -155,9 +154,13 @@ namespace VoidCat.Controllers /// [HttpGet] [Route("{id}")] - public ValueTask GetInfo([FromRoute] string id) + public async Task 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)); } /// @@ -207,7 +210,7 @@ namespace VoidCat.Controllers var gid = id.FromBase58Guid(); var meta = await _metadata.Get(gid); if (meta == default) return NotFound(); - if (!meta.CanEdit(req.EditSecret, HttpContext)) return Unauthorized(); + if (!meta.CanEdit(req.EditSecret)) return Unauthorized(); if (req.Strike != default) { @@ -236,7 +239,7 @@ namespace VoidCat.Controllers var gid = id.FromBase58Guid(); var meta = await _metadata.Get(gid); 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); return Ok(); diff --git a/VoidCat/Controllers/UserController.cs b/VoidCat/Controllers/UserController.cs index 2cff0db..b1ab7e9 100644 --- a/VoidCat/Controllers/UserController.cs +++ b/VoidCat/Controllers/UserController.cs @@ -10,12 +10,14 @@ public class UserController : Controller private readonly IUserStore _store; private readonly IUserUploadsStore _userUploads; 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; _userUploads = userUploads; _emailVerification = emailVerification; + _fileInfoManager = fileInfoManager; } /// @@ -95,7 +97,15 @@ public class UserController : Controller !user.Flags.HasFlag(VoidUserFlags.PublicUploads)) return Forbid(); 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() + { + PageSize = results.PageSize, + Page = results.Page, + TotalResults = results.TotalResults, + Results = fileInfo.Where(a => a != null).ToList()! + }); } /// diff --git a/VoidCat/Model/Extensions.cs b/VoidCat/Model/Extensions.cs index b50a05e..dc655b4 100644 --- a/VoidCat/Model/Extensions.cs +++ b/VoidCat/Model/Extensions.cs @@ -61,10 +61,9 @@ public static class Extensions 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 - || file.Uploader == context.GetUserId(); + return file.EditSecret == editSecret; } public static string ToHex(this byte[] data) diff --git a/VoidCat/Model/VoidFileMeta.cs b/VoidCat/Model/VoidFileMeta.cs index 808b3bd..74fc2f9 100644 --- a/VoidCat/Model/VoidFileMeta.cs +++ b/VoidCat/Model/VoidFileMeta.cs @@ -65,12 +65,6 @@ public record VoidFileMeta : IVoidFileMeta /// Url to download the file /// public Uri? Url { get; set; } - - /// - /// User who uploaded the file - /// - [JsonConverter(typeof(Base58GuidConverter))] - public Guid? Uploader { get; init; } } /// diff --git a/VoidCat/Program.cs b/VoidCat/Program.cs index ae4c94a..a6b68bf 100644 --- a/VoidCat/Program.cs +++ b/VoidCat/Program.cs @@ -158,9 +158,8 @@ services.AddCaptcha(voidSettings); // postgres if (!string.IsNullOrEmpty(voidSettings.Postgres)) { - services.AddScoped(); - services.AddScoped((_) => new NpgsqlConnection(voidSettings.Postgres)); - services.AddScoped((svc) => svc.GetRequiredService()); + services.AddSingleton(); + services.AddTransient(_ => new NpgsqlConnection(voidSettings.Postgres)); // fluent migrations services.AddTransient(); @@ -221,11 +220,6 @@ app.UseSwaggerUI(); app.UseAuthentication(); app.UseAuthorization(); -if (!string.IsNullOrEmpty(voidSettings.Postgres)) -{ - app.UseMiddleware(); -} - app.UseEndpoints(ep => { ep.MapControllers(); diff --git a/VoidCat/Services/Abstractions/IFileInfoManager.cs b/VoidCat/Services/Abstractions/IFileInfoManager.cs index 6b09f3d..3320faf 100644 --- a/VoidCat/Services/Abstractions/IFileInfoManager.cs +++ b/VoidCat/Services/Abstractions/IFileInfoManager.cs @@ -14,6 +14,13 @@ public interface IFileInfoManager /// /// ValueTask Get(Guid id); + + /// + /// Get all private metadata for a single file + /// + /// + /// + ValueTask GetPrivate(Guid id); /// /// Get all metadata for multiple files diff --git a/VoidCat/Services/Abstractions/IUserUploadsStore.cs b/VoidCat/Services/Abstractions/IUserUploadsStore.cs index aeff7e4..c1d5b4f 100644 --- a/VoidCat/Services/Abstractions/IUserUploadsStore.cs +++ b/VoidCat/Services/Abstractions/IUserUploadsStore.cs @@ -2,8 +2,31 @@ using VoidCat.Model; namespace VoidCat.Services.Abstractions; +/// +/// Mapping store to associate files to users +/// public interface IUserUploadsStore { - ValueTask> ListFiles(Guid user, PagedRequest request); + /// + /// List all files for the user + /// + /// + /// + /// + ValueTask> ListFiles(Guid user, PagedRequest request); + + /// + /// Assign a file upload to a user + /// + /// + /// + /// ValueTask AddFile(Guid user, PrivateVoidFile voidFile); + + /// + /// Get the uploader of a single file + /// + /// + /// + ValueTask Uploader(Guid file); } diff --git a/VoidCat/Services/Background/DeleteUnverifiedAccounts.cs b/VoidCat/Services/Background/DeleteUnverifiedAccounts.cs index 1266c70..aaee1de 100644 --- a/VoidCat/Services/Background/DeleteUnverifiedAccounts.cs +++ b/VoidCat/Services/Background/DeleteUnverifiedAccounts.cs @@ -40,8 +40,8 @@ public class DeleteUnverifiedAccounts : BackgroundService // ReSharper disable once UseCancellationTokenForIAsyncEnumerable await foreach (var file in files.Results) { - await fileStore.DeleteFile(file.Id); - await fileInfoManager.Delete(file.Id); + await fileStore.DeleteFile(file); + await fileInfoManager.Delete(file); } } } diff --git a/VoidCat/Services/Files/FileInfoManager.cs b/VoidCat/Services/Files/FileInfoManager.cs index 97f544b..767685d 100644 --- a/VoidCat/Services/Files/FileInfoManager.cs +++ b/VoidCat/Services/Files/FileInfoManager.cs @@ -11,40 +11,29 @@ public class FileInfoManager : IFileInfoManager private readonly IStatsReporter _statsReporter; private readonly IUserStore _userStore; private readonly IVirusScanStore _virusScanStore; + private readonly IUserUploadsStore _userUploadsStore; public FileInfoManager(IFileMetadataStore metadataStore, IPaywallStore paywallStore, IStatsReporter statsReporter, - IUserStore userStore, IVirusScanStore virusScanStore) + IUserStore userStore, IVirusScanStore virusScanStore, IUserUploadsStore userUploadsStore) { _metadataStore = metadataStore; _paywallStore = paywallStore; _statsReporter = statsReporter; _userStore = userStore; _virusScanStore = virusScanStore; + _userUploadsStore = userUploadsStore; } /// - public async ValueTask Get(Guid id) + public ValueTask Get(Guid id) { - var meta = _metadataStore.Get(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()); + return Get(id); + } - if (meta.Result == default) return default; - - var uploader = meta.Result?.Uploader; - var user = uploader.HasValue ? await _userStore.Get(uploader.Value) : null; - - 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 - }; + /// + public ValueTask GetPrivate(Guid id) + { + return Get(id); } /// @@ -71,4 +60,28 @@ public class FileInfoManager : IFileInfoManager await _statsReporter.Delete(id); await _virusScanStore.Delete(id); } + + private async ValueTask Get(Guid id) + where TMeta : VoidFileMeta where TFile : VoidFile, new() + { + var meta = _metadataStore.Get(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(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 + }; + } } \ No newline at end of file diff --git a/VoidCat/Services/Files/PostgresFileMetadataStore.cs b/VoidCat/Services/Files/PostgresFileMetadataStore.cs index 62253b7..d23df97 100644 --- a/VoidCat/Services/Files/PostgresFileMetadataStore.cs +++ b/VoidCat/Services/Files/PostgresFileMetadataStore.cs @@ -1,5 +1,4 @@ using Dapper; -using Npgsql; using VoidCat.Model; using VoidCat.Services.Abstractions; @@ -8,9 +7,9 @@ namespace VoidCat.Services.Files; /// public class PostgresFileMetadataStore : IFileMetadataStore { - private readonly NpgsqlConnection _connection; + private readonly PostgresConnectionFactory _connection; - public PostgresFileMetadataStore(NpgsqlConnection connection) + public PostgresFileMetadataStore(PostgresConnectionFactory connection) { _connection = connection; } @@ -30,7 +29,8 @@ public class PostgresFileMetadataStore : IFileMetadataStore /// public async ValueTask Set(Guid id, SecretVoidFileMeta obj) { - await _connection.ExecuteAsync( + await using var conn = await _connection.Get(); + await conn.ExecuteAsync( @"insert into ""Files""(""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 /// 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}); } /// public async ValueTask Get(Guid id) where TMeta : VoidFileMeta { - return await _connection.QuerySingleOrDefaultAsync(@"select * from ""Files"" where ""Id"" = :id", + await using var conn = await _connection.Get(); + return await conn.QuerySingleOrDefaultAsync(@"select * from ""Files"" where ""Id"" = :id", new {id}); } /// public async ValueTask> Get(Guid[] ids) where TMeta : VoidFileMeta { - var ret = await _connection.QueryAsync("select * from \"Files\" where \"Id\" in :ids", new {ids}); + await using var conn = await _connection.Get(); + var ret = await conn.QueryAsync("select * from \"Files\" where \"Id\" in :ids", new {ids}); return ret.ToList(); } @@ -83,7 +86,8 @@ on conflict (""Id"") do update set ""Name"" = :name, ""Description"" = :descript /// public async ValueTask> ListFiles(PagedRequest request) where TMeta : VoidFileMeta { - var count = await _connection.ExecuteScalarAsync(@"select count(*) from ""Files"""); + await using var conn = await _connection.Get(); + var count = await conn.ExecuteScalarAsync(@"select count(*) from ""Files"""); async IAsyncEnumerable Enumerate() { @@ -94,8 +98,9 @@ on conflict (""Id"") do update set ""Name"" = :name, ""Description"" = :descript PagedSortBy.Size => "Size", _ => "Id" }; + await using var iconn = await _connection.Get(); var orderDirection = request.SortOrder == PageSortOrder.Asc ? "asc" : "desc"; - var results = await _connection.QueryAsync( + var results = await iconn.QueryAsync( $"select * from \"Files\" order by \"{orderBy}\" {orderDirection} offset @offset limit @limit", new {offset = request.PageSize * request.Page, limit = request.PageSize}); @@ -117,7 +122,8 @@ on conflict (""Id"") do update set ""Name"" = :name, ""Description"" = :descript /// public async ValueTask 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"""); return new(v.Files, (ulong) v.Size); } diff --git a/VoidCat/Services/OpenDatabase.cs b/VoidCat/Services/OpenDatabase.cs deleted file mode 100644 index 0ee2de4..0000000 --- a/VoidCat/Services/OpenDatabase.cs +++ /dev/null @@ -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(); - } - } -} \ No newline at end of file diff --git a/VoidCat/Services/PostgresConnectionFactory.cs b/VoidCat/Services/PostgresConnectionFactory.cs new file mode 100644 index 0000000..e5126cb --- /dev/null +++ b/VoidCat/Services/PostgresConnectionFactory.cs @@ -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 Get() + { + var conn = new NpgsqlConnection(_settings.Postgres); + if (!conn.State.HasFlag(ConnectionState.Open)) + { + await conn.OpenAsync(); + } + + return conn; + } +} \ No newline at end of file diff --git a/VoidCat/Services/Users/PostgresEmailVerification.cs b/VoidCat/Services/Users/PostgresEmailVerification.cs index 24883db..825daf2 100644 --- a/VoidCat/Services/Users/PostgresEmailVerification.cs +++ b/VoidCat/Services/Users/PostgresEmailVerification.cs @@ -1,5 +1,4 @@ using Dapper; -using Npgsql; using VoidCat.Model; namespace VoidCat.Services.Users; @@ -7,10 +6,10 @@ namespace VoidCat.Services.Users; /// public class PostgresEmailVerification : BaseEmailVerification { - private readonly NpgsqlConnection _connection; + private readonly PostgresConnectionFactory _connection; public PostgresEmailVerification(ILogger logger, VoidSettings settings, - RazorPartialToStringRenderer renderer, NpgsqlConnection connection) : base(logger, settings, renderer) + RazorPartialToStringRenderer renderer, PostgresConnectionFactory connection) : base(logger, settings, renderer) { _connection = connection; } @@ -18,7 +17,8 @@ public class PostgresEmailVerification : BaseEmailVerification /// 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)", new { @@ -31,7 +31,8 @@ public class PostgresEmailVerification : BaseEmailVerification /// protected override async ValueTask GetToken(Guid user, Guid code) { - return await _connection.QuerySingleOrDefaultAsync( + await using var conn = await _connection.Get(); + return await conn.QuerySingleOrDefaultAsync( @"select * from ""EmailVerification"" where ""User"" = :user and ""Code"" = :code", new {user, code}); } @@ -39,7 +40,9 @@ public class PostgresEmailVerification : BaseEmailVerification /// 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}); } } \ No newline at end of file diff --git a/VoidCat/Services/Users/PostgresUserStore.cs b/VoidCat/Services/Users/PostgresUserStore.cs index b77ef60..cb740ca 100644 --- a/VoidCat/Services/Users/PostgresUserStore.cs +++ b/VoidCat/Services/Users/PostgresUserStore.cs @@ -1,5 +1,4 @@ using Dapper; -using Npgsql; using VoidCat.Model; using VoidCat.Services.Abstractions; @@ -8,9 +7,9 @@ namespace VoidCat.Services.Users; /// public class PostgresUserStore : IUserStore { - private readonly NpgsqlConnection _connection; + private readonly PostgresConnectionFactory _connection; - public PostgresUserStore(NpgsqlConnection connection) + public PostgresUserStore(PostgresConnectionFactory connection) { _connection = connection; } @@ -30,7 +29,8 @@ public class PostgresUserStore : IUserStore /// public async ValueTask Set(Guid id, InternalVoidUser obj) { - await _connection.ExecuteAsync( + await using var conn = await _connection.Get(); + await conn.ExecuteAsync( @"insert into ""Users""(""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)) { - 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}); } } @@ -57,17 +58,20 @@ values(:id, :email, :password, :lastLogin, :displayName, :avatar, :flags)", /// 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}); } /// public async ValueTask Get(Guid id) where T : VoidUser { - var user = await _connection.QuerySingleOrDefaultAsync(@"select * from ""Users"" where ""Id"" = :id", + await using var conn = await _connection.Get(); + var user = await conn.QuerySingleOrDefaultAsync(@"select * from ""Users"" where ""Id"" = :id", new {id}); if (user != default) { - var roles = await _connection.QueryAsync(@"select ""Role"" from ""UserRoles"" where ""User"" = :id", + var roles = await conn.QueryAsync( + @"select ""Role"" from ""UserRoles"" where ""User"" = :id", new {id}); foreach (var r in roles) { @@ -81,7 +85,8 @@ values(:id, :email, :password, :lastLogin, :displayName, :avatar, :flags)", /// public async ValueTask LookupUser(string email) { - return await _connection.QuerySingleOrDefaultAsync( + await using var conn = await _connection.Get(); + return await conn.QuerySingleOrDefaultAsync( @"select ""Id"" from ""Users"" where ""Email"" = :email", new {email}); } @@ -89,31 +94,34 @@ values(:id, :email, :password, :lastLogin, :displayName, :avatar, :flags)", /// public async ValueTask> ListUsers(PagedRequest request) { - var orderBy = request.SortBy switch - { - PagedSortBy.Date => "Created", - PagedSortBy.Name => "DisplayName", - _ => "Id" - }; - var sortBy = request.SortOrder switch - { - PageSortOrder.Dsc => "desc", - _ => "asc" - }; - var totalUsers = await _connection.ExecuteScalarAsync(@"select count(*) from ""Users"""); - var users = await _connection.QueryAsync( - $@"select * from ""Users"" order by ""{orderBy}"" {sortBy} offset :offset limit :limit", - new - { - offset = request.PageSize * request.Page, - limit = request.PageSize - }); + await using var conn = await _connection.Get(); + var totalUsers = await conn.ExecuteScalarAsync(@"select count(*) from ""Users"""); async IAsyncEnumerable Enumerate() { - foreach (var u in users ?? Enumerable.Empty()) + 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(); + while (await users.ReadAsync()) + { + yield return rowParser(users); } } @@ -133,8 +141,8 @@ values(:id, :email, :password, :lastLogin, :displayName, :avatar, :flags)", if (oldUser == null) return; var emailFlag = oldUser.Flags.HasFlag(VoidUserFlags.EmailVerified) ? VoidUserFlags.EmailVerified : 0; - - await _connection.ExecuteAsync( + await using var conn = await _connection.Get(); + await conn.ExecuteAsync( @"update ""Users"" set ""DisplayName"" = @displayName, ""Avatar"" = @avatar, ""Flags"" = :flags where ""Id"" = :id", new { @@ -148,7 +156,8 @@ values(:id, :email, :password, :lastLogin, :displayName, :avatar, :flags)", /// 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}); } } \ No newline at end of file diff --git a/VoidCat/Services/Users/PostgresUserUploadStore.cs b/VoidCat/Services/Users/PostgresUserUploadStore.cs index 9addf48..b1478e2 100644 --- a/VoidCat/Services/Users/PostgresUserUploadStore.cs +++ b/VoidCat/Services/Users/PostgresUserUploadStore.cs @@ -1,22 +1,20 @@ using Dapper; -using Npgsql; using VoidCat.Model; using VoidCat.Services.Abstractions; namespace VoidCat.Services.Users; +/// public class PostgresUserUploadStore : IUserUploadsStore { - private readonly NpgsqlConnection _connection; - private readonly IFileInfoManager _fileInfoManager; + private readonly PostgresConnectionFactory _connection; - public PostgresUserUploadStore(NpgsqlConnection connection, IFileInfoManager fileInfoManager) + public PostgresUserUploadStore(PostgresConnectionFactory connection) { _connection = connection; - _fileInfoManager = fileInfoManager; } - public async ValueTask> ListFiles(Guid user, PagedRequest request) + public async ValueTask> ListFiles(Guid user, PagedRequest request) { var query = @"select {0} from ""UserFiles"" uf, ""Files"" f @@ -24,32 +22,31 @@ where uf.""User"" = :user and uf.""File"" = f.""Id"""; var queryOrder = @"order by f.""{1}"" {2} limit :limit offset :offset"; - var orderBy = request.SortBy switch - { - PagedSortBy.Name => "Name", - PagedSortBy.Date => "Uploaded", - PagedSortBy.Size => "Size", - _ => "Id" - }; - var sortOrder = request.SortOrder switch - { - PageSortOrder.Dsc => "desc", - _ => "asc" - }; - var count = await _connection.ExecuteScalarAsync(string.Format(query, "count(*)"), new {user}); - var files = await _connection.QueryAsync( - string.Format(query + queryOrder, "uf.\"File\"", orderBy, sortOrder), - new {user, offset = request.Page * request.PageSize, limit = request.PageSize}); + await using var conn = await _connection.Get(); + var count = await conn.ExecuteScalarAsync(string.Format(query, "count(*)"), new {user}); - async IAsyncEnumerable EnumerateFiles() + async IAsyncEnumerable EnumerateFiles() { - foreach (var file in files ?? Enumerable.Empty()) + var orderBy = request.SortBy switch { - var v = await _fileInfoManager.Get(file); - if (v != default) - { - yield return v; - } + PagedSortBy.Name => "Name", + PagedSortBy.Date => "Uploaded", + PagedSortBy.Size => "Size", + _ => "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(); + while (await files.ReadAsync()) + { + yield return rowParser(files); } } @@ -62,12 +59,22 @@ and uf.""File"" = f.""Id"""; }; } + /// 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, user }); } + + /// + public async ValueTask Uploader(Guid file) + { + await using var conn = await _connection.Get(); + return await conn.ExecuteScalarAsync( + @"select ""User"" from ""UserFiles"" where ""File"" = :file", new {file}); + } } \ No newline at end of file diff --git a/VoidCat/Services/Users/UserUploadStore.cs b/VoidCat/Services/Users/UserUploadStore.cs index e13d0ed..41c1fbf 100644 --- a/VoidCat/Services/Users/UserUploadStore.cs +++ b/VoidCat/Services/Users/UserUploadStore.cs @@ -3,18 +3,18 @@ using VoidCat.Services.Abstractions; namespace VoidCat.Services.Users; +/// public class UserUploadStore : IUserUploadsStore { private readonly ICache _cache; - private readonly IFileInfoManager _fileInfo; - public UserUploadStore(ICache cache, IFileInfoManager fileInfo) + public UserUploadStore(ICache cache) { _cache = cache; - _fileInfo = fileInfo; } - public async ValueTask> ListFiles(Guid user, PagedRequest request) + /// + public async ValueTask> ListFiles(Guid user, PagedRequest request) { var ids = (await _cache.GetList(MapKey(user))).Select(Guid.Parse); ids = (request.SortBy, request.SortOrder) switch @@ -24,15 +24,12 @@ public class UserUploadStore : IUserUploadsStore _ => ids }; - async IAsyncEnumerable EnumerateResults(IEnumerable page) + var idsRendered = ids.ToList(); + async IAsyncEnumerable EnumerateResults(IEnumerable page) { - foreach (var guid in page) + foreach (var id in page) { - var info = await _fileInfo.Get(guid); - if (info != default) - { - yield return info; - } + yield return id; } } @@ -40,15 +37,24 @@ public class UserUploadStore : IUserUploadsStore { Page = request.Page, PageSize = request.PageSize, - TotalResults = ids?.Count() ?? 0, - Results = EnumerateResults(ids.Skip(request.Page * request.PageSize).Take(request.PageSize)) + TotalResults = idsRendered.Count, + Results = EnumerateResults(idsRendered.Skip(request.Page * request.PageSize).Take(request.PageSize)) }; } - public ValueTask AddFile(Guid user, PrivateVoidFile voidFile) + /// + 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); + } + + /// + public ValueTask Uploader(Guid file) + { + return _cache.Get(MapUploader(file)); } private static string MapKey(Guid id) => $"user:{id}:uploads"; + private static string MapUploader(Guid file) => $"file:{file}:uploader"; } \ No newline at end of file