Skip to content

Commit a6de199

Browse files
authored
Revert "[TECH] Retirer le script de suppression en masse d'organisations (PIX-16269)"
1 parent 78d88ba commit a6de199

File tree

12 files changed

+278
-3
lines changed

12 files changed

+278
-3
lines changed

Diff for: api/src/organizational-entities/infrastructure/repositories/data-protection-officer.repository.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ async function create(dataProtectionOfficer) {
3535
return new DataProtectionOfficer(dataProtectionOfficerRow);
3636
}
3737

38+
async function deleteDpoByOrganizationId(organizationId) {
39+
const knexConn = DomainTransaction.getConnection();
40+
await knexConn(DATA_PROTECTION_OFFICERS_TABLE_NAME).where({ organizationId }).delete();
41+
}
42+
3843
async function update(dataProtectionOfficer) {
3944
const knexConn = DomainTransaction.getConnection();
4045
const { firstName, lastName, email, organizationId, certificationCenterId } = dataProtectionOfficer;
@@ -54,4 +59,4 @@ async function update(dataProtectionOfficer) {
5459
return new DataProtectionOfficer(dataProtectionOfficerRow);
5560
}
5661

57-
export { batchAddDataProtectionOfficerToOrganization, create, get, update };
62+
export { batchAddDataProtectionOfficerToOrganization, create, deleteDpoByOrganizationId, get, update };

Diff for: api/src/organizational-entities/infrastructure/repositories/organization-feature-repository.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,15 @@ async function findAllOrganizationFeaturesFromOrganizationId({ organizationId })
5959
return organizationFeatures.map((organizationFeature) => new OrganizationFeatureItem(organizationFeature));
6060
}
6161

62-
export { findAllOrganizationFeaturesFromOrganizationId, saveInBatch };
62+
/**
63+
* @function
64+
* @name delete
65+
*
66+
* @param {number} organizationId
67+
*/
68+
async function deleteOrganizationFeatureByOrganizationId(organizationId) {
69+
const knexConn = DomainTransaction.getConnection();
70+
await knexConn('organization-features').where({ organizationId }).delete();
71+
}
72+
73+
export { deleteOrganizationFeatureByOrganizationId, findAllOrganizationFeaturesFromOrganizationId, saveInBatch };

Diff for: api/src/organizational-entities/infrastructure/repositories/organization-for-admin.repository.js

+11
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,16 @@ const update = async function (organization) {
216216
.where({ id: organization.id });
217217
};
218218

219+
/**
220+
* @type {function}
221+
* @param organizationId
222+
* @return {Promise<void>}
223+
*/
224+
const deleteById = async function (organizationId) {
225+
const knexConn = DomainTransaction.getConnection();
226+
await knexConn(ORGANIZATIONS_TABLE_NAME).where({ id: organizationId }).delete();
227+
};
228+
219229
async function _addOrUpdateDataProtectionOfficer(knexConn, dataProtectionOfficer) {
220230
await knexConn(DATA_PROTECTION_OFFICERS_TABLE_NAME)
221231
.insert(dataProtectionOfficer)
@@ -331,4 +341,5 @@ export const organizationForAdminRepository = {
331341
get,
332342
save,
333343
update,
344+
deleteById,
334345
};

Diff for: api/src/organizational-entities/infrastructure/repositories/organization-tag.repository.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,9 @@ const getRecentlyUsedTags = async function ({ tagId, numberOfRecentTags }) {
5252
return tags.map(({ tagId: id, name }) => new Tag({ id, name }));
5353
};
5454

55-
export { batchCreate, create, getRecentlyUsedTags, isExistingByOrganizationIdAndTagId };
55+
const deleteTagsByOrganizationId = async function (organizationId) {
56+
const knexConn = DomainTransaction.getConnection();
57+
await knexConn('organization-tags').where({ organizationId }).delete();
58+
};
59+
60+
export { batchCreate, create, deleteTagsByOrganizationId, getRecentlyUsedTags, isExistingByOrganizationIdAndTagId };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import Joi from 'joi';
2+
3+
import * as schoolRepository from '../../school/infrastructure/repositories/school-repository.js';
4+
import { csvFileParser } from '../../shared/application/scripts/parsers.js';
5+
import { Script } from '../../shared/application/scripts/script.js';
6+
import { ScriptRunner } from '../../shared/application/scripts/script-runner.js';
7+
import * as dataProtectionOfficerRepository from '../infrastructure/repositories/data-protection-officer.repository.js';
8+
import * as organizationFeatureRepository from '../infrastructure/repositories/organization-feature-repository.js';
9+
import { organizationForAdminRepository } from '../infrastructure/repositories/organization-for-admin.repository.js';
10+
import * as organizationTagRepository from '../infrastructure/repositories/organization-tag.repository.js';
11+
12+
const columnsSchema = [{ name: 'Organization ID', schema: Joi.number().required() }];
13+
14+
export class DeleteOrganizationsScript extends Script {
15+
constructor() {
16+
super({
17+
description: 'Delete all organizations and associated tags',
18+
permanent: false,
19+
options: {
20+
file: {
21+
type: 'string',
22+
describe: 'File path to CSV file with organizations to delete',
23+
demandOption: true,
24+
requiresArg: true,
25+
coerce: csvFileParser(columnsSchema),
26+
},
27+
dryRun: {
28+
type: 'boolean',
29+
describe: 'Run the script without actually deleting anything',
30+
default: false,
31+
},
32+
},
33+
});
34+
}
35+
36+
async handle({
37+
options,
38+
logger,
39+
dependencies = {
40+
organizationForAdminRepository,
41+
organizationTagRepository,
42+
dataProtectionOfficerRepository,
43+
organizationFeatureRepository,
44+
schoolRepository,
45+
},
46+
}) {
47+
const { file, dryRun } = options;
48+
49+
let count = 0;
50+
for (const row of file) {
51+
const organizationId = row['Organization ID'];
52+
if (!dryRun) {
53+
logger.info(organizationId);
54+
// Delete data protection officer via data-protection-officer.repository
55+
await dependencies.dataProtectionOfficerRepository.deleteDpoByOrganizationId(organizationId);
56+
// delete organization tags via organization-tags.repository
57+
await dependencies.organizationTagRepository.deleteTagsByOrganizationId(organizationId);
58+
// delete organization features via organization-feature.repository
59+
await dependencies.organizationFeatureRepository.deleteOrganizationFeatureByOrganizationId(organizationId);
60+
// delete school via school.repository
61+
await dependencies.schoolRepository.deleteByOrganizationId({ organizationId: organizationId });
62+
// delete organizationvia organization-for-admin.repository
63+
await dependencies.organizationForAdminRepository.deleteById(organizationId);
64+
}
65+
count++;
66+
}
67+
68+
if (dryRun) {
69+
logger.info(`Would delete ${count} organizations.`);
70+
} else {
71+
logger.info(`Deleted ${count} organizations.`);
72+
}
73+
}
74+
}
75+
76+
await ScriptRunner.execute(import.meta.url, DeleteOrganizationsScript);

Diff for: api/src/school/infrastructure/repositories/school-repository.js

+5
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ const getSessionExpirationDate = async function ({ code }) {
5252
return sessionExpirationDate;
5353
};
5454

55+
const deleteByOrganizationId = async function ({ organizationId }) {
56+
await knex('schools').where({ organizationId }).delete();
57+
};
58+
5559
export {
60+
deleteByOrganizationId,
5661
getByCode,
5762
getById,
5863
getDivisions,

Diff for: api/tests/organizational-entities/integration/infrastructure/repositories/data-protection-officer.repository.test.js

+31
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,37 @@ describe('Integration | Organizational Entities | Repository | data-protection-o
8080
});
8181
});
8282

83+
describe('#deleteByOrganizationId', function () {
84+
it('deletes DPO from data protection officers table by organisation id ', async function () {
85+
// given
86+
const organization = databaseBuilder.factory.buildOrganization();
87+
const organization2 = databaseBuilder.factory.buildOrganization();
88+
databaseBuilder.factory.buildDataProtectionOfficer.withOrganizationId({
89+
firstName: 'Justin',
90+
lastName: 'Ninstan',
91+
email: 'justin-ninstan@example.net',
92+
organizationId: organization.id,
93+
});
94+
databaseBuilder.factory.buildDataProtectionOfficer.withOrganizationId({
95+
firstName: 'Justin',
96+
lastName: 'Ninstan',
97+
email: 'justin-ninstan@example.net',
98+
organizationId: organization2.id,
99+
});
100+
101+
await databaseBuilder.commit();
102+
103+
// when
104+
105+
await dataProtectionOfficerRepository.deleteDpoByOrganizationId(organization2.id);
106+
107+
// then
108+
const dataProtectionOfficers = await knex('data-protection-officers').select();
109+
expect(dataProtectionOfficers).to.have.lengthOf(1);
110+
expect(dataProtectionOfficers[0]).to.have.property('organizationId', organization.id);
111+
});
112+
});
113+
83114
describe('#get', function () {
84115
context('when DPO exists', function () {
85116
it('returns a DPO domain object', async function () {

Diff for: api/tests/organizational-entities/integration/infrastructure/repositories/organization-feature-repository_test.js

+17
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,21 @@ describe('Integration | Repository | Organization-for-admin', function () {
190190
expect(results).to.be.lengthOf(0);
191191
});
192192
});
193+
194+
describe('#deleteOrganizationFeatureByOrganizationId', function () {
195+
it('should delete all organization features from an organization', async function () {
196+
// given
197+
const organization = databaseBuilder.factory.buildOrganization();
198+
const feature = databaseBuilder.factory.buildFeature(ORGANIZATION_FEATURE.PLACES_MANAGEMENT);
199+
databaseBuilder.factory.buildOrganizationFeature({ organizationId: organization.id, featureId: feature.id });
200+
await databaseBuilder.commit();
201+
202+
// when
203+
await organizationFeatureRepository.deleteOrganizationFeatureByOrganizationId(organization.id);
204+
205+
// then
206+
const result = await knex('organization-features').where({ organizationId: organization.id });
207+
expect(result).to.have.lengthOf(0);
208+
});
209+
});
193210
});

Diff for: api/tests/organizational-entities/integration/infrastructure/repositories/organization-for-admin.repository.test.js

+18
Original file line numberDiff line numberDiff line change
@@ -869,4 +869,22 @@ describe('Integration | Organizational Entities | Infrastructure | Repository |
869869
expect(nbOrganizationsAfterUpdate).to.equal(nbOrganizationsBeforeUpdate);
870870
});
871871
});
872+
873+
describe('#deleteById', function () {
874+
it('deletes organization by id', async function () {
875+
// given
876+
const organization = databaseBuilder.factory.buildOrganization();
877+
const organization2 = databaseBuilder.factory.buildOrganization();
878+
await databaseBuilder.commit();
879+
880+
// when
881+
await organizationForAdminRepository.deleteById(organization.id);
882+
883+
// then
884+
const organizationDeleted = await knex('organizations').where({ id: organization.id });
885+
expect(organizationDeleted).to.have.lengthOf(0);
886+
const organizationNotDeleted = await knex('organizations').where({ id: organization2.id });
887+
expect(organizationNotDeleted).to.have.lengthOf(1);
888+
});
889+
});
872890
});

Diff for: api/tests/organizational-entities/integration/infrastructure/repositories/organization-tag.repository.test.js

+30
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,34 @@ describe('Integration | Organizational Entities | Infrastructure | Repository |
9696
expect(foundOrganizations).to.have.lengthOf(2);
9797
});
9898
});
99+
100+
describe('#deleteTag', function () {
101+
it('delete organizationTags', async function () {
102+
// given
103+
const organization1 = databaseBuilder.factory.buildOrganization();
104+
const organization2 = databaseBuilder.factory.buildOrganization();
105+
const tag1 = databaseBuilder.factory.buildTag({ name: 'tag1' });
106+
const tag2 = databaseBuilder.factory.buildTag({ name: 'tag2' });
107+
databaseBuilder.factory.buildOrganizationTag({
108+
organizationId: organization1.id,
109+
tagId: tag1.id,
110+
});
111+
databaseBuilder.factory.buildOrganizationTag({
112+
organizationId: organization2.id,
113+
tagId: tag2.id,
114+
});
115+
databaseBuilder.factory.buildOrganizationTag({
116+
organizationId: organization1.id,
117+
tagId: tag2.id,
118+
});
119+
await databaseBuilder.commit();
120+
// when
121+
await organizationTagRepository.deleteTagsByOrganizationId(organization1.id);
122+
// then
123+
const organizationTags = await knex('organization-tags').select();
124+
expect(organizationTags).to.have.lengthOf(1);
125+
expect(organizationTags[0]).to.have.property('tagId', tag2.id);
126+
expect(organizationTags[0]).to.have.property('organizationId', organization2.id);
127+
});
128+
});
99129
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { DeleteOrganizationsScript } from '../../../../src/organizational-entities/scripts/./delete-organizations-script.js';
2+
import { expect, sinon } from '../../../test-helper.js';
3+
4+
describe('DeleteOrganizationsScript', function () {
5+
describe('Handle', function () {
6+
let script;
7+
let logger;
8+
let organizationForAdminRepository;
9+
let organizationTagRepository;
10+
let dataProtectionOfficerRepository;
11+
let organizationFeatureRepository;
12+
let schoolRepository;
13+
14+
beforeEach(function () {
15+
script = new DeleteOrganizationsScript();
16+
logger = { info: sinon.spy() };
17+
organizationForAdminRepository = { deleteById: sinon.stub() };
18+
organizationTagRepository = { deleteTagsByOrganizationId: sinon.stub() };
19+
dataProtectionOfficerRepository = { deleteDpoByOrganizationId: sinon.stub() };
20+
organizationFeatureRepository = { deleteOrganizationFeatureByOrganizationId: sinon.stub() };
21+
schoolRepository = { deleteByOrganizationId: sinon.stub() };
22+
});
23+
24+
it('handles data correctly', async function () {
25+
const file = [{ 'Organization ID': 1 }, { 'Organization ID': 2 }];
26+
27+
await script.handle({
28+
options: { file },
29+
logger,
30+
dependencies: {
31+
organizationForAdminRepository,
32+
organizationTagRepository,
33+
dataProtectionOfficerRepository,
34+
organizationFeatureRepository,
35+
schoolRepository,
36+
},
37+
});
38+
expect(organizationForAdminRepository.deleteById.calledWith(1)).to.be.true;
39+
expect(organizationForAdminRepository.deleteById.calledWith(2)).to.be.true;
40+
expect(organizationTagRepository.deleteTagsByOrganizationId.calledWith(1)).to.be.true;
41+
expect(organizationTagRepository.deleteTagsByOrganizationId.calledWith(2)).to.be.true;
42+
expect(dataProtectionOfficerRepository.deleteDpoByOrganizationId.calledWith(1)).to.be.true;
43+
expect(dataProtectionOfficerRepository.deleteDpoByOrganizationId.calledWith(2)).to.be.true;
44+
expect(organizationFeatureRepository.deleteOrganizationFeatureByOrganizationId.calledWith(1)).to.be.true;
45+
expect(organizationFeatureRepository.deleteOrganizationFeatureByOrganizationId.calledWith(2)).to.be.true;
46+
expect(schoolRepository.deleteByOrganizationId.calledWith({ organizationId: 1 })).to.be.true;
47+
expect(schoolRepository.deleteByOrganizationId.calledWith({ organizationId: 2 })).to.be.true;
48+
});
49+
});
50+
});

Diff for: api/tests/school/integration/infrastructure/repositories/school-repository_test.js

+16
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,20 @@ describe('Integration | Repository | School', function () {
168168
expect(sessionExpirationDate).to.equal(undefined);
169169
});
170170
});
171+
172+
describe('#deleteByOrganizationId', function () {
173+
it('should delete the school corresponding to the organizationId', async function () {
174+
// given
175+
const organization = databaseBuilder.factory.buildOrganization();
176+
databaseBuilder.factory.buildSchool({ organizationId: organization.id });
177+
await databaseBuilder.commit();
178+
179+
// when
180+
await repositories.schoolRepository.deleteByOrganizationId({ organizationId: organization.id });
181+
182+
// then
183+
const school = await knex('schools').first();
184+
expect(school).to.be.undefined;
185+
});
186+
});
171187
});

0 commit comments

Comments
 (0)