diff --git a/src/lib/features/users/user-updates-read-model.test.ts b/src/lib/features/users/user-updates-read-model.test.ts index f2e1b00a76..6b7cedd79e 100644 --- a/src/lib/features/users/user-updates-read-model.test.ts +++ b/src/lib/features/users/user-updates-read-model.test.ts @@ -62,4 +62,21 @@ test('returns the users in pages', async () => { ); expect(usersPage3).toHaveLength(2); expect(usersPage3[1].username).toBe('test-user-9'); + + const usersPage4 = await userUpdatesReadModel.getUsersUpdatedAfterOrEqual( + INSERT_INSTANT, + pageSize, + usersPage3[usersPage3.length - 1].id, + ); + expect(usersPage4).toHaveLength(0); +}); + +test('getLastUpdatedAt returns the latest updated_at timestamp with max id', async () => { + const userUpdatesReadModel = db.stores.userUpdatesReadModel; + + const result = await userUpdatesReadModel.getLastUpdatedAt(); + expect(result).not.toBeNull(); + expect(result?.lastUpdatedAt).toEqual(INSERT_INSTANT); + expect(result?.userId).toBeDefined(); + expect(result?.userId).toBe(10); // The last inserted user should have the highest ID }); diff --git a/src/lib/features/users/user-updates-read-model.ts b/src/lib/features/users/user-updates-read-model.ts index 4444b45e8c..f0c36de38a 100644 --- a/src/lib/features/users/user-updates-read-model.ts +++ b/src/lib/features/users/user-updates-read-model.ts @@ -38,16 +38,26 @@ export class UserUpdatesReadModel { this.logger = getLogger('user-updates-read-model.ts'); } - async getLastUpdatedAt(): Promise { + async getLastUpdatedAt(): Promise<{ + lastUpdatedAt: Date; + userId: number; + } | null> { const result = await this.db(USERS_TABLE) + .select('id', 'updated_at') .where({ // also consider deleted users (different than activeUsers query) is_system: false, is_service: false, }) - .max('updated_at as last_updated_at') + .orderBy('updated_at', 'desc') + .orderBy('id', 'desc') .first(); - return result ? result.last_updated_at : null; + return result + ? { + lastUpdatedAt: result.updated_at, + userId: result.id, + } + : null; } /** @deprecated */ diff --git a/src/test/e2e/users/user-updates-read-model.e2e.test.ts b/src/test/e2e/users/user-updates-read-model.e2e.test.ts index 9c8c8c77fd..e54a790f29 100644 --- a/src/test/e2e/users/user-updates-read-model.e2e.test.ts +++ b/src/test/e2e/users/user-updates-read-model.e2e.test.ts @@ -15,10 +15,10 @@ beforeEach(async () => { test('should have no users', async () => { const readModel = stores.userUpdatesReadModel; - const lastUpdatedAt = await readModel.getLastUpdatedAt(); - expect(lastUpdatedAt).toBeNull(); + const lastUpdate = await readModel.getLastUpdatedAt(); + expect(lastUpdate).toBeNull(); - const users = await readModel.getUsersUpdatedAfter(new Date(0)); + const users = await readModel.getUsersUpdatedAfterOrEqual(new Date(0)); expect(users).toEqual([]); }); @@ -28,15 +28,15 @@ test('Adding a user should return that user', async () => { const beforeInsert = new Date(Date.now() - 1000); await userStore.upsert({ email: 'test@example.com' }); - const lastUpdatedAt = await readModel.getLastUpdatedAt(); - expect(lastUpdatedAt).toBeDefined(); - expect(lastUpdatedAt).toBeInstanceOf(Date); + const lastUpdate = await readModel.getLastUpdatedAt(); + expect(lastUpdate).toBeDefined(); + expect(lastUpdate!.lastUpdatedAt).toBeInstanceOf(Date); // check that it's recent - expect(lastUpdatedAt!.getTime()).toBeGreaterThanOrEqual( + expect(lastUpdate!.lastUpdatedAt!.getTime()).toBeGreaterThanOrEqual( beforeInsert.getTime(), ); - const users = await readModel.getUsersUpdatedAfter(beforeInsert); + const users = await readModel.getUsersUpdatedAfterOrEqual(beforeInsert); expect(users).toHaveLength(1); expect(users[0].email).toBe('test@example.com'); expect(users[0].createdAt).toBeInstanceOf(Date); @@ -52,23 +52,25 @@ test('Modifying a user should return that user', async () => { }); const afterInsert = new Date(); - const lastUpdatedAt = await readModel.getLastUpdatedAt(); - expect(lastUpdatedAt).toBeDefined(); + const lastUpdate = await readModel.getLastUpdatedAt(); + expect(lastUpdate).toBeDefined(); + const lastUpdatedAt = lastUpdate!.lastUpdatedAt; expect(lastUpdatedAt).toBeInstanceOf(Date); - const users = await readModel.getUsersUpdatedAfter(afterInsert); + const users = await readModel.getUsersUpdatedAfterOrEqual(afterInsert); expect(users).toHaveLength(0); await userStore.update(inserted.id, { name: 'New Name' }); - const lastUpdatedAt2 = await readModel.getLastUpdatedAt(); - expect(lastUpdatedAt2).toBeDefined(); + const lastUpdate2 = await readModel.getLastUpdatedAt(); + expect(lastUpdate2).toBeDefined(); + const lastUpdatedAt2 = lastUpdate2!.lastUpdatedAt; expect(lastUpdatedAt2).toBeInstanceOf(Date); expect(lastUpdatedAt2!.getTime()).toBeGreaterThanOrEqual( lastUpdatedAt!.getTime(), ); - const users2 = await readModel.getUsersUpdatedAfter(afterInsert); + const users2 = await readModel.getUsersUpdatedAfterOrEqual(afterInsert); expect(users2).toHaveLength(1); expect(users2[0].email).toBe('test@example.com'); expect(users2[0].name).toBe('New Name'); @@ -85,23 +87,25 @@ test('Deleting a user should return that user', async () => { }); const afterInsert = new Date(); - const lastUpdatedAt = await readModel.getLastUpdatedAt(); - expect(lastUpdatedAt).toBeDefined(); + const lastUpdate = await readModel.getLastUpdatedAt(); + expect(lastUpdate).toBeDefined(); + const lastUpdatedAt = lastUpdate!.lastUpdatedAt; expect(lastUpdatedAt).toBeInstanceOf(Date); - const users = await readModel.getUsersUpdatedAfter(afterInsert); + const users = await readModel.getUsersUpdatedAfterOrEqual(afterInsert); expect(users).toHaveLength(0); await userStore.delete(inserted.id); - const lastUpdatedAt2 = await readModel.getLastUpdatedAt(); - expect(lastUpdatedAt2).toBeDefined(); + const lastUpdate2 = await readModel.getLastUpdatedAt(); + expect(lastUpdate2).toBeDefined(); + const lastUpdatedAt2 = lastUpdate2!.lastUpdatedAt; expect(lastUpdatedAt2).toBeInstanceOf(Date); expect(lastUpdatedAt2!.getTime()).toBeGreaterThanOrEqual( lastUpdatedAt!.getTime(), ); - const users2 = await readModel.getUsersUpdatedAfter(afterInsert); + const users2 = await readModel.getUsersUpdatedAfterOrEqual(afterInsert); expect(users2).toHaveLength(1); expect(users2[0].email).toBeNull(); // currently we nullify the email but this might change in the future expect(users2[0].createdAt).toBeInstanceOf(Date);