Skip to content

Commit 8cd698d

Browse files
committed
fix: migrate legacy backups missing wallet id on restore
Backups written before activity data became wallet-scoped have no wallet id on their activity, tag and pre-activity-metadata records. With bitkit-core 0.4.0 those records require a wallet id, so decoding such a backup throws before the records reach Core. Before decoding a restored envelope, hand each Core-owned array to the matching bitkit-core migration helper (migrateBackupActivitiesJson, migrateBackupActivityTagsJson, migrateBackupPreActivityMetadataJson), which fills in the default wallet id for records that lack one and preserves any existing id. The app never edits Core model JSON itself, and current backups that already carry a wallet id are unaffected. Bumps bitkit-core to 0.4.0.
1 parent aea4314 commit 8cd698d

3 files changed

Lines changed: 34 additions & 5 deletions

File tree

Bitkit.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1201,7 +1201,7 @@
12011201
repositoryURL = "https://github.com/synonymdev/bitkit-core";
12021202
requirement = {
12031203
kind = exactVersion;
1204-
version = 0.3.9;
1204+
version = 0.4.0;
12051205
};
12061206
};
12071207
96E20CD22CB6D91A00C24149 /* XCRemoteSwiftPackageReference "CodeScanner" */ = {

Bitkit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Bitkit/Services/BackupService.swift

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,11 @@ class BackupService {
211211
}
212212

213213
try await performRestore(category: .activity) { dataBytes in
214-
let payload = try JSONDecoder().decode(ActivityBackupV1.self, from: dataBytes)
214+
let migrated = try self.migrateCoreOwnedBackupFields(dataBytes, fieldMigrations: [
215+
"activities": migrateBackupActivitiesJson,
216+
"activityTags": migrateBackupActivityTagsJson,
217+
])
218+
let payload = try JSONDecoder().decode(ActivityBackupV1.self, from: migrated)
215219

216220
try await CoreService.shared.activity.upsertList(payload.activities)
217221
try await CoreService.shared.activity.upsertTags(payload.activityTags)
@@ -224,7 +228,10 @@ class BackupService {
224228
}
225229

226230
try await performRestore(category: .metadata) { dataBytes in
227-
let payload = try JSONDecoder().decode(MetadataBackupV1.self, from: dataBytes)
231+
let migrated = try self.migrateCoreOwnedBackupFields(dataBytes, fieldMigrations: [
232+
"tagMetadata": migrateBackupPreActivityMetadataJson,
233+
])
234+
let payload = try JSONDecoder().decode(MetadataBackupV1.self, from: migrated)
228235

229236
try await CoreService.shared.activity.upsertPreActivityMetadata(payload.tagMetadata)
230237

@@ -739,6 +746,28 @@ class BackupService {
739746
}
740747
}
741748

749+
/// Fills in wallet ids that predate wallet-scoped activity data before a
750+
/// backup envelope is decoded. Each Core-owned array field is handed to the
751+
/// matching Core migration helper as raw JSON, so the app never edits Core
752+
/// model JSON itself. Records that already carry a wallet id are left
753+
/// unchanged, so this is safe to run on current backups too.
754+
private func migrateCoreOwnedBackupFields(
755+
_ dataBytes: Data,
756+
fieldMigrations: [String: (String) throws -> String]
757+
) throws -> Data {
758+
guard var root = try JSONSerialization.jsonObject(with: dataBytes) as? [String: Any] else {
759+
return dataBytes
760+
}
761+
for (field, migrate) in fieldMigrations {
762+
guard let array = root[field] as? [Any] else { continue }
763+
let arrayData = try JSONSerialization.data(withJSONObject: array)
764+
let migratedJson = try migrate(String(decoding: arrayData, as: UTF8.self))
765+
guard let migratedData = migratedJson.data(using: .utf8) else { continue }
766+
root[field] = try JSONSerialization.jsonObject(with: migratedData)
767+
}
768+
return try JSONSerialization.data(withJSONObject: root)
769+
}
770+
742771
private func performRestore(category: BackupCategory, restoreAction: (Data) async throws -> Void) async throws {
743772
do {
744773
let item = try await vssBackupClient.getObject(key: category.rawValue)

0 commit comments

Comments
 (0)