diff --git a/src/JobsJobsJobs/Client/Pages/SoLong/Options.razor b/src/JobsJobsJobs/Client/Pages/SoLong/Options.razor
new file mode 100644
index 0000000..5a7ed22
--- /dev/null
+++ b/src/JobsJobsJobs/Client/Pages/SoLong/Options.razor
@@ -0,0 +1,39 @@
+@page "/so-long/options"
+@inject HttpClient http
+@inject AppState state
+@inject NavigationManager nav
+@inject IToastService toast
+
+
+
+
Account Deletion Options
+
+
Option 1 – Delete Your Profile
+
+ Utilizing this option will remove your current employment profile and skills. This will preserve any success stories
+ you may have written, and preserves this application’s knowledge of you. This is what you want to use if you
+ want to clear out your profile and start again (and remove the current one from others’ view).
+
+
+
+
+
+
+
+
Option 2 – Delete Your Account
+
+ This option will make it like you never visited this site. It will delete your profile, skills, success stories, and
+ account. This is what you want to use if you want to disappear from this application. Clicking the button below
+ will not affect your No Agenda Social account in any way; its effects are limited to Jobs, Jobs,
+ Jobs.
+
+
+
+ (This will not revoke this application’s permissions on No Agenda Social; you will have to remove this
+ yourself. The confirmation message has a link where you can do this; once the page loads, find the
+ Jobs, Jobs, Jobs entry, and click the × Revoke link for that entry.)
+
+
+
+
+
diff --git a/src/JobsJobsJobs/Client/Pages/SoLong/Options.razor.cs b/src/JobsJobsJobs/Client/Pages/SoLong/Options.razor.cs
new file mode 100644
index 0000000..8bc5d9c
--- /dev/null
+++ b/src/JobsJobsJobs/Client/Pages/SoLong/Options.razor.cs
@@ -0,0 +1,54 @@
+using Microsoft.AspNetCore.Components;
+using System.Net.Http;
+using System.Threading.Tasks;
+
+namespace JobsJobsJobs.Client.Pages.SoLong
+{
+ public partial class Options : ComponentBase
+ {
+ ///
+ /// Extract an error phrase from a response similar to 404 - Not Found
+ ///
+ /// The HTTP response
+ /// The formatted error code
+ private static string ErrorPhrase(HttpResponseMessage response) =>
+ $"{response.StatusCode}{(string.IsNullOrEmpty(response.ReasonPhrase) ? "" : $" - {response.ReasonPhrase }")}";
+
+ ///
+ /// Delete the profile only; redirect to home page on success
+ ///
+ private async Task DeleteProfile()
+ {
+ ServerApi.SetJwt(http, state);
+ var result = await http.DeleteAsync("/api/profile/");
+ if (result.IsSuccessStatusCode)
+ {
+ toast.ShowSuccess("Profile Deleted Successfully");
+ nav.NavigateTo("/citizen/dashboard");
+ }
+ else
+ {
+ toast.ShowError(ErrorPhrase(result));
+ }
+ }
+
+ ///
+ /// Delete everything pertaining to the user's account
+ ///
+ private async Task DeleteAccount()
+ {
+ ServerApi.SetJwt(http, state);
+ var result = await http.DeleteAsync("/api/citizen/");
+ if (result.IsSuccessStatusCode)
+ {
+ state.Jwt = "";
+ state.User = null;
+ nav.NavigateTo("/so-long/success");
+ }
+ else
+ {
+ toast.ShowError(ErrorPhrase(result));
+ }
+ }
+ }
+}
diff --git a/src/JobsJobsJobs/Client/Pages/SoLong/Success.razor b/src/JobsJobsJobs/Client/Pages/SoLong/Success.razor
new file mode 100644
index 0000000..6d5e12a
--- /dev/null
+++ b/src/JobsJobsJobs/Client/Pages/SoLong/Success.razor
@@ -0,0 +1,15 @@
+@page "/so-long/success"
+
+
+
+
Account Deletion Success
+
+
+ Your account has been successfully deleted. To revoke the permissions you have previously granted to this
+ application, find it in this list and click
+ × Revoke. Otherwise, clicking “Log On” will create a new, empty account without
+ prompting you further.
+
+
+ Thank you for participating, and thank you for your courage. #GitmoNation
+
diff --git a/src/JobsJobsJobs/Server/Areas/Api/Controllers/CitizenController.cs b/src/JobsJobsJobs/Server/Areas/Api/Controllers/CitizenController.cs
index 2d03103..10b1723 100644
--- a/src/JobsJobsJobs/Server/Areas/Api/Controllers/CitizenController.cs
+++ b/src/JobsJobsJobs/Server/Areas/Api/Controllers/CitizenController.cs
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using NodaTime;
+using System.Security.Claims;
using System.Threading.Tasks;
namespace JobsJobsJobs.Server.Areas.Api.Controllers
@@ -44,6 +45,11 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers
_db = db;
}
+ ///
+ /// The current citizen ID
+ ///
+ private CitizenId CurrentCitizenId => CitizenId.Parse(User.FindFirst(ClaimTypes.NameIdentifier)!.Value);
+
[HttpGet("log-on/{authCode}")]
public async Task LogOn([FromRoute] string authCode)
{
@@ -87,5 +93,15 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers
var citizen = await _db.FindCitizenById(CitizenId.Parse(id));
return citizen == null ? NotFound() : Ok(citizen);
}
+
+ [Authorize]
+ [HttpDelete("")]
+ public async Task Remove()
+ {
+ await _db.DeleteCitizen(CurrentCitizenId);
+ await _db.SaveChangesAsync();
+
+ return Ok();
+ }
}
}
diff --git a/src/JobsJobsJobs/Server/Areas/Api/Controllers/ProfileController.cs b/src/JobsJobsJobs/Server/Areas/Api/Controllers/ProfileController.cs
index 83f398b..ad9cc53 100644
--- a/src/JobsJobsJobs/Server/Areas/Api/Controllers/ProfileController.cs
+++ b/src/JobsJobsJobs/Server/Areas/Api/Controllers/ProfileController.cs
@@ -129,6 +129,14 @@ namespace JobsJobsJobs.Server.Areas.Api.Controllers
return Ok();
}
-
+
+ [HttpDelete("")]
+ public async Task Remove()
+ {
+ await _db.DeleteProfileByCitizen(CurrentCitizenId);
+ await _db.SaveChangesAsync();
+
+ return Ok();
+ }
}
}
diff --git a/src/JobsJobsJobs/Server/Data/CitizenExtensions.cs b/src/JobsJobsJobs/Server/Data/CitizenExtensions.cs
index f2f8d79..e20f45b 100644
--- a/src/JobsJobsJobs/Server/Data/CitizenExtensions.cs
+++ b/src/JobsJobsJobs/Server/Data/CitizenExtensions.cs
@@ -34,7 +34,7 @@ namespace JobsJobsJobs.Server.Data
///
/// The citizen to be added
public static async Task AddCitizen(this JobsDbContext db, Citizen citizen) =>
- await db.Citizens.AddAsync(citizen);
+ await db.Citizens.AddAsync(citizen).ConfigureAwait(false);
///
/// Update a citizen after they have logged on (update last seen, sync display name)
@@ -42,5 +42,20 @@ namespace JobsJobsJobs.Server.Data
/// The updated citizen
public static void UpdateCitizen(this JobsDbContext db, Citizen citizen) =>
db.Entry(citizen).State = EntityState.Modified;
+
+ ///
+ /// Delete a citizen
+ ///
+ /// The ID of the citizen to be deleted
+ ///
+ public static async Task DeleteCitizen(this JobsDbContext db, CitizenId citizenId)
+ {
+ var id = citizenId.ToString();
+ await db.DeleteProfileByCitizen(citizenId).ConfigureAwait(false);
+ await db.Database.ExecuteSqlInterpolatedAsync($"DELETE FROM jjj.success WHERE citizen_id = {id}")
+ .ConfigureAwait(false);
+ await db.Database.ExecuteSqlInterpolatedAsync($"DELETE FROM jjj.citizen WHERE id = {id}")
+ .ConfigureAwait(false);
+ }
}
}
diff --git a/src/JobsJobsJobs/Server/Data/ProfileExtensions.cs b/src/JobsJobsJobs/Server/Data/ProfileExtensions.cs
index 5d2503d..9a10266 100644
--- a/src/JobsJobsJobs/Server/Data/ProfileExtensions.cs
+++ b/src/JobsJobsJobs/Server/Data/ProfileExtensions.cs
@@ -160,5 +160,18 @@ namespace JobsJobsJobs.Server.Data
x.Profile.SeekingEmployment, x.Profile.RemoteWork, x.Profile.FullTime, x.Profile.LastUpdatedOn))
.ToListAsync().ConfigureAwait(false);
}
+
+ ///
+ /// Delete skills and profile for the given citizen
+ ///
+ /// The ID of the citizen whose profile should be deleted
+ public static async Task DeleteProfileByCitizen(this JobsDbContext db, CitizenId citizenId)
+ {
+ var id = citizenId.ToString();
+ await db.Database.ExecuteSqlInterpolatedAsync($"DELETE FROM jjj.skill WHERE citizen_id = {id}")
+ .ConfigureAwait(false);
+ await db.Database.ExecuteSqlInterpolatedAsync($"DELETE FROM jjj.profile WHERE citizen_id = {id}")
+ .ConfigureAwait(false);
+ }
}
}