diff --git a/src/JobsJobsJobs/Client/Pages/Citizen/Authorized.razor b/src/JobsJobsJobs/Client/Pages/Citizen/Authorized.razor index dfec9cb..5cc718b 100644 --- a/src/JobsJobsJobs/Client/Pages/Citizen/Authorized.razor +++ b/src/JobsJobsJobs/Client/Pages/Citizen/Authorized.razor @@ -3,6 +3,6 @@ @inject NavigationManager nav @inject AppState state - +

@Message

diff --git a/src/JobsJobsJobs/Client/Pages/Citizen/EditProfile.razor b/src/JobsJobsJobs/Client/Pages/Citizen/EditProfile.razor index 05c8047..18eff4e 100644 --- a/src/JobsJobsJobs/Client/Pages/Citizen/EditProfile.razor +++ b/src/JobsJobsJobs/Client/Pages/Citizen/EditProfile.razor @@ -115,4 +115,8 @@
View Your User Profile

} +

+
If you want to delete your profile, or your entire account, + see your deletion options here. +

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); + } } }