Skip to content

Commit 89a0177

Browse files
committed
Add footer with privacy policy and contact information
Add some missing page titles Add delete account feature Add download data feature
1 parent 64efa37 commit 89a0177

File tree

8 files changed

+223
-4
lines changed

8 files changed

+223
-4
lines changed

ReplayBrowser/Controllers/AccountController.cs

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
using Microsoft.AspNetCore.Authentication;
1+
using System.IO.Compression;
2+
using System.Text.Json;
3+
using System.Text.Json.Serialization;
4+
using Microsoft.AspNetCore.Authentication;
25
using Microsoft.AspNetCore.Mvc;
6+
using Microsoft.EntityFrameworkCore;
7+
using ReplayBrowser.Data;
8+
using ReplayBrowser.Helpers;
39

410
namespace ReplayBrowser.Controllers;
511

@@ -9,10 +15,12 @@ namespace ReplayBrowser.Controllers;
915
public class AccountController : Controller
1016
{
1117
private readonly IConfiguration _configuration;
18+
private readonly ReplayDbContext _context;
1219

13-
public AccountController(IConfiguration configuration)
20+
public AccountController(IConfiguration configuration, ReplayDbContext context)
1421
{
1522
_configuration = configuration;
23+
_context = context;
1624
}
1725

1826
[Route("login")]
@@ -30,4 +38,84 @@ public async Task<IActionResult> Logout()
3038
await HttpContext.SignOutAsync("Cookies");
3139
return Redirect("/");
3240
}
41+
42+
/// <summary>
43+
/// Deletes the account from the logged in user.
44+
/// </summary>
45+
[HttpGet("delete")]
46+
public async Task<IActionResult> DeleteAccount()
47+
{
48+
if (!User.Identity.IsAuthenticated)
49+
{
50+
return Unauthorized();
51+
}
52+
53+
var guid = AccountHelper.GetAccountGuid(User);
54+
55+
var user = await _context.Accounts
56+
.Include(a => a.Settings)
57+
.Include(a => a.History)
58+
.FirstOrDefaultAsync(a => a.Guid == guid);
59+
60+
if (user == null)
61+
{
62+
return NotFound("Account is null. This should not happen.");
63+
}
64+
65+
_context.Accounts.Remove(user);
66+
await _context.SaveChangesAsync();
67+
68+
await HttpContext.SignOutAsync("Cookies");
69+
// Redirect to the home page
70+
return Redirect("/");
71+
}
72+
73+
/// <summary>
74+
/// Returns a zip of the current users stored data.
75+
/// </summary>
76+
[HttpGet("download")]
77+
public async Task<IActionResult> DownloadAccount()
78+
{
79+
if (!User.Identity.IsAuthenticated)
80+
{
81+
return Unauthorized();
82+
}
83+
84+
var guid = AccountHelper.GetAccountGuid(User);
85+
86+
var user = await _context.Accounts
87+
.Include(a => a.Settings)
88+
.Include(a => a.History)
89+
.FirstOrDefaultAsync(a => a.Guid == guid);
90+
91+
if (user == null)
92+
{
93+
return NotFound("Account is null. This should not happen.");
94+
}
95+
96+
var zipStream = new MemoryStream();
97+
using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
98+
{
99+
var historyEntry = archive.CreateEntry("history.json");
100+
using (var entryStream = historyEntry.Open())
101+
{
102+
await JsonSerializer.SerializeAsync(entryStream, user.History);
103+
}
104+
105+
user.History = null;
106+
107+
var baseEntry = archive.CreateEntry("user.json");
108+
using (var entryStream = baseEntry.Open())
109+
{
110+
await JsonSerializer.SerializeAsync(entryStream, user, new JsonSerializerOptions
111+
{
112+
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
113+
});
114+
}
115+
}
116+
117+
zipStream.Seek(0, SeekOrigin.Begin);
118+
119+
return File(zipStream, "application/zip", "account.zip");
120+
}
33121
}

ReplayBrowser/Helpers/AccountHelper.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.AspNetCore.Components.Authorization;
1+
using System.Security.Claims;
2+
using Microsoft.AspNetCore.Components.Authorization;
23

34
namespace ReplayBrowser.Helpers;
45

@@ -18,4 +19,19 @@ public static class AccountHelper
1819

1920
return Guid.Parse(authState.User.Claims.First(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value);
2021
}
22+
23+
public static Guid? GetAccountGuid(ClaimsPrincipal authState)
24+
{
25+
if (authState.Identity == null)
26+
{
27+
return null;
28+
}
29+
30+
if (!authState.Identity.IsAuthenticated)
31+
{
32+
return null;
33+
}
34+
35+
return Guid.Parse(authState.Claims.First(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value);
36+
}
2137
}

ReplayBrowser/Pages/Account/Manage.razor

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
@using System.Text.Json
33
@using Microsoft.AspNetCore.Authorization
44
@using Microsoft.AspNetCore.Components.Authorization
5+
@using Microsoft.AspNetCore.Components.Web
56
@using ReplayBrowser.Data.Models.Account
67
@using ReplayBrowser.Services
78
@using Action = ReplayBrowser.Data.Models.Account.Action
@@ -10,6 +11,8 @@
1011
@attribute [Authorize]
1112
@inject AccountService AccountService
1213

14+
<PageTitle>Manage Account</PageTitle>
15+
1316
<h3>Manage Account</h3>
1417

1518
@if(ErrorMessage != null)
@@ -45,6 +48,21 @@ else if (account != null)
4548
}</p>
4649

4750
<button class="btn btn-primary" id="save">Save</button>
51+
52+
// Collapse for deleting account
53+
<button class="btn btn-danger" type="button" data-bs-toggle="collapse" data-bs-target="#deleteAccount" aria-expanded="false" aria-controls="deleteAccount">
54+
Delete Account
55+
</button>
56+
57+
<div class="collapse" id="deleteAccount">
58+
<div class="card card-body">
59+
<p>Are you sure you want to delete your account? This action is irreversible and will delete all your settings and all other data related to your account.</p>
60+
<button class="btn btn-danger" id="deleteAccount">Delete Account</button>
61+
</div>
62+
</div>
63+
64+
// Download data
65+
<a class="btn btn-primary" href="/account/download" target="_blank">Download Account Data</a>
4866
}
4967
else
5068
{
@@ -59,6 +77,13 @@ else
5977
uri.searchParams.set("redact", redact);
6078
window.location.href = uri;
6179
});
80+
81+
$("#deleteAccount").click(function() {
82+
if (confirm("Are you sure you want to delete your account? This action is irreversible and will delete all your settings and all other data related to your account."))
83+
{
84+
window.location.href = "/account/delete";
85+
}
86+
});
6287
});
6388
</script>
6489

ReplayBrowser/Pages/Contact.razor

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
@page "/contact"
2+
@using Microsoft.AspNetCore.Components.Web
3+
4+
@inject IConfiguration Configuration
5+
6+
<PageTitle>Contact</PageTitle>
7+
8+
<h3>Contact</h3>
9+
10+
<p>You can contact me using the following methods:</p>
11+
<ul>
12+
<li>Email: <a href="mailto:@Configuration["Contact:Email"]">@Configuration["Contact:Email"]</a></li>
13+
<li>Discord: @Configuration["Contact:Discord"]</li>
14+
</ul>
15+
16+
You can also raise an issue on the <a href="https://github.com/Simyon264/ReplayBrowser/issues/new">GitHub repository</a> if you have any questions or suggestions.

ReplayBrowser/Pages/Downloads.razor

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
@page "/downloads"
2+
@using Microsoft.AspNetCore.Components.Web
3+
4+
<PageTitle>Downloads</PageTitle>
25

36
<h3>Downloads</h3>
47
<p>Here you can see the progress of the internal downloads of the website.<br/>The website looks for new replays every 10th minute. So 12:00, 12:10, 12:20, 12:30, etc.</p>

ReplayBrowser/Pages/Privacy.razor

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
@page "/privacy"
2+
@using Microsoft.AspNetCore.Components.Web
3+
4+
<PageTitle>Privacy Policy</PageTitle>
5+
6+
<h3>Privacy policy</h3>
7+
8+
<p>This document explains what data is collected and how it is used. It applies to every person who visits this website.</p>
9+
10+
<h4>1. Data Collection</h4>
11+
<p>I, the operator of this website, collect the following types of data:</p>
12+
<ul>
13+
<li>Log entries of your visits, including:
14+
<ul>
15+
<li>Time of visit</li>
16+
<li>Actions performed (e.g., search, opened leaderboards)</li>
17+
<li>Details of actions (e.g., "Mode: PlayerOocName, Query: abc")</li>
18+
</ul>
19+
</li>
20+
<li>Personal information (if provided by logging in):
21+
<ul>
22+
<li>Username</li>
23+
<li>GUID</li>
24+
</ul>
25+
</li>
26+
</ul>
27+
28+
<h4>2. Purpose of Data Collection</h4>
29+
<p>I collect data to:</p>
30+
<ul>
31+
<li>Improve the service</li>
32+
<li>Detect and prevent abuse</li>
33+
</ul>
34+
35+
<h4>3. Data Sharing and Disclosure</h4>
36+
<p>I do not share your data with third parties.</p>
37+
38+
39+
<h4>4. Data Storage and Security</h4>
40+
<p>Logs are <em>NOT</em> encrypted. Log data is cleared periodically for visits without a login present.</p>
41+
42+
<p>You can delete your account, which will remove all associated data.</p>
43+
44+
<h4>5. Data Download</h4>
45+
<p>You can download a copy of your data by clicking <a href="/account/download" target="_blank">here</a>.</p>
46+
47+
<h4>6. Contact Information</h4>
48+
<p>If you have any questions or concerns, please contact me <a href="/contact">here</a>.</p>

ReplayBrowser/Pages/Shared/MainLayout.razor

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
@using Microsoft.AspNetCore.Components.Authorization
2+
@using Microsoft.EntityFrameworkCore
23
@using ReplayBrowser.Data
34
@using ReplayBrowser.Data.Models.Account
45
@using ReplayBrowser.Helpers
@@ -32,7 +33,7 @@
3233
};
3334

3435
DbContext.Accounts.Add(_account);
35-
await DbContext.SaveChangesAsync();
36+
DbContext.SaveChanges();
3637

3738
Log.Information("Created new account for {Guid} with username {Username}", guid, data.Username);
3839
}
@@ -79,6 +80,16 @@
7980
</main>
8081
</div>
8182

83+
<footer class="footer text-muted text-center">
84+
<div class="container">
85+
<p>Replay Browser is a project by Simyon (so far no other contributors). Source code available on <a href="https://github.com/Simyon264/ReplayBrowser">GitHub</a>.</p>
86+
</div>
87+
88+
<div class="container">
89+
<p><a href="/contact">Contact</a> | <a href="/privacy">Privacy Policy</a></p>
90+
</div>
91+
</footer>
92+
8293
<script>
8394
const modalRoot = document.getElementById('modal-root');
8495
const pageRoot = document.querySelector('.page');

ReplayBrowser/Services/AccountService.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,4 +271,16 @@ public async Task<List<Account>> GetAllAccounts()
271271
.Include(a => a.History)
272272
.ToListAsync();
273273
}
274+
275+
/// <summary>
276+
/// Deletes an account from the database, including logs and settings.
277+
/// </summary>
278+
public async Task DeleteAccount(Account account)
279+
{
280+
using var scope = _scopeFactory.CreateScope();
281+
var context = scope.ServiceProvider.GetRequiredService<ReplayDbContext>();
282+
context.Accounts.Remove(account);
283+
await context.SaveChangesAsync();
284+
Log.Information($"Deleted account {account.Username} ({account.Guid})");
285+
}
274286
}

0 commit comments

Comments
 (0)