Skip to content

Commit b8277cb

Browse files
authored
fix(LoginEvent): prevent duplicate entries (#104)
1 parent afaecab commit b8277cb

File tree

3 files changed

+55
-1
lines changed

3 files changed

+55
-1
lines changed

server/controllers/LoginEventController.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ import { LoginEvent } from '../models';
33
export class LoginEventController {
44
public async log(user_id: string, timestamp?: Date) {
55
const finalTimestamp = timestamp ?? new Date();
6-
await LoginEvent.create({ timestamp: finalTimestamp, user_id });
6+
await LoginEvent.create({ timestamp: finalTimestamp, user_id }, { ignoreDuplicates: true });
77
}
88
}

server/models/LoginEvent.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ import {
1212
import { User } from './User';
1313

1414
@Table({
15+
indexes: [{
16+
fields: ['user_id', 'timestamp'],
17+
name: 'login_events_user_id_timestamp_unique',
18+
unique: true,
19+
}],
1520
timestamps: true,
1621
tableName: 'login_events',
1722
})

server/tests/loginevents.unit.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { after, afterEach, describe } from 'mocha';
2+
import { expect } from 'chai';
3+
import { LoginEvent } from '../models';
4+
import { LoginEventController } from '@server/controllers/LoginEventController';
5+
import { server } from '@server/index';
6+
import { User } from '@server/models';
7+
import { v4 as uuidv4 } from 'uuid';
8+
9+
describe('LoginEventController', () => {
10+
const controller = new LoginEventController();
11+
let user1: User;
12+
13+
before(async () => {
14+
user1 = await User.create({
15+
uuid: uuidv4(),
16+
email: 'info@libretext.org',
17+
});
18+
});
19+
after(async () => {
20+
await User.destroy({ where: {} });
21+
if (server?.listening) {
22+
server.close();
23+
}
24+
});
25+
afterEach(async () => {
26+
await LoginEvent.destroy({ where: {} });
27+
});
28+
29+
describe('log', () => {
30+
it('should log event', async () => {
31+
await controller.log(user1.uuid);
32+
const foundLog = await LoginEvent.findOne({ where: { user_id: user1.uuid } });
33+
expect(foundLog).to.not.be.null;
34+
});
35+
it('should log event with timestamp specified', async () => {
36+
await controller.log(user1.uuid, new Date(1000));
37+
const foundLog = await LoginEvent.findOne({ where: { user_id: user1.uuid } });
38+
expect(foundLog).to.not.be.null;
39+
expect(foundLog?.get('timestamp')?.getTime()).to.deep.equal(1000);
40+
});
41+
it('should not error on duplicate entry', async () => {
42+
const timestamp = new Date();
43+
await controller.log(user1.uuid, timestamp);
44+
await controller.log(user1.uuid, timestamp);
45+
const foundLogs = await LoginEvent.findAll({ where: { user_id: user1.uuid } });
46+
expect(foundLogs?.length).to.deep.equal(1);
47+
});
48+
});
49+
});

0 commit comments

Comments
 (0)