//! An account deleting its knotless data (`getAccountData`, //! `deleteAccountData`): every record it holds in a `dev.knotless.*` //! collection, and the spaces of its private repositories, through the //! same code as `knotless account delete-data` ([`knotless::account`]); then what //! this server holds from it, as for an account deleted at its PDS //! ([`crate::accounts::forget`]). //! //! The pages ask with their cookie, and the records are deleted with the //! session the server keeps for the account, a part a call, so the page //! can say how far it has got and a run cut short carries on. The command //! line and MCP delete the records themselves and ask with a service //! token only to be forgotten, which is done once the account's PDS holds //! no knotless record. use super::stars::logged_in; use super::*; use crate::viewer::Viewer; use axum::Extension; use knotless::account::{self, Step}; /// The routes. pub fn routes() -> Router> { Router::new() .route("/xrpc/dev.knotless.getAccountData", get(get_account_data)) .route( &format!("/xrpc/{}", account::FORGET), post(delete_account_data), ) } /// `dev.knotless.getAccountData`. pub async fn get_account_data( State(app): State>, Extension(viewer): Extension, headers: HeaderMap, ) -> Result { let did = logged_in(&app, viewer, &headers, "see its knotless data")?; let ctx = crate::login::ctx_for(&app, &did); let held = account::inventory(&ctx).await.map_err(AppError::upstream)?; Ok(answer(held)) } #[derive(Deserialize)] pub struct DeleteInput { did: String, limit: Option, } /// `dev.knotless.deleteAccountData`. pub async fn delete_account_data( State(app): State>, Extension(viewer): Extension, headers: HeaderMap, axum::Json(input): axum::Json, ) -> Result { if headers.contains_key(header::AUTHORIZATION) { return forgotten(&app, viewer, &input).await; } let did = logged_in(&app, viewer, &headers, "delete its knotless data")?; named(&did, &input)?; let ctx = crate::login::ctx_for(&app, &did); let limit = input.limit.unwrap_or(400).clamp(1, 1000); let step = account::delete_some(&ctx, limit).await; let step = step.map_err(AppError::upstream)?; if !step.done { return Ok(answer(step)); } crate::accounts::forget(&app, &did).await; let gw = crate::gateway::gateway(&app)?; let mut res = answer(step); crate::login::moved_on(&app, gw, &headers, &mut res); Ok(res) } /// Refuses a call naming another account than the one asking. fn named(did: &str, input: &DeleteInput) -> Result<()> { if input.did == did { return Ok(()); } let why = format!("{did} asks, but names {}", input.did); Err(Shown(StatusCode::FORBIDDEN, why)) } /// Forgets the account a service token names, once its PDS holds no /// knotless record: it deleted them itself. async fn forgotten(app: &App, viewer: Viewer, input: &DeleteInput) -> Result { let why = "ask with a service token, or logged in to the pages"; let did = viewer .0 .ok_or(Shown(StatusCode::UNAUTHORIZED, why.into()))?; named(&did, input)?; let holds = account::holds_any(&app.ctx, &did).await; if holds.map_err(AppError::upstream)? { let failure = Failure { error: "RecordsRemain".into(), message: format!("{did} still holds knotless records: delete them first"), }; return Ok((StatusCode::BAD_REQUEST, axum::Json(failure)).into_response()); } crate::accounts::forget(app, &did).await; Ok(answer(Step { deleted: 0, done: true, })) }