Aurora is a clone of Quora with an added "ranking" of questions by expertise level. The expertise level ranking allows the questions to be easily accessed by novice, intermediate and expert levels of difficulty.
- Users can log in or sign up to access some functionality the site.
- A logged in user has the ability to post questions with both a topic and expertise level.
- Posted questions can be answered, and answers can be commented on.
- The search bar can find questions using a search term and SQL queries.
- Search results can be sorted by topic or expertise level.
Try the site live: Here | Check out our documentation
- Clone the repo
- Use the command
npm install
to install all dependencies - Make a copy of the .env.example file and edit to match local db configuration
- Create the database and user in psql
- Run all migrations with
npx dotenv sequelize db:migrate
- Seed all data with
npx dotenv sequelize db:seed:all
- Run all migrations with
- Use the start script
npm start
to run the server
Sequelize was used to store and easily manage data with its amazing data models and migrations.
Express JS was our framework and it reduced a ton of boiler plate code, freeing us to implement more features.
npm was our software registry, and within it we installed many packages; some notable examples include:
- nodemon,
- morgan,
- express-validator,
- and more
Pug is an excellent view engine we used to quickly and beautifully display HTML on our pages.
Heroku is the web hosting app of our choice that allowed us to run our app on the cloud!
Honorable Mentions are the developement tools that made life much more enjoyable!
- Postman made route testing very easy and fun!
- Postbird, its wonderful GUI made all the difference!
The first big feature we tackled is the searching algorithm, which populates the page with results containing either a question's title or its message. ``` const { searchTerm } = req.body;
const results = await Question.findAll({
where: {
[Op.or]: [{
title: {
[Op.iLike]: '%' + searchTerm + '%'
}
},
{
message: {
[Op.iLike]: '%' + searchTerm + '%'
}
}
]
},
include:
[Topic, Expertise, User]
,
order: [
['createdAt', 'DESC']
]
})
```
How it was done
-
We started by extracting the search term from the POST request.
const { searchTerm } = req.body;
-
Then we queried the database for questions where either the question title or the question message (case insensitive) matched the search term.
const results = await Question.findAll({ where: { [Op.or]: [{ title: { [Op.iLike]: '%' + searchTerm + '%' } }, { message: { [Op.iLike]: '%' + searchTerm + '%' } } ] }, })
-
We included each question's topic, expertise level, and user, and ordered the results so that the most recent question appears first.
include: [Topic, Expertise, User] , order: [ ['createdAt', 'DESC'] ]
The other big feature that we implemented was a sorting algorithm on our search results.
```
document.addEventListener('DOMContentLoaded', ev => {
localStorage.clear();
})
const filterText = id => {
let select = document.getElementById(id);
let option = select.value;
if (id === 'expertiseSelect') {
localStorage.setItem('expertiseSort', option);
} else {
localStorage.setItem('topicSort', option);
}
let localTopic = localStorage.getItem('topicSort');
let localExpertise = localStorage.getItem('expertiseSort');
if (!localTopic) localStorage.setItem('topicSort', 'All');
if (!localExpertise) localStorage.setItem('expertiseSort', 'All');
localTopic = localStorage.getItem('topicSort');
localExpertise = localStorage.getItem('expertiseSort');
let divs = document.querySelectorAll(".result");
divs.forEach((div) => {
displayCombinator(localTopic, localExpertise, div);
});
};
function displayCombinator(topic, expertise, div) {
if (topic === 'All' && expertise === 'All') {
div.style.display = 'flex';
} else {
if (topic === 'All' && expertise !== 'All') {
if (div.classList.contains(expertise)) {
div.style.display = "flex";
} else {
div.style.display = "none";
}
} else if (expertise === 'All' && topic !== 'All') {
if (div.classList.contains(topic)) {
div.style.display = "flex";
} else {
div.style.display = "none";
}
} else if (div.classList.contains(topic) && div.classList.contains(expertise)) {
div.style.display = 'flex';
} else {
div.style.display = 'none';
}
}
}
```
How it was done
-
We started by populating the dropdown menus for Topic and Expertise Level on the search results page to reflect the topics and expertise levels of the result questions:
let topicIds = [] let expertiseIds = []; results.forEach((result) => { if (!topicIds.includes(result.Topic.id)) { topicIds.push(result.Topic.id) } if (!expertiseIds.includes(result.Expertise.id)) { expertiseIds.push(result.Expertise.id) } }) const topics = await Topic.findAll({ where: { id: { [Op.in]: topicIds } }}) const expertises = await Expertise.findAll({ where: { id: { [Op.in]: expertiseIds } }})
-
Then we cleared local storage when the search results page was loaded in order to make space for our sorting function variables:
document.addEventListener('DOMContentLoaded', ev => { localStorage.clear(); })
-
We rendered the dropdown select menus with the content from our query in step 1, then set up an event listener to save the selected value to local storage:
div.sort_bar select#topicSelect(name="topicId" class="form__dropdown" onchange="filterText('topicSelect')") option(value="" disabled selected hidden) Topic option(value="All") All each topic in topics option(value=topic.id class="form__dropdown--option")=topic.name select#expertiseSelect(name="expertiseId" class="form__dropdown" onchange="filterText('expertiseSelect')") option(value="" disabled selected hidden) Expertise Level option(value="All") All each expertise in expertises option(value=expertise.description class="form__dropdown--option")=expertise.description
const filterText = id => { let select = document.getElementById(id); let option = select.value; if (id === 'expertiseSelect') { localStorage.setItem('expertiseSort', option); } else { localStorage.setItem('topicSort', option); } let localTopic = localStorage.getItem('topicSort'); let localExpertise = localStorage.getItem('expertiseSort'); if (!localTopic) localStorage.setItem('topicSort', 'All'); if (!localExpertise) localStorage.setItem('expertiseSort', 'All'); localTopic = localStorage.getItem('topicSort'); localExpertise = localStorage.getItem('expertiseSort'); };
-
We called a helper function on each of our result divs to filter results based on the variables in local storage and render them dynamically:
let divs = document.querySelectorAll(".result"); divs.forEach((div) => { displayCombinator(localTopic, localExpertise, div);
function displayCombinator(topic, expertise, div) { if (topic === 'All' && expertise === 'All') { div.style.display = 'flex'; } else { if (topic === 'All' && expertise !== 'All') { if (div.classList.contains(expertise)) { div.style.display = "flex"; } else { div.style.display = "none"; } } else if (expertise === 'All' && topic !== 'All') { if (div.classList.contains(topic)) { div.style.display = "flex"; } else { div.style.display = "none"; } } else if (div.classList.contains(topic) && div.classList.contains(expertise)) { div.style.display = 'flex'; } else { div.style.display = 'none'; } } }
We faced a few challenges while we were building Aurora:
-
We encountered a merge issue with one of our features that took a long time to sort out. Make sure you stay up to date with
main
, folks! -
It took us a long time to figure out what the best way to sort our search results was. Thankfully, we were able to reference some other people's strategies and come up with something that fit our project.
Antonio A. | Matt K. | Quintin H. | Max L. |
@vantanova | @cellomatt | @QuintinHull | @lettemax |
Thank you for reading our project README ❤️