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

feat: send projet creation email #305

Merged
merged 25 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e33d813
feat: send mail to user with no activity since 10 days from signup
mehdilouraoui Dec 10, 2024
c89f0bf
update vars name
mehdilouraoui Dec 10, 2024
1bd30e1
update mail filter
mehdilouraoui Dec 10, 2024
91f3d63
merge main and fix conflicts
mehdilouraoui Dec 10, 2024
c64a3de
Merge branch 'feat/csm-no-activity-since-signup' into feat/csm-batch
mehdilouraoui Dec 10, 2024
1e464e8
update message response
mehdilouraoui Dec 10, 2024
47f0b90
Merge branch 'feat/csm-no-activity-since-signup' into feat/csm-batch
mehdilouraoui Dec 10, 2024
fe27632
feat: ajout d'un batch
mehdilouraoui Dec 10, 2024
e1c7424
update return message
mehdilouraoui Dec 10, 2024
66e6966
Merge branch 'feat/csm-no-activity-since-signup' into feat/csm-batch
mehdilouraoui Dec 10, 2024
b919840
feat: csm batch
mehdilouraoui Dec 10, 2024
dad713c
change var name
mehdilouraoui Dec 10, 2024
80b78a1
add flexibility for inactivity days
mehdilouraoui Dec 11, 2024
dd259d6
change params wording
mehdilouraoui Dec 11, 2024
78b2e49
edit cron json
mehdilouraoui Dec 11, 2024
60b4281
fix: update env var
mehdilouraoui Dec 11, 2024
83aae31
fix: update logs
mehdilouraoui Dec 11, 2024
dd8da87
feat: send projet creation email
rtaieb Dec 11, 2024
4e67fea
fix: get user with no activity query (wip)
mehdilouraoui Dec 11, 2024
b30c183
Merge branch 'feat/csm-batch' into feat/csm-no-activity-since-signup
mehdilouraoui Dec 11, 2024
33fdede
fix: update logic
mehdilouraoui Dec 11, 2024
baf1ff6
feat: add user id in email
mehdilouraoui Dec 12, 2024
cb1d7ab
feat: add created_at user in params
mehdilouraoui Dec 12, 2024
db8461b
feat: remove useless script file and merge last PR
mehdilouraoui Dec 13, 2024
b19a2c5
update following comments
mehdilouraoui Dec 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions prisma/migrations/20241210154607_add_email_template/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- AlterEnum
-- This migration adds more than one value to an enum.
-- With PostgreSQL versions 11 and earlier, this is not possible
-- in a single migration. This can be worked around by creating
-- multiple migrations, each migration adding only one value to
-- the enum.


ALTER TYPE "emailType" ADD VALUE 'projetCreationRandomRex';
ALTER TYPE "emailType" ADD VALUE 'projetCreationFixedRex';
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
Warnings:

- The values [projetCreationRandomRex,projetCreationFixedRex] on the enum `emailType` will be removed. If these variants are still used in the database, this will fail.

*/
-- AlterEnum
BEGIN;
CREATE TYPE "emailType_new" AS ENUM ('projetCreation', 'projetInvitation', 'projetRequestAccess', 'projetAccessGranted', 'projetAccessDeclined', 'contactMessageSent', 'welcomeMessage');
ALTER TABLE "email" ALTER COLUMN "type" TYPE "emailType_new" USING ("type"::text::"emailType_new");
ALTER TYPE "emailType" RENAME TO "emailType_old";
ALTER TYPE "emailType_new" RENAME TO "emailType";
DROP TYPE "emailType_old";
COMMIT;
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ model user_projet {
}

enum emailType {
projetCreation
projetInvitation
projetRequestAccess
projetAccessGranted
Expand Down
17 changes: 17 additions & 0 deletions scripts/send-csm-emails.ts
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ce fichier sera à supprimer avec la PR sur le batch

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { customCaptureException } from "@/src/lib/sentry/sentryCustomMessage";
import { EmailService } from "@/src/services/brevo";

const sendCsmEmails = async () => {
try {
if (process.env.SEND_CSM_EMAILS !== "true") {
console.log("L'envoi de mail CSM est désactivé pour cet environnement'.");
} else {
await new EmailService().sendProjetCreationEmail();
}
} catch (error) {
customCaptureException("Erreur lors du batch d'envoi des mails CSM.", error);
process.exit(1);
}
};

sendCsmEmails();
2 changes: 2 additions & 0 deletions src/helpers/dateUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { Attribute } from "@strapi/strapi";

export const FAR_FUTURE = new Date(3024, 0, 0, 1);

export const removeDaysToDate = (date: Date, nbDays: number) => new Date(date.getTime() - nbDays * 24 * 60 * 60 * 1000);

export function monthDateToString(value: Date | null | undefined): string {
return value ? `${value.getFullYear()}-${("0" + (value.getMonth() + 1)).slice(-2)}` : "";
}
Expand Down
3 changes: 3 additions & 0 deletions src/helpers/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const PFMV_ROUTES = {
FICHES_DIAGNOSTIC: "/fiches-diagnostic",
MES_FICHES_SOLUTIONS: "/mon-projet/favoris",
RETOURS_EXPERIENCE: "/projet",
RETOUR_EXPERIENCE: (slug: string) => `${PFMV_ROUTES.RETOURS_EXPERIENCE}/${slug}`,
CONTACT: "/contact",
CONTACT_SUCCESS: "/contact/success",
NEWSLETTER: "/newsletter",
Expand Down Expand Up @@ -45,6 +46,8 @@ export const PFMV_ROUTES = {
`/espace-projet/${projetId}/financement/edit/${estimationId}`,
};

export const getFullUrl = (route: string): string => `${process.env.NEXT_PUBLIC_URL_SITE}${route}`;

export const GET_AIDES_TERRITOIRES_BY_AIDE_ID_URL = (aideId: number) =>
`/api/get-aides-territoires-aide-by-aide-id?aideId=${aideId}`;

Expand Down
31 changes: 30 additions & 1 deletion src/lib/prisma/prismaProjetQueries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { prismaClient } from "@/src/lib/prisma/prismaClient";
import { InvitationStatus, Prisma, projet, RoleProjet, user_projet } from "@prisma/client";
import { emailType, InvitationStatus, Prisma, projet, RoleProjet, user_projet } from "@prisma/client";
import { ProjetWithPublicRelations, ProjetWithRelations } from "./prismaCustomTypes";
import { generateRandomId } from "@/src/helpers/common";
import { GeoJsonProperties } from "geojson";
Expand Down Expand Up @@ -443,3 +443,32 @@ export const updateProjetVisibility = async (
include: projetIncludes,
});
};

export const getProjetsForProjetCreationEmail = async (
afterDate: Date,
beforeDate: Date,
): Promise<ProjetWithRelations[]> => {
return prismaClient.projet.findMany({
where: {
deleted_at: null,
created_at: {
gte: afterDate,
lte: beforeDate,
},
NOT: {
users: {
some: {
role: "ADMIN",
email: {
some: {
type: emailType.projetCreation,
email_status: "SUCCESS",
},
},
},
},
},
},
include: projetIncludes,
});
};
79 changes: 75 additions & 4 deletions src/services/brevo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ import { brevoSendEmail } from "./brevo-api";
import { ResponseAction } from "@/src/actions/actions-types";
import { getOldestProjectAdmin } from "@/src/lib/prisma/prisma-user-projet-queries";
import { captureError } from "@/src/lib/sentry/sentryCustomMessage";
import { UserProjetWithRelations, UserWithCollectivite } from "@/src/lib/prisma/prismaCustomTypes";
import { ProjetWithRelations, UserProjetWithRelations, UserWithCollectivite } from "@/src/lib/prisma/prismaCustomTypes";
import { getPrimaryCollectiviteForUser } from "@/src/helpers/user";
import { PFMV_ROUTES } from "@/src/helpers/routes";
import { getFullUrl, PFMV_ROUTES } from "@/src/helpers/routes";
import { ContactFormData } from "@/src/forms/contact/contact-form-schema";
import { getProjetsForProjetCreationEmail } from "@/src/lib/prisma/prismaProjetQueries";
import { removeDaysToDate } from "@/src/helpers/dateUtils";
import { getRetoursExperiences } from "@/src/lib/strapi/queries/retoursExperienceQueries";
import { RetourExperienceResponse } from "@/src/components/ficheSolution/type";
import shuffle from "lodash/shuffle";

interface Templates {
templateId: number;
Expand All @@ -24,6 +29,44 @@ export type EmailProjetPartageConfig = {
destinationMail: string;
};

export type EmailProjetCreationParam = {
nomUtilisateur: string;
nomProjet: string;
rex1Titre?: string;
rex1Url?: string;
rex2Titre?: string;
rex2Url?: string;
rex3Titre?: string;
rex3Url?: string;
rex4Titre?: string;
rex4Url?: string;
};

const computeProjetCreationEmailParam = (
projet: ProjetWithRelations,
rexExamples: RetourExperienceResponse[],
): EmailProjetCreationParam => {
if (rexExamples.length < 3) {
return {
nomProjet: projet.nom,
nomUtilisateur: projet.creator.nom || "",
};
} else {
return {
nomProjet: projet.nom,
nomUtilisateur: projet.creator.nom || "",
rex1Titre: rexExamples[0].attributes.titre,
rex1Url: getFullUrl(PFMV_ROUTES.RETOUR_EXPERIENCE(rexExamples[0].attributes.slug)),
rex2Titre: rexExamples[1].attributes.titre,
rex2Url: getFullUrl(PFMV_ROUTES.RETOUR_EXPERIENCE(rexExamples[1].attributes.slug)),
rex3Titre: rexExamples[2].attributes.titre,
rex3Url: getFullUrl(PFMV_ROUTES.RETOUR_EXPERIENCE(rexExamples[2].attributes.slug)),
rex4Titre: rexExamples[3]?.attributes.titre,
...(rexExamples[3] && { rex4Url: getFullUrl(PFMV_ROUTES.RETOUR_EXPERIENCE(rexExamples[3]?.attributes.slug)) }),
};
}
};

export class EmailService {
private readonly templates: Record<emailType, Templates>;

Expand All @@ -47,6 +90,9 @@ export class EmailService {
welcomeMessage: {
templateId: 52,
},
projetCreation: {
templateId: 54,
},
};
}

Expand Down Expand Up @@ -79,8 +125,7 @@ export class EmailService {

const data = await response.json();

let email = null;
email = await this.updateEmailStatus(dbEmail.id, emailStatus.SUCCESS, data.messageId);
const email = await this.updateEmailStatus(dbEmail.id, emailStatus.SUCCESS, data.messageId);

return { type: "success", message: "EMAIL_SENT", email };
} catch (error) {
Expand Down Expand Up @@ -173,4 +218,30 @@ export class EmailService {
extra: data,
});
}

async sendProjetCreationEmail(lastSyncDate?: Date) {
const projets = await getProjetsForProjetCreationEmail(
removeDaysToDate(lastSyncDate || new Date(), 3),
removeDaysToDate(new Date(), 1),
);
console.log(`Nb de mails de création de projet à envoyer : ${projets.length}`);
const allRex = await getRetoursExperiences();
const shuffledRex = shuffle(allRex);
return await Promise.all(
projets.map(async (projet) => {
const rexExamples = shuffledRex
// @ts-ignore
.filter((rex) => rex.attributes.types_espaces?.includes(projet.type_espace))
.slice(0, 4);
const emailParams = computeProjetCreationEmailParam(projet, rexExamples);
return await this.sendEmail({
to: projet.creator.email,
emailType: emailType.projetCreation,
params: emailParams,
extra: emailParams,
userProjetId: projet.users.find((up) => up.role === "ADMIN")?.id,
});
}),
);
}
}
Loading