diff options
| author | Christian Cleberg <[email protected]> | 2026-07-15 23:52:45 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-07-15 23:52:45 -0500 |
| commit | f3627deef0848b95a083d9e16468b0f8484d784e (patch) | |
| tree | a30ee4dc46bd2e575820316b857dd08cfa9a53c2 /Hutch/Views/Settings/SettingsViewModel.swift | |
| parent | c4930f31ffc5d7c5de5eeafd3da184c7691f8ab7 (diff) | |
| download | hutch-f3627deef0848b95a083d9e16468b0f8484d784e.tar.gz hutch-f3627deef0848b95a083d9e16468b0f8484d784e.tar.bz2 hutch-f3627deef0848b95a083d9e16468b0f8484d784e.zip | |
feat: show the meta.sr.ht audit log
auditLog existed in the API but was never called, so the record of what has
happened to your account — logins, key changes, the addresses they came from —
was web-only.
Sits under the tokens in Profile and loads on demand for the same reason they
do: an audit log is something you go looking for, not something worth a request
on every profile view. One page, newest first; the archive stays on meta.sr.ht.
Its errors are kept separate from the shared `error` so a failed audit fetch
cannot bury a profile save failure, and vice versa.
Diffstat (limited to 'Hutch/Views/Settings/SettingsViewModel.swift')
| -rw-r--r-- | Hutch/Views/Settings/SettingsViewModel.swift | 42 |
1 files changed, 42 insertions, 0 deletions
diff --git a/Hutch/Views/Settings/SettingsViewModel.swift b/Hutch/Views/Settings/SettingsViewModel.swift index edfaad4..8536e60 100644 --- a/Hutch/Views/Settings/SettingsViewModel.swift +++ b/Hutch/Views/Settings/SettingsViewModel.swift @@ -43,6 +43,15 @@ private struct PATListResponse: Decodable, Sendable { let personalAccessTokens: [PersonalAccessToken] } +private struct AuditLogResponse: Decodable, Sendable { + let auditLog: AuditLogPage +} + +private struct AuditLogPage: Decodable, Sendable { + let results: [AuditLogEntry] + let cursor: String? +} + // MARK: - View Model @Observable @@ -53,6 +62,11 @@ final class SettingsViewModel { private(set) var sshKeys: [SSHKey] = [] private(set) var pgpKeys: [PGPKey] = [] private(set) var personalAccessTokens: [PersonalAccessToken] = [] + private(set) var auditLog: [AuditLogEntry] = [] + private(set) var isLoadingAuditLog = false + /// Kept apart from `error` so a failed audit fetch cannot bury a profile + /// save failure, and vice versa. + var auditLogError: String? private(set) var isLoading = false private(set) var isLoadingPATs = false @@ -139,6 +153,12 @@ final class SettingsViewModel { } """ + private static let auditLogQuery = """ + query auditLog { + auditLog { results { id created ipAddress eventType details } } + } + """ + private static let personalAccessTokensQuery = """ query personalAccessTokens { personalAccessTokens { id issued expires comment grants } @@ -393,4 +413,26 @@ final class SettingsViewModel { isLoadingPATs = false } + // MARK: - Audit Log + + /// Loads the most recent audit entries. + /// + /// Deliberately one page: this is a glanceable "has anything happened to my + /// account" surface, not an archive. The full log is on meta.sr.ht. + func loadAuditLog() async { + guard !isLoadingAuditLog else { return } + isLoadingAuditLog = true + defer { isLoadingAuditLog = false } + + do { + let result = try await client.execute( + service: .meta, + query: Self.auditLogQuery, + responseType: AuditLogResponse.self + ) + auditLog = result.auditLog.results + } catch { + auditLogError = error.userFacingMessage + } + } } |
