mirror of
https://git.v0l.io/Kieran/void.cat.git
synced 2025-04-02 11:38:01 +02:00
Refactor metadata
This commit is contained in:
parent
ad7b40df57
commit
6a5e1e24bd
@ -1,7 +1,6 @@
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services;
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Controllers;
|
||||
@ -32,9 +31,9 @@ public class DownloadController : Controller
|
||||
public async Task DownloadFile([FromRoute] string id)
|
||||
{
|
||||
var gid = id.FromBase58Guid();
|
||||
var meta = await SetupDownload(gid);
|
||||
var voidFile = await SetupDownload(gid);
|
||||
|
||||
var egressReq = new EgressRequest(gid, GetRanges(Request, (long)meta!.Size));
|
||||
var egressReq = new EgressRequest(gid, GetRanges(Request, (long) voidFile!.Metadata!.Size));
|
||||
if (egressReq.Ranges.Count() > 1)
|
||||
{
|
||||
_logger.LogWarning("Multi-range request not supported!");
|
||||
@ -46,10 +45,10 @@ public class DownloadController : Controller
|
||||
}
|
||||
else if (egressReq.Ranges.Count() == 1)
|
||||
{
|
||||
Response.StatusCode = (int)HttpStatusCode.PartialContent;
|
||||
Response.StatusCode = (int) HttpStatusCode.PartialContent;
|
||||
if (egressReq.Ranges.Sum(a => a.Size) == 0)
|
||||
{
|
||||
Response.StatusCode = (int)HttpStatusCode.RequestedRangeNotSatisfiable;
|
||||
Response.StatusCode = (int) HttpStatusCode.RequestedRangeNotSatisfiable;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -70,7 +69,7 @@ public class DownloadController : Controller
|
||||
await Response.CompleteAsync();
|
||||
}
|
||||
|
||||
private async Task<VoidFile?> SetupDownload(Guid id)
|
||||
private async Task<PublicVoidFile?> SetupDownload(Guid id)
|
||||
{
|
||||
var meta = await _storage.Get(id);
|
||||
if (meta == null)
|
||||
@ -111,4 +110,4 @@ public class DownloadController : Controller
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -23,11 +23,13 @@ namespace VoidCat.Controllers
|
||||
{
|
||||
var bw = await _statsReporter.GetBandwidth();
|
||||
var bytes = 0UL;
|
||||
var count = 0;
|
||||
await foreach (var vf in _fileStore.ListFiles())
|
||||
{
|
||||
bytes += vf.Size;
|
||||
bytes += vf.Metadata?.Size ?? 0;
|
||||
count++;
|
||||
}
|
||||
return new(bw, bytes);
|
||||
return new(bw, bytes, count);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -39,6 +41,6 @@ namespace VoidCat.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record GlobalStats(Bandwidth Bandwidth, ulong TotalBytes);
|
||||
public sealed record GlobalStats(Bandwidth Bandwidth, ulong TotalBytes, int Count);
|
||||
public sealed record FileStats(Bandwidth Bandwidth);
|
||||
}
|
||||
|
@ -28,7 +28,9 @@ namespace VoidCat.Controllers
|
||||
var meta = new VoidFileMeta()
|
||||
{
|
||||
MimeType = Request.Headers.GetHeader("V-Content-Type"),
|
||||
Name = Request.Headers.GetHeader("V-Filename")
|
||||
Name = Request.Headers.GetHeader("V-Filename"),
|
||||
Description = Request.Headers.GetHeader("V-Description"),
|
||||
Digest = Request.Headers.GetHeader("V-Full-Digest")
|
||||
};
|
||||
|
||||
var digest = Request.Headers.GetHeader("V-Digest");
|
||||
@ -72,24 +74,10 @@ namespace VoidCat.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[Route("{id}")]
|
||||
public ValueTask<VoidFile?> GetInfo([FromRoute] string id)
|
||||
public ValueTask<PublicVoidFile?> GetInfo([FromRoute] string id)
|
||||
{
|
||||
return _storage.Get(id.FromBase58Guid());
|
||||
}
|
||||
|
||||
[HttpPatch]
|
||||
[Route("{id}")]
|
||||
public ValueTask UpdateFileInfo([FromRoute] string id, [FromBody] UpdateFileInfoRequest request)
|
||||
{
|
||||
return _storage.UpdateInfo(new VoidFile()
|
||||
{
|
||||
Id = id.FromBase58Guid(),
|
||||
Metadata = request.Metadata
|
||||
}, request.EditSecret);
|
||||
}
|
||||
|
||||
public record UpdateFileInfoRequest([JsonConverter(typeof(Base58GuidConverter))] Guid EditSecret,
|
||||
VoidFileMeta Metadata);
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
@ -108,9 +96,9 @@ namespace VoidCat.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
public record UploadResult(bool Ok, InternalVoidFile? File, string? ErrorMessage)
|
||||
public record UploadResult(bool Ok, PrivateVoidFile? File, string? ErrorMessage)
|
||||
{
|
||||
public static UploadResult Success(InternalVoidFile vf)
|
||||
public static UploadResult Success(PrivateVoidFile vf)
|
||||
=> new(true, vf, null);
|
||||
|
||||
public static UploadResult Error(string message)
|
||||
|
30
VoidCat/Model/Paywall.cs
Normal file
30
VoidCat/Model/Paywall.cs
Normal file
@ -0,0 +1,30 @@
|
||||
namespace VoidCat.Model;
|
||||
|
||||
public record Paywall
|
||||
{
|
||||
public PaywallServices Service { get; init; }
|
||||
|
||||
public PaywallConfig? Config { get; init; }
|
||||
}
|
||||
|
||||
public enum PaywallServices
|
||||
{
|
||||
None,
|
||||
Strike
|
||||
}
|
||||
|
||||
public enum PaywallCurrencies
|
||||
{
|
||||
BTC,
|
||||
USD,
|
||||
EUR,
|
||||
GBP
|
||||
}
|
||||
|
||||
public abstract record PaywallConfig
|
||||
{
|
||||
public PaywallCurrencies Currency { get; init; }
|
||||
public decimal Cost { get; init; }
|
||||
}
|
||||
|
||||
public record StrikePaywallConfig(string Handle) : PaywallConfig;
|
@ -1,32 +1,31 @@
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace VoidCat.Model
|
||||
{
|
||||
public record VoidFile
|
||||
public abstract record VoidFile<TMeta> where TMeta : VoidFileMeta
|
||||
{
|
||||
/// <summary>
|
||||
/// Id of the file
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(Base58GuidConverter))]
|
||||
public Guid Id { get; init; }
|
||||
|
||||
public VoidFileMeta? Metadata { get; set; }
|
||||
|
||||
public ulong Size { get; init; }
|
||||
|
||||
public DateTimeOffset Uploaded { get; init; }
|
||||
}
|
||||
|
||||
public record InternalVoidFile : VoidFile
|
||||
{
|
||||
[JsonConverter(typeof(Base58GuidConverter))]
|
||||
public Guid EditSecret { get; init; }
|
||||
}
|
||||
|
||||
public record VoidFileMeta
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
|
||||
public string? Description { get; init; }
|
||||
/// <summary>
|
||||
/// Metadta related to the file
|
||||
/// </summary>
|
||||
public TMeta? Metadata { get; init; }
|
||||
|
||||
public string? MimeType { get; init; }
|
||||
/// <summary>
|
||||
/// Optional paywall config
|
||||
/// </summary>
|
||||
public Paywall? Paywall { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record PublicVoidFile : VoidFile<VoidFileMeta>
|
||||
{
|
||||
}
|
||||
|
||||
public sealed record PrivateVoidFile : VoidFile<SecretVoidFileMeta>
|
||||
{
|
||||
}
|
||||
}
|
57
VoidCat/Model/VoidFileMeta.cs
Normal file
57
VoidCat/Model/VoidFileMeta.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using Newtonsoft.Json;
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Model;
|
||||
|
||||
/// <summary>
|
||||
/// File metadata which is managed by <see cref="IFileMetadataStore"/>
|
||||
/// </summary>
|
||||
public record VoidFileMeta
|
||||
{
|
||||
/// <summary>
|
||||
/// Metadata version
|
||||
/// </summary>
|
||||
public int Version { get; init; } = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Filename
|
||||
/// </summary>
|
||||
public string? Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the file in storage
|
||||
/// </summary>
|
||||
public ulong Size { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Date file was uploaded
|
||||
/// </summary>
|
||||
public DateTimeOffset Uploaded { get; init; } = DateTimeOffset.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// Description about the file
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The content type of the file
|
||||
/// </summary>
|
||||
public string? MimeType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// SHA-256 hash of the file
|
||||
/// </summary>
|
||||
public string? Digest { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="VoidFile"/> with attached <see cref="EditSecret"/>
|
||||
/// </summary>
|
||||
public record SecretVoidFileMeta : VoidFileMeta
|
||||
{
|
||||
/// <summary>
|
||||
/// A secret key used to make edits to the file after its uploaded
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(Base58GuidConverter))]
|
||||
public Guid EditSecret { get; init; }
|
||||
}
|
@ -6,6 +6,7 @@ using StackExchange.Redis;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services;
|
||||
using VoidCat.Services.Abstractions;
|
||||
using VoidCat.Services.Migrations;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var services = builder.Services;
|
||||
@ -41,6 +42,8 @@ services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
};
|
||||
});
|
||||
|
||||
// void.cat services
|
||||
services.AddVoidMigrations();
|
||||
services.AddScoped<IFileMetadataStore, LocalDiskFileMetadataStore>();
|
||||
services.AddScoped<IFileStore, LocalDiskFileStore>();
|
||||
services.AddScoped<IAggregateStatsCollector, AggregateStatsCollector>();
|
||||
@ -61,6 +64,13 @@ else
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// run migrations
|
||||
var migrations = app.Services.GetServices<IMigration>();
|
||||
foreach (var migration in migrations)
|
||||
{
|
||||
await migration.Migrate();
|
||||
}
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseAuthentication();
|
||||
app.UseRouting();
|
||||
|
@ -4,9 +4,9 @@ namespace VoidCat.Services.Abstractions;
|
||||
|
||||
public interface IFileMetadataStore
|
||||
{
|
||||
ValueTask<InternalVoidFile?> Get(Guid id);
|
||||
ValueTask<SecretVoidFileMeta?> Get(Guid id);
|
||||
|
||||
ValueTask Set(InternalVoidFile meta);
|
||||
ValueTask Set(Guid id, SecretVoidFileMeta meta);
|
||||
|
||||
ValueTask Update(VoidFile patch, Guid editSecret);
|
||||
ValueTask Update(Guid id, SecretVoidFileMeta patch);
|
||||
}
|
||||
|
@ -4,15 +4,13 @@ namespace VoidCat.Services.Abstractions;
|
||||
|
||||
public interface IFileStore
|
||||
{
|
||||
ValueTask<VoidFile?> Get(Guid id);
|
||||
ValueTask<PublicVoidFile?> Get(Guid id);
|
||||
|
||||
ValueTask<InternalVoidFile> Ingress(IngressPayload payload, CancellationToken cts);
|
||||
ValueTask<PrivateVoidFile> Ingress(IngressPayload payload, CancellationToken cts);
|
||||
|
||||
ValueTask Egress(EgressRequest request, Stream outStream, CancellationToken cts);
|
||||
|
||||
ValueTask UpdateInfo(VoidFile patch, Guid editSecret);
|
||||
|
||||
IAsyncEnumerable<VoidFile> ListFiles();
|
||||
IAsyncEnumerable<PublicVoidFile> ListFiles();
|
||||
}
|
||||
|
||||
public sealed record IngressPayload(Stream InStream, VoidFileMeta Meta, string Hash)
|
||||
|
@ -7,7 +7,7 @@ namespace VoidCat.Services;
|
||||
|
||||
public class LocalDiskFileMetadataStore : IFileMetadataStore
|
||||
{
|
||||
private const string MetadataDir = "metadata";
|
||||
private const string MetadataDir = "metadata-v2";
|
||||
private readonly VoidSettings _settings;
|
||||
|
||||
public LocalDiskFileMetadataStore(VoidSettings settings)
|
||||
@ -21,34 +21,31 @@ public class LocalDiskFileMetadataStore : IFileMetadataStore
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<InternalVoidFile?> Get(Guid id)
|
||||
public async ValueTask<SecretVoidFileMeta?> Get(Guid id)
|
||||
{
|
||||
var path = MapMeta(id);
|
||||
if (!File.Exists(path)) return default;
|
||||
|
||||
var json = await File.ReadAllTextAsync(path);
|
||||
return JsonConvert.DeserializeObject<InternalVoidFile>(json);
|
||||
return JsonConvert.DeserializeObject<SecretVoidFileMeta>(json);
|
||||
}
|
||||
|
||||
public async ValueTask Set(InternalVoidFile meta)
|
||||
public async ValueTask Set(Guid id, SecretVoidFileMeta meta)
|
||||
{
|
||||
var path = MapMeta(meta.Id);
|
||||
var path = MapMeta(id);
|
||||
var json = JsonConvert.SerializeObject(meta);
|
||||
await File.WriteAllTextAsync(path, json);
|
||||
}
|
||||
|
||||
public async ValueTask Update(VoidFile patch, Guid editSecret)
|
||||
public async ValueTask Update(Guid id, SecretVoidFileMeta patch)
|
||||
{
|
||||
var oldMeta = await Get(patch.Id);
|
||||
if (oldMeta?.EditSecret != editSecret)
|
||||
var oldMeta = await Get(id);
|
||||
if (oldMeta?.EditSecret != patch.EditSecret)
|
||||
{
|
||||
throw new VoidNotAllowedException("Edit secret incorrect");
|
||||
}
|
||||
|
||||
// only patch metadata
|
||||
oldMeta.Metadata = patch.Metadata;
|
||||
|
||||
await Set(oldMeta);
|
||||
await Set(id, patch);
|
||||
}
|
||||
|
||||
private string MapMeta(Guid id) =>
|
||||
|
@ -26,9 +26,13 @@ public class LocalDiskFileStore : IFileStore
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<VoidFile?> Get(Guid id)
|
||||
public async ValueTask<PublicVoidFile?> Get(Guid id)
|
||||
{
|
||||
return await _metadataStore.Get(id);
|
||||
return new()
|
||||
{
|
||||
Id = id,
|
||||
Metadata = await _metadataStore.Get(id)
|
||||
};
|
||||
}
|
||||
|
||||
public async ValueTask Egress(EgressRequest request, Stream outStream, CancellationToken cts)
|
||||
@ -47,11 +51,11 @@ public class LocalDiskFileStore : IFileStore
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<InternalVoidFile> Ingress(IngressPayload payload, CancellationToken cts)
|
||||
public async ValueTask<PrivateVoidFile> Ingress(IngressPayload payload, CancellationToken cts)
|
||||
{
|
||||
var id = payload.Id ?? Guid.NewGuid();
|
||||
var fPath = MapPath(id);
|
||||
InternalVoidFile? vf = null;
|
||||
SecretVoidFileMeta? vf = null;
|
||||
if (payload.IsAppend)
|
||||
{
|
||||
vf = await _metadataStore.Get(payload.Id!.Value);
|
||||
@ -81,10 +85,12 @@ public class LocalDiskFileStore : IFileStore
|
||||
}
|
||||
else
|
||||
{
|
||||
vf = new InternalVoidFile()
|
||||
vf = new SecretVoidFileMeta()
|
||||
{
|
||||
Id = id,
|
||||
Metadata = payload.Meta,
|
||||
Name = payload.Meta.Name,
|
||||
Description = payload.Meta.Description,
|
||||
Digest = payload.Meta.Digest,
|
||||
MimeType = payload.Meta.MimeType,
|
||||
Uploaded = DateTimeOffset.UtcNow,
|
||||
EditSecret = Guid.NewGuid(),
|
||||
Size = total
|
||||
@ -92,16 +98,15 @@ public class LocalDiskFileStore : IFileStore
|
||||
}
|
||||
|
||||
|
||||
await _metadataStore.Set(vf);
|
||||
return vf;
|
||||
await _metadataStore.Set(id, vf);
|
||||
return new()
|
||||
{
|
||||
Id = id,
|
||||
Metadata = vf
|
||||
};
|
||||
}
|
||||
|
||||
public ValueTask UpdateInfo(VoidFile patch, Guid editSecret)
|
||||
{
|
||||
return _metadataStore.Update(patch, editSecret);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<VoidFile> ListFiles()
|
||||
public async IAsyncEnumerable<PublicVoidFile> ListFiles()
|
||||
{
|
||||
foreach (var fe in Directory.EnumerateFiles(_settings.DataDirectory))
|
||||
{
|
||||
@ -111,7 +116,11 @@ public class LocalDiskFileStore : IFileStore
|
||||
var meta = await _metadataStore.Get(id);
|
||||
if (meta != default)
|
||||
{
|
||||
yield return meta;
|
||||
yield return new()
|
||||
{
|
||||
Id = id,
|
||||
Metadata = meta
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -134,9 +143,9 @@ public class LocalDiskFileStore : IFileStore
|
||||
var totalRead = readLength + offset;
|
||||
var buf = buffer.Memory[..totalRead];
|
||||
await fs.WriteAsync(buf, cts);
|
||||
await _stats.TrackIngress(id, (ulong)buf.Length);
|
||||
await _stats.TrackIngress(id, (ulong) buf.Length);
|
||||
sha.TransformBlock(buf.ToArray(), 0, buf.Length, null, 0);
|
||||
total += (ulong)buf.Length;
|
||||
total += (ulong) buf.Length;
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
@ -160,7 +169,7 @@ public class LocalDiskFileStore : IFileStore
|
||||
|
||||
var fullSize = readLength + offset;
|
||||
await outStream.WriteAsync(buffer.Memory[..fullSize], cts);
|
||||
await _stats.TrackEgress(id, (ulong)fullSize);
|
||||
await _stats.TrackEgress(id, (ulong) fullSize);
|
||||
await outStream.FlushAsync(cts);
|
||||
offset = 0;
|
||||
}
|
||||
@ -188,8 +197,8 @@ public class LocalDiskFileStore : IFileStore
|
||||
|
||||
var fullSize = readLength + offset;
|
||||
var toWrite = Math.Min(fullSize, dataRemaining);
|
||||
await outStream.WriteAsync(buffer.Memory[..(int)toWrite], cts);
|
||||
await _stats.TrackEgress(id, (ulong)toWrite);
|
||||
await outStream.WriteAsync(buffer.Memory[..(int) toWrite], cts);
|
||||
await _stats.TrackEgress(id, (ulong) toWrite);
|
||||
await outStream.FlushAsync(cts);
|
||||
dataRemaining -= toWrite;
|
||||
offset = 0;
|
||||
@ -204,4 +213,4 @@ public class LocalDiskFileStore : IFileStore
|
||||
|
||||
private string MapPath(Guid id) =>
|
||||
Path.Join(_settings.DataDirectory, id.ToString());
|
||||
}
|
||||
}
|
116
VoidCat/Services/Migrations/20220217_MigrateMetadata.cs
Normal file
116
VoidCat/Services/Migrations/20220217_MigrateMetadata.cs
Normal file
@ -0,0 +1,116 @@
|
||||
using Newtonsoft.Json;
|
||||
using VoidCat.Model;
|
||||
|
||||
namespace VoidCat.Services.Migrations;
|
||||
|
||||
public class MigrateMetadata_20220217 : IMigration
|
||||
{
|
||||
private const string MetadataDir = "metadata";
|
||||
private const string MetadataV2Dir = "metadata-v2";
|
||||
private readonly ILogger<MigrateMetadata_20220217> _logger;
|
||||
private readonly VoidSettings _settings;
|
||||
|
||||
public MigrateMetadata_20220217(VoidSettings settings, ILogger<MigrateMetadata_20220217> log)
|
||||
{
|
||||
_settings = settings;
|
||||
_logger = log;
|
||||
}
|
||||
|
||||
public async ValueTask Migrate()
|
||||
{
|
||||
var newMeta = Path.Combine(_settings.DataDirectory, MetadataV2Dir);
|
||||
if (!Directory.Exists(newMeta))
|
||||
{
|
||||
Directory.CreateDirectory(newMeta);
|
||||
}
|
||||
|
||||
foreach (var fe in Directory.EnumerateFiles(_settings.DataDirectory))
|
||||
{
|
||||
var filename = Path.GetFileNameWithoutExtension(fe);
|
||||
if (!Guid.TryParse(filename, out var id)) continue;
|
||||
|
||||
var fp = MapMeta(id);
|
||||
if (File.Exists(fp))
|
||||
{
|
||||
_logger.LogInformation("Migrating metadata for {file}", fp);
|
||||
try
|
||||
{
|
||||
var oldJson = await File.ReadAllTextAsync(fp);
|
||||
if(!oldJson.Contains("\"Metadata\":")) continue; // old format should contain "Metadata":
|
||||
|
||||
var old = JsonConvert.DeserializeObject<InternalVoidFile>(oldJson);
|
||||
var newObj = new PrivateVoidFile()
|
||||
{
|
||||
Id = old!.Id,
|
||||
Metadata = new()
|
||||
{
|
||||
Name = old.Metadata!.Name,
|
||||
Description = old.Metadata.Description,
|
||||
Uploaded = old.Uploaded,
|
||||
MimeType = old.Metadata.MimeType,
|
||||
EditSecret = old.EditSecret,
|
||||
Size = old.Size
|
||||
}
|
||||
};
|
||||
|
||||
await File.WriteAllTextAsync(MapV2Meta(id), JsonConvert.SerializeObject(newObj));
|
||||
|
||||
// delete old metadata
|
||||
File.Delete(fp);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string MapMeta(Guid id) =>
|
||||
Path.ChangeExtension(Path.Join(_settings.DataDirectory, MetadataDir, id.ToString()), ".json");
|
||||
private string MapV2Meta(Guid id) =>
|
||||
Path.ChangeExtension(Path.Join(_settings.DataDirectory, MetadataV2Dir, id.ToString()), ".json");
|
||||
|
||||
private record VoidFile
|
||||
{
|
||||
[JsonConverter(typeof(Base58GuidConverter))]
|
||||
public Guid Id { get; init; }
|
||||
|
||||
public VoidFileMeta? Metadata { get; set; }
|
||||
|
||||
public ulong Size { get; init; }
|
||||
|
||||
public DateTimeOffset Uploaded { get; init; }
|
||||
}
|
||||
|
||||
private record InternalVoidFile : VoidFile
|
||||
{
|
||||
[JsonConverter(typeof(Base58GuidConverter))]
|
||||
public Guid EditSecret { get; init; }
|
||||
}
|
||||
|
||||
private record VoidFileMeta
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
|
||||
public string? Description { get; init; }
|
||||
|
||||
public string? MimeType { get; init; }
|
||||
}
|
||||
|
||||
private record NewVoidFileMeta
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
public ulong Size { get; init; }
|
||||
public DateTimeOffset Uploaded { get; init; } = DateTimeOffset.UtcNow;
|
||||
public string? Description { get; init; }
|
||||
public string? MimeType { get; init; }
|
||||
public string? Digest { get; init; }
|
||||
}
|
||||
|
||||
private record NewSecretVoidFileMeta : NewVoidFileMeta
|
||||
{
|
||||
[JsonConverter(typeof(Base58GuidConverter))]
|
||||
public Guid EditSecret { get; init; }
|
||||
}
|
||||
}
|
15
VoidCat/Services/Migrations/IMigration.cs
Normal file
15
VoidCat/Services/Migrations/IMigration.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace VoidCat.Services.Migrations;
|
||||
|
||||
public interface IMigration
|
||||
{
|
||||
ValueTask Migrate();
|
||||
}
|
||||
|
||||
public static class Migrations
|
||||
{
|
||||
public static IServiceCollection AddVoidMigrations(this IServiceCollection svc)
|
||||
{
|
||||
svc.AddTransient<IMigration, MigrateMetadata_20220217>();
|
||||
return svc;
|
||||
}
|
||||
}
|
@ -4,14 +4,11 @@
|
||||
"private": true,
|
||||
"proxy": "https://localhost:7195",
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.0.0",
|
||||
"@testing-library/user-event": "^13.2.1",
|
||||
"feather-icons-react": "^0.5.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-router-dom": "^6.2.1",
|
||||
"react-scripts": "5.0.0",
|
||||
"web-vitals": "^2.1.0"
|
||||
"react-scripts": "5.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
@ -3,7 +3,6 @@ import {useEffect, useState} from "react";
|
||||
import "./FileUpload.css";
|
||||
import {buf2hex, ConstName, FormatBytes} from "./Util";
|
||||
import {RateCalculator} from "./RateCalculator";
|
||||
import {upload} from "@testing-library/user-event/dist/upload";
|
||||
|
||||
const UploadState = {
|
||||
NotStarted: 0,
|
||||
|
@ -2,4 +2,13 @@
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
margin: 0 30px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.stats svg {
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.stats > div {
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import FeatherIcon from "feather-icons-react";
|
||||
import {FormatBytes} from "./Util";
|
||||
|
||||
import "./GlobalStats.css";
|
||||
@ -16,13 +17,23 @@ export function GlobalStats(props) {
|
||||
useEffect(() => loadStats(), []);
|
||||
|
||||
return (
|
||||
<div className="stats">
|
||||
<div>Ingress:</div>
|
||||
<div>{FormatBytes(stats?.bandwidth?.ingress ?? 0, 2)}</div>
|
||||
<div>Egress:</div>
|
||||
<div>{FormatBytes(stats?.bandwidth?.egress ?? 0, 2)}</div>
|
||||
<div>Storage:</div>
|
||||
<div>{FormatBytes(stats?.totalBytes ?? 0, 2)}</div>
|
||||
</div>
|
||||
<dl className="stats">
|
||||
<div>
|
||||
<FeatherIcon icon="upload-cloud" />
|
||||
{FormatBytes(stats?.bandwidth?.ingress ?? 0, 2)}
|
||||
</div>
|
||||
<div>
|
||||
<FeatherIcon icon="download-cloud" />
|
||||
{FormatBytes(stats?.bandwidth?.egress ?? 0, 2)}
|
||||
</div>
|
||||
<div>
|
||||
<FeatherIcon icon="database" />
|
||||
{FormatBytes(stats?.totalBytes ?? 0, 2)}
|
||||
</div>
|
||||
<div>
|
||||
<FeatherIcon icon="hash" />
|
||||
{stats?.count ?? 0}
|
||||
</div>
|
||||
</dl>
|
||||
);
|
||||
}
|
@ -1,17 +1,11 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import './index.css';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
);
|
@ -1,13 +0,0 @@
|
||||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
@ -1,5 +0,0 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
@ -1015,7 +1015,7 @@
|
||||
core-js-pure "^3.20.2"
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.8.4":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa"
|
||||
integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==
|
||||
@ -1491,50 +1491,6 @@
|
||||
"@svgr/plugin-svgo" "^5.5.0"
|
||||
loader-utils "^2.0.0"
|
||||
|
||||
"@testing-library/dom@^8.0.0":
|
||||
version "8.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.11.2.tgz#fc110c665a066c2287be765e4a35ba8dad737015"
|
||||
integrity sha512-idsS/cqbYudXcVWngc1PuWNmXs416oBy2g/7Q8QAUREt5Z3MUkAL2XJD7xazLJ6esDfqRDi/ZBxk+OPPXitHRw==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.10.4"
|
||||
"@babel/runtime" "^7.12.5"
|
||||
"@types/aria-query" "^4.2.0"
|
||||
aria-query "^5.0.0"
|
||||
chalk "^4.1.0"
|
||||
dom-accessibility-api "^0.5.9"
|
||||
lz-string "^1.4.4"
|
||||
pretty-format "^27.0.2"
|
||||
|
||||
"@testing-library/jest-dom@^5.14.1":
|
||||
version "5.16.1"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.1.tgz#3db7df5ae97596264a7da9696fe14695ba02e51f"
|
||||
integrity sha512-ajUJdfDIuTCadB79ukO+0l8O+QwN0LiSxDaYUTI4LndbbUsGi6rWU1SCexXzBA2NSjlVB9/vbkasQIL3tmPBjw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.9.2"
|
||||
"@types/testing-library__jest-dom" "^5.9.1"
|
||||
aria-query "^5.0.0"
|
||||
chalk "^3.0.0"
|
||||
css "^3.0.0"
|
||||
css.escape "^1.5.1"
|
||||
dom-accessibility-api "^0.5.6"
|
||||
lodash "^4.17.15"
|
||||
redent "^3.0.0"
|
||||
|
||||
"@testing-library/react@^12.0.0":
|
||||
version "12.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.2.tgz#f1bc9a45943461fa2a598bb4597df1ae044cfc76"
|
||||
integrity sha512-ihQiEOklNyHIpo2Y8FREkyD1QAea054U0MVbwH1m8N9TxeFz+KoJ9LkqoKqJlzx2JDm56DVwaJ1r36JYxZM05g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
"@testing-library/dom" "^8.0.0"
|
||||
|
||||
"@testing-library/user-event@^13.2.1":
|
||||
version "13.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.5.0.tgz#69d77007f1e124d55314a2b73fd204b333b13295"
|
||||
integrity sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.5"
|
||||
|
||||
"@tootallnate/once@1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
|
||||
@ -1545,11 +1501,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
|
||||
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
|
||||
|
||||
"@types/aria-query@^4.2.0":
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
|
||||
integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==
|
||||
|
||||
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14":
|
||||
version "7.1.18"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.18.tgz#1a29abcc411a9c05e2094c98f9a1b7da6cdf49f8"
|
||||
@ -1704,14 +1655,6 @@
|
||||
dependencies:
|
||||
"@types/istanbul-lib-report" "*"
|
||||
|
||||
"@types/jest@*":
|
||||
version "27.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.4.0.tgz#037ab8b872067cae842a320841693080f9cb84ed"
|
||||
integrity sha512-gHl8XuC1RZ8H2j5sHv/JqsaxXkDDM9iDOgu0Wp8sjs4u/snb2PVehyWXJPr+ORA0RPpgw231mnutWI1+0hgjIQ==
|
||||
dependencies:
|
||||
jest-diff "^27.0.0"
|
||||
pretty-format "^27.0.0"
|
||||
|
||||
"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
|
||||
version "7.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
|
||||
@ -1796,13 +1739,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
|
||||
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
|
||||
|
||||
"@types/testing-library__jest-dom@^5.9.1":
|
||||
version "5.14.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.2.tgz#564fb2b2dc827147e937a75b639a05d17ce18b44"
|
||||
integrity sha512-vehbtyHUShPxIa9SioxDwCvgxukDMH//icJG90sXQBUm5lJOHLT5kNeU9tnivhnA/TkOFMzGIXN2cTc4hY8/kg==
|
||||
dependencies:
|
||||
"@types/jest" "*"
|
||||
|
||||
"@types/trusted-types@^2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
|
||||
@ -2241,11 +2177,6 @@ aria-query@^4.2.2:
|
||||
"@babel/runtime" "^7.10.2"
|
||||
"@babel/runtime-corejs3" "^7.10.2"
|
||||
|
||||
aria-query@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c"
|
||||
integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==
|
||||
|
||||
array-flatten@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
|
||||
@ -2322,11 +2253,6 @@ at-least-node@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
|
||||
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
|
||||
|
||||
atob@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||
|
||||
autoprefixer@^10.4.2:
|
||||
version "10.4.2"
|
||||
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.2.tgz#25e1df09a31a9fba5c40b578936b90d35c9d4d3b"
|
||||
@ -2688,14 +2614,6 @@ chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
|
||||
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||
@ -3087,20 +3005,6 @@ css-what@^5.1.0:
|
||||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe"
|
||||
integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==
|
||||
|
||||
css.escape@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb"
|
||||
integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=
|
||||
|
||||
css@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d"
|
||||
integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==
|
||||
dependencies:
|
||||
inherits "^2.0.4"
|
||||
source-map "^0.6.1"
|
||||
source-map-resolve "^0.6.0"
|
||||
|
||||
cssdb@^5.0.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-5.1.0.tgz#ec728d5f5c0811debd0820cbebda505d43003400"
|
||||
@ -3224,11 +3128,6 @@ decimal.js@^10.2.1:
|
||||
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
|
||||
integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==
|
||||
|
||||
decode-uri-component@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
||||
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
|
||||
|
||||
dedent@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
|
||||
@ -3392,11 +3291,6 @@ doctrine@^3.0.0:
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9:
|
||||
version "0.5.10"
|
||||
resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.10.tgz#caa6d08f60388d0bb4539dd75fe458a9a1d0014c"
|
||||
integrity sha512-Xu9mD0UjrJisTmv7lmVSDMagQcU9R5hwAbxsaAE/35XPnPLJobbuREfV/rraiSaEj/UOvgrzQs66zyTWTlyd+g==
|
||||
|
||||
dom-converter@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
|
||||
@ -4012,6 +3906,11 @@ fb-watchman@^2.0.0:
|
||||
dependencies:
|
||||
bser "2.1.1"
|
||||
|
||||
feather-icons-react@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/feather-icons-react/-/feather-icons-react-0.5.0.tgz#74f8b398f4031491901aa47ff470899e408df159"
|
||||
integrity sha512-k7y6JnghcwLi3uo5SaSnnngfHOE+IPpAFzlsmNhlXwbP8jev2rOYbEq1g5lhMglbV934KhcaSbo3DYV32I3/Ug==
|
||||
|
||||
file-entry-cache@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
|
||||
@ -4603,7 +4502,7 @@ inflight@^1.0.4:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
|
||||
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
@ -4973,7 +4872,7 @@ jest-config@^27.4.7:
|
||||
pretty-format "^27.4.6"
|
||||
slash "^3.0.0"
|
||||
|
||||
jest-diff@^27.0.0, jest-diff@^27.4.6:
|
||||
jest-diff@^27.4.6:
|
||||
version "27.4.6"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.4.6.tgz#93815774d2012a2cbb6cf23f84d48c7a2618f98d"
|
||||
integrity sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w==
|
||||
@ -5589,7 +5488,7 @@ lodash.uniq@^4.5.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
|
||||
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
|
||||
|
||||
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
|
||||
lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
@ -5615,11 +5514,6 @@ lru-cache@^6.0.0:
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
lz-string@^1.4.4:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
|
||||
integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=
|
||||
|
||||
magic-string@^0.25.0, magic-string@^0.25.7:
|
||||
version "0.25.7"
|
||||
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
|
||||
@ -5713,11 +5607,6 @@ mimic-fn@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
||||
|
||||
min-indent@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
|
||||
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
|
||||
|
||||
mini-css-extract-plugin@^2.4.5:
|
||||
version "2.5.3"
|
||||
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.5.3.tgz#c5c79f9b22ce9b4f164e9492267358dbe35376d9"
|
||||
@ -6737,7 +6626,7 @@ pretty-error@^4.0.0:
|
||||
lodash "^4.17.20"
|
||||
renderkid "^3.0.0"
|
||||
|
||||
pretty-format@^27.0.0, pretty-format@^27.0.2, pretty-format@^27.4.6:
|
||||
pretty-format@^27.4.6:
|
||||
version "27.4.6"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.4.6.tgz#1b784d2f53c68db31797b2348fa39b49e31846b7"
|
||||
integrity sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==
|
||||
@ -7027,14 +6916,6 @@ recursive-readdir@^2.2.2:
|
||||
dependencies:
|
||||
minimatch "3.0.4"
|
||||
|
||||
redent@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
|
||||
integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==
|
||||
dependencies:
|
||||
indent-string "^4.0.0"
|
||||
strip-indent "^3.0.0"
|
||||
|
||||
regenerate-unicode-properties@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz#54d09c7115e1f53dc2314a974b32c1c344efe326"
|
||||
@ -7477,14 +7358,6 @@ source-map-loader@^3.0.0:
|
||||
iconv-lite "^0.6.3"
|
||||
source-map-js "^1.0.1"
|
||||
|
||||
source-map-resolve@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2"
|
||||
integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==
|
||||
dependencies:
|
||||
atob "^2.1.2"
|
||||
decode-uri-component "^0.2.0"
|
||||
|
||||
source-map-support@^0.5.6, source-map-support@~0.5.20:
|
||||
version "0.5.21"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
|
||||
@ -7692,13 +7565,6 @@ strip-final-newline@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
|
||||
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
|
||||
|
||||
strip-indent@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
|
||||
integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==
|
||||
dependencies:
|
||||
min-indent "^1.0.0"
|
||||
|
||||
strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
||||
@ -8184,11 +8050,6 @@ wbuf@^1.1.0, wbuf@^1.7.3:
|
||||
dependencies:
|
||||
minimalistic-assert "^1.0.0"
|
||||
|
||||
web-vitals@^2.1.0:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-2.1.4.tgz#76563175a475a5e835264d373704f9dde718290c"
|
||||
integrity sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==
|
||||
|
||||
webidl-conversions@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
|
||||
|
Loading…
x
Reference in New Issue
Block a user