Some checks failed
Build release Docker image / Build Docker Images (push) Failing after 8s
571 lines
16 KiB
TypeScript
571 lines
16 KiB
TypeScript
"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 <admin@harmonychoral.com>",
|
||
});
|
||
|
||
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;
|
||
};
|