Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Ajout de l'API getLegalDocumentStatusByUserId dans legal-document context (PIX-15581) #10786

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { databaseBuffer } from '../database-buffer.js';

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

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

/**
* Accept legal document by user id.
* Accepts a legal document for a user by their ID.
*
* @param{string} params.service
* @param{string} params.type
* @param{string} params.userId
* @param {Object} params - The parameters.
* @param {string} params.userId - The ID of the user.
* @param {string} params.service - The service associated with the legal document. (e.g. 'pix-orga')
* @param {string} params.type - The type of the legal document. (e.g. 'TOS')
* @returns {Promise<void>} - A promise that resolves when the legal document is accepted.
*/
const acceptLegalDocumentByUserId = async ({ userId, service, type }) => {
return usecases.acceptLegalDocumentByUserId({ userId, service, type });
};

/**
* Gets the status of a legal document for a user by their ID.
*
* @returns {Promise<void>}
* @param {Object} params - The parameters.
* @param {string} params.userId - The ID of the user.
* @param {string} params.service - The service associated with the legal document. (e.g. 'pix-orga')
* @param {string} params.type - The type of the legal document. (e.g. 'TOS')
* @returns {Promise<LegalDocumentStatus>} - A promise that resolves with the status of the legal document.
*/
const acceptLegalDocumentByUserId = async ({ type, service, userId }) => {
return usecases.acceptLegalDocumentByUserId({ type, service, userId });
const getLegalDocumentStatusByUserId = async ({ userId, service, type }) => {
return usecases.getLegalDocumentStatusByUserId({ userId, service, type });
};

export { acceptLegalDocumentByUserId };
export { acceptLegalDocumentByUserId, getLegalDocumentStatusByUserId };
19 changes: 12 additions & 7 deletions api/src/legal-documents/domain/errors.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { DomainError } from '../../shared/domain/errors.js';

class LegalDocumentInvalidDateError extends DomainError {
constructor({
code = 'LEGAL_DOCUMENT_INVALID_DATE',
message = 'Document version must not be before or equal to same document type and service',
} = {}) {
super(message);
this.code = code;
constructor() {
super(
'Document version must not be before or equal to same document service and type',
'LEGAL_DOCUMENT_INVALID_DATE',
);
}
}

export { LegalDocumentInvalidDateError };
class LegalDocumentVersionNotFoundError extends DomainError {
constructor() {
super('No legal document version found for service and type', 'LEGAL_DOCUMENT_VERSION_NOT_FOUND');
}
}

export { LegalDocumentInvalidDateError, LegalDocumentVersionNotFoundError };
13 changes: 11 additions & 2 deletions api/src/legal-documents/domain/models/LegalDocument.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import dayjs from 'dayjs';

export class LegalDocument {
constructor({ id, type, service, versionAt }) {
constructor({ id, service, type, versionAt }) {
this.id = id;
this.type = type;
this.service = service;
this.type = type;
this.versionAt = versionAt;
}

buildDocumentPath() {
const service = this.service.toLowerCase();
const type = this.type.toLowerCase();
const versionAt = dayjs(this.versionAt).format('YYYY-MM-DD');
return `${service}-${type}-${versionAt}`;
}
}
57 changes: 57 additions & 0 deletions api/src/legal-documents/domain/models/LegalDocumentStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
export const STATUS = {
ACCEPTED: 'accepted',
REQUESTED: 'requested',
UPDATE_REQUESTED: 'update-requested',
};

export class LegalDocumentStatus {
constructor({ status, acceptedAt, documentPath }) {
this.status = status;
this.acceptedAt = acceptedAt;
this.documentPath = documentPath;
}

/**
* Builds a LegalDocumentStatus based on legacy PixOrga CGU.
*
* @param {Object} userPixOrgaCgu - The user object.
* @param {boolean} userPixOrgaCgu.pixOrgaTermsOfServiceAccepted - Indicates if the PixOrga terms of service are accepted.
* @param {Date} userPixOrgaCgu.lastPixOrgaTermsOfServiceValidatedAt - The date when the PixOrga terms of service were last validated.
* @returns {LegalDocumentStatus} The legal document status.
*/
static buildForLegacyPixOrgaCgu(userPixOrgaCgu) {
const LEGACY_PIXORGA_TOS_PATH = 'pix-orga-tos-2024-01-02';
const { pixOrgaTermsOfServiceAccepted, lastPixOrgaTermsOfServiceValidatedAt } = userPixOrgaCgu;

return new LegalDocumentStatus({
status: pixOrgaTermsOfServiceAccepted ? STATUS.ACCEPTED : STATUS.REQUESTED,
acceptedAt: lastPixOrgaTermsOfServiceValidatedAt,
documentPath: LEGACY_PIXORGA_TOS_PATH,
});
}

/**
* Builds a LegalDocumentStatus based on the last document version and user acceptance.
*
* @param {Object} lastDocumentVersion - The last document version object.
* @param {string} lastDocumentVersion.id - The ID of the last document version.
* @param {Object} lastUserAcceptance - The last user acceptance object.
* @param {string} lastUserAcceptance.legalDocumentVersionId - The ID of the accepted legal document version.
* @param {Date} lastUserAcceptance.acceptedAt - The date when the document was accepted.
* @returns {LegalDocumentStatus} The legal document status.
*/
static build(lastDocumentVersion, lastUserAcceptance) {
const documentPath = lastDocumentVersion.buildDocumentPath();

if (!lastUserAcceptance) {
return new LegalDocumentStatus({ status: STATUS.REQUESTED, acceptedAt: null, documentPath });
}

const { legalDocumentVersionId, acceptedAt } = lastUserAcceptance;
if (lastDocumentVersion.id === legalDocumentVersionId) {
return new LegalDocumentStatus({ status: STATUS.ACCEPTED, acceptedAt, documentPath });
}

return new LegalDocumentStatus({ status: STATUS.UPDATE_REQUESTED, acceptedAt: null, documentPath });
}
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,39 @@
import { LegalDocumentService } from '../models/LegalDocumentService.js';
import { LegalDocumentType } from '../models/LegalDocumentType.js';

const { TOS } = LegalDocumentType.VALUES;
const { PIX_ORGA } = LegalDocumentService.VALUES;
const { TOS } = LegalDocumentType.VALUES;

/**
* 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.
* @param {string} params.service - The service of the legal document.
* @param {string} params.type - The type of the legal document.
* @returns {Promise<void>} A promise that resolves when the operation is complete.
*/
const acceptLegalDocumentByUserId = async ({
type,
service,
userId,
service,
type,
userRepository,
legalDocumentRepository,
userAcceptanceRepository,
logger,
}) => {
LegalDocumentType.assert(type);
LegalDocumentService.assert(service);

// legacy document acceptance
if (type === TOS && service === PIX_ORGA) {
await userRepository.setPixOrgaCguByUserId(userId);
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@ import { LegalDocumentType } from '../models/LegalDocumentType.js';
* Creates a new legal document.
*
* @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.type - The type of the legal document.
* @param {string} params.versionAt - Version date of the new legal document.
* @returns {Promise<LegalDocument>} A promise that resolves the new legal document.
*/
const createLegalDocument = async ({ type, service, versionAt, legalDocumentRepository }) => {
LegalDocumentType.assert(type);
const createLegalDocument = async ({ service, type, versionAt, legalDocumentRepository }) => {
LegalDocumentService.assert(service);
LegalDocumentType.assert(type);

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

if (lastDocument && lastDocument.versionAt >= versionAt) {
throw new LegalDocumentInvalidDateError();
}

return legalDocumentRepository.create({ type, service, versionAt });
return legalDocumentRepository.create({ service, type, versionAt });
};

export { createLegalDocument };
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { config } from '../../../shared/config.js';
import { LegalDocumentVersionNotFoundError } from '../errors.js';
import { LegalDocumentService } from '../models/LegalDocumentService.js';
import { LegalDocumentStatus } from '../models/LegalDocumentStatus.js';
import { LegalDocumentType } from '../models/LegalDocumentType.js';

/**
* Gets the legal document status by user ID.
*
* @param {Object} params - The parameters.
* @param {string} params.userId - The user ID.
* @param {string} params.service - The service associated with the legal document.
* @param {string} params.type - The type of the legal document.
* @returns {Promise<LegalDocumentStatus>} The legal document status.
* @throws {Error} If no legal document version is found for the type and service.
*/
const getLegalDocumentStatusByUserId = async ({
userId,
service,
type,
userRepository,
legalDocumentRepository,
userAcceptanceRepository,
featureToggles = config.featureToggles,
}) => {
LegalDocumentService.assert(service);
LegalDocumentType.assert(type);

const { isLegalDocumentsVersioningEnabled } = featureToggles;

if (!isLegalDocumentsVersioningEnabled) {
const user = await userRepository.findPixOrgaCgusByUserId(userId);
return LegalDocumentStatus.buildForLegacyPixOrgaCgu(user);
}

const lastLegalDocument = await legalDocumentRepository.findLastVersionByTypeAndService({ service, type });

if (!lastLegalDocument) throw new LegalDocumentVersionNotFoundError();

const lastUserAcceptance = await userAcceptanceRepository.findLastForLegalDocument({ userId, service, type });

return LegalDocumentStatus.build(lastLegalDocument, lastUserAcceptance);
};

export { getLegalDocumentStatusByUserId };
3 changes: 1 addition & 2 deletions api/src/legal-documents/domain/usecases/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
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';
Expand All @@ -17,7 +16,7 @@ const repositories = {
userRepository,
};

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

const usecasesWithoutInjectedDependencies = {
...(await importNamedExportsFromDirectory({ path: join(path, './'), ignoredFileNames: ['index.js'] })),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ const TABLE_NAME = 'legal-document-versions';
* 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.
* @param {string} params.type - The type of 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 findLastVersionByTypeAndService = async ({ service, type }) => {
const knexConnection = DomainTransaction.getConnection();
const documentVersionDto = await knexConnection(TABLE_NAME)
.where({ type, service })
.where({ service, type })
.orderBy('versionAt', 'desc')
.first();

Expand All @@ -27,15 +27,15 @@ const findLastVersionByTypeAndService = async ({ type, service }) => {
* Creates a new legal document in the database.
*
* @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.
* @param {string} params.type - The type of the legal document.
* @param {Date} params.versionAt - The date of the legal document version.
* @returns {Promise<LegalDocument>} The newly created legal document.
*/
const create = async ({ type, service, versionAt }) => {
const create = async ({ service, type, versionAt }) => {
const knexConnection = DomainTransaction.getConnection();

const [documentVersionDto] = await knexConnection(TABLE_NAME).insert({ type, service, versionAt }).returning('*');
const [documentVersionDto] = await knexConnection(TABLE_NAME).insert({ service, type, versionAt }).returning('*');

return new LegalDocument(documentVersionDto);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { DomainTransaction } from '../../../shared/domain/DomainTransaction.js';

const TABLE_NAME = 'legal-document-version-user-acceptances';

/**
* Creates a new user acceptance record for a legal document version.
*
Expand All @@ -10,7 +12,34 @@ import { DomainTransaction } from '../../../shared/domain/DomainTransaction.js';
*/
const create = async ({ userId, legalDocumentVersionId }) => {
const knexConnection = DomainTransaction.getConnection();
await knexConnection('legal-document-version-user-acceptances').insert({ userId, legalDocumentVersionId });
await knexConnection(TABLE_NAME).insert({ userId, legalDocumentVersionId });
};

/**
* Finds the last user acceptance record for a specific legal document type and service.
*
* @param {Object} params - The parameters for finding the user acceptance.
* @param {string} params.userId - The ID of the user.
* @param {string} params.service - The service associated with the legal document.
* @param {string} params.type - The type of the legal document.
* @returns {Promise<Object|null>} A promise that resolves to the user acceptance record or null if not found.
*/
const findLastForLegalDocument = async ({ userId, service, type }) => {
const knexConnection = DomainTransaction.getConnection();
const userAcceptanceDto = await knexConnection(TABLE_NAME)
.select('userId', 'legalDocumentVersionId', 'acceptedAt')
.join(
'legal-document-versions',
'legal-document-version-user-acceptances.legalDocumentVersionId',
'legal-document-versions.id',
)
.where({ userId, service, type })
.orderBy('versionAt', 'desc')
.first();

if (!userAcceptanceDto) return null;

return userAcceptanceDto;
};

export { create };
export { create, findLastForLegalDocument };
Loading
Loading