/** * post controller */ import { factories } from "@strapi/strapi"; export default factories.createCoreController( "api::post.post", ({ strapi }) => ({ async create(ctx) { const data = JSON.parse(ctx.request.body.data); if (ctx.request.files.media) { const files = Array.isArray(ctx.request.files.media) ? ctx.request.files.media[0] : ctx.request.files.media; const extension = files.originalFilename.match(/\.[0-9a-z]+$/i); const payload = { fileInfo: { caption: "undefined", alternativeText: data.name || "", name: `${data.name}_media${extension}`, }, }; const asset = await strapi.services["plugin::upload.upload"].upload({ data: payload, files, }); data.media = asset[0].id; } ctx.request.body = { data }; const result = await super.create(ctx); // Créer le postOwnership associé if (result.data) { const userId = ctx.state.user?.id; await strapi.db.query("api::post-ownership.post-ownership").create({ data: { post: result.data.id, author: userId, contextType: data.contextType || "user", contextId: data.contextId || null, relation: "owner", }, }); } return result; }, /** * Retrieves the feed for the authenticated user, including posts from friends, followed contacts, * user's groups, friends' groups, and system posts. The feed is enriched with additional properties * such as friend, contactFollow, member, and group. * * @param {Object} ctx - The Koa context object containing the request and response. * @returns {Promise} - Sends the enriched feed as the response. */ async feed(ctx) { const userId = ctx.state.user?.id; const { _limit = 20, _start = 0 } = ctx.query; if (!userId) return ctx.badRequest("userId is required"); const queryParams = { populate: { post: { populate: { likes: true, comments: { populate: { owner: { populate: { avatar: true, }, }, }, }, author: { populate: { avatar: true, }, }, media: true, }, }, author: { populate: { avatar: true, }, }, // populate context info if needed }, sort: { createdAt: "desc" }, pagination: { start: parseInt(String(_start)), limit: parseInt(String(_limit)), }, }; // 1️⃣ Récupérer les groupes de l'utilisateur const groups = await strapi.db .query("api::group-membership.group-membership") .findMany({ where: { user: { id: parseInt(userId) } }, populate: ["group"], }); const groupIds = groups.map((g) => g.group.id); // 2️⃣ Récupérer les amis (contacts acceptés) et les follows const friendsContacts = await strapi.db .query("api::contact.contact") .findMany({ where: { $or: [ { owner: { id: parseInt(userId) } }, { user: { id: parseInt(userId) } }, ], state: "accepted", }, populate: ["owner", "user"], }); const friendIds = friendsContacts.map((c) => c.owner.id !== parseInt(userId) ? c.owner.id : c.user.id ); // Récupérer les contacts suivis (follow) const followContacts = await strapi.db .query("api::contact.contact") .findMany({ where: { owner: { id: parseInt(userId) }, state: "follow", }, populate: ["user"], }); const followIds = followContacts.map((c) => c.user.id); // Récupérer les contacts suivis (follow) const blockedContacts = await strapi.db .query("api::contact.contact") .findMany({ where: { owner: { id: parseInt(userId) }, state: "blocked", }, populate: ["user"], }); const blockedIds = blockedContacts.map((c) => c.user.id); // 3️⃣ Récupérer les groupes où mes amis sont membres (en excluant mes propres groupes) const friendsGroupMemberships = await strapi.db .query("api::group-membership.group-membership") .findMany({ where: { user: { id: friendIds } }, populate: ["group"], }); const friendsGroupIds = friendsGroupMemberships .map((membership) => membership.group.id) .filter((groupId) => !groupIds.includes(groupId)); // Exclure mes propres groupes // 4️⃣ Récupérer le feed const feed = await strapi.db .query("api::post-ownership.post-ownership") .findMany({ where: { $or: [ // Posts réguliers (amis, follows, groupes, système) - exclure les cachés { $and: [ { $or: [ { author: { id: friendIds } }, // Posts de mes amis { author: { id: followIds } }, // Posts des contacts que je suis { contextType: "group", contextId: groupIds }, // Posts de mes groupes { contextType: "group", contextId: friendsGroupIds }, // Posts des groupes de mes amis { contextType: "system" }, // Posts système ], }, { relation: { $ne: "hidden" } }, // exclure les posts masqués pour les posts réguliers ], }, // Posts sauvés par l'utilisateur { contextType: "user", contextId: parseInt(userId), relation: "saved", }, // Posts cachés par l'utilisateur { contextType: "user", contextId: parseInt(userId), relation: "hidden", }, ], }, ...queryParams, }); // 5️⃣ Récupérer tous les groupes mentionnés dans le feed pour les populer const allGroupIds = [...new Set([...groupIds, ...friendsGroupIds])]; const allGroups = await strapi.db.query("api::group.group").findMany({ where: { id: allGroupIds }, }); // Créer un map pour un accès rapide aux groupes par ID const groupsMap = new Map(allGroups.map((group) => [group.id, group])); // 6️⃣ Enrichir le feed avec les propriétés friend, member, contactFollow et group const enrichedFeed = feed.map((postOwnership) => { const authorId = postOwnership.author?.id; const contextType = postOwnership.contextType; const contextId = postOwnership.contextId; // Vérifier si l'auteur est un ami const isFriend = authorId ? friendIds.includes(authorId) : false; // Vérifier si l'auteur est un contact suivi const isContactFollow = authorId ? followIds.includes(authorId) : false; // Vérifier si l'auteur est bloqué const isBlocked = authorId ? blockedIds.includes(authorId) : false; // Vérifier si je suis membre du groupe (seulement pour les posts de groupe) const isMember = contextType === "group" && contextId ? groupIds.includes(contextId) : false; // Ajouter l'objet group si contextType est "group" const group = contextType === "group" && contextId ? groupsMap.get(contextId) : null; return { ...postOwnership, friend: isFriend, contactFollow: isContactFollow, member: isMember, blocked: isBlocked, ...(group && { group }), // Ajouter group seulement s'il existe }; }); const filteredFeedBlocked = enrichedFeed.filter((post) => !post.blocked); ctx.send(filteredFeedBlocked); }, async savePost(ctx) { console.log("🎯 savePost called with params:", ctx.params); console.log("🎯 Request method:", ctx.request.method); console.log("🎯 Request URL:", ctx.request.url); const user = ctx.state.user; if (!user) { console.log("❌ User not authenticated"); return ctx.unauthorized("You must be logged in to save posts"); } console.log("✅ User authenticated:", user.id); const postId = parseInt(String(ctx.params.id)); const authorId = parseInt(String(ctx.query.authorId)); if (isNaN(postId)) { return ctx.badRequest("Invalid post ID"); } // Verify the post exists const post = await strapi.entityService.findOne("api::post.post", postId); if (!post) { return ctx.notFound("Post not found"); } // Check if post is already saved by this user const existingSavedPost = await strapi.db .query("api::post-ownership.post-ownership") .findOne({ where: { post: postId, author: authorId, contextType: "user", contextId: user.id, relation: "saved", }, }); if (existingSavedPost) { console.log("⚠️ Post already saved"); return ctx.badRequest("Post is already saved"); } // Create postOwnership entry to mark post as saved const savedPostOwnership = await strapi.db .query("api::post-ownership.post-ownership") .create({ data: { post: postId, author: authorId, contextType: "user", contextId: user.id, relation: "saved", }, }); console.log("✅ Post marked as saved:", savedPostOwnership); // Get count of saved posts for this user const savedPostsCount = await strapi.db .query("api::post-ownership.post-ownership") .count({ where: { author: authorId, contextType: "user", contextId: user.id, relation: "saved", }, }); return ctx.send({ message: "Post saved successfully", savedPostsCount: savedPostsCount, }); }, async removeSavedPost(ctx) { console.log("🎯 removeSavedPost called with params:", ctx.params); console.log("🎯 Request method:", ctx.request.method); console.log("🎯 Request URL:", ctx.request.url); const user = ctx.state.user; if (!user) { console.log("❌ User not authenticated"); return ctx.unauthorized("You must be logged in to remove saved posts"); } console.log("✅ User authenticated:", user.id); const postId = parseInt(String(ctx.params.id)); if (isNaN(postId)) { return ctx.badRequest("Invalid post ID"); } // Verify the post exists const post = await strapi.entityService.findOne("api::post.post", postId); if (!post) { return ctx.notFound("Post not found"); } // Find the saved post ownership entry const savedPostOwnership = await strapi.db .query("api::post-ownership.post-ownership") .findOne({ where: { post: postId, contextType: "user", contextId: user.id, relation: "saved", }, }); if (!savedPostOwnership) { console.log("⚠️ Post is not saved"); return ctx.badRequest("Post is not in your saved posts"); } // Delete the saved post ownership entry await strapi.db.query("api::post-ownership.post-ownership").delete({ where: { id: savedPostOwnership.id }, }); console.log("✅ Post removed from saved posts successfully"); // Get updated count of saved posts for this user const savedPostsCount = await strapi.db .query("api::post-ownership.post-ownership") .count({ where: { author: user.id, contextType: "user", contextId: user.id, relation: "saved", }, }); return ctx.send({ message: "Post removed from saved posts successfully", savedPostsCount: savedPostsCount, }); }, async hidePost(ctx) { console.log("🎯 hidePost called with params:", ctx.params); console.log("🎯 Request method:", ctx.request.method); console.log("🎯 Request URL:", ctx.request.url); const user = ctx.state.user; if (!user) { console.log("❌ User not authenticated"); return ctx.unauthorized("You must be logged in to hide posts"); } console.log("✅ User authenticated:", user.id); const postId = parseInt(String(ctx.params.id)); if (isNaN(postId)) { return ctx.badRequest("Invalid post ID"); } // Verify the post exists const post = await strapi.entityService.findOne("api::post.post", postId); if (!post) { return ctx.notFound("Post not found"); } // Check if post is already hidden by this user const existingHiddenPost = await strapi.db .query("api::post-ownership.post-ownership") .findOne({ where: { post: postId, author: user.id, contextType: "user", contextId: user.id, relation: "hidden", }, }); if (existingHiddenPost) { console.log("⚠️ Post already hidden"); return ctx.badRequest("Post is already hidden"); } // Create postOwnership entry to mark post as hidden const hiddenPostOwnership = await strapi.db .query("api::post-ownership.post-ownership") .create({ data: { post: postId, author: user.id, contextType: "user", contextId: user.id, relation: "hidden", }, }); console.log("✅ Post marked as hidden:", hiddenPostOwnership); return ctx.send({ message: "Post hidden successfully", hiddenPostId: postId, }); }, async removeHiddenPost(ctx) { console.log("🎯 removeHiddenPost called with params:", ctx.params); console.log("🎯 Request method:", ctx.request.method); console.log("🎯 Request URL:", ctx.request.url); const user = ctx.state.user; if (!user) { console.log("❌ User not authenticated"); return ctx.unauthorized("You must be logged in to unhide posts"); } console.log("✅ User authenticated:", user.id); const postId = parseInt(String(ctx.params.id)); if (isNaN(postId)) { return ctx.badRequest("Invalid post ID"); } // Verify the post exists const post = await strapi.entityService.findOne("api::post.post", postId); if (!post) { return ctx.notFound("Post not found"); } // Find the hidden post ownership entry const hiddenPostOwnership = await strapi.db .query("api::post-ownership.post-ownership") .findOne({ where: { post: postId, author: user.id, contextType: "user", contextId: user.id, relation: "hidden", }, }); if (!hiddenPostOwnership) { console.log("⚠️ Post is not hidden"); return ctx.badRequest("Post is not hidden"); } // Delete the hidden post ownership entry await strapi.db.query("api::post-ownership.post-ownership").delete({ where: { id: hiddenPostOwnership.id }, }); console.log("✅ Post unhidden successfully"); return ctx.send({ message: "Post unhidden successfully", unhiddenPostId: postId, }); }, }) );