diff --git a/package-lock.json b/package-lock.json index 15e699d..65daf8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "harmony-back", - "version": "0.1.0", + "version": "0.11.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "harmony-back", - "version": "0.1.0", + "version": "0.11.6", "dependencies": { "@strapi/plugin-cloud": "5.8.1", "@strapi/plugin-documentation": "^5.12.6", @@ -27,6 +27,7 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "husky": "^9.1.7", "typescript": "^5" }, "engines": { @@ -14541,6 +14542,22 @@ "node": ">=10.17.0" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", diff --git a/package.json b/package.json index a2a2130..2a33a0b 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "deploy": "strapi deploy", "develop": "strapi develop --debug", "start": "strapi start", - "strapi": "strapi" + "strapi": "strapi", + "prepare": "husky install" }, "dependencies": { "@strapi/plugin-cloud": "5.8.1", @@ -30,6 +31,7 @@ "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "husky": "^9.1.7", "typescript": "^5" }, "engines": { diff --git a/scripts/check-version.js b/scripts/check-version.js new file mode 100644 index 0000000..5ac501a --- /dev/null +++ b/scripts/check-version.js @@ -0,0 +1,34 @@ +// scripts/check-version.js +import { execSync } from "child_process"; +import fs from "fs"; + +try { + const pkg = JSON.parse(fs.readFileSync("package.json", "utf-8")); + + let lastVersion; + try { + const output = execSync("git show origin/main:package.json", { + encoding: "utf-8", + }); + lastVersion = JSON.parse(output).version; + } catch { + console.log( + "⚠️ Impossible de lire la version distante (peut-être le premier commit ?)" + ); + process.exit(0); + } + + if (pkg.version === lastVersion) { + console.error(`❌ Version inchangée : ${pkg.version}`); + console.error( + "➡️ Utilise 'npm version patch' ou 'npm version minor' avant de commit." + ); + process.exit(1); + } + + console.log(`✅ Version incrementée : ${lastVersion} → ${pkg.version}`); + process.exit(0); +} catch (err) { + console.error("Erreur lors de la vérification de la version :", err.message); + process.exit(1); +} diff --git a/src/api/event/controllers/delete.ts b/src/api/event/controllers/delete.ts new file mode 100644 index 0000000..b12ccda --- /dev/null +++ b/src/api/event/controllers/delete.ts @@ -0,0 +1,103 @@ +import type { Core } from "@strapi/strapi"; +import type { Context } from "koa"; + +/** + * Custom delete handler for events + * + * When deleting an event: + * 1. Verify that the current user is the owner of the event + * 2. Delete all associated EventRelationship records + * 3. Delete the event itself + * + * DELETE /events/:id + */ +export default ({ strapi }: { strapi: Core.Strapi }) => + async function deleteEvent(ctx: Context) { + const userId = ctx.state.user?.id; + const eventId = ctx.params.id; + + // Validation + if (!userId) { + return ctx.badRequest("User ID is required"); + } + + if (!eventId || isNaN(parseInt(eventId))) { + return ctx.badRequest("Event ID is required and must be a valid number"); + } + + try { + // 1. Verify the event exists + const event = await strapi.db.query("api::event.event").findOne({ + where: { id: parseInt(eventId) }, + }); + + if (!event) { + return ctx.notFound("Event not found"); + } + + // 2. Check that the current user is the owner of this event + const ownerRelationship = await strapi.db + .query("api::event-relationship.event-relationship") + .findOne({ + where: { + event: { id: parseInt(eventId) }, + relation: "owner", + }, + populate: { + author: true, + }, + }); + + if (!ownerRelationship) { + return ctx.forbidden("Event has no owner relationship - cannot delete"); + } + + if (ownerRelationship.author?.id !== userId) { + return ctx.forbidden("Only the event owner can delete this event"); + } + + // 3. Delete all EventRelationship records associated with this event + // First, find all relationships for this event + const allRelationships = await strapi.db + .query("api::event-relationship.event-relationship") + .findMany({ + where: { + event: { + id: parseInt(eventId), + }, + }, + }); + + // Delete each relationship individually + let deletedCount = 0; + for (const relationship of allRelationships) { + await strapi.db + .query("api::event-relationship.event-relationship") + .delete({ + where: { id: relationship.id }, + }); + deletedCount++; + } + + strapi.log.info( + `Deleted ${deletedCount} EventRelationship records for event ${eventId}` + ); + + // 4. Delete the event itself + await strapi.db.query("api::event.event").delete({ + where: { id: parseInt(eventId) }, + }); + + strapi.log.info( + `Event ${eventId} deleted successfully by user ${userId}` + ); + + // Return success response + ctx.send({ message: "Event deleted successfully" }, 200); + } catch (error) { + strapi.log.error("Error in deleteEvent controller:", error); + return ctx.internalServerError( + `Failed to delete event: ${error.message}` + ); + } + }; diff --git a/src/extensions/documentation/documentation/1.0.0/full_documentation.json b/src/extensions/documentation/documentation/1.0.0/full_documentation.json index ac4b000..3f64b43 100644 --- a/src/extensions/documentation/documentation/1.0.0/full_documentation.json +++ b/src/extensions/documentation/documentation/1.0.0/full_documentation.json @@ -14,7 +14,7 @@ "name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html" }, - "x-generation-date": "2025-10-20T21:52:11.110Z" + "x-generation-date": "2025-10-21T16:36:26.242Z" }, "x-strapi-config": { "plugins": [