full account deletion done

This commit is contained in:
Artemis 2025-01-25 18:40:26 +01:00
parent 3f9ac03c92
commit 08ae8e6443
11 changed files with 165 additions and 15 deletions

View file

@ -24,6 +24,5 @@ a profile is
- p2: saving register form as it gets filled / re-display it with partial values
- account
- p2: optional email for forgotten password i guess
- ability to delete a tag (remove all data except the ID to lock the ID)
- ability to delete the whole account (incl. all registered tags)
- deleting a tag keeping the account allows to re-use the tag later on. no one else can use it still

View file

@ -106,6 +106,7 @@ input:hover,
select:hover,
button:hover,
textarea:hover,
button:hover,
.btn:hover {
border-color: var(--clr-primary-a0);
}
@ -138,7 +139,8 @@ section {
}
p.form-error,
div.form-error {
div.form-error,
a.error {
background-color: var(--clr-error-surface);
border: 2pt solid var(--clr-error-primary-0);
color: var(--clr-error-primary-40);
@ -214,12 +216,16 @@ p.heading {
border-radius: 4pt;
}
.fields>*:not(:last-child) {
.fields:not(:last-of-type) {
margin-bottom: 1em;
}
.fields *:not(:last-child) {
margin-bottom: 1em;
/* margin-top: 1em; */
}
.fields>* * {
.fields * * {
display: block;
width: 100%
}

View file

@ -1,7 +1,7 @@
use crate::db::schema::DollProfile;
use uuid::Uuid;
use uuid::{uuid, Uuid};
use super::schema::{CreateDollProfile, DbHook};
use super::schema::{CreateDollProfile, DbHook, TrxHook};
pub async fn list(db: &mut DbHook, from: &Uuid) -> sqlx::Result<Vec<DollProfile>> {
sqlx::query_as!(
@ -95,7 +95,7 @@ pub async fn create(db: &mut DbHook, doll: CreateDollProfile<'_>) -> sqlx::Resul
/// the account holder to "re-create" one with this ID.
///
/// A period of time after which deleted accounts will have their IDs freed is to be set.
pub async fn delete(db: &mut DbHook, id: i32) -> sqlx::Result<()> {
pub async fn delete(trx: &mut TrxHook<'_>, id: i32) -> sqlx::Result<()> {
sqlx::query!(
r#"
update doll_profiles
@ -119,8 +119,31 @@ pub async fn delete(db: &mut DbHook, id: i32) -> sqlx::Result<()> {
"#,
id
)
.execute(&mut **db)
.execute(&mut **trx)
.await?;
Ok(())
}
pub async fn delete_all_from_account(trx: &mut TrxHook<'_>, from: &Uuid) -> sqlx::Result<()> {
let null_uuid = uuid!("00000000-0000-0000-0000-000000000000");
// 1. archive all tags from the account
let tags = sqlx::query_as!(
DollProfile,
"select * from doll_profiles where bound_to_id = $1 and archived_at is null",
from
)
.fetch_all(&mut **trx)
.await?;
for tag in tags {
delete(trx, tag.id).await?;
}
// 2. unlink archived tags from the account
sqlx::query!("update doll_profiles set bound_to_id = $1 where archived_at is not null and bound_to_id = $2", null_uuid, from)
.execute(&mut **trx)
.await?;
Ok(())
}

View file

@ -4,7 +4,7 @@ use rocket_db_pools::{Connection, Database};
use sqlx::{
pool::PoolConnection,
types::{chrono, Uuid},
Postgres,
Postgres, Transaction,
};
#[derive(Database)]
@ -12,6 +12,7 @@ use sqlx::{
pub struct DollTags(sqlx::PgPool);
pub type DollTagsDb = Connection<DollTags>;
pub type DbHook = PoolConnection<Postgres>;
pub type TrxHook<'a> = Transaction<'a, Postgres>;
// Doll Profiles stuff
#[derive(Debug, Serialize)]

View file

@ -2,7 +2,7 @@ use uuid::Uuid;
use crate::db::schema::User;
use super::schema::DollTagsDb;
use super::schema::{DollTagsDb, TrxHook};
pub async fn get(mut db: DollTagsDb, username: &str) -> sqlx::Result<Option<User>> {
sqlx::query_as!(User, "select * from users where username = $1", username)
@ -31,3 +31,11 @@ pub async fn create(
.fetch_one(&mut **db)
.await
}
pub async fn delete(trx: &mut TrxHook<'_>, id: &Uuid) -> sqlx::Result<()> {
sqlx::query!("delete from users where id = $1", id)
.execute(&mut **trx)
.await?;
Ok(())
}

View file

@ -36,6 +36,8 @@ fn rocket() -> _ {
form::register_tag::handle_register,
account::ask_delete,
account::confirm_delete,
account::ask_terminate_account,
account::confirm_terminate_account,
],
)
.mount(

View file

@ -1,10 +1,13 @@
use rocket::response::Redirect;
use rocket::{http::CookieJar, response::Redirect};
use rocket_dyn_templates::{context, Template};
use sqlx::Acquire;
use crate::{
auth::session,
db::{
doll,
schema::{DollTagsDb, User},
user,
},
pages::CommonTemplateState,
};
@ -30,7 +33,7 @@ pub async fn index(mut db: DollTagsDb, user: User, meta: CommonTemplateState) ->
#[get("/settings")]
pub fn show_settings(user: User, meta: CommonTemplateState) -> Template {
todo!("woof");
Template::render("account/settings", context! {user,meta})
}
#[get("/delete/<id>")]
@ -58,6 +61,28 @@ pub async fn ask_delete(
#[get("/yes_delete_this/<id>")]
pub async fn confirm_delete(mut db: DollTagsDb, id: i32, _user: User) -> PageResult {
doll::delete(&mut *db, id).await?;
let mut trx = db.begin().await?;
doll::delete(&mut trx, id).await?;
trx.commit().await?;
Ok(Redirect::to(uri!("/account", index)).into())
}
#[get("/terminate")]
pub fn ask_terminate_account(_user: User, meta: CommonTemplateState) -> Template {
Template::render("account/terminate", context! {meta})
}
#[get("/termin@or")]
pub async fn confirm_terminate_account(
mut db: DollTagsDb,
user: User,
cookies: &CookieJar<'_>,
) -> PageResult {
let mut trx = db.begin().await?;
doll::delete_all_from_account(&mut trx, &user.id).await?;
user::delete(&mut trx, &user.id).await?;
session::logout(cookies);
trx.commit().await?;
Ok(Redirect::to("/").into())
}

View file

@ -1,5 +1,5 @@
{% extends "base" %}
{% block title %}Log in - {% endblock title %}
{% block title %}Your account - {% endblock title %}
{% block main %}
<aside>
<p class="subnav">

View file

@ -0,0 +1,67 @@
{% extends "base" %}
{% block title %}Settings - {% endblock title %}
{% block main %}
<h2>Your settings</h2>
<div class="fields raised">
<form method="post" class="">
<input type="hidden" name="form" value="info">
<div>
<p class="heading"><label for="username">Username</label></p>
<input type="text" id="username" name="username" maxlength="256" placeholder="{{user.username}}">
<p class="note">
Reminder: your username is your login ID.
If you change it, don't forget to use the new one to log in.
</p>
</div>
<div>
<p class="heading"><label for="email">Recovery email</label></p>
<input type="email" id="email" name="email" maxlength="254" placeholder="{{user.email}}">
</div>
<button type="submit">Save those changes</button>
</form>
</div>
<div class="fields raised">
<form method="post" class="">
<input type="hidden" name="form" value="password">
<div>
<p class="heading"><label for="old_password">Current password</label></p>
<input type="password" id="old_password" name="old_password" minlength="8">
</div>
<div>
<p class="heading"><label for="password">New password</label></p>
<input type="password" id="password" name="password" minlength="8">
</div>
<div>
<p class="heading"><label for="confirm_password">Confirm the password</label></p>
<input type="password" id="confirm_password" name="confirm_password" minlength="8">
</div>
<button type="submit">Change your password</button>
</form>
</div>
<section>
<h3>Exporting your data</h3>
<p>You can export all your account's data using this button.</p>
<p>It will be provided in a JSON file whose structure is documented.</p>
<!-- TODO: Link structure documentation. -->
<a href="/account/export" class="btn">Export all my data</a>
</section>
<section>
<h3>Deleting your account</h3>
<p>You can delete your account by clicking on the button below.</p>
<p>
Deleting your account will also delete all tags registered to you
while locking them out of registration for anyone else for a certain time period.
</p>
<p>This is not a recoverable operation.</p>
<a href="/account/terminate" class="error btn">Delete my account</a>
</section>
{% endblock main %}

View file

@ -0,0 +1,19 @@
{% extends "base" %}
{% block title %}Terminate your account - {% endblock title %}
{% block main %}
<h2>Are you sure to delete your entire account?</h2>
<p>You are about to delete your entire account.</p>
<p>
Doing so will irreversibly remove all data,
and the IDs of all the tags you had linked to your account will be locked
for a certain time to avoid issues.
</p>
<p>This is not reversible.</p>
<p class="subnav">
<a href="/account" class="btn">No, take me back</a>
<a href="/account/termin@or" class="btn error">Yes, delete my account and associated tags</a>
</p>
{% endblock main %}

View file

@ -1,5 +1,5 @@
{% extends "base" %}
{% block title %}Log in - {% endblock title %}
{% block title %}Delete {{tag.id|pretty_id}} - {% endblock title %}
{% block main %}
<h2>Are you sure to delete it?</h2>