diff --git a/api/Controllers/PasteController.cs b/api/Controllers/PasteController.cs index 6aa8d7c7..052716f5 100644 --- a/api/Controllers/PasteController.cs +++ b/api/Controllers/PasteController.cs @@ -57,10 +57,16 @@ public async Task TogglePinPaste(string pasteId) await _pasteService.TogglePinnedAsync(pasteId); } + [HttpPost("{pasteId}/private")] + public async Task TogglePrivatePaste(string pasteId) + { + await _pasteService.TogglePrivateAsync(pasteId); + } + [HttpPost] public async Task CreatePaste([FromBody] PasteCreateInfo createInfo) { var paste = await _pasteService.CreateAsync(createInfo); return Created("/api/v3/pastes/" + paste.Id, paste); } -} \ No newline at end of file +} diff --git a/api/Services/PasteService.cs b/api/Services/PasteService.cs index 91f967f6..12375e72 100644 --- a/api/Services/PasteService.cs +++ b/api/Services/PasteService.cs @@ -30,6 +30,8 @@ public interface IPasteService public Task TogglePinnedAsync(string id); + public Task TogglePrivateAsync(string id); + public Task ExistsByIdAsync(string id); } @@ -265,7 +267,9 @@ public async Task IsStarredAsync(string id) public async Task ToggleStarAsync(string id) { if (!_userContext.IsLoggedIn()) + { throw new HttpException(HttpStatusCode.Unauthorized, "You must be authorized to star pastes."); + } var paste = await GetAsync(id); @@ -273,7 +277,9 @@ public async Task ToggleStarAsync(string id) { // Returning not found instead of unauthorized to not expose that the paste exists. if (paste.Private) + { throw new HttpException(HttpStatusCode.NotFound, "Paste not found."); + } } if (paste.Stars.Any(u => u == _userContext.Self.Id)) @@ -312,6 +318,29 @@ public async Task TogglePinnedAsync(string id) await _mongo.Pastes.UpdateOneAsync(p => p.Id == paste.Id, update); } + public async Task TogglePrivateAsync(string id) + { + if (!_userContext.IsLoggedIn()) + { + throw new HttpException(HttpStatusCode.Unauthorized, "You must be authorized to change the private status of pastes."); + } + + var paste = await GetAsync(id); + + if (paste.OwnerId is null) + { + throw new HttpException(HttpStatusCode.BadRequest, "Only owned pastes can be set/unset to private."); + } + + if (paste.OwnerId != _userContext.Self.Id) + { + throw new HttpException(HttpStatusCode.Unauthorized, "You can only change the private status of your own pastes."); + } + + var update = Builders.Update.Set(p => p.Private, !paste.Private); + await _mongo.Pastes.UpdateOneAsync(p => p.Id == paste.Id, update); + } + public async Task ExistsByIdAsync(string id) { return await _mongo.Pastes.Find(p => p.Id == id).FirstOrDefaultAsync() is not null; diff --git a/client/src/lib/api/paste.ts b/client/src/lib/api/paste.ts index 11f0034a..a9d1fbac 100755 --- a/client/src/lib/api/paste.ts +++ b/client/src/lib/api/paste.ts @@ -191,6 +191,15 @@ export const isPasteStarred = async (fetchFunc: FetchFunc, id: string): Promise< return false; }; +export const togglePrivatePaste = async (id: string): Promise => { + const res = await fetch(`${env.PUBLIC_API_BASE}/pastes/${id}/private`, { + method: "post", + credentials: "include" + }); + + return res.ok; +}; + export const getPasteStats = async ( fetchFunc: FetchFunc, id: string diff --git a/client/src/routes/[paste]/+page.svelte b/client/src/routes/[paste]/+page.svelte index bb7b953f..950e5f16 100755 --- a/client/src/routes/[paste]/+page.svelte +++ b/client/src/routes/[paste]/+page.svelte @@ -2,7 +2,14 @@ import type { PageData } from "./$types"; import Tab from "$lib/Tab.svelte"; import PastyMeta from "$lib/PastyMeta.svelte"; - import { deletePaste, ExpiresIn, pinPaste, starPaste, type Pasty } from "$lib/api/paste"; + import { + deletePaste, + ExpiresIn, + pinPaste, + starPaste, + type Pasty, + togglePrivatePaste + } from "$lib/api/paste"; import { tooltip } from "$lib/tooltips"; import { currentUserStore } from "$lib/stores"; import { goto } from "$app/navigation"; @@ -71,9 +78,19 @@ }; const onPinClick = async () => { - await pinPaste(data.paste.id); + const ok = await pinPaste(data.paste.id); - data.paste.pinned = !data.paste.pinned; + if (ok) { + data.paste.pinned = !data.paste.pinned; + } + }; + + const onPrivateClick = async () => { + const ok = await togglePrivatePaste(data.paste.id); + + if (ok) { + data.paste.private = !data.paste.private; + } }; @@ -185,6 +202,28 @@

{data.paste.stars}

+ {#if data.paste.private && $currentUserStore?.id === data.paste.ownerId} + + {:else} + + {/if} +