diff --git a/src/controllers/user.js b/src/controllers/user.js index 40ff0f1c..1ba59896 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -1,5 +1,6 @@ import User from '../domain/user.js' import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' +import bcrypt from 'bcrypt' export const create = async (req, res) => { const userToCreate = await User.fromJson(req.body) @@ -57,11 +58,105 @@ export const getAll = async (req, res) => { } export const updateById = async (req, res) => { - const { cohort_id: cohortId } = req.body + const userId = parseInt(req.params.id) + const { + firstName, + lastName, + email, + biography, + githubUrl, + password, + cohort_id: cohortIdSnake, + cohortId: cohortIdCamel, + role + } = req.body + + const cohortId = cohortIdSnake ?? cohortIdCamel - if (!cohortId) { - return sendDataResponse(res, 400, { cohort_id: 'Cohort ID is required' }) - } + try { + const userToUpdate = await User.findById(userId) + + if (!userToUpdate) { + return sendDataResponse(res, 404, { id: 'User not found' }) + } + + // Check if user is updating their own profile or is a teacher + const isOwnProfile = req.user.id === userId + const isTeacher = req.user.role === 'TEACHER' + + if (!isOwnProfile && !isTeacher) { + return sendDataResponse(res, 403, { + authorization: 'You are not authorized to update this profile' + }) + } + + // Check if student is trying to update restricted fields + if (!isTeacher && (cohortId || role)) { + return sendDataResponse(res, 403, { + authorization: + 'Students cannot modify cohort or role information. Please contact a teacher for these changes.' + }) + } + + // Create update data object + const updateData = {} + + // Helper function to validate and add field if it has a valid value + const addValidField = (field, value) => { + if ( + value !== undefined && + value !== null && + value !== '' && + value !== 'string' + ) { + updateData[field] = value + } + } - return sendDataResponse(res, 201, { user: { cohort_id: cohortId } }) + // Profile fields any user can update on their own profile + if (isOwnProfile) { + if (password && typeof password === 'string') { + updateData.passwordHash = await bcrypt.hash(password, 8) + } + } + + // Fields only teachers can update + if (isTeacher) { + if (cohortId !== undefined) { + const cohortIdInt = parseInt(cohortId, 10) + if (!isNaN(cohortIdInt)) { + updateData.cohortId = cohortIdInt + } + } + if (role === 'STUDENT' || role === 'TEACHER') { + updateData.role = role + } + } + + // Profile fields any user can update on their own profile + if (isOwnProfile || isTeacher) { + addValidField('firstName', firstName) + addValidField('lastName', lastName) + addValidField('bio', biography) + addValidField('githubUrl', githubUrl) + addValidField('email', email) + } + + // If no valid fields to update + if (Object.keys(updateData).length === 0) { + return sendDataResponse(res, 400, { + fields: 'No valid fields provided for update' + }) + } + + const updatedUser = await userToUpdate.update(updateData) + + return sendDataResponse(res, 200, updatedUser) + } catch (error) { + return sendMessageResponse( + res, + 500, + `Unable to update user: ${error.message}` + ) + } } diff --git a/src/domain/user.js b/src/domain/user.js index fd7734c7..eb393def 100644 --- a/src/domain/user.js +++ b/src/domain/user.js @@ -170,4 +170,57 @@ export default class User { return foundUsers.map((user) => User.fromDb(user)) } + + async update(data) { + const updateData = { + where: { + id: this.id + }, + data: {}, + include: { + profile: true + } + } + + // Handle user-level updates + if (data.email && data.email !== 'string') + updateData.data.email = data.email + if (data.passwordHash) updateData.data.password = data.passwordHash + if (data.role && (data.role === 'STUDENT' || data.role === 'TEACHER')) { + updateData.data.role = data.role + } + if (typeof data.cohortId === 'number') { + updateData.data.cohortId = data.cohortId + } + + // Handle profile-related updates + const profileUpdates = {} + let hasProfileUpdates = false + + if (data.firstName && data.firstName !== 'string') { + profileUpdates.firstName = data.firstName + hasProfileUpdates = true + } + if (data.lastName && data.lastName !== 'string') { + profileUpdates.lastName = data.lastName + hasProfileUpdates = true + } + if (data.bio && data.bio !== 'string') { + profileUpdates.bio = data.bio + hasProfileUpdates = true + } + if (data.githubUrl && data.githubUrl !== 'string') { + profileUpdates.githubUrl = data.githubUrl + hasProfileUpdates = true + } + + if (hasProfileUpdates) { + updateData.data.profile = { + update: profileUpdates + } + } + + const updatedUser = await dbClient.user.update(updateData) + return User.fromDb(updatedUser) + } } diff --git a/src/routes/user.js b/src/routes/user.js index 9f63d162..c2714f9b 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -1,15 +1,12 @@ import { Router } from 'express' import { create, getById, getAll, updateById } from '../controllers/user.js' -import { - validateAuthentication, - validateTeacherRole -} from '../middleware/auth.js' +import { validateAuthentication } from '../middleware/auth.js' const router = Router() router.post('/', create) router.get('/', validateAuthentication, getAll) router.get('/:id', validateAuthentication, getById) -router.patch('/:id', validateAuthentication, validateTeacherRole, updateById) +router.patch('/:id', validateAuthentication, updateById) export default router