From b0b4e2331d403bae3338b87aca18a568d9be7860 Mon Sep 17 00:00:00 2001 From: Mohamed Dawoud Date: Mon, 7 Apr 2025 17:36:25 +0200 Subject: [PATCH 1/7] docs: todo --- src/controllers/team.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/team.controller.js b/src/controllers/team.controller.js index 542d5dc..a99fbb3 100644 --- a/src/controllers/team.controller.js +++ b/src/controllers/team.controller.js @@ -283,7 +283,7 @@ export const addTeamMember = async (req, res, next) => { }); } - // TODO: Extract all permission checks into a helper function like hasTeamAddPermission(user, org, dep, team) to simplify controller logic. + // TODO: Extract all permission checks into a helper function like hasTeamAddPermission(user, org, dep, team) to simplify controller logic. and validate the these IDs // Check permissions - only admins and organization owners const isAdmin = req.user.role === 'ADMIN'; const isOwner = existingOrg.owners.some( From 2278e466e4bf52a20618c48609f2fa00f47010c1 Mon Sep 17 00:00:00 2001 From: Mohamed Dawoud Date: Mon, 7 Apr 2025 17:48:59 +0200 Subject: [PATCH 2/7] feat: add `deleteTeamAvatar` controller --- src/controllers/team.controller.js | 118 ++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/src/controllers/team.controller.js b/src/controllers/team.controller.js index a99fbb3..1ad006a 100644 --- a/src/controllers/team.controller.js +++ b/src/controllers/team.controller.js @@ -1,5 +1,8 @@ import prisma from '../config/prismaClient.js'; -import { uploadToCloudinary } from '../utils/cloudinary.utils.js'; +import { + deleteFromCloudinary, + uploadToCloudinary, +} from '../utils/cloudinary.utils.js'; import { addTeamMemberValidation, createTeamValidation, @@ -636,3 +639,116 @@ export const uploadTeamAvatar = async (req, res, next) => { next(error); } }; + +/** + * @desc Delete team avatar + * @route /api/organization/:organizationId/department/:departmentId/team/:teamId/avatar/delete + * @method DELETE + * @access private - admins or organization owners only + */ +export const deleteTeamAvatar = async (req, res, next) => { + try { + const { organizationId, departmentId, teamId } = req.params; + + if (!organizationId || !departmentId || !teamId) { + return res.status(400).json({ + success: false, + message: 'Organization ID, Department ID, and Team ID are required', + }); + } + + // Check if organization exists and is not deleted + const existingOrg = await prisma.organization.findFirst({ + where: { + id: organizationId, + deletedAt: null, + }, + include: { + owners: { + select: { + userId: true, + }, + }, + }, + }); + + if (!existingOrg) { + return res.status(404).json({ + success: false, + message: 'Organization not found', + }); + } + + // Check if department exists and is not deleted + const existingDep = await prisma.department.findFirst({ + where: { + id: departmentId, + deletedAt: null, + }, + select: { managerId: true }, + }); + + if (!existingDep) { + return res.status(404).json({ + success: false, + message: 'Department not found', + }); + } + + // Check if team exists and is not deleted + const team = await prisma.team.findFirst({ + where: { + id: teamId, + organizationId, + deletedAt: null, + }, + select: { + id: true, + name: true, + description: true, + createdBy: true, + }, + }); + if (!team) { + return res.status(404).json({ + success: false, + message: 'Team not found', + }); + } + + // TODO: Extract all permission checks into a helper function like hasTeamAddPermission(user, org, dep, team) to simplify controller logic. + // Check permissions - only admins and organization owners + const isAdmin = req.user.role === 'ADMIN'; + const isOwner = existingOrg.owners.some( + (owner) => owner.userId === req.user.id, + ); + const isDepManager = existingDep.managerId === req.user.id; + const isTeamManager = team.createdBy === req.user.id; + + if (!isAdmin && !isOwner && !isDepManager && !isTeamManager) { + return res.status(403).json({ + success: false, + message: + 'You do not have permission to update this team in this department', + }); + } + + if (team.avatar) { + return res.status(404).json({ message: 'Team avatar not found' }); + } + + await deleteFromCloudinary(team.avatar); + + const updatedTeam = await prisma.team.update({ + where: { id: teamId }, + data: { avatar: null }, + }); + + res.status(200).json({ + message: 'Team avatar deleted successfully', + team: updatedTeam, + }); + } catch (error) { + next(error); + } +}; From c51910f6581a937123163399d5139c85388071a3 Mon Sep 17 00:00:00 2001 From: Mohamed Dawoud Date: Mon, 7 Apr 2025 17:49:21 +0200 Subject: [PATCH 3/7] feat: add route for `deleteTeamAvatar` controller --- src/routes/team.routes.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/routes/team.routes.js b/src/routes/team.routes.js index 96c6bb2..79182c3 100644 --- a/src/routes/team.routes.js +++ b/src/routes/team.routes.js @@ -3,6 +3,7 @@ import { verifyAccessToken } from '../middlewares/auth.middleware.js'; import { addTeamMember, createTeam, + deleteTeamAvatar, updateTeam, uploadTeamAvatar, } from '../controllers/team.controller.js'; @@ -35,4 +36,10 @@ router.post( uploadTeamAvatar, ); +router.delete( + '/api/organization/:organizationId/department/:departmentId/team/:teamId/avatar/delete', + verifyAccessToken, + deleteTeamAvatar, +); + export default router; From 815d2275d2f075b59adf91c6738d1f236bd7764d Mon Sep 17 00:00:00 2001 From: Mohamed Dawoud Date: Mon, 7 Apr 2025 17:50:12 +0200 Subject: [PATCH 4/7] docs: add swagger docs for delete team avatar --- src/docs/swagger.json | 180 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/src/docs/swagger.json b/src/docs/swagger.json index e6736de..e79c5fa 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -2163,6 +2163,133 @@ } } } + }, + "/api/organization/{organizationId}/department/{departmentId}/team/{teamId}/avatar/delete": { + "delete": { + "tags": ["Team"], + "summary": "Delete team avatar", + "description": "Remove the avatar image for a team. Requires admin privileges, organization ownership, department management, or team leadership rights.", + "operationId": "deleteTeamAvatar", + "security": [{ "bearerAuth": [] }], + "parameters": [ + { + "name": "organizationId", + "in": "path", + "required": true, + "description": "ID of the organization", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "departmentId", + "in": "path", + "required": true, + "description": "ID of the department", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "teamId", + "in": "path", + "required": true, + "description": "ID of the team", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Team avatar deleted successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TeamAvatarDeleteResponse" + } + } + } + }, + "400": { + "description": "Bad request - missing parameters", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "missingParams": { + "value": { + "success": false, + "message": "Organization ID, Department ID, and Team ID are required" + } + } + } + } + } + }, + "403": { + "description": "Forbidden - insufficient permissions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Organization, department, team, or avatar not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + }, + "examples": { + "orgNotFound": { + "value": { + "success": false, + "message": "Organization not found" + } + }, + "depNotFound": { + "value": { + "success": false, + "message": "Department not found" + } + }, + "teamNotFound": { + "value": { + "success": false, + "message": "Team not found" + } + }, + "avatarNotFound": { + "value": { + "success": false, + "message": "Team avatar not found" + } + } + } + } + } + }, + "500": { + "description": "Internal server error - failed to delete image", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } } }, "components": { @@ -3825,6 +3952,59 @@ } } } + }, + "TeamAvatarDeleteResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Team avatar deleted successfully" + }, + "team": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "avatar": { + "type": "string", + "nullable": true, + "format": "uri" + }, + "organizationId": { + "type": "string", + "format": "uuid" + }, + "departmentId": { + "type": "string", + "format": "uuid" + }, + "createdBy": { + "type": "string", + "format": "uuid" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + } + } + } + } } } } From 74651ba2e3911836a3dd362af0813532d34b1119 Mon Sep 17 00:00:00 2001 From: Mohamed Dawoud Date: Mon, 7 Apr 2025 17:50:47 +0200 Subject: [PATCH 5/7] docs: add `TODO` file for future todo tasks --- src/docs/TODO.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/docs/TODO.md diff --git a/src/docs/TODO.md b/src/docs/TODO.md new file mode 100644 index 0000000..896169f --- /dev/null +++ b/src/docs/TODO.md @@ -0,0 +1 @@ +# Funture TODO From d923327c26c25b5e68a82eecb759303d79838d25 Mon Sep 17 00:00:00 2001 From: Mohamed Dawoud Date: Mon, 7 Apr 2025 17:54:09 +0200 Subject: [PATCH 6/7] fix: the absence of an avatar --- src/controllers/team.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/team.controller.js b/src/controllers/team.controller.js index 1ad006a..81a8ed2 100644 --- a/src/controllers/team.controller.js +++ b/src/controllers/team.controller.js @@ -733,7 +733,7 @@ export const deleteTeamAvatar = async (req, res, next) => { }); } - if (team.avatar) { + if (!team.avatar) { return res.status(404).json({ message: 'Team avatar not found' }); } From 5138e59cb17fd06f348aafa629029784eda94b57 Mon Sep 17 00:00:00 2001 From: Mohamed Dawoud Date: Mon, 7 Apr 2025 17:55:27 +0200 Subject: [PATCH 7/7] fix: typo --- src/docs/TODO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docs/TODO.md b/src/docs/TODO.md index 896169f..268be11 100644 --- a/src/docs/TODO.md +++ b/src/docs/TODO.md @@ -1 +1 @@ -# Funture TODO +# Future TODO