"use strict"; const lod = require("lodash"); const utils = require("@strapi/utils"); const { concat, compact, isArray, toNumber, getOr } = require("lodash/fp"); const cryptoLib = require("crypto"); const bcrypt = require("bcryptjs"); const fs = require("fs").promises; const path = require("path"); module.exports = (plugin) => { const rawProviders = plugin.services.providers({ strapi }); const { ApplicationError, ValidationError, ForbiddenError } = utils.errors; const USER_MODEL_UID = "plugin::users-permissions.user"; const sanitizeUser = (user, ctx) => { const { auth } = ctx.state; const userSchema = strapi.getModel("plugin::users-permissions.user"); return strapi.contentAPI.sanitize.output(user, userSchema, { auth }); }; const ensureHashedPasswords = async (values) => { const attributes = strapi.getModel(USER_MODEL_UID).attributes; for (const key in values) { if (attributes[key] && attributes[key].type === "password") { // Check if a custom encryption.rounds has been set on the password attribute const rounds = toNumber( getOr(10, "encryption.rounds", attributes[key]) ); values[key] = await bcrypt.hash(values[key], rounds); } } values["confirmed"] = false; return values; }; const edit = async (userId, params = {}) => { return strapi.db.query(USER_MODEL_UID).update({ where: { id: userId }, data: await ensureHashedPasswords(params), populate: ["role"], }); }; const sendConfirmationEmail = async (user) => { // Génération du token de confirmation const templates = await strapi .documents("api::mails.mails") .findFirst({ populate: "*" }); //const onCreateUser = templates.onCreateUser; const onCreateUser = templates?.onCreateUser; const confirmationToken = cryptoLib.randomBytes(20).toString("hex"); await edit(user.id, { confirmationToken }); const confirmUrl = `${process.env.NEXTJS_URL}/confirmation/submit?confirmation=${confirmationToken}`; // Lecture du template HTML depuis le fichier const htmlPath = path.join(process.cwd(), "public", "confirmation.html"); //let html = await fs.readFile(htmlPath, "utf-8"); let html = onCreateUser?.message; // Remplacement des variables html = html .replace(/{{USER_NAME}}/g, user.username || user.email) .replace(/{{CONFIRM_URL}}/g, confirmUrl) .replace(/{{YEAR}}/g, new Date().getFullYear().toString()); // Envoi de l'e-mail await strapi .plugin("email") .service("email") .send({ to: user.email, subject: onCreateUser?.subject ?? "Confirme ton adresse e-mail", html, from: "ChoralSync ", }); return { ok: true }; }; const getService = (name) => { return strapi.plugin("users-permissions").service(name); }; const getProfile = async (provider, query) => { const accessToken = query.access_token || query.code || query.oauth_token; const providers = await strapi .store({ type: "plugin", name: "users-permissions", key: "grant" }) .get(); return getService("providers-registry").run({ provider, query, accessToken, providers, }); }; const providers = ({ strapi }) => { return { ...rawProviders, connect: async (provider, query) => { const accessToken = query.access_token || query.code || query.oauth_token; if (!accessToken) { throw new Error("No access_token."); } const profile = await getProfile(provider, query); const email = lod.toLower(profile.email); if (!email) { throw new Error("Email was not available."); } const users = await strapi.db .query("plugin::users-permissions.user") .findMany({ where: { email }, }); const advancedSettings = (await strapi .store({ type: "plugin", name: "users-permissions", key: "advanced" }) .get()) as { allow_register: boolean; unique_email?: boolean; default_role?: string; }; let user = lod.find(users, { provider }); if (lod.isEmpty(user)) { user = lod.find(users); } if (lod.isEmpty(user) && !advancedSettings.allow_register) { throw new Error("Register action is actually not available."); } if (!lod.isEmpty(user)) { return user; } if (users.length && advancedSettings.unique_email) { throw new Error("Email is already taken."); } const defaultRole = await strapi.db .query("plugin::users-permissions.role") .findOne({ where: { type: advancedSettings.default_role } }); const newUser = { ...profile, email, provider, role: defaultRole.id, confirmed: true, }; const createdUser = await strapi.db .query("plugin::users-permissions.user") .create({ data: newUser }); return createdUser; }, }; }; const originalMe = plugin.controllers.user.me; plugin.controllers.user.me = async (ctx) => { const fullUser = await strapi.db .query("plugin::users-permissions.user") .findOne({ where: { id: ctx.state.user.id }, populate: true, }); if (fullUser.contacts && Array.isArray(fullUser.contacts)) { fullUser.contacts = fullUser.contacts.filter( (contact) => contact.state === "accepted" ); } if (fullUser.post_ownerships && Array.isArray(fullUser.post_ownerships)) { fullUser.post_ownerships = fullUser.post_ownerships.filter( (ownership) => ownership.contextType === "user" && ownership.relation === "owner" ); } if ( fullUser.group_memberships && Array.isArray(fullUser.group_memberships) ) { fullUser.group_memberships = fullUser.group_memberships.filter( (membership) => ["member", "admin", "owner"].includes(membership.role) ); } const user = ctx.state.user; const userId = user.id; const [ contactsCount, groupsCount, postsCount, eventsCount, followersCount, followingCount, ] = await Promise.all([ strapi.db.query("api::contact.contact").count({ where: { $or: [{ owner: userId }, { user: userId }], }, }), strapi.db.query("api::group-membership.group-membership").count({ where: { user: userId, role: { $in: ["owner", "member", "admin"] }, }, }), strapi.db .query("api::post-ownership.post-ownership") .count({ where: { author: userId } }), strapi.db .query("api::event-relationship.event-relationship") .count({ where: { author: userId } }), strapi.db.query("api::contact.contact").count({ where: { user: userId, state: "follow", }, }), strapi.db.query("api::contact.contact").count({ where: { owner: userId, state: "follow", }, }), ]); const result = { ...JSON.parse(JSON.stringify(fullUser)), stats: { contacts: contactsCount, groups: groupsCount, posts: postsCount, events: eventsCount, followers: followersCount, following: followingCount, }, }; return result; }; const originalFindOne = plugin.controllers.user.findOne; plugin.controllers.user.findOne = async (ctx) => { // 1️⃣ Appel du controller d'origine (permissions, policies) await originalFindOne(ctx); // 2️⃣ Récupère l'utilisateur renvoyé let user = ctx.body; if (!user) { return ctx.notFound("User not found"); } // 4️⃣ Ajoute un champ calculé user.profileCompleted = Boolean(user.username && user.surname); // 3️⃣ Supprime les champs sensibles const sensitive = ["password", "resetPasswordToken", "confirmationToken"]; sensitive.forEach((key) => delete user[key]); //post_ownerships // 5️⃣ Refetch avec relations peuplées try { const populatedUser = await strapi.entityService.findOne( "plugin::users-permissions.user", user.id, { populate: { post_ownerships: { populate: { post: { populate: { media: true, }, }, }, }, contacts: { populate: { owner: { populate: { avatar: true, }, }, user: { populate: { avatar: true, }, }, }, }, group_memberships: { populate: { group: { populate: { banner: true, }, }, }, }, activities: true, }, } ); // Fusionne les données originales (permissions/serialization) avec les relations user = { ...user, ...populatedUser }; if (user.contacts && Array.isArray(user.contacts)) { user.contacts = user.contacts.filter( (contact) => contact.state === "accepted" ); } if (user.post_ownerships && Array.isArray(user.post_ownerships)) { user.post_ownerships = user.post_ownerships.filter( (ownership) => ownership.contextType === "user" && ownership.relation === "owner" ); } if (user.group_memberships && Array.isArray(user.group_memberships)) { user.group_memberships = user.group_memberships.filter((membership) => ["member", "admin", "owner"].includes(membership.role) ); } const eventRelationships = await strapi.db .query("api::event-relationship.event-relationship") .findMany({ where: { author: user.id, relation: "owner", contextType: "user" }, populate: { event: true, }, }); user.event_relationships = eventRelationships || []; const [ contactsCount, groupsCount, postsCount, eventsCount, followersCount, followingCount, ] = await Promise.all([ strapi.db.query("api::contact.contact").count({ where: { $or: [{ owner: user.id }, { user: user.id }], }, }), strapi.db.query("api::group-membership.group-membership").count({ where: { user: user.id, role: { $in: ["owner", "member", "admin"] }, }, }), strapi.db .query("api::post-ownership.post-ownership") .count({ where: { author: user.id } }), strapi.db .query("api::event-relationship.event-relationship") .count({ where: { author: user.id } }), strapi.db.query("api::contact.contact").count({ where: { user: user.id, state: "follow", }, }), strapi.db.query("api::contact.contact").count({ where: { owner: user.id, state: "follow", }, }), ]); user.stats = { contacts: contactsCount, groups: groupsCount, posts: postsCount, events: eventsCount, followers: followersCount, following: followingCount, }; } catch (err) { console.error("Erreur populate relations user:", err); // fallback : retourne juste l'utilisateur original } ctx.body = user; return ctx; }; const uploadImage = async (ctx, label: string, username: string) => { const key = `${label}Image`; if (ctx.request.files[key]) { const files = Array.isArray(ctx.request.files[key]) ? ctx.request.files[key][0] : ctx.request.files[key]; const extension = files.originalFilename.match(/\.[0-9a-z]+$/i); const payload = { fileInfo: { caption: "undefined", alternativeText: username || "", name: `${username}_avatar${extension}`, }, }; const asset = await strapi.services["plugin::upload.upload"].upload({ data: payload, files, }); return asset[0].id; } return 0; }; plugin.services.providers = providers; plugin.controllers.user.updateMe = async (ctx) => { const user = ctx.state.user; if (!user) { return ctx.unauthorized(); } const data = JSON.parse(ctx.request.body.data); const newData = lod.pick(data, [ "email", "username", "name", "surname", "address", "gender", "voice", "job", "dob", "phone", "bio", "experience", "tags", "languages", "parameter", "privacy", ]); /* if (data.tags) { newData.tags = data.tags.map((tag) => ({ __component: "social.tags", text: tag.text, })); } if (data.languages) { newData.languages = data.languages.map((lang) => ({ __component: "user.language", language: lang.language, level: lang.level, })); }*/ if (newData.username) { const userWithSameUsername = await strapi .query("plugin::users-permissions.user") .findOne({ where: { username: newData.username } }); if (userWithSameUsername && userWithSameUsername.id != user.id) { return ctx.badRequest("Username already taken"); } } if (newData.email) { const userWithSameEmail = await strapi .query("plugin::users-permissions.user") .findOne({ where: { email: newData.email.toLowerCase() } }); if (userWithSameEmail && userWithSameEmail.id != user.id) { return ctx.badRequest("Email already taken"); } newData.email = newData.email.toLowerCase(); } ctx.request.body = newData; ctx.params = { id: user.id }; const keysExcludingParameterAndPrivacy = Object.keys(newData).filter( (key) => key !== "parameter" && key !== "privacy" ); if (keysExcludingParameterAndPrivacy.length === 0) { await strapi.entityService .update("plugin::users-permissions.user", user.id, { data: newData, }) .then((res) => { ctx.response.status = 200; ctx.response.body = res; return ctx; }); } else { const avatarId = await uploadImage(ctx, "avatar", newData.username); if (avatarId != 0) ctx.request.body.avatar = avatarId; return plugin.controllers.user.update(ctx); } }; plugin.contentTypes.user.lifecycles = { async afterCreate(event) { const { result } = event; await sendConfirmationEmail(result); await strapi .service("api::notification.notification") ?.addActivity({ userId: result.id, activityMessage: `Bienvenue ${result.username}, Ton compte est maintenant activé.`, activityUser: result.username, activityType: "user", }); // Appel du service de notification await strapi .service("api::notification.notification") ?.createNotification({ title: "Bienvenue !", message: `Ton compte est maintenant activé.`, type: "success", target_user: result.id, source: "system", payload: { email: result.email }, }); strapi.log.info( `🔔 Notification envoyée pour l'utilisateur ${result.id}` ); }, }; plugin.routes["content-api"].routes.push({ method: "PUT", path: "/users/me", handler: "user.updateMe", }); return plugin; };