Skip to content

Commit

Permalink
[TECH] Migrer la route DELETE /api/admin/certification-centers/{id}/i…
Browse files Browse the repository at this point in the history
…nvitations/{certificationCenterInvitationId} (PIX-12623)

 #9073
  • Loading branch information
pix-service-auto-merge authored May 28, 2024
2 parents fb11a9c + e0c3a6f commit 27859d1
Show file tree
Hide file tree
Showing 18 changed files with 372 additions and 277 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,6 @@ const getCertificationCenterInvitation = async function (request) {
return certificationCenterInvitationSerializer.serialize(certificationCenterInvitation);
};

const cancelCertificationCenterInvitation = async function (request, h) {
const certificationCenterInvitationId = request.params.certificationCenterInvitationId;
await usecases.cancelCertificationCenterInvitation({ certificationCenterInvitationId });
return h.response().code(204);
};

const resendCertificationCenterInvitation = async function (request, h) {
const certificationCenterInvitationId = request.params.certificationCenterInvitationId;
const locale = requestResponseUtils.extractLocaleFromRequest(request);
Expand All @@ -49,7 +43,6 @@ const resendCertificationCenterInvitation = async function (request, h) {
const certificationCenterInvitationController = {
acceptCertificationCenterInvitation,
getCertificationCenterInvitation,
cancelCertificationCenterInvitation,
resendCertificationCenterInvitation,
};

Expand Down
36 changes: 2 additions & 34 deletions api/lib/application/certification-center-invitations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,11 @@ import Joi from 'joi';

import { securityPreHandlers } from '../../../src/shared/application/security-pre-handlers.js';
import { identifiersType } from '../../../src/shared/domain/types/identifiers-type.js';
import { certificationCenterInvitationAdminController } from '../../../src/team/application/certification-center-invitation/certification-center-invitation.admin.controller.js';
import { certificationCenterInvitationController } from './certification-center-invitation-controller.js';

const register = async function (server) {
const adminRoutes = [
{
method: 'DELETE',
path: '/api/admin/certification-center-invitations/{certificationCenterInvitationId}',
config: {
pre: [
{
method: (request, h) =>
securityPreHandlers.hasAtLeastOneAccessOf([
securityPreHandlers.checkAdminMemberHasRoleSuperAdmin,
securityPreHandlers.checkAdminMemberHasRoleCertif,
securityPreHandlers.checkAdminMemberHasRoleSupport,
securityPreHandlers.checkAdminMemberHasRoleMetier,
])(request, h),
assign: 'hasAuthorizationToAccessAdminScope',
},
],
validate: {
params: Joi.object({
certificationCenterInvitationId: identifiersType.certificationCenterInvitationId,
}),
},
handler: certificationCenterInvitationController.cancelCertificationCenterInvitation,
tags: ['api', 'admin', 'invitations', 'cancel'],
notes: [
"- **Cette route est restreinte aux utilisateurs authentifiés ayant les droits d'accès**\n" +
"- Elle permet d'annuler une invitation envoyée mais non acceptée encore.",
],
},
},
];

server.route([
...adminRoutes,
{
method: 'POST',
path: '/api/certification-center-invitations/{id}/accept',
Expand Down Expand Up @@ -87,7 +55,7 @@ const register = async function (server) {
method: 'DELETE',
path: '/api/certification-center-invitations/{certificationCenterInvitationId}',
config: {
handler: certificationCenterInvitationController.cancelCertificationCenterInvitation,
handler: certificationCenterInvitationAdminController.cancelCertificationCenterInvitation,
pre: [
{
method: securityPreHandlers.checkUserIsAdminOfCertificationCenterWithCertificationCenterInvitationId,
Expand Down
10 changes: 0 additions & 10 deletions api/lib/domain/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,15 +165,6 @@ class UncancellableOrganizationInvitationError extends DomainError {
}
}

class UncancellableCertificationCenterInvitationError extends DomainError {
constructor(
message = "L'invitation à ce centre de certification ne peut pas être annulée.",
code = 'UNCANCELLABLE_CERTIFICATION_CENTER_INVITATION_CODE',
) {
super(message, code);
}
}

class CantImproveCampaignParticipationError extends DomainError {
constructor(message = 'Une campagne de collecte de profils ne peut pas être retentée.') {
super(message);
Expand Down Expand Up @@ -1140,7 +1131,6 @@ export {
TargetProfileCannotBeCreated,
TargetProfileInvalidError,
TooManyRows,
UncancellableCertificationCenterInvitationError,
UncancellableOrganizationInvitationError,
UnexpectedUserAccountError,
UnknownCountryForStudentEnrolmentError,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { extractLocaleFromRequest } from '../../../lib/infrastructure/utils/request-response-utils.js';
import { usecases } from '../domain/usecases/index.js';
import { certificationCenterInvitationSerializer } from '../infrastructure/serializers/jsonapi/certification-center-invitation-serializer.js';

const sendInvitations = async function (request, h) {
const certificationCenterId = request.params.certificationCenterId;
const emails = request.payload.data.attributes.emails;
const locale = extractLocaleFromRequest(request);

await usecases.createOrUpdateCertificationCenterInvitation({ certificationCenterId, emails, locale });

import { usecases } from '../../domain/usecases/index.js';
import { certificationCenterInvitationSerializer } from '../../infrastructure/serializers/jsonapi/certification-center-invitation-serializer.js';

/**
*
* @callback cancelCertificationCenterInvitation
* @param request
* @param h
* @returns {Promise<void>}
*/
const cancelCertificationCenterInvitation = async function (request, h) {
const certificationCenterInvitationId = request.params.certificationCenterInvitationId;
await usecases.cancelCertificationCenterInvitation({ certificationCenterInvitationId });
return h.response().code(204);
};

Expand All @@ -32,6 +34,7 @@ const sendInvitationForAdmin = async function (request, h, dependencies = { cert
return h.response(serializedCertificationCenterInvitation);
};

const certificationCenterInvitationController = { sendInvitations, sendInvitationForAdmin };

export { certificationCenterInvitationController };
export const certificationCenterInvitationAdminController = {
cancelCertificationCenterInvitation,
sendInvitationForAdmin,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import Joi from 'joi';

import { securityPreHandlers } from '../../../shared/application/security-pre-handlers.js';
import { identifiersType } from '../../../shared/domain/types/identifiers-type.js';
import { certificationCenterInvitationAdminController } from './certification-center-invitation.admin.controller.js';

export const certificationCenterInvitationAdminRoutes = [
{
method: 'POST',
path: '/api/admin/certification-centers/{certificationCenterId}/invitations',
config: {
pre: [
{
method: (request, h) =>
securityPreHandlers.hasAtLeastOneAccessOf([
securityPreHandlers.checkAdminMemberHasRoleSuperAdmin,
securityPreHandlers.checkAdminMemberHasRoleCertif,
securityPreHandlers.checkAdminMemberHasRoleSupport,
securityPreHandlers.checkAdminMemberHasRoleMetier,
])(request, h),
assign: 'hasAuthorizationToAccessAdminScope',
},
],
handler: certificationCenterInvitationAdminController.sendInvitationForAdmin,
validate: {
params: Joi.object({
certificationCenterId: identifiersType.certificationCenterId,
}),
options: {
allowUnknown: true,
},
payload: Joi.object({
data: {
attributes: {
email: Joi.string().email().required(),
language: Joi.string().valid('fr-fr', 'fr', 'en'),
role: Joi.string().valid('ADMIN', 'MEMBER').allow(null),
},
},
}),
},
notes: [
"- **Cette route est restreinte aux utilisateurs authentifiés ayant les droits d'accès**\n" +
"- Elle permet à un administrateur d'inviter des personnes, déjà utilisateurs de Pix ou non, à être membre d'un centre de certification, via leur **email**",
],
tags: ['api', 'admin', 'invitations', 'certification-center'],
},
},
{
method: 'DELETE',
path: '/api/admin/certification-center-invitations/{certificationCenterInvitationId}',
config: {
pre: [
{
method: (request, h) =>
securityPreHandlers.hasAtLeastOneAccessOf([
securityPreHandlers.checkAdminMemberHasRoleSuperAdmin,
securityPreHandlers.checkAdminMemberHasRoleCertif,
securityPreHandlers.checkAdminMemberHasRoleSupport,
securityPreHandlers.checkAdminMemberHasRoleMetier,
])(request, h),
assign: 'hasAuthorizationToAccessAdminScope',
},
],
validate: {
params: Joi.object({
certificationCenterInvitationId: identifiersType.certificationCenterInvitationId,
}),
},
handler: certificationCenterInvitationAdminController.cancelCertificationCenterInvitation,
tags: ['api', 'admin', 'invitations', 'certification-center', 'cancel'],
notes: [
"- **Cette route est restreinte aux utilisateurs authentifiés ayant les droits d'accès**\n" +
"- Elle permet d'annuler une invitation envoyée mais non acceptée encore.",
],
},
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { extractLocaleFromRequest } from '../../../../lib/infrastructure/utils/request-response-utils.js';
import { usecases } from '../../domain/usecases/index.js';

/**
* @callback sendInvitations
* @param request
* @param h
* @returns {Promise<void>}
*/
const sendInvitations = async function (request, h) {
const certificationCenterId = request.params.certificationCenterId;
const emails = request.payload.data.attributes.emails;
const locale = extractLocaleFromRequest(request);

await usecases.createOrUpdateCertificationCenterInvitation({ certificationCenterId, emails, locale });

return h.response().code(204);
};

export const certificationCenterInvitationController = { sendInvitations };
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Joi from 'joi';

import { securityPreHandlers } from '../../../shared/application/security-pre-handlers.js';
import { identifiersType } from '../../../shared/domain/types/identifiers-type.js';
import { certificationCenterInvitationController } from './certification-center-invitation.controller.js';

export const certificationCenterInvitationRoutes = [
{
method: 'POST',
path: '/api/certification-centers/{certificationCenterId}/invitations',
config: {
pre: [
{
method: securityPreHandlers.checkUserIsAdminOfCertificationCenter,
assign: 'isAdminOfCertificationCenter',
},
],
validate: {
params: Joi.object({
certificationCenterId: identifiersType.certificationCenterId,
}),
payload: Joi.object({
data: {
attributes: {
emails: Joi.array().items(Joi.string().email()).required(),
},
},
}),
},
handler: certificationCenterInvitationController.sendInvitations,
notes: [
'- **Cette route est restreinte aux utilisateurs authentifiés en tant que responsables à un centre de certification**\n' +
"- Elle permet d'inviter des personnes, déjà utilisateurs de Pix ou non, à être membre d'un centre de certification via leur **email**",
],
tags: ['api', 'certification-center', 'invitations'],
},
},
];
81 changes: 3 additions & 78 deletions api/src/team/application/routes.js
Original file line number Diff line number Diff line change
@@ -1,83 +1,8 @@
import Joi from 'joi';

import { securityPreHandlers } from '../../shared/application/security-pre-handlers.js';
import { identifiersType } from '../../shared/domain/types/identifiers-type.js';
import { certificationCenterInvitationController } from './certification-center-invitation.controller.js';
import { certificationCenterInvitationAdminRoutes } from './certification-center-invitation/certification-center-invitation.admin.route.js';
import { certificationCenterInvitationRoutes } from './certification-center-invitation/certification-center-invitation.route.js';

const register = async function (server) {
server.route([
{
method: 'POST',
path: '/api/certification-centers/{certificationCenterId}/invitations',
config: {
pre: [
{
method: securityPreHandlers.checkUserIsAdminOfCertificationCenter,
assign: 'isAdminOfCertificationCenter',
},
],
validate: {
params: Joi.object({
certificationCenterId: identifiersType.certificationCenterId,
}),
payload: Joi.object({
data: {
attributes: {
emails: Joi.array().items(Joi.string().email()).required(),
},
},
}),
},
handler: certificationCenterInvitationController.sendInvitations,
notes: [
'- **Cette route est restreinte aux utilisateurs authentifiés en tant que responsables à un centre de certification**\n' +
"- Elle permet d'inviter des personnes, déjà utilisateurs de Pix ou non, à être membre d'un centre de certification via leur **email**",
],
tags: ['api', 'certification-center', 'invitations'],
},
},
{
method: 'POST',
path: '/api/admin/certification-centers/{certificationCenterId}/invitations',
config: {
pre: [
{
method: (request, h) =>
securityPreHandlers.hasAtLeastOneAccessOf([
securityPreHandlers.checkAdminMemberHasRoleSuperAdmin,
securityPreHandlers.checkAdminMemberHasRoleCertif,
securityPreHandlers.checkAdminMemberHasRoleSupport,
securityPreHandlers.checkAdminMemberHasRoleMetier,
])(request, h),
assign: 'hasAuthorizationToAccessAdminScope',
},
],
handler: certificationCenterInvitationController.sendInvitationForAdmin,
validate: {
params: Joi.object({
certificationCenterId: identifiersType.certificationCenterId,
}),
options: {
allowUnknown: true,
},
payload: Joi.object({
data: {
attributes: {
email: Joi.string().email().required(),
language: Joi.string().valid('fr-fr', 'fr', 'en'),
role: Joi.string().valid('ADMIN', 'MEMBER').allow(null),
},
},
}),
},
notes: [
"- **Cette route est restreinte aux utilisateurs authentifiés ayant les droits d'accès**\n" +
"- Elle permet à un administrateur d'inviter des personnes, déjà utilisateurs de Pix ou non, à être membre d'un centre de certification, via leur **email**",
],
tags: ['api', 'invitations', 'certification-center'],
},
},
]);
server.route([...certificationCenterInvitationRoutes, ...certificationCenterInvitationAdminRoutes]);
};

const name = 'team-api';
Expand Down
10 changes: 10 additions & 0 deletions api/src/team/domain/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { DomainError } from '../../shared/domain/errors.js';

export class UncancellableCertificationCenterInvitationError extends DomainError {
constructor(
message = "L'invitation à ce centre de certification ne peut pas être annulée.",
code = 'UNCANCELLABLE_CERTIFICATION_CENTER_INVITATION_CODE',
) {
super(message, code);
}
}
4 changes: 1 addition & 3 deletions api/src/team/domain/models/CertificationCenterInvitation.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const validationScheme = Joi.object({
code: Joi.string().optional(),
});

class CertificationCenterInvitation {
export class CertificationCenterInvitation {
constructor({ id, email, updatedAt, role, status, certificationCenterId, certificationCenterName, code } = {}) {
this.id = id;
this.email = email;
Expand Down Expand Up @@ -80,5 +80,3 @@ class CertificationCenterInvitation {

CertificationCenterInvitation.StatusType = statuses;
CertificationCenterInvitation.Roles = roles;

export { CertificationCenterInvitation };
Loading

0 comments on commit 27859d1

Please sign in to comment.