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
120 changes: 118 additions & 2 deletions src/controllers/team.controller.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -283,7 +286,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(
Expand Down Expand Up @@ -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);
}
};
1 change: 1 addition & 0 deletions src/docs/TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Future TODO
180 changes: 180 additions & 0 deletions src/docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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"
}
}
}
}
}
}
}
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,
deleteTeamAvatar,
updateTeam,
uploadTeamAvatar,
} from '../controllers/team.controller.js';
Expand Down Expand Up @@ -35,4 +36,10 @@ router.post(
uploadTeamAvatar,
);

router.delete(
'/api/organization/:organizationId/department/:departmentId/team/:teamId/avatar/delete',
verifyAccessToken,
deleteTeamAvatar,
);

export default router;