mirror of
https://github.com/narrrl/holy-flavor-rater.git
synced 2026-11-03 16:41:27 +01:00
fixed account deletion
This commit is contained in:
1 parent
2f2935a1cb
commit
296ae167fb
3 files changed
+45
-7
No files matched your search
@@ -492,10 +492,7 @@ async fn discover(
|
||||
cond = cond.add(rating::Column::FlavorId.is_in(flavor_ids));
|
||||
}
|
||||
|
||||
let count = Rating::find()
|
||||
.filter(cond.clone())
|
||||
.count(&state.db)
|
||||
.await?;
|
||||
let count = Rating::find().filter(cond.clone()).count(&state.db).await?;
|
||||
let mut query = Rating::find().filter(cond);
|
||||
query = if top {
|
||||
query
|
||||
|
||||
@@ -353,7 +353,12 @@ async fn suggested(
|
||||
// users_by_ids orders by id; restore the activity ranking afterwards.
|
||||
let users = users_by_ids(&state, &ranked).await?;
|
||||
let mut out = build_users(&state, &ctx, users, Some(uid)).await?;
|
||||
out.sort_by_key(|u| ranked.iter().position(|id| *id == u.id).unwrap_or(usize::MAX));
|
||||
out.sort_by_key(|u| {
|
||||
ranked
|
||||
.iter()
|
||||
.position(|id| *id == u.id)
|
||||
.unwrap_or(usize::MAX)
|
||||
});
|
||||
Ok(Json(out))
|
||||
}
|
||||
|
||||
@@ -1045,12 +1050,21 @@ async fn request_account_deletion(
|
||||
active.deletion_code_expires = Set(Some(expires));
|
||||
active.update(&state.db).await?;
|
||||
|
||||
let link = format!(
|
||||
"{}/settings?deletion_code={}",
|
||||
state.config.frontend_url, code
|
||||
);
|
||||
crate::email::spawn_mail(
|
||||
state.config.email.clone(),
|
||||
"Confirm Account Deletion - Holy Flavors Archive".into(),
|
||||
format!(
|
||||
"Hi {uname},\n\nYou requested to delete your account. \
|
||||
This action is permanent.\n\nYour deletion code is: {code}"
|
||||
This action is permanent and cannot be undone.\n\n\
|
||||
Your deletion code is: {code}\n\n\
|
||||
Or open this link (while signed in) to pre-fill the code, then \
|
||||
confirm the deletion on the page:\n{link}\n\n\
|
||||
If you didn't request this, you can safely ignore this email — \
|
||||
nothing will be deleted without confirmation."
|
||||
),
|
||||
vec![email],
|
||||
);
|
||||
@@ -1138,6 +1152,19 @@ pub(crate) async fn delete_user_cascade(state: &AppState, uid: i32) -> ApiResult
|
||||
"DELETE FROM api_user_following WHERE from_user_id = ?1 OR to_user_id = ?1",
|
||||
))
|
||||
.await?;
|
||||
// Legacy Django tables that still carry FKs to api_user: the DRF auth token,
|
||||
// the admin action log, and the (empty but constrained) group / permission
|
||||
// M2M join tables. SQLite blocks the user delete unless these go first.
|
||||
txn.execute(exec("DELETE FROM authtoken_token WHERE user_id = ?"))
|
||||
.await?;
|
||||
txn.execute(exec("DELETE FROM django_admin_log WHERE user_id = ?"))
|
||||
.await?;
|
||||
txn.execute(exec("DELETE FROM api_user_groups WHERE user_id = ?"))
|
||||
.await?;
|
||||
txn.execute(exec(
|
||||
"DELETE FROM api_user_user_permissions WHERE user_id = ?",
|
||||
))
|
||||
.await?;
|
||||
// Token blacklist tables reference the user via outstanding tokens.
|
||||
txn.execute(exec(
|
||||
"DELETE FROM token_blacklist_blacklistedtoken WHERE token_id IN \
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
Switch,
|
||||
} from '@mui/material';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useAuth } from '../hooks/useAuth';
|
||||
@@ -49,6 +49,7 @@ const readErr = (err: unknown): string | undefined =>
|
||||
const Settings: React.FC<SettingsProps> = ({ themeName, onThemeChange }) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const queryClient = useQueryClient();
|
||||
const { refetchUser } = useAuth();
|
||||
useTitle(t('nav.settings'));
|
||||
@@ -78,6 +79,19 @@ const Settings: React.FC<SettingsProps> = ({ themeName, onThemeChange }) => {
|
||||
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
||||
const [selectedBannerId, setSelectedBannerId] = useState<number | string>('');
|
||||
|
||||
// Deep link from the deletion email: ?deletion_code=NNNNNN pre-fills the code
|
||||
// and opens the confirm form. The user still has to click "Delete" — the link
|
||||
// is a convenience, not a one-click destruct. Strip the param after reading so
|
||||
// it doesn't linger in the URL / browser history.
|
||||
useEffect(() => {
|
||||
const code = searchParams.get('deletion_code');
|
||||
if (!code) return;
|
||||
setDeletionCode(code);
|
||||
setIsDeleting(true);
|
||||
searchParams.delete('deletion_code');
|
||||
setSearchParams(searchParams, { replace: true });
|
||||
}, [searchParams, setSearchParams]);
|
||||
|
||||
const handleGoBack = () => {
|
||||
if (window.history.length > 1) navigate(-1);
|
||||
else navigate('/');
|
||||
|
||||
Reference in new issue
Block a user