Compare commits
6 Commits
f7f0ee17d7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 30c4c4ed3c | |||
| ca2604d0fb | |||
| 574d81ac54 | |||
| 6034c4f07b | |||
| 945ad5d095 | |||
| ce25ba2695 |
+2
-1
@@ -4,4 +4,5 @@ dist
|
||||
.tmp
|
||||
.cache
|
||||
.env
|
||||
build
|
||||
build
|
||||
.git
|
||||
@@ -5,9 +5,9 @@ on: [push]
|
||||
env:
|
||||
DOCKER_IMAGE_NAME: harmony-back
|
||||
DOCKER_REGISTRY_URL: git.harmonylab.ovh
|
||||
DOCKER_REGISTRY_ORG: harmony
|
||||
DOCKER_REGISTRY_ORG: admin
|
||||
RELEASE_VERSION: 1.2.0
|
||||
DOCKPLOY_WEBHOOK_URL: http://192.168.0.220:3000/api/deploy/HHWS7dR5rT-8vbXD6Zypv
|
||||
DOCKPLOY_WEBHOOK_URL: https://dokploy.harmonylab.ovh/api/deploy/fO2GDNPJAoNicWALwiBPa
|
||||
DISCORD_WEBHOOK_URL: https://discord.com/api/webhooks/1433240422901088339/GAvL79ESBRabkB6rvxN2DsWI74KJ_Szgp9W2_PycPIY113rMCT_9LvIv-iTLCMD9W9qH
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -5,6 +5,12 @@ RUN apk update && apk add --no-cache \
|
||||
|
||||
ARG NODE_ENV=production
|
||||
ENV NODE_ENV=${NODE_ENV}
|
||||
# On les transforme en variables d'environnement pour le processus de build
|
||||
ENV NODE_ENV=${NODE_ENV}
|
||||
ARG URL=https://back.harmonylab.ovh
|
||||
ENV URL=${URL}
|
||||
# Strapi v4/v5 utilise aussi souvent celle-ci pour l'admin
|
||||
ENV STRAPI_ADMIN_BACKEND_URL=${URL}
|
||||
|
||||
WORKDIR /opt/app
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
Vendored
+8
-7
@@ -3,17 +3,18 @@ export default () => ({
|
||||
config: {
|
||||
provider: "nodemailer",
|
||||
providerOptions: {
|
||||
host: "mail.harmonychoral.com",
|
||||
port: 465,
|
||||
host: "smtp.zeptomail.eu",
|
||||
port: 587,
|
||||
auth: {
|
||||
user: "admin@harmonychoral.com",
|
||||
pass: "Apslxnap12bn23",
|
||||
user: "emailapikey",
|
||||
pass: "yA6KbHsJ4lrywWtTFUc+0pSC94lm/aE/2nzks3i2fpZ1LYXp3qE71RBvd4O4c2CLjdfT5a9UbIkVJoCwvIpbfpczPIBXJpTGTuv4P2uV48xh8ciEYNYjhJivALIWFqVOeBsnDyo4QfEjWA==",
|
||||
},
|
||||
// ... any custom nodemailer options
|
||||
debug: true,
|
||||
logger: true,
|
||||
},
|
||||
settings: {
|
||||
defaultFrom: "admin@harmonychoral.com",
|
||||
defaultReplyTo: "admin@harmonychoral.com",
|
||||
defaultFrom: "ChoralSync <noreply@choralsync.com>",
|
||||
defaultReplyTo: "contact@choralsync.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+8
-6
@@ -23,16 +23,18 @@ export default () => ({
|
||||
config: {
|
||||
provider: "aws-s3",
|
||||
providerOptions: {
|
||||
baseUrl: "https://container.harmonylab.ovh/harmony",
|
||||
baseUrl: "https://container.choralsync.com",
|
||||
credentials: {
|
||||
accessKeyId: "admin",
|
||||
secretAccessKey: "Apslxnap12bn23",
|
||||
accessKeyId: "a6f474b04f010543022788efc626cbfe",
|
||||
secretAccessKey:
|
||||
"eeeb631194378edee5145b3ef6314630b152fa186b3dab2fefdf8b48952cde21",
|
||||
},
|
||||
endpoint: "https://container.harmonylab.ovh",
|
||||
endpoint:
|
||||
"https://4c59d3c67191bdd08e311a5a49c3ac98.r2.cloudflarestorage.com",
|
||||
forcePathStyle: true,
|
||||
region: "eu-west-3",
|
||||
region: "weur",
|
||||
params: {
|
||||
Bucket: "harmony",
|
||||
Bucket: "choralsync",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "harmony-back",
|
||||
"version": "0.13.10",
|
||||
"version": "0.13.11",
|
||||
"private": true,
|
||||
"description": "A Strapi application",
|
||||
"scripts": {
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
<!doctype html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Confirmation d'e-mail - ChoralSync</title>
|
||||
<!--[if mso]>
|
||||
<style type="text/css">
|
||||
body,
|
||||
table,
|
||||
td,
|
||||
a {
|
||||
font-family: Arial, Helvetica, sans-serif !important;
|
||||
}
|
||||
</style>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body
|
||||
style="
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f9fafb;
|
||||
font-family:
|
||||
"Inter",
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
"
|
||||
>
|
||||
<table
|
||||
width="100%"
|
||||
border="0"
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
style="background-color: #f9fafb; padding: 40px 20px"
|
||||
>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<!-- Main Container -->
|
||||
<table
|
||||
width="100%"
|
||||
border="0"
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
style="
|
||||
max-width: 650px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 16px;
|
||||
border: 1px solid #e2e8f0;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
"
|
||||
>
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="padding: 32px 32px 16px 32px">
|
||||
<div
|
||||
style="
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
letter-spacing: -0.025em;
|
||||
"
|
||||
>
|
||||
ChoralSync
|
||||
</div>
|
||||
<div
|
||||
style="
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
font-weight: 500;
|
||||
margin-top: 4px;
|
||||
"
|
||||
>
|
||||
Bienvenue — plus qu’une étape
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Content -->
|
||||
<tr>
|
||||
<td style="padding: 0 32px 32px 32px">
|
||||
<h1
|
||||
style="
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 16px;
|
||||
"
|
||||
>
|
||||
Bonjour {{USER_NAME}},
|
||||
</h1>
|
||||
<p
|
||||
style="
|
||||
font-size: 18px;
|
||||
line-height: 1.6;
|
||||
color: #4b5563;
|
||||
margin-bottom: 32px;
|
||||
"
|
||||
>
|
||||
Merci de t'être inscrit. Clique sur le bouton ci-dessous pour
|
||||
confirmer ton adresse e-mail et activer ton compte.
|
||||
</p>
|
||||
|
||||
<!-- CTA Button -->
|
||||
<table
|
||||
border="0"
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
style="margin-bottom: 40px"
|
||||
>
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
bgcolor="#0b1c30"
|
||||
style="border-radius: 12px"
|
||||
>
|
||||
<a
|
||||
href="{{CONFIRM_URL}}"
|
||||
target="_blank"
|
||||
style="
|
||||
display: inline-block;
|
||||
padding: 16px 32px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
border-radius: 12px;
|
||||
"
|
||||
>
|
||||
Confirmer mon e-mail →
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Fallback Link -->
|
||||
<div style="margin-top: 32px">
|
||||
<p
|
||||
style="font-size: 14px; color: #6b7280; margin-bottom: 12px"
|
||||
>
|
||||
Si le bouton ne fonctionne pas, copie-colle ce lien dans ton
|
||||
navigateur :
|
||||
</p>
|
||||
<div
|
||||
style="
|
||||
background-color: #f1f5f9;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
border: 1px solid #e2e8f0;
|
||||
"
|
||||
>
|
||||
<a
|
||||
href="{{CONFIRM_URL}}"
|
||||
style="
|
||||
color: #0ea5e9;
|
||||
font-size: 14px;
|
||||
text-decoration: underline;
|
||||
word-break: break-all;
|
||||
font-weight: 500;
|
||||
"
|
||||
>
|
||||
{{CONFIRM_URL}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="padding: 24px 32px; border-top: 1px solid #f1f5f9">
|
||||
<p style="font-size: 14px; color: #9ca3af; margin: 0 0 8px 0">
|
||||
Si tu n'es pas à l'origine de cette inscription, ignore cet
|
||||
e-mail.
|
||||
</p>
|
||||
<p style="font-size: 14px; color: #9ca3af; margin: 0">
|
||||
© ChoralSync 2026 •
|
||||
<a
|
||||
href="https://www.choralsync.com"
|
||||
style="color: #9ca3af; text-decoration: underline"
|
||||
>www.choralsync.com</a
|
||||
>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- External Footer -->
|
||||
<table
|
||||
width="100%"
|
||||
border="0"
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
style="max-width: 650px; margin-top: 32px"
|
||||
>
|
||||
<tr>
|
||||
<td align="center" style="font-size: 14px; color: #9ca3af">
|
||||
Besoin d'aide ? Réponds directement à cet e-mail ou consulte
|
||||
notre
|
||||
<a
|
||||
href="#"
|
||||
style="
|
||||
color: #0ea5e9;
|
||||
font-weight: 500;
|
||||
text-decoration: underline;
|
||||
"
|
||||
>centre d'aide</a
|
||||
>.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,43 @@
|
||||
module.exports = (plugin) => {
|
||||
// 1. LE SECRET : On ajoute le champ 'path' au schéma officiel de Strapi.
|
||||
// Strapi va ainsi créer la colonne en BDD et arrêter de supprimer notre variable.
|
||||
plugin.contentTypes.file.schema.attributes.path = {
|
||||
type: "string",
|
||||
configurable: false,
|
||||
};
|
||||
|
||||
const originalServiceFactory = plugin.services.upload;
|
||||
|
||||
plugin.services.upload = ({ strapi }) => {
|
||||
const service = originalServiceFactory({ strapi });
|
||||
|
||||
return {
|
||||
...service,
|
||||
|
||||
// 2. Surcharge de l'Uploader
|
||||
async upload(fileData, usage) {
|
||||
const ctx = strapi.requestContext.get();
|
||||
// On récupère le custom path envoyé par ton client Next.js
|
||||
const customPath = ctx?.request?.body?.path;
|
||||
|
||||
if (customPath) {
|
||||
// Pour que le provider S3 l'utilise tout de suite (R2)
|
||||
fileData.path = customPath;
|
||||
|
||||
// Pour que Strapi l'enregistre dans notre nouvelle colonne BDD
|
||||
if (!fileData.fileInfo) fileData.fileInfo = {};
|
||||
fileData.fileInfo.path = customPath;
|
||||
}
|
||||
|
||||
// Le service original va maintenant sauvegarder le path en BDD !
|
||||
return service.upload(fileData, usage);
|
||||
},
|
||||
|
||||
// Note : Plus besoin de surcharger `remove` (l'équivalent de delete) !
|
||||
// Comme la colonne 'path' est maintenant en BDD, le service original
|
||||
// récupère l'info et la passe au provider S3 qui supprime le bon dossier.
|
||||
};
|
||||
};
|
||||
|
||||
return plugin;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
"use strict";
|
||||
const lod = require("lodash");
|
||||
const utils = require("@strapi/utils");
|
||||
const { concat, compact, isArray, toNumber, getOr } = require("lodash/fp");
|
||||
const { toNumber, getOr } = require("lodash/fp");
|
||||
const cryptoLib = require("crypto");
|
||||
const bcrypt = require("bcryptjs");
|
||||
const fs = require("fs").promises;
|
||||
@@ -9,16 +9,60 @@ const path = require("path");
|
||||
|
||||
module.exports = (plugin) => {
|
||||
const rawProviders = plugin.services.providers({ strapi });
|
||||
const { ApplicationError, ValidationError, ForbiddenError } = utils.errors;
|
||||
const { ApplicationError, ValidationError } = utils.errors;
|
||||
const USER_MODEL_UID = "plugin::users-permissions.user";
|
||||
|
||||
const sanitizeUser = (user, ctx) => {
|
||||
const sanitizeUser = async (user, ctx) => {
|
||||
const { auth } = ctx.state;
|
||||
const userSchema = strapi.getModel("plugin::users-permissions.user");
|
||||
|
||||
return strapi.contentAPI.sanitize.output(user, userSchema, { auth });
|
||||
};
|
||||
|
||||
const getUserStats = async (userId) => {
|
||||
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" },
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
contacts: contactsCount,
|
||||
groups: groupsCount,
|
||||
posts: postsCount,
|
||||
events: eventsCount,
|
||||
followers: followersCount,
|
||||
following: followingCount,
|
||||
};
|
||||
};
|
||||
|
||||
const ensureHashedPasswords = async (values) => {
|
||||
const attributes = strapi.getModel(USER_MODEL_UID).attributes;
|
||||
|
||||
@@ -26,7 +70,7 @@ module.exports = (plugin) => {
|
||||
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])
|
||||
getOr(10, "encryption.rounds", attributes[key]),
|
||||
);
|
||||
values[key] = await bcrypt.hash(values[key], rounds);
|
||||
}
|
||||
@@ -78,7 +122,7 @@ module.exports = (plugin) => {
|
||||
to: user.email,
|
||||
subject: onCreateUser?.subject ?? "Confirme ton adresse e-mail",
|
||||
html,
|
||||
from: "ChoralSync <admin@harmonychoral.com>",
|
||||
from: "ChoralSync <contact@choralsync.com>",
|
||||
});
|
||||
|
||||
return { ok: true };
|
||||
@@ -111,7 +155,7 @@ module.exports = (plugin) => {
|
||||
query.access_token || query.code || query.oauth_token;
|
||||
|
||||
if (!accessToken) {
|
||||
throw new Error("No access_token.");
|
||||
throw new ApplicationError("No access_token.");
|
||||
}
|
||||
|
||||
const profile = await getProfile(provider, query);
|
||||
@@ -119,7 +163,7 @@ module.exports = (plugin) => {
|
||||
const email = lod.toLower(profile.email);
|
||||
|
||||
if (!email) {
|
||||
throw new Error("Email was not available.");
|
||||
throw new ApplicationError("Email was not available.");
|
||||
}
|
||||
|
||||
const users = await strapi.db
|
||||
@@ -142,7 +186,7 @@ module.exports = (plugin) => {
|
||||
}
|
||||
|
||||
if (lod.isEmpty(user) && !advancedSettings.allow_register) {
|
||||
throw new Error("Register action is actually not available.");
|
||||
throw new ApplicationError("Register action is actually not available.");
|
||||
}
|
||||
|
||||
if (!lod.isEmpty(user)) {
|
||||
@@ -150,7 +194,7 @@ module.exports = (plugin) => {
|
||||
}
|
||||
|
||||
if (users.length && advancedSettings.unique_email) {
|
||||
throw new Error("Email is already taken.");
|
||||
throw new ApplicationError("Email is already taken.");
|
||||
}
|
||||
|
||||
const defaultRole = await strapi.db
|
||||
@@ -174,8 +218,6 @@ module.exports = (plugin) => {
|
||||
};
|
||||
};
|
||||
|
||||
const originalMe = plugin.controllers.user.me;
|
||||
|
||||
plugin.controllers.user.me = async (ctx) => {
|
||||
const fullUser = await strapi.db
|
||||
.query("plugin::users-permissions.user")
|
||||
@@ -186,14 +228,14 @@ module.exports = (plugin) => {
|
||||
|
||||
if (fullUser.contacts && Array.isArray(fullUser.contacts)) {
|
||||
fullUser.contacts = fullUser.contacts.filter(
|
||||
(contact) => contact.state === "accepted"
|
||||
(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"
|
||||
ownership.contextType === "user" && ownership.relation === "owner",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -202,62 +244,15 @@ module.exports = (plugin) => {
|
||||
Array.isArray(fullUser.group_memberships)
|
||||
) {
|
||||
fullUser.group_memberships = fullUser.group_memberships.filter(
|
||||
(membership) => ["member", "admin", "owner"].includes(membership.role)
|
||||
(membership) => ["member", "admin", "owner"].includes(membership.role),
|
||||
);
|
||||
}
|
||||
|
||||
const user = ctx.state.user;
|
||||
const stats = await getUserStats(ctx.state.user.id);
|
||||
|
||||
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,
|
||||
},
|
||||
stats,
|
||||
};
|
||||
|
||||
return result;
|
||||
@@ -279,10 +274,6 @@ module.exports = (plugin) => {
|
||||
// 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(
|
||||
@@ -291,6 +282,7 @@ module.exports = (plugin) => {
|
||||
{
|
||||
populate: {
|
||||
post_ownerships: {
|
||||
filters: { contextType: "user", relation: "owner" },
|
||||
populate: {
|
||||
post: {
|
||||
populate: {
|
||||
@@ -300,6 +292,7 @@ module.exports = (plugin) => {
|
||||
},
|
||||
},
|
||||
contacts: {
|
||||
filters: { state: "accepted" },
|
||||
populate: {
|
||||
owner: {
|
||||
populate: {
|
||||
@@ -314,6 +307,7 @@ module.exports = (plugin) => {
|
||||
},
|
||||
},
|
||||
group_memberships: {
|
||||
filters: { role: { $in: ["member", "admin", "owner"] } },
|
||||
populate: {
|
||||
group: {
|
||||
populate: {
|
||||
@@ -324,29 +318,8 @@ module.exports = (plugin) => {
|
||||
},
|
||||
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")
|
||||
@@ -356,84 +329,28 @@ module.exports = (plugin) => {
|
||||
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,
|
||||
|
||||
const stats = await getUserStats(user.id);
|
||||
|
||||
const sanitizedPopulatedUser = await sanitizeUser(populatedUser, ctx) as object;
|
||||
|
||||
// Fusionne les données originales avec les relations sécurisées
|
||||
user = {
|
||||
...user,
|
||||
...sanitizedPopulatedUser,
|
||||
event_relationships: eventRelationships || [],
|
||||
stats
|
||||
};
|
||||
} catch (err) {
|
||||
console.error("Erreur populate relations user:", err);
|
||||
// fallback : retourne juste l'utilisateur original
|
||||
user.stats = await getUserStats(user.id);
|
||||
}
|
||||
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;
|
||||
};
|
||||
// Removed unused uploadImage function
|
||||
|
||||
plugin.services.providers = providers;
|
||||
|
||||
@@ -444,7 +361,14 @@ module.exports = (plugin) => {
|
||||
return ctx.unauthorized();
|
||||
}
|
||||
|
||||
const data = JSON.parse(ctx.request.body.data);
|
||||
let data = ctx.request.body.data || ctx.request.body;
|
||||
if (typeof data === "string") {
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch (err) {
|
||||
throw new ValidationError("Invalid JSON format in request body data");
|
||||
}
|
||||
}
|
||||
|
||||
const newData = lod.pick(data, [
|
||||
"email",
|
||||
@@ -463,6 +387,7 @@ module.exports = (plugin) => {
|
||||
"languages",
|
||||
"parameter",
|
||||
"privacy",
|
||||
"avatar",
|
||||
]);
|
||||
|
||||
/*
|
||||
@@ -506,7 +431,7 @@ module.exports = (plugin) => {
|
||||
ctx.params = { id: user.id };
|
||||
|
||||
const keysExcludingParameterAndPrivacy = Object.keys(newData).filter(
|
||||
(key) => key !== "parameter" && key !== "privacy"
|
||||
(key) => key !== "parameter" && key !== "privacy",
|
||||
);
|
||||
|
||||
if (keysExcludingParameterAndPrivacy.length === 0) {
|
||||
@@ -520,9 +445,6 @@ module.exports = (plugin) => {
|
||||
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);
|
||||
}
|
||||
};
|
||||
@@ -533,14 +455,12 @@ module.exports = (plugin) => {
|
||||
|
||||
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",
|
||||
});
|
||||
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
|
||||
@@ -548,14 +468,18 @@ module.exports = (plugin) => {
|
||||
?.createNotification({
|
||||
title: "Bienvenue !",
|
||||
message: `Ton compte est maintenant activé.`,
|
||||
type: "success",
|
||||
type: "accountCreation",
|
||||
target_user: result.id,
|
||||
source: "system",
|
||||
payload: { email: result.email },
|
||||
payload: {
|
||||
email: result.email,
|
||||
url: "/app/user",
|
||||
description: "Penses à remplir ton profil",
|
||||
},
|
||||
});
|
||||
|
||||
strapi.log.info(
|
||||
`🔔 Notification envoyée pour l'utilisateur ${result.id}`
|
||||
`🔔 Notification envoyée pour l'utilisateur ${result.id}`,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
Vendored
+1
@@ -1971,6 +1971,7 @@ export interface PluginUploadFile extends Struct.CollectionTypeSchema {
|
||||
Schema.Attribute.Private;
|
||||
mime: Schema.Attribute.String & Schema.Attribute.Required;
|
||||
name: Schema.Attribute.String & Schema.Attribute.Required;
|
||||
path: Schema.Attribute.String;
|
||||
previewUrl: Schema.Attribute.String;
|
||||
provider: Schema.Attribute.String & Schema.Attribute.Required;
|
||||
provider_metadata: Schema.Attribute.JSON;
|
||||
|
||||
Reference in New Issue
Block a user