Skip to content

Commit

Permalink
feat: account deletion
Browse files Browse the repository at this point in the history
  • Loading branch information
CodeMyst committed May 15, 2024
1 parent ac1e6cf commit ea70fa3
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 7 deletions.
6 changes: 6 additions & 0 deletions api/Controllers/UserController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ public async Task<IActionResult> GetUser(string username, [FromQuery] string id)
return user is not null ? Ok(user) : NotFound();
}

[HttpDelete("{username}")]
public async Task DeleteUser(string username)
{
await _userProvider.DeleteUserAsync(username);
}

[HttpGet("{username}/pastes")]
public async Task<Page<PasteWithLangStats>> GetUserOwnedPastes(string username, [FromQuery] PageRequest pageRequest, [FromQuery] string tag)
{
Expand Down
2 changes: 1 addition & 1 deletion api/Models/Paste.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class Paste
public string OwnerId { get; set; }

public bool Private { get; set; }

public bool Pinned { get; set; }

public List<string> Tags { get; set; } = new();
Expand Down
1 change: 0 additions & 1 deletion api/Services/AuthService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using JWT.Builder;
using JWT.Exceptions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.ObjectPool;
using MongoDB.Driver;
using pastemyst.Exceptions;
using pastemyst.Models;
Expand Down
37 changes: 33 additions & 4 deletions api/Services/UserProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,24 @@ public interface IUserProvider
public Task<Page<PasteWithLangStats>> GetOwnedPastesAsync(string username, string tag, bool pinnedOnly, PageRequest pageRequest);

public Task<List<string>> GetTagsAsync(string username);

public Task DeleteUserAsync(string username);
}

public class UserProvider : IUserProvider
{
private readonly IUserContext _userContext;
private readonly IPasteService _pasteService;
private readonly IActionLogger _actionLogger;
private readonly IImageService _imageService;
private readonly IMongoService _mongo;

public UserProvider(IUserContext userContext, IPasteService pasteService, IMongoService mongo)
public UserProvider(IUserContext userContext, IPasteService pasteService, IMongoService mongo, IActionLogger actionLogger, IImageService imageService)
{
_userContext = userContext;
_pasteService = pasteService;
_actionLogger = actionLogger;
_imageService = imageService;
_mongo = mongo;
}

Expand Down Expand Up @@ -76,7 +82,7 @@ public async Task<Page<PasteWithLangStats>> GetOwnedPastesAsync(string username,

if (tag is not null)
{
if (_userContext.Self != user)
if (_userContext.Self.Id != user.Id)
{
throw new HttpException(HttpStatusCode.Unauthorized, "You must be authorized to view paste tags.");
}
Expand All @@ -93,7 +99,7 @@ public async Task<Page<PasteWithLangStats>> GetOwnedPastesAsync(string username,
var totalItems = await _mongo.Pastes.CountDocumentsAsync(filter);
var totalPages = (int)Math.Ceiling((float)totalItems / pageRequest.PageSize);

if (_userContext.Self != user)
if (_userContext.Self.Id != user.Id)
{
pastes.ForEach(p => p.Tags = new());
}
Expand Down Expand Up @@ -123,7 +129,7 @@ public async Task<List<string>> GetTagsAsync(string username)

var user = await GetByUsernameAsync(username);

if (_userContext.Self != user)
if (_userContext.Self.Id != user.Id)
throw new HttpException(HttpStatusCode.Unauthorized, "You can only fetch your own tags.");

var filter = Builders<Paste>.Filter.Eq(p => p.OwnerId, user.Id);
Expand All @@ -133,4 +139,27 @@ public async Task<List<string>> GetTagsAsync(string username)
.Distinct()
.ToList();
}

public async Task DeleteUserAsync(string username)
{
if (!_userContext.IsLoggedIn())
throw new HttpException(HttpStatusCode.Unauthorized, "You must be authorized to delete your account.");

var user = await GetByUsernameAsync(username);

if (_userContext.Self.Id != user.Id)
throw new HttpException(HttpStatusCode.Unauthorized, "You can delete only your account.");

await _imageService.DeleteAsync(user.AvatarId);

await _mongo.Pastes.DeleteManyAsync(p => p.OwnerId == user.Id);
await _mongo.Users.DeleteOneAsync(u => u.Id == user.Id);

// Delete all stars of this user
var starsFilter = Builders<Paste>.Filter.AnyEq(p => p.Stars, user.Id);
var starsUpdate = Builders<Paste>.Update.Pull(p => p.Stars, user.Id);
await _mongo.Pastes.UpdateManyAsync(starsFilter, starsUpdate);

await _actionLogger.LogActionAsync(ActionLogType.UserDeleted, user.Id);
}
}
9 changes: 9 additions & 0 deletions client/src/lib/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,12 @@ export const getUserTags = async (fetchFunc: FetchFunc, username: string): Promi

return [];
};

export const deleteUser = async (username: string): Promise<boolean> => {
const res = await fetch(`${env.PUBLIC_API_BASE}/users/${username}`, {
method: "delete",
credentials: "include"
});

return res.ok;
};
22 changes: 21 additions & 1 deletion client/src/routes/settings/profile/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { env } from "$env/dynamic/public";
import { getSelf } from "$lib/api/auth";
import { updateUserSettings } from "$lib/api/settings";
import { getUserByUsername } from "$lib/api/user";
import { getUserByUsername, deleteUser } from "$lib/api/user";
import Checkbox from "$lib/Checkbox.svelte";
import { usernameRegex } from "$lib/patterns";
import { currentUserStore } from "$lib/stores";
Expand Down Expand Up @@ -95,6 +95,17 @@
const saveSettings = async () => {
await updateUserSettings(fetch, data.userSettings);
};
const onAccountDelete = async () => {
// TODO: better confirm dialog
if (confirm("are you sure you want to delete your account? this will delete your account and all the associated data, including the pastes")) {
const ok = await deleteUser(data.self.username);
if (!ok) return;
window.location.href = `${env.PUBLIC_API_BASE}/auth/logout`;
}
};
</script>

<svelte:head>
Expand Down Expand Up @@ -203,6 +214,11 @@
<span class="hint">
toggle whether to show only your pinned pastes or all your public pastes on your profile
</span>

<button class="delete-account btn btn-danger" on:click={onAccountDelete}>delete account</button>
<span class="hint">
this will delete your account and all the associated data, including the pastes
</span>
</div>

<style lang="scss">
Expand Down Expand Up @@ -250,6 +266,10 @@
.privacy {
margin-bottom: 2rem;
.delete-account {
margin-top: 2rem;
}
}
button {
Expand Down

0 comments on commit ea70fa3

Please sign in to comment.