diff --git a/backend/config/database/schema.sql b/backend/config/database/schema.sql index 3c1feccdc55660dc6e25738728cc0e6a2e9be70c..a01f8db1d4f54429f804b8976d4abae457de8217 100644 --- a/backend/config/database/schema.sql +++ b/backend/config/database/schema.sql @@ -89,9 +89,10 @@ CREATE TABLE IF NOT EXISTS chapterLookup ( ); CREATE TABLE IF NOT EXISTS peopleXAddress ( - peopleId INT PRIMARY KEY, - addressId INT UNIQUE, + peopleId INT, + addressId INT, preferredAddress VARCHAR(255) NOT NULL, + PRIMARY KEY (peopleId, addressId), FOREIGN KEY (peopleId) REFERENCES people(peopleId) ON DELETE CASCADE, FOREIGN KEY (addressId) REFERENCES address(addressId) ); diff --git a/backend/repository/addressRepository.js b/backend/repository/addressRepository.js index 617379830005009892af89870ea302f74d85eae8..b0e8ee12836bd280f6ecf3de6a8897d86a69d67a 100644 --- a/backend/repository/addressRepository.js +++ b/backend/repository/addressRepository.js @@ -1,5 +1,5 @@ const { pool } = require("../config/database/database.config"); - +const { getStateId } = require("./stateRepository"); /* Add address info into address table and peopleXaddress table */ @@ -12,7 +12,7 @@ async function addAddress(address, peopleId) { try { // Use helper functions to look up stateId and countryId - const stateId = await _lookupStateId(state); + const stateId = await getStateId(state); const countryId = await _lookupCountryId(country); // query for adding the address @@ -62,8 +62,125 @@ async function addAddress(address, peopleId) { } } -async function updateAddress() { - console.log("Update address not implemented!"); +/* +Function to update an address. If the address is successfully updated, +the function returns the updated address's ID. If the address is not successfully +updated, the function returns null +*/ +async function updateAddress(addressId, peopleId, updatedAddress) { + // get the parts of the address + let { address1, address2, city, state, country, zipCode, preferredAddress } = + updatedAddress; + + console.log("Updating address..."); + + try { + // look up the state and country + const stateId = await getStateId(state); + const countryId = await _lookupCountryId(country); + + // Update the main address table + const updateAddressQuery = ` + UPDATE address + SET address1 = ?, address2 = ?, city = ?, stateId = ?, countryId = ?, zipCode = ? + WHERE addressId = ? + `; + const [addressResult] = await pool.query(updateAddressQuery, [ + address1, + address2, + city, + stateId, + countryId, + zipCode, + addressId, + ]); + + // Check if any rows were updated in the address table + if (addressResult.affectedRows === 0) { + console.error("Address not found. No rows updated in the address table."); + return null; + } + console.log("Address table updated successfully."); + + // Update the preferredAddress in peopleXAddress if provided + if (preferredAddress !== undefined) { + const updatePeopleXAddressQuery = ` + UPDATE peopleXAddress + SET preferredAddress = ? + WHERE peopleId = ? AND addressId = ? + `; + + const [peopleXAddressResult] = await pool.query( + updatePeopleXAddressQuery, + [preferredAddress, peopleId, addressId] + ); + + // Check if any rows were updated in the peopleXAddress table + if (peopleXAddressResult.affectedRows === 0) { + console.error( + "Associated record in peopleXAddress not found. No rows updated in the peopleXAddress table." + ); + return null; + } + + console.log("peopleXAddress table updated successfully."); + } + + return addressId; + } catch (error) { + console.log("Error updating address: ", error); + return null; + } +} + +/* +Function to delete an address by its ID. +Deletes from both peopleXAddress and address tables. +Returns true if the deletion was successful and false if no rows were affected. +*/ +async function deleteAddressById(addressId) { + console.log("Deleting address with ID: ", addressId); + + try { + // delete from the peopleXAddress table to satisfy any foreign key constraints + const deletePeopleXAddressQuery = ` + DELETE FROM peopleXAddress + WHERE addressId = ? + `; + const [peopleXAddressResult] = await pool.query(deletePeopleXAddressQuery, [ + addressId, + ]); + + // Check if any rows were affected in peopleXAddress + if (peopleXAddressResult.affectedRows === 0) { + console.error( + "No associated record found in peopleXAddress. No rows deleted." + ); + return false; + } + + console.log("Deleted from peopleXAddress table"); + + // delete the address from the address table + const deleteAddressQuery = ` + DELETE FROM address + WHERE addressId = ? + `; + const [addressResult] = await pool.query(deleteAddressQuery, [addressId]); + + // Check if any rows were affected in the address table + if (addressResult.affectedRows === 0) { + console.error("Address not found in address table. No rows deleted."); + return false; + } + + console.log("Address deleted successfully from address table."); + + return true; + } catch (error) { + console.error("Error deleting address: ", error); + return false; + } } /* @@ -125,48 +242,15 @@ async function getAddressesForPerson(peopleId) { } } -// Private helper function to look up the stateId -async function _lookupStateId(state) { - // for now return 1 while tables are not populated - return 1; - - // try { - // const lookupQuery = ` - // SELECT stateId FROM stateLookup WHERE name = ? - // `; - // const [lookupResults] = await pool.query(lookupQuery, [state]); - - // if (lookupResults.length === 0) { - // console.log("State not found"); - // return null; - // } - - // return lookupResults[0].stateId; - // } catch (error) { - // console.error(`Error looking up state: ${state}`, error); - // throw error; - // } -} - // Private helper function to look up the countryId async function _lookupCountryId(country) { // for now return 1 while tables are not populated return 1; - - // try { - // const lookupQuery = ` - // SELECT countryId FROM countryLookup WHERE name = ? - // `; - // const [lookupResults] = await pool.query(lookupQuery, [country]); - // if (lookupResults.length === 0) { - // console.log("Country not found"); - // return null; - // } - // return lookupResults[0].countryId; - // } catch (error) { - // console.error(`Error looking up country: ${country}`, error); - // throw error; - // } } -module.exports = { addAddress, getAddressesForPerson }; +module.exports = { + addAddress, + getAddressesForPerson, + deleteAddressById, + updateAddress, +}; diff --git a/backend/repository/contactRepository.js b/backend/repository/contactRepository.js index 404d9c1087a2e38a2fbeded1d32b46464a197611..72247593391cc356fe1dccd80bc37e2514a772f1 100644 --- a/backend/repository/contactRepository.js +++ b/backend/repository/contactRepository.js @@ -123,4 +123,38 @@ async function getContactsForPerson(peopleId) { } } -module.exports = { addContact, updateContact, getContactsForPerson }; +/* +Function to delete a contact by its ID. +Returns true if the deletion was successful and false if no rows were affected. +*/ +async function deleteContactById(peopleContactId) { + console.log("Deleting contact with ID: ", peopleContactId); + + try { + const query = ` + DELETE FROM peopleContact + WHERE peopleContactId = ? + `; + + const [results] = await pool.query(query, [peopleContactId]); + + // Check if any rows were affected + if (results.affectedRows === 0) { + console.error("Contact not found. No rows deleted."); + return false; + } + + console.log("Contact deleted successfully."); + return true; + } catch (error) { + console.error("Error deleting contact: ", error); + return false; + } +} + +module.exports = { + addContact, + updateContact, + getContactsForPerson, + deleteContactById, +}; diff --git a/backend/repository/degreeRepository.js b/backend/repository/degreeRepository.js index 09aacfca9461a6fbc28c1dc675f7735ce04d6724..1c3dda1e84c39358dd7e3b8ecea1dbf7d3f31509 100644 --- a/backend/repository/degreeRepository.js +++ b/backend/repository/degreeRepository.js @@ -26,14 +26,6 @@ async function addDegree(degree, peopleId) { VALUES (?,?,?,?,?,?) `; - console.log("Adding degree with the following details:"); - console.log("People ID:", peopleId); - console.log("Degree Type:", degreeType); - console.log("Degree Department:", degreeDepartment); - console.log("Degree College:", degreeCollege); - console.log("Degree Year:", degreeYear); - console.log("Degree Description:", degreeDescription); - // insert data into db const [results] = await pool.query(query, [ peopleId, @@ -84,7 +76,7 @@ async function updateDegree(peopleDegreeId, peopleId, degreeInfo) { const query = ` UPDATE peopleDegree SET - peopleId = ? + peopleId = ?, degreeTypeId = ?, degreeDepartment = ?, degreeCollege = ?, @@ -149,30 +141,6 @@ async function deleteDegreeById(peopleDegreeId) { } } -// Private helper function to look up the degreeTypeId -async function _lookupDegreeTypeId(degreeType) { - try { - // return null for now - return null; - - // const lookupQuery = ` - // SELECT degreeTypeId FROM degreeTypeLookup WHERE degreeType = ? - // `; - // const [lookupResults] = await pool.query(lookupQuery, [degreeType]); - - // if (lookupResults.length === 0) { - // console.log("Degree type not found"); - // return null; - // // throw new Error(`Invalid degree type: ${degreeType}`); - // } - - // return lookupResults[0].degreeTypeId; - } catch (error) { - console.error(`Error looking up degree type: ${degreeType}`, error); - throw error; - } -} - /* Function to find all degree IDs associated with a person. Returns an array of peopleDegreeIds or an empty array if no degrees exist. @@ -255,6 +223,17 @@ async function getDegreesForPerson(peopleId) { } } +// Private helper function to look up the degreeTypeId +async function _lookupDegreeTypeId(degreeType) { + try { + // return null for now + return null; + } catch (error) { + console.error(`Error looking up degree type: ${degreeType}`, error); + throw error; + } +} + module.exports = { addDegree, updateDegree, diff --git a/backend/repository/stateRepository.js b/backend/repository/stateRepository.js index f30c679ceb1c905aade0deedeeee0a6a9ab71f18..a6852e3664ae3887ac6a39b64b20cc4f5452411d 100644 --- a/backend/repository/stateRepository.js +++ b/backend/repository/stateRepository.js @@ -53,6 +53,26 @@ async function getState(name) { } } +async function getStateId(name) { + // SQL query to get a certain state id + const query = "SELECT stateId FROM stateLookup WHERE name = ?"; + + try { + console.log("Getting state: ", name); + const [rows] = await pool.query(query, [name]); + if (rows.length > 0) { + console.log("Found state: ", name); + return rows[0]; + } else { + console.log("State not found: ", name); + return null; + } + } catch (error) { + console.error(`Error fetching state with ID ${name}:`, error); + throw error; + } +} + async function checkTableExistence() { try { const [rows] = await pool.query("SHOW TABLES LIKE 'stateLookup'"); @@ -66,4 +86,9 @@ async function checkTableExistence() { } } -module.exports = { checkTableExistence, addStateIfNotExists, getState }; +module.exports = { + checkTableExistence, + addStateIfNotExists, + getState, + getStateId, +}; diff --git a/backend/routes/peopleRoutes.js b/backend/routes/peopleRoutes.js index 9cf642ca842032eac1ce043d0b69d64b2ec5fb08..c9c22276affa904d5b1118df64e16c65a1e2d312 100644 --- a/backend/routes/peopleRoutes.js +++ b/backend/routes/peopleRoutes.js @@ -5,6 +5,8 @@ const { createPerson, findByName, deleteById, + updatePersonById, + getPersonFullDetails, } = require("../service/peopleService"); /* Function to check that the POST request body in @@ -200,4 +202,58 @@ peopleRouter.delete("/:id", async (req, res) => { } }); +peopleRouter.put("/update/:id", async (req, res) => { + // get the personId from the URL params + const personId = parseInt(req.params.id, 10); + + // Check if personId is a valid integer + if (!Number.isInteger(personId)) { + return res + .status(400) + .json({ message: "Invalid ID. ID must be an integer." }); + } + + try { + // Call the update service + console.log("req.body: ", req.body); + const success = await updatePersonById(personId, req.body); + + // no person was updated + if (!success) { + return res.status(404).json({ message: "Person not found" }); + } + + return res + .status(200) + .json({ message: "Person updated successfully", personId }); + } catch (error) { + console.error("Error in PUT /update/:id", error); + return res.status(500).json({ message: "Internal server error" }); + } +}); + +peopleRouter.get("/fullInfo/:id", async (req, res) => { + // get the personId from the URL params + const personId = parseInt(req.params.id, 10); + + // Check if personId is a valid integer + if (!Number.isInteger(personId)) { + return res + .status(400) + .json({ message: "Invalid ID. ID must be an integer." }); + } + + try { + const person = await getPersonFullDetails(personId); + + if (person == null) { + return res.status(404).json({ message: "Person not found" }); + } + + return res.status(200).json({ message: "Person found", person }); + } catch (error) { + console.error("Error in GET /fullInfo/:id", error); + return res.status(500).json({ message: "Internal server error" }); + } +}); module.exports = peopleRouter; diff --git a/backend/service/peopleService.js b/backend/service/peopleService.js index b0f9aabb9f1c3343bba207fe3ac812e2527606a3..413079f96bcd41e92c1d11d3ff8465a1a4bd7525 100644 --- a/backend/service/peopleService.js +++ b/backend/service/peopleService.js @@ -1,10 +1,28 @@ -const { addAddress } = require("../repository/addressRepository"); -const { addContact } = require("../repository/contactRepository"); -const { addDegree } = require("../repository/degreeRepository"); +const { + addAddress, + getAddressesForPerson, + deleteAddressById, + updateAddress, +} = require("../repository/addressRepository"); +const { + addContact, + getContactsForPerson, + updateContact, + deleteContactById, +} = require("../repository/contactRepository"); +const { + addDegree, + getDegreeIdsForPerson, + deleteDegreeById, + updateDegree, + getDegreesForPerson, +} = require("../repository/degreeRepository"); const { addPerson, searchPeopleByName, deletePersonById, + updatePerson, + getPersonById, } = require("../repository/peopleRepository"); /* @@ -22,19 +40,19 @@ async function createPerson(person) { // insert the degree information into the database for (const degree of degrees) { const addedDegree = await addDegree(degree, newPersonID); - console.log("added degree: ", degree); + console.log("added degree: ", addedDegree); } // insert the address information into the database for (const address of addresses) { const addedAddress = await addAddress(address, newPersonID); - console.log("added address: ", address); + console.log("added address: ", addedAddress); } // insert the contact information into the database for (const contact of contacts) { const addedContact = await addContact(contact, newPersonID); - console.log("added contact: ", contact); + console.log("added contact: ", addedContact); } // insert the involvment information into the database @@ -70,4 +88,191 @@ async function deleteById(peopleId) { return false; } -module.exports = { createPerson, findByName, deleteById }; +/* +Function to update all of a person with the given id's information. +*/ +async function updatePersonById(personId, person) { + console.log("Person: ", person); + + const { personInfo, degrees, addresses, contacts, involvements } = person; + + console.log("Updating person..."); + + // update personInfo + const updatedPersonInfo = await updatePerson(personId, personInfo); + if (updatedPersonInfo == false) { + return null; + } + + console.log("updated person info"); + + // update degrees + if (degrees) { + await updateDegreesForPerson(personId, degrees); + } + + // update address info + if (addresses) { + await updateAddressesForPerson(personId, addresses); + } + + // update contact info + if (contacts) { + await updateContactsForPerson(personId, contacts); + } + + if (involvements) { + await updateInvolvementsForPerson(personId, involvements); + } + return true; +} + +/* +Function to get all the data that is associated with a single +person. +*/ +async function getPersonFullDetails(personId) { + // get the personal details + const personalInformation = await getPersonById(personId); + + // get the degree information + const degreeInformation = await getDegreesForPerson(personId); + + // get the address information + const addressInformation = await getAddressesForPerson(personId); + + // get the contact information + const contactInformation = await getContactsForPerson(personId); + + // get the involvement information (none for now) + const involvmentInformation = []; + + // return all of the data + return { + personalInformation, + degreeInformation, + addressInformation, + contactInformation, + involvmentInformation, + }; +} + +/* +Helper function to update the degree information for a person. +*/ +async function updateDegreesForPerson(personId, degrees) { + // Get the person's current degrees + const currentDegreeIds = await getDegreeIdsForPerson(personId); + let updatedDegreeIds = []; + + // Add/update the given degrees + for (const degree of degrees) { + const peopleDegreeId = degree.peopleDegreeId || null; + + // degree does not previously have a peopleDegreeId -> this means its new (add degree) + if (!peopleDegreeId) { + const addedDegree = await addDegree(degree, personId); + console.log("Added new degree: ", addedDegree); + } else { + // previous peopleDegreeId exists, update the degree + const updatedDegree = await updateDegree( + peopleDegreeId, + personId, + degree + ); + console.log("Updated degree: ", updatedDegree); + if (updatedDegree == true) { + updatedDegreeIds.push(peopleDegreeId); + } + } + } + + // Delete degrees not included in the update + for (const degreeId of currentDegreeIds) { + if (!updatedDegreeIds.includes(degreeId)) { + await deleteDegreeById(degreeId); + console.log("Deleted degree: ", degreeId); + } + } +} + +/* +Helper function to update addresses for a person. +*/ +async function updateAddressesForPerson(personId, addresses) { + // Get the current addresses + const currentAddresses = await getAddressesForPerson(personId); + let updatedAddressIds = []; + + for (const address of addresses) { + const addressId = address.addressId || null; + + if (!addressId) { + const addedAddress = await addAddress(address, personId); + console.log("Added new address: ", addedAddress); + } else { + const updatedAddress = await updateAddress(addressId, personId, address); + if (updatedAddress != null) { + console.log("Updated address: ", updatedAddress); + updatedAddressIds.push(addressId); + } else { + console.log("Error updating adddress"); + } + } + } + + // Delete addresses not included in the update + for (const currentAddress of currentAddresses) { + if (!updatedAddressIds.includes(currentAddress.addressId)) { + await deleteAddressById(currentAddress.addressId); + console.log("Deleted address: ", currentAddress.addressId); + } + } +} + +/* +Helper function to update contacts for a person. +*/ +async function updateContactsForPerson(personId, contacts) { + // Get the current contacts + const currentContacts = await getContactsForPerson(personId); + let updatedContactIds = []; + + for (const contact of contacts) { + const contactId = contact.peopleContactId || null; + + if (!contactId) { + const addedContact = await addContact(contact, personId); + console.log("Added new contact: ", addedContact); + } else { + const updatedContact = await updateContact(contactId, personId, contact); + console.log("Updated contact: ", updatedContact); + if (updatedContact == true) { + updatedContactIds.push(contactId); + } + } + } + + // Delete contacts not included in the update + for (const currentContact of currentContacts) { + if (!updatedContactIds.includes(currentContact.peopleContactId)) { + // TODO: delete contact by id + let success = await deleteContactById(currentContact.peopleContactId); + if (success == true) { + console.log("Deleted contact: ", currentContact.peopleContactId); + } else { + console.log("Error deleting contact: ", currentContact.peopleContactId); + } + } + } +} + +async function updateInvolvementsForPerson(personId, involevements) {} + +module.exports = { + createPerson, + findByName, + deleteById, + updatePersonById, + getPersonFullDetails, +};