summaryrefslogtreecommitdiff
path: root/Hutch/Views/Settings/SettingsViewModel.swift
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-07-15 23:52:45 -0500
committerChristian Cleberg <[email protected]>2026-07-15 23:52:45 -0500
commitf3627deef0848b95a083d9e16468b0f8484d784e (patch)
treea30ee4dc46bd2e575820316b857dd08cfa9a53c2 /Hutch/Views/Settings/SettingsViewModel.swift
parentc4930f31ffc5d7c5de5eeafd3da184c7691f8ab7 (diff)
downloadhutch-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.swift42
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
+ }
+ }
}