feat(admin): deleted-account dossier + operator delete-user action
The user-detail console gains a Deletion & retention panel: last login (time + IP), the tombstone (deleted-at + retained real name), and the retention journal (the legal dossier of detached credentials). A Delete-user action runs the same deletion orchestration as the in-app flow (mirrors the email-erase pattern). Store readers RetainedIdentities + DeletionInfo back the view; integration test covers them.
This commit is contained in:
@@ -107,6 +107,24 @@
|
||||
</form>
|
||||
{{end}}
|
||||
</section>
|
||||
<section class="panel"><h2>Deletion & retention</h2>
|
||||
{{if .LastLoginAt}}<p class="note">Last login: {{.LastLoginAt}}{{if .LastLoginIP}} — <code>{{.LastLoginIP}}</code>{{end}}</p>{{end}}
|
||||
{{if .Deleted}}<p><span class="warn">Deleted</span> at {{.DeletedAt}}{{if .DeletedName}} — was <code>{{.DeletedName}}</code>{{end}}</p>{{end}}
|
||||
{{if .Retained}}
|
||||
<h3>Retention journal (legal dossier of detached credentials)</h3>
|
||||
<table class="list">
|
||||
<thead><tr><th>Kind</th><th>Credential</th><th>Reason</th><th>Detached</th></tr></thead>
|
||||
<tbody>
|
||||
{{range .Retained}}<tr><td>{{.Kind}}</td><td><code>{{.ExternalID}}</code></td><td>{{.Reason}}</td><td>{{.DetachedAt}}</td></tr>{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
{{if not .Deleted}}{{if not .Guest}}
|
||||
<form class="form" method="post" action="/_gm/users/{{.ID}}/delete" onsubmit="return confirm('Delete this account? Its credentials are journalled and freed, its data anonymised, and its sessions revoked. This cannot be undone.')">
|
||||
<button type="submit">Delete user</button>
|
||||
</form>
|
||||
{{end}}{{end}}
|
||||
</section>
|
||||
<section class="panel"><h2>Friends</h2>
|
||||
<table class="list">
|
||||
<thead><tr><th>Account</th><th>Friends since</th></tr></thead>
|
||||
|
||||
@@ -151,6 +151,15 @@ type UserDetailView struct {
|
||||
// MergedInto is the primary account id when this account has been retired by a
|
||||
// merge, or empty for a live account.
|
||||
MergedInto string
|
||||
// The account-deletion dossier. Deleted marks a tombstoned account; DeletedAt and
|
||||
// DeletedName are its deletion time and retained real name; LastLoginAt/IP are the
|
||||
// last cold-load stamp (shown for any account); Retained is the credential journal.
|
||||
Deleted bool
|
||||
DeletedAt string
|
||||
DeletedName string
|
||||
LastLoginAt string
|
||||
LastLoginIP string
|
||||
Retained []RetainedRow
|
||||
// FlaggedHighRateAt is the pre-formatted soft high-rate marker timestamp,
|
||||
// empty for an unflagged account; the card shows it with the Clear action.
|
||||
FlaggedHighRateAt string
|
||||
@@ -237,6 +246,17 @@ type IdentityRow struct {
|
||||
CreatedAt string
|
||||
}
|
||||
|
||||
// RetainedRow is one credential in the account-deletion retention journal (the legal
|
||||
// dossier of detached credentials): what was detached, when, and why.
|
||||
type RetainedRow struct {
|
||||
Kind string
|
||||
ExternalID string
|
||||
Reason string
|
||||
Confirmed bool
|
||||
LinkedAt string
|
||||
DetachedAt string
|
||||
}
|
||||
|
||||
// GameRow is one game row in a list.
|
||||
type GameRow struct {
|
||||
ID string
|
||||
|
||||
Reference in New Issue
Block a user