Skip to content
This repository was archived by the owner on May 5, 2023. It is now read-only.

Commit 90ad2a2

Browse files
authored
fix(personaIdentifier lock): Add lockedAt field to handle expired locks on personaIdentifiers (LLC-1877) (#400)
1 parent 1d012e8 commit 90ad2a2

25 files changed

Lines changed: 585 additions & 85 deletions

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ MODELS_REPO=memory
55
STORAGE_REPO=memory
66
DEFAULT_TIMEOUT_MS=300000
77

8+
# Persona Identifier lock expiration time in ms (default 30000)
9+
#IDENTIFIER_LOCK_EXPIRATION_MS=30000
10+
811
# Winston
912
WINSTON_CONSOLE_LEVEL=info
1013
WINSTON_CLOUDWATCH_ENABLED=false

src/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ const getMongoNumberOption = (option?: string) => {
2727
return defaultTo<number|undefined>(Number(option), undefined);
2828
};
2929

30+
const DEFAULT_IDENTIFIER_LOCK_EXPIRATION_MS = 30000; // 30 seconds.
31+
export const IDENTIFIER_LOCK_EXPIRATION_MS = getNumberOption(
32+
process.env.IDENTIFIER_LOCK_EXPIRATION_MS,
33+
DEFAULT_IDENTIFIER_LOCK_EXPIRATION_MS,
34+
);
35+
3036
export default {
3137
defaultTimeout: getNumberOption(process.env.DEFAULT_TIMEOUT_MS, DEFAULT_TIMEOUT_MS),
3238
lang: getStringOption(process.env.LANG, 'en'),

src/errors/ExpiredLock.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import BaseError from 'jscommons/dist/errors/BaseError';
2+
import Identifier from '../models/Identifier';
3+
4+
export class ExpiredLock extends BaseError {
5+
constructor(
6+
public identifier: Identifier,
7+
public ignorePersonaId: boolean,
8+
) {
9+
super();
10+
}
11+
}

src/errors/Locked.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import BaseError from 'jscommons/dist/errors/BaseError';
33
import Identifier from '../models/Identifier';
44

55
export default class extends BaseError {
6+
public readonly message: string;
7+
68
constructor(public identifier: Identifier) {
79
super();
10+
this.message = 'Locked';
811
}
912
}

src/models/Identifier.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,8 @@ interface Identifier extends BaseModel {
77
readonly ifi: Ifi;
88
}
99

10+
export interface IdentifierWithPersona extends Identifier {
11+
readonly persona: string;
12+
}
13+
1014
export default Identifier;

src/mongoModelsRepo/createUpdateIdentifierPersona.ts

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import NoModel from 'jscommons/dist/errors/NoModel';
2+
import { ObjectID } from 'mongodb';
23
import * as promiseRetry from 'promise-retry';
4+
5+
import { ExpiredLock } from '../errors/ExpiredLock';
36
import Locked from '../errors/Locked';
47
import CreateUpdateIdentifierPersonaOptions // tslint:disable-line:import-spacing
58
from '../repoFactory/options/CreateUpdateIdentifierPersonaOptions';
@@ -9,11 +12,15 @@ import CreateUpdateIdentifierPersonaResult // tslint:disable-line:import-spacing
912
import GetIdentifierResult from '../repoFactory/results/GetIdentifierResult';
1013
import Lockable from '../repoFactory/utils/Lockable';
1114
import Config from './Config';
12-
import createPersona from './createPersona';
1315
import getTheIdentifier from './getIdentifier';
1416
import getIdentifierByIfi from './getIdentifierByIfi';
15-
import setIdentifierPersona from './setIdentifierPersona';
1617
import createIdentifier from './utils/createIdentifier';
18+
import createOrUpdateIdentifier from './utils/createOrUpdateIdentifier';
19+
import { createPersonaAndAddToIdentifier } from './utils/createPersonaAndAddToIdentifier';
20+
21+
type TheCreateUpdateIdentifierPersonaOptions = CreateUpdateIdentifierPersonaOptions & {
22+
readonly getIdentifier?: (opts: GetIdentifierOptions) => Promise<GetIdentifierResult & Lockable>;
23+
};
1724

1825
const create = (config: Config) =>
1926
async ({
@@ -32,23 +39,10 @@ const create = (config: Config) =>
3239
throw new Locked(identifier);
3340
}
3441

35-
const { persona } = await createPersona(config)({
36-
name: personaName,
37-
organisation,
38-
});
39-
40-
const { identifier: updatedIdentifier } = await setIdentifierPersona(config)({
41-
id: identifier.id,
42-
organisation,
43-
persona: persona.id,
42+
return await createPersonaAndAddToIdentifier(config)({
43+
identifier,
44+
personaName,
4445
});
45-
46-
return {
47-
identifier: updatedIdentifier,
48-
identifierId: identifier.id,
49-
personaId: persona.id,
50-
wasCreated,
51-
};
5246
};
5347

5448
const createUpdateIdentifierPersona = (config: Config) =>
@@ -70,7 +64,7 @@ const createUpdateIdentifierPersona = (config: Config) =>
7064
organisation,
7165
});
7266

73-
if ( locked === true ) {
67+
if (locked === true) {
7468
// We are locked, wait for unlock
7569
throw new Locked(foundIdentifier);
7670
}
@@ -85,8 +79,8 @@ const createUpdateIdentifierPersona = (config: Config) =>
8579
// currently it doesn't get updated
8680

8781
return {
88-
identifier: foundIdentifier,
8982
identifierId,
83+
identifier: foundIdentifier,
9084
personaId: foundIdentifier.persona,
9185
wasCreated: false,
9286
};
@@ -99,15 +93,38 @@ const createUpdateIdentifierPersona = (config: Config) =>
9993
personaName,
10094
});
10195
}
96+
if (err instanceof ExpiredLock) {
97+
const {
98+
identifier: expiredIdentifier,
99+
ignorePersonaId,
100+
} = err;
101+
102+
const { identifier: identifierWithoutPersona } = await createOrUpdateIdentifier(config)({
103+
filter: {
104+
_id: new ObjectID(expiredIdentifier.id),
105+
organisation: new ObjectID(organisation),
106+
},
107+
update: {
108+
$set: { lockedAt: new Date() },
109+
...(
110+
ignorePersonaId
111+
? { $unset: { persona: '' } }
112+
: {}
113+
),
114+
},
115+
upsert: false,
116+
});
117+
118+
return await createPersonaAndAddToIdentifier(config)({
119+
identifier: identifierWithoutPersona,
120+
personaName,
121+
});
122+
}
123+
102124
throw err;
103125
}
104-
105126
};
106127

107-
type TheCreateUpdateIdentifierPersonaOptions = CreateUpdateIdentifierPersonaOptions & {
108-
readonly getIdentifier?: (opts: GetIdentifierOptions) => Promise<GetIdentifierResult & Lockable>;
109-
};
110-
111128
const retryCreateUpdateIdentifierPersona = (config: Config) =>
112129
async (opts: TheCreateUpdateIdentifierPersonaOptions):
113130
Promise<CreateUpdateIdentifierPersonaResult> => {
@@ -116,7 +133,8 @@ const retryCreateUpdateIdentifierPersona = (config: Config) =>
116133

117134
return promiseRetry<CreateUpdateIdentifierPersonaResult>(async (retry) => {
118135
try {
119-
return await createUpdateIdentifierPersonaFn(opts);
136+
const res = await createUpdateIdentifierPersonaFn(opts);
137+
return res;
120138
} catch (err) {
121139
/* istanbul ignore else */
122140
if (err instanceof Locked) {

src/mongoModelsRepo/getAttribute.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ export default (config: Config) => {
2525
id: document._id.toString(),
2626
key: document.key,
2727
organisation: document.organisation.toString(),
28-
/* istanbul ignore next */ // shouldnt be null..
29-
personaId: document.personaId === null ? null : document.personaId.toString(),
28+
personaId: document.personaId?.toString(),
3029
value: document.value,
3130
};
3231
return { attribute };

src/mongoModelsRepo/getIdentifier.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import NoModel from 'jscommons/dist/errors/NoModel';
22
import { ObjectID } from 'mongodb';
3+
4+
import { IDENTIFIER_LOCK_EXPIRATION_MS } from '../config';
5+
import { ExpiredLock } from '../errors/ExpiredLock';
6+
import Identifier from '../models/Identifier';
37
import GetIdentifierOptions from '../repoFactory/options/GetIdentifierOptions';
48
import GetIdentifierResult from '../repoFactory/results/GetIdentifierResult';
59
import Lockable from '../repoFactory/utils/Lockable';
@@ -22,13 +26,19 @@ export default (config: Config) => {
2226
throw new NoModel('Identifier');
2327
}
2428

25-
const identifier = {
29+
const identifier: Identifier = {
2630
id: document._id.toString(),
2731
ifi: document.ifi,
2832
organisation: document.organisation.toString(),
29-
/* istanbul ignore next */ // shouldnt be null..
30-
persona: document.persona === null ? null : document.persona.toString(),
33+
persona: document.persona?.toString(),
3134
};
35+
36+
const lockAge = (new Date()).getTime() - (document.lockedAt?.getTime() ?? 0);
37+
38+
if (document.locked && lockAge > IDENTIFIER_LOCK_EXPIRATION_MS) {
39+
throw new ExpiredLock(identifier, document.lockedAt === undefined);
40+
}
41+
3242
return { identifier, locked: document.locked };
3343
};
3444
};

src/mongoModelsRepo/getIdentifierByIfi.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ export default (config: Config) => {
2020
}
2121

2222
const identifierId = document._id.toString();
23-
/* istanbul ignore next */ // shouldnt be null..
24-
const personaId = document.persona === null ? null : document.persona.toString();
23+
const personaId = document.persona?.toString();
2524
return { identifierId, personaId };
2625
};
2726
};

src/mongoModelsRepo/getPersonaAttributes.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ export default (config: Config) => {
3232
id: document._id.toString(),
3333
key: document.key,
3434
organisation: document.organisation.toString(),
35-
/* istanbul ignore next */ // shouldnt be null..
36-
personaId: document.personaId === null ? null : document.personaId.toString(),
35+
personaId: document.personaId?.toString(),
3736
value: document.value,
3837
}));
3938

0 commit comments

Comments
 (0)