Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/npm_and_yarn/axios-1.7.4
Browse files Browse the repository at this point in the history
  • Loading branch information
douglasduteil authored Sep 3, 2024
2 parents c47ef23 + 0d383c8 commit a5dcf95
Show file tree
Hide file tree
Showing 14 changed files with 198 additions and 24 deletions.
1 change: 1 addition & 0 deletions .github/workflows/end-to-end.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jobs:
- check_email_deliverability
- delete_account
- delete_totp
- join_and_moderation
- join_collectivite_territoriale_official_contact_domain
- join_must_confirm
- join_org_with_gouv_fr_domain
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ Afin de configurer votre module ou votre client OpenId Connect, vous trouverez c

Voici à quoi ressemble le bouton MonComptePro :

![](/assets/button-moncomptepro.svg)
![](/public/button-moncomptepro.svg)

Pour intègrer ce bouton sur votre service, 3 options s’offrent à vous.

Expand Down
Empty file.
10 changes: 10 additions & 0 deletions cypress/e2e/join_and_moderation/fixtures.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
INSERT INTO users
(id, email, email_verified, email_verified_at, encrypted_password, created_at, updated_at, given_name, family_name,
phone_number, job)
VALUES
(1, '[email protected]', true, CURRENT_TIMESTAMP, '$2a$10$kzY3LINL6..50Fy9shWCcuNlRfYq0ft5lS.KCcJ5PzrhlWfKK4NIO', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'Jean', 'Nouveau', '0123456789', 'Sbire');

INSERT INTO organizations
(id, siret, created_at, updated_at)
VALUES
(1, '66204244933106', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
34 changes: 34 additions & 0 deletions cypress/e2e/join_and_moderation/index.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//

//

describe("join and moderation", () => {
before(() => {
cy.mailslurp().then((mailslurp) =>
mailslurp.inboxController.deleteAllInboxEmails({
inboxId: "86983334-028f-48b5-881d-8b05d738bec5",
}),
);
});

beforeEach(() => {
cy.login(
"[email protected]",
"password123",
);
});

it("will be moderated", function () {
cy.visit(`/`);
cy.get('[name="siret"]').type("66204244933106");
cy.get('[type="submit"]').click();

cy.contains("Rattachement en cours");
cy.contains(
"⏱️ Notre équipe étudie votre demande de rattachement à l’organisation Bnp paribas - Bnp pariba",
);
cy.contains(
"avec l’adresse email [email protected].",
);
});
});
8 changes: 8 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"connect-redis": "^7.1.1",
"console-log-level": "^1.4.1",
"cookie-parser": "^1.4.6",
"@numerique-gouv/crisp": "https://github.com/douglasduteil/crisp/releases/download/v1.6.1/douglasduteil-crisp-1.6.1.tgz",
"csrf-sync": "^4.0.3",
"dotenv": "^16.4.5",
"ejs": "^3.1.10",
Expand Down
Binary file added public/mail-banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import "dotenv/config";

export const {
NODE_ENV,
CRISP_BASE_URL = "https://api.crisp.chat",
CRISP_IDENTIFIER,
CRISP_KEY,
CRISP_PLUGIN_URN,
CRISP_USER_NICKNAME = "MonComptePro",
CRISP_WEBSITE_ID,
DEPLOY_ENV = "preview",
PORT = 3000,
MONCOMPTEPRO_HOST = `http://localhost:${PORT}`,
Expand Down
1 change: 0 additions & 1 deletion src/connectors/brevo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ type RemoteTemplateSlug =
| "verify-email";
type LocalTemplateSlug =
| "organization-welcome"
| "unable-to-auto-join-organization"
| "welcome"
| "moderation-processed"
| "delete-account"
Expand Down
88 changes: 88 additions & 0 deletions src/connectors/crisp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//

import { fetch_crisp, type Config } from "@numerique-gouv/crisp";
import {
CreateConversationRoute,
SendMessageInAConversationRoute,
UpdateConversationMetaRoute,
UpdateConversationStateRoute,
} from "@numerique-gouv/crisp/router/conversation";
import {
CRISP_BASE_URL,
CRISP_IDENTIFIER,
CRISP_KEY,
CRISP_PLUGIN_URN,
CRISP_USER_NICKNAME,
CRISP_WEBSITE_ID,
} from "../config/env";

//

const config: Config = {
base_url: CRISP_BASE_URL,
identifier: CRISP_IDENTIFIER || "",
key: CRISP_KEY || "",
plugin_urn: CRISP_PLUGIN_URN || "",
user_nickname: CRISP_USER_NICKNAME,
website_id: CRISP_WEBSITE_ID || "",
debug: true,
};
//

export async function startCripsConversation({
content,
email,
nickname,
subject,
}: {
content: string;
email: string;
nickname: string;
subject: string;
}) {
const { session_id } = await fetch_crisp<CreateConversationRoute>(config, {
endpoint: `/v1/website/${config.website_id}/conversation`,
method: "POST",
searchParams: {},
});

await fetch_crisp<UpdateConversationMetaRoute>(config, {
endpoint: `/v1/website/${config.website_id}/conversation/${session_id}/meta`,
method: "PATCH",
searchParams: {},
body: {
email,
nickname,
segments: ["email", "moderation"],
subject,
},
});

await fetch_crisp<SendMessageInAConversationRoute>(config, {
endpoint: `/v1/website/${config.website_id}/conversation/${session_id}/message`,
method: "POST",
searchParams: {},
body: {
type: "text",
origin: `urn:${config.website_id}`,
from: "operator",
content,
user: {
nickname: config.user_nickname,
},
},
});

// HACK(douglasduteil): Wait for the message to be sent
// Crisp seems to have a delay between the message being sent and the state being updated
await new Promise((resolve) => setTimeout(resolve, 500));

await fetch_crisp<UpdateConversationStateRoute>(config, {
endpoint: `/v1/website/${config.website_id}/conversation/${session_id}/state`,
method: "PATCH",
searchParams: {},
body: { state: "resolved" },
});

return session_id;
}
36 changes: 25 additions & 11 deletions src/managers/organization/join.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as Sentry from "@sentry/node";
import { isEmpty, some } from "lodash-es";
import { MAX_SUGGESTED_ORGANIZATIONS } from "../../config/env";
import {
CRISP_WEBSITE_ID,
MAX_SUGGESTED_ORGANIZATIONS,
} from "../../config/env";
import {
InseeConnectionError,
InseeNotActiveError,
Expand All @@ -15,7 +18,7 @@ import {
import { getAnnuaireEducationNationaleContactEmail } from "../../connectors/api-annuaire-education-nationale";
import { getAnnuaireServicePublicContactEmail } from "../../connectors/api-annuaire-service-public";
import { getOrganizationInfo } from "../../connectors/api-sirene";
import { sendZammadMail } from "../../connectors/send-zammad-mail";
import { startCripsConversation } from "../../connectors/crisp";
import { findEmailDomainsByOrganizationId } from "../../repositories/email-domain";
import {
createModeration,
Expand Down Expand Up @@ -45,6 +48,7 @@ import {
isEtablissementScolaireDuPremierEtSecondDegre,
} from "../../services/organization";
import { isEmailValid } from "../../services/security";
import { unableToAutoJoinOrganizationMd } from "../../views/mails/unable-to-auto-join-organization";
import { markDomainAsVerified } from "./main";

export const doSuggestOrganizations = async ({
Expand Down Expand Up @@ -150,7 +154,7 @@ export const joinOrganization = async ({
}

const { id: organization_id, cached_libelle } = organization;
const { email } = user;
const { email, given_name, family_name } = user;
const domain = getEmailDomain(email);
const organizationEmailDomains =
await findEmailDomainsByOrganizationId(organization_id);
Expand Down Expand Up @@ -303,20 +307,30 @@ export const joinOrganization = async ({
});
}

const ticket = await sendZammadMail({
to: email,
subject: `[MonComptePro] Demande pour rejoindre ${cached_libelle || siret}`,
template: "unable-to-auto-join-organization",
params: {
let ticket_id = null;
if (CRISP_WEBSITE_ID) {
ticket_id = await startCripsConversation({
content: unableToAutoJoinOrganizationMd({
libelle: cached_libelle || siret,
}),
email,
nickname: `${given_name} ${family_name}`,
subject: `[MonComptePro] Demande pour rejoindre ${cached_libelle || siret}`,
});
} else {
logger.info(
`unable_to_auto_join_organization_md mail not send to ${email}:`,
);
logger.info({
libelle: cached_libelle || siret,
},
});
});
}

const { id: moderation_id } = await createModeration({
user_id,
organization_id,
type: "organization_join_block",
ticket_id: String(ticket.id),
ticket_id,
});

throw new UnableToAutoJoinOrganizationError(moderation_id);
Expand Down
11 changes: 0 additions & 11 deletions src/views/mails/unable-to-auto-join-organization.ejs

This file was deleted.

24 changes: 24 additions & 0 deletions src/views/mails/unable-to-auto-join-organization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//

import { MONCOMPTEPRO_HOST } from "../../config/env";

//

export function unableToAutoJoinOrganizationMd({
libelle,
}: {
libelle: string;
}) {
return `
![MonComptePro](${MONCOMPTEPRO_HOST}/dist/mail-banner.png)
Bonjour,
⏱️ Notre équipe est en train de vous rattacher à l’organisation **${libelle}**.
Vous recevrez un email pour accéder à votre démarche dès que nous aurons terminé.
(délai moyen : 1 jour ouvré)
Cordialement,
L’équipe MonComptePro
`.trim();
}

0 comments on commit a5dcf95

Please sign in to comment.