Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: toggle paste private status #445

Merged
merged 2 commits into from
May 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion api/Controllers/PasteController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<IActionResult> CreatePaste([FromBody] PasteCreateInfo createInfo)
{
var paste = await _pasteService.CreateAsync(createInfo);
return Created("/api/v3/pastes/" + paste.Id, paste);
}
}
}
29 changes: 29 additions & 0 deletions api/Services/PasteService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public interface IPasteService

public Task TogglePinnedAsync(string id);

public Task TogglePrivateAsync(string id);

public Task<bool> ExistsByIdAsync(string id);
}

Expand Down Expand Up @@ -265,15 +267,19 @@ public async Task<bool> 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);

if (paste.OwnerId is null || paste.OwnerId != _userContext.Self.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))
Expand Down Expand Up @@ -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<Paste>.Update.Set(p => p.Private, !paste.Private);
await _mongo.Pastes.UpdateOneAsync(p => p.Id == paste.Id, update);
}

public async Task<bool> ExistsByIdAsync(string id)
{
return await _mongo.Pastes.Find(p => p.Id == id).FirstOrDefaultAsync() is not null;
Expand Down
9 changes: 9 additions & 0 deletions client/src/lib/api/paste.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,15 @@ export const isPasteStarred = async (fetchFunc: FetchFunc, id: string): Promise<
return false;
};

export const togglePrivatePaste = async (id: string): Promise<boolean> => {
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
Expand Down
45 changes: 42 additions & 3 deletions client/src/routes/[paste]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
}
};
</script>

Expand Down Expand Up @@ -185,6 +202,28 @@
<p>{data.paste.stars}</p>
</button>

{#if data.paste.private && $currentUserStore?.id === data.paste.ownerId}
<button aria-label="set to public" use:tooltip on:click={onPrivateClick}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="icon">
<title>Unlock Icon</title>
<path
fill="currentColor"
d="M5.5 4v2h7A1.5 1.5 0 0 1 14 7.5v6a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 2 13.5v-6A1.5 1.5 0 0 1 3.499 6H4V4a4 4 0 0 1 7.371-2.154.75.75 0 0 1-1.264.808A2.5 2.5 0 0 0 5.5 4Zm-2 3.5v6h9v-6h-9Z"
/>
</svg>
</button>
{:else}
<button aria-label="set to private" use:tooltip on:click={onPrivateClick}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="icon">
<title>Lock Icon</title>
<path
fill="currentColor"
d="M4 4a4 4 0 0 1 8 0v2h.25c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 12.25 15h-8.5A1.75 1.75 0 0 1 2 13.25v-5.5C2 6.784 2.784 6 3.75 6H4Zm8.25 3.5h-8.5a.25.25 0 0 0-.25.25v5.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25ZM10.5 6V4a2.5 2.5 0 1 0-5 0v2Z"
/>
</svg>
</button>
{/if}

<button aria-label="edit" use:tooltip>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="icon">
<title>Pen Icon</title>
Expand Down
Loading