diff --git a/VoidCat/Controllers/Admin/AdminController.cs b/VoidCat/Controllers/Admin/AdminController.cs index ef22b81..45258d7 100644 --- a/VoidCat/Controllers/Admin/AdminController.cs +++ b/VoidCat/Controllers/Admin/AdminController.cs @@ -34,7 +34,7 @@ public class AdminController : Controller [HttpPost] [Route("user")] - public async Task> ListUsers([FromBody] PagedRequest request) + public async Task> ListUsers([FromBody] PagedRequest request) { var result = await _userStore.ListUsers(request); return await result.GetResults(); diff --git a/VoidCat/Controllers/DownloadController.cs b/VoidCat/Controllers/DownloadController.cs index 90b9ee8..ecc8799 100644 --- a/VoidCat/Controllers/DownloadController.cs +++ b/VoidCat/Controllers/DownloadController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using VoidCat.Model; using VoidCat.Model.Paywall; using VoidCat.Services.Abstractions; +using VoidCat.Services.Files; namespace VoidCat.Controllers; @@ -10,13 +11,15 @@ namespace VoidCat.Controllers; public class DownloadController : Controller { private readonly IFileStore _storage; + private readonly IFileInfoManager _fileInfo; private readonly IPaywallStore _paywall; private readonly ILogger _logger; - public DownloadController(IFileStore storage, ILogger logger, IPaywallStore paywall) + public DownloadController(IFileStore storage, ILogger logger, IFileInfoManager fileInfo, IPaywallStore paywall) { _storage = storage; _logger = logger; + _fileInfo = fileInfo; _paywall = paywall; } @@ -37,7 +40,7 @@ public class DownloadController : Controller var voidFile = await SetupDownload(gid); if (voidFile == default) return; - var egressReq = new EgressRequest(gid, GetRanges(Request, (long) voidFile!.Metadata!.Size)); + var egressReq = new EgressRequest(gid, GetRanges(Request, (long)voidFile!.Metadata!.Size)); if (egressReq.Ranges.Count() > 1) { _logger.LogWarning("Multi-range request not supported!"); @@ -49,10 +52,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; } } @@ -75,7 +78,7 @@ public class DownloadController : Controller private async Task SetupDownload(Guid id) { - var meta = await _storage.Get(id); + var meta = await _fileInfo.Get(id); if (meta == null) { Response.StatusCode = 404; @@ -88,7 +91,7 @@ public class DownloadController : Controller var orderId = Request.Headers.GetHeader("V-OrderId") ?? Request.Query["orderId"]; if (!await IsOrderPaid(orderId)) { - Response.StatusCode = (int) HttpStatusCode.PaymentRequired; + Response.StatusCode = (int)HttpStatusCode.PaymentRequired; return default; } } @@ -139,4 +142,4 @@ public class DownloadController : Controller } } } -} \ No newline at end of file +} diff --git a/VoidCat/Controllers/UploadController.cs b/VoidCat/Controllers/UploadController.cs index 791cf2b..6df40f8 100644 --- a/VoidCat/Controllers/UploadController.cs +++ b/VoidCat/Controllers/UploadController.cs @@ -15,14 +15,16 @@ namespace VoidCat.Controllers private readonly IFileMetadataStore _metadata; private readonly IPaywallStore _paywall; private readonly IPaywallFactory _paywallFactory; + private readonly IFileInfoManager _fileInfo; public UploadController(IFileStore storage, IFileMetadataStore metadata, IPaywallStore paywall, - IPaywallFactory paywallFactory) + IPaywallFactory paywallFactory, IFileInfoManager fileInfo) { _storage = storage; _metadata = metadata; _paywall = paywall; _paywallFactory = paywallFactory; + _fileInfo = fileInfo; } [HttpPost] @@ -33,7 +35,7 @@ namespace VoidCat.Controllers try { var uid = HttpContext.GetUserId(); - var meta = new VoidFileMeta() + var meta = new SecretVoidFileMeta() { MimeType = Request.Headers.GetHeader("V-Content-Type"), Name = Request.Headers.GetHeader("V-Filename"), @@ -62,12 +64,12 @@ namespace VoidCat.Controllers try { var gid = id.FromBase58Guid(); - var fileInfo = await _storage.Get(gid); - if (fileInfo == default) return UploadResult.Error("File not found"); + var meta = await _metadata.Get(gid); + if (meta == default) return UploadResult.Error("File not found"); var editSecret = Request.Headers.GetHeader("V-EditSecret"); var digest = Request.Headers.GetHeader("V-Digest"); - var vf = await _storage.Ingress(new(Request.Body, fileInfo.Metadata, digest!) + var vf = await _storage.Ingress(new(Request.Body, meta, digest!) { EditSecret = editSecret?.FromBase58Guid() ?? Guid.Empty, Id = gid @@ -85,7 +87,7 @@ namespace VoidCat.Controllers [Route("{id}")] public ValueTask GetInfo([FromRoute] string id) { - return _storage.Get(id.FromBase58Guid()); + return _fileInfo.Get(id.FromBase58Guid()); } [HttpGet] @@ -93,16 +95,16 @@ namespace VoidCat.Controllers public async ValueTask CreateOrder([FromRoute] string id) { var gid = id.FromBase58Guid(); - var file = await _storage.Get(gid); + var file = await _fileInfo.Get(gid); var config = await _paywall.GetConfig(gid); var provider = await _paywallFactory.CreateProvider(config!.Service); return await provider.CreateOrder(file!); } - + [HttpGet] [Route("{id}/paywall/{order:guid}")] - public async ValueTask GetOrderStatus([FromRoute] string id, [FromRoute]Guid order) + public async ValueTask GetOrderStatus([FromRoute] string id, [FromRoute] Guid order) { var gid = id.FromBase58Guid(); var config = await _paywall.GetConfig(gid); @@ -116,7 +118,7 @@ namespace VoidCat.Controllers public async Task SetPaywallConfig([FromRoute] string id, [FromBody] SetPaywallConfigRequest req) { var gid = id.FromBase58Guid(); - var meta = await _metadata.Get(gid); + var meta = await _metadata.Get(gid); if (meta == default) return NotFound(); if (req.EditSecret != meta.EditSecret) return Unauthorized(); @@ -165,4 +167,4 @@ namespace VoidCat.Controllers public StrikePaywallConfig? Strike { get; init; } } -} \ No newline at end of file +} diff --git a/VoidCat/Controllers/UserController.cs b/VoidCat/Controllers/UserController.cs new file mode 100644 index 0000000..e446797 --- /dev/null +++ b/VoidCat/Controllers/UserController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; +using VoidCat.Model; +using VoidCat.Services.Abstractions; + +namespace VoidCat.Controllers; + +[Route("user")] +public class UserController : Controller +{ + private readonly IUserStore _store; + + public UserController(IUserStore store) + { + _store = store; + } + + [HttpGet] + [Route("{id}")] + public async Task GetUser([FromRoute] string id) + { + var loggedUser = HttpContext.GetUserId(); + var requestedId = id.FromBase58Guid(); + if (loggedUser == requestedId) + { + return await _store.Get(id.FromBase58Guid()); + } + else + { + return await _store.Get(id.FromBase58Guid()); + } + } +} diff --git a/VoidCat/Model/Extensions.cs b/VoidCat/Model/Extensions.cs index a0dc59e..fc80c92 100644 --- a/VoidCat/Model/Extensions.cs +++ b/VoidCat/Model/Extensions.cs @@ -155,7 +155,7 @@ public static class Extensions throw new ArgumentException("Unknown algo", nameof(algo)); } - public static bool CheckPassword(this PrivateVoidUser vu, string password) + public static bool CheckPassword(this InternalVoidUser vu, string password) { var hashParts = vu.PasswordHash.Split(":"); return vu.PasswordHash == password.HashPassword(hashParts[0], hashParts.Length == 3 ? hashParts[1] : null); diff --git a/VoidCat/Model/IngressPayload.cs b/VoidCat/Model/IngressPayload.cs index 081254d..de8c703 100644 --- a/VoidCat/Model/IngressPayload.cs +++ b/VoidCat/Model/IngressPayload.cs @@ -1,6 +1,6 @@ namespace VoidCat.Model; -public sealed record IngressPayload(Stream InStream, VoidFileMeta Meta, string Hash) +public sealed record IngressPayload(Stream InStream, SecretVoidFileMeta Meta, string Hash) { public Guid? Id { get; init; } public Guid? EditSecret { get; init; } diff --git a/VoidCat/Model/VoidFile.cs b/VoidCat/Model/VoidFile.cs index e338fb6..4cecef0 100644 --- a/VoidCat/Model/VoidFile.cs +++ b/VoidCat/Model/VoidFile.cs @@ -20,15 +20,23 @@ namespace VoidCat.Model /// Optional paywall config /// public PaywallConfig? Paywall { get; init; } + + /// + /// User profile that uploaded the file + /// + public PublicVoidUser? Uploader { get; init; } + + /// + /// Traffic stats for this file + /// + public Bandwidth? Bandwidth { get; init; } } public sealed record PublicVoidFile : VoidFile { - public Bandwidth? Bandwidth { get; init; } } public sealed record PrivateVoidFile : VoidFile { - public Bandwidth? Bandwidth { get; init; } } } \ No newline at end of file diff --git a/VoidCat/Model/VoidUser.cs b/VoidCat/Model/VoidUser.cs index 47cde86..8882c23 100644 --- a/VoidCat/Model/VoidUser.cs +++ b/VoidCat/Model/VoidUser.cs @@ -1,32 +1,41 @@ using Newtonsoft.Json; -using VoidCat.Model; namespace VoidCat.Model; public abstract class VoidUser { - protected VoidUser(Guid id, string email) + protected VoidUser(Guid id) { Id = id; - Email = email; } [JsonConverter(typeof(Base58GuidConverter))] public Guid Id { get; } - public string Email { get; } - - public HashSet Roles { get; init; } = new() { Model.Roles.User }; + public HashSet Roles { get; init; } = new() {Model.Roles.User}; public DateTimeOffset Created { get; init; } public DateTimeOffset LastLogin { get; set; } - + + /// + /// Display avatar for user profile + /// public string? Avatar { get; set; } + + /// + /// Display name for user profile + /// + public string? DisplayName { get; set; } = "void user"; + + /// + /// Public profile, otherwise the profile will be hidden + /// + public bool Public { get; set; } = true; public PublicVoidUser ToPublic() { - return new(Id, Email) + return new(Id) { Roles = Roles, Created = Created, @@ -36,9 +45,9 @@ public abstract class VoidUser } } -public sealed class PrivateVoidUser : VoidUser +public sealed class InternalVoidUser : PrivateVoidUser { - public PrivateVoidUser(Guid id, string email, string passwordHash) : base(id, email) + public InternalVoidUser(Guid id, string email, string passwordHash) : base(id, email) { PasswordHash = passwordHash; } @@ -46,9 +55,19 @@ public sealed class PrivateVoidUser : VoidUser public string PasswordHash { get; } } +public class PrivateVoidUser : VoidUser +{ + public PrivateVoidUser(Guid id, string email) : base(id) + { + Email = email; + } + + public string Email { get; } +} + public sealed class PublicVoidUser : VoidUser { - public PublicVoidUser(Guid id, string email) : base(id, email) + public PublicVoidUser(Guid id) : base(id) { } -} \ No newline at end of file +} diff --git a/VoidCat/Program.cs b/VoidCat/Program.cs index 1c08bd1..a96987e 100644 --- a/VoidCat/Program.cs +++ b/VoidCat/Program.cs @@ -14,6 +14,17 @@ using VoidCat.Services.Redis; using VoidCat.Services.Stats; using VoidCat.Services.Users; +// setup JsonConvert default settings +JsonSerializerSettings ConfigJsonSettings(JsonSerializerSettings s) +{ + s.NullValueHandling = NullValueHandling.Ignore; + s.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor; + s.MissingMemberHandling = MissingMemberHandling.Ignore; + return s; +} + +JsonConvert.DefaultSettings = () => ConfigJsonSettings(new()); + var builder = WebApplication.CreateBuilder(args); var services = builder.Services; @@ -44,12 +55,7 @@ services.AddCors(opt => }); services.AddRouting(); -services.AddControllers().AddNewtonsoftJson((opt) => -{ - opt.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; - opt.SerializerSettings.ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor; - opt.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Ignore; -}); +services.AddControllers().AddNewtonsoftJson((opt) => { ConfigJsonSettings(opt.SerializerSettings); }); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => @@ -65,13 +71,7 @@ services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) }; }); -services.AddAuthorization((opt) => -{ - opt.AddPolicy(Policies.RequireAdmin, (auth) => - { - auth.RequireRole(Roles.Admin); - }); -}); +services.AddAuthorization((opt) => { opt.AddPolicy(Policies.RequireAdmin, (auth) => { auth.RequireRole(Roles.Admin); }); }); // void.cat services // @@ -80,6 +80,7 @@ services.AddVoidMigrations(); // file storage services.AddTransient(); services.AddTransient(); +services.AddTransient(); // stats services.AddTransient(); diff --git a/VoidCat/Services/Abstractions/IFileInfoManager.cs b/VoidCat/Services/Abstractions/IFileInfoManager.cs new file mode 100644 index 0000000..8138e75 --- /dev/null +++ b/VoidCat/Services/Abstractions/IFileInfoManager.cs @@ -0,0 +1,8 @@ +using VoidCat.Model; + +namespace VoidCat.Services.Abstractions; + +public interface IFileInfoManager +{ + ValueTask Get(Guid id); +} diff --git a/VoidCat/Services/Abstractions/IFileMetadataStore.cs b/VoidCat/Services/Abstractions/IFileMetadataStore.cs index decba1d..d51fb85 100644 --- a/VoidCat/Services/Abstractions/IFileMetadataStore.cs +++ b/VoidCat/Services/Abstractions/IFileMetadataStore.cs @@ -4,8 +4,7 @@ namespace VoidCat.Services.Abstractions; public interface IFileMetadataStore { - ValueTask Get(Guid id); - ValueTask GetPublic(Guid id); + ValueTask Get(Guid id) where TMeta : VoidFileMeta; ValueTask Set(Guid id, SecretVoidFileMeta meta); diff --git a/VoidCat/Services/Abstractions/IFileStore.cs b/VoidCat/Services/Abstractions/IFileStore.cs index 0bdcf5b..14df267 100644 --- a/VoidCat/Services/Abstractions/IFileStore.cs +++ b/VoidCat/Services/Abstractions/IFileStore.cs @@ -4,8 +4,6 @@ namespace VoidCat.Services.Abstractions; public interface IFileStore { - ValueTask Get(Guid id); - ValueTask Ingress(IngressPayload payload, CancellationToken cts); ValueTask Egress(EgressRequest request, Stream outStream, CancellationToken cts); diff --git a/VoidCat/Services/Abstractions/IUserStore.cs b/VoidCat/Services/Abstractions/IUserStore.cs index e59fef6..c112ac4 100644 --- a/VoidCat/Services/Abstractions/IUserStore.cs +++ b/VoidCat/Services/Abstractions/IUserStore.cs @@ -6,6 +6,6 @@ public interface IUserStore { ValueTask LookupUser(string email); ValueTask Get(Guid id) where T : VoidUser; - ValueTask Set(PrivateVoidUser user); - ValueTask> ListUsers(PagedRequest request); + ValueTask Set(InternalVoidUser user); + ValueTask> ListUsers(PagedRequest request); } \ No newline at end of file diff --git a/VoidCat/Services/Files/FileInfoManager.cs b/VoidCat/Services/Files/FileInfoManager.cs new file mode 100644 index 0000000..fb1262d --- /dev/null +++ b/VoidCat/Services/Files/FileInfoManager.cs @@ -0,0 +1,43 @@ +using VoidCat.Model; +using VoidCat.Services.Abstractions; + +namespace VoidCat.Services.Files; + +public class FileInfoManager : IFileInfoManager +{ + private readonly IFileMetadataStore _metadataStore; + private readonly IPaywallStore _paywallStore; + private readonly IStatsReporter _statsReporter; + private readonly IUserStore _userStore; + + public FileInfoManager(IFileMetadataStore metadataStore, IPaywallStore paywallStore, IStatsReporter statsReporter, + IUserStore userStore) + { + _metadataStore = metadataStore; + _paywallStore = paywallStore; + _statsReporter = statsReporter; + _userStore = userStore; + } + + public async ValueTask Get(Guid id) + { + var meta = _metadataStore.Get(id); + var paywall = _paywallStore.GetConfig(id); + var bandwidth = _statsReporter.GetBandwidth(id); + await Task.WhenAll(meta.AsTask(), paywall.AsTask(), bandwidth.AsTask()); + + 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?.Public == true ? user : null + }; + } +} diff --git a/VoidCat/Services/Files/LocalDiskFileMetadataStore.cs b/VoidCat/Services/Files/LocalDiskFileMetadataStore.cs index 67a5a98..8f0a25c 100644 --- a/VoidCat/Services/Files/LocalDiskFileMetadataStore.cs +++ b/VoidCat/Services/Files/LocalDiskFileMetadataStore.cs @@ -23,14 +23,9 @@ public class LocalDiskFileMetadataStore : IFileMetadataStore } } - public ValueTask GetPublic(Guid id) + public ValueTask Get(Guid id) where TMeta : VoidFileMeta { - return GetMeta(id); - } - - public ValueTask Get(Guid id) - { - return GetMeta(id); + return GetMeta(id); } public async ValueTask Set(Guid id, SecretVoidFileMeta meta) @@ -42,7 +37,7 @@ public class LocalDiskFileMetadataStore : IFileMetadataStore public async ValueTask Update(Guid id, SecretVoidFileMeta patch) { - var oldMeta = await Get(id); + var oldMeta = await Get(id); if (oldMeta?.EditSecret != patch.EditSecret) { throw new VoidNotAllowedException("Edit secret incorrect"); @@ -74,4 +69,4 @@ public class LocalDiskFileMetadataStore : IFileMetadataStore private string MapMeta(Guid id) => Path.ChangeExtension(Path.Join(_settings.DataDirectory, MetadataDir, id.ToString()), ".json"); -} \ No newline at end of file +} diff --git a/VoidCat/Services/Files/LocalDiskFileStorage.cs b/VoidCat/Services/Files/LocalDiskFileStorage.cs index 1b4a36c..8e2df5d 100644 --- a/VoidCat/Services/Files/LocalDiskFileStorage.cs +++ b/VoidCat/Services/Files/LocalDiskFileStorage.cs @@ -32,22 +32,6 @@ public class LocalDiskFileStore : IFileStore } } - public async ValueTask Get(Guid id) - { - var meta = _metadataStore.GetPublic(id); - var paywall = _paywallStore.GetConfig(id); - var bandwidth = _statsReporter.GetBandwidth(id); - await Task.WhenAll(meta.AsTask(), paywall.AsTask(), bandwidth.AsTask()); - - return new() - { - Id = id, - Metadata = meta.Result, - Paywall = paywall.Result, - Bandwidth = bandwidth.Result - }; - } - public async ValueTask Egress(EgressRequest request, Stream outStream, CancellationToken cts) { var path = MapPath(request.Id); @@ -68,10 +52,9 @@ public class LocalDiskFileStore : IFileStore { var id = payload.Id ?? Guid.NewGuid(); var fPath = MapPath(id); - SecretVoidFileMeta? vf = null; + var vf = payload.Meta; if (payload.IsAppend) { - vf = await _metadataStore.Get(payload.Id!.Value); if (vf?.EditSecret != null && vf.EditSecret != payload.EditSecret) { throw new VoidNotAllowedException("Edit secret incorrect!"); @@ -98,13 +81,8 @@ public class LocalDiskFileStore : IFileStore } else { - vf = new SecretVoidFileMeta() + vf = vf! with { - Name = payload.Meta.Name, - Description = payload.Meta.Description, - Digest = payload.Meta.Digest, - MimeType = payload.Meta.MimeType, - Uploader = payload.Meta.Uploader, Uploaded = DateTimeOffset.UtcNow, EditSecret = Guid.NewGuid(), Size = total @@ -146,10 +124,14 @@ public class LocalDiskFileStore : IFileStore { if (!Guid.TryParse(Path.GetFileNameWithoutExtension(file), out var gid)) continue; - var loaded = await Get(gid); + var loaded = await _metadataStore.Get(gid); if (loaded != default) { - yield return loaded; + yield return new() + { + Id = gid, + Metadata = loaded + }; } } } diff --git a/VoidCat/Services/Users/UserManager.cs b/VoidCat/Services/Users/UserManager.cs index 31930eb..b52e97c 100644 --- a/VoidCat/Services/Users/UserManager.cs +++ b/VoidCat/Services/Users/UserManager.cs @@ -18,7 +18,7 @@ public class UserManager : IUserManager var userId = await _store.LookupUser(email); if (!userId.HasValue) throw new InvalidOperationException("User does not exist"); - var user = await _store.Get(userId.Value); + var user = await _store.Get(userId.Value); if (!(user?.CheckPassword(password) ?? false)) throw new InvalidOperationException("User does not exist"); user.LastLogin = DateTimeOffset.UtcNow; @@ -32,7 +32,7 @@ public class UserManager : IUserManager var existingUser = await _store.LookupUser(email); if (existingUser != Guid.Empty) throw new InvalidOperationException("User already exists"); - var newUser = new PrivateVoidUser(Guid.NewGuid(), email, password.HashPassword()) + var newUser = new InternalVoidUser(Guid.NewGuid(), email, password.HashPassword()) { Created = DateTimeOffset.UtcNow, LastLogin = DateTimeOffset.UtcNow diff --git a/VoidCat/Services/Users/UserStore.cs b/VoidCat/Services/Users/UserStore.cs index 9fa32a0..c916626 100644 --- a/VoidCat/Services/Users/UserStore.cs +++ b/VoidCat/Services/Users/UserStore.cs @@ -23,14 +23,14 @@ public class UserStore : IUserStore return await _cache.Get(MapKey(id)); } - public async ValueTask Set(PrivateVoidUser user) + public async ValueTask Set(InternalVoidUser user) { await _cache.Set(MapKey(user.Id), user); await _cache.AddToList(UserList, user.Id.ToString()); await _cache.Set(MapKey(user.Email), user.Id.ToString()); } - public async ValueTask> ListUsers(PagedRequest request) + public async ValueTask> ListUsers(PagedRequest request) { var users = (await _cache.GetList(UserList))?.Select(Guid.Parse); users = (request.SortBy, request.SortOrder) switch @@ -40,9 +40,9 @@ public class UserStore : IUserStore _ => users }; - async IAsyncEnumerable EnumerateUsers(IEnumerable ids) + async IAsyncEnumerable EnumerateUsers(IEnumerable ids) { - var usersLoaded = await Task.WhenAll(ids.Select(async a => await Get(a))); + var usersLoaded = await Task.WhenAll(ids.Select(async a => await Get(a))); foreach (var user in usersLoaded) { if (user != default) diff --git a/VoidCat/spa/src/Api.js b/VoidCat/spa/src/Api.js index f9d6f3e..5665cff 100644 --- a/VoidCat/spa/src/Api.js +++ b/VoidCat/spa/src/Api.js @@ -36,7 +36,8 @@ export function useApi() { createOrder: (id) => getJson("GET", `/upload/${id}/paywall`), getOrder: (file, order) => getJson("GET", `/upload/${file}/paywall/${order}`), login: (username, password) => getJson("POST", `/auth/login`, {username, password}), - register: (username, password) => getJson("POST", `/auth/register`, {username, password}) + register: (username, password) => getJson("POST", `/auth/register`, {username, password}), + getUser: (id) => getJson("GET", `/user/${id}`, undefined, auth) } }; } \ No newline at end of file diff --git a/VoidCat/spa/src/Const.js b/VoidCat/spa/src/Const.js index 3004055..4692ab9 100644 --- a/VoidCat/spa/src/Const.js +++ b/VoidCat/spa/src/Const.js @@ -2,6 +2,8 @@ import preval from "preval.macro"; export const ApiHost = preval`module.exports = process.env.API_HOST || '';`; +export const DefaultAvatar = "https://i.imgur.com/8A5Fu65.jpeg"; + /** * @constant {number} - Size of 1 kiB */ diff --git a/VoidCat/spa/src/FilePreview.css b/VoidCat/spa/src/FilePreview.css index 7e50fa4..2d2dd9a 100644 --- a/VoidCat/spa/src/FilePreview.css +++ b/VoidCat/spa/src/FilePreview.css @@ -1,9 +1,9 @@ .preview { - text-align: center; margin-top: 2vh; width: 720px; margin-left: auto; margin-right: auto; + text-align: center; } .preview > a { diff --git a/VoidCat/spa/src/FilePreview.js b/VoidCat/spa/src/FilePreview.js index bff5e0d..b370318 100644 --- a/VoidCat/spa/src/FilePreview.js +++ b/VoidCat/spa/src/FilePreview.js @@ -9,6 +9,7 @@ import {useApi} from "./Api"; import {Helmet} from "react-helmet"; import {FormatBytes} from "./Util"; import {ApiHost} from "./Const"; +import {FileUploader} from "./FileUploader"; export function FilePreview() { const {Api} = useApi(); @@ -121,6 +122,7 @@ export function FilePreview() { {FormatBytes(info?.metadata?.size ?? 0, 2)} + {info.uploader ? : null} ) : "Not Found"} diff --git a/VoidCat/spa/src/FileUploader.css b/VoidCat/spa/src/FileUploader.css new file mode 100644 index 0000000..7145572 --- /dev/null +++ b/VoidCat/spa/src/FileUploader.css @@ -0,0 +1,22 @@ +.uploader-info { + margin-top: 10px; + text-align: start; +} + +.uploader-info .small-profile { + display: inline-flex; + align-items: center; +} + +.uploader-info .small-profile .avatar { + width: 64px; + height: 64px; + border-radius: 16px; + background-size: cover; + background-position: center; + background-repeat: no-repeat; +} + +.uploader-info .small-profile .name { + padding-left: 15px; +} \ No newline at end of file diff --git a/VoidCat/spa/src/FileUploader.js b/VoidCat/spa/src/FileUploader.js new file mode 100644 index 0000000..f8c3a96 --- /dev/null +++ b/VoidCat/spa/src/FileUploader.js @@ -0,0 +1,22 @@ +import {DefaultAvatar} from "./Const"; +import {Link} from "react-router-dom"; +import "./FileUploader.css"; + +export function FileUploader(props) { + const uploader = props.uploader; + + let avatarStyles = { + backgroundImage: `url(${uploader.avatar ?? DefaultAvatar})` + }; + + return ( +
+ +
+
+
{uploader.displayName}
+
+ +
+ ) +} \ No newline at end of file diff --git a/VoidCat/spa/src/Profile.css b/VoidCat/spa/src/Profile.css new file mode 100644 index 0000000..2692841 --- /dev/null +++ b/VoidCat/spa/src/Profile.css @@ -0,0 +1,12 @@ +.profile { + +} + +.profile .avatar { + width: 256px; + height: 256px; + border-radius: 40px; + background-position: center; + background-repeat: no-repeat; + background-size: cover; +} \ No newline at end of file diff --git a/VoidCat/spa/src/Profile.js b/VoidCat/spa/src/Profile.js index 4924772..5b90eb0 100644 --- a/VoidCat/spa/src/Profile.js +++ b/VoidCat/spa/src/Profile.js @@ -1,5 +1,47 @@ +import {useEffect, useState} from "react"; +import {useParams} from "react-router-dom"; +import {useApi} from "./Api"; +import {DefaultAvatar} from "./Const"; + +import "./Profile.css"; + export function Profile() { - return ( -

Coming soon..

- ); + const [profile, setProfile] = useState(); + const {Api} = useApi(); + const params = useParams(); + + async function loadProfile() { + let p = await Api.getUser(params.id); + if (p.ok) { + setProfile(await p.json()); + } + } + + useEffect(() => { + loadProfile(); + }, []); + + if (profile) { + let avatarStyles = { + backgroundImage: `url(${profile.avatar ?? DefaultAvatar})` + }; + return ( +
+
+

{profile.displayName}

+
+
+

Roles:

+ {profile.roles.map(a => {a})} +
+
+
+ ); + } else { + return ( +
+

Loading..

+
+ ); + } } \ No newline at end of file