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