diff --git a/backend/config/auth/auth.login.js b/backend/config/auth/auth.login.js index ebf5d6eaba54608c32e4e9c7706f429d03c832e0..be9e9710fc7632d5464a6ddd26d3638bdb6a05dc 100644 --- a/backend/config/auth/auth.login.js +++ b/backend/config/auth/auth.login.js @@ -1,9 +1,12 @@ // library useful for development const passport = require("passport"); const jwt = require("jsonwebtoken"); +const { + documentSuccessfulLogin, +} = require("../../service/activityLoggerService"); // function to handle the login process -const casLogin = function (req, res, next) { +const casLogin = async function (req, res, next) { console.log("Beginning log in...."); // hard-coded for now. @@ -13,6 +16,14 @@ const casLogin = function (req, res, next) { console.log("user: ", process.env.DB_USER); if (process.env.DB_USER === "corps_directory_dev") { + // document a successful login + try { + const timestamp = new Date(); // Current timestamp + await documentSuccessfulLogin(1, timestamp); // userID = 1 for now? + } catch (error) { + console.error("Error logging the successful login: ", error); + } + // payload for the JWT token const payload = { id: 1, @@ -32,7 +43,7 @@ const casLogin = function (req, res, next) { } // call the authenticate function with CAS - passport.authenticate("cas", function (err, user, info) { + passport.authenticate("cas", async function (err, user, info) { if (err) { console.log("found error during authentication: ", err); return next(err); @@ -48,7 +59,7 @@ const casLogin = function (req, res, next) { // create a user object and redirect user // to the root route - req.logIn(user, function (err) { + req.logIn(user, async function (err) { console.log("Executing log in."); if (err) { return next(err); @@ -64,6 +75,14 @@ const casLogin = function (req, res, next) { // sign the token with secret key const token = jwt.sign(payload, JWT_SECRET, { expiresIn: "1h" }); + // document a successful login + try { + const timestamp = new Date(); // Current timestamp + await documentSuccessfulLogin(user.id, timestamp); + } catch (error) { + console.error("Error logging the successful login: ", error); + } + // Send the JWT token to the frontend console.log("sending JWT To: ", process.env.FRONTEND_URL); res.redirect(`${process.env.FRONTEND_URL}/home?token=${token}`); diff --git a/backend/config/database/schema.sql b/backend/config/database/schema.sql index 3c1feccdc55660dc6e25738728cc0e6a2e9be70c..2ae7c243331f276eee89f85b69c5d5cd157ccf92 100644 --- a/backend/config/database/schema.sql +++ b/backend/config/database/schema.sql @@ -13,12 +13,11 @@ CREATE TABLE IF NOT EXISTS users ( FOREIGN KEY (roleID) REFERENCES rolesPermissions(roleID) ); --- Table to log login/logout information -CREATE TABLE IF NOT EXISTS userLoginLogout ( +-- Table to log login information +CREATE TABLE IF NOT EXISTS userLogin ( eventId INT AUTO_INCREMENT PRIMARY KEY, eventTimestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, userID INT NOT NULL, - eventType VARCHAR(255) NOT NULL, -- successfulLogin, unsuccessfulLogin, logout FOREIGN KEY (userID) REFERENCES users(userID) ); @@ -89,9 +88,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) ); @@ -119,8 +119,18 @@ CREATE TABLE IF NOT EXISTS peopleXInvolvement ( FOREIGN KEY (involvementId) REFERENCES involvementLookup(involvementId) ); +CREATE TABLE IF NOT EXISTS changeLog ( + logId INT PRIMARY KEY AUTO_INCREMENT, + changedTable VARCHAR(255) NOT NULL, -- e.g., "people" to specify the table updated + recordId INT NOT NULL, -- ID of the record that was updated + userId INT NOT NULL, -- User who made the change (foreign key to `users` table) + changeTimestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + changeType ENUM('INSERT', 'UPDATE', 'DELETE') NOT NULL, + changedColumns TEXT NOT NULL, -- Names of columns that were changed + oldValues TEXT, -- JSON format for previous values + newValues TEXT, -- JSON format for new values + FOREIGN KEY (userId) REFERENCES users(userID) +); + INSERT INTO countryLookup (countryId, name) VALUES (1, 'United States'); - --- I think there is already a people table that exists in our DB and --- has data that does not include peopoleID \ No newline at end of file diff --git a/backend/index.js b/backend/index.js index 056453e40784243ab24898d5b41fb3a91faf2fe5..e571663e5ad0328388d8fe2f6192953877f1f843 100644 --- a/backend/index.js +++ b/backend/index.js @@ -3,6 +3,7 @@ const session = require("express-session"); const cors = require("cors"); require("dotenv").config(); const userRoutes = require("./routes/userRoutes"); +const activityRouter = require("./routes/activityMonitoringRoutes"); // create connection to db and initialize table schema const { pool } = require("./config/database/database.config"); @@ -49,6 +50,7 @@ app.use(function (req, res, next) { require("./config/auth/auth.routes")(app); app.use("/api/user", userRoutes); app.use("/api/person", peopleRouter); +app.use("/api/activity", activityRouter); // Basic route to check if server is running app.get("/api", (req, res) => { diff --git a/backend/repository/activityRepository.js b/backend/repository/activityRepository.js new file mode 100644 index 0000000000000000000000000000000000000000..7cfefcd602145fe2a5eb59afde162bb4eb92088c --- /dev/null +++ b/backend/repository/activityRepository.js @@ -0,0 +1,94 @@ +const { pool } = require("../config/database/database.config"); + +/* +Repository for login and changelog tables +*/ + +/* +Add a row to the userLogin table indicating a successful +login. +*/ +async function logSuccessfulLogin(userId, timestamp) { + try { + // SQL query to insert the login info + const query = ` + INSERT INTO userLogin (eventTimestamp, userId) VALUES (?,?) + `; + + // make query + const [result] = await pool.query(query, [timestamp, userId]); + console.log("Successfully logged log in."); + + // return the inserted id + return result.insertId; + } catch (error) { + // throw error to be caught by the service + throw error; + } +} + +async function getLoginDataTimerange(start, end) { + try { + // get all login data within the start and end timerange + const query = ` + SELECT * + FROM userLogin + WHERE eventTimestamp BETWEEN ? AND ? + ORDER BY eventTimestamp DESC + `; + const [rows] = await pool.query(query, [start, end]); + return rows; + } catch (error) { + // throw error to be caught in service + console.error("Error fetching login data:", error); + throw error; + } +} + +async function getLoginDataByUser() {} + +/* +Insert a row into changeLog which keeps track of every time the database is changed. +*/ +async function logDatabaseChange( + tableName, + recordId, + userId, + changeType, + changedColumns, + oldValues, + newValues +) { + const query = ` + INSERT INTO changeLog (changedTable, recordId, userId, changeType, changedColumns, oldValues, newValues) + VALUES (?, ?, ?, ?, ?, ?, ?) + `; + const values = [ + tableName, + recordId, + userId, + changeType, + changedColumns, + oldValues, + newValues, + ]; + + try { + await pool.query(query, values); + console.log("Change logged successfully."); + } catch (error) { + // throw error to be caught by service + console.log("activity repository error when logging change: ", error); + throw error; + } +} + +async function getDatabaseChangeTimerange() {} + +async function getDatabaseChangeByUser() {} + +module.exports = { + logSuccessfulLogin, + getLoginDataTimerange, + logDatabaseChange, +}; diff --git a/backend/repository/addressRepository.js b/backend/repository/addressRepository.js index 06da1ce87d9ea8966dea421bf50243733e54feb9..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,48 +62,195 @@ async function addAddress(address, peopleId) { } } -// Private helper function to look up the stateId -async function _lookupStateId(state) { - // for now return 1 while tables are not populated - return 1; +/* +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; + } - // 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; - // } + 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; + } +} + +/* +Function to find all address information associated with a person. +Returns an array of address objects or an empty array if no addresses exist. +*/ +async function getAddressesForPerson(peopleId) { + console.log("Finding addresses for person with ID: ", peopleId); + + try { + const query = ` + SELECT + a.addressId, + a.address1, + a.address2, + a.city, + sl.name AS state, + cl.name AS country, + a.zipCode, + pa.preferredAddress + FROM + peopleXAddress pa + LEFT JOIN + address a ON pa.addressId = a.addressId + LEFT JOIN + stateLookup sl ON a.stateId = sl.stateId + LEFT JOIN + countryLookup cl ON a.countryId = cl.countryId + WHERE + pa.peopleId = ?; + `; + + // Execute the query + const [results] = await pool.query(query, [peopleId]); + + if (results.length === 0) { + console.log("No addresses found for person with ID:", peopleId); + return []; + } + + // Map the results to structured address objects + const addresses = results.map((row) => ({ + addressId: row.addressId, + address1: row.address1, + address2: row.address2, + city: row.city, + state: row.state, + country: row.country, + zipCode: row.zipCode, + preferredAddress: row.preferredAddress, + })); + + console.log("Found addresses: ", addresses); + + return addresses; + } catch (error) { + console.error("Error fetching addresses for person: ", error); + return []; + } } // 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 }; +module.exports = { + addAddress, + getAddressesForPerson, + deleteAddressById, + updateAddress, +}; diff --git a/backend/repository/contactRepository.js b/backend/repository/contactRepository.js index 0a091e928f1acd8ce88d4086d3d3303580685cae..72247593391cc356fe1dccd80bc37e2514a772f1 100644 --- a/backend/repository/contactRepository.js +++ b/backend/repository/contactRepository.js @@ -2,7 +2,8 @@ const { pool } = require("../config/database/database.config"); /* Function to add a row into the peopleContact -table with the contact info given +table with the contact info given. Returns the id of the new contact. +If no contact is added, null is returned */ async function addContact(contact, peopleId) { // extract the parts of the contact @@ -25,11 +26,135 @@ async function addContact(contact, peopleId) { preferredContact, ]); - return results; + return results.insertId; } catch (error) { console.log(error); return null; } } -module.exports = { addContact }; +/* +Function to update the data for a specific contact. The +function will return true if the update was successful and false +otherwise. +*/ +async function updateContact(peopleContactId, peopleId, contactInfo) { + // extract the parts of the contact + let { contactNumber, contactType, preferredContact } = contactInfo; + + console.log("updating contact...."); + + try { + const query = ` + UPDATE peopleContact + SET + peopleId = ?, + contactNumber = ?, + contactType = ?, + preferredContact = ? + WHERE peopleContactId = ? + `; + + const [results] = await pool.query(query, [ + peopleId, + contactNumber, + contactType, + preferredContact, + peopleContactId, + ]); + + // Check if any rows were affected + if (results.affectedRows === 0) { + console.error("Contact not found. no rows updated"); + return false; + } + + // return true to signify success + return true; + } catch (error) { + console.log("Error updating contact: ", error); + return false; + } +} + +/* +Function to find all contact information associated with a person. +Returns an array of contact objects or an empty array if no contacts exist. +*/ +async function getContactsForPerson(peopleId) { + console.log("Finding contacts for person with ID: ", peopleId); + + try { + // get all contacts with the wanted peopleId + const query = ` + SELECT + peopleContactId, + contactNumber, + contactType, + preferredContact + FROM + peopleContact + WHERE + peopleId = ?; + `; + + // Execute the query + const [results] = await pool.query(query, [peopleId]); + + if (results.length === 0) { + console.log("No contacts found for person with ID:", peopleId); + return []; // empty array, no contacts found + } + + // Map the results to structured contact objects + const contacts = results.map((row) => ({ + peopleContactId: row.peopleContactId, + contactNumber: row.contactNumber, + contactType: row.contactType, + preferredContact: row.preferredContact, + })); + + console.log("Found contacts: ", contacts); + + return contacts; + } catch (error) { + console.error("Error fetching contacts for person: ", error); + return []; // empty array, no contacts found + } +} + +/* +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 9e7835f6df78a84ca076cbe753c0fabfcf5efb23..1c3dda1e84c39358dd7e3b8ecea1dbf7d3f31509 100644 --- a/backend/repository/degreeRepository.js +++ b/backend/repository/degreeRepository.js @@ -1,7 +1,8 @@ const { pool } = require("../config/database/database.config"); /* -Add degree information into the database. +Add degree information into the database. The method will return +the id of the newly inserted degree, or null if nothing was added. */ async function addDegree(degree, peopleId) { // get all the parts of the degree @@ -35,7 +36,14 @@ async function addDegree(degree, peopleId) { degreeDescription, ]); - return results; + console.log("Inserted degree: ", results.insertId); + + const checkQuery = `SELECT * FROM peopleDegree WHERE peopleDegreeId = ?`; + const [checkResults] = await pool.query(checkQuery, [results.insertId]); + + console.log(checkResults[0]); + + return results.insertId; } catch (error) { // return null if there is an error console.error(error); @@ -43,28 +51,194 @@ async function addDegree(degree, peopleId) { } } +/* +Function to update the information about a specific degree +in the database. The function will return true if a row +was successfully updated and false otherwise. +*/ +async function updateDegree(peopleDegreeId, peopleId, degreeInfo) { + console.log("updating degree...."); + + // get all the parts of the degree + let { + degreeType, + degreeDepartment, + degreeCollege, + degreeYear, + degreeDescription, + } = degreeInfo; + + try { + // Use the helper function to determine the degreeTypeId + let degreeTypeId = await _lookupDegreeTypeId(degreeType); + + // SQL query to update + const query = ` + UPDATE peopleDegree + SET + peopleId = ?, + degreeTypeId = ?, + degreeDepartment = ?, + degreeCollege = ?, + degreeYear = ?, + degreeDescription = ? + WHERE peopleDegreeId = ? + `; + + const [results] = await pool.query(query, [ + peopleId, + degreeTypeId, + degreeDepartment, + degreeCollege, + degreeYear, + degreeDescription, + peopleDegreeId, + ]); + + // Check if any rows were affected + if (results.affectedRows === 0) { + console.error("Degree not found. no rows updated"); + return false; + } + + console.log("degree info updated: ", peopleDegreeId); + + // return true to signify success + return true; + } catch (error) { + console.error("error updating degree: ", error); + return false; + } +} + +/* +Function to delete a degree by its ID. +Returns true if the degree was successfully deleted, false otherwise. +*/ +async function deleteDegreeById(peopleDegreeId) { + console.log("Deleting degree with ID: ", peopleDegreeId); + + try { + const query = ` + DELETE FROM peopleDegree + WHERE peopleDegreeId = ? + `; + + const [results] = await pool.query(query, [peopleDegreeId]); + + // Check if any rows were affected + if (results.affectedRows === 0) { + console.error("Degree not found. No rows deleted."); + return false; + } + + console.log("Degree successfully deleted: ", peopleDegreeId); + + return true; + } catch (error) { + console.error("Error deleting degree: ", error); + return false; + } +} + +/* +Function to find all degree IDs associated with a person. +Returns an array of peopleDegreeIds or an empty array if no degrees exist. +*/ +async function getDegreeIdsForPerson(peopleId) { + console.log("Finding degree IDs for person with ID: ", peopleId); + + try { + // get the degrees for a given peopleId + const query = ` + SELECT peopleDegreeId + FROM peopleDegree + WHERE peopleId = ? + `; + + const [results] = await pool.query(query, [peopleId]); + + // Extract the degree IDs + const degreeIds = results.map((row) => row.peopleDegreeId); + + console.log("Found degree IDs: ", degreeIds); + + return degreeIds; + } catch (error) { + console.error("Error finding degree IDs: ", error); + return []; + } +} + +/* +Function to get all degree information associated with a person. +Returns an array of degree objects or an empty array if no degrees exist. +*/ +async function getDegreesForPerson(peopleId) { + console.log("Finding degrees for person with ID: ", peopleId); + + try { + // get the full degree information for degreees for person with peopleId + const query = ` + SELECT + d.peopleDegreeId, + d.degreeDepartment, + d.degreeCollege, + d.degreeYear, + d.degreeDescription, + dt.degreeType + FROM + peopleDegree d + LEFT JOIN + degreeTypeLookup dt ON d.degreeTypeId = dt.degreeTypeId + WHERE + d.peopleId = ?; + `; + + const [results] = await pool.query(query, [peopleId]); + + // Print out each row returned by the query + console.log("Raw results from query:", results); + + if (results.length === 0) { + console.log("No degrees found for person with ID:", peopleId); + return []; + } + + const degrees = results.map((row) => ({ + peopleDegreeId: row.peopleDegreeId, + degreeType: row.degreeType, + degreeDepartment: row.degreeDepartment, + degreeCollege: row.degreeCollege, + degreeYear: row.degreeYear, + degreeDescription: row.degreeDescription, + })); + + console.log("Found degrees: ", degrees); + + return degrees; + } catch (error) { + console.error("Error fetching degrees for person: ", error); + return []; + } +} + // 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; } } -module.exports = { addDegree, _lookupDegreeTypeId }; +module.exports = { + addDegree, + updateDegree, + deleteDegreeById, + getDegreeIdsForPerson, + getDegreesForPerson, + _lookupDegreeTypeId, +}; diff --git a/backend/repository/peopleRepository.js b/backend/repository/peopleRepository.js index df0c1a37535190456236ed76c3447e0e130bb0e5..59dda80f2d70b9bdde1d78737ea05a2bf3a9666b 100644 --- a/backend/repository/peopleRepository.js +++ b/backend/repository/peopleRepository.js @@ -84,7 +84,54 @@ async function searchPeopleByName(firstName, lastName) { } } -async function getPersonById() {} +/* +Function to get the data associated with a specific peopleId. +If none is found, the function returns null. +*/ +async function getPersonById(peopleId) { + console.log("searching for person..."); + + try { + // big query to get all of the data + const query = ` + SELECT + peopleId, + firstName, + lastName, + middleName, + maidenName, + suffix, + nickName, + techAlumniChapter, + classYear, + gradYear, + gradSemester, + gender + FROM + people + WHERE + peopleId = ?; + `; + + // Execute the query + const [rows] = await pool.query(query, [peopleId]); + + if (rows.length === 0) { + console.log("No person found"); + null; + } + + // structure the data + console.log("data recieved... structuring now"); + + // create the structure of a person + const person = rows[0]; + return person; + } catch (error) { + console.log("Error getting person's full details: ", error); + return null; + } +} /* Function to delete a person with the peopleId given. @@ -107,6 +154,82 @@ async function deletePersonById(peopleId) { } console.log("row deleted"); + + return true; + } catch (error) { + console.error(error); + return false; + } +} + +/* +Function to update the people table's information for +the person with the given id. +*/ +async function updatePerson(personId, personInfo) { + // extract all the values from person + let { + firstName, + lastName, + middleName, + maidenName, + suffix, + nickName, + techAlumniChapter, + classYear, + gradYear, + gradSemester, + gender, + } = personInfo; + + // find the integer that represents the alumni chapter (for now do 2) + techAlumniChapter = 2; + + try { + console.log("Updating person: ", firstName); + + // SQL UPDATE query + const query = ` + UPDATE people + SET + firstName = ?, + lastName = ?, + middleName = ?, + maidenName = ?, + suffix = ?, + nickName = ?, + techAlumniChapter = ?, + classYear = ?, + gradYear = ?, + gradSemester = ?, + gender = ? + WHERE peopleId = ? + `; + + const [results] = await pool.query(query, [ + firstName, + lastName, + middleName, + maidenName, + suffix, + nickName, + techAlumniChapter, + classYear, + gradYear, + gradSemester, + gender, + personId, + ]); + + // Check if any rows were affected + if (results.affectedRows === 0) { + console.error("Person not found. no rows updated"); + return false; + } + + console.log("person info updated: ", personId); + + // return true to signify success return true; } catch (error) { console.error(error); @@ -114,4 +237,10 @@ async function deletePersonById(peopleId) { } } -module.exports = { addPerson, searchPeopleByName, deletePersonById }; +module.exports = { + addPerson, + searchPeopleByName, + deletePersonById, + updatePerson, + getPersonById, +}; diff --git a/backend/repository/stateRepository.js b/backend/repository/stateRepository.js index 14e489d053bf2320fae6ef639ef9f320cc51e946..15dd0801155fb2c4f002397a3f11d18861da1832 100644 --- a/backend/repository/stateRepository.js +++ b/backend/repository/stateRepository.js @@ -53,7 +53,27 @@ async function getState(name) { } } -async function checkStateTableExistence() { +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'"); if (rows.length > 0) { @@ -66,4 +86,9 @@ async function checkStateTableExistence() { } } -module.exports = { checkStateTableExistence, addStateIfNotExists, getState }; +module.exports = { + checkTableExistence, + addStateIfNotExists, + getState, + getStateId, +}; diff --git a/backend/routes/activityMonitoringRoutes.js b/backend/routes/activityMonitoringRoutes.js new file mode 100644 index 0000000000000000000000000000000000000000..17d4c1f0e6454570c3e1cb44c5d20253d859c273 --- /dev/null +++ b/backend/routes/activityMonitoringRoutes.js @@ -0,0 +1,28 @@ +const express = require("express"); +const { viewLoginsTimerange } = require("../service/activityLoggerService"); + +const activityRouter = express.Router(); + +// get login history by timerange +activityRouter.get("/login-data", async (req, res) => { + const { start, end } = req.query; + + // ensure both params are given + if (!start || !end) { + return res + .status(400) + .json({ message: "Please provide both start and end dates." }); + } + + try { + const data = await viewLoginsTimerange(start, end); + res.status(200).json(data); + } catch { + console.log("error fetching login data: ", error); + res.status(500).json({ message: "Internal service error." }); + } +}); + +// get db history for timerange + +module.exports = activityRouter; 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/activityLoggerService.js b/backend/service/activityLoggerService.js new file mode 100644 index 0000000000000000000000000000000000000000..9fa3a89b4a3b22ec7d12a49ac14dbe23ba50fa66 --- /dev/null +++ b/backend/service/activityLoggerService.js @@ -0,0 +1,44 @@ +const { + logSuccessfulLogin, + getLoginDataTimerange, + logDatabaseChange, +} = require("../repository/activityRepository"); + +/* +Function that creates a row of data in the userLogin table +to documenmt a successful log in. +*/ +async function documentSuccessfulLogin(userId, timestamp) { + try { + const result = await logSuccessfulLogin(userId, timestamp); + } catch (error) { + // SQL error from the repository + console.log("Error when logging a successful login: ", error); + } +} + +async function logDatabaseChange( + tableName, + recordId, + userId, + changeType, + changedColumns, + oldValues, + newValues +) {} + +/* +Get the login data for a sopecific time range +*/ +async function viewLoginsTimerange(start, end) { + // will throw error if there is a problem + return await getLoginDataTimerange(start, end); +} + +async function viewDatabaseChanges() {} + +module.exports = { + documentSuccessfulLogin, + viewLoginsTimerange, + logDatabaseChange, +}; diff --git a/backend/service/peopleService.js b/backend/service/peopleService.js index b0f9aabb9f1c3343bba207fe3ac812e2527606a3..10b760c4ac84705ffb8d48a037c23ac8ab10e1d8 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 @@ -61,6 +79,7 @@ async function findByName(firstName, lastName) { Function to delete person by their id. */ async function deleteById(peopleId) { + // delete the person const personDeleted = await deletePersonById(peopleId); if (personDeleted) { @@ -70,4 +89,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, +};