Delete guest accounts left idle for five days
The app had no cleanup of any kind: in multi-user mode every first visit mints a users row, so the demo has been accumulating one permanent account per visitor along with everything they generated. cleanup.py sweeps guests idle for AIDND_GUEST_RETENTION_DAYS (default 5), once at startup and then every few hours. Startup is the load-bearing trigger — the free tier sleeps after ~15 minutes, so a long timer rarely gets to fire. Idle is COALESCE(last_seen_at, created_at), not last_seen_at: _touch only writes that column hourly, and a guest minted by /auth/me has it NULL until its second request, so the simpler query would have deleted brand-new visitors mid-session. It's one Core DELETE rather than db.delete(user), which would SELECT every adventure, action and memory into Python purely to delete them — the same egress pattern as the 189x fix. Every FK from users down is ON DELETE CASCADE, so the database does the whole graph and returns a count. The filter requires is_guest AND email IS NULL, so registered users (who upgrade in place) and local mode's implicit user are both out of reach, and is_public is output-only so a guest can never own content another user can see. Session cookies have no expiry and can outlive a swept row; that path 401s and the frontend's existing retry re-mints a session. Guests are told: /auth/me serves guest_retention_days and the signup modal states the window, sourced from the server so it can't drift from what is enforced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CYEJKobJ2Re4Dv7qUoSA7
This commit is contained in:
co-authored by
Claude Opus 5
parent
c500203270
commit
bbcb07c6be
@@ -68,7 +68,12 @@ export default function App() {
|
||||
<div className="nav-account">
|
||||
{me.is_guest ? (
|
||||
<>
|
||||
<span className="guest-nudge">Playing as guest — sign up to keep your adventures</span>
|
||||
<span className="guest-nudge"
|
||||
title={me.guest_retention_days
|
||||
? `Guest adventures are deleted after ${me.guest_retention_days} days without a visit.`
|
||||
: undefined}>
|
||||
Playing as guest — sign up to keep your adventures
|
||||
</span>
|
||||
<button onClick={() => setAuthMode('login')}>Log in</button>
|
||||
<button className="primary" onClick={() => setAuthMode('register')}>Sign up</button>
|
||||
</>
|
||||
@@ -84,7 +89,8 @@ export default function App() {
|
||||
</nav>
|
||||
<Outlet context={{ me, setMe }} />
|
||||
{authMode && (
|
||||
<AuthModal mode={authMode} onClose={() => setAuthMode(null)} onAuthed={onAuthed} />
|
||||
<AuthModal mode={authMode} onClose={() => setAuthMode(null)} onAuthed={onAuthed}
|
||||
retentionDays={me?.guest_retention_days} />
|
||||
)}
|
||||
</ToastHost>
|
||||
)
|
||||
|
||||
@@ -234,7 +234,7 @@ export function PlaceholderModal({ title, names, onSubmit, onCancel }) {
|
||||
|
||||
// Phase 8: register/login for the hosted multi-user mode. `onAuthed(me)` gets
|
||||
// the fresh /auth/me payload after success.
|
||||
export function AuthModal({ mode: initialMode, onClose, onAuthed }) {
|
||||
export function AuthModal({ mode: initialMode, onClose, onAuthed, retentionDays }) {
|
||||
const [mode, setMode] = useState(initialMode || 'register')
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
@@ -283,6 +283,12 @@ export function AuthModal({ mode: initialMode, onClose, onAuthed }) {
|
||||
{registering
|
||||
? 'Everything you’ve played as a guest stays with your new account, and you can pick it up from any device.'
|
||||
: 'Log in to reach your adventures.'}
|
||||
{/* Guest data really is deleted, so say so where the decision is
|
||||
being made. The window comes from the server (see cleanup.py) so
|
||||
it can't drift from what's enforced. */}
|
||||
{registering && retentionDays ? (
|
||||
<> Guest adventures are deleted after {retentionDays} days without a visit.</>
|
||||
) : null}
|
||||
</p>
|
||||
|
||||
<div className="auth-tabs" role="tablist">
|
||||
|
||||
Reference in New Issue
Block a user