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
6 changes: 3 additions & 3 deletions src/controllers/organization.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,14 @@ export const resendOTP = async (req, res, next) => {
});
}

const org = await prisma.organization.findFirst({
const org = await prisma.organization.findUnique({
where: { id: orgId },
});

if (!org) {
return res.status(404).json({
success: false,
message: 'This organization not found',
message: 'Organization not found',
});
}

Expand All @@ -176,7 +176,7 @@ export const resendOTP = async (req, res, next) => {
await sendEmail({
to: org.contactEmail,
subject: 'Re-verify Your Organization Email',
text: `Organization name: ${org.name}\nYour verification code is: ${verificationOTP}. will expire in 10 min`,
text: `Organization name: ${org.name}\nYour verification code is: ${verificationOTP}. It will expire in 10 minutes`,
});
} catch (emailError) {
return res.status(500).json({
Expand Down
120 changes: 120 additions & 0 deletions src/controllers/team.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import prisma from '../config/prismaClient.js';
import {
addTeamMemberValidation,
createTeamValidation,
updateTeamValidation,
} from '../validations/team.validation.js';

/**
Expand Down Expand Up @@ -398,3 +399,122 @@ export const addTeamMember = async (req, res, next) => {
next(error);
}
};

/**
* @desc Update a team
* @route /api/organization/:organizationId/department/:departmentId/team/:teamId
* @method PUT
* @access private - admins or organization owners only
*/
export const updateTeam = 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',
});
}

// Validate input
const { error } = updateTeamValidation(req.body);
if (error) {
return res.status(400).json({
success: false,
message: 'Validation failed',
errors: error.details.map((e) => e.message),
});
}

const { name, description, avatar } = req.body;

const updatedTeam = await prisma.team.update({
where: { id: teamId },
data: { name, description, avatar },
});

res.status(200).json({
success: true,
team: updatedTeam,
});
} catch (error) {
next(error);
}
};
227 changes: 227 additions & 0 deletions src/docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1866,6 +1866,159 @@
}
}
}
},
"/api/organization/{organizationId}/department/{departmentId}/team/{teamId}": {
"put": {
"tags": ["Team"],
"summary": "Update a team",
"description": "Update team details. Requires admin privileges, organization ownership, department management, or team leadership rights.",
"operationId": "updateTeam",
"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 update",
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"requestBody": {
"description": "Team update data",
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateTeamRequest"
},
"examples": {
"basicUpdate": {
"value": {
"name": "Updated Team Name",
"description": "Updated team description",
"avatar": "https://example.com/new-team-avatar.jpg"
}
},
"nameOnly": {
"value": {
"name": "New Team Name"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Team updated successfully",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateTeamResponse"
}
}
}
},
"400": {
"description": "Bad request - validation error or missing parameters",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"examples": {
"validationError": {
"value": {
"success": false,
"message": "Validation failed",
"errors": ["\"name\" must be at least 2 characters long"]
}
},
"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 Expand Up @@ -3402,6 +3555,80 @@
}
}
}
},
"UpdateTeamRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"minLength": 2,
"maxLength": 100,
"example": "Updated Team Name"
},
"description": {
"type": "string",
"maxLength": 500,
"example": "Updated team description"
},
"avatar": {
"type": "string",
"format": "uri",
"example": "https://example.com/new-team-avatar.jpg"
}
},
"anyOf": [
{ "required": ["name"] },
{ "required": ["description"] },
{ "required": ["avatar"] }
]
},
"UpdateTeamResponse": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"team": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"avatar": {
"type": "string",
"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
Loading