diff --git a/VoidCat/Controllers/UploadController.cs b/VoidCat/Controllers/UploadController.cs index 843de36..be8740f 100644 --- a/VoidCat/Controllers/UploadController.cs +++ b/VoidCat/Controllers/UploadController.cs @@ -194,8 +194,7 @@ namespace VoidCat.Controllers var gid = id.FromBase58Guid(); var meta = await _metadata.Get(gid); if (meta == default) return NotFound(); - - if (req.EditSecret != meta.EditSecret) return Unauthorized(); + if (!meta.CanEdit(req.EditSecret, HttpContext)) return Unauthorized(); if (req.Strike != default) { @@ -207,6 +206,28 @@ namespace VoidCat.Controllers await _paywall.Set(gid, new NoPaywallConfig()); return Ok(); } + + /// + /// Update metadata about file + /// + /// Id of file to edit + /// New metadata to update + /// + /// + /// You can only change `Name`, `Description` and `MimeType` + /// + [HttpPost] + [Route("{id}/meta")] + public async Task UpdateFileMeta([FromRoute] string id, [FromBody] SecretVoidFileMeta fileMeta) + { + var gid = id.FromBase58Guid(); + var meta = await _metadata.Get(gid); + if (meta == default) return NotFound(); + if (!meta.CanEdit(fileMeta.EditSecret, HttpContext)) return Unauthorized(); + + await _metadata.Update(gid, fileMeta); + return Ok(); + } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] diff --git a/VoidCat/Model/Extensions.cs b/VoidCat/Model/Extensions.cs index ec71a8f..11f7bbf 100644 --- a/VoidCat/Model/Extensions.cs +++ b/VoidCat/Model/Extensions.cs @@ -61,6 +61,12 @@ 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) + { + return file.EditSecret == editSecret + || file.Uploader == context.GetUserId(); + } + public static string ToHex(this byte[] data) { return BitConverter.ToString(data).Replace("-", string.Empty).ToLower(); diff --git a/VoidCat/Model/VoidFileMeta.cs b/VoidCat/Model/VoidFileMeta.cs index aaece05..5fbb471 100644 --- a/VoidCat/Model/VoidFileMeta.cs +++ b/VoidCat/Model/VoidFileMeta.cs @@ -26,7 +26,7 @@ public record VoidFileMeta : IVoidFileMeta /// /// Filename /// - public string? Name { get; init; } + public string? Name { get; set; } /// /// Size of the file in storage @@ -41,12 +41,12 @@ public record VoidFileMeta : IVoidFileMeta /// /// Description about the file /// - public string? Description { get; init; } + public string? Description { get; set; } /// /// The content type of the file /// - public string? MimeType { get; init; } + public string? MimeType { get; set; } /// /// SHA-256 hash of the file diff --git a/VoidCat/Services/Abstractions/IFileMetadataStore.cs b/VoidCat/Services/Abstractions/IFileMetadataStore.cs index 1ff27ef..a35691f 100644 --- a/VoidCat/Services/Abstractions/IFileMetadataStore.cs +++ b/VoidCat/Services/Abstractions/IFileMetadataStore.cs @@ -5,4 +5,5 @@ namespace VoidCat.Services.Abstractions; public interface IFileMetadataStore : IPublicPrivateStore { ValueTask Get(Guid id) where TMeta : VoidFileMeta; + ValueTask Update(Guid id, TMeta meta) where TMeta : VoidFileMeta; } diff --git a/VoidCat/Services/Files/LocalDiskFileMetadataStore.cs b/VoidCat/Services/Files/LocalDiskFileMetadataStore.cs index 786693a..c96e09d 100644 --- a/VoidCat/Services/Files/LocalDiskFileMetadataStore.cs +++ b/VoidCat/Services/Files/LocalDiskFileMetadataStore.cs @@ -1,6 +1,5 @@ using Newtonsoft.Json; using VoidCat.Model; -using VoidCat.Model.Exceptions; using VoidCat.Services.Abstractions; namespace VoidCat.Services.Files; @@ -28,6 +27,18 @@ public class LocalDiskFileMetadataStore : IFileMetadataStore return GetMeta(id); } + public async ValueTask Update(Guid id, TMeta meta) where TMeta : VoidFileMeta + { + var oldMeta = await GetMeta(id); + if (oldMeta == default) return; + + oldMeta.Description = meta.Description ?? oldMeta.Description; + oldMeta.Name = meta.Name ?? oldMeta.Name; + oldMeta.MimeType = meta.MimeType ?? oldMeta.MimeType; + + await Set(id, oldMeta); + } + public ValueTask Get(Guid id) { return GetMeta(id); diff --git a/VoidCat/Services/Files/S3FileMetadataStore.cs b/VoidCat/Services/Files/S3FileMetadataStore.cs index c340a8e..b2adda1 100644 --- a/VoidCat/Services/Files/S3FileMetadataStore.cs +++ b/VoidCat/Services/Files/S3FileMetadataStore.cs @@ -25,6 +25,18 @@ public class S3FileMetadataStore : IFileMetadataStore return GetMeta(id); } + public async ValueTask Update(Guid id, TMeta meta) where TMeta : VoidFileMeta + { + var oldMeta = await GetMeta(id); + if (oldMeta == default) return; + + oldMeta.Description = meta.Description ?? oldMeta.Description; + oldMeta.Name = meta.Name ?? oldMeta.Name; + oldMeta.MimeType = meta.MimeType ?? oldMeta.MimeType; + + await Set(id, oldMeta); + } + public ValueTask Get(Guid id) { return GetMeta(id); diff --git a/VoidCat/spa/src/Api.js b/VoidCat/spa/src/Api.js index 0e1c203..d24217a 100644 --- a/VoidCat/spa/src/Api.js +++ b/VoidCat/spa/src/Api.js @@ -41,7 +41,8 @@ export function useApi() { updateUser: (u) => getJson("POST", `/user/${u.id}`, u, auth), listUserFiles: (uid, pageReq) => getJson("POST", `/user/${uid}/files`, pageReq, auth), submitVerifyCode: (uid, code) => getJson("POST", `/user/${uid}/verify`, code, auth), - sendNewCode: (uid) => getJson("GET", `/user/${uid}/verify`, undefined, auth) + sendNewCode: (uid) => getJson("GET", `/user/${uid}/verify`, undefined, auth), + updateMetadata: (id, meta) => getJson("POST", `/upload/${id}/meta`, meta, auth) } }; } \ No newline at end of file diff --git a/VoidCat/spa/src/FileEdit.js b/VoidCat/spa/src/FileEdit.js index c3ee76b..34683e8 100644 --- a/VoidCat/spa/src/FileEdit.js +++ b/VoidCat/spa/src/FileEdit.js @@ -5,6 +5,7 @@ import {NoPaywallConfig} from "./NoPaywallConfig"; import {useApi} from "./Api"; import "./FileEdit.css"; import {useSelector} from "react-redux"; +import {VoidButton} from "./VoidButton"; export function FileEdit(props) { const {Api} = useApi(); @@ -25,6 +26,15 @@ export function FileEdit(props) { return req.ok; } + async function saveMeta() { + let meta = { + name, + description, + editSecret: privateFile?.metadata?.editSecret + }; + await Api.updateMetadata(file.id, meta); + } + function renderPaywallConfig() { switch (paywall) { case 0: { @@ -47,7 +57,7 @@ export function FileEdit(props) {
Description:
setDescription(e.target.value)}/>
- + saveMeta()} options={{showSuccess: true}}>Save

Paywall Config

diff --git a/VoidCat/spa/src/Profile.js b/VoidCat/spa/src/Profile.js index 9d4da94..04c47a0 100644 --- a/VoidCat/spa/src/Profile.js +++ b/VoidCat/spa/src/Profile.js @@ -8,14 +8,12 @@ import {logout, setProfile as setGlobalProfile} from "./LoginState"; import {DigestAlgo} from "./FileUpload"; import {buf2hex, hasFlag} from "./Util"; import moment from "moment"; -import FeatherIcon from "feather-icons-react"; import {FileList} from "./FileList"; import {VoidButton} from "./VoidButton"; export function Profile() { const [profile, setProfile] = useState(); const [noProfile, setNoProfile] = useState(false); - const [saved, setSaved] = useState(false); const [emailCode, setEmailCode] = useState(""); const [emailCodeError, setEmailCodeError] = useState(""); const [newCodeSent, setNewCodeSent] = useState(false); @@ -103,7 +101,6 @@ export function Profile() { if (r.ok) { // saved dispatch(setGlobalProfile(profile)); - setSaved(true); } } @@ -160,10 +157,7 @@ export function Profile() {
- Save -
-
- {saved ? : null} + Save
dispatch(logout())}>Logout @@ -177,12 +171,6 @@ export function Profile() { loadProfile(); }, []); - useEffect(() => { - if (saved === true) { - setTimeout(() => setSaved(false), 1000); - } - }, [saved]); - if (profile) { let avatarUrl = profile.avatar ?? DefaultAvatar; if (!avatarUrl.startsWith("http")) { diff --git a/VoidCat/spa/src/VoidButton.js b/VoidCat/spa/src/VoidButton.js index 3aa3777..54a2803 100644 --- a/VoidCat/spa/src/VoidButton.js +++ b/VoidCat/spa/src/VoidButton.js @@ -1,4 +1,13 @@ +import {useEffect, useState} from "react"; +import FeatherIcon from "feather-icons-react"; + export function VoidButton(props) { + const options = { + showSuccess: false, + ...props.options + }; + const [success, setSuccess] = useState(false); + async function handleClick(e) { if (e.target.classList.contains("disabled")) return; e.target.classList.add("disabled"); @@ -9,10 +18,24 @@ export function VoidButton(props) { if (typeof ret === "object" && typeof ret.then === "function") { await ret; } + setSuccess(options.showSuccess); } e.target.classList.remove("disabled"); } - return
{props.children}
; + useEffect(() => { + if (success === true) { + setTimeout(() => setSuccess(false), 1000); + } + }, [success]); + + return ( +
+
+
{props.children}
+
+ {success ?
: null} +
+ ); } \ No newline at end of file diff --git a/VoidCat/spa/src/index.css b/VoidCat/spa/src/index.css index 6575e98..22ef33b 100644 --- a/VoidCat/spa/src/index.css +++ b/VoidCat/spa/src/index.css @@ -40,6 +40,10 @@ a:hover { display: flex; } +.flex-inline { + display: inline-flex; +} + .flx-1 { flex: 1; }