Skip to content

Commit

Permalink
Add Resource Completion Status Update Endpoint for Cooperations (#1005)
Browse files Browse the repository at this point in the history
* add endpoint service logic

* add endpoint controller

* add endpoint route to router

* add body validation schema

* add proper role error

* refactor cooperationService to extract common logic

* add integration test for valid request

* add integration tests for errors

* add endpoint body, params docs

* add docs for responses

* refactor checks in service

* fix FORBIDDEN integration test for endpoint

* refactor _validateCooperationUser to be private method of service

* replace forEach with for loop to stop search in current section if resource already found
  • Loading branch information
luiqor authored Dec 3, 2024
1 parent 6be7351 commit 38ab22e
Show file tree
Hide file tree
Showing 8 changed files with 266 additions and 36 deletions.
10 changes: 6 additions & 4 deletions docs/cooperation/cooperation-schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,9 @@ definitions:
format: date-time
default: null
completionStatus:
type: string
enum:
- active
- completed
$ref: '#/definitions/completionStatus'
completionStatus:
type: string
enum:
- completed
- active
102 changes: 102 additions & 0 deletions docs/cooperation/cooperation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -406,3 +406,105 @@ paths:
status: 404
code: DOCUMENT_NOT_FOUND
message: Cooperation with the specified id was not found.
/cooperations/{id}/{resourceId}/completionStatus:
patch:
security:
- cookieAuth: []
tags:
- Cooperations
summary: Update completion status of resource in cooperation by ID
description: Finds and update a completion status of resource in cooperation with the specified ID.
produces:
- application/json
parameters:
- name: id
in: path
required: true
description: Cooperation ID
schema:
type: string
- name: resourceId
in: path
required: true
description: Resource ID
schema:
type: string
requestBody:
required: true
description: Provide required data to update completion status of resource in cooperation.
content:
application/json:
schema:
properties:
completionStatus:
$ref: '#/definitions/completionStatus'
example:
completionStatus: completed
responses:
204:
description: No Content
links:
400:
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/Error'
example:
status: 400
code: INVALID_ID
message: ID is invalid.
401:
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/Error'
example:
status: 401
code: UNAUTHORIZED
message: The requested URL requires user authorization.
403:
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/Error'
examples:
Forbidden:
value:
status: 403
code: FORBIDDEN
message: You do not have permission to perform this action.
'Role required for action':
value:
status: 403
code: ROLE_REQUIRED_FOR_ACTION
message: Only student can perform this action.
404:
description: Not Found
content:
application/json:
schema:
$ref: '#/components/Error'
example:
status: 404
code: DOCUMENT_NOT_FOUND
message: Resource in Cooperation with the specified ID was not found.
422:
description: Unprocessable Entity
content:
application/json:
schema:
$ref: '#/components/Error'
examples:
'Field is not of proper enum value':
value:
status: 422
code: FIELD_IS_NOT_OF_PROPER_ENUM_VALUE
message: 'completionStatus should be either one of the values: [active, completed]'
'Field is not defined':
value:
status: 422
code: FIELD_IS_NOT_DEFINED
message: completionStatus field should be defined
4 changes: 4 additions & 0 deletions src/consts/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ const errors = {
code: 'FIELD_IS_NOT_OF_PROPER_ENUM_VALUE',
message: `${field} should be either one of the values: [${enumSet.join(', ')}]`
}),
ROLE_REQUIRED_FOR_ACTION: (role) => ({
code: 'ROLE_REQUIRED_FOR_ACTION',
message: `Only ${role} can perform this action.`
}),
CHAT_ALREADY_EXISTS: {
code: 'CHAT_ALREADY_EXISTS',
message: 'The chat with this user already exists.'
Expand Down
13 changes: 12 additions & 1 deletion src/controllers/cooperation.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,20 @@ const updateCooperation = async (req, res) => {
res.status(204).end()
}

const updateResourceCompletionStatus = async (req, res) => {
const { id, resourceId } = req.params
const { completionStatus } = req.body
const currentUser = req.user

await cooperationService.updateResourceCompletionStatus({ id, currentUser, resourceId, completionStatus })

res.status(204).end()
}

module.exports = {
getCooperations,
getCooperationById,
createCooperation,
updateCooperation
updateCooperation,
updateResourceCompletionStatus
}
8 changes: 8 additions & 0 deletions src/routes/cooperation.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ const idValidation = require('~/middlewares/idValidation')
const asyncWrapper = require('~/middlewares/asyncWrapper')
const { authMiddleware } = require('~/middlewares/auth')
const isEntityValid = require('~/middlewares/entityValidation')
const validationMiddleware = require('~/middlewares/validation')

const noteRouter = require('~/routes/note')

const cooperationController = require('~/controllers/cooperation')
const Offer = require('~/models/offer')
const Cooperation = require('~/models/cooperation')
const { updateResourceCompletionStatusValidationSchema } = require('~/validation/schemas/cooperation')

const body = [{ model: Offer, idName: 'offer' }]
const params = [{ model: Cooperation, idName: 'id' }]
Expand All @@ -24,5 +26,11 @@ router.get('/', asyncWrapper(cooperationController.getCooperations))
router.post('/', isEntityValid({ body }), asyncWrapper(cooperationController.createCooperation))
router.get('/:id', isEntityValid({ params }), asyncWrapper(cooperationController.getCooperationById))
router.patch('/:id', isEntityValid({ params }), asyncWrapper(cooperationController.updateCooperation))
router.patch(
'/:id/:resourceId/completionStatus',
isEntityValid({ params }),
validationMiddleware(updateResourceCompletionStatusValidationSchema),
asyncWrapper(cooperationController.updateResourceCompletionStatus)
)

module.exports = router
57 changes: 45 additions & 12 deletions src/services/cooperation.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@ const mergeArraysUniqueValues = require('~/utils/mergeArraysUniqueValues')
const removeArraysUniqueValues = require('~/utils/removeArraysUniqueValues')
const handleResources = require('~/utils/handleResources')
const { createError, createForbiddenError } = require('~/utils/errorsHelper')
const { VALIDATION_ERROR, DOCUMENT_NOT_FOUND } = require('~/consts/errors')
const { VALIDATION_ERROR, DOCUMENT_NOT_FOUND, ROLE_REQUIRED_FOR_ACTION } = require('~/consts/errors')
const { roles } = require('~/consts/auth')

const cooperationService = {
_validateCooperationUser: (cooperation, userId) => {
const initiator = cooperation.initiator.toString()
const receiver = cooperation.receiver.toString()

if (initiator !== userId && receiver !== userId) {
throw createForbiddenError()
}
},

getCooperations: async (pipeline) => {
const [result] = await Cooperation.aggregate(pipeline).exec()
return result
Expand Down Expand Up @@ -69,17 +79,8 @@ const cooperationService = {
throw createError(409, VALIDATION_ERROR('You can change only either the status or the price in one operation'))
}

const cooperation = await Cooperation.findById(id).exec()
if (!cooperation) {
throw createError(404, DOCUMENT_NOT_FOUND(Cooperation.modelName))
}

const initiator = cooperation.initiator.toString()
const receiver = cooperation.receiver.toString()

if (initiator !== currentUserId && receiver !== currentUserId) {
throw createForbiddenError()
}
const cooperation = await Cooperation.findById(id)
cooperationService._validateCooperationUser(cooperation, currentUserId)

if (price) {
if (currentUserRole !== cooperation.needAction.toString()) {
Expand Down Expand Up @@ -114,6 +115,38 @@ const cooperationService = {
cooperation.availableQuizzes = removeArraysUniqueValues(cooperation.availableQuizzes, cooperation.finishedQuizzes)
await cooperation.save()
}
},

updateResourceCompletionStatus: async ({ id, currentUser, resourceId, completionStatus }) => {
const { id: currentUserId, role: currentUserRole } = currentUser

if (currentUserRole !== roles.STUDENT) {
throw createError(403, ROLE_REQUIRED_FOR_ACTION(roles.STUDENT))
}

const cooperation = await Cooperation.findById(id)
cooperationService._validateCooperationUser(cooperation, currentUserId)

let resourceIdExists = false

for (const section of cooperation.sections) {
for (const resource of section.resources) {
if (resource.resource.toString() === resourceId) {
resource.completionStatus = completionStatus
resourceIdExists = true
break
}
}
}

if (!resourceIdExists) {
throw createError(404, DOCUMENT_NOT_FOUND([`Resource in ${Cooperation.modelName}`]))
}

cooperation.markModified('sections')

await cooperation.validate()
await cooperation.save()
}
}

Expand Down
Loading

0 comments on commit 38ab22e

Please sign in to comment.