Skip to content

Commit

Permalink
[FEATURE] Création de l'API legal documents (PIX-15580)
Browse files Browse the repository at this point in the history
  • Loading branch information
pix-service-auto-merge authored Dec 10, 2024
2 parents 289e174 + 3b46f23 commit 9b7078d
Show file tree
Hide file tree
Showing 15 changed files with 382 additions and 0 deletions.
15 changes: 15 additions & 0 deletions api/db/database-builder/factory/build-legal-document-version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { databaseBuffer } from '../database-buffer.js';

const buildLegalDocumentVersion = function ({
id = databaseBuffer.getNextId(),
type,
service,
versionAt = new Date(),
} = {}) {
return databaseBuffer.pushInsertable({
tableName: 'legal-document-versions',
values: { id, type, service, versionAt },
});
};

export { buildLegalDocumentVersion };
16 changes: 16 additions & 0 deletions api/src/legal-documents/application/api/legal-documents-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { usecases } from '../../domain/usecases/index.js';

/**
* Accept legal document by user id.
*
* @param{string} params.service
* @param{string} params.type
* @param{string} params.userId
*
* @returns {Promise<void>}
*/
const acceptLegalDocumentByUserId = async ({ type, service, userId }) => {
return usecases.acceptLegalDocumentByUserId({ type, service, userId });
};

export { acceptLegalDocumentByUserId };
18 changes: 18 additions & 0 deletions api/src/legal-documents/domain/models/LegalDocument.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export class LegalDocument {
static TYPES = {
TOS: 'TOS',
};

static SERVICES = {
PIX_APP: 'pix-app',
PIX_ORGA: 'pix-orga',
PIX_CERTIF: 'pix-certif',
};

constructor({ id, type, service, versionAt }) {
this.id = id;
this.type = type;
this.service = service;
this.versionAt = versionAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { LegalDocument } from '../models/LegalDocument.js';

const { TOS } = LegalDocument.TYPES;
const { PIX_ORGA } = LegalDocument.SERVICES;

/**
* Accepts a legal document by user ID.
*
* @param {Object} params - The parameters.
* @param {string} params.type - The type of the legal document.
* @param {string} params.service - The service of the legal document.
* @param {string} params.userId - The ID of the user.
* @returns {Promise<void>} A promise that resolves when the operation is complete.
*/
const acceptLegalDocumentByUserId = async ({
type,
service,
userId,
userRepository,
legalDocumentRepository,
userAcceptanceRepository,
logger,
}) => {
// legacy document acceptance
if (type === TOS && service === PIX_ORGA) {
await userRepository.setPixOrgaCguByUserId(userId);
}

// new document acceptance
const legalDocument = await legalDocumentRepository.findLastVersionByTypeAndService({ type, service });
if (!legalDocument) {
logger.warn(`No legal document found for type: ${type} and service: ${service}`);
return;
}

await userAcceptanceRepository.create({ userId, legalDocumentVersionId: legalDocument.id });
};

export { acceptLegalDocumentByUserId };
28 changes: 28 additions & 0 deletions api/src/legal-documents/domain/usecases/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

import { config } from '../../../shared/config.js';
import { injectDependencies } from '../../../shared/infrastructure/utils/dependency-injection.js';
import { importNamedExportsFromDirectory } from '../../../shared/infrastructure/utils/import-named-exports-from-directory.js';
import { logger } from '../../../shared/infrastructure/utils/logger.js';
import * as legalDocumentRepository from '../../infrastructure/repositories/legal-document.repository.js';
import * as userRepository from '../../infrastructure/repositories/user.repository.js';
import * as userAcceptanceRepository from '../../infrastructure/repositories/user-acceptance.repository.js';

const path = dirname(fileURLToPath(import.meta.url));

const repositories = {
legalDocumentRepository,
userAcceptanceRepository,
userRepository,
};

const dependencies = Object.assign({ config, logger }, repositories);

const usecasesWithoutInjectedDependencies = {
...(await importNamedExportsFromDirectory({ path: join(path, './'), ignoredFileNames: ['index.js'] })),
};

const usecases = injectDependencies(usecasesWithoutInjectedDependencies, dependencies);

export { usecases };
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { DomainTransaction } from '../../../shared/domain/DomainTransaction.js';
import { LegalDocument } from '../../domain/models/LegalDocument.js';

/**
* Retrieves the latest version of a legal document by type and service.
*
* @param {Object} params - The parameters.
* @param {string} params.type - The type of the legal document.
* @param {string} params.service - The service associated with the legal document.
* @returns {Promise<LegalDocument|null>} The latest version of the legal document or null if not found.
*/
const findLastVersionByTypeAndService = async ({ type, service }) => {
const knexConnection = DomainTransaction.getConnection();
const documentVersionDto = await knexConnection('legal-document-versions')
.where({ type, service })
.orderBy('versionAt', 'desc')
.first();

if (!documentVersionDto) return null;

return new LegalDocument(documentVersionDto);
};

export { findLastVersionByTypeAndService };
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { DomainTransaction } from '../../../shared/domain/DomainTransaction.js';

/**
* Creates a new user acceptance record for a legal document version.
*
* @param {Object} params - The parameters for creating the user acceptance.
* @param {string} params.userId - The ID of the user.
* @param {string} params.legalDocumentVersionId - The ID of the legal document version.
* @returns {Promise<void>} A promise that resolves when the record is created.
*/
const create = async ({ userId, legalDocumentVersionId }) => {
const knexConnection = DomainTransaction.getConnection();
await knexConnection('legal-document-version-user-acceptances').insert({ userId, legalDocumentVersionId });
};

export { create };
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { DomainTransaction } from '../../../shared/domain/DomainTransaction.js';

/**
* Updates the Pix terms of service acceptance status for a user.
*
* @param {string} userId - The ID of the user to update.
* @returns {Promise<void>} A promise that resolves when the update is complete.
*/
const setPixOrgaCguByUserId = async (userId) => {
const knexConnection = DomainTransaction.getConnection();
await knexConnection('users').where('id', userId).update({
pixOrgaTermsOfServiceAccepted: true,
lastPixOrgaTermsOfServiceValidatedAt: new Date(),
});
};

export { setPixOrgaCguByUserId };
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as legalDocumentsApi from '../../../../../src/legal-documents/application/api/legal-documents-api.js';
import { LegalDocument } from '../../../../../src/legal-documents/domain/models/LegalDocument.js';
import { databaseBuilder, expect, knex } from '../../../../test-helper.js';

const { TOS } = LegalDocument.TYPES;
const { PIX_ORGA } = LegalDocument.SERVICES;

describe('Integration | Privacy | Application | Api | legal documents', function () {
describe('#acceptLegalDocumentByUserId', function () {
it('accepts the latest legal document version by user id ', async function () {
// given
const userId = databaseBuilder.factory.buildUser().id;
const latestDocument = databaseBuilder.factory.buildLegalDocumentVersion({ type: TOS, service: PIX_ORGA });
await databaseBuilder.commit();

// when
await legalDocumentsApi.acceptLegalDocumentByUserId({ userId, type: TOS, service: PIX_ORGA });

// then
const userAcceptance = await knex('legal-document-version-user-acceptances')
.where({ userId })
.where('legalDocumentVersionId', latestDocument.id)
.first();

expect(userAcceptance).to.exist;
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { LegalDocument } from '../../../../../src/legal-documents/domain/models/LegalDocument.js';
import { usecases } from '../../../../../src/legal-documents/domain/usecases/index.js';
import { databaseBuilder, expect, knex, sinon } from '../../../../test-helper.js';

const { TOS } = LegalDocument.TYPES;
const { PIX_ORGA } = LegalDocument.SERVICES;

describe('Integration | Legal documents | Domain | Use case | accept-legal-document-by-user-id', function () {
it('accepts the lastest legal document version for a user', async function () {
// given
const user = databaseBuilder.factory.buildUser();
databaseBuilder.factory.buildLegalDocumentVersion({
type: TOS,
service: PIX_ORGA,
versionAt: new Date('2021-01-01'),
});
const document = databaseBuilder.factory.buildLegalDocumentVersion({ type: TOS, service: PIX_ORGA });
await databaseBuilder.commit();

// when
await usecases.acceptLegalDocumentByUserId({ userId: user.id, type: TOS, service: PIX_ORGA });

// then
const userAcceptance = await knex('legal-document-version-user-acceptances')
.where('userId', user.id)
.where('legalDocumentVersionId', document.id)
.first();
expect(userAcceptance).to.exist;
});

context('when the legal document is the Terms of Service for Pix Orga', function () {
it('accepts the Pix Orga CGUs in the legacy and legal document model', async function () {
// given
const user = databaseBuilder.factory.buildUser({ pixOrgaTermsOfServiceAccepted: false });
databaseBuilder.factory.buildLegalDocumentVersion({ type: TOS, service: PIX_ORGA });

await databaseBuilder.commit();

// when
await usecases.acceptLegalDocumentByUserId({ userId: user.id, type: TOS, service: PIX_ORGA });

// then
const updatedUser = await knex('users').where('id', user.id).first();
expect(updatedUser.pixOrgaTermsOfServiceAccepted).to.equal(true);
});

it('logs an error, when no legal document is found', async function () {
// given
const user = databaseBuilder.factory.buildUser({ pixOrgaTermsOfServiceAccepted: false });
const loggerStub = { warn: sinon.stub() };
await databaseBuilder.commit();

// when
await usecases.acceptLegalDocumentByUserId({ userId: user.id, type: TOS, service: PIX_ORGA, logger: loggerStub });

// then
expect(loggerStub.warn).to.have.been.calledWith(
`No legal document found for type: ${TOS} and service: ${PIX_ORGA}`,
);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { LegalDocument } from '../../../../../src/legal-documents/domain/models/LegalDocument.js';
import * as legalDocumentRepository from '../../../../../src/legal-documents/infrastructure/repositories/legal-document.repository.js';
import { databaseBuilder, domainBuilder, expect } from '../../../../test-helper.js';

const { TOS } = LegalDocument.TYPES;
const { PIX_ORGA, PIX_APP } = LegalDocument.SERVICES;

describe('Integration | Legal document | Infrastructure | Repository | legal-document', function () {
describe('#findLastVersionByTypeAndService', function () {
it('returns the last legal document version by type and service', async function () {
// given
const type = TOS;
const service = PIX_ORGA;
databaseBuilder.factory.buildLegalDocumentVersion({
type,
service,
versionAt: new Date('2020-12-01'),
});
const expectedDocument = databaseBuilder.factory.buildLegalDocumentVersion({
type,
service,
versionAt: new Date('2024-12-01'),
});

databaseBuilder.factory.buildLegalDocumentVersion({
type,
service: PIX_APP,
versionAt: new Date('2024-12-01'),
});
await databaseBuilder.commit();

// when
const lastDocument = await legalDocumentRepository.findLastVersionByTypeAndService({ type, service });

// then
expect(lastDocument).to.deepEqualInstance(domainBuilder.buildLegalDocument(expectedDocument));
});

it('returns null when no document found', async function () {
// when
const lastDocument = await legalDocumentRepository.findLastVersionByTypeAndService({
type: 'toto',
service: 'tutu',
});

// then
expect(lastDocument).to.be.null;
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { LegalDocument } from '../../../../../src/legal-documents/domain/models/LegalDocument.js';
import * as userAcceptanceRepository from '../../../../../src/legal-documents/infrastructure/repositories/user-acceptance.repository.js';
import { databaseBuilder, expect, knex } from '../../../../test-helper.js';

const { TOS } = LegalDocument.TYPES;
const { PIX_ORGA } = LegalDocument.SERVICES;

describe('Integration | Legal document | Infrastructure | Repository | user-acceptance', function () {
describe('#create', function () {
it('creates an acceptance record for a user and legal document', async function () {
// given
const user = databaseBuilder.factory.buildUser();
const document = databaseBuilder.factory.buildLegalDocumentVersion({ type: TOS, service: PIX_ORGA });
await databaseBuilder.commit();

// when
await userAcceptanceRepository.create({ userId: user.id, legalDocumentVersionId: document.id });

// then
const userAcceptance = await knex('legal-document-version-user-acceptances')
.where('userId', user.id)
.where('legalDocumentVersionId', document.id)
.first();
expect(userAcceptance.acceptedAt).to.be.a('date');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as userRepository from '../../../../../src/legal-documents/infrastructure/repositories/user.repository.js';
import { databaseBuilder, expect, knex } from '../../../../test-helper.js';

describe('Integration | Legal document | Infrastructure | Repository | user', function () {
describe('#setPixOrgaCguByUserId', function () {
it('sets the Pix Orga CGU for a user id', async function () {
// given
const user = databaseBuilder.factory.buildUser({
pixOrgaTermsOfServiceAccepted: false,
lastPixOrgaTermsOfServiceValidatedAt: null,
});
await databaseBuilder.commit();

// when
await userRepository.setPixOrgaCguByUserId(user.id);

// then
const updatedUser = await knex('users').where('id', user.id).first();
expect(updatedUser.pixOrgaTermsOfServiceAccepted).to.equal(true);
expect(updatedUser.lastPixOrgaTermsOfServiceValidatedAt).to.be.a('date');
});
});
});
17 changes: 17 additions & 0 deletions api/tests/tooling/domain-builder/factory/build-legal-document.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { LegalDocument } from '../../../../src/legal-documents/domain/models/LegalDocument.js';

const buildLegalDocument = function ({
id = 123,
type = LegalDocument.TYPES.TOS,
service = LegalDocument.SERVICES.PIX_APP,
versionAt = new Date(),
} = {}) {
return new LegalDocument({
id,
type,
service,
versionAt,
});
};

export { buildLegalDocument };
Loading

0 comments on commit 9b7078d

Please sign in to comment.