Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions src/controllers/team.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -752,3 +752,111 @@ export const deleteTeamAvatar = async (req, res, next) => {
next(error);
}
};

/**
* @desc Delete team
* @route /api/organization/:organizationId/department/:departmentId/team/:teamId/
* @method DELETE
* @access private - admins or organization owners only
Copy link

Copilot AI Apr 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The access documentation does not accurately reflect the implemented permission checks, which also allow department managers and team creators. Please update the comment to accurately describe the allowed roles.

Suggested change
* @access private - admins or organization owners only
* @access private - admins, organization owners, department managers, or team creators

Copilot uses AI. Check for mistakes.
*/
export const deleteTeam = 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({
Comment thread
mdawoud27 marked this conversation as resolved.
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 delete this team in this department',
});
}

// delete the team
await prisma.team.update({
where: { id: teamId },
data: { deletedAt: new Date() },
});

return res.status(200).json({
success: true,
message: 'Team deleted successfully',
});
} catch (error) {
next(error);
}
};
131 changes: 131 additions & 0 deletions src/docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -2290,6 +2290,137 @@
}
}
}
},
"/api/organization/{organizationId}/department/{departmentId}/team/{teamId}": {
"delete": {
"tags": ["Team"],
"summary": "Delete a team",
"description": "Soft delete a team (marks as deleted but retains in database). Requires admin privileges, organization ownership, department management, or team leadership rights.",
"operationId": "deleteTeam",
"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 to delete",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"200": {
"description": "Team deleted successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"message": {
"type": "string",
"example": "Team deleted successfully"
}
}
}
}
}
},
"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 or team 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"
}
}
}
}
}
},
"500": {
"description": "Internal server error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
Expand Down
7 changes: 7 additions & 0 deletions src/routes/team.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { verifyAccessToken } from '../middlewares/auth.middleware.js';
import {
addTeamMember,
createTeam,
deleteTeam,
deleteTeamAvatar,
updateTeam,
uploadTeamAvatar,
Expand Down Expand Up @@ -42,4 +43,10 @@ router.delete(
deleteTeamAvatar,
);

router.delete(
'/api/organization/:organizationId/department/:departmentId/team/:teamId/',
Comment thread
mdawoud27 marked this conversation as resolved.
verifyAccessToken,
deleteTeam,
);

export default router;