Skip to content

Commit c415447

Browse files
P-Jeremybpetetot
authored andcommitted
feat(api): add script to convert users orga cgu accepted to legal document user acceptances
1 parent f8ea8e3 commit c415447

File tree

2 files changed

+292
-0
lines changed

2 files changed

+292
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { setTimeout } from 'node:timers/promises';
2+
3+
import { DomainTransaction } from '../../../lib/infrastructure/DomainTransaction.js';
4+
import { Script } from '../../shared/application/scripts/script.js';
5+
import { ScriptRunner } from '../../shared/application/scripts/script-runner.js';
6+
import { LegalDocumentService } from '../domain/models/LegalDocumentService.js';
7+
import { LegalDocumentType } from '../domain/models/LegalDocumentType.js';
8+
import * as legalDocumentRepository from '../infrastructure/repositories/legal-document.repository.js';
9+
10+
const { TOS } = LegalDocumentType.VALUES;
11+
const { PIX_ORGA } = LegalDocumentService.VALUES;
12+
13+
const DEFAULT_BATCH_SIZE = 1000;
14+
const DEFAULT_THROTTLE_DELAY = 300;
15+
16+
export class ConvertUsersOrgaCguData extends Script {
17+
constructor() {
18+
super({
19+
description: 'Converts users orga CGU data to legal-document-versions-user-acceptances',
20+
permanent: false,
21+
options: {
22+
dryRun: {
23+
type: 'boolean',
24+
describe: 'Executes the script in dry run mode',
25+
default: false,
26+
},
27+
batchSize: {
28+
type: 'number',
29+
describe: 'Size of the batch to process',
30+
default: DEFAULT_BATCH_SIZE,
31+
},
32+
throttleDelay: {
33+
type: 'number',
34+
describe: 'Delay between batches in milliseconds',
35+
default: DEFAULT_THROTTLE_DELAY,
36+
},
37+
},
38+
});
39+
}
40+
41+
async handle({ options, logger }) {
42+
const { dryRun, batchSize, throttleDelay } = options;
43+
44+
const legalDocument = await legalDocumentRepository.findLastVersionByTypeAndService({
45+
type: TOS,
46+
service: PIX_ORGA,
47+
});
48+
49+
if (!legalDocument) {
50+
throw new Error(`No legal document found for type: ${TOS}, service: ${PIX_ORGA}`);
51+
}
52+
53+
let hasMoreUsers = true;
54+
let offset = 0;
55+
let migrationCount = 0;
56+
57+
while (hasMoreUsers) {
58+
const usersWithCgu = await this.#findOrgaCguAcceptedUsers({ batchSize, offset });
59+
60+
if (usersWithCgu.length === 0) {
61+
hasMoreUsers = false;
62+
break;
63+
}
64+
65+
logger.info(`Batch #${Math.ceil(offset / batchSize + 1)}`);
66+
67+
const usersToMigrate = await this.#filterAlreadyMigratedUsers({ legalDocument, users: usersWithCgu });
68+
69+
if (!dryRun) {
70+
await this.#createNewLegalDocumentAcceptanceForUsersBatch({ legalDocument, users: usersToMigrate });
71+
}
72+
73+
migrationCount += usersToMigrate.length;
74+
offset += usersWithCgu.length;
75+
76+
await setTimeout(throttleDelay);
77+
}
78+
79+
logger.info(`Total users ${dryRun ? 'to migrate' : 'migrated'}: ${migrationCount}`);
80+
}
81+
82+
async #findOrgaCguAcceptedUsers({ batchSize, offset }) {
83+
const knexConnection = DomainTransaction.getConnection();
84+
85+
return knexConnection('users')
86+
.select('*')
87+
.where('pixOrgaTermsOfServiceAccepted', true)
88+
.limit(batchSize)
89+
.offset(offset);
90+
}
91+
92+
async #filterAlreadyMigratedUsers({ legalDocument, users }) {
93+
const knexConnection = DomainTransaction.getConnection();
94+
95+
const alreadyMigratedUsers = await knexConnection('legal-document-version-user-acceptances')
96+
.select('userId')
97+
.where('legalDocumentVersionId', legalDocument.id)
98+
.whereIn(
99+
'userId',
100+
users.map((user) => user.id),
101+
);
102+
103+
const alreadyMigratedIds = alreadyMigratedUsers.map((user) => user.userId);
104+
return users.filter((user) => !alreadyMigratedIds.includes(user.id));
105+
}
106+
107+
async #createNewLegalDocumentAcceptanceForUsersBatch({ legalDocument, users }) {
108+
const knexConnection = DomainTransaction.getConnection();
109+
110+
const chunkSize = 100;
111+
const rows = users.map((user) => ({
112+
userId: user.id,
113+
legalDocumentVersionId: legalDocument.id,
114+
acceptedAt: user.lastPixOrgaTermsOfServiceValidatedAt || new Date(),
115+
}));
116+
117+
await knexConnection.batchInsert('legal-document-version-user-acceptances', rows, chunkSize);
118+
}
119+
}
120+
121+
await ScriptRunner.execute(import.meta.url, ConvertUsersOrgaCguData);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import { LegalDocumentService } from '../../../../src/legal-documents/domain/models/LegalDocumentService.js';
2+
import { LegalDocumentType } from '../../../../src/legal-documents/domain/models/LegalDocumentType.js';
3+
import { ConvertUsersOrgaCguData } from '../../../../src/legal-documents/scripts/convert-users-orga-cgu-data.js';
4+
import { databaseBuilder, expect, knex, sinon } from '../../../test-helper.js';
5+
6+
const { TOS } = LegalDocumentType.VALUES;
7+
const { PIX_ORGA } = LegalDocumentService.VALUES;
8+
9+
describe('Integration | Legal documents | Scripts | convert-users-orga-cgu-data', function () {
10+
describe('Options', function () {
11+
it('has the correct options', function () {
12+
// given
13+
const script = new ConvertUsersOrgaCguData();
14+
15+
// when
16+
const { options, permanent } = script.metaInfo;
17+
18+
// then
19+
expect(permanent).to.be.false;
20+
expect(options).to.deep.include({
21+
dryRun: {
22+
type: 'boolean',
23+
describe: 'Executes the script in dry run mode',
24+
default: false,
25+
},
26+
batchSize: {
27+
type: 'number',
28+
describe: 'Size of the batch to process',
29+
default: 1000,
30+
},
31+
throttleDelay: {
32+
type: 'number',
33+
describe: 'Delay between batches in milliseconds',
34+
default: 300,
35+
},
36+
});
37+
});
38+
});
39+
40+
describe('#handle', function () {
41+
let clock;
42+
let legalDocumentVersion;
43+
let logger;
44+
45+
beforeEach(async function () {
46+
clock = sinon.useFakeTimers({ now: new Date('2024-01-01'), toFake: ['Date'] });
47+
legalDocumentVersion = databaseBuilder.factory.buildLegalDocumentVersion({ type: TOS, service: PIX_ORGA });
48+
logger = { info: sinon.stub() };
49+
});
50+
51+
afterEach(async function () {
52+
clock.restore();
53+
});
54+
55+
it('converts Pix Orga user cgus to legal document user acceptances', async function () {
56+
// given
57+
const userAcceptedCgu = databaseBuilder.factory.buildUser({
58+
pixOrgaTermsOfServiceAccepted: true,
59+
lastPixOrgaTermsOfServiceValidatedAt: new Date('2021-01-01'),
60+
});
61+
const userAcceptedCguWithoutDate = databaseBuilder.factory.buildUser({
62+
pixOrgaTermsOfServiceAccepted: true,
63+
lastPixOrgaTermsOfServiceValidatedAt: null,
64+
});
65+
const userNotAcceptedCgu = databaseBuilder.factory.buildUser({
66+
pixOrgaTermsOfServiceAccepted: false,
67+
lastPixOrgaTermsOfServiceValidatedAt: null,
68+
});
69+
await databaseBuilder.commit();
70+
71+
// when
72+
const script = new ConvertUsersOrgaCguData();
73+
const options = { dryRun: false, batchSize: 1, throttleDelay: 0 };
74+
await script.handle({ options, logger });
75+
76+
// then
77+
expect(logger.info).to.have.been.calledWith('Batch #1');
78+
expect(logger.info).to.have.been.calledWith('Batch #2');
79+
expect(logger.info).to.have.been.calledWith('Total users migrated: 2');
80+
81+
const userAcceptances = await knex('legal-document-version-user-acceptances').where({
82+
legalDocumentVersionId: legalDocumentVersion.id,
83+
});
84+
85+
const acceptance1 = userAcceptances.find((user) => user.userId === userAcceptedCgu.id);
86+
expect(acceptance1.acceptedAt).to.deep.equal(new Date('2021-01-01'));
87+
88+
const acceptance2 = userAcceptances.find((user) => user.userId === userAcceptedCguWithoutDate.id);
89+
expect(acceptance2.acceptedAt).to.deep.equal(new Date('2024-01-01'));
90+
91+
const acceptance3 = userAcceptances.find((user) => user.userId === userNotAcceptedCgu.id);
92+
expect(acceptance3).to.be.undefined;
93+
});
94+
95+
context('when a user cgu is already converted to legal document user acceptances', function () {
96+
it('does not change already converted user acceptances', async function () {
97+
// given
98+
const alreadyConvertedUser = databaseBuilder.factory.buildUser({
99+
pixOrgaTermsOfServiceAccepted: true,
100+
lastPixOrgaTermsOfServiceValidatedAt: new Date('2021-01-01'),
101+
});
102+
const newUserAcceptedCgu = databaseBuilder.factory.buildUser({
103+
pixOrgaTermsOfServiceAccepted: true,
104+
lastPixOrgaTermsOfServiceValidatedAt: new Date('2021-01-01'),
105+
});
106+
databaseBuilder.factory.buildLegalDocumentVersionUserAcceptance({
107+
legalDocumentVersionId: legalDocumentVersion.id,
108+
userId: alreadyConvertedUser.id,
109+
acceptedAt: new Date('2020-01-01'),
110+
});
111+
await databaseBuilder.commit();
112+
113+
// when
114+
const script = new ConvertUsersOrgaCguData();
115+
const options = { dryRun: false, batchSize: 1, throttleDelay: 0 };
116+
await script.handle({ options, logger });
117+
118+
// then
119+
expect(logger.info).to.have.been.calledWith('Total users migrated: 1');
120+
121+
const userAcceptances = await knex('legal-document-version-user-acceptances').where({
122+
legalDocumentVersionId: legalDocumentVersion.id,
123+
});
124+
125+
const acceptance1 = userAcceptances.find((user) => user.userId === alreadyConvertedUser.id);
126+
expect(acceptance1.acceptedAt).to.deep.equal(new Date('2020-01-01'));
127+
128+
const acceptance2 = userAcceptances.find((user) => user.userId === newUserAcceptedCgu.id);
129+
expect(acceptance2.acceptedAt).to.deep.equal(new Date('2021-01-01'));
130+
});
131+
});
132+
133+
context('when the script has the dry run mode', function () {
134+
it('does not create legal document user acceptance', async function () {
135+
// given
136+
databaseBuilder.factory.buildUser({
137+
pixOrgaTermsOfServiceAccepted: true,
138+
lastPixOrgaTermsOfServiceValidatedAt: new Date('2021-01-01'),
139+
});
140+
await databaseBuilder.commit();
141+
142+
// when
143+
const script = new ConvertUsersOrgaCguData();
144+
const options = { dryRun: true, batchSize: 1, throttleDelay: 0 };
145+
await script.handle({ options, logger });
146+
147+
// then
148+
expect(logger.info).to.have.been.calledWith('Batch #1');
149+
expect(logger.info).to.have.been.calledWith('Total users to migrate: 1');
150+
151+
const userAcceptances = await knex('legal-document-version-user-acceptances').where({
152+
legalDocumentVersionId: legalDocumentVersion.id,
153+
});
154+
expect(userAcceptances.length).to.equal(0);
155+
});
156+
});
157+
158+
context('when no legal document is found', function () {
159+
it('throws an error', async function () {
160+
// given
161+
const script = new ConvertUsersOrgaCguData();
162+
163+
// when / then
164+
const options = { dryRun: true, batchSize: 1, throttleDelay: 0 };
165+
await expect(script.handle({ options, logger })).to.be.rejectedWith(
166+
'No legal document found for type: TOS, service: pix-orga',
167+
);
168+
});
169+
});
170+
});
171+
});

0 commit comments

Comments
 (0)