Formulaire academie

This commit is contained in:
2026-06-07 10:16:15 +02:00
parent 0711bd6996
commit 9dfce9bc58
21 changed files with 1666 additions and 854 deletions

View File

@@ -0,0 +1,287 @@
import { createError, readBody } from "h3"
import { getMysqlPool } from "~~/server/utils/mysql"
import { sendProjetAcademieEmails } from "~~/server/utils/mailer"
function normalizeValue(value) {
return typeof value === "string" ? value.trim() : ""
}
function normalizeBoolean(value) {
return value === true
}
function normalizePositiveInteger(value) {
const number = Number(value)
return Number.isInteger(number) && number > 0 ? number : null
}
function normalizeDiploma(diploma) {
return {
type: normalizeValue(diploma?.type),
otherType: normalizeValue(diploma?.otherType),
discipline: normalizeValue(diploma?.discipline),
year: normalizeValue(diploma?.year),
establishment: normalizeValue(diploma?.establishment),
}
}
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}
function withTimeout(promise, delay, message) {
return Promise.race([
promise,
new Promise((_, reject) => {
setTimeout(() => reject(new Error(message)), delay)
}),
])
}
function hasDiplomaData(diploma) {
return Boolean(diploma.type || diploma.otherType || diploma.discipline || diploma.year || diploma.establishment)
}
function validateDiploma(diploma) {
if (!hasDiplomaData(diploma)) {
return true
}
if (!diploma.type || !diploma.discipline || !diploma.year || !diploma.establishment) {
return false
}
if (diploma.type === "Autre" && !diploma.otherType) {
return false
}
return true
}
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const payload = {
lastName: normalizeValue(body?.lastName).toUpperCase(),
firstName: normalizeValue(body?.firstName),
gender: normalizeValue(body?.gender),
birthDate: normalizeValue(body?.birthDate),
birthPlace: normalizeValue(body?.birthPlace),
nationality: normalizeValue(body?.nationality),
email: normalizeValue(body?.email),
phone: normalizeValue(body?.phone),
address: normalizeValue(body?.address),
city: normalizeValue(body?.city),
postalCode: normalizeValue(body?.postalCode),
country: normalizeValue(body?.country),
instrument: normalizeValue(body?.instrument),
previousAcademyParticipation: normalizeValue(body?.previousAcademyParticipation),
previousAcademyParticipationCount: normalizePositiveInteger(body?.previousAcademyParticipationCount),
trainingLevel: normalizeValue(body?.trainingLevel),
otherTrainingLevel: normalizeValue(body?.otherTrainingLevel),
conservatory: normalizeValue(body?.conservatory),
otherConservatory: normalizeValue(body?.otherConservatory),
teacherName: normalizeValue(body?.teacherName),
diplomas: Array.isArray(body?.diplomas)
? body.diplomas.slice(0, 3).map(normalizeDiploma)
: [],
emergencyLastName: normalizeValue(body?.emergencyLastName),
emergencyFirstName: normalizeValue(body?.emergencyFirstName),
emergencyRelation: normalizeValue(body?.emergencyRelation),
emergencyPhone: normalizeValue(body?.emergencyPhone),
emergencyEmail: normalizeValue(body?.emergencyEmail),
acceptRules: normalizeBoolean(body?.acceptRules),
}
while (payload.diplomas.length < 3) {
payload.diplomas.push(normalizeDiploma({}))
}
if (
!payload.lastName ||
!payload.firstName ||
!payload.gender ||
!payload.birthDate ||
!payload.birthPlace ||
!payload.nationality ||
!payload.email ||
!payload.phone ||
!payload.address ||
!payload.city ||
!payload.postalCode ||
!payload.country ||
!payload.instrument ||
!payload.previousAcademyParticipation ||
!payload.trainingLevel ||
!payload.conservatory ||
!payload.teacherName ||
!payload.emergencyLastName ||
!payload.emergencyFirstName ||
!payload.emergencyRelation ||
!payload.emergencyPhone ||
!payload.emergencyEmail ||
!payload.acceptRules
) {
throw createError({
statusCode: 400,
statusMessage: "Tous les champs obligatoires doivent être remplis.",
})
}
if (!isValidEmail(payload.email) || !isValidEmail(payload.emergencyEmail)) {
throw createError({
statusCode: 400,
statusMessage: "Une adresse email nest pas valide.",
})
}
if (payload.previousAcademyParticipation === "oui" && !payload.previousAcademyParticipationCount) {
throw createError({
statusCode: 400,
statusMessage: "Le nombre de participations est obligatoire.",
})
}
if (payload.trainingLevel === "Autre" && !payload.otherTrainingLevel) {
throw createError({
statusCode: 400,
statusMessage: "Lautre formation est obligatoire.",
})
}
if (payload.conservatory === "Autre" && !payload.otherConservatory) {
throw createError({
statusCode: 400,
statusMessage: "Lautre conservatoire est obligatoire.",
})
}
if (!payload.diplomas.every(validateDiploma)) {
throw createError({
statusCode: 400,
statusMessage: "Chaque diplôme renseigné doit être complet.",
})
}
const db = getMysqlPool()
const [diploma1, diploma2, diploma3] = payload.diplomas
try {
const [result] = await db.execute(
`
INSERT INTO academie_orchestre_candidatures (
nom,
prenom,
genre,
date_naissance,
lieu_naissance,
nationalite,
email,
telephone,
adresse,
ville,
code_postal,
pays_residence,
instrument,
deja_participe_academie,
nombre_participations_academie,
niveau_cycle_2026_2027,
autre_formation,
conservatoire_2026_2027,
autre_conservatoire,
professeur_instrument,
diplome_1_type,
diplome_1_autre_type,
diplome_1_discipline,
diplome_1_annee,
diplome_1_etablissement,
diplome_2_type,
diplome_2_autre_type,
diplome_2_discipline,
diplome_2_annee,
diplome_2_etablissement,
diplome_3_type,
diplome_3_autre_type,
diplome_3_discipline,
diplome_3_annee,
diplome_3_etablissement,
contact_urgence_nom,
contact_urgence_prenom,
contact_urgence_lien,
contact_urgence_telephone,
contact_urgence_email,
reglement_accepte
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`,
[
payload.lastName,
payload.firstName,
payload.gender,
payload.birthDate,
payload.birthPlace,
payload.nationality,
payload.email,
payload.phone,
payload.address,
payload.city,
payload.postalCode,
payload.country,
payload.instrument,
payload.previousAcademyParticipation,
payload.previousAcademyParticipationCount,
payload.trainingLevel,
payload.otherTrainingLevel,
payload.conservatory,
payload.otherConservatory,
payload.teacherName,
diploma1.type,
diploma1.otherType,
diploma1.discipline,
diploma1.year,
diploma1.establishment,
diploma2.type,
diploma2.otherType,
diploma2.discipline,
diploma2.year,
diploma2.establishment,
diploma3.type,
diploma3.otherType,
diploma3.discipline,
diploma3.year,
diploma3.establishment,
payload.emergencyLastName,
payload.emergencyFirstName,
payload.emergencyRelation,
payload.emergencyPhone,
payload.emergencyEmail,
payload.acceptRules,
]
)
let emailsSent = false
try {
emailsSent = await withTimeout(
sendProjetAcademieEmails(payload),
15000,
"Délai dépassé pour l'envoi email projet-academie."
)
} catch (mailError) {
console.error("Erreur envoi email projet-academie:", mailError)
}
return {
ok: true,
id: result.insertId,
emailsSent,
}
} catch (error) {
console.error("Erreur API projet-academie:", error)
throw createError({
statusCode: 500,
statusMessage: "Impossible denregistrer la candidature pour le moment.",
})
}
})

View File

@@ -0,0 +1,46 @@
CREATE TABLE academie_orchestre_candidatures (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
nom VARCHAR(255) NOT NULL,
prenom VARCHAR(255) NOT NULL,
genre VARCHAR(50) NOT NULL,
date_naissance DATE NOT NULL,
lieu_naissance VARCHAR(255) NOT NULL,
nationalite VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
telephone VARCHAR(100) NOT NULL,
adresse TEXT NOT NULL,
ville VARCHAR(255) NOT NULL,
code_postal VARCHAR(20) NOT NULL,
pays_residence VARCHAR(255) NOT NULL,
instrument VARCHAR(255) NOT NULL,
deja_participe_academie VARCHAR(10) NOT NULL,
nombre_participations_academie INT UNSIGNED NULL,
niveau_cycle_2026_2027 VARCHAR(255) NOT NULL,
autre_formation VARCHAR(255) NULL,
conservatoire_2026_2027 VARCHAR(500) NOT NULL,
autre_conservatoire VARCHAR(500) NULL,
professeur_instrument VARCHAR(255) NOT NULL,
diplome_1_type VARCHAR(255) NULL,
diplome_1_autre_type VARCHAR(255) NULL,
diplome_1_discipline VARCHAR(255) NULL,
diplome_1_annee VARCHAR(20) NULL,
diplome_1_etablissement VARCHAR(255) NULL,
diplome_2_type VARCHAR(255) NULL,
diplome_2_autre_type VARCHAR(255) NULL,
diplome_2_discipline VARCHAR(255) NULL,
diplome_2_annee VARCHAR(20) NULL,
diplome_2_etablissement VARCHAR(255) NULL,
diplome_3_type VARCHAR(255) NULL,
diplome_3_autre_type VARCHAR(255) NULL,
diplome_3_discipline VARCHAR(255) NULL,
diplome_3_annee VARCHAR(20) NULL,
diplome_3_etablissement VARCHAR(255) NULL,
contact_urgence_nom VARCHAR(255) NOT NULL,
contact_urgence_prenom VARCHAR(255) NOT NULL,
contact_urgence_lien VARCHAR(100) NOT NULL,
contact_urgence_telephone VARCHAR(100) NOT NULL,
contact_urgence_email VARCHAR(255) NOT NULL,
reglement_accepte TINYINT(1) NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

View File

@@ -189,3 +189,121 @@ export async function sendProjetLyceeEmails(payload) {
return true
}
function formatAcademieDiploma(diploma, index) {
if (!diploma?.type && !diploma?.discipline && !diploma?.year && !diploma?.establishment) {
return `Diplome ${index + 1} : Non renseigne`
}
return [
`Diplome ${index + 1} :`,
`Type : ${diploma.type || "Non renseigne"}`,
diploma.type === "Autre" ? `Autre type : ${diploma.otherType || "Non renseigne"}` : null,
`Discipline : ${diploma.discipline || "Non renseignee"}`,
`Annee : ${diploma.year || "Non renseignee"}`,
`Etablissement : ${diploma.establishment || "Non renseigne"}`,
].filter(Boolean).join("\n")
}
function getProjetAcademieSummary(payload) {
return [
"Identite :",
`Nom : ${payload.lastName}`,
`Prenom : ${payload.firstName}`,
`Genre : ${payload.gender}`,
`Date de naissance : ${payload.birthDate}`,
`Lieu de naissance : ${payload.birthPlace}`,
`Nationalite : ${payload.nationality}`,
"",
"Coordonnees :",
`Email : ${payload.email}`,
`Telephone : ${payload.phone}`,
`Adresse : ${payload.address}`,
`Ville : ${payload.city}`,
`Code postal : ${payload.postalCode}`,
`Pays de residence : ${payload.country}`,
"",
"Parcours musical :",
`Instrument : ${payload.instrument}`,
`A deja participe a l'academie : ${payload.previousAcademyParticipation}`,
payload.previousAcademyParticipation === "oui"
? `Nombre de participations : ${payload.previousAcademyParticipationCount}`
: null,
"",
"Formation :",
`Niveau - cycle actuel 2026-2027 : ${payload.trainingLevel}`,
payload.trainingLevel === "Autre" ? `Autre formation : ${payload.otherTrainingLevel}` : null,
`Conservatoire 2026-2027 : ${payload.conservatory}`,
payload.conservatory === "Autre" ? `Autre conservatoire : ${payload.otherConservatory}` : null,
`Professeur d'instrument : ${payload.teacherName}`,
"",
"Diplomes musicaux :",
...payload.diplomas.map(formatAcademieDiploma),
"",
"Contact d'urgence :",
`Nom : ${payload.emergencyLastName}`,
`Prenom : ${payload.emergencyFirstName}`,
`Lien : ${payload.emergencyRelation}`,
`Telephone : ${payload.emergencyPhone}`,
`Mail : ${payload.emergencyEmail}`,
"",
`Reglement accepte : ${payload.acceptRules ? "Oui" : "Non"}`,
].filter((line) => line !== null).join("\n")
}
export async function sendProjetAcademieEmails(payload) {
const config = useRuntimeConfig()
const mailer = getTransporter()
if (!mailer) {
console.warn("Email projet académie non envoyé: configuration SMTP incomplète.")
return false
}
if (!config.academieRequestRecipientEmail) {
console.warn("Email projet académie non envoyé: destinataire académie manquant.")
return false
}
const from = config.smtpFromName
? `"${config.smtpFromName}" <${config.smtpFromEmail}>`
: config.smtpFromEmail
const summary = getProjetAcademieSummary(payload)
const fullName = `${payload.lastName} ${payload.firstName}`
const adminSubject = `WONDIF - Candidature Academie d'orchestre - ${fullName}`
const adminText = [
"Une nouvelle candidature a été envoyée depuis le formulaire Académie d'orchestre.",
"",
summary,
].join("\n")
const userSubject = "ONDIF - Confirmation de votre candidature - Académie d'orchestre"
const userText = [
`Bonjour ${payload.firstName} ${payload.lastName},`,
"",
"Votre candidature à l'Académie d'orchestre de l'Orchestre national d'Île-de-France a bien été transmise.",
"",
"Recapitulatif :",
summary,
].join("\n")
await Promise.all([
mailer.sendMail({
from,
to: config.academieRequestRecipientEmail,
replyTo: payload.email,
subject: adminSubject,
text: adminText,
}),
mailer.sendMail({
from,
to: payload.email,
subject: userSubject,
text: userText,
}),
])
return true
}