Skip to content

Commit 203ebb1

Browse files
authored
Skip deleted actors for backfilling lastLoginAt (#1671)
* Fixes getodk/central#1447: skip deleted actor for backfilling lastLoginAt * PR Feedback
1 parent 570a693 commit 203ebb1

File tree

2 files changed

+38
-6
lines changed

2 files changed

+38
-6
lines changed

lib/model/migrations/20250910-02-backfill-last-login-at.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ const up = (db) =>
2020
AND "actorId" IS NOT NULL
2121
GROUP BY "actorId"
2222
) AS latest_login
23-
WHERE users."actorId" = latest_login."actorId";
23+
JOIN actors ON actors.id = latest_login."actorId"
24+
WHERE users."actorId" = latest_login."actorId"
25+
AND actors."deletedAt" IS NULL;
2426
`);
2527

2628
const down = (db) =>

test/db-migrations/20250910-02-backfill-last-login-at.spec.js

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,25 @@ describeMigration('20250910-02-backfill-last-login-at', ({ runMigrationBeingTest
66
await rowsExistFor('actees',
77
{ id: 'actee1', species: 'user' },
88
{ id: 'actee2', species: 'user' },
9-
{ id: 'actee3', species: 'user' }
9+
{ id: 'actee3', species: 'user' },
10+
{ id: 'actee4', species: 'user' },
1011
);
1112

1213
await rowsExistFor('actors',
1314
{ id: 1, type: 'user', acteeId: 'actee1', displayName: 'Alice', createdAt: new Date('2025-01-01T10:00:00Z') },
1415
{ id: 2, type: 'user', acteeId: 'actee2', displayName: 'Bob', createdAt: new Date('2025-01-01T11:00:00Z') },
15-
{ id: 3, type: 'user', acteeId: 'actee3', displayName: 'Charlie', createdAt: new Date('2025-01-01T12:00:00Z') }
16+
{ id: 3, type: 'user', acteeId: 'actee3', displayName: 'Charlie', createdAt: new Date('2025-01-01T12:00:00Z') },
17+
{ id: 4, type: 'user', acteeId: 'actee4', displayName: 'Dave', createdAt: new Date('2025-01-01T12:00:00Z') }
1618
);
1719

1820
await rowsExistFor('users',
1921
{ actorId: 1, email: '[email protected]', lastLoginAt: null },
2022
{ actorId: 2, email: '[email protected]', lastLoginAt: null },
21-
{ actorId: 3, email: '[email protected]', lastLoginAt: null }
23+
{ actorId: 3, email: '[email protected]', lastLoginAt: null },
24+
{ actorId: 4, email: '[email protected]', lastLoginAt: null }
2225
);
2326

24-
// Create audit records - Alice has multiple logins, Bob has one, Charlie has none
27+
// Create audit records - Alice has multiple logins, Bob has one, Charlie has none, Dave has one
2528
await rowsExistFor('audits',
2629
// Alice's login sessions (most recent should be picked)
2730
{ actorId: 1, action: 'user.session.create', acteeId: 'actee1', loggedAt: new Date('2025-01-10T10:00:00Z') },
@@ -30,6 +33,27 @@ describeMigration('20250910-02-backfill-last-login-at', ({ runMigrationBeingTest
3033

3134
// Bob's single login session
3235
{ actorId: 2, action: 'user.session.create', acteeId: 'actee2', loggedAt: new Date('2025-01-08T16:45:00Z') },
36+
37+
// Dave's single login session
38+
{ actorId: 4, action: 'user.session.create', acteeId: 'actee4', loggedAt: new Date('2025-01-08T16:45:00Z') },
39+
);
40+
41+
// Delete Dave
42+
await db.any(sql`UPDATE actors SET "deletedAt" = '2025-02-01T00:00:00Z' WHERE id = 4`);
43+
44+
// Create Dave again
45+
await rowsExistFor('actees',
46+
{ id: 'actee5', species: 'user' }
47+
);
48+
await rowsExistFor('actors',
49+
{ id: 5, type: 'user', acteeId: 'actee5', displayName: 'NewDave', createdAt: new Date('2025-03-01T00:00:00Z') }
50+
);
51+
await rowsExistFor('users',
52+
{ actorId: 5, email: '[email protected]', lastLoginAt: null }
53+
);
54+
// Login with new Dave
55+
await rowsExistFor('audits',
56+
{ actorId: 5, action: 'user.session.create', acteeId: 'actee5', loggedAt: new Date('2025-03-20T10:00:00Z') }
3357
);
3458

3559
await runMigrationBeingTested();
@@ -44,7 +68,13 @@ describeMigration('20250910-02-backfill-last-login-at', ({ runMigrationBeingTest
4468
{ actorId: 2, email: '[email protected]', lastLoginAt: 1736354700000 },
4569

4670
// Charlie should remain null (never logged in)
47-
{ actorId: 3, email: '[email protected]', lastLoginAt: null }
71+
{ actorId: 3, email: '[email protected]', lastLoginAt: null },
72+
73+
// Dave should remain null because actor is deleted
74+
{ actorId: 4, email: '[email protected]', lastLoginAt: null },
75+
76+
// New Dave should get his login time (2025-03-20T10:00:00Z)
77+
{ actorId: 5, email: '[email protected]', lastLoginAt: 1742464800000 },
4878
);
4979
});
5080
});

0 commit comments

Comments
 (0)