From 04ce1ee301c2d44137ab754711afa6b5edddc19a Mon Sep 17 00:00:00 2001 From: Leandro Matayoshi Date: Thu, 24 Dec 2020 21:03:41 -0300 Subject: [PATCH] Use the newest `lastSyncAt` instead of the eldest one during `sync` (#328) For https://github.com/instedd/maap-collector/issues/327 Approach Instead of sending to the server the eldest `lastSyncAt` at Collector's end and updating the `lastSyncAt` of all the entries at the end of the `sync`, it now just sends the newest `lastSyncAt` which falls more natural (no need of modifying the `lastSyncAt` of any other entries at the end). In layman's terms it's asking: "Ok server, can you please bring me all the records that have been created or updated since the last time I synced with you?", where the last time is determined by the last entry that has been fetched (last entry has the most recent `lastSyncAt` value because they come sorted by `updated_at` in `ASCENDING` order from the server). This allows the syncing to be interrupted and resumed without any problem as it doesn't depend on any event on completion such as updating `lastSyncAt` for all entries at the end of the `sync` process. Avoiding pitfalls on upload What happens if other entries or updates are submitted to the server by other collectors while the current one is uploading their own changes? Will the current collector miss those changes? No, that's not a problem. In order to cover that scenario, `lastSyncAt` of each entry that is being `uploaded` on `remoteUpload` or `remoteUploadUpdate` is set to the newest `lastSyncAt` in the database at that moment. This allows capturing the new entries or updates that could have been uploaded to the server by other collectors while the current one was uploading their own changes. --- app/actions/sync.js | 79 ++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/app/actions/sync.js b/app/actions/sync.js index ccc0350..4fc2779 100644 --- a/app/actions/sync.js +++ b/app/actions/sync.js @@ -104,18 +104,18 @@ export const auth = () => async (dispatch, getState) => { export const remoteSync = (url, user, entityName, mapper) => async () => { const initializedDb = await db.initializeForUser(user); const entity = initializedDb[entityName]; - const oldestEntity = await entity.findOne({ + const newestEntity = await entity.findOne({ where: initializedDb.sequelize.literal( "lastSyncAt IS NOT 'Invalid date' AND lastSyncAt is NOT NULL" ), - order: [['lastSyncAt', 'ASC']] + order: [['lastSyncAt', 'DESC']] }); const newestRemoteIdEntity = await entity.findOne({ order: [['remoteId', 'DESC']] }); - const oldestDate = - oldestEntity && oldestEntity.lastSyncAt - ? moment(oldestEntity.lastSyncAt).toISOString() + const newestLastSyncAt = + newestEntity && newestEntity.lastSyncAt + ? moment(newestEntity.lastSyncAt).toISOString() : null; const newestRemoteId = newestRemoteIdEntity ? newestRemoteIdEntity.remoteId @@ -125,7 +125,7 @@ export const remoteSync = (url, user, entityName, mapper) => async () => { user.auth, { qs: { - updated_at_gth: oldestDate, + updated_at_gth: newestLastSyncAt, id_gth: newestRemoteId } }, @@ -180,26 +180,7 @@ export const remoteSync = (url, user, entityName, mapper) => async () => { ) .catch(e => console.log(e)); } - ).then(({ greather_updated_at: greatherUpdatedAt }) => { - if (!greatherUpdatedAt) return; - entity.update( - { - lastSyncAt: moment(greatherUpdatedAt) - .local() - .toDate(), - updatedAt: moment(greatherUpdatedAt) - .local() - .toDate() - }, - { - where: initializedDb.sequelize.literal( - "remoteId is NOT NULL AND strftime('%Y-%m-%d %H:%M:%S', updatedAt) <= strftime('%Y-%m-%d %H:%M:%S', lastSyncAt)" - ), - silent: true - } - ); - return Promise.resolve(); - }); + ); }; export const remoteUpload = ( @@ -212,6 +193,19 @@ export const remoteUpload = ( const initializedDb = await db.initializeForUser(user); const { sequelize } = initializedDb; const entity = initializedDb[entityName]; + + const newestEntity = await entity.findOne({ + where: initializedDb.sequelize.literal( + "lastSyncAt IS NOT 'Invalid date' AND lastSyncAt is NOT NULL" + ), + order: [['lastSyncAt', 'DESC']] + }); + + const newestLastSyncAt = + newestEntity && newestEntity.lastSyncAt + ? moment(newestEntity.lastSyncAt).toISOString() + : null; + const query = withSoftDelete ? "(deletedAt is NULL OR deletedAt IS 'Invalid date') AND (remoteId is NULL OR remoteId = '')" : "remoteId is NULL OR remoteId = ''"; @@ -235,9 +229,12 @@ export const remoteUpload = ( }); if (existingEntity && existingEntity.id !== currentEntity.id) existingEntity.destroy(); + // TODO: `updatedAt` is not being updated for some reason + // thus triggering an extra innocuous `update` return currentEntity.update({ remoteId: res.id, - lastSyncAt: new Date() + lastSyncAt: newestLastSyncAt || new Date(), + updatedAt: newestLastSyncAt || new Date() }); }) .catch(e => console.log(e)); @@ -255,6 +252,18 @@ export const remoteUploadUpdate = ( const initializedDb = await db.initializeForUser(user); const { sequelize } = initializedDb; const entity = initializedDb[entityName]; + const newestEntity = await entity.findOne({ + where: initializedDb.sequelize.literal( + "lastSyncAt IS NOT 'Invalid date' AND lastSyncAt is NOT NULL" + ), + order: [['lastSyncAt', 'DESC']] + }); + + const newestLastSyncAt = + newestEntity && newestEntity.lastSyncAt + ? moment(newestEntity.lastSyncAt).toISOString() + : null; + const query = withSoftDelete ? "(deletedAt is NULL OR deletedAt IS 'Invalid date') AND (remoteId is NOT NULL AND strftime('%Y-%m-%d %H:%M:%S', updatedAt) > strftime('%Y-%m-%d %H:%M:%S', lastSyncAt))" : "remoteId is NOT NULL AND strftime('%Y-%m-%d %H:%M:%S', updatedAt) > strftime('%Y-%m-%d %H:%M:%S', lastSyncAt)"; @@ -271,12 +280,16 @@ export const remoteUploadUpdate = ( .then(item => entity.update( { - lastSyncAt: moment(item.updated_at) - .local() - .toDate(), - updatedAt: moment(item.updated_at) - .local() - .toDate() + lastSyncAt: + newestLastSyncAt || + moment(item.updated_at) + .local() + .toDate(), + updatedAt: + newestLastSyncAt || + moment(item.updated_at) + .local() + .toDate() }, { where: {