Skip to content
Snippets Groups Projects
Commit f8e9077c authored by fhurtado14's avatar fhurtado14
Browse files

Merge branch 'feature/new-search-backend' into 'main'

Feature/new search backend

See merge request !19
parents a914eed4 7481a298
No related branches found
No related tags found
1 merge request!19Feature/new search backend
...@@ -97,6 +97,7 @@ async function getChapterById(chapterId) { ...@@ -97,6 +97,7 @@ async function getChapterById(chapterId) {
} }
} catch (error) { } catch (error) {
console.error(`Error fetching chapter with ID ${chapterId}:`, error); console.error(`Error fetching chapter with ID ${chapterId}:`, error);
return null;
} }
} }
......
...@@ -51,6 +51,26 @@ async function getInvolvementType(description) { ...@@ -51,6 +51,26 @@ async function getInvolvementType(description) {
} }
} }
async function getInvolvementTypeById(involvementId) {
// SQL query to get a certain involvement type
const query = "SELECT * FROM involvementType WHERE involvementTypeId = ?";
try {
console.log("Getting involvement type with id: ", involvementId);
const [rows] = await pool.query(query, [involvementId]);
if (rows.length > 0) {
console.log("Found involvement type: ", involvementId);
return rows[0];
} else {
console.log("Involvement type not found: ", involvementId);
return null;
}
} catch (error) {
console.error(`Error fetching involvement type ${involvementId}:`, error);
throw error;
}
}
/* /*
Function to add an involvement to the involvementLookup table Function to add an involvement to the involvementLookup table
*/ */
...@@ -215,4 +235,5 @@ module.exports = { ...@@ -215,4 +235,5 @@ module.exports = {
addPeopleInvolvement, addPeopleInvolvement,
getInvolvementsForPerson, getInvolvementsForPerson,
deleteInvolvementForPerson, deleteInvolvementForPerson,
getInvolvementTypeById,
}; };
...@@ -252,53 +252,138 @@ async function updatePerson(personId, personInfo) { ...@@ -252,53 +252,138 @@ async function updatePerson(personId, personInfo) {
} }
// Define a whitelist of allowed fields to prevent SQL injection // Define a whitelist of allowed fields to prevent SQL injection
const allowedSearchFields = [ const allowedFilters = [
"gradYear", "firstName",
"lastName",
"nickName",
"classYearStart",
"classYearEnd",
"gradYearStart",
"gradYearEnd",
"degreeYear", "degreeYear",
"degreeDepartment",
"degreeCollege",
"city", "city",
"stateId", "stateId",
"contactNumber", "involvementType",
"firstName",
"lastName",
]; ];
async function search(filters) { async function search(filters) {
//begin by selecting all people //begin by selecting all people and concatenating their fields with multiple values
let baseQuery = ` let baseQuery = `
SELECT p.*, pd.degreeYear, a.city, a.stateId, c.contactNumber SELECT
FROM people p p.peopleId,
LEFT JOIN peopleDegree pd ON p.peopleId = pd.peopleId p.firstName,
LEFT JOIN peopleXAddress pa ON p.peopleId = pa.peopleId p.lastName,
LEFT JOIN address a ON pa.addressId = a.addressId p.middleName,
LEFT JOIN peopleContact c ON p.peopleId = c.peopleId p.maidenName,
WHERE 1=1 p.suffix,
`; p.nickName,
p.techAlumniChapter,
// add filters to the base query in order to keep refining the set of people returned p.classYear,
p.gradYear,
p.gradSemester,
p.gender,
GROUP_CONCAT(DISTINCT CONCAT(pd.degreeYear, ' - ', pd.degreeDepartment, ' (', pd.degreeCollege, ')') SEPARATOR ' | ') AS degrees,
GROUP_CONCAT(DISTINCT CONCAT(a.city, ', ', sl.name) SEPARATOR ' | ') AS addresses,
GROUP_CONCAT(DISTINCT c.contactNumber SEPARATOR ', ') AS contactNumbers,
GROUP_CONCAT(DISTINCT il.involvementType SEPARATOR ', ') AS involvements
FROM people p
LEFT JOIN peopleDegree pd ON p.peopleId = pd.peopleId
LEFT JOIN peopleXAddress pa ON p.peopleId = pa.peopleId
LEFT JOIN address a ON pa.addressId = a.addressId
LEFT JOIN stateLookup sl ON a.stateId = sl.stateId
LEFT JOIN peopleContact c ON p.peopleId = c.peopleId
LEFT JOIN peopleXInvolvement pi ON p.peopleId = pi.peopleId
LEFT JOIN involvementLookup il ON pi.involvementId = il.involvementId
WHERE p.peopleId IN (
SELECT DISTINCT p2.peopleId
FROM people p2
LEFT JOIN peopleDegree pd2 ON p2.peopleId = pd2.peopleId
LEFT JOIN peopleXAddress pa2 ON p2.peopleId = pa2.peopleId
LEFT JOIN address a2 ON pa2.addressId = a2.addressId
LEFT JOIN peopleContact c2 ON p2.peopleId = c2.peopleId
LEFT JOIN peopleXInvolvement pi2 ON p2.peopleId = pi2.peopleId
LEFT JOIN involvementLookup il2 ON pi2.involvementId = il2.involvementId
WHERE 1=1
`;
// Query values for parameterized query
let queryValues = []; let queryValues = [];
for (const filter of filters) {
console.log("Filter: ", filter);
// extract field or value
let field = filter.field ? filter.field : null;
let value = filter.value ? filter.value : null;
// throw error if the field or value is missing
if (field == null || value == null) {
throw new Error(
`Missing field or value in filter: ${JSON.stringify(filter)}`
);
}
// make sure the search field is allowed // Check each filter field and add to the query if not null
if (!allowedSearchFields.includes(field)) { if (filters.firstName) {
throw new Error(`Invalid field specified: ${field}`); baseQuery += ` AND p2.firstName LIKE ?`;
} queryValues.push(`%${filters.firstName}%`);
}
if (filters.lastName) {
baseQuery += ` AND p2.lastName LIKE ?`;
queryValues.push(`%${filters.lastName}%`);
}
if (filters.nickName) {
baseQuery += ` AND p2.nickName LIKE ?`;
queryValues.push(`%${filters.nickName}%`);
}
// Handle classYearStart and classYearEnd
if (filters.classYearStart) {
baseQuery += ` AND p2.classYear >= ?`;
queryValues.push(filters.classYearStart);
}
if (filters.classYearEnd) {
baseQuery += ` AND p2.classYear <= ?`;
queryValues.push(filters.classYearEnd);
}
// Handle gradYearStart and gradYearEnd
if (filters.gradYearStart) {
baseQuery += ` AND p2.gradYear >= ?`;
queryValues.push(filters.gradYearStart);
}
if (filters.gradYearEnd) {
baseQuery += ` AND p2.gradYear <= ?`;
queryValues.push(filters.gradYearEnd);
}
if (filters.degreeYearStart) {
baseQuery += ` AND pd2.degreeYear >= ?`;
queryValues.push(filters.degreeYearStart);
}
if (filters.degreeYearEnd) {
baseQuery += ` AND pd2.degreeYear <= ?`;
queryValues.push(filters.degreeYearEnd);
}
// Add condition and value to the query if (filters.degreeDepartment) {
baseQuery += ` AND ${field} = ?`; baseQuery += ` AND pd2.degreeDepartment LIKE ?`;
queryValues.push(value); queryValues.push(`%${filters.degreeDepartment}%`);
}
if (filters.degreeCollege) {
baseQuery += ` AND pd2.degreeCollege LIKE ?`;
queryValues.push(`%${filters.degreeCollege}%`);
}
if (filters.city) {
baseQuery += ` AND a2.city LIKE ?`;
queryValues.push(`%${filters.city}%`);
}
if (filters.stateId) {
baseQuery += ` AND a2.stateId = ?`;
queryValues.push(filters.stateId);
} }
if (filters.involvementType && filters.involvementType.length > 0) {
baseQuery += ` AND il2.involvementType IN (?)`;
queryValues.push(filters.involvementType);
}
// Finalize the query
baseQuery += `
)
GROUP BY
p.peopleId, p.firstName, p.lastName, p.middleName, p.maidenName, p.suffix,
p.nickName, p.techAlumniChapter, p.classYear, p.gradYear, p.gradSemester,
p.gender;
`;
try { try {
const [results] = await pool.query(baseQuery, queryValues); const [results] = await pool.query(baseQuery, queryValues);
......
...@@ -262,9 +262,8 @@ peopleRouter.post("/search", async (req, res) => { ...@@ -262,9 +262,8 @@ peopleRouter.post("/search", async (req, res) => {
// filters is an array of { field, value } pairs // filters is an array of { field, value } pairs
let filters = req.body.filters; let filters = req.body.filters;
if (filters == null || !Array.isArray(filters) || filters.length == 0) { if (!filters) {
res.status(400).json({ message: "Missing filters." }); return res.status(400).json({ message: "Filters not provided!" });
return;
} }
try { try {
......
...@@ -31,8 +31,11 @@ const { ...@@ -31,8 +31,11 @@ const {
getInvolvement, getInvolvement,
getInvolvementsForPerson, getInvolvementsForPerson,
deleteInvolvementForPerson, deleteInvolvementForPerson,
getInvolvementTypeById,
} = require("../repository/involvementRepository"); } = require("../repository/involvementRepository");
const { upsertUser } = require("../repository/userRepository"); const { upsertUser } = require("../repository/userRepository");
const { getStateId } = require("../repository/stateRepository");
const { getChapterById } = require("../repository/chapterRepository");
/* /*
Function to handle the logic for creating a person. Function to handle the logic for creating a person.
...@@ -366,7 +369,126 @@ async function updateInvolvementsForPerson(personId, involevements) { ...@@ -366,7 +369,126 @@ async function updateInvolvementsForPerson(personId, involevements) {
} }
async function generalSearch(filters) { async function generalSearch(filters) {
return search(filters); // combine corpsUnits and corpsOrgs lists into one list of involvements
const involvementNames = [
...(filters.corpsUnits || []),
...(filters.corpsOrgs || []),
];
// find the involvement type for each involvement
let involvementTypes = [];
try {
involvementTypes = await Promise.all(
involvementNames.map(async (name) => {
try {
const involvementType = await getInvolvementType(name); // Fetch the full involvement type object
return involvementType.involvementTypeId; // Extract and return only the ID
} catch (error) {
console.error(`Error getting involvement type for: ${name}`, error);
return null; // Return null for failed translations
}
})
);
} catch (error) {
console.error("Error processing involvement types:", error);
}
// Filter out nulls in case of translation failures
involvementTypes = involvementTypes.filter((type) => type !== null);
// translate state into a state id
let stateId = null;
if (filters.state) {
try {
stateId = await getStateId(filters.state);
} catch (error) {
console.log("error getting state id");
}
}
filters.stateId = stateId;
filters.involvementType = involvementTypes;
let results = await search(filters);
console.log(results);
// post processing:
for (let result of results) {
// 1. translate tech alumni chapter
if (result.techAlumniChapter) {
try {
const chapterName = await getChapterById(result.techAlumniChapter);
result.techAlumniChapter = chapterName;
} catch (error) {
console.error(
`Error translating techAlumniChapter: ${result.techAlumniChapter}`,
error
);
}
}
// 2. separate degrees
if (result.degrees) {
result.degrees = result.degrees.split(" | ").map((degree) => {
const [year, details] = degree.split(" - ");
const [department, college] = details.slice(0, -1).split(" (");
return {
degreeYear: year.trim(),
degreeDepartment: department.trim(),
degreeCollege: college.trim(),
};
});
}
// 3. separate addresses
if (result.addresses) {
result.addresses = result.addresses.split(" | ").map((address) => {
const [city, state] = address.split(", ");
return {
city: city.trim(),
state: state.trim(),
};
});
}
// 4. separate contacts
if (result.contactNumbers) {
result.contactNumbers = result.contactNumbers
.split(", ")
.map((contact) => contact.trim());
}
// 5. separate involvements
if (result.involvements) {
result.involvements = result.involvements
.split(", ")
.map((involvement) => parseInt(involvement, 10));
}
// 6. Translate involvements
if (result.involvements) {
result.involvements = await Promise.all(
result.involvements.map(async (involvementId) => {
try {
const involvementType = await getInvolvementTypeById(involvementId);
const involvementName = involvementType.description;
console.log("Name: ", involvementName);
return {
involvementName,
};
} catch (error) {
console.error(
`Error translating involvementId: ${involvementId}`,
error
);
return { involvementId, involvementName: "Unknown" };
}
})
);
}
}
return results;
} }
module.exports = { module.exports = {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment