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

Merge branch 'feature/search-person' into 'main'

Basic functionality achieved of generalized search function. Need to define...

See merge request fhurtado14/corps-directory!11
parents d1fe834e ecc73ab0
No related branches found
No related tags found
1 merge request!11Basic functionality achieved of generalized search function. Need to define...
......@@ -29,7 +29,7 @@ docker push container.cs.vt.edu/fhurtado14/corps-directory/frontend:latest
Go to CS Launch and scale down backend deployment to 0, then scale back up to 1.
# Steps to create a branch and merge request on git lab
# Steps to create a branch and merge request on git lab
To make a branch do: git checkout -b Branch-Name
......@@ -39,8 +39,13 @@ To push your local changes to remote git: git push --set-upstream origin Branch-
To merge go to GitLab. Click merge request tab. Click new merge request. Fill out the information. Assign to members if you want branch to be tested before merged. They will have to approve your branch in order for it to be merged. Make sure you check "delete source branch when merge request is accepted" and "squash commits when merge request is accepted."
If you are assigned a merge-request you can view them on the merge-request tab on GitLab. You will have to switch to their branch by doing git checkout Branch-Name to test their branch. Note: You can only switch to their branch if you have pulled in an up to date main branch. For example let's say I did a "git push --set-upstream origin test-branch" so it could connect with git. If I did that after you last pulled, then you won't be able to switch into it until you git pull again.
If you are assigned a merge-request you can view them on the merge-request tab on GitLab. You will have to switch to their branch by doing git checkout Branch-Name to test their branch. Note: You can only switch to their branch if you have pulled in an up to date main branch. For example let's say I did a "git push --set-upstream origin test-branch" so it could connect with git. If I did that after you last pulled, then you won't be able to switch into it until you git pull again.
After merge happens:
Switch back to main: git checkout main
Pull changes from remote repository: git pull origin main
# Integration tests
docker-compose -f docker-compose.test.yml up --build
docker-compose -f docker-compose.test.yml down -v --remove-orphans
......@@ -10,10 +10,14 @@ require("dotenv").config();
// Initialize the pool instance to read the SQL startup file
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
host: process.env.NODE_ENV === "test" ? "test-mysql" : process.env.DB_HOST,
user: process.env.NODE_ENV === "test" ? "test_user" : process.env.DB_USER,
database:
process.env.NODE_ENV === "test"
? "corps_directory_test_db"
: process.env.DB_NAME,
password:
process.env.NODE_ENV === "test" ? "test_password" : process.env.DB_PASSWORD,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
......@@ -23,46 +27,4 @@ console.log("Database Host: ", process.env.DB_HOST);
console.log("Database user: ", process.env.DB_USER);
console.log("Database name: ", process.env.DB_NAME);
// Function to execute the SQL file at initilization
const executeSQLFile = (filePath) => {
const sql = fs.readFileSync(filePath, "utf8");
// Split SQL into individual statements
const sqlStatements = sql.split(/;\s*$/m);
// connect to MySQL
pool.getConnection((err, connection) => {
if (err) {
console.error("Error connecting to MySQL:", err.message);
return;
}
// successfully connected to DB
console.log("connected to DB, executing schema...");
// Execute each SQL statement in the file one by one
sqlStatements.forEach((statement) => {
if (statement.trim()) {
// Ignore empty statements
connection.query(statement, (err, result) => {
if (err) {
console.error("Error executing SQL statement:", err.message);
// connection.release();
return;
}
});
}
console.log("Just added: ", statement);
});
// release connection after all statements
connection.release();
});
};
// Execute the SQL file during startup
const schemaFilePath = path.join(__dirname, "./schema.sql");
executeSQLFile(schemaFilePath);
module.exports = { pool: pool.promise() };
......@@ -16,6 +16,8 @@ const {
checkChapterTableExistence,
addChapterIfNotExists,
} = require("../../repository/chapterRepository");
const fs = require("fs").promises;
const path = require("path");
/*
Class responsible for populating the database
......@@ -27,6 +29,32 @@ class DatabaseInitializer {
this.pool = pool;
}
/*
Execute SQL schema file to create necessary tables on startup.
*/
async executeSQLFile(filePath) {
try {
const sql = await fs.readFile(filePath, "utf8");
const sqlStatements = sql.split(/;\s*$/m);
const connection = await this.pool.getConnection();
console.log("Connected to DB, executing schema...");
for (const statement of sqlStatements) {
if (statement.trim()) {
await connection.query(statement);
console.log("Executed statement:", statement);
}
}
connection.release();
} catch (err) {
console.error("Error executing SQL schema file:", err.message);
throw err;
}
}
/*
Method for ensuring database is accessible.
*/
......@@ -374,6 +402,10 @@ class DatabaseInitializer {
async initializeAll() {
await this.testDBConnection();
const schemaFilePath = path.join(__dirname, "./schema.sql");
await this.executeSQLFile(schemaFilePath);
await this.initializeRoles();
// await this.initializeTestUsers(); // no not initialize test users anymore
await this.initializeStates();
......@@ -383,6 +415,11 @@ class DatabaseInitializer {
console.log("database init completed.");
}
async initializeTestDB() {
const schemaPath = path.join(__dirname, "./schema.sql");
await this.executeSQLFile(schemaPath);
}
}
module.exports = DatabaseInitializer;
......@@ -251,10 +251,69 @@ async function updatePerson(personId, personInfo) {
}
}
// Define a whitelist of allowed fields to prevent SQL injection
const allowedSearchFields = [
"gradYear",
"degreeYear",
"city",
"stateId",
"contactNumber",
"firstName",
"lastName",
];
async function search(filters) {
//begin by selecting all people
let baseQuery = `
SELECT p.*, pd.degreeYear, a.city, a.stateId, c.contactNumber
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 peopleContact c ON p.peopleId = c.peopleId
WHERE 1=1
`;
// add filters to the base query in order to keep refining the set of people returned
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
if (!allowedSearchFields.includes(field)) {
throw new Error(`Invalid field specified: ${field}`);
}
// Add condition and value to the query
baseQuery += ` AND ${field} = ?`;
queryValues.push(value);
}
try {
const [results] = await pool.query(baseQuery, queryValues);
return results;
} catch (error) {
console.log("Error in repository w/ base query");
throw error;
}
}
module.exports = {
addPerson,
searchPeopleByName,
deletePersonById,
updatePerson,
getPersonById,
search,
};
......@@ -17,7 +17,7 @@ activityRouter.get("/login-data", async (req, res) => {
try {
const data = await viewLoginsTimerange(start, end);
res.status(200).json(data);
} catch {
} catch (error) {
console.log("error fetching login data: ", error);
res.status(500).json({ message: "Internal service error." });
}
......
......@@ -7,6 +7,7 @@ const {
deleteById,
updatePersonById,
getPersonFullDetails,
generalSearch,
} = require("../service/peopleService");
/*
Function to check that the POST request body in
......@@ -256,4 +257,24 @@ peopleRouter.get("/fullInfo/:id", async (req, res) => {
return res.status(500).json({ message: "Internal server error" });
}
});
peopleRouter.post("/search", async (req, res) => {
// filters is an array of { field, value } pairs
let filters = req.body.filters;
if (filters == null || !Array.isArray(filters) || filters.length == 0) {
res.status(400).json({ message: "Missing filters." });
return;
}
try {
// get the list of people who match the given filters
const results = await generalSearch(filters);
res.status(200).json({ people: results });
} catch (error) {
console.log("Error in search route: ", error);
res.status(500).json({ message: `Internal service error: ${error}` });
}
});
module.exports = peopleRouter;
......@@ -23,6 +23,7 @@ const {
deletePersonById,
updatePerson,
getPersonById,
search,
} = require("../repository/peopleRepository");
const {
addPeopleInvolvement,
......@@ -344,10 +345,15 @@ async function updateInvolvementsForPerson(personId, involevements) {
}
}
async function generalSearch(filters) {
return search(filters);
}
module.exports = {
createPerson,
findByName,
deleteById,
updatePersonById,
getPersonFullDetails,
generalSearch,
};
services:
test-backend:
build: ./backend
environment:
- NODE_ENV=test
- DB_HOST=test-mysql
- DB_USER=test_user
- DB_PASSWORD=test_password
- DB_NAME=corps_directory_test_db
depends_on:
test-mysql:
condition: service_healthy
command: ["sh", "-c", "npm test"]
test-mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: test_root_password
MYSQL_DATABASE: corps_directory_test_db
MYSQL_USER: test_user
MYSQL_PASSWORD: test_password
ports:
- "3307:3306" # Map to a different port to avoid conflicts
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
......@@ -8,7 +8,8 @@ services:
volumes:
- ./backend:/app
depends_on:
- mysql
mysql:
condition: service_healthy
environment:
DB_HOST: mysql
DB_USER: corps_directory_dev
......@@ -36,6 +37,11 @@ services:
MYSQL_PASSWORD: corps_db_password
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
volumes:
db_data:
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