From 4e5a437ddb9bfb22c5420ba762849a11411b74be Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Fri, 22 Nov 2024 18:00:06 -0500 Subject: [PATCH 01/19] Programming implementation is done, all that's left is deleting/dropping the old tables and adding Pl to it. --- .../EmployeeCards/EmployeeCards.tsx | 2 +- server/src/router/upload.ts | 42 +++++++++---------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/EmployeeCards/EmployeeCards.tsx b/frontend/src/components/EmployeeCards/EmployeeCards.tsx index dde780853..3d599c577 100644 --- a/frontend/src/components/EmployeeCards/EmployeeCards.tsx +++ b/frontend/src/components/EmployeeCards/EmployeeCards.tsx @@ -86,7 +86,7 @@ const EmployeeCard = ({ firstName={firstName} lastName={lastName} netId={netId} - photoLink={photoLink} + photoLink={photoLink ? photoLink : 'frontend/public/logo512.png'} >

{fmtPhone}

diff --git a/server/src/router/upload.ts b/server/src/router/upload.ts index 532202603..fd913cad3 100644 --- a/server/src/router/upload.ts +++ b/server/src/router/upload.ts @@ -6,51 +6,49 @@ import { Driver } from '../models/driver'; import { Admin } from '../models/admin'; import { validateUser } from '../util'; +//const BUCKET_NAME = 'dti-carriage-staging-public'; old bucket +const BUCKET_NAME = 'carriage-images'; const router = express.Router(); +const s3Bucket = new S3(); -const BUCKET_NAME = 'dti-carriage-staging-public'; -const s3bucket = new S3(); - -// Uploads base64-encoded fileBuffer to S3 in the folder {tableName} -// Sets the user's DB photoLink field to the url of the uploaded image, if not set router.post('/', validateUser('User'), (req, res) => { const { body: { id, tableName, fileBuffer }, } = req; - const validTables = ['Riders', 'Drivers', 'Admins']; if (fileBuffer === '') { res.status(400).send({ err: 'Invalid file name: empty string' }); - } else if (validTables.includes(tableName)) { + } else if (['Riders', 'Drivers', 'Admins'].includes(tableName)) { const objectKey = `${tableName}/${id}`; - const params = { Bucket: BUCKET_NAME, Key: objectKey, Body: Buffer.from(fileBuffer, 'base64'), - ACL: ObjectCannedACL.public_read, // Use the enum for ACL here + ACL: ObjectCannedACL.public_read, ContentEncoding: 'base64', ContentType: 'image/jpeg', }; - - // Put object into S3 bucket - s3bucket.putObject(params, (s3Err: any) => { + s3Bucket.putObject(params, (s3Err: any) => { if (s3Err) { - res.status(s3Err || 500).send({ err: s3Err.message }); + res.status(s3Err.statusCode || 500).send({ err: s3Err.message }); } else { const photoLink = `https://${BUCKET_NAME}.s3.us-east-2.amazonaws.com/${objectKey}`; const operation = { $SET: { photoLink } }; - - if (tableName === 'Drivers') { - db.update(res, Driver, { id }, operation, tableName); - } else if (tableName === 'Riders') { - db.update(res, Rider, { id }, operation, tableName); - } else if (tableName === 'Admins') { - db.update(res, Admin, { id }, operation, tableName); + switch (tableName) { + case 'Drivers': + db.update(res, Driver, { id }, operation, tableName); + break; + case 'Riders': + db.update(res, Rider, { id }, operation, tableName); + break; + case 'Admins': + db.update(res, Admin, { id }, operation, tableName); + break; + default: + res.status(400).send({ err: 'Invalid table.' }); + break; } } }); - } else { - res.status(400).send({ err: 'Invalid table.' }); } }); From 6e2ae67ea26a8d4cac2372758e39978a45d6eafb Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Wed, 27 Nov 2024 22:24:33 -0500 Subject: [PATCH 02/19] progress on fixing empmodal and api --- .../EmployeeModal/EmployeeModal.tsx | 389 +++++++++++------- .../src/components/EmployeeModal/Upload.tsx | 14 +- server/src/app.ts | 10 + 3 files changed, 251 insertions(+), 162 deletions(-) diff --git a/frontend/src/components/EmployeeModal/EmployeeModal.tsx b/frontend/src/components/EmployeeModal/EmployeeModal.tsx index 8e965fbbe..62ebe16e4 100644 --- a/frontend/src/components/EmployeeModal/EmployeeModal.tsx +++ b/frontend/src/components/EmployeeModal/EmployeeModal.tsx @@ -72,9 +72,6 @@ const EmployeeModal = ({ const { refreshAdmins, refreshDrivers } = useEmployees(); const methods = useForm(); - const modalTitle = existingEmployee ? 'Edit Profile' : 'Add an Employee'; - const submitButtonText = existingEmployee ? 'Save' : 'Add'; - const closeModal = () => { methods.clearErrors(); setIsOpen(false); @@ -99,118 +96,131 @@ const EmployeeModal = ({ return result; }; - const uploadPhotoForEmployee = async ( + async function uploadEmployeePhoto( employeeId: string, table: string, refresh: () => Promise, - isCreate: boolean // show toast if new employee is created - ) => { - const photo = { + imageBase64: string + ): Promise { + const photoData = { id: employeeId, tableName: table, fileBuffer: imageBase64, }; - // Upload image - await axios - .post('/api/upload', photo) - .then(() => { - refresh(); - }) - .catch((err) => console.log(err)); - }; - const createNewEmployee = async ( + try { + console.log('Uploading photo for employee:', employeeId); + + // Make the photo upload request + await axios.post('/api/upload', photoData); + + console.log('Photo uploaded successfully.'); + + // Refresh after the upload is complete + //await refresh(); + } catch (error) { + console.error('Error uploading photo:', error); + + // Show a toast notification for the failure (optional) + showToast('Failed to upload the photo.', ToastStatus.ERROR); + + // Optionally throw the error to propagate it if needed + throw error; + } + } + + async function createEmployee( employeeData: AdminData | DriverData, endpoint: string, refresh: () => Promise, table: string - ) => { - const res = await axios.post(endpoint, employeeData); - if (imageBase64 === '') { - // If no image has been uploaded, create new employee - refresh(); + ): Promise { + try { + // Create the employee + const { data: createdEmployee } = await axios.post( + endpoint, + employeeData + ); + + // Upload the photo if provided + if (imageBase64) { + await uploadEmployeePhoto( + createdEmployee.id || '', + table, + refresh, + imageBase64 + ); + console.log('Photo uploaded successfully.'); + } + + // Refresh after successful creation and photo upload + //await refresh(); + showToast('The employee has been added.', ToastStatus.SUCCESS); - } else { - const { data: createdEmployee } = await res.data; - uploadPhotoForEmployee(createdEmployee.id, table, refresh, true); + + return createdEmployee; + } catch (error) { + console.error('Error creating employee:', error); + showToast('Failed to add the employee.', ToastStatus.ERROR); + throw error; } - return res; - }; + } - const updateExistingEmployee = async ( + async function updateEmployee( employeeData: AdminData | DriverData, endpoint: string, refresh: () => Promise, table: string - ) => { - const updatedEmployee = await axios - .put(`${endpoint}/${existingEmployee!.id}`, employeeData) - .then((res) => { - refresh(); - showToast('The employee has been edited.', ToastStatus.SUCCESS); - return res.data; - }); - if (imageBase64 !== '') { - uploadPhotoForEmployee(updatedEmployee.id, table, refresh, false); - } - return updatedEmployee; - }; - - const createOrUpdateDriver = async ( - driver: AdminData | DriverData, - isNewDriver = false - ) => { - if (isNewDriver) { - return await createNewEmployee( - driver, - '/api/drivers', - () => refreshDrivers(), - 'Drivers' - ); - } else { - return await updateExistingEmployee( - driver, - '/api/drivers', - () => refreshDrivers(), - 'Drivers' + ): Promise { + try { + // Update the employee + const { data: updatedEmployee } = await axios.put( + `${endpoint}/${existingEmployee!.id}`, + employeeData ); + + // Upload the photo if provided + if (imageBase64) { + await uploadEmployeePhoto( + employeeData?.id || '', + table, + refresh, + imageBase64 + ); + console.log('Photo uploaded successfully.'); + } + + //await refresh(); + showToast('The employee has been edited.', ToastStatus.SUCCESS); + return updatedEmployee; + } catch (error) { + console.error('Error updating employee:', error); + showToast('Failed to edit the employee.', ToastStatus.ERROR); + throw error; } - }; + } - const createOrUpdateAdmin = async (admin: AdminData, isNewAdmin = false) => { - if (isNewAdmin) { - await createNewEmployee( - admin, - '/api/admins', - () => refreshAdmins(), - 'Admins' - ); + async function deleteEmployee( + id: string | undefined, + emptype: 'drivers' | 'admins' + ) { + if (id === undefined) { + console.log('Invalid/Null ID: deleteEmployee'); } else { - await updateExistingEmployee( - admin, - '/api/admins', - () => refreshAdmins(), - 'Admins' - ); + await axios.delete(`/api/${emptype}/${id}`); } - }; - - const deleteDriver = async (id: string | undefined) => { - await axios.delete(`/api/drivers/${id}`); - }; - - const deleteAdmin = async (id: string | undefined) => { - await axios.delete(`/api/admins/${id}`); - }; + } - const onSubmit = async (data: ObjectType) => { + async function onSubmit(data: ObjectType) { + console.log(selectedRole) + console.log("console log is here") const { firstName, lastName, netid, phoneNumber, startDate, availability } = data; const driver = { firstName, lastName, - email: netid + '@cornell.edu', + email: `${netid}@cornell.edu`, phoneNumber, startDate, availability: parseAvailability(availability), @@ -219,103 +229,162 @@ const EmployeeModal = ({ const admin = { firstName, lastName, - email: netid + '@cornell.edu', - type: selectedRole.filter((role) => !(role === 'driver')), + email: `${netid}@cornell.edu`, + type: selectedRole.filter((role) => role !== 'driver'), phoneNumber, availability: parseAvailability(availability), isDriver: selectedRole.includes('driver'), }; - const existingDriver = existingEmployee?.isDriver === undefined; - const existingAdmin = existingEmployee?.isDriver !== undefined; - - if (existingEmployee) { - if (selectedRole.includes('driver')) { - if (selectedRole.some((role) => role.includes('admin'))) { - if (existingDriver && existingAdmin) { - await createOrUpdateDriver(driver, false); - await createOrUpdateAdmin(admin, false); - } else if (existingDriver) { - await createOrUpdateDriver(driver, false); - await createOrUpdateAdmin( - { ...admin, id: existingEmployee.id }, - true - ); - } else if (existingAdmin) { - await createOrUpdateDriver( - { ...driver, id: existingEmployee.id }, - true - ); - await createOrUpdateAdmin(admin, false); - } - } else { - if (existingDriver && existingAdmin) { - await createOrUpdateDriver(driver, false); - await deleteAdmin(existingEmployee.id); - } else if (existingDriver) { - await createOrUpdateDriver(driver, false); - } else if (existingAdmin) { - await createOrUpdateDriver( - { ...driver, id: existingEmployee.id }, - true - ); - await deleteAdmin(existingEmployee.id); - } + const existingDriver = existingEmployee?.isDriver === true; + const existingAdmin = existingEmployee?.isDriver === false; + + switch (true) { + case existingEmployee && + selectedRole.includes('driver') && + selectedRole.includes('admins'): + if (existingDriver && existingAdmin) { + await updateEmployee( + driver, + '/api/drivers', + refreshDrivers, + 'drivers' + ); + await updateEmployee(admin, '/api/admins', refreshAdmins, 'admins'); + } else if (existingDriver) { + await updateEmployee( + driver, + '/api/drivers', + refreshDrivers, + 'drivers' + ); + await createEmployee( + { ...admin, id: existingEmployee.id }, + '/api/admins', + refreshAdmins, + 'admins' + ); + } else if (existingAdmin) { + await createEmployee( + { ...driver, id: existingEmployee.id }, + '/api/drivers', + refreshDrivers, + 'drivers' + ); + await updateEmployee(admin, '/api/admins', refreshAdmins, 'admins'); } - } else { + break; + + case existingEmployee && selectedRole.includes('driver'): if (existingDriver && existingAdmin) { - await deleteDriver(existingEmployee.id); - await createOrUpdateAdmin(admin, false); + await updateEmployee( + driver, + '/api/drivers', + refreshDrivers, + 'drivers' + ); + await deleteEmployee(existingEmployee?.id, 'admins'); } else if (existingDriver) { - await deleteDriver(existingEmployee.id); - await createOrUpdateAdmin( + await updateEmployee( + driver, + '/api/Drivers', + refreshDrivers, + 'drivers' + ); + } else if (existingAdmin) { + await createEmployee( + { ...driver, id: existingEmployee.id }, + '/api/drivers', + refreshDrivers, + 'drivers' + ); + await deleteEmployee(existingEmployee?.id, 'admins'); + } + break; + + case existingEmployee && selectedRole.includes('admin'): + if (existingDriver && existingAdmin) { + await deleteEmployee(existingEmployee?.id, 'drivers'); + await updateEmployee(admin, '/api/admins', refreshAdmins, 'admins'); + } else if (existingDriver) { + await deleteEmployee(existingEmployee?.id, 'drivers'); + await createEmployee( { ...admin, id: existingEmployee.id }, - true + '/api/admins', + refreshAdmins, + 'admins' ); } - } - } else { - if (selectedRole.includes('driver')) { - if (selectedRole.some((role) => role.includes('admin'))) { - const id = (await createOrUpdateDriver(driver, true)).data.data.id; - await createOrUpdateAdmin({ ...admin, id: id }, true); - } else { - await createOrUpdateDriver(driver, true); + break; + + default: + if (selectedRole.includes('driver') && selectedRole.includes('admin')) { + const id = ( + await createEmployee( + driver, + '/api/drivers', + refreshDrivers, + 'drivers' + ) + ).id; + await createEmployee( + { ...admin, id }, + '/api/admins', + refreshAdmins, + 'admins' + ); + } else if (selectedRole.includes('driver')) { + await createEmployee( + driver, + '/api/drivers', + refreshDrivers, + 'drivers' + ); + } else if (selectedRole.includes('admin')) { + await createEmployee(admin, '/api/admins', refreshAdmins, 'admins'); } - } else { - await createOrUpdateAdmin(admin, true); - } + break; } closeModal(); - }; + } - function updateBase64(e: React.ChangeEvent) { + async function updateBase64(e: React.ChangeEvent) { e.preventDefault(); - if (e.target.files && e.target.files[0]) { + const { files } = e.target; + if (files && files[0]) { + const file = files[0]; const reader = new FileReader(); - const file = e.target.files[0]; + + console.log('Starting to read the file:', file.name); reader.readAsDataURL(file); - reader.onload = function () { - let res = reader.result; - if (res) { - res = res.toString(); - // remove "data:image/png;base64," and "data:image/jpeg;base64," - const strBase64 = res.toString().substring(res.indexOf(',') + 1); - setImageBase64(strBase64); + + reader.onload = async () => { + const base64 = reader.result?.toString().split(',')[1]; // Extract base64 + console.log( + 'File read successfully. Base64 extracted:', + base64 ? base64.substring(0, 20) + '...' : 'No base64 data' + ); + + if (base64) { + setImageBase64(base64); // Save the base64 string + console.error('Set base64 data.'); } }; - reader.onerror = function (error) { - console.log('Error reading file: ', error); + + reader.onerror = (error) => { + console.error('Error reading file:', error); }; } else { - console.log('Undefined file upload'); + console.error('No file selected.'); + alert('No file selected'); } } + return ( <> +
{ + console.log('Form Event:', e); + methods.handleSubmit(onSubmit); + }} aria-labelledby="employee-modal" > -
diff --git a/frontend/src/components/EmployeeModal/Upload.tsx b/frontend/src/components/EmployeeModal/Upload.tsx index 62ef509cf..dc9bff7f7 100644 --- a/frontend/src/components/EmployeeModal/Upload.tsx +++ b/frontend/src/components/EmployeeModal/Upload.tsx @@ -2,6 +2,8 @@ import React, { useState, createRef } from 'react'; import uploadBox from './upload.svg'; import styles from './employeemodal.module.css'; +const IMAGE_SIZE_LIMIT = 10000000; + type UploadProps = { imageChange: (e: React.ChangeEvent) => void; existingPhoto?: string; @@ -12,23 +14,23 @@ const Upload = ({ imageChange, existingPhoto }: UploadProps) => { existingPhoto ? `${existingPhoto}` : '' ); const inputRef = createRef(); + /* This is for accessibility purposes only */ const handleKeyboardPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { inputRef.current && inputRef.current.click(); } }; + function previewImage(e: React.ChangeEvent) { e.preventDefault(); const { files } = e.target; - if (files && files[0] && files[0].size < 500000) { - const file = files[0]; - const photoURL = URL.createObjectURL(file); - setImageURL(photoURL); + if (files && files[0] && files[0].size < IMAGE_SIZE_LIMIT) { + setImageURL(URL.createObjectURL(files[0])); + imageChange(e); } else { - console.log('invalid file'); + alert('File is too large'); } - imageChange(e); } return ( diff --git a/server/src/app.ts b/server/src/app.ts index 64c6cbe6e..457065b8d 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -36,7 +36,17 @@ const app = express(); app.use(cors()); app.use(express.json({ limit: '500kb' })); app.use(express.urlencoded({ extended: false })); +app.use((req, res, next) => { + console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); + next(); +}); +app.use((req, res, next) => { + console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); + console.log('Headers:', req.headers); + console.log('Body:', req.body); // Requires body-parser or express.json() + next(); +}); app.use('/api/riders', rider); app.use('/api/drivers', driver); app.use('/api/admins', admin); From 4769738c468bc0e50ba3c4896ceab8d206bded5e Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Thu, 28 Nov 2024 04:57:39 -0500 Subject: [PATCH 03/19] s3 working impl --- .../EmployeeCards/EmployeeCards.tsx | 3 +- .../EmployeeModal/EmployeeModal.tsx | 301 ++++++++---------- .../src/components/EmployeeModal/Upload.tsx | 2 +- server/src/app.ts | 4 + server/src/router/upload.ts | 10 +- 5 files changed, 155 insertions(+), 165 deletions(-) diff --git a/frontend/src/components/EmployeeCards/EmployeeCards.tsx b/frontend/src/components/EmployeeCards/EmployeeCards.tsx index 3d599c577..f2e769eb6 100644 --- a/frontend/src/components/EmployeeCards/EmployeeCards.tsx +++ b/frontend/src/components/EmployeeCards/EmployeeCards.tsx @@ -86,7 +86,8 @@ const EmployeeCard = ({ firstName={firstName} lastName={lastName} netId={netId} - photoLink={photoLink ? photoLink : 'frontend/public/logo512.png'} + photoLink={ + photoLink } >

{fmtPhone}

diff --git a/frontend/src/components/EmployeeModal/EmployeeModal.tsx b/frontend/src/components/EmployeeModal/EmployeeModal.tsx index 62ebe16e4..199b25845 100644 --- a/frontend/src/components/EmployeeModal/EmployeeModal.tsx +++ b/frontend/src/components/EmployeeModal/EmployeeModal.tsx @@ -107,29 +107,62 @@ const EmployeeModal = ({ tableName: table, fileBuffer: imageBase64, }; - try { - console.log('Uploading photo for employee:', employeeId); - - // Make the photo upload request - await axios.post('/api/upload', photoData); - - console.log('Photo uploaded successfully.'); - - // Refresh after the upload is complete - //await refresh(); + await axios.post('/api/upload/', photoData); } catch (error) { console.error('Error uploading photo:', error); - - // Show a toast notification for the failure (optional) - showToast('Failed to upload the photo.', ToastStatus.ERROR); - - // Optionally throw the error to propagate it if needed - throw error; } + refresh(); } + const createOrUpdateDriver = async ( + driver: AdminData | DriverData, + uid: string | '', + isNewDriver = false + ) => { + if (isNewDriver) { + return await createEmployee( + uid, + driver, + '/api/drivers', + () => refreshDrivers(), + 'Drivers' + ); + } else { + return await updateEmployee( + uid, + driver, + '/api/drivers', + () => refreshDrivers(), + 'Drivers' + ); + } + }; + const createOrUpdateAdmin = async ( + admin: AdminData, + uid: string | '', + isNewAdmin = false + ) => { + if (isNewAdmin) { + await createEmployee( + uid, + admin, + '/api/admins', + () => refreshAdmins(), + 'Admins' + ); + } else { + await updateEmployee( + uid, + admin, + '/api/admins', + () => refreshAdmins(), + 'Admins' + ); + } + }; async function createEmployee( + id: string, employeeData: AdminData | DriverData, endpoint: string, refresh: () => Promise, @@ -137,27 +170,23 @@ const EmployeeModal = ({ ): Promise { try { // Create the employee + // HERE const { data: createdEmployee } = await axios.post( endpoint, employeeData ); // Upload the photo if provided - if (imageBase64) { - await uploadEmployeePhoto( - createdEmployee.id || '', - table, - refresh, - imageBase64 - ); + if (imageBase64 !== '') { + await uploadEmployeePhoto(id || '', table, refresh, imageBase64); + console.log('Photo uploaded successfully.'); } // Refresh after successful creation and photo upload - //await refresh(); + await refresh(); showToast('The employee has been added.', ToastStatus.SUCCESS); - return createdEmployee; } catch (error) { console.error('Error creating employee:', error); @@ -167,32 +196,21 @@ const EmployeeModal = ({ } async function updateEmployee( + id: string, employeeData: AdminData | DriverData, endpoint: string, refresh: () => Promise, table: string ): Promise { try { - // Update the employee - const { data: updatedEmployee } = await axios.put( - `${endpoint}/${existingEmployee!.id}`, - employeeData - ); + await axios.put(`${endpoint}/${id}`, employeeData); // Upload the photo if provided - if (imageBase64) { - await uploadEmployeePhoto( - employeeData?.id || '', - table, - refresh, - imageBase64 - ); - console.log('Photo uploaded successfully.'); - } + uploadEmployeePhoto(id || '', table, refresh, imageBase64); + console.log('Photo uploaded successfully.'); - //await refresh(); + refresh(); showToast('The employee has been edited.', ToastStatus.SUCCESS); - return updatedEmployee; } catch (error) { console.error('Error updating employee:', error); showToast('Failed to edit the employee.', ToastStatus.ERROR); @@ -210,10 +228,7 @@ const EmployeeModal = ({ await axios.delete(`/api/${emptype}/${id}`); } } - async function onSubmit(data: ObjectType) { - console.log(selectedRole) - console.log("console log is here") const { firstName, lastName, netid, phoneNumber, startDate, availability } = data; @@ -235,116 +250,86 @@ const EmployeeModal = ({ availability: parseAvailability(availability), isDriver: selectedRole.includes('driver'), }; - - const existingDriver = existingEmployee?.isDriver === true; - const existingAdmin = existingEmployee?.isDriver === false; - switch (true) { - case existingEmployee && - selectedRole.includes('driver') && - selectedRole.includes('admins'): - if (existingDriver && existingAdmin) { - await updateEmployee( - driver, - '/api/drivers', - refreshDrivers, - 'drivers' - ); - await updateEmployee(admin, '/api/admins', refreshAdmins, 'admins'); - } else if (existingDriver) { - await updateEmployee( - driver, - '/api/drivers', - refreshDrivers, - 'drivers' - ); - await createEmployee( - { ...admin, id: existingEmployee.id }, - '/api/admins', - refreshAdmins, - 'admins' - ); - } else if (existingAdmin) { - await createEmployee( - { ...driver, id: existingEmployee.id }, - '/api/drivers', - refreshDrivers, - 'drivers' - ); - await updateEmployee(admin, '/api/admins', refreshAdmins, 'admins'); - } - break; - - case existingEmployee && selectedRole.includes('driver'): - if (existingDriver && existingAdmin) { - await updateEmployee( - driver, - '/api/drivers', - refreshDrivers, - 'drivers' - ); - await deleteEmployee(existingEmployee?.id, 'admins'); - } else if (existingDriver) { - await updateEmployee( - driver, - '/api/Drivers', - refreshDrivers, - 'drivers' - ); - } else if (existingAdmin) { - await createEmployee( - { ...driver, id: existingEmployee.id }, - '/api/drivers', - refreshDrivers, - 'drivers' - ); - await deleteEmployee(existingEmployee?.id, 'admins'); - } - break; - - case existingEmployee && selectedRole.includes('admin'): - if (existingDriver && existingAdmin) { - await deleteEmployee(existingEmployee?.id, 'drivers'); - await updateEmployee(admin, '/api/admins', refreshAdmins, 'admins'); - } else if (existingDriver) { - await deleteEmployee(existingEmployee?.id, 'drivers'); - await createEmployee( - { ...admin, id: existingEmployee.id }, - '/api/admins', - refreshAdmins, - 'admins' - ); - } - break; - - default: - if (selectedRole.includes('driver') && selectedRole.includes('admin')) { - const id = ( - await createEmployee( - driver, - '/api/drivers', - refreshDrivers, - 'drivers' - ) - ).id; - await createEmployee( - { ...admin, id }, - '/api/admins', - refreshAdmins, - 'admins' - ); - } else if (selectedRole.includes('driver')) { - await createEmployee( - driver, - '/api/drivers', - refreshDrivers, - 'drivers' - ); - } else if (selectedRole.includes('admin')) { - await createEmployee(admin, '/api/admins', refreshAdmins, 'admins'); - } - break; + // Sort selectedRole array in reverse order: ['sds-admin', 'redrunner-admin', 'driver'] + selectedRole.sort().reverse(); + + + let iteration = 0; + + for (const employeeRole of selectedRole) { + switch (employeeRole) { + case 'sds-admin': + if (iteration > 0) continue; + + console.log('Handling sds-admin...'); + if (existingEmployee) { + // Handle existing employee scenario for sds-admin + if (existingEmployee.isDriver) { + console.log('Switching from driver to sds-admin...'); + await deleteEmployee(existingEmployee.id, 'drivers'); + await createOrUpdateAdmin(admin, existingEmployee.id || '', false); + } else { + console.log('Updating existing sds-admin...'); + await createOrUpdateAdmin(admin, existingEmployee.id || '', false); + } + } else { + // Handle new employee creation for sds-admin + console.log('Creating new sds-admin...'); + await createOrUpdateAdmin(admin, '', true); + } + break; + + case 'redrunner-admin': + if (iteration > 0) continue; // Don't create a duplicate + + console.log('Handling redrunner-admin...'); + if (existingEmployee) { + // Handle existing employee scenario for redrunner-admin + if (existingEmployee.isDriver) { + console.log('Switching from driver to redrunner-admin...'); + await deleteEmployee(existingEmployee.id, 'drivers'); + await createOrUpdateAdmin(admin, existingEmployee.id || '', false); + } else { + console.log('Updating existing redrunner-admin...'); + await createOrUpdateAdmin(admin, existingEmployee.id || '', true); + } + } else { + // Handle new employee creation for redrunner-admin + console.log('Creating new redrunner-admin...'); + await createOrUpdateAdmin(admin, '', true); + } + break; + + case 'driver': + if (existingEmployee) { + if (existingEmployee.isDriver) { + console.log('Updating existing driver...'); + await createOrUpdateDriver(driver, '', false); + } else { + console.log('Switching from admin to driver...'); + await deleteEmployee(existingEmployee.id, 'admins'); + await createOrUpdateDriver( + driver, + existingEmployee.id || '', + true + ); + } + } else { + console.log('Creating new driver...'); + await createOrUpdateDriver(driver, '', true); + } + break; + + default: + console.log(`Unrecognized role: ${employeeRole}`); + break; + } + iteration += 1; // Increment iteration to keep track of processed roles + console.log(iteration) } + + console.log('Completed role processing'); closeModal(); } @@ -368,7 +353,7 @@ const EmployeeModal = ({ if (base64) { setImageBase64(base64); // Save the base64 string - console.error('Set base64 data.'); + console.log('Set base64 data.'); } }; @@ -397,8 +382,8 @@ const EmployeeModal = ({
{ - console.log('Form Event:', e); - methods.handleSubmit(onSubmit); + methods.handleSubmit(onSubmit)(e); + console.log(e); }} aria-labelledby="employee-modal" > @@ -419,11 +404,7 @@ const EmployeeModal = ({ selectedRoles={selectedRole} setSelectedRoles={setSelectedRole} /> -
diff --git a/frontend/src/components/EmployeeModal/Upload.tsx b/frontend/src/components/EmployeeModal/Upload.tsx index dc9bff7f7..7ac855876 100644 --- a/frontend/src/components/EmployeeModal/Upload.tsx +++ b/frontend/src/components/EmployeeModal/Upload.tsx @@ -2,7 +2,7 @@ import React, { useState, createRef } from 'react'; import uploadBox from './upload.svg'; import styles from './employeemodal.module.css'; -const IMAGE_SIZE_LIMIT = 10000000; +const IMAGE_SIZE_LIMIT = 50000; type UploadProps = { imageChange: (e: React.ChangeEvent) => void; diff --git a/server/src/app.ts b/server/src/app.ts index 457065b8d..d7c882603 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -47,6 +47,7 @@ app.use((req, res, next) => { console.log('Body:', req.body); // Requires body-parser or express.json() next(); }); + app.use('/api/riders', rider); app.use('/api/drivers', driver); app.use('/api/admins', admin); @@ -57,6 +58,9 @@ app.use('/api/auth', auth); app.use('/api/upload', upload); app.use('/api/notification', notification); app.use('/api/stats', stats); +// Increase JSON payload limit +app.use(express.json({ limit: '5000mb' })); +app.use(express.urlencoded({ extended: true, limit: '5000mb' })); app.get('/api/health-check', (_, response) => response.status(200).send('OK')); // Serve static files from frontend diff --git a/server/src/router/upload.ts b/server/src/router/upload.ts index fd913cad3..9c6fa1cc3 100644 --- a/server/src/router/upload.ts +++ b/server/src/router/upload.ts @@ -5,11 +5,15 @@ import { Rider } from '../models/rider'; import { Driver } from '../models/driver'; import { Admin } from '../models/admin'; import { validateUser } from '../util'; +import dotenv from 'dotenv'; + +// Load environment variables from .env file (if available) +dotenv.config(); //const BUCKET_NAME = 'dti-carriage-staging-public'; old bucket const BUCKET_NAME = 'carriage-images'; const router = express.Router(); -const s3Bucket = new S3(); +const s3Bucket = new S3({ region: 'us-east-2' }); router.post('/', validateUser('User'), (req, res) => { const { @@ -17,15 +21,15 @@ router.post('/', validateUser('User'), (req, res) => { } = req; if (fileBuffer === '') { res.status(400).send({ err: 'Invalid file name: empty string' }); + } else if (id == '') { + res.status(400).send({ err: 'Empty ID: empty string' }); } else if (['Riders', 'Drivers', 'Admins'].includes(tableName)) { const objectKey = `${tableName}/${id}`; const params = { Bucket: BUCKET_NAME, Key: objectKey, Body: Buffer.from(fileBuffer, 'base64'), - ACL: ObjectCannedACL.public_read, ContentEncoding: 'base64', - ContentType: 'image/jpeg', }; s3Bucket.putObject(params, (s3Err: any) => { if (s3Err) { From 880ea906b295efa20b2f8aa8d046c04156674038 Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Thu, 28 Nov 2024 15:02:58 -0500 Subject: [PATCH 04/19] S3 UPload is Functional --- .../EmployeeCards/EmployeeCards.tsx | 21 +- .../EmployeeModal/EmployeeModal.tsx | 233 +++++++----------- server/src/router/upload.ts | 80 +++--- 3 files changed, 147 insertions(+), 187 deletions(-) diff --git a/frontend/src/components/EmployeeCards/EmployeeCards.tsx b/frontend/src/components/EmployeeCards/EmployeeCards.tsx index f2e769eb6..c2089a1ce 100644 --- a/frontend/src/components/EmployeeCards/EmployeeCards.tsx +++ b/frontend/src/components/EmployeeCards/EmployeeCards.tsx @@ -33,7 +33,12 @@ const EmployeeCard = ({ }: EmployeeCardProps) => { const navigate = useNavigate(); const netId = email.split('@')[0]; - const fmtPhone = formatPhone(phoneNumber); + const fmtPhone = ''; + if (phoneNumber !== undefined) { + const fmtPhone = formatPhone(phoneNumber); + } else { + const fmtPhone = ''; + } const formatAvail = (availability: { [key: string]: { startTime: string; endTime: string }; @@ -59,17 +64,6 @@ const EmployeeCard = ({ return 'Driver'; }; - const userInfo = { - id, - firstName, - lastName, - netId, - type, - phone: fmtPhone, - photoLink, - startDate, - }; - const handleClick = () => { const path = isAdmin || isBoth ? `/admin/admins/${id}` : `/admin/drivers/${id}`; @@ -86,8 +80,7 @@ const EmployeeCard = ({ firstName={firstName} lastName={lastName} netId={netId} - photoLink={ - photoLink } + photoLink={photoLink} >

{fmtPhone}

diff --git a/frontend/src/components/EmployeeModal/EmployeeModal.tsx b/frontend/src/components/EmployeeModal/EmployeeModal.tsx index 199b25845..601ee0a27 100644 --- a/frontend/src/components/EmployeeModal/EmployeeModal.tsx +++ b/frontend/src/components/EmployeeModal/EmployeeModal.tsx @@ -87,13 +87,18 @@ const EmployeeModal = ({ * the start and end time of each availibility period */ const parseAvailability = (availability: ObjectType[]) => { - const result: ObjectType = {}; - availability.forEach(({ startTime, endTime, days }) => { - days.forEach((day: string) => { - result[day] = { startTime, endTime }; + if (availability === null || availability === undefined) { + console.error('Null ptr: Availablity'); + return []; // placeholder + } else { + const result: ObjectType = {}; + availability.forEach(({ startTime, endTime, days }) => { + days.forEach((day: string) => { + result[day] = { startTime, endTime }; + }); }); - }); - return result; + return result; + } }; async function uploadEmployeePhoto( @@ -114,53 +119,8 @@ const EmployeeModal = ({ } refresh(); } - const createOrUpdateDriver = async ( - driver: AdminData | DriverData, - uid: string | '', - isNewDriver = false - ) => { - if (isNewDriver) { - return await createEmployee( - uid, - driver, - '/api/drivers', - () => refreshDrivers(), - 'Drivers' - ); - } else { - return await updateEmployee( - uid, - driver, - '/api/drivers', - () => refreshDrivers(), - 'Drivers' - ); - } - }; + - const createOrUpdateAdmin = async ( - admin: AdminData, - uid: string | '', - isNewAdmin = false - ) => { - if (isNewAdmin) { - await createEmployee( - uid, - admin, - '/api/admins', - () => refreshAdmins(), - 'Admins' - ); - } else { - await updateEmployee( - uid, - admin, - '/api/admins', - () => refreshAdmins(), - 'Admins' - ); - } - }; async function createEmployee( id: string, employeeData: AdminData | DriverData, @@ -204,8 +164,6 @@ const EmployeeModal = ({ ): Promise { try { await axios.put(`${endpoint}/${id}`, employeeData); - - // Upload the photo if provided uploadEmployeePhoto(id || '', table, refresh, imageBase64); console.log('Photo uploaded successfully.'); @@ -228,10 +186,80 @@ const EmployeeModal = ({ await axios.delete(`/api/${emptype}/${id}`); } } + const createOrUpdateEmployee = async ( + employee: AdminData | DriverData, + uid: string | '', + isNewEmployee: boolean, + type: 'admins' | 'drivers' + ) => { + const apiEndpoint = type === 'admins' ? '/api/admins' : '/api/drivers'; + const refreshFunction = type === 'admins' ? refreshAdmins : refreshDrivers; + const entityType = type === 'admins' ? 'Admins' : 'Drivers'; + + if (isNewEmployee) { + return await createEmployee(uid, employee, apiEndpoint, refreshFunction, entityType); + } else { + return await updateEmployee(uid, employee, apiEndpoint, refreshFunction, entityType); + } + }; + + async function processRoles(selectedRole: any, existingEmployee: any , admin: any , driver: any) { + const containsDriver = selectedRole.includes('driver'); + const containsAdmin = (containsDriver && selectedRole.length > 1) || (!containsDriver && selectedRole.length > 1); + const acc = []; + + if (containsAdmin) acc.push('admins'); + if (containsDriver) acc.push('drivers'); + + // Process roles in acc + let iteration = 0; + + for (const role of acc) { + switch (role) { + case 'admins': + console.log('Processing admin role...'); + if (existingEmployee) { + if (existingEmployee.isDriver && !containsDriver) { + // Transition from driver to admin + await deleteEmployee(existingEmployee.id, 'drivers'); + } + // Update or create admin + await createOrUpdateEmployee(admin, existingEmployee.id || '', false, 'admins'); + } else { + // Create new admin + await createOrUpdateEmployee(admin, '', true && iteration === 0, 'admins'); + } + break; + + case 'drivers': + console.log('Processing driver role...'); + if (existingEmployee) { + if (existingEmployee.isDriver) { + // Update driver + await createOrUpdateEmployee(driver, existingEmployee.id || '', false, 'drivers'); + } else if (existingEmployee.isAdmin && !containsAdmin) { + // Transition from admin to driver + await deleteEmployee(existingEmployee.id, 'admins'); + await createOrUpdateEmployee(driver, existingEmployee.id || '', false , 'drivers'); + } + } else { + await createOrUpdateEmployee(driver, '', true && iteration === 0, 'drivers'); + } + break; + + default: + console.warn(`Unhandled role in acc: ${role}`); + break; + } + + // Increment iteration to ensure no duplicate creation + iteration += 1; + } + } + async function onSubmit(data: ObjectType) { - const { firstName, lastName, netid, phoneNumber, startDate, availability } = - data; - + const { firstName, lastName, netid, phoneNumber, startDate, availability } = data; + const driver = { firstName, lastName, @@ -240,7 +268,7 @@ const EmployeeModal = ({ startDate, availability: parseAvailability(availability), }; - + const admin = { firstName, lastName, @@ -250,89 +278,16 @@ const EmployeeModal = ({ availability: parseAvailability(availability), isDriver: selectedRole.includes('driver'), }; - - // Sort selectedRole array in reverse order: ['sds-admin', 'redrunner-admin', 'driver'] - selectedRole.sort().reverse(); - - - let iteration = 0; - - for (const employeeRole of selectedRole) { - switch (employeeRole) { - case 'sds-admin': - if (iteration > 0) continue; - - console.log('Handling sds-admin...'); - if (existingEmployee) { - // Handle existing employee scenario for sds-admin - if (existingEmployee.isDriver) { - console.log('Switching from driver to sds-admin...'); - await deleteEmployee(existingEmployee.id, 'drivers'); - await createOrUpdateAdmin(admin, existingEmployee.id || '', false); - } else { - console.log('Updating existing sds-admin...'); - await createOrUpdateAdmin(admin, existingEmployee.id || '', false); - } - } else { - // Handle new employee creation for sds-admin - console.log('Creating new sds-admin...'); - await createOrUpdateAdmin(admin, '', true); - } - break; - - case 'redrunner-admin': - if (iteration > 0) continue; // Don't create a duplicate - - console.log('Handling redrunner-admin...'); - if (existingEmployee) { - // Handle existing employee scenario for redrunner-admin - if (existingEmployee.isDriver) { - console.log('Switching from driver to redrunner-admin...'); - await deleteEmployee(existingEmployee.id, 'drivers'); - await createOrUpdateAdmin(admin, existingEmployee.id || '', false); - } else { - console.log('Updating existing redrunner-admin...'); - await createOrUpdateAdmin(admin, existingEmployee.id || '', true); - } - } else { - // Handle new employee creation for redrunner-admin - console.log('Creating new redrunner-admin...'); - await createOrUpdateAdmin(admin, '', true); - } - break; - - case 'driver': - if (existingEmployee) { - if (existingEmployee.isDriver) { - console.log('Updating existing driver...'); - await createOrUpdateDriver(driver, '', false); - } else { - console.log('Switching from admin to driver...'); - await deleteEmployee(existingEmployee.id, 'admins'); - await createOrUpdateDriver( - driver, - existingEmployee.id || '', - true - ); - } - } else { - console.log('Creating new driver...'); - await createOrUpdateDriver(driver, '', true); - } - break; - - default: - console.log(`Unrecognized role: ${employeeRole}`); - break; - } - iteration += 1; // Increment iteration to keep track of processed roles - console.log(iteration) + + try { + await processRoles(selectedRole, existingEmployee, admin, driver); + } catch (error) { + console.error('Error processing roles:', error); + } finally { + closeModal(); } - - console.log('Completed role processing'); - closeModal(); } - + async function updateBase64(e: React.ChangeEvent) { e.preventDefault(); diff --git a/server/src/router/upload.ts b/server/src/router/upload.ts index 9c6fa1cc3..0b7b81e1a 100644 --- a/server/src/router/upload.ts +++ b/server/src/router/upload.ts @@ -19,41 +19,53 @@ router.post('/', validateUser('User'), (req, res) => { const { body: { id, tableName, fileBuffer }, } = req; - if (fileBuffer === '') { - res.status(400).send({ err: 'Invalid file name: empty string' }); - } else if (id == '') { - res.status(400).send({ err: 'Empty ID: empty string' }); - } else if (['Riders', 'Drivers', 'Admins'].includes(tableName)) { - const objectKey = `${tableName}/${id}`; - const params = { - Bucket: BUCKET_NAME, - Key: objectKey, - Body: Buffer.from(fileBuffer, 'base64'), - ContentEncoding: 'base64', - }; - s3Bucket.putObject(params, (s3Err: any) => { - if (s3Err) { - res.status(s3Err.statusCode || 500).send({ err: s3Err.message }); - } else { - const photoLink = `https://${BUCKET_NAME}.s3.us-east-2.amazonaws.com/${objectKey}`; - const operation = { $SET: { photoLink } }; - switch (tableName) { - case 'Drivers': - db.update(res, Driver, { id }, operation, tableName); - break; - case 'Riders': - db.update(res, Rider, { id }, operation, tableName); - break; - case 'Admins': - db.update(res, Admin, { id }, operation, tableName); - break; - default: - res.status(400).send({ err: 'Invalid table.' }); - break; - } - } - }); + + if (!id || typeof id !== 'string' || id.trim() === '') { + return res.status(400).json({ err: 'Invalid ID: empty or missing' }); + } + + if (!fileBuffer) { + return res + .status(400) + .json({ err: 'Invalid file buffer: empty or missing' }); } + + if (!['Riders', 'Drivers', 'Admins'].includes(tableName)) { + return res.status(400).json({ err: 'Invalid table name' }); + } + + const objectKey = `${tableName}/${id}`; + const params = { + Bucket: BUCKET_NAME, + Key: objectKey, + Body: Buffer.from(fileBuffer, 'base64'), + ObjectCannedACL: 'public-read', + ContentEncoding: 'base64', + }; + s3Bucket.putObject(params, (s3Err: any) => { + if (s3Err) { + res.status(s3Err.statusCode || 400).send({ err: s3Err.message }); + } else { + const photoLink = `https://${BUCKET_NAME}.s3.us-east-2.amazonaws.com/${objectKey}`; + const operation = { $SET: { photoLink } }; + switch (tableName) { + case 'Drivers': + db.update(res, Driver, { id }, operation, tableName); + break; + case 'Riders': + db.update(res, Rider, { id }, operation, tableName); + break; + case 'Admins': + db.update(res, Admin, { id }, operation, tableName); + break; + default: + res.status(400).send({ err: 'Invalid table.' }); + console.error('Invalid table.') + break; + } + console.log('Success upload.') + } + }); }); export default router; From a6d75de1ca6f097f83acb5f7c1c04e1fa43391b9 Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Thu, 28 Nov 2024 15:08:01 -0500 Subject: [PATCH 05/19] Fix Prettier --- .../EmployeeModal/EmployeeModal.tsx | 92 ++++++++++++++----- server/src/router/upload.ts | 6 +- 2 files changed, 71 insertions(+), 27 deletions(-) diff --git a/frontend/src/components/EmployeeModal/EmployeeModal.tsx b/frontend/src/components/EmployeeModal/EmployeeModal.tsx index 601ee0a27..af2cf445f 100644 --- a/frontend/src/components/EmployeeModal/EmployeeModal.tsx +++ b/frontend/src/components/EmployeeModal/EmployeeModal.tsx @@ -119,7 +119,6 @@ const EmployeeModal = ({ } refresh(); } - async function createEmployee( id: string, @@ -195,25 +194,44 @@ const EmployeeModal = ({ const apiEndpoint = type === 'admins' ? '/api/admins' : '/api/drivers'; const refreshFunction = type === 'admins' ? refreshAdmins : refreshDrivers; const entityType = type === 'admins' ? 'Admins' : 'Drivers'; - + if (isNewEmployee) { - return await createEmployee(uid, employee, apiEndpoint, refreshFunction, entityType); + return await createEmployee( + uid, + employee, + apiEndpoint, + refreshFunction, + entityType + ); } else { - return await updateEmployee(uid, employee, apiEndpoint, refreshFunction, entityType); + return await updateEmployee( + uid, + employee, + apiEndpoint, + refreshFunction, + entityType + ); } }; - - async function processRoles(selectedRole: any, existingEmployee: any , admin: any , driver: any) { + + async function processRoles( + selectedRole: any, + existingEmployee: any, + admin: any, + driver: any + ) { const containsDriver = selectedRole.includes('driver'); - const containsAdmin = (containsDriver && selectedRole.length > 1) || (!containsDriver && selectedRole.length > 1); + const containsAdmin = + (containsDriver && selectedRole.length > 1) || + (!containsDriver && selectedRole.length > 1); const acc = []; - + if (containsAdmin) acc.push('admins'); if (containsDriver) acc.push('drivers'); - + // Process roles in acc let iteration = 0; - + for (const role of acc) { switch (role) { case 'admins': @@ -224,42 +242,68 @@ const EmployeeModal = ({ await deleteEmployee(existingEmployee.id, 'drivers'); } // Update or create admin - await createOrUpdateEmployee(admin, existingEmployee.id || '', false, 'admins'); + await createOrUpdateEmployee( + admin, + existingEmployee.id || '', + false, + 'admins' + ); } else { // Create new admin - await createOrUpdateEmployee(admin, '', true && iteration === 0, 'admins'); + await createOrUpdateEmployee( + admin, + '', + true && iteration === 0, + 'admins' + ); } break; - + case 'drivers': console.log('Processing driver role...'); if (existingEmployee) { if (existingEmployee.isDriver) { // Update driver - await createOrUpdateEmployee(driver, existingEmployee.id || '', false, 'drivers'); + await createOrUpdateEmployee( + driver, + existingEmployee.id || '', + false, + 'drivers' + ); } else if (existingEmployee.isAdmin && !containsAdmin) { // Transition from admin to driver await deleteEmployee(existingEmployee.id, 'admins'); - await createOrUpdateEmployee(driver, existingEmployee.id || '', false , 'drivers'); + await createOrUpdateEmployee( + driver, + existingEmployee.id || '', + false, + 'drivers' + ); } } else { - await createOrUpdateEmployee(driver, '', true && iteration === 0, 'drivers'); + await createOrUpdateEmployee( + driver, + '', + true && iteration === 0, + 'drivers' + ); } break; - + default: console.warn(`Unhandled role in acc: ${role}`); break; } - + // Increment iteration to ensure no duplicate creation iteration += 1; } } - + async function onSubmit(data: ObjectType) { - const { firstName, lastName, netid, phoneNumber, startDate, availability } = data; - + const { firstName, lastName, netid, phoneNumber, startDate, availability } = + data; + const driver = { firstName, lastName, @@ -268,7 +312,7 @@ const EmployeeModal = ({ startDate, availability: parseAvailability(availability), }; - + const admin = { firstName, lastName, @@ -278,7 +322,7 @@ const EmployeeModal = ({ availability: parseAvailability(availability), isDriver: selectedRole.includes('driver'), }; - + try { await processRoles(selectedRole, existingEmployee, admin, driver); } catch (error) { @@ -287,7 +331,7 @@ const EmployeeModal = ({ closeModal(); } } - + async function updateBase64(e: React.ChangeEvent) { e.preventDefault(); diff --git a/server/src/router/upload.ts b/server/src/router/upload.ts index 0b7b81e1a..9365ac052 100644 --- a/server/src/router/upload.ts +++ b/server/src/router/upload.ts @@ -10,7 +10,7 @@ import dotenv from 'dotenv'; // Load environment variables from .env file (if available) dotenv.config(); -//const BUCKET_NAME = 'dti-carriage-staging-public'; old bucket +// const BUCKET_NAME = 'dti-carriage-staging-public'; old bucket const BUCKET_NAME = 'carriage-images'; const router = express.Router(); const s3Bucket = new S3({ region: 'us-east-2' }); @@ -60,10 +60,10 @@ router.post('/', validateUser('User'), (req, res) => { break; default: res.status(400).send({ err: 'Invalid table.' }); - console.error('Invalid table.') + console.error('Invalid table.'); break; } - console.log('Success upload.') + console.log('Success upload.'); } }); }); From b4e235103ae1dc8c5fdad20b2bd405699a1c07ed Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Mon, 2 Dec 2024 13:30:22 -0500 Subject: [PATCH 06/19] Fixed duplication error and streamlined processing logic. --- .../EmployeeModal/EmployeeModal.tsx | 255 ++++++++---------- .../src/components/EmployeeModal/Upload.tsx | 7 +- server/src/router/admin.ts | 5 +- server/src/router/driver.ts | 3 +- server/src/router/upload.ts | 37 ++- 5 files changed, 139 insertions(+), 168 deletions(-) diff --git a/frontend/src/components/EmployeeModal/EmployeeModal.tsx b/frontend/src/components/EmployeeModal/EmployeeModal.tsx index af2cf445f..b373abdce 100644 --- a/frontend/src/components/EmployeeModal/EmployeeModal.tsx +++ b/frontend/src/components/EmployeeModal/EmployeeModal.tsx @@ -12,6 +12,7 @@ import styles from './employeemodal.module.css'; import { useEmployees } from '../../context/EmployeesContext'; import { useToast, ToastStatus } from '../../context/toastContext'; import axios from '../../util/axios'; +import Toast from 'components/ConfirmationToast/ConfirmationToast'; type EmployeeModalProps = { existingEmployee?: { @@ -125,33 +126,25 @@ const EmployeeModal = ({ employeeData: AdminData | DriverData, endpoint: string, refresh: () => Promise, - table: string + table: string, + iteration: number ): Promise { - try { - // Create the employee - // HERE - const { data: createdEmployee } = await axios.post( - endpoint, - employeeData + if (Boolean(id) && id !== '') { + (employeeData as any).id = id; + } + const { + data: { data: createdEmployee }, + } = await axios.post(endpoint, employeeData); + if (iteration === 0 && imageBase64 !== '') { + await uploadEmployeePhoto( + createdEmployee.id, + table, + refresh, + imageBase64 ); - - // Upload the photo if provided - if (imageBase64 !== '') { - await uploadEmployeePhoto(id || '', table, refresh, imageBase64); - - console.log('Photo uploaded successfully.'); - } - - // Refresh after successful creation and photo upload - await refresh(); - - showToast('The employee has been added.', ToastStatus.SUCCESS); - return createdEmployee; - } catch (error) { - console.error('Error creating employee:', error); - showToast('Failed to add the employee.', ToastStatus.ERROR); - throw error; } + await refresh(); + return createdEmployee; } async function updateEmployee( @@ -159,20 +152,15 @@ const EmployeeModal = ({ employeeData: AdminData | DriverData, endpoint: string, refresh: () => Promise, - table: string + table: string, + iteration: number ): Promise { - try { - await axios.put(`${endpoint}/${id}`, employeeData); - uploadEmployeePhoto(id || '', table, refresh, imageBase64); - console.log('Photo uploaded successfully.'); - - refresh(); - showToast('The employee has been edited.', ToastStatus.SUCCESS); - } catch (error) { - console.error('Error updating employee:', error); - showToast('Failed to edit the employee.', ToastStatus.ERROR); - throw error; + await axios.put(`${endpoint}/${id}`, employeeData); + // iteration count prevents a second write to S3 + if (iteration === 0 && imageBase64 !== '') { + uploadEmployeePhoto(id, table, refresh, imageBase64); } + refresh(); } async function deleteEmployee( @@ -185,34 +173,6 @@ const EmployeeModal = ({ await axios.delete(`/api/${emptype}/${id}`); } } - const createOrUpdateEmployee = async ( - employee: AdminData | DriverData, - uid: string | '', - isNewEmployee: boolean, - type: 'admins' | 'drivers' - ) => { - const apiEndpoint = type === 'admins' ? '/api/admins' : '/api/drivers'; - const refreshFunction = type === 'admins' ? refreshAdmins : refreshDrivers; - const entityType = type === 'admins' ? 'Admins' : 'Drivers'; - - if (isNewEmployee) { - return await createEmployee( - uid, - employee, - apiEndpoint, - refreshFunction, - entityType - ); - } else { - return await updateEmployee( - uid, - employee, - apiEndpoint, - refreshFunction, - entityType - ); - } - }; async function processRoles( selectedRole: any, @@ -222,80 +182,107 @@ const EmployeeModal = ({ ) { const containsDriver = selectedRole.includes('driver'); const containsAdmin = - (containsDriver && selectedRole.length > 1) || - (!containsDriver && selectedRole.length > 1); - const acc = []; + selectedRole.includes('sds-admin') || + selectedRole.includes('redrunner-admin'); - if (containsAdmin) acc.push('admins'); - if (containsDriver) acc.push('drivers'); + const rolesToProcess = []; + if (containsAdmin) rolesToProcess.push('admins'); + if (containsDriver) rolesToProcess.push('drivers'); - // Process roles in acc + let newEmployee = null; // To track new employee creation let iteration = 0; - for (const role of acc) { - switch (role) { - case 'admins': - console.log('Processing admin role...'); - if (existingEmployee) { + for (const role of rolesToProcess) { + const apiEndpoint = role === 'admins' ? '/api/admins' : '/api/drivers'; + const refreshFunction = + role === 'admins' ? refreshAdmins : refreshDrivers; + const entityType = role === 'admins' ? 'Admins' : 'Drivers'; + + if (Boolean(existingEmployee)) { + switch (role) { + case 'admins': if (existingEmployee.isDriver && !containsDriver) { // Transition from driver to admin - await deleteEmployee(existingEmployee.id, 'drivers'); + await deleteEmployee( + newEmployee?.id || existingEmployee.id, + 'drivers' + ); + } - // Update or create admin - await createOrUpdateEmployee( - admin, - existingEmployee.id || '', - false, - 'admins' - ); - } else { - // Create new admin - await createOrUpdateEmployee( - admin, - '', - true && iteration === 0, - 'admins' - ); - } - break; - case 'drivers': - console.log('Processing driver role...'); - if (existingEmployee) { - if (existingEmployee.isDriver) { - // Update driver - await createOrUpdateEmployee( - driver, - existingEmployee.id || '', - false, - 'drivers' + if (!existingEmployee.isAdmin) { + // Create admin + await createEmployee( + newEmployee?.id || existingEmployee.id, + admin, + apiEndpoint, + refreshFunction, + entityType, + iteration + ); + + } else { + // Update admin + await updateEmployee( + newEmployee?.id || existingEmployee.id, + admin, + apiEndpoint, + refreshFunction, + entityType, + iteration ); - } else if (existingEmployee.isAdmin && !containsAdmin) { + + } + break; + + case 'drivers': + if (existingEmployee.isAdmin && !containsAdmin) { // Transition from admin to driver - await deleteEmployee(existingEmployee.id, 'admins'); - await createOrUpdateEmployee( - driver, - existingEmployee.id || '', - false, - 'drivers' + await deleteEmployee( + newEmployee?.id || existingEmployee.id, + 'admins' ); } - } else { - await createOrUpdateEmployee( - driver, - '', - true && iteration === 0, - 'drivers' - ); - } - break; - default: - console.warn(`Unhandled role in acc: ${role}`); - break; + if (!existingEmployee.isDriver) { + // Create driver + await createEmployee( + newEmployee?.id || existingEmployee.id, + driver, + apiEndpoint, + refreshFunction, + entityType, + iteration + ); + + + } else { + // Update driver + await updateEmployee( + newEmployee?.id || existingEmployee.id, + driver, + apiEndpoint, + refreshFunction, + entityType, + iteration + ); + + } + break; + } + } else if (!newEmployee) { + // Create a new employee if no existing employee is present + newEmployee = await createEmployee( + '', + role === 'admins' ? admin : driver, + apiEndpoint, + refreshFunction, + entityType, + iteration + ); + existingEmployee = newEmployee; + showToast(`Created a new employee with the role of ${role} based on your provided data`, ToastStatus.SUCCESS) } - - // Increment iteration to ensure no duplicate creation iteration += 1; } } @@ -325,8 +312,9 @@ const EmployeeModal = ({ try { await processRoles(selectedRole, existingEmployee, admin, driver); + showToast(`Employee information proccessed`, ToastStatus.SUCCESS) } catch (error) { - console.error('Error processing roles:', error); + showToast("An error occured: ",ToastStatus.ERROR) } finally { closeModal(); } @@ -339,29 +327,13 @@ const EmployeeModal = ({ if (files && files[0]) { const file = files[0]; const reader = new FileReader(); - - console.log('Starting to read the file:', file.name); reader.readAsDataURL(file); - reader.onload = async () => { const base64 = reader.result?.toString().split(',')[1]; // Extract base64 - console.log( - 'File read successfully. Base64 extracted:', - base64 ? base64.substring(0, 20) + '...' : 'No base64 data' - ); - if (base64) { setImageBase64(base64); // Save the base64 string - console.log('Set base64 data.'); } }; - - reader.onerror = (error) => { - console.error('Error reading file:', error); - }; - } else { - console.error('No file selected.'); - alert('No file selected'); } } @@ -382,7 +354,6 @@ const EmployeeModal = ({
{ methods.handleSubmit(onSubmit)(e); - console.log(e); }} aria-labelledby="employee-modal" > diff --git a/frontend/src/components/EmployeeModal/Upload.tsx b/frontend/src/components/EmployeeModal/Upload.tsx index 7ac855876..aa874b50e 100644 --- a/frontend/src/components/EmployeeModal/Upload.tsx +++ b/frontend/src/components/EmployeeModal/Upload.tsx @@ -1,6 +1,8 @@ import React, { useState, createRef } from 'react'; import uploadBox from './upload.svg'; import styles from './employeemodal.module.css'; +//import { useToast, ToastStatus } from '../../context/toastContext'; + const IMAGE_SIZE_LIMIT = 50000; @@ -10,6 +12,7 @@ type UploadProps = { }; const Upload = ({ imageChange, existingPhoto }: UploadProps) => { + //const { showToast } = useToast(); const [imageURL, setImageURL] = useState( existingPhoto ? `${existingPhoto}` : '' ); @@ -23,13 +26,13 @@ const Upload = ({ imageChange, existingPhoto }: UploadProps) => { }; function previewImage(e: React.ChangeEvent) { - e.preventDefault(); const { files } = e.target; if (files && files[0] && files[0].size < IMAGE_SIZE_LIMIT) { setImageURL(URL.createObjectURL(files[0])); imageChange(e); } else { - alert('File is too large'); + alert(`Images must be under ${IMAGE_SIZE_LIMIT} MB`) // works but not preferred. + //showToast(`Images must be under ${IMAGE_SIZE_LIMIT} MB`, ToastStatus.ERROR) : The ideal version but does not work. } } diff --git a/server/src/router/admin.ts b/server/src/router/admin.ts index f564e67e0..dfa1fe082 100644 --- a/server/src/router/admin.ts +++ b/server/src/router/admin.ts @@ -3,6 +3,7 @@ import { v4 as uuid } from 'uuid'; import * as db from './common'; import { Admin } from '../models/admin'; import { validateUser } from '../util'; +import { UserType } from '../models/subscription'; const router = express.Router(); const tableName = 'Admins'; @@ -23,8 +24,9 @@ router.get('/', validateUser('Admin'), (req, res) => { // Put a driver in Admins table router.post('/', validateUser('Admin'), (req, res) => { const { body } = req; + const id = body.id || uuid(); const admin = new Admin({ - id: uuid(), + id: id, ...body, }); db.create(res, admin); @@ -36,6 +38,7 @@ router.put('/:id', validateUser('Admin'), (req, res) => { params: { id }, body, } = req; + db.update(res, Admin, { id }, body, tableName); }); diff --git a/server/src/router/driver.ts b/server/src/router/driver.ts index e2f78ccd2..86961b50f 100644 --- a/server/src/router/driver.ts +++ b/server/src/router/driver.ts @@ -122,8 +122,9 @@ router.get('/:id/stats', validateUser('Admin'), (req, res) => { // Put a driver in Drivers table router.post('/', validateUser('Admin'), (req, res) => { const { body } = req; + const id = body.id || uuid(); const driver = new Driver({ - id: uuid(), + id: id, ...body, }); db.create(res, driver); diff --git a/server/src/router/upload.ts b/server/src/router/upload.ts index 9365ac052..7f6bddd13 100644 --- a/server/src/router/upload.ts +++ b/server/src/router/upload.ts @@ -1,13 +1,10 @@ import express from 'express'; import { S3, ObjectCannedACL } from '@aws-sdk/client-s3'; // Import required types import * as db from './common'; -import { Rider } from '../models/rider'; import { Driver } from '../models/driver'; import { Admin } from '../models/admin'; import { validateUser } from '../util'; import dotenv from 'dotenv'; - -// Load environment variables from .env file (if available) dotenv.config(); // const BUCKET_NAME = 'dti-carriage-staging-public'; old bucket @@ -15,23 +12,26 @@ const BUCKET_NAME = 'carriage-images'; const router = express.Router(); const s3Bucket = new S3({ region: 'us-east-2' }); -router.post('/', validateUser('User'), (req, res) => { +router.post('/', validateUser('User'), (request, response) => { const { body: { id, tableName, fileBuffer }, - } = req; + } = request; if (!id || typeof id !== 'string' || id.trim() === '') { - return res.status(400).json({ err: 'Invalid ID: empty or missing' }); + return response.status(400).json({ err: 'Invalid ID: empty or missing' }); } - if (!fileBuffer) { - return res + if (!fileBuffer || typeof fileBuffer !== 'string') { + return response .status(400) .json({ err: 'Invalid file buffer: empty or missing' }); } - if (!['Riders', 'Drivers', 'Admins'].includes(tableName)) { - return res.status(400).json({ err: 'Invalid table name' }); + // Only Drivers and Admins can upload images. + if (tableName != 'Drivers' && tableName != 'Admins') { + return response + .status(400) + .json({ err: `Invalid table name: ${tableName}` }); } const objectKey = `${tableName}/${id}`; @@ -42,28 +42,21 @@ router.post('/', validateUser('User'), (req, res) => { ObjectCannedACL: 'public-read', ContentEncoding: 'base64', }; + s3Bucket.putObject(params, (s3Err: any) => { if (s3Err) { - res.status(s3Err.statusCode || 400).send({ err: s3Err.message }); + response.status(s3Err.statusCode || 400).send({ err: s3Err.message }); } else { const photoLink = `https://${BUCKET_NAME}.s3.us-east-2.amazonaws.com/${objectKey}`; - const operation = { $SET: { photoLink } }; + const databaseOperation = { $SET: { photoLink } }; switch (tableName) { case 'Drivers': - db.update(res, Driver, { id }, operation, tableName); - break; - case 'Riders': - db.update(res, Rider, { id }, operation, tableName); + db.update(response, Driver, { id }, databaseOperation, tableName); break; case 'Admins': - db.update(res, Admin, { id }, operation, tableName); - break; - default: - res.status(400).send({ err: 'Invalid table.' }); - console.error('Invalid table.'); + db.update(response, Admin, { id }, databaseOperation, tableName); break; } - console.log('Success upload.'); } }); }); From 9a255b0f7f1dcb9a9c9b29f08ff4335f53e2c4e9 Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Mon, 2 Dec 2024 13:31:37 -0500 Subject: [PATCH 07/19] Fix prettier --- .../components/EmployeeModal/EmployeeModal.tsx | 15 ++++++--------- frontend/src/components/EmployeeModal/Upload.tsx | 5 ++--- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/EmployeeModal/EmployeeModal.tsx b/frontend/src/components/EmployeeModal/EmployeeModal.tsx index b373abdce..c818bb1e1 100644 --- a/frontend/src/components/EmployeeModal/EmployeeModal.tsx +++ b/frontend/src/components/EmployeeModal/EmployeeModal.tsx @@ -207,7 +207,6 @@ const EmployeeModal = ({ newEmployee?.id || existingEmployee.id, 'drivers' ); - } if (!existingEmployee.isAdmin) { @@ -220,7 +219,6 @@ const EmployeeModal = ({ entityType, iteration ); - } else { // Update admin await updateEmployee( @@ -231,7 +229,6 @@ const EmployeeModal = ({ entityType, iteration ); - } break; @@ -254,8 +251,6 @@ const EmployeeModal = ({ entityType, iteration ); - - } else { // Update driver await updateEmployee( @@ -266,7 +261,6 @@ const EmployeeModal = ({ entityType, iteration ); - } break; } @@ -281,7 +275,10 @@ const EmployeeModal = ({ iteration ); existingEmployee = newEmployee; - showToast(`Created a new employee with the role of ${role} based on your provided data`, ToastStatus.SUCCESS) + showToast( + `Created a new employee with the role of ${role} based on your provided data`, + ToastStatus.SUCCESS + ); } iteration += 1; } @@ -312,9 +309,9 @@ const EmployeeModal = ({ try { await processRoles(selectedRole, existingEmployee, admin, driver); - showToast(`Employee information proccessed`, ToastStatus.SUCCESS) + showToast(`Employee information proccessed`, ToastStatus.SUCCESS); } catch (error) { - showToast("An error occured: ",ToastStatus.ERROR) + showToast('An error occured: ', ToastStatus.ERROR); } finally { closeModal(); } diff --git a/frontend/src/components/EmployeeModal/Upload.tsx b/frontend/src/components/EmployeeModal/Upload.tsx index aa874b50e..0d8d597d1 100644 --- a/frontend/src/components/EmployeeModal/Upload.tsx +++ b/frontend/src/components/EmployeeModal/Upload.tsx @@ -3,7 +3,6 @@ import uploadBox from './upload.svg'; import styles from './employeemodal.module.css'; //import { useToast, ToastStatus } from '../../context/toastContext'; - const IMAGE_SIZE_LIMIT = 50000; type UploadProps = { @@ -31,8 +30,8 @@ const Upload = ({ imageChange, existingPhoto }: UploadProps) => { setImageURL(URL.createObjectURL(files[0])); imageChange(e); } else { - alert(`Images must be under ${IMAGE_SIZE_LIMIT} MB`) // works but not preferred. - //showToast(`Images must be under ${IMAGE_SIZE_LIMIT} MB`, ToastStatus.ERROR) : The ideal version but does not work. + alert(`Images must be under ${IMAGE_SIZE_LIMIT} MB`); // works but not preferred. + //showToast(`Images must be under ${IMAGE_SIZE_LIMIT} MB`, ToastStatus.ERROR) : The ideal version but does not work. } } From 69a9c9ef020d8c1d24bb25c0099b4365e1b3212e Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Mon, 2 Dec 2024 14:14:47 -0500 Subject: [PATCH 08/19] Removed dubug methods --- server/src/app.ts | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/server/src/app.ts b/server/src/app.ts index d7c882603..b23d85d09 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -36,17 +36,14 @@ const app = express(); app.use(cors()); app.use(express.json({ limit: '500kb' })); app.use(express.urlencoded({ extended: false })); -app.use((req, res, next) => { - console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); - next(); -}); -app.use((req, res, next) => { - console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); - console.log('Headers:', req.headers); - console.log('Body:', req.body); // Requires body-parser or express.json() - next(); -}); +// Very useful API Debugger Method. +//app.use((req, res, next) => { + //console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); + //console.log('Headers:', req.headers); + //console.log('Body:', req.body); // Requires body-parser or express.json() + //next(); +//}); app.use('/api/riders', rider); app.use('/api/drivers', driver); @@ -58,9 +55,6 @@ app.use('/api/auth', auth); app.use('/api/upload', upload); app.use('/api/notification', notification); app.use('/api/stats', stats); -// Increase JSON payload limit -app.use(express.json({ limit: '5000mb' })); -app.use(express.urlencoded({ extended: true, limit: '5000mb' })); app.get('/api/health-check', (_, response) => response.status(200).send('OK')); // Serve static files from frontend From ff399070affb6c1f12b59582a677f2bdfbadae81 Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Mon, 2 Dec 2024 14:15:30 -0500 Subject: [PATCH 09/19] fix prettier --- server/src/app.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server/src/app.ts b/server/src/app.ts index b23d85d09..1773dfed7 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -37,12 +37,12 @@ app.use(cors()); app.use(express.json({ limit: '500kb' })); app.use(express.urlencoded({ extended: false })); -// Very useful API Debugger Method. +// Very useful API Debugger Method. //app.use((req, res, next) => { - //console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); - //console.log('Headers:', req.headers); - //console.log('Body:', req.body); // Requires body-parser or express.json() - //next(); +//console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); +//console.log('Headers:', req.headers); +//console.log('Body:', req.body); // Requires body-parser or express.json() +//next(); //}); app.use('/api/riders', rider); From cc871be7e89427805c9510b824af85e3a56cb952 Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Wed, 4 Dec 2024 18:44:33 -0500 Subject: [PATCH 10/19] Quick addition to error msg setup --- frontend/src/components/EmployeeModal/Upload.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/EmployeeModal/Upload.tsx b/frontend/src/components/EmployeeModal/Upload.tsx index 0d8d597d1..06f1a8bba 100644 --- a/frontend/src/components/EmployeeModal/Upload.tsx +++ b/frontend/src/components/EmployeeModal/Upload.tsx @@ -15,6 +15,7 @@ const Upload = ({ imageChange, existingPhoto }: UploadProps) => { const [imageURL, setImageURL] = useState( existingPhoto ? `${existingPhoto}` : '' ); + const [errorMessage, setErrorMessage] = useState(null); const inputRef = createRef(); /* This is for accessibility purposes only */ @@ -30,8 +31,8 @@ const Upload = ({ imageChange, existingPhoto }: UploadProps) => { setImageURL(URL.createObjectURL(files[0])); imageChange(e); } else { - alert(`Images must be under ${IMAGE_SIZE_LIMIT} MB`); // works but not preferred. - //showToast(`Images must be under ${IMAGE_SIZE_LIMIT} MB`, ToastStatus.ERROR) : The ideal version but does not work. + setErrorMessage(`Images must be under ${IMAGE_SIZE_LIMIT / 1000} KB`); + console.log(errorMessage); // placeholder for MUI chane. } } @@ -52,7 +53,7 @@ const Upload = ({ imageChange, existingPhoto }: UploadProps) => { accept="image/png, image/jpeg" ref={inputRef} style={{ display: 'none' }} - onChange={(e) => previewImage(e)} + onChange={previewImage} /> ); From 2b6dc58c5bf3c855f299778f70e65cc3619f063e Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Wed, 4 Dec 2024 18:51:14 -0500 Subject: [PATCH 11/19] fix prettier x3 --- frontend/src/components/EmployeeModal/Upload.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/components/EmployeeModal/Upload.tsx b/frontend/src/components/EmployeeModal/Upload.tsx index 06f1a8bba..b7f49cbb8 100644 --- a/frontend/src/components/EmployeeModal/Upload.tsx +++ b/frontend/src/components/EmployeeModal/Upload.tsx @@ -64,8 +64,6 @@ const Upload = ({ imageChange, existingPhoto }: UploadProps) => { > Upload a picture - - ); From f7d5c2188c34e8258b8d79eb94717a416742b617 Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Wed, 4 Dec 2024 18:58:02 -0500 Subject: [PATCH 12/19] change to delete-employee function --- .../src/components/EmployeeModal/EmployeeModal.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/EmployeeModal/EmployeeModal.tsx b/frontend/src/components/EmployeeModal/EmployeeModal.tsx index c818bb1e1..8b7a4277a 100644 --- a/frontend/src/components/EmployeeModal/EmployeeModal.tsx +++ b/frontend/src/components/EmployeeModal/EmployeeModal.tsx @@ -12,7 +12,6 @@ import styles from './employeemodal.module.css'; import { useEmployees } from '../../context/EmployeesContext'; import { useToast, ToastStatus } from '../../context/toastContext'; import axios from '../../util/axios'; -import Toast from 'components/ConfirmationToast/ConfirmationToast'; type EmployeeModalProps = { existingEmployee?: { @@ -163,15 +162,8 @@ const EmployeeModal = ({ refresh(); } - async function deleteEmployee( - id: string | undefined, - emptype: 'drivers' | 'admins' - ) { - if (id === undefined) { - console.log('Invalid/Null ID: deleteEmployee'); - } else { - await axios.delete(`/api/${emptype}/${id}`); - } + async function deleteEmployee(id: string, emptype: 'drivers' | 'admins') { + await axios.delete(`/api/${emptype}/${id}`); } async function processRoles( From 826b330ded2578e959b265cd4136d9822fe163a5 Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Wed, 4 Dec 2024 19:02:29 -0500 Subject: [PATCH 13/19] Added function documention --- .../EmployeeModal/EmployeeModal.tsx | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/frontend/src/components/EmployeeModal/EmployeeModal.tsx b/frontend/src/components/EmployeeModal/EmployeeModal.tsx index 8b7a4277a..2bbf473f8 100644 --- a/frontend/src/components/EmployeeModal/EmployeeModal.tsx +++ b/frontend/src/components/EmployeeModal/EmployeeModal.tsx @@ -120,6 +120,15 @@ const EmployeeModal = ({ refresh(); } + /** + * [createEmployee id employeeData endpoint refresh table iteration] creates a new employee + * using the provided data and endpoint. Optionally uploads a photo if [iteration] is 0 and + * [imageBase64] is non-empty. + * Requires: [id] to be a valid string or empty, [employeeData] to be a valid object, + * [endpoint] to be a valid API endpoint string, [refresh] to be a function, and [iteration] + * to be a non-negative integer. + * Returns: the created employee data. + */ async function createEmployee( id: string, employeeData: AdminData | DriverData, @@ -146,6 +155,17 @@ const EmployeeModal = ({ return createdEmployee; } + /** + * [updateEmployee id employeeData endpoint refresh table iteration] updates an existing + * employee's data using the specified endpoint. Optionally uploads a photo if [iteration] + * is 0 and [imageBase64] is non-empty. + * Requires: [id] to be a valid string, [employeeData] to be a valid object, [endpoint] to + * be a valid API endpoint string, [refresh] to be a function, and [iteration] to be a + * non-negative integer. + * Returns: a promise that resolves after successfully updating the employee and refreshing + * the data. + */ + async function updateEmployee( id: string, employeeData: AdminData | DriverData, @@ -162,10 +182,26 @@ const EmployeeModal = ({ refresh(); } + /** + * [deleteEmployee id emptype] removes an employee with the specified [id] from the backend, + * using the employee type [emptype] ('drivers' or 'admins') to determine the endpoint. + * Requires: [id] to be a valid string, [emptype] to be either 'drivers' or 'admins'. + * Returns: a promise that resolves after successfully deleting the employee. + */ + async function deleteEmployee(id: string, emptype: 'drivers' | 'admins') { await axios.delete(`/api/${emptype}/${id}`); } + /** + * [processRoles selectedRole existingEmployee admin driver] processes and assigns roles + * ('driver', 'admin') for the given employee, creating, updating, or deleting their + * information as necessary. + * Requires: [selectedRole] to be a valid array of roles, [existingEmployee] to be an object + * or null, and [admin] and [driver] to be valid employee data objects. + * Returns: a promise that resolves after processing all roles. + */ + async function processRoles( selectedRole: any, existingEmployee: any, @@ -276,6 +312,14 @@ const EmployeeModal = ({ } } + /** + * [onSubmit data] handles form submission, processes employee data, and invokes role + * processing. + * Requires: [data] to be an object containing valid employee form fields, including + * [firstName], [lastName], [netid], [phoneNumber], [startDate], and [availability]. + * Returns: a promise that resolves after successfully processing the form data. + */ + async function onSubmit(data: ObjectType) { const { firstName, lastName, netid, phoneNumber, startDate, availability } = data; @@ -309,6 +353,12 @@ const EmployeeModal = ({ } } + /** + * [updateBase64 e] updates the [imageBase64] state by converting the selected file from the + * input event [e] into a base64-encoded string. + * Requires: [e] to be a valid React change event containing file input data. + * Returns: a promise that resolves after successfully updating the [imageBase64] state. + */ async function updateBase64(e: React.ChangeEvent) { e.preventDefault(); From 60c24715f73958af67a1cdbbb4ac69089d4f64e7 Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Wed, 18 Dec 2024 17:23:24 -0500 Subject: [PATCH 14/19] Fixed format availablity --- frontend/src/components/EmployeeCards/EmployeeCards.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/frontend/src/components/EmployeeCards/EmployeeCards.tsx b/frontend/src/components/EmployeeCards/EmployeeCards.tsx index c2089a1ce..b836d7a08 100644 --- a/frontend/src/components/EmployeeCards/EmployeeCards.tsx +++ b/frontend/src/components/EmployeeCards/EmployeeCards.tsx @@ -33,13 +33,7 @@ const EmployeeCard = ({ }: EmployeeCardProps) => { const navigate = useNavigate(); const netId = email.split('@')[0]; - const fmtPhone = ''; - if (phoneNumber !== undefined) { - const fmtPhone = formatPhone(phoneNumber); - } else { - const fmtPhone = ''; - } - + const fmtPhone = formatPhone(phoneNumber); const formatAvail = (availability: { [key: string]: { startTime: string; endTime: string }; }) => { From c62dbfc496543023f4551b434890d83a4b56422e Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Sat, 15 Feb 2025 23:10:32 -0500 Subject: [PATCH 15/19] WeekContext and Image Size Fix --- .../src/components/EmployeeModal/Upload.tsx | 8 +-- .../components/EmployeeModal/WeekContext.tsx | 51 +++++++++++-------- server/src/app.ts | 16 +++--- 3 files changed, 43 insertions(+), 32 deletions(-) diff --git a/frontend/src/components/EmployeeModal/Upload.tsx b/frontend/src/components/EmployeeModal/Upload.tsx index b7f49cbb8..62724cdc1 100644 --- a/frontend/src/components/EmployeeModal/Upload.tsx +++ b/frontend/src/components/EmployeeModal/Upload.tsx @@ -3,7 +3,9 @@ import uploadBox from './upload.svg'; import styles from './employeemodal.module.css'; //import { useToast, ToastStatus } from '../../context/toastContext'; -const IMAGE_SIZE_LIMIT = 50000; + +const IMAGE_SIZE_LIMIT = 500000000; + type UploadProps = { imageChange: (e: React.ChangeEvent) => void; @@ -31,7 +33,7 @@ const Upload = ({ imageChange, existingPhoto }: UploadProps) => { setImageURL(URL.createObjectURL(files[0])); imageChange(e); } else { - setErrorMessage(`Images must be under ${IMAGE_SIZE_LIMIT / 1000} KB`); + setErrorMessage(`Images must be under ${ IMAGE_SIZE_LIMIT / 1000} KB`); console.log(errorMessage); // placeholder for MUI chane. } } @@ -50,7 +52,7 @@ const Upload = ({ imageChange, existingPhoto }: UploadProps) => { void; @@ -9,12 +9,8 @@ type WeekState = { }; const initialState: WeekState = { - selectDay: () => { - // do nothing - }, - deselectDay: () => { - // do nothing - }, + selectDay: () => {}, + deselectDay: () => {}, isDayOpen: () => false, isDaySelectedByInstance: () => false, getSelectedDays: () => [], @@ -33,9 +29,6 @@ type WeekType = { }; export const WeekProvider = ({ children }: WeekProviderProps) => { - // week keeps track of which AvailabilityInput has what days selected, based - // on its index number. if a day is not selected in any AvailabilityInput - // instance, the value is -1. const [week, setWeek] = useState({ Sun: -1, Mon: -1, @@ -46,21 +39,37 @@ export const WeekProvider = ({ children }: WeekProviderProps) => { Sat: -1, }); - const setDay = (day: string, value: number) => { - setWeek((prev) => ({ ...prev, [day]: value })); - }; + const setDay = useCallback((day: string, value: number) => { + setWeek(prev => { + if (prev[day] === value) return prev; + return { ...prev, [day]: value }; + }); + }, []); - const selectDay = (day: string, index: number) => setDay(day, index); + const selectDay = useCallback( + (day: string, index: number) => setDay(day, index), + [setDay] + ); - const deselectDay = (day: string) => setDay(day, -1); + const deselectDay = useCallback( + (day: string) => setDay(day, -1), + [setDay] + ); - const isDayOpen = (day: string) => week[day] === -1; + const isDayOpen = useCallback( + (day: string) => week[day] === -1, + [week] + ); - const isDaySelectedByInstance = (day: string, index: number) => - week[day] === index; + const isDaySelectedByInstance = useCallback( + (day: string, index: number) => week[day] === index, + [week] + ); - const getSelectedDays = (index: number) => - Object.keys(week).filter((day) => week[day] === index); + const getSelectedDays = useCallback( + (index: number) => Object.keys(week).filter(day => week[day] === index), + [week] + ); return ( { {children} ); -}; +}; \ No newline at end of file diff --git a/server/src/app.ts b/server/src/app.ts index 1773dfed7..07e908857 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -34,16 +34,16 @@ initDynamoose(); const app = express(); app.use(cors()); -app.use(express.json({ limit: '500kb' })); -app.use(express.urlencoded({ extended: false })); +app.use(express.json({ limit: '500mb' })); +app.use(express.urlencoded({ limit: '500mb', extended: true })); // Very useful API Debugger Method. -//app.use((req, res, next) => { -//console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); -//console.log('Headers:', req.headers); -//console.log('Body:', req.body); // Requires body-parser or express.json() -//next(); -//}); +app.use((req, res, next) => { +console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); +console.log('Headers:', req.headers); +console.log('Body:', req.body); // Requires body-parser or express.json() +next(); +}); app.use('/api/riders', rider); app.use('/api/drivers', driver); From 81190086bec1fede00a9d7031c40f554e808163e Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Sat, 15 Feb 2025 23:45:03 -0500 Subject: [PATCH 16/19] Formatting --- frontend/src/components/EmployeeModal/Upload.tsx | 4 +--- .../src/components/EmployeeModal/WeekContext.tsx | 16 +++++----------- server/src/app.ts | 8 ++++---- server/src/router/upload.ts | 7 ++++++- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/EmployeeModal/Upload.tsx b/frontend/src/components/EmployeeModal/Upload.tsx index 62724cdc1..882f17446 100644 --- a/frontend/src/components/EmployeeModal/Upload.tsx +++ b/frontend/src/components/EmployeeModal/Upload.tsx @@ -3,10 +3,8 @@ import uploadBox from './upload.svg'; import styles from './employeemodal.module.css'; //import { useToast, ToastStatus } from '../../context/toastContext'; - const IMAGE_SIZE_LIMIT = 500000000; - type UploadProps = { imageChange: (e: React.ChangeEvent) => void; existingPhoto?: string; @@ -33,7 +31,7 @@ const Upload = ({ imageChange, existingPhoto }: UploadProps) => { setImageURL(URL.createObjectURL(files[0])); imageChange(e); } else { - setErrorMessage(`Images must be under ${ IMAGE_SIZE_LIMIT / 1000} KB`); + setErrorMessage(`Images must be under ${IMAGE_SIZE_LIMIT / 1000} KB`); console.log(errorMessage); // placeholder for MUI chane. } } diff --git a/frontend/src/components/EmployeeModal/WeekContext.tsx b/frontend/src/components/EmployeeModal/WeekContext.tsx index 0aebc613d..e38755ee9 100644 --- a/frontend/src/components/EmployeeModal/WeekContext.tsx +++ b/frontend/src/components/EmployeeModal/WeekContext.tsx @@ -40,7 +40,7 @@ export const WeekProvider = ({ children }: WeekProviderProps) => { }); const setDay = useCallback((day: string, value: number) => { - setWeek(prev => { + setWeek((prev) => { if (prev[day] === value) return prev; return { ...prev, [day]: value }; }); @@ -51,15 +51,9 @@ export const WeekProvider = ({ children }: WeekProviderProps) => { [setDay] ); - const deselectDay = useCallback( - (day: string) => setDay(day, -1), - [setDay] - ); + const deselectDay = useCallback((day: string) => setDay(day, -1), [setDay]); - const isDayOpen = useCallback( - (day: string) => week[day] === -1, - [week] - ); + const isDayOpen = useCallback((day: string) => week[day] === -1, [week]); const isDaySelectedByInstance = useCallback( (day: string, index: number) => week[day] === index, @@ -67,7 +61,7 @@ export const WeekProvider = ({ children }: WeekProviderProps) => { ); const getSelectedDays = useCallback( - (index: number) => Object.keys(week).filter(day => week[day] === index), + (index: number) => Object.keys(week).filter((day) => week[day] === index), [week] ); @@ -84,4 +78,4 @@ export const WeekProvider = ({ children }: WeekProviderProps) => { {children} ); -}; \ No newline at end of file +}; diff --git a/server/src/app.ts b/server/src/app.ts index 07e908857..8b59a8600 100644 --- a/server/src/app.ts +++ b/server/src/app.ts @@ -39,10 +39,10 @@ app.use(express.urlencoded({ limit: '500mb', extended: true })); // Very useful API Debugger Method. app.use((req, res, next) => { -console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); -console.log('Headers:', req.headers); -console.log('Body:', req.body); // Requires body-parser or express.json() -next(); + console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); + console.log('Headers:', req.headers); + console.log('Body:', req.body); // Requires body-parser or express.json() + next(); }); app.use('/api/riders', rider); diff --git a/server/src/router/upload.ts b/server/src/router/upload.ts index 7f6bddd13..d1045896b 100644 --- a/server/src/router/upload.ts +++ b/server/src/router/upload.ts @@ -1,5 +1,10 @@ import express from 'express'; -import { S3, ObjectCannedACL } from '@aws-sdk/client-s3'; // Import required types +import { + S3, + PutObjectCommand, + DeleteObjectCommand, + HeadObjectCommand, +} from '@aws-sdk/client-s3'; import * as db from './common'; import { Driver } from '../models/driver'; import { Admin } from '../models/admin'; From b716fff3bb6b70eab34ea11106614ccce6d87d12 Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Sun, 16 Feb 2025 00:04:03 -0500 Subject: [PATCH 17/19] Documentation --- .../EmployeeModal/EmployeeModal.tsx | 103 ++++++++++-------- .../src/components/EmployeeModal/Upload.tsx | 6 +- 2 files changed, 57 insertions(+), 52 deletions(-) diff --git a/frontend/src/components/EmployeeModal/EmployeeModal.tsx b/frontend/src/components/EmployeeModal/EmployeeModal.tsx index 2bbf473f8..4a41cf7b6 100644 --- a/frontend/src/components/EmployeeModal/EmployeeModal.tsx +++ b/frontend/src/components/EmployeeModal/EmployeeModal.tsx @@ -120,15 +120,21 @@ const EmployeeModal = ({ refresh(); } - /** - * [createEmployee id employeeData endpoint refresh table iteration] creates a new employee - * using the provided data and endpoint. Optionally uploads a photo if [iteration] is 0 and - * [imageBase64] is non-empty. - * Requires: [id] to be a valid string or empty, [employeeData] to be a valid object, - * [endpoint] to be a valid API endpoint string, [refresh] to be a function, and [iteration] - * to be a non-negative integer. - * Returns: the created employee data. - */ +/** + * Creates a new employee using the provided data and endpoint. Optionally uploads a photo if + * [iteration] is 0 and [imageBase64] is non-empty. + * + * @param id - The unique identifier of the employee to create, or an empty string if not provided. + * @param employeeData - The data to create the new employee with. This should be a valid object. + * @param endpoint - The API endpoint to which the employee data will be sent. + * @param refresh - A function that refreshes the employee data or table after the creation. + * @param iteration - A non-negative integer used to conditionally upload a photo. + * @param imageBase64 - A base64 encoded string representing the employee's photo (optional). + * + * @returns A promise that resolves to the created employee data. + * + * @throws {Error} Throws an error if any of the parameters are invalid or if the creation fails. + */ async function createEmployee( id: string, employeeData: AdminData | DriverData, @@ -155,16 +161,23 @@ const EmployeeModal = ({ return createdEmployee; } - /** - * [updateEmployee id employeeData endpoint refresh table iteration] updates an existing - * employee's data using the specified endpoint. Optionally uploads a photo if [iteration] - * is 0 and [imageBase64] is non-empty. - * Requires: [id] to be a valid string, [employeeData] to be a valid object, [endpoint] to - * be a valid API endpoint string, [refresh] to be a function, and [iteration] to be a - * non-negative integer. - * Returns: a promise that resolves after successfully updating the employee and refreshing - * the data. - */ + +/** + * Updates an existing employee's data using the specified endpoint. Optionally uploads a photo + * if [iteration] is 0 and [imageBase64] is non-empty. + * + * @param id - The unique identifier of the employee to update. + * @param employeeData - The data to update the employee with. This should be a valid object. + * @param endpoint - The API endpoint to which the employee data will be sent. + * @param refresh - A function that refreshes the employee data or table after the update. + * @param iteration - A non-negative integer used to conditionally upload a photo. + * @param imageBase64 - A base64 encoded string representing the employee's photo (optional). + * + * @returns A promise that resolves after successfully updating the employee and refreshing + * the data. + * + * @throws {Error} Throws an error if any of the parameters are invalid or if the update fails. + */ async function updateEmployee( id: string, @@ -183,25 +196,32 @@ const EmployeeModal = ({ } /** - * [deleteEmployee id emptype] removes an employee with the specified [id] from the backend, - * using the employee type [emptype] ('drivers' or 'admins') to determine the endpoint. - * Requires: [id] to be a valid string, [emptype] to be either 'drivers' or 'admins'. - * Returns: a promise that resolves after successfully deleting the employee. - */ - + * [deleteEmployee id emptype] removes an employee with the specified [id] from the backend, + * using the employee type [emptype] ('drivers' or 'admins') to determine the endpoint. + * + * @param id - The unique identifier of the employee to delete. + * @param emptype - The type of employee, either 'drivers' or 'admins'. + * + * @returns A promise that resolves after successfully deleting the employee. + * + * @throws {Error} Throws an error if the id is not a valid string or the emptype is not 'drivers' or 'admins'. + */ async function deleteEmployee(id: string, emptype: 'drivers' | 'admins') { await axios.delete(`/api/${emptype}/${id}`); } - /** - * [processRoles selectedRole existingEmployee admin driver] processes and assigns roles - * ('driver', 'admin') for the given employee, creating, updating, or deleting their - * information as necessary. - * Requires: [selectedRole] to be a valid array of roles, [existingEmployee] to be an object - * or null, and [admin] and [driver] to be valid employee data objects. - * Returns: a promise that resolves after processing all roles. - */ - +/** + * Processes and assigns roles ('driver', 'admin') for an employee, handling creation, + * updating, or deletion of their information as needed. + * + * @param selectedRole - Valid array of roles to assign. + * @param existingEmployee - Existing employee data object or `null` (if new). + * @param admin - Valid employee data object for the 'admin' role. + * @param driver - Valid employee data object for the 'driver' role. + * @returns A promise that resolves when all role processing is complete. + * @throws {Error} If input parameters fail validation (invalid types or structure). + * + */ async function processRoles( selectedRole: any, existingEmployee: any, @@ -312,14 +332,6 @@ const EmployeeModal = ({ } } - /** - * [onSubmit data] handles form submission, processes employee data, and invokes role - * processing. - * Requires: [data] to be an object containing valid employee form fields, including - * [firstName], [lastName], [netid], [phoneNumber], [startDate], and [availability]. - * Returns: a promise that resolves after successfully processing the form data. - */ - async function onSubmit(data: ObjectType) { const { firstName, lastName, netid, phoneNumber, startDate, availability } = data; @@ -353,12 +365,7 @@ const EmployeeModal = ({ } } - /** - * [updateBase64 e] updates the [imageBase64] state by converting the selected file from the - * input event [e] into a base64-encoded string. - * Requires: [e] to be a valid React change event containing file input data. - * Returns: a promise that resolves after successfully updating the [imageBase64] state. - */ + async function updateBase64(e: React.ChangeEvent) { e.preventDefault(); diff --git a/frontend/src/components/EmployeeModal/Upload.tsx b/frontend/src/components/EmployeeModal/Upload.tsx index 882f17446..2ad479148 100644 --- a/frontend/src/components/EmployeeModal/Upload.tsx +++ b/frontend/src/components/EmployeeModal/Upload.tsx @@ -1,7 +1,6 @@ import React, { useState, createRef } from 'react'; import uploadBox from './upload.svg'; import styles from './employeemodal.module.css'; -//import { useToast, ToastStatus } from '../../context/toastContext'; const IMAGE_SIZE_LIMIT = 500000000; @@ -11,14 +10,13 @@ type UploadProps = { }; const Upload = ({ imageChange, existingPhoto }: UploadProps) => { - //const { showToast } = useToast(); + const [imageURL, setImageURL] = useState( existingPhoto ? `${existingPhoto}` : '' ); const [errorMessage, setErrorMessage] = useState(null); const inputRef = createRef(); - /* This is for accessibility purposes only */ const handleKeyboardPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { inputRef.current && inputRef.current.click(); @@ -32,7 +30,7 @@ const Upload = ({ imageChange, existingPhoto }: UploadProps) => { imageChange(e); } else { setErrorMessage(`Images must be under ${IMAGE_SIZE_LIMIT / 1000} KB`); - console.log(errorMessage); // placeholder for MUI chane. + console.log(errorMessage); } } From ae1ef0333e766a89ed1403d2e0b2ccc903204b0b Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Sun, 16 Feb 2025 00:08:52 -0500 Subject: [PATCH 18/19] Formatting fix --- .../EmployeeModal/EmployeeModal.tsx | 108 +++++++++--------- .../src/components/EmployeeModal/Upload.tsx | 3 +- 2 files changed, 54 insertions(+), 57 deletions(-) diff --git a/frontend/src/components/EmployeeModal/EmployeeModal.tsx b/frontend/src/components/EmployeeModal/EmployeeModal.tsx index 4a41cf7b6..7ffda6e24 100644 --- a/frontend/src/components/EmployeeModal/EmployeeModal.tsx +++ b/frontend/src/components/EmployeeModal/EmployeeModal.tsx @@ -120,21 +120,21 @@ const EmployeeModal = ({ refresh(); } -/** - * Creates a new employee using the provided data and endpoint. Optionally uploads a photo if - * [iteration] is 0 and [imageBase64] is non-empty. - * - * @param id - The unique identifier of the employee to create, or an empty string if not provided. - * @param employeeData - The data to create the new employee with. This should be a valid object. - * @param endpoint - The API endpoint to which the employee data will be sent. - * @param refresh - A function that refreshes the employee data or table after the creation. - * @param iteration - A non-negative integer used to conditionally upload a photo. - * @param imageBase64 - A base64 encoded string representing the employee's photo (optional). - * - * @returns A promise that resolves to the created employee data. - * - * @throws {Error} Throws an error if any of the parameters are invalid or if the creation fails. - */ + /** + * Creates a new employee using the provided data and endpoint. Optionally uploads a photo if + * [iteration] is 0 and [imageBase64] is non-empty. + * + * @param id - The unique identifier of the employee to create, or an empty string if not provided. + * @param employeeData - The data to create the new employee with. This should be a valid object. + * @param endpoint - The API endpoint to which the employee data will be sent. + * @param refresh - A function that refreshes the employee data or table after the creation. + * @param iteration - A non-negative integer used to conditionally upload a photo. + * @param imageBase64 - A base64 encoded string representing the employee's photo (optional). + * + * @returns A promise that resolves to the created employee data. + * + * @throws {Error} Throws an error if any of the parameters are invalid or if the creation fails. + */ async function createEmployee( id: string, employeeData: AdminData | DriverData, @@ -161,23 +161,22 @@ const EmployeeModal = ({ return createdEmployee; } - -/** - * Updates an existing employee's data using the specified endpoint. Optionally uploads a photo - * if [iteration] is 0 and [imageBase64] is non-empty. - * - * @param id - The unique identifier of the employee to update. - * @param employeeData - The data to update the employee with. This should be a valid object. - * @param endpoint - The API endpoint to which the employee data will be sent. - * @param refresh - A function that refreshes the employee data or table after the update. - * @param iteration - A non-negative integer used to conditionally upload a photo. - * @param imageBase64 - A base64 encoded string representing the employee's photo (optional). - * - * @returns A promise that resolves after successfully updating the employee and refreshing - * the data. - * - * @throws {Error} Throws an error if any of the parameters are invalid or if the update fails. - */ + /** + * Updates an existing employee's data using the specified endpoint. Optionally uploads a photo + * if [iteration] is 0 and [imageBase64] is non-empty. + * + * @param id - The unique identifier of the employee to update. + * @param employeeData - The data to update the employee with. This should be a valid object. + * @param endpoint - The API endpoint to which the employee data will be sent. + * @param refresh - A function that refreshes the employee data or table after the update. + * @param iteration - A non-negative integer used to conditionally upload a photo. + * @param imageBase64 - A base64 encoded string representing the employee's photo (optional). + * + * @returns A promise that resolves after successfully updating the employee and refreshing + * the data. + * + * @throws {Error} Throws an error if any of the parameters are invalid or if the update fails. + */ async function updateEmployee( id: string, @@ -196,32 +195,32 @@ const EmployeeModal = ({ } /** - * [deleteEmployee id emptype] removes an employee with the specified [id] from the backend, - * using the employee type [emptype] ('drivers' or 'admins') to determine the endpoint. - * - * @param id - The unique identifier of the employee to delete. - * @param emptype - The type of employee, either 'drivers' or 'admins'. - * - * @returns A promise that resolves after successfully deleting the employee. - * - * @throws {Error} Throws an error if the id is not a valid string or the emptype is not 'drivers' or 'admins'. - */ + * [deleteEmployee id emptype] removes an employee with the specified [id] from the backend, + * using the employee type [emptype] ('drivers' or 'admins') to determine the endpoint. + * + * @param id - The unique identifier of the employee to delete. + * @param emptype - The type of employee, either 'drivers' or 'admins'. + * + * @returns A promise that resolves after successfully deleting the employee. + * + * @throws {Error} Throws an error if the id is not a valid string or the emptype is not 'drivers' or 'admins'. + */ async function deleteEmployee(id: string, emptype: 'drivers' | 'admins') { await axios.delete(`/api/${emptype}/${id}`); } -/** - * Processes and assigns roles ('driver', 'admin') for an employee, handling creation, - * updating, or deletion of their information as needed. - * - * @param selectedRole - Valid array of roles to assign. - * @param existingEmployee - Existing employee data object or `null` (if new). - * @param admin - Valid employee data object for the 'admin' role. - * @param driver - Valid employee data object for the 'driver' role. - * @returns A promise that resolves when all role processing is complete. - * @throws {Error} If input parameters fail validation (invalid types or structure). - * - */ + /** + * Processes and assigns roles ('driver', 'admin') for an employee, handling creation, + * updating, or deletion of their information as needed. + * + * @param selectedRole - Valid array of roles to assign. + * @param existingEmployee - Existing employee data object or `null` (if new). + * @param admin - Valid employee data object for the 'admin' role. + * @param driver - Valid employee data object for the 'driver' role. + * @returns A promise that resolves when all role processing is complete. + * @throws {Error} If input parameters fail validation (invalid types or structure). + * + */ async function processRoles( selectedRole: any, existingEmployee: any, @@ -365,7 +364,6 @@ const EmployeeModal = ({ } } - async function updateBase64(e: React.ChangeEvent) { e.preventDefault(); diff --git a/frontend/src/components/EmployeeModal/Upload.tsx b/frontend/src/components/EmployeeModal/Upload.tsx index 2ad479148..7cfa47f49 100644 --- a/frontend/src/components/EmployeeModal/Upload.tsx +++ b/frontend/src/components/EmployeeModal/Upload.tsx @@ -10,7 +10,6 @@ type UploadProps = { }; const Upload = ({ imageChange, existingPhoto }: UploadProps) => { - const [imageURL, setImageURL] = useState( existingPhoto ? `${existingPhoto}` : '' ); @@ -30,7 +29,7 @@ const Upload = ({ imageChange, existingPhoto }: UploadProps) => { imageChange(e); } else { setErrorMessage(`Images must be under ${IMAGE_SIZE_LIMIT / 1000} KB`); - console.log(errorMessage); + console.log(errorMessage); } } From d086a1b539bb9dfed2ff12e8f6c6c3125cc5938c Mon Sep 17 00:00:00 2001 From: Dwain-Anderson Date: Sun, 16 Feb 2025 00:25:10 -0500 Subject: [PATCH 19/19] Additional documentation --- .../src/components/EmployeeModal/Upload.tsx | 15 ++++++++++++ server/src/router/upload.ts | 23 ++++++++++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/EmployeeModal/Upload.tsx b/frontend/src/components/EmployeeModal/Upload.tsx index 7cfa47f49..9c5ec7869 100644 --- a/frontend/src/components/EmployeeModal/Upload.tsx +++ b/frontend/src/components/EmployeeModal/Upload.tsx @@ -67,3 +67,18 @@ const Upload = ({ imageChange, existingPhoto }: UploadProps) => { }; export default Upload; + +/** + * This component, `Upload`, provides an interface for users to upload a profile picture. It accepts + * an image file, validates its size, and previews the selected image. If the file size exceeds the + * pre- specified limit, an error message is displayed. The component supports both drag-and-drop and keyboard + * interactions to trigger the image upload. + * + * Props: + * - `imageChange`: A callback function that is invoked when the image is successfully selected or changed. + * - `existingPhoto` (optional): A URL for an existing profile photo to be displayed as the default. + * + * Features: + * - Displays an image preview after selection. + * - Validates that the selected image is under a defined size limit (500MB). + */ diff --git a/server/src/router/upload.ts b/server/src/router/upload.ts index d1045896b..09671bffc 100644 --- a/server/src/router/upload.ts +++ b/server/src/router/upload.ts @@ -1,10 +1,5 @@ import express from 'express'; -import { - S3, - PutObjectCommand, - DeleteObjectCommand, - HeadObjectCommand, -} from '@aws-sdk/client-s3'; +import { S3 } from '@aws-sdk/client-s3'; import * as db from './common'; import { Driver } from '../models/driver'; import { Admin } from '../models/admin'; @@ -67,3 +62,19 @@ router.post('/', validateUser('User'), (request, response) => { }); export default router; + +/** + * This file handles image uploads to an S3 bucket. + * The image upload allows for the replacement of an existing image for drivers and admins, + * and updates the corresponding user's photo URL in the database. The uploaded image is + * stored in the 'carriage-images' S3 bucket and is made publicly accessible. The route + * validates the user, checks required fields, uploads the image to S3, and then updates + * the relevant user record with the new image URL in the database. + * This route performs the following actions: + * 1. Validates the user via the `validateUser` middleware. + * 2. Ensures that the `id`, `tableName`, and `fileBuffer` parameters are valid. + * 3. Uploads the image to the S3 bucket using AWS SDK. + * 4. Updates the corresponding driver's or admin's record in the database with the new + * photo URL. + * 5. Handles success and error responses, including S3 upload errors. + */