create a delete-my-data button

This commit is contained in:
2025-08-20 21:04:55 +02:00
parent ea65f51704
commit f7647829bd
6 changed files with 97 additions and 5 deletions

View File

@@ -1,7 +1,8 @@
use crate::{language::language, page::Page};
use axum::http::HeaderMap;
use crate::{language::language, page::{MyMessage, Page}};
use axum::{extract::Query, http::HeaderMap};
use axum_extra::extract::CookieJar;
use maud::{html, Markup, PreEscaped};
use serde::Deserialize;
pub(super) async fn index(cookies: CookieJar, headers: HeaderMap) -> Markup {
let lang = language(&cookies, &headers);
@@ -54,11 +55,21 @@ pub(super) async fn index(cookies: CookieJar, headers: HeaderMap) -> Markup {
})
}
pub(super) async fn data(cookies: CookieJar, headers: HeaderMap) -> Markup {
#[derive(Deserialize)]
pub(super) struct PrivacyQuery {
deleted: Option<u8>,
}
pub(super) async fn data(cookies: CookieJar, headers: HeaderMap, Query(query): Query<PrivacyQuery>) -> Markup {
let lang = language(&cookies, &headers);
rust_i18n::set_locale(lang.to_locale());
let page = Page::new(lang);
let mut page = Page::new(lang);
// Show success message if data was deleted
if query.deleted == Some(1) {
page.set_message(MyMessage::DataDeleted);
}
page.content(html! {
h1 { (t!("privacy_policy_title")) }
h2 { (t!("data_controller")) }
@@ -152,5 +163,14 @@ pub(super) async fn data(cookies: CookieJar, headers: HeaderMap) -> Markup {
(PreEscaped(t!("contact_us")))
}
}
h3 { (t!("delete_personal_data")) }
p {
(t!("delete_data_description"))
}
form method="POST" action="/delete-data" onsubmit={"return confirm('" (t!("delete_confirmation")) "');"} {
button type="submit" class="secondary" {
(t!("delete_my_data"))
}
}
})
}

View File

@@ -1,5 +1,5 @@
use crate::model::client::Client;
use axum::{http::HeaderMap, routing::get, Router};
use axum::{http::HeaderMap, response::Redirect, routing::{get, post}, Router};
use axum_extra::extract::{
cookie::{Cookie, Expiration, Key},
CookieJar, PrivateCookieJar,
@@ -260,6 +260,32 @@ fn load_or_create_key() -> Result<Key, Box<dyn std::error::Error>> {
Ok(Key::from(&config.key))
}
async fn delete_personal_data(
axum::extract::State(state): axum::extract::State<AppState>,
cookies: PrivateCookieJar,
) -> (PrivateCookieJar, Redirect) {
let backend = &state.backend;
// Get the client from cookies
if let Some(client_cookie) = cookies.get("client_id") {
if let Ok(uuid) = Uuid::parse_str(client_cookie.value()) {
// Delete all client data from database
let _ = backend.delete_client_data(&uuid).await;
}
}
// Remove the client_id cookie by setting an expired cookie
let expired_cookie = Cookie::build(("client_id", ""))
.expires(Expiration::DateTime(OffsetDateTime::now_utc() - time::Duration::days(1)))
.http_only(true)
.secure(true)
.build();
let updated_cookies = cookies.add(expired_cookie);
// Redirect back to privacy page with success message
(updated_cookies, Redirect::to("/privacy?deleted=1"))
}
#[tokio::main]
async fn main() {
tracing_subscriber::registry()
@@ -282,6 +308,7 @@ async fn main() {
let app = Router::new()
.route("/", get(index::index))
.route("/privacy", get(index::data))
.route("/delete-data", post(delete_personal_data))
.nest_service("/static", ServeDir::new("./static/serve"))
.merge(game::routes())
.with_state(state);

View File

@@ -41,4 +41,28 @@ impl Backend {
}
}
}
pub(crate) async fn delete_client_data(&self, uuid: &Uuid) -> Result<(), sqlx::Error> {
let uuid_str = uuid.to_string();
match self {
Backend::Sqlite(db) => {
// Start a transaction to ensure data consistency
let mut tx = db.begin().await?;
// Delete sightings first (foreign key constraint)
sqlx::query!("DELETE FROM sightings WHERE client_uuid = ?", uuid_str)
.execute(&mut *tx)
.await?;
// Delete client record
sqlx::query!("DELETE FROM client WHERE uuid = ?", uuid_str)
.execute(&mut *tx)
.await?;
tx.commit().await?;
Ok(())
}
}
}
}

View File

@@ -10,6 +10,7 @@ pub(crate) enum MyMessage {
NameChanged,
FoundCam(String, i64),
Error(String, String, String),
DataDeleted,
}
impl Page {
@@ -97,6 +98,14 @@ impl Page {
}
}
}
MyMessage::DataDeleted => {
div.flex {
article class="succ msg" {
header { (t!("data_deletion_success_title")) }
(t!("data_deletion_success_body"))
}
}
}
}
}
section { (content) }