Skip to content

Commit

Permalink
[FEATURE] Ajout de l'API getLegalDocumentStatusByUserId dans legal-do…
Browse files Browse the repository at this point in the history
…cument context (PIX-15581)

 #10786
  • Loading branch information
pix-service-auto-merge authored Dec 20, 2024
2 parents fb63425 + 10c267c commit fa5773b
Show file tree
Hide file tree
Showing 25 changed files with 730 additions and 72 deletions.
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

0 comments on commit fa5773b

Please sign in to comment.