forked from Kieran/void.cat
Refactor basic store
This commit is contained in:
parent
5d07cc93eb
commit
156cec9828
@ -28,7 +28,9 @@ public class UserController : Controller
|
|||||||
var requestedId = isMe ? loggedUser!.Value : id.FromBase58Guid();
|
var requestedId = isMe ? loggedUser!.Value : id.FromBase58Guid();
|
||||||
if (loggedUser == requestedId)
|
if (loggedUser == requestedId)
|
||||||
{
|
{
|
||||||
return Json(await _store.Get<PrivateVoidUser>(requestedId));
|
var pUser = await _store.Get<PrivateVoidUser>(requestedId);
|
||||||
|
if (pUser == default) return NotFound();
|
||||||
|
return Json(pUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
var user = await _store.Get<PublicVoidUser>(requestedId);
|
var user = await _store.Get<PublicVoidUser>(requestedId);
|
||||||
@ -94,7 +96,7 @@ public class UserController : Controller
|
|||||||
if (!await _emailVerification.VerifyCode(user, token)) return BadRequest();
|
if (!await _emailVerification.VerifyCode(user, token)) return BadRequest();
|
||||||
|
|
||||||
user.Flags |= VoidUserFlags.EmailVerified;
|
user.Flags |= VoidUserFlags.EmailVerified;
|
||||||
await _store.Set(user);
|
await _store.Set(user.Id, user);
|
||||||
return Accepted();
|
return Accepted();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ using VoidCat.Services.Paywall;
|
|||||||
using VoidCat.Services.Redis;
|
using VoidCat.Services.Redis;
|
||||||
using VoidCat.Services.Stats;
|
using VoidCat.Services.Stats;
|
||||||
using VoidCat.Services.Users;
|
using VoidCat.Services.Users;
|
||||||
|
using VoidCat.Services.VirusScanner;
|
||||||
|
|
||||||
// setup JsonConvert default settings
|
// setup JsonConvert default settings
|
||||||
JsonSerializerSettings ConfigJsonSettings(JsonSerializerSettings s)
|
JsonSerializerSettings ConfigJsonSettings(JsonSerializerSettings s)
|
||||||
@ -99,6 +100,9 @@ services.AddTransient<IEmailVerification, EmailVerification>();
|
|||||||
// background services
|
// background services
|
||||||
services.AddHostedService<DeleteUnverifiedAccounts>();
|
services.AddHostedService<DeleteUnverifiedAccounts>();
|
||||||
|
|
||||||
|
// virus scanner
|
||||||
|
services.AddVirusScanner(voidSettings);
|
||||||
|
|
||||||
if (useRedis)
|
if (useRedis)
|
||||||
{
|
{
|
||||||
services.AddTransient<ICache, RedisCache>();
|
services.AddTransient<ICache, RedisCache>();
|
||||||
|
12
VoidCat/Services/Abstractions/IBasicStore.cs
Normal file
12
VoidCat/Services/Abstractions/IBasicStore.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
|
public interface IBasicStore<T>
|
||||||
|
{
|
||||||
|
ValueTask<T?> Get(Guid id);
|
||||||
|
|
||||||
|
ValueTask Set(Guid id, T obj);
|
||||||
|
|
||||||
|
ValueTask Delete(Guid id);
|
||||||
|
|
||||||
|
string MapKey(Guid id);
|
||||||
|
}
|
@ -2,11 +2,7 @@ using VoidCat.Model;
|
|||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
public interface IFileMetadataStore
|
public interface IFileMetadataStore : IPublicPrivateStore<VoidFileMeta, SecretVoidFileMeta>
|
||||||
{
|
{
|
||||||
ValueTask<TMeta?> Get<TMeta>(Guid id) where TMeta : VoidFileMeta;
|
ValueTask<TMeta?> Get<TMeta>(Guid id) where TMeta : VoidFileMeta;
|
||||||
|
|
||||||
ValueTask Set(Guid id, SecretVoidFileMeta meta);
|
|
||||||
|
|
||||||
ValueTask Delete(Guid id);
|
|
||||||
}
|
}
|
||||||
|
@ -11,4 +11,6 @@ public interface IFileStore
|
|||||||
ValueTask<PagedResult<PublicVoidFile>> ListFiles(PagedRequest request);
|
ValueTask<PagedResult<PublicVoidFile>> ListFiles(PagedRequest request);
|
||||||
|
|
||||||
ValueTask DeleteFile(Guid id);
|
ValueTask DeleteFile(Guid id);
|
||||||
|
|
||||||
|
ValueTask<Stream> Open(EgressRequest request, CancellationToken cts);
|
||||||
}
|
}
|
@ -2,12 +2,8 @@ using VoidCat.Model.Paywall;
|
|||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
public interface IPaywallStore
|
public interface IPaywallStore : IBasicStore<PaywallConfig>
|
||||||
{
|
{
|
||||||
ValueTask<PaywallOrder?> GetOrder(Guid id);
|
ValueTask<PaywallOrder?> GetOrder(Guid id);
|
||||||
ValueTask SaveOrder(PaywallOrder order);
|
ValueTask SaveOrder(PaywallOrder order);
|
||||||
|
|
||||||
ValueTask<PaywallConfig?> Get(Guid id);
|
|
||||||
ValueTask Set(Guid id, PaywallConfig config);
|
|
||||||
ValueTask Delete(Guid id);
|
|
||||||
}
|
}
|
10
VoidCat/Services/Abstractions/IPublicPrivateStore.cs
Normal file
10
VoidCat/Services/Abstractions/IPublicPrivateStore.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
|
public interface IPublicPrivateStore<TPublic, in TPrivate>
|
||||||
|
{
|
||||||
|
ValueTask<TPublic?> Get(Guid id);
|
||||||
|
|
||||||
|
ValueTask Set(Guid id, TPrivate obj);
|
||||||
|
|
||||||
|
ValueTask Delete(Guid id);
|
||||||
|
}
|
@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
public interface IUserStore
|
public interface IUserStore : IPublicPrivateStore<VoidUser, InternalVoidUser>
|
||||||
{
|
{
|
||||||
ValueTask<Guid?> LookupUser(string email);
|
|
||||||
ValueTask<T?> Get<T>(Guid id) where T : VoidUser;
|
ValueTask<T?> Get<T>(Guid id) where T : VoidUser;
|
||||||
ValueTask Set(InternalVoidUser user);
|
ValueTask Delete(PrivateVoidUser user);
|
||||||
|
|
||||||
|
ValueTask<Guid?> LookupUser(string email);
|
||||||
ValueTask<PagedResult<PrivateVoidUser>> ListUsers(PagedRequest request);
|
ValueTask<PagedResult<PrivateVoidUser>> ListUsers(PagedRequest request);
|
||||||
ValueTask UpdateProfile(PublicVoidUser newUser);
|
ValueTask UpdateProfile(PublicVoidUser newUser);
|
||||||
ValueTask Delete(PrivateVoidUser user);
|
|
||||||
}
|
}
|
@ -34,6 +34,7 @@ public class DeleteUnverifiedAccounts : BackgroundService
|
|||||||
await _userStore.Delete(account);
|
await _userStore.Delete(account);
|
||||||
|
|
||||||
var files = await _userUploads.ListFiles(account.Id, new(0, Int32.MinValue));
|
var files = await _userUploads.ListFiles(account.Id, new(0, Int32.MinValue));
|
||||||
|
// ReSharper disable once UseCancellationTokenForIAsyncEnumerable
|
||||||
await foreach (var file in files.Results)
|
await foreach (var file in files.Results)
|
||||||
{
|
{
|
||||||
await _fileStore.DeleteFile(file.Id);
|
await _fileStore.DeleteFile(file.Id);
|
||||||
|
30
VoidCat/Services/BasicCacheStore.cs
Normal file
30
VoidCat/Services/BasicCacheStore.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
|
namespace VoidCat.Services;
|
||||||
|
|
||||||
|
public abstract class BasicCacheStore<TStore> : IBasicStore<TStore>
|
||||||
|
{
|
||||||
|
protected readonly ICache _cache;
|
||||||
|
|
||||||
|
protected BasicCacheStore(ICache cache)
|
||||||
|
{
|
||||||
|
_cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual ValueTask<TStore?> Get(Guid id)
|
||||||
|
{
|
||||||
|
return _cache.Get<TStore>(MapKey(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual ValueTask Set(Guid id, TStore obj)
|
||||||
|
{
|
||||||
|
return _cache.Set(MapKey(id), obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual ValueTask Delete(Guid id)
|
||||||
|
{
|
||||||
|
return _cache.Delete(MapKey(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract string MapKey(Guid id);
|
||||||
|
}
|
@ -28,6 +28,11 @@ public class LocalDiskFileMetadataStore : IFileMetadataStore
|
|||||||
return GetMeta<TMeta>(id);
|
return GetMeta<TMeta>(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ValueTask<VoidFileMeta?> Get(Guid id)
|
||||||
|
{
|
||||||
|
return GetMeta<VoidFileMeta>(id);
|
||||||
|
}
|
||||||
|
|
||||||
public async ValueTask Set(Guid id, SecretVoidFileMeta meta)
|
public async ValueTask Set(Guid id, SecretVoidFileMeta meta)
|
||||||
{
|
{
|
||||||
var path = MapMeta(id);
|
var path = MapMeta(id);
|
||||||
|
@ -30,10 +30,7 @@ public class LocalDiskFileStore : StreamFileStore, IFileStore
|
|||||||
|
|
||||||
public async ValueTask Egress(EgressRequest request, Stream outStream, CancellationToken cts)
|
public async ValueTask Egress(EgressRequest request, Stream outStream, CancellationToken cts)
|
||||||
{
|
{
|
||||||
var path = MapPath(request.Id);
|
await using var fs = await Open(request, cts);
|
||||||
if (!File.Exists(path)) throw new VoidFileNotFoundException(request.Id);
|
|
||||||
|
|
||||||
await using var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
|
|
||||||
await EgressFromStream(fs, request, outStream, cts);
|
await EgressFromStream(fs, request, outStream, cts);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,6 +97,14 @@ public class LocalDiskFileStore : StreamFileStore, IFileStore
|
|||||||
await _metadataStore.Delete(id);
|
await _metadataStore.Delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ValueTask<Stream> Open(EgressRequest request, CancellationToken cts)
|
||||||
|
{
|
||||||
|
var path = MapPath(request.Id);
|
||||||
|
if (!File.Exists(path)) throw new VoidFileNotFoundException(request.Id);
|
||||||
|
|
||||||
|
return ValueTask.FromResult<Stream>(new FileStream(path, FileMode.Open, FileAccess.Read));
|
||||||
|
}
|
||||||
|
|
||||||
private string MapPath(Guid id) =>
|
private string MapPath(Guid id) =>
|
||||||
Path.Join(_settings.DataDirectory, FilesDir, id.ToString());
|
Path.Join(_settings.DataDirectory, FilesDir, id.ToString());
|
||||||
}
|
}
|
@ -20,7 +20,33 @@ public class S3FileMetadataStore : IFileMetadataStore
|
|||||||
_client = _config.CreateClient();
|
_client = _config.CreateClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<TMeta?> Get<TMeta>(Guid id) where TMeta : VoidFileMeta
|
public ValueTask<TMeta?> Get<TMeta>(Guid id) where TMeta : VoidFileMeta
|
||||||
|
{
|
||||||
|
return GetMeta<TMeta>(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask<VoidFileMeta?> Get(Guid id)
|
||||||
|
{
|
||||||
|
return GetMeta<VoidFileMeta>(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask Set(Guid id, SecretVoidFileMeta meta)
|
||||||
|
{
|
||||||
|
await _client.PutObjectAsync(new()
|
||||||
|
{
|
||||||
|
BucketName = _config.BucketName,
|
||||||
|
Key = ToKey(id),
|
||||||
|
ContentBody = JsonConvert.SerializeObject(meta),
|
||||||
|
ContentType = "application/json"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask Delete(Guid id)
|
||||||
|
{
|
||||||
|
await _client.DeleteObjectAsync(_config.BucketName, ToKey(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask<TMeta?> GetMeta<TMeta>(Guid id) where TMeta : VoidFileMeta
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -48,22 +74,6 @@ public class S3FileMetadataStore : IFileMetadataStore
|
|||||||
|
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask Set(Guid id, SecretVoidFileMeta meta)
|
|
||||||
{
|
|
||||||
await _client.PutObjectAsync(new()
|
|
||||||
{
|
|
||||||
BucketName = _config.BucketName,
|
|
||||||
Key = ToKey(id),
|
|
||||||
ContentBody = JsonConvert.SerializeObject(meta),
|
|
||||||
ContentType = "application/json"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask Delete(Guid id)
|
|
||||||
{
|
|
||||||
await _client.DeleteObjectAsync(_config.BucketName, ToKey(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ToKey(Guid id) => $"{id}-metadata";
|
private static string ToKey(Guid id) => $"{id}-metadata";
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ public class S3FileStore : StreamFileStore, IFileStore
|
|||||||
},
|
},
|
||||||
Headers =
|
Headers =
|
||||||
{
|
{
|
||||||
ContentLength = (long)payload.Meta.Size
|
ContentLength = (long) payload.Meta.Size
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -50,19 +50,8 @@ public class S3FileStore : StreamFileStore, IFileStore
|
|||||||
|
|
||||||
public async ValueTask Egress(EgressRequest request, Stream outStream, CancellationToken cts)
|
public async ValueTask Egress(EgressRequest request, Stream outStream, CancellationToken cts)
|
||||||
{
|
{
|
||||||
var req = new GetObjectRequest()
|
await using var stream = await Open(request, cts);
|
||||||
{
|
await EgressFull(request.Id, stream, outStream, cts);
|
||||||
BucketName = _config.BucketName,
|
|
||||||
Key = request.Id.ToString()
|
|
||||||
};
|
|
||||||
if (request.Ranges.Any())
|
|
||||||
{
|
|
||||||
var r = request.Ranges.First();
|
|
||||||
req.ByteRange = new ByteRange(r.OriginalString);
|
|
||||||
}
|
|
||||||
|
|
||||||
var obj = await _client.GetObjectAsync(req, cts);
|
|
||||||
await EgressFull(request.Id, obj.ResponseStream, outStream, cts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<PagedResult<PublicVoidFile>> ListFiles(PagedRequest request)
|
public async ValueTask<PagedResult<PublicVoidFile>> ListFiles(PagedRequest request)
|
||||||
@ -124,4 +113,21 @@ public class S3FileStore : StreamFileStore, IFileStore
|
|||||||
{
|
{
|
||||||
await _client.DeleteObjectAsync(_config.BucketName, id.ToString());
|
await _client.DeleteObjectAsync(_config.BucketName, id.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Stream> Open(EgressRequest request, CancellationToken cts)
|
||||||
|
{
|
||||||
|
var req = new GetObjectRequest()
|
||||||
|
{
|
||||||
|
BucketName = _config.BucketName,
|
||||||
|
Key = request.Id.ToString()
|
||||||
|
};
|
||||||
|
if (request.Ranges.Any())
|
||||||
|
{
|
||||||
|
var r = request.Ranges.First();
|
||||||
|
req.ByteRange = new ByteRange(r.OriginalString);
|
||||||
|
}
|
||||||
|
|
||||||
|
var obj = await _client.GetObjectAsync(req, cts);
|
||||||
|
return obj.ResponseStream;
|
||||||
|
}
|
||||||
}
|
}
|
@ -3,36 +3,24 @@ using VoidCat.Services.Abstractions;
|
|||||||
|
|
||||||
namespace VoidCat.Services.Paywall;
|
namespace VoidCat.Services.Paywall;
|
||||||
|
|
||||||
public class PaywallStore : IPaywallStore
|
public class PaywallStore : BasicCacheStore<PaywallConfig>, IPaywallStore
|
||||||
{
|
{
|
||||||
private readonly ICache _cache;
|
|
||||||
|
|
||||||
public PaywallStore(ICache database)
|
public PaywallStore(ICache database)
|
||||||
|
: base(database)
|
||||||
{
|
{
|
||||||
_cache = database;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<PaywallConfig?> Get(Guid id)
|
public override async ValueTask<PaywallConfig?> Get(Guid id)
|
||||||
{
|
{
|
||||||
var cfg = await _cache.Get<NoPaywallConfig>(ConfigKey(id));
|
var cfg = await _cache.Get<NoPaywallConfig>(MapKey(id));
|
||||||
return cfg?.Service switch
|
return cfg?.Service switch
|
||||||
{
|
{
|
||||||
PaywallServices.None => cfg,
|
PaywallServices.None => cfg,
|
||||||
PaywallServices.Strike => await _cache.Get<StrikePaywallConfig>(ConfigKey(id)),
|
PaywallServices.Strike => await _cache.Get<StrikePaywallConfig>(MapKey(id)),
|
||||||
_ => default
|
_ => default
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask Set(Guid id, PaywallConfig config)
|
|
||||||
{
|
|
||||||
return _cache.Set(ConfigKey(id), config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueTask Delete(Guid id)
|
|
||||||
{
|
|
||||||
return _cache.Delete(ConfigKey(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<PaywallOrder?> GetOrder(Guid id)
|
public async ValueTask<PaywallOrder?> GetOrder(Guid id)
|
||||||
{
|
{
|
||||||
return await _cache.Get<PaywallOrder>(OrderKey(id));
|
return await _cache.Get<PaywallOrder>(OrderKey(id));
|
||||||
@ -44,6 +32,6 @@ public class PaywallStore : IPaywallStore
|
|||||||
order.Status == PaywallOrderStatus.Paid ? TimeSpan.FromDays(1) : TimeSpan.FromSeconds(5));
|
order.Status == PaywallOrderStatus.Paid ? TimeSpan.FromDays(1) : TimeSpan.FromSeconds(5));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ConfigKey(Guid id) => $"paywall:config:{id}";
|
public override string MapKey(Guid id) => $"paywall:config:{id}";
|
||||||
private string OrderKey(Guid id) => $"paywall:order:{id}";
|
private string OrderKey(Guid id) => $"paywall:order:{id}";
|
||||||
}
|
}
|
@ -24,7 +24,7 @@ public class UserManager : IUserManager
|
|||||||
if (!(user?.CheckPassword(password) ?? false)) throw new InvalidOperationException("User does not exist");
|
if (!(user?.CheckPassword(password) ?? false)) throw new InvalidOperationException("User does not exist");
|
||||||
|
|
||||||
user.LastLogin = DateTimeOffset.UtcNow;
|
user.LastLogin = DateTimeOffset.UtcNow;
|
||||||
await _store.Set(user);
|
await _store.Set(user.Id, user);
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
@ -51,7 +51,7 @@ public class UserManager : IUserManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await _store.Set(newUser);
|
await _store.Set(newUser.Id, newUser);
|
||||||
await _emailVerification.SendNewCode(newUser);
|
await _emailVerification.SendNewCode(newUser);
|
||||||
return newUser;
|
return newUser;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,11 @@ public class UserStore : IUserStore
|
|||||||
return await _cache.Get<Guid>(MapKey(email));
|
return await _cache.Get<Guid>(MapKey(email));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async ValueTask<VoidUser?> Get(Guid id)
|
||||||
|
{
|
||||||
|
return await Get<PublicVoidUser>(id);
|
||||||
|
}
|
||||||
|
|
||||||
public async ValueTask<T?> Get<T>(Guid id) where T : VoidUser
|
public async ValueTask<T?> Get<T>(Guid id) where T : VoidUser
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -34,8 +39,10 @@ public class UserStore : IUserStore
|
|||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask Set(InternalVoidUser user)
|
public async ValueTask Set(Guid id, InternalVoidUser user)
|
||||||
{
|
{
|
||||||
|
if (id != user.Id) throw new InvalidOperationException();
|
||||||
|
|
||||||
await _cache.Set(MapKey(user.Id), user);
|
await _cache.Set(MapKey(user.Id), user);
|
||||||
await _cache.AddToList(UserList, user.Id.ToString());
|
await _cache.AddToList(UserList, user.Id.ToString());
|
||||||
await _cache.Set(MapKey(user.Email), user.Id.ToString());
|
await _cache.Set(MapKey(user.Email), user.Id.ToString());
|
||||||
@ -84,7 +91,14 @@ public class UserStore : IUserStore
|
|||||||
oldUser.Flags = newUser.Flags;
|
oldUser.Flags = newUser.Flags;
|
||||||
oldUser.DisplayName = newUser.DisplayName;
|
oldUser.DisplayName = newUser.DisplayName;
|
||||||
|
|
||||||
await Set(oldUser);
|
await Set(newUser.Id, oldUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask Delete(Guid id)
|
||||||
|
{
|
||||||
|
var user = await Get<InternalVoidUser>(id);
|
||||||
|
if (user == default) throw new InvalidOperationException();
|
||||||
|
await Delete(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask Delete(PrivateVoidUser user)
|
public async ValueTask Delete(PrivateVoidUser user)
|
||||||
|
@ -19,4 +19,10 @@
|
|||||||
.preview .file-stats svg {
|
.preview .file-stats svg {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview .virus-warning {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid red;
|
||||||
}
|
}
|
@ -108,6 +108,23 @@ export function FilePreview() {
|
|||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderVirusWarning() {
|
||||||
|
if(info.virusScan && info.virusScan.isVirus === true) {
|
||||||
|
let scanResult = info.virusScan;
|
||||||
|
return (
|
||||||
|
<div className="virus-warning">
|
||||||
|
<p>
|
||||||
|
This file apears to be a virus, take care when downloading this file.
|
||||||
|
</p>
|
||||||
|
Detected as:
|
||||||
|
<pre>
|
||||||
|
{scanResult.virusNames.join('\n')}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadInfo();
|
loadInfo();
|
||||||
}, []);
|
}, []);
|
||||||
@ -135,6 +152,7 @@ export function FilePreview() {
|
|||||||
{info.metadata?.description ? <meta name="description" content={info.metadata?.description}/> : null}
|
{info.metadata?.description ? <meta name="description" content={info.metadata?.description}/> : null}
|
||||||
{renderOpenGraphTags()}
|
{renderOpenGraphTags()}
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
{renderVirusWarning()}
|
||||||
<div className="flex flex-center">
|
<div className="flex flex-center">
|
||||||
<div className="flx-grow">
|
<div className="flx-grow">
|
||||||
{info.uploader ? <InlineProfile profile={info.uploader}/> : null}
|
{info.uploader ? <InlineProfile profile={info.uploader}/> : null}
|
||||||
|
Loading…
Reference in New Issue
Block a user