using Dapper;
using VoidCat.Model;
using VoidCat.Services.Abstractions;
namespace VoidCat.Services.Files;
///
public class PostgresFileMetadataStore : IFileMetadataStore
{
private readonly PostgresConnectionFactory _connection;
public PostgresFileMetadataStore(PostgresConnectionFactory connection)
{
_connection = connection;
}
///
public ValueTask Get(Guid id)
{
return Get(id);
}
///
public ValueTask GetPrivate(Guid id)
{
return Get(id);
}
///
public async ValueTask Set(Guid id, SecretVoidFileMeta obj)
{
await using var conn = await _connection.Get();
await conn.ExecuteAsync(
@"insert into
""Files""(""Id"", ""Name"", ""Size"", ""Uploaded"", ""Description"", ""MimeType"", ""Digest"", ""EditSecret"", ""Expires"")
values(:id, :name, :size, :uploaded, :description, :mimeType, :digest, :editSecret, :expires)
on conflict (""Id"") do update set ""Name"" = :name, ""Description"" = :description, ""MimeType"" = :mimeType, ""Expires"" = :expires",
new
{
id,
name = obj.Name,
size = (long) obj.Size,
uploaded = obj.Uploaded.ToUniversalTime(),
description = obj.Description,
mimeType = obj.MimeType,
digest = obj.Digest,
editSecret = obj.EditSecret,
expires = obj.Expires
});
}
///
public async ValueTask Delete(Guid 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
{
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
{
await using var conn = await _connection.Get();
var ret = await conn.QueryAsync("select * from \"Files\" where \"Id\" in :ids", new {ids});
return ret.ToList();
}
///
public async ValueTask Update(Guid id, TMeta meta) where TMeta : VoidFileMeta
{
var oldMeta = await Get(id);
if (oldMeta == default) return;
oldMeta.Description = meta.Description ?? oldMeta.Description;
oldMeta.Name = meta.Name ?? oldMeta.Name;
oldMeta.MimeType = meta.MimeType ?? oldMeta.MimeType;
oldMeta.Expires = meta.Expires ?? oldMeta.Expires;
await Set(id, oldMeta);
}
///
public async ValueTask> ListFiles(PagedRequest request) where TMeta : VoidFileMeta
{
await using var conn = await _connection.Get();
var count = await conn.ExecuteScalarAsync(@"select count(*) from ""Files""");
async IAsyncEnumerable Enumerate()
{
var orderBy = request.SortBy switch
{
PagedSortBy.Date => "Uploaded",
PagedSortBy.Name => "Name",
PagedSortBy.Size => "Size",
_ => "Id"
};
await using var iconn = await _connection.Get();
var orderDirection = request.SortOrder == PageSortOrder.Asc ? "asc" : "desc";
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});
foreach (var meta in results)
{
yield return meta;
}
}
return new()
{
TotalResults = count,
PageSize = request.PageSize,
Page = request.Page,
Results = Enumerate()
};
}
///
public async ValueTask Stats()
{
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);
}
}