diff --git a/README.md b/README.md index 40b3aeb..9f79f2d 100644 --- a/README.md +++ b/README.md @@ -89,5 +89,6 @@ base url: `http://localhost:3000` - Update the project status: `PATCH /api/organization/:organizationId/team/:teamId/project/:projectId/status` - Update the project priority: `PATCH /api/organization/:organizationId/team/:teamId/project/:projectId/priority` - Delete a project: `DELETE /api/organization/:organizationId/team/:teamId/project/:projectId` +- Restore a project: `PATCH /api/organization/:organizationId/team/:teamId/project/:projectId/restore` - Add new member in the project: `POST /api/organization/:organizationId/team/:teamId/project/:projectId/addMember` - Remove member from a project: `DELETE /api/organization/:organizationId/team/:teamId/project/:projectId/removeMember` diff --git a/src/controllers/project.controller.js b/src/controllers/project.controller.js index 59758fe..65f76dd 100644 --- a/src/controllers/project.controller.js +++ b/src/controllers/project.controller.js @@ -856,6 +856,82 @@ export const deleteProject = async (req, res, next) => { } }; +/** + * @desc Restore a project + * @route /api/organization/:organizationId/team/:teamId/project/:projectId/restore + * @method PATCH + * @access private + */ +export const restoreProject = async (req, res, next) => { + try { + const { organizationId, teamId, projectId } = req.params; + const user = req.user; + + // Validate required parameters + const validationResult = validateParams( + { organizationId, teamId, projectId }, + ['organizationId', 'teamId', 'projectId'], + ); + + if (!validationResult.success) { + return res.status(400).json({ message: validationResult.message }); + } + + // Check if organization exists + const orgResult = await checkOrganization(organizationId); + if (!orgResult.success) { + return res.status(404).json({ message: orgResult.message }); + } + + // Check if team exists + const teamResult = await checkTeam(teamId, organizationId); + if (!teamResult.success) { + return res.status(404).json({ message: teamResult.message }); + } + + // Check if project exists + const project = await prisma.project.findFirst({ + where: { + id: projectId, + teamId, + }, + }); + + if (!project) { + return res.status(404).json({ message: 'Project not found' }); + } + + // Check user permissions + const permissionResult = checkTeamPermissions( + user, + orgResult.organization, + teamResult.team, + 'restore projects from', + ); + if (!permissionResult.success) { + return res.status(403).json({ message: permissionResult.message }); + } + + // restore the project + await prisma.project.update({ + where: { + id: projectId, + }, + data: { + deletedAt: null, + lastModifiedBy: user.id, + }, + }); + + res.status(200).json({ + success: true, + message: 'Project restored successfully', + }); + } catch (error) { + next(error); + } +}; + /** * @desc Add project members * @route /api/organization/:organizationId/team/:teamId/project/:projectId/addMember diff --git a/src/docs/swagger.json b/src/docs/swagger.json index c3e912d..73715a9 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -4097,6 +4097,116 @@ } ] } + }, + "/api/organization/{organizationId}/team/{teamId}/project/{projectId}/restore": { + "patch": { + "tags": ["project"], + "summary": "Restore a deleted project", + "description": "Restores a previously soft-deleted project. Requires admin, organization owner, or team manager permissions.", + "parameters": [ + { + "name": "organizationId", + "in": "path", + "description": "Organization ID", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "teamId", + "in": "path", + "description": "Team ID", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "projectId", + "in": "path", + "description": "Project ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project restored successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "Project restored successfully" + } + } + } + } + } + }, + "400": { + "description": "Bad request - Missing or invalid parameters", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Organization ID is required" + } + } + } + } + } + }, + "403": { + "description": "Forbidden - Insufficient permissions", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "You don't have permission to restore projects" + } + } + } + } + } + }, + "404": { + "description": "Not found - Organization, team or project not found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } } }, diff --git a/src/routes/project.routes.js b/src/routes/project.routes.js index 586b609..4855d9d 100644 --- a/src/routes/project.routes.js +++ b/src/routes/project.routes.js @@ -5,6 +5,7 @@ import { createProject, deleteProject, removeProjectMember, + restoreProject, updateProject, updateProjectPriority, updateProjectStatus, @@ -42,6 +43,12 @@ router.delete( deleteProject, ); +router.patch( + '/api/organization/:organizationId/team/:teamId/project/:projectId/restore', + verifyAccessToken, + restoreProject, +); + router.post( '/api/organization/:organizationId/team/:teamId/project/:projectId/addMember', verifyAccessToken,