the delete button that does not delete

September 7, 2026 meta gdprgoprivacy

The blog has user accounts now, which means it has user data, which means EU law and my own conscience agree that people need two buttons: one to download everything I have on them, and one to make it all go away. This is the story of building both, and of why the delete button does not actually delete.

The download button was the easy half. One endpoint that collects everything tied to your account from both services (your account details from auth, your comments, likes and reports from blog) and hands it back as one JSON file. Done, or so I thought. The code review caught that I had put it behind login but forgotten the rate limiter, so anyone with an account could hammer a fairly expensive endpoint. One middleware wrap later it was actually done.

The delete button was where it got interesting. My first instinct was the obvious one: delete the account, delete the comments, gone. But that breaks conversations. If you wrote a comment and three people replied to it, deleting your comment leaves their replies dangling in the air, answering nobody. The thread stops making sense for everyone who is not you.

The data tab in profile settings, with the download and delete buttons

So instead the blog seeds a shared account called anonymous at first startup, and deleting your account hands everything you wrote over to it. Your name comes off, the words stay. The comment thread still reads fine, it just says anonymous where your username used to be:

-- name: AnonymizeComments :execrows
-- Reattributes a deleted user's comments to the shared anonymous account.
UPDATE comments
SET author_id = sqlc.arg(anonymous_id)::uuid, author_username = 'anonymous'
WHERE author_id = sqlc.arg(user_id)::uuid;

That part worked on the first try. The likes did not. A like is a row saying "this user liked this comment", and the pair has to be unique. If you liked a comment that some earlier deleted user also liked, both likes now want to become "anonymous liked this comment", and the database rightly refuses the duplicate. The fix is to delete the colliding likes first (the count on the comment stays honest, anonymous already liked it) and then reattribute the rest.

Ordering mattered too. The blog service anonymizes first, then the auth service deletes the account. If it crashes in the middle you are left as a user whose content is already anonymous, which is safe and can just be retried. The other order could delete your account and leave your comments orphaned under a user id that no longer exists.

Two guards went in at the end: the anonymous account cannot delete itself (that would be a fun bug), and the last admin cannot delete themselves either, because a blog where nobody can log into the admin panel is a very quiet blog.

I tested the whole thing with a throwaway account: registered, commented, downloaded my data, pressed delete. The comment is still there, signed anonymous, and the login stopped working. Exactly what the button promises, even if it is not exactly what it says.

0 comments

Log in to comment.

Log in

No account?