Skip to content

Commit

Permalink
🐛 api: add transactions on update
Browse files Browse the repository at this point in the history
  • Loading branch information
Steph0 committed Jul 8, 2024
1 parent b90b5c5 commit 7f606c9
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 67 deletions.
3 changes: 3 additions & 0 deletions api/lib/domain/usecases/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@ function requirePoleEmploiNotifier() {
* @typedef {sessionPublicationService} SessionPublicationService
* @typedef {sessionRepository} SessionRepository
* @typedef {centerRepository} CenterRepository
* @typedef {certificationCenterForAdminRepository} CertificationCenterForAdminRepository
* @typedef {complementaryCertificationHabilitationRepository} ComplementaryCertificationHabilitationRepository
* @typedef {dataProtectionOfficerRepository} DataProtectionOfficerRepository
*/

const dependencies = {
Expand Down
143 changes: 99 additions & 44 deletions api/lib/domain/usecases/update-certification-center.js
Original file line number Diff line number Diff line change
@@ -1,79 +1,51 @@
/**
* @typedef {import('./index.js').CenterRepository} CenterRepository
* @typedef {import('./index.js').CertificationCenterForAdminRepository} CertificationCenterForAdminRepository
* @typedef {import('./index.js').ComplementaryCertificationHabilitationRepository} ComplementaryCertificationHabilitationRepository
* @typedef {import('./index.js').DataProtectionOfficerRepository} DataProtectionOfficerRepository
*/
import bluebird from 'bluebird';

import { CenterForAdminFactory } from '../../../src/certification/enrolment/domain/models/factories/CenterForAdminFactory.js';
import { CertificationCenterPilotFeaturesConflictError } from '../../../src/shared/domain/errors.js';
import { withTransaction } from '../../infrastructure/DomainTransaction.js';
import { ComplementaryCertificationHabilitation, DataProtectionOfficer } from '../models/index.js';
import * as certificationCenterCreationValidator from '../validators/certification-center-creation-validator.js';

async function _addOrUpdateDataProtectionOfficer({
certificationCenterId,
certificationCenterInformation,
dataProtectionOfficerRepository,
}) {
const dataProtectionOfficer = new DataProtectionOfficer({
firstName: certificationCenterInformation.dataProtectionOfficerFirstName ?? '',
lastName: certificationCenterInformation.dataProtectionOfficerLastName ?? '',
email: certificationCenterInformation.dataProtectionOfficerEmail ?? '',
certificationCenterId,
});

const dataProtectionOfficerFound = await dataProtectionOfficerRepository.get({
certificationCenterId,
});

if (dataProtectionOfficerFound) return dataProtectionOfficerRepository.update(dataProtectionOfficer);

return dataProtectionOfficerRepository.create(dataProtectionOfficer);
}

/**
* @param {Object} params
* @param {CenterRepository} params.centerRepository
* @param {CertificationCenterForAdminRepository} params.certificationCenterForAdminRepository
* @param {ComplementaryCertificationHabilitationRepository} params.ComplementaryCertificationHabilitationRepository
* @param {DataProtectionOfficerRepository} params.dataProtectionOfficerRepository
*/
const updateCertificationCenter = async function ({
certificationCenterId,
certificationCenterInformation,
complementaryCertificationIds,
centerRepository,
certificationCenterForAdminRepository,
complementaryCertificationHabilitationRepository,
dataProtectionOfficerRepository,
centerRepository,
}) {
certificationCenterCreationValidator.validate(certificationCenterInformation);

const certificationCenter = await centerRepository.getById({
id: certificationCenterId,
});

_verifyCenterPilotFeaturesCompatibility({
currentCenter: certificationCenter,
newCenterData: certificationCenterInformation,
await _updateCenter({
certificationCenterId,
certificationCenterInformation,
complementaryCertificationIds,
centerRepository,
certificationCenterForAdminRepository,
complementaryCertificationHabilitationRepository,
});

await complementaryCertificationHabilitationRepository.deleteByCertificationCenterId(certificationCenterId);

if (complementaryCertificationIds) {
await bluebird.mapSeries(complementaryCertificationIds, (complementaryCertificationId) => {
const complementaryCertificationHabilitation = new ComplementaryCertificationHabilitation({
complementaryCertificationId: parseInt(complementaryCertificationId),
certificationCenterId,
});
return complementaryCertificationHabilitationRepository.save(complementaryCertificationHabilitation);
});
}

await certificationCenterForAdminRepository.update(certificationCenterInformation);
const updatedCertificationCenter = await centerRepository.getById({ id: certificationCenterId });

const dataProtectionOfficer = await _addOrUpdateDataProtectionOfficer({
certificationCenterId,
certificationCenterInformation,
dataProtectionOfficerRepository,
});

const updatedCertificationCenter = await centerRepository.getById({ id: certificationCenterId });
return CenterForAdminFactory.fromCenterAndDataProtectionOfficer({
center: updatedCertificationCenter,
dataProtectionOfficer,
Expand All @@ -82,8 +54,91 @@ const updateCertificationCenter = async function ({

export { updateCertificationCenter };

const _updateCenter = withTransaction(
/**
* @param {Object} params
* @param {CenterRepository} params.centerRepository
* @param {CertificationCenterForAdminRepository} params.certificationCenterForAdminRepository
* @param {ComplementaryCertificationHabilitationRepository} params.complementaryCertificationHabilitationRepository
*/
async ({
certificationCenterInformation,
certificationCenterId,
complementaryCertificationIds,
centerRepository,
certificationCenterForAdminRepository,
complementaryCertificationHabilitationRepository,
}) => {
const certificationCenter = await centerRepository.getById({
id: certificationCenterId,
});

_verifyCenterPilotFeaturesCompatibility({
currentCenter: certificationCenter,
newCenterData: certificationCenterInformation,
});

await certificationCenterForAdminRepository.update(certificationCenterInformation);

await _updateHabilitations({
certificationCenterId,
complementaryCertificationIds,
complementaryCertificationHabilitationRepository,
});
},
{ isolationLevel: 'repeatable read' },
);

const _verifyCenterPilotFeaturesCompatibility = ({ currentCenter, newCenterData }) => {
if (currentCenter.isComplementaryAlonePilot && !newCenterData.isV3Pilot) {
throw new CertificationCenterPilotFeaturesConflictError();
}
};

/**
* @param {Object} params
* @param {ComplementaryCertificationHabilitationRepository} params.complementaryCertificationHabilitationRepository
*/
const _updateHabilitations = async ({
certificationCenterId,
complementaryCertificationIds,
complementaryCertificationHabilitationRepository,
}) => {
await complementaryCertificationHabilitationRepository.deleteByCertificationCenterId(certificationCenterId);

if (complementaryCertificationIds) {
await bluebird.mapSeries(complementaryCertificationIds, (complementaryCertificationId) => {
const complementaryCertificationHabilitation = new ComplementaryCertificationHabilitation({
complementaryCertificationId: parseInt(complementaryCertificationId),
certificationCenterId,
});
return complementaryCertificationHabilitationRepository.save(complementaryCertificationHabilitation);
});
}
};

const _addOrUpdateDataProtectionOfficer = withTransaction(
/**
* @param {Object} params
* @param {DataProtectionOfficerRepository} params.dataProtectionOfficerRepository
*/
async ({ certificationCenterId, certificationCenterInformation, dataProtectionOfficerRepository }) => {
const dataProtectionOfficer = new DataProtectionOfficer({
firstName: certificationCenterInformation.dataProtectionOfficerFirstName ?? '',
lastName: certificationCenterInformation.dataProtectionOfficerLastName ?? '',
email: certificationCenterInformation.dataProtectionOfficerEmail ?? '',
certificationCenterId,
});

const dataProtectionOfficerFound = await dataProtectionOfficerRepository.get({
certificationCenterId,
});

if (dataProtectionOfficerFound) {
return dataProtectionOfficerRepository.update(dataProtectionOfficer);
}

return dataProtectionOfficerRepository.create(dataProtectionOfficer);
},
{ isolationLevel: 'repeatable read' },
);
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import _ from 'lodash';

import { knex } from '../../../db/knex-database-connection.js';
import { CenterForAdmin } from '../../../src/certification/enrolment/domain/models/CenterForAdmin.js';
import { DomainTransaction } from '../DomainTransaction.js';

const save = async function (certificationCenter) {
const knexConn = DomainTransaction.getConnection();
const data = _toDTO(certificationCenter);
const [certificationCenterCreated] = await knex('certification-centers').returning('*').insert(data);
const [certificationCenterCreated] = await knexConn('certification-centers').returning('*').insert(data);
return _toDomain(certificationCenterCreated);
};

const update = async function (certificationCenter) {
const knexConn = DomainTransaction.getConnection();
const data = _toDTO(certificationCenter);
return knex('certification-centers').update(data).where({ id: certificationCenter.id });
return knexConn('certification-centers').update(data).where({ id: certificationCenter.id });
};

export { save, update };
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { knex } from '../../../db/knex-database-connection.js';
import { DomainTransaction } from '../DomainTransaction.js';

const COMPLEMENTARY_CERTIFICATION_HABILITATIONS_TABLE_NAME = 'complementary-certification-habilitations';

const save = async function (complementaryCertification) {
const knexConn = DomainTransaction.getConnection();
const columnsToSave = {
complementaryCertificationId: complementaryCertification.complementaryCertificationId,
certificationCenterId: complementaryCertification.certificationCenterId,
};
return knex(COMPLEMENTARY_CERTIFICATION_HABILITATIONS_TABLE_NAME).insert(columnsToSave);
return knexConn(COMPLEMENTARY_CERTIFICATION_HABILITATIONS_TABLE_NAME).insert(columnsToSave);
};

const deleteByCertificationCenterId = async function (certificationCenterId) {
return knex(COMPLEMENTARY_CERTIFICATION_HABILITATIONS_TABLE_NAME).delete().where({ certificationCenterId });
const knexConn = DomainTransaction.getConnection();
return knexConn(COMPLEMENTARY_CERTIFICATION_HABILITATIONS_TABLE_NAME).delete().where({ certificationCenterId });
};

export { deleteByCertificationCenterId, save };
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ async function batchAddDataProtectionOfficerToOrganization(
}

async function get({ organizationId = null, certificationCenterId = null }) {
const [dataProtectionOfficerRow] = await knex(DATA_PROTECTION_OFFICERS_TABLE_NAME)
const knexConn = DomainTransaction.getConnection();
const [dataProtectionOfficerRow] = await knexConn(DATA_PROTECTION_OFFICERS_TABLE_NAME)
.where({ organizationId, certificationCenterId })
.returning('*');

Expand Down Expand Up @@ -43,16 +44,16 @@ async function create(dataProtectionOfficer, { knexTransaction } = DomainTransac
async function update(dataProtectionOfficer) {
const { firstName, lastName, email, organizationId, certificationCenterId } = dataProtectionOfficer;
const updatedAt = new Date();
const knexConn = DomainTransaction.getConnection();

const query = knex(DATA_PROTECTION_OFFICERS_TABLE_NAME).update({
const query = knexConn(DATA_PROTECTION_OFFICERS_TABLE_NAME).update({
firstName,
lastName,
email,
updatedAt,
});

if (organizationId) query.where({ organizationId });
else query.where({ certificationCenterId });
organizationId ? query.where({ organizationId }) : query.where({ certificationCenterId });

const [dataProtectionOfficerRow] = await query.returning('*');

Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
import { knex } from '../../../../../db/knex-database-connection.js';
import { DomainTransaction } from '../../../../shared/domain/DomainTransaction.js';
import { NotFoundError } from '../../../../shared/domain/errors.js';
import { CERTIFICATION_FEATURES } from '../../../shared/domain/constants.js';
import { Center } from '../../domain/models/Center.js';
import { Habilitation } from '../../domain/models/Habilitation.js';

const getById = async ({ id }) => {
const center = await knex
const knexConn = DomainTransaction.getConnection();
const center = await knexConn
.select({
id: 'certification-centers.id',
name: 'certification-centers.name',
type: 'certification-centers.type',
externalId: 'certification-centers.externalId',
habilitations: knex.raw(
habilitations: knexConn.raw(
`json_agg(json_build_object(
'complementaryCertificationId', "complementary-certification-habilitations"."complementaryCertificationId",
'key', "complementary-certifications"."key",
'label', "complementary-certifications"."label"
))`,
),
features: knex.raw('array_remove(array_agg(DISTINCT "certificationCenterFeatures"."key"), NULL)'),
features: knexConn.raw('array_remove(array_agg(DISTINCT "certificationCenterFeatures"."key"), NULL)'),
createdAt: 'certification-centers.createdAt',
updatedAt: 'certification-centers.updatedAt',
isV3Pilot: 'certification-centers.isV3Pilot',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,12 @@ describe('Integration | UseCases | update-certification-center', function () {
it('should update certification center and his data protection officer information', async function () {
// given
const certificationCenterId = databaseBuilder.factory.buildCertificationCenter().id;
const anotherCertificationCenterId = databaseBuilder.factory.buildCertificationCenter().id;

const feature = databaseBuilder.factory.buildFeature({
key: CERTIFICATION_FEATURES.CAN_REGISTER_FOR_A_COMPLEMENTARY_CERTIFICATION_ALONE.key,
});
databaseBuilder.factory.buildCertificationCenterFeature({
certificationCenterId: anotherCertificationCenterId,
featureId: feature.id,
});
databaseBuilder.factory.buildDataProtectionOfficer.withCertificationCenterId({
firstName: 'Aa',
lastName: 'Ab',
email: 'aa@example.net',
certificationCenterId,
}).id;

const complementaryCertification = databaseBuilder.factory.buildComplementaryCertification();
const certificationCenterInformation = domainBuilder.buildCenterForAdmin({
Expand Down

0 comments on commit 7f606c9

Please sign in to comment.