feat(account): unlink game accounts on the shard before an account is deleted (MODULE_API 1.20.0)
ACCOUNT_DELETION.md §4.4 (AD10, AD11). api.onUserDeleting queues every linked
account in the new shard_pending_unlinks table, then calls the sidecar's
DELETE /link/{account} (actor web:account-deletion) and drops the mirror on 200/404.
A shard that is away leaves the row pending, retried on WS reconnect and on a live
server.hello; a protected account (403) is refused and listed on Admin -> Shard as
still linked on the shard. A live mob.login without the deleted user's webId clears
the row. coreApi ^1.20.0; core-ref pinned to website#232's merge (7bd93b8).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -83,6 +83,39 @@ function StatusPanel({ config }) {
|
||||
)
|
||||
}
|
||||
|
||||
// ── Unlinks a deleted website account left behind ───────────────────────────
|
||||
//
|
||||
// ACCOUNT_DELETION.md §4.4. Deleting a website account unlinks its game accounts
|
||||
// on the shard first; what the shard could not take is listed here until it
|
||||
// clears. `pending` is retried by itself when the bridge reconnects. `refused` is
|
||||
// a protected staff account the shard will not unlink over the bridge, so the
|
||||
// shard's staff clear its WebsiteUserId tag by hand. A row goes on its own once a
|
||||
// login on that account no longer carries the deleted user's id.
|
||||
function PendingUnlinks({ rows }) {
|
||||
if (!rows || rows.length === 0) return null
|
||||
return (
|
||||
<section style={{ border: '1px solid var(--line)', borderRadius: 10, padding: 16, display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||
<h3 className="display" style={{ margin: 0, fontSize: '1.05rem', color: 'var(--head)' }}>Still linked on the shard</h3>
|
||||
<p className="sans dim" style={{ margin: 0, fontSize: '0.82rem' }}>
|
||||
Game accounts whose website account was deleted, but whose link the shard has not cleared yet.
|
||||
</p>
|
||||
<ul style={{ margin: 0, padding: 0, listStyle: 'none', display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
{rows.map((r) => (
|
||||
<li key={r.account} className="sans" style={{ fontSize: '0.85rem', color: 'var(--ink)' }}>
|
||||
<strong>{r.account}</strong>
|
||||
<span className="dim"> · deleted user #{r.webUserId} · {new Date(r.queuedAt).toLocaleString()}</span>
|
||||
<div className="dim" style={{ fontSize: '0.78rem', marginTop: 2 }}>
|
||||
{r.state === 'refused'
|
||||
? 'Protected staff account: the shard will not unlink it over the bridge. Clear its link on the shard by hand.'
|
||||
: `Waiting for the shard; tried ${r.attempts} time${r.attempts === 1 ? '' : 's'}, and tried again when the bridge reconnects.`}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
// ── Game-account signup ─────────────────────────────────────────────────────
|
||||
//
|
||||
// This field lived in core's Site Settings until slice 3 of the extraction. It
|
||||
@@ -285,6 +318,8 @@ export default function ShardAdmin() {
|
||||
|
||||
<StatusPanel config={config} />
|
||||
|
||||
<PendingUnlinks rows={config.pendingUnlinks} />
|
||||
|
||||
<Toggle checked={enabled} onChange={setEnabled} label="Enable the shard integration" />
|
||||
|
||||
<label style={{ display: 'block' }}>
|
||||
|
||||
Reference in New Issue
Block a user