Skip to content

Commit 0ffe5d7

Browse files
committed
Store sync date during insert transaction
1 parent e4c2521 commit 0ffe5d7

9 files changed

+122
-336
lines changed

Modules/Sources/Storage/GRDB/Model/PersistedSite.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ public struct PersistedSite: Codable {
66
// periphery:ignore - TODO: remove ignore when populating database
77
public let id: Int64
88
// periphery:ignore - TODO: remove ignore when populating database
9-
public let lastCatalogIncrementalSyncDate: Date?
9+
public var lastCatalogIncrementalSyncDate: Date?
1010
// periphery:ignore - TODO: remove ignore when populating database
11-
public let lastCatalogFullSyncDate: Date?
11+
public var lastCatalogFullSyncDate: Date?
1212

1313
// periphery:ignore - TODO: remove ignore when populating database
1414
public init(id: Int64, lastCatalogIncrementalSyncDate: Date? = nil, lastCatalogFullSyncDate: Date? = nil) {
@@ -24,11 +24,11 @@ extension PersistedSite: FetchableRecord, PersistableRecord {
2424

2525
public enum Columns {
2626
// periphery:ignore - TODO: remove ignore when populating database
27-
static let id = Column(CodingKeys.id)
27+
public static let id = Column(CodingKeys.id)
2828
// periphery:ignore - TODO: remove ignore when populating database
29-
static let lastCatalogIncrementalSyncDate = Column(CodingKeys.lastCatalogIncrementalSyncDate)
29+
public static let lastCatalogIncrementalSyncDate = Column(CodingKeys.lastCatalogIncrementalSyncDate)
3030
// periphery:ignore - TODO: remove ignore when populating database
31-
static let lastCatalogFullSyncDate = Column(CodingKeys.lastCatalogFullSyncDate)
31+
public static let lastCatalogFullSyncDate = Column(CodingKeys.lastCatalogFullSyncDate)
3232
}
3333
}
3434

Modules/Sources/Yosemite/Tools/POS/POSCatalogFullSyncService.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import Foundation
22
import protocol Networking.POSCatalogSyncRemoteProtocol
33
import class Networking.AlamofireNetwork
44
import class Networking.POSCatalogSyncRemote
5-
import CocoaLumberjackSwift
65
import Storage
76

87
// TODO - remove the periphery ignore comment when the catalog is integrated with POS.
@@ -20,6 +19,7 @@ public protocol POSCatalogFullSyncServiceProtocol {
2019
public struct POSCatalog {
2120
public let products: [POSProduct]
2221
public let variations: [POSProductVariation]
22+
public let syncDate: Date
2323
}
2424

2525
// TODO - remove the periphery ignore comment when the service is integrated with POS.
@@ -74,6 +74,7 @@ public final class POSCatalogFullSyncService: POSCatalogFullSyncServiceProtocol
7474

7575
private extension POSCatalogFullSyncService {
7676
func loadCatalog(for siteID: Int64, syncRemote: POSCatalogSyncRemoteProtocol) async throws -> POSCatalog {
77+
let syncStartDate = Date.now
7778
// Loads products and variations in batches in parallel.
7879
async let productsTask = batchedLoader.loadAll(
7980
makeRequest: { pageNumber in
@@ -87,7 +88,7 @@ private extension POSCatalogFullSyncService {
8788
)
8889

8990
let (products, variations) = try await (productsTask, variationsTask)
90-
return POSCatalog(products: products, variations: variations)
91+
return POSCatalog(products: products, variations: variations, syncDate: syncStartDate)
9192
}
9293

9394
}

Modules/Sources/Yosemite/Tools/POS/POSCatalogIncrementalSyncService.swift

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import Foundation
22
import protocol Networking.POSCatalogSyncRemoteProtocol
33
import class Networking.AlamofireNetwork
44
import class Networking.POSCatalogSyncRemote
5-
import CocoaLumberjackSwift
65
import protocol Storage.GRDBManagerProtocol
76

87
// TODO - remove the periphery ignore comment when the service is integrated with POS.
@@ -12,7 +11,7 @@ public protocol POSCatalogIncrementalSyncServiceProtocol {
1211
/// - Parameters:
1312
/// - siteID: The site ID to sync catalog for.
1413
/// - lastFullSyncDate: The date of the last full sync to use if no incremental sync date exists.
15-
func startIncrementalSync(for siteID: Int64, lastFullSyncDate: Date) async throws
14+
func startIncrementalSync(for siteID: Int64, lastFullSyncDate: Date, lastIncrementalSyncDate: Date?) async throws
1615
}
1716

1817
// TODO - remove the periphery ignore comment when the service is integrated with POS.
@@ -43,21 +42,18 @@ public final class POSCatalogIncrementalSyncService: POSCatalogIncrementalSyncSe
4342

4443
// MARK: - Protocol Conformance
4544

46-
public func startIncrementalSync(for siteID: Int64, lastFullSyncDate: Date) async throws {
47-
let modifiedAfter = try await latestSyncDate(siteID: siteID, lastFullSyncDate: lastFullSyncDate)
45+
public func startIncrementalSync(for siteID: Int64, lastFullSyncDate: Date, lastIncrementalSyncDate: Date?) async throws {
46+
let modifiedAfter = latestSyncDate(fullSyncDate: lastFullSyncDate, incrementalSyncDate: lastIncrementalSyncDate)
4847

4948
DDLogInfo("🔄 Starting incremental catalog sync for site ID: \(siteID), modifiedAfter: \(modifiedAfter)")
5049

5150
do {
52-
let syncStartDate = Date()
5351
let catalog = try await loadCatalog(for: siteID, modifiedAfter: modifiedAfter, syncRemote: syncRemote)
5452
DDLogInfo("✅ Loaded \(catalog.products.count) products and \(catalog.variations.count) variations for siteID \(siteID)")
5553

5654
try await persistenceService.persistIncrementalCatalogData(catalog, siteID: siteID)
5755
DDLogInfo("✅ Persisted \(catalog.products.count) products and \(catalog.variations.count) variations to database for siteID \(siteID)")
5856

59-
try await persistenceService.updateSite(.init(siteID: siteID, lastIncrementalSyncDate: syncStartDate))
60-
DDLogInfo("✅ Updated last incremental sync date to \(syncStartDate) for siteID \(siteID)")
6157
} catch {
6258
DDLogError("❌ Failed to sync and persist catalog incrementally: \(error)")
6359
throw error
@@ -69,6 +65,7 @@ public final class POSCatalogIncrementalSyncService: POSCatalogIncrementalSyncSe
6965

7066
private extension POSCatalogIncrementalSyncService {
7167
func loadCatalog(for siteID: Int64, modifiedAfter: Date, syncRemote: POSCatalogSyncRemoteProtocol) async throws -> POSCatalog {
68+
let syncStartDate = Date.now
7269
async let productsTask = batchedLoader.loadAll(
7370
makeRequest: { pageNumber in
7471
try await syncRemote.loadProducts(modifiedAfter: modifiedAfter, siteID: siteID, pageNumber: pageNumber)
@@ -81,14 +78,14 @@ private extension POSCatalogIncrementalSyncService {
8178
)
8279

8380
let (products, variations) = try await (productsTask, variationsTask)
84-
return POSCatalog(products: products, variations: variations)
81+
return POSCatalog(products: products, variations: variations, syncDate: syncStartDate)
8582
}
8683
}
8784

8885
// MARK: - Sync date
8986

9087
private extension POSCatalogIncrementalSyncService {
91-
func latestSyncDate(siteID: Int64, lastFullSyncDate: Date) async throws -> Date {
92-
try await persistenceService.loadSite(siteID: siteID)?.lastIncrementalSyncDate ?? lastFullSyncDate
88+
func latestSyncDate(fullSyncDate: Date, incrementalSyncDate: Date?) -> Date {
89+
max(fullSyncDate, incrementalSyncDate ?? .distantPast)
9390
}
9491
}

Modules/Sources/Yosemite/Tools/POS/POSCatalogPersistenceService.swift

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@ import Foundation
33
import Storage
44
import GRDB
55

6-
enum POSCatalogPersistenceError: Error, Equatable {
7-
case siteNotFound(siteID: Int64)
8-
}
9-
106
protocol POSCatalogPersistenceServiceProtocol {
117
/// Clears existing data and persists new catalog data
128
/// - Parameters:
@@ -19,15 +15,6 @@ protocol POSCatalogPersistenceServiceProtocol {
1915
/// - catalog: The catalog difference to persist
2016
/// - siteID: The site ID to associate the catalog with
2117
func persistIncrementalCatalogData(_ catalog: POSCatalog, siteID: Int64) async throws
22-
23-
/// Loads the POS site for the given site ID
24-
/// - Parameter siteID: The site ID to load the POSSite for
25-
/// - Returns: The loaded POSSite or nil if not found in storage
26-
func loadSite(siteID: Int64) async throws -> POSSite?
27-
28-
/// Updates the PersistedSite based on POSSite data
29-
/// - Parameter site: The POSSite containing the updated data
30-
func updateSite(_ site: POSSite) async throws
3118
}
3219

3320
final class POSCatalogPersistenceService: POSCatalogPersistenceServiceProtocol {
@@ -45,7 +32,7 @@ final class POSCatalogPersistenceService: POSCatalogPersistenceServiceProtocol {
4532
// currently, we can't save for more than one site as entity IDs are not namespaced.
4633
try PersistedSite.deleteAll(db)
4734

48-
let site = PersistedSite(id: siteID)
35+
let site = PersistedSite(id: siteID, lastCatalogFullSyncDate: catalog.syncDate)
4936
try site.insert(db)
5037

5138
for product in catalog.productsToPersist {
@@ -132,6 +119,9 @@ final class POSCatalogPersistenceService: POSCatalogPersistenceServiceProtocol {
132119
for var attribute in catalog.variationAttributesToPersist {
133120
try attribute.insert(db, onConflict: .replace)
134121
}
122+
123+
var site = try PersistedSite.fetchOne(db, key: siteID)
124+
try site?.updateChanges(db) { $0.lastCatalogIncrementalSyncDate = catalog.syncDate }
135125
}
136126

137127
DDLogInfo("✅ Incremental catalog persistence complete")
@@ -149,23 +139,6 @@ final class POSCatalogPersistenceService: POSCatalogPersistenceServiceProtocol {
149139
"\(variationImageCount) variation images, \(variationAttributeCount) variation attributes")
150140
}
151141
}
152-
153-
func loadSite(siteID: Int64) async throws -> POSSite? {
154-
try await grdbManager.databaseConnection.read { db in
155-
try PersistedSite.filter(key: siteID).fetchOne(db)?.toPOSSite()
156-
}
157-
}
158-
159-
func updateSite(_ site: POSSite) async throws {
160-
try await grdbManager.databaseConnection.write { db in
161-
guard try PersistedSite.filter(key: site.siteID).fetchOne(db) != nil else {
162-
throw POSCatalogPersistenceError.siteNotFound(siteID: site.siteID)
163-
}
164-
165-
let persistedSite = PersistedSite(from: site)
166-
try persistedSite.update(db)
167-
}
168-
}
169142
}
170143

171144
private extension POSCatalog {

Modules/Sources/Yosemite/Tools/POS/POSCatalogSyncCoordinator.swift

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -62,22 +62,6 @@ public actor POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol {
6262

6363
let catalog = try await fullSyncService.startFullSync(for: siteID)
6464

65-
// Update the site with the full sync date
66-
let currentSite = try await persistenceService.loadSite(siteID: siteID)
67-
let updatedSite = POSSite(
68-
siteID: siteID,
69-
lastIncrementalSyncDate: currentSite?.lastIncrementalSyncDate,
70-
lastFullSyncDate: Date()
71-
)
72-
73-
do {
74-
try await persistenceService.updateSite(updatedSite)
75-
} catch POSCatalogPersistenceError.siteNotFound {
76-
// Site doesn't exist yet, this could happen if sync coordinator is called before replaceAllCatalogData
77-
// In this case, the full sync service should have handled the catalog persistence already
78-
DDLogInfo("🟡 POSCatalogSyncCoordinator: Site \(siteID) not found during sync timestamp update - sync timestamp will be set during catalog persistence")
79-
}
80-
8165
DDLogInfo("✅ POSCatalogSyncCoordinator completed full sync for site \(siteID)")
8266
}
8367

@@ -108,7 +92,9 @@ public actor POSCatalogSyncCoordinator: POSCatalogSyncCoordinatorProtocol {
10892

10993
private func lastFullSyncDate(for siteID: Int64) async -> Date? {
11094
do {
111-
return try await persistenceService.loadSite(siteID: siteID)?.lastFullSyncDate
95+
return try await grdbManager.databaseConnection.read { db in
96+
return try PersistedSite.filter(key: siteID).fetchOne(db)?.lastCatalogFullSyncDate
97+
}
11298
} catch {
11399
DDLogError("⛔️ POSCatalogSyncCoordinator: Error loading site \(siteID) for full sync date: \(error)")
114100
return nil

Modules/Tests/YosemiteTests/Mocks/MockPOSCatalogPersistenceService.swift

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,6 @@ final class MockPOSCatalogPersistenceService: POSCatalogPersistenceServiceProtoc
77
private(set) var persistIncrementalCatalogDataLastPersistedCatalog: POSCatalog?
88
var persistIncrementalCatalogDataError: Error?
99

10-
// MARK: - loadSite tracking
11-
var loadSiteResult: Result<POSSite?, Error> = .success(nil)
12-
private(set) var loadSiteCallCount = 0
13-
14-
// Track specific sites for multi-site tests
15-
var siteResults: [Int64: POSSite] = [:]
16-
17-
// MARK: - updateSite tracking
18-
private(set) var updateSiteCallCount = 0
19-
private(set) var lastUpdatedSite: POSSite?
20-
21-
// Internal storage for updated sites
22-
private var storedSites: [Int64: POSSite] = [:]
23-
2410
// MARK: - Protocol Implementation
2511

2612
func replaceAllCatalogData(_ catalog: POSCatalog, siteID: Int64) async throws {
@@ -35,32 +21,4 @@ final class MockPOSCatalogPersistenceService: POSCatalogPersistenceServiceProtoc
3521
throw error
3622
}
3723
}
38-
39-
func loadSite(siteID: Int64) async throws -> POSSite? {
40-
loadSiteCallCount += 1
41-
42-
// Check if we have a stored site from updateSite calls
43-
if let storedSite = storedSites[siteID] {
44-
return storedSite
45-
}
46-
47-
// Check if we have a specific result for this site
48-
if let specificSite = siteResults[siteID] {
49-
return specificSite
50-
}
51-
52-
// Fall back to configured result
53-
switch loadSiteResult {
54-
case .success(let site):
55-
return site
56-
case .failure(let error):
57-
throw error
58-
}
59-
}
60-
61-
func updateSite(_ site: POSSite) async throws {
62-
updateSiteCallCount += 1
63-
lastUpdatedSite = site
64-
storedSites[site.siteID] = site
65-
}
6624
}

Modules/Tests/YosemiteTests/Tools/POS/POSCatalogIncrementalSyncServiceTests.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ struct POSCatalogIncrementalSyncServiceTests {
2828
mockSyncRemote.setIncrementalVariationResult(pageNumber: 1, result: .success(PagedItems(items: expectedVariations, hasMorePages: false, totalItems: 0)))
2929

3030
// When
31-
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastFullSyncDate)
31+
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastFullSyncDate, lastIncrementalSyncDate: nil)
3232

3333
// Then
3434
#expect(mockSyncRemote.loadIncrementalProductsCallCount == 2)
@@ -46,10 +46,10 @@ struct POSCatalogIncrementalSyncServiceTests {
4646
// First sync to establish incremental date.
4747
mockSyncRemote.setIncrementalProductResult(pageNumber: 1, result: .success(PagedItems(items: [], hasMorePages: false, totalItems: 0)))
4848
mockSyncRemote.setIncrementalVariationResult(pageNumber: 1, result: .success(PagedItems(items: [], hasMorePages: false, totalItems: 0)))
49-
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastIncrementalDate)
49+
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastIncrementalDate, lastIncrementalSyncDate: lastIncrementalDate)
5050

5151
// When
52-
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastFullSyncDate)
52+
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastFullSyncDate, lastIncrementalSyncDate: lastIncrementalDate)
5353

5454
// Then
5555
#expect(mockSyncRemote.lastIncrementalProductsModifiedAfter != lastFullSyncDate)
@@ -73,7 +73,7 @@ struct POSCatalogIncrementalSyncServiceTests {
7373
mockSyncRemote.setIncrementalVariationResult(pageNumber: 1, result: .success(PagedItems(items: [], hasMorePages: false, totalItems: 0)))
7474

7575
// When
76-
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastFullSyncDate)
76+
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastFullSyncDate, lastIncrementalSyncDate: nil)
7777

7878
// Then
7979
#expect(mockSyncRemote.loadIncrementalProductsCallCount == 4)
@@ -94,7 +94,7 @@ struct POSCatalogIncrementalSyncServiceTests {
9494
])
9595

9696
// When
97-
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastFullSyncDate)
97+
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastFullSyncDate, lastIncrementalSyncDate: nil)
9898

9999
// Then
100100
#expect(mockSyncRemote.loadIncrementalProductVariationsCallCount == 2)
@@ -114,13 +114,13 @@ struct POSCatalogIncrementalSyncServiceTests {
114114

115115
// When/Then
116116
await #expect(throws: expectedError) {
117-
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastFullSyncDate)
117+
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastFullSyncDate, lastIncrementalSyncDate: nil)
118118
}
119119
#expect(mockPersistenceService.persistIncrementalCatalogDataCallCount == 0)
120120

121121
// When attempting a second sync
122122
mockSyncRemote.setIncrementalProductResult(pageNumber: 1, result: .success(PagedItems(items: [], hasMorePages: false, totalItems: 0)))
123-
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastFullSyncDate)
123+
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastFullSyncDate, lastIncrementalSyncDate: nil)
124124

125125
// Then it uses lastFullSyncDate since no incremental date was stored due to previous failure
126126
#expect(mockSyncRemote.lastIncrementalProductsModifiedAfter == lastFullSyncDate)
@@ -138,13 +138,13 @@ struct POSCatalogIncrementalSyncServiceTests {
138138

139139
// When/Then
140140
await #expect(throws: Error.self) {
141-
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastFullSyncDate)
141+
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastFullSyncDate, lastIncrementalSyncDate: nil)
142142
}
143143
#expect(mockPersistenceService.persistIncrementalCatalogDataCallCount == 1)
144144

145145
// When attempting a second sync
146146
mockPersistenceService.persistIncrementalCatalogDataError = nil // Clear the error
147-
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastFullSyncDate)
147+
try await sut.startIncrementalSync(for: sampleSiteID, lastFullSyncDate: lastFullSyncDate, lastIncrementalSyncDate: nil)
148148

149149
// Then it uses lastFullSyncDate since no incremental date was stored due to previous persistence failure
150150
#expect(mockSyncRemote.lastIncrementalProductsModifiedAfter == lastFullSyncDate)
@@ -163,11 +163,11 @@ struct POSCatalogIncrementalSyncServiceTests {
163163
mockSyncRemote.setIncrementalVariationResult(pageNumber: 1, result: .success(PagedItems(items: [], hasMorePages: false, totalItems: 0)))
164164

165165
// When - Sync site 1
166-
try await sut.startIncrementalSync(for: site1ID, lastFullSyncDate: lastFullSyncDate)
166+
try await sut.startIncrementalSync(for: site1ID, lastFullSyncDate: lastFullSyncDate, lastIncrementalSyncDate: nil)
167167
let site1ModifiedAfter = try #require(mockSyncRemote.lastIncrementalProductsModifiedAfter)
168168

169169
// When - Sync site 2
170-
try await sut.startIncrementalSync(for: site2ID, lastFullSyncDate: lastFullSyncDate)
170+
try await sut.startIncrementalSync(for: site2ID, lastFullSyncDate: lastFullSyncDate, lastIncrementalSyncDate: nil)
171171
let site2ModifiedAfter = try #require(mockSyncRemote.lastIncrementalProductsModifiedAfter)
172172

173173
#expect(site1ModifiedAfter == lastFullSyncDate)

0 commit comments

Comments
 (0)