Live Site | Project Wiki | Report Bug
Study Buddy is a website where users can create, study, and share decks of cards for studying computer science topics. This website was designed as a Week 20 project as part of App Academy's 24-week Full Stack Software Engineering Bootcamp.
Javascript | Node.js | Flask | React | Redux | SQLAlchemy | PostgreSQL
- Clone the project repository
git clone https://github.com/DanielLaV/study_buddy.git
- Install dependencies
pipenv install --dev -r dev-requirements.txt && pipenv install -r requirements.txt
- Create a local .env file modeled after the .env.example file in the root directory
FLASK_APP=app
FLASK_ENV=development
SECRET_KEY=<<YOUR-SECRET_KEY>>
DATABASE_URL=postgresql://study_buddy_dev:<<PASSWORD>>@localhost/study_buddy_db
-
Set up your PostgreSQL user, password, and database. Make sure that it matches your .env file!
-
Access your
pipenv shell, migrate your database, seed your database, and run your flask app with the following commands:
pipenv shell
flask db upgrade
flask seed all
flask run
- To run the React App,
cdinto thereact-appdirectory, installreact-app, and then start React:
cd react-app
npm install
npm start
Full user stories for the initial development phase are available on the User Stories section of the project wiki. A feature list for the initial development phase is available on the Feature List section of the project wiki.
New users can register for an account by entering a unique username, email and a secure password.
Existing users can log in to their account by submitting their credentials via the login form.
Logged in users can edit their profile biography
Users may log out of their account by clicking the LOGOUT button on the site-wide header.
Logged-in users can create a new deck with a title and a description.
All users can view the deck information. Deck owners can only edit or delete their own decks.
When modifying a deck, an Edit form will populate with the deck's current information. Users will be able to edit the deck title and description.
A user may add new cards to their deck.
Users can edit or remove cards from their deck.
Users can mark any deck as to-be-studied and it will be added to their to-study collection.
Users can add/remove tags to their decks.
Each deck will have its tags visible.
Users can click on the tags to do a search of all decks with that tag.
With the hundreds of decks to view – how will you remember which ones you want to study? That's where the Study List comes in! Users can dynamically add or remove any deck to their Study List with a click of a button wherever a deck is displayed. They may view their Study List at any time by using the link in the navigation bar or on their home page. The user can click on any deck in their Study List and will be directed to the deck's page to view all of the cards in the deck. The Study List is able to display all of the user's decks on their Study List by making a query in the database for the user's id on the UserStudyDeck model and then joins the Deck model to return all of the matching results.
The full database schema is available to view on dbdiagram.io, or as a list of tables on the Database Schema page of the wiki.
All frontend routes are covered in detail on the Fronted Routes section of our project wiki. Frontend routes were designed to enable users access to basic functionality such as registration, authentication, viewing decks, accessing cards, searching by tags, and viewing their profile page where users can manage their decks.
All frontend routes are covered in detail on the API Routes section of our project wiki. API routes were designed for users to interact with a page without being redirected.
The search function searches for a query in the following resources and their columns: deck title, deck description, card front, card back. The search route needs to query the database for those four columns, return the data in a way that's easily accessible by the frontend, and, when there are no matches, return an indication that no entries in the database match the search query. Thus, the business logic for the search function requires try ... except blocks, concatenate matches from the Deck resource with each other, concatenate matches from the Card resource with each other, check if either of those resources exist, check for the existence of results from either resource, and return an appropriate response from the backend. The reponse from the backend should not cause issues with the searchReducer on the frontend if there are search results in one resource but not the other.
@search_routes.route('/<string:query>', methods=['GET'])
def main(query):
"""
'GET' searches the Card and Deck database .
The function returns all tags associated with that deck.
"""
if 16 < len(query) < 2:
return {"errors": "Query must be between 2 and 16 characters long"}, 401
else:
# deck results: querying title and description
try:
deck_title_results = Deck.query.filter(Deck.title.ilike(f"%{query}%")).all()
deck_title_results = [deck.to_dict() for deck in deck_title_results]
except:
pass
try:
deck_desc_results = Deck.query.filter(Deck.description.ilike(f"%{query}%")).all()
deck_desc_results = [deck.to_dict() for deck in deck_desc_results]
except:
pass
# card results: querying front and back
try:
card_front_results = Card.query.filter(Card.front.ilike(f"%{query}%")).all()
card_front_results = [card.to_dict() for card in card_front_results]
except:
pass
try:
card_back_results = Card.query.filter(Card.back.ilike(f"%{query}%")).all()
card_back_results = [card.to_dict() for card in card_back_results]
except:
pass
all_deck_results = deck_title_results + deck_desc_results
all_card_results = card_front_results + card_back_results
if (all_deck_results or all_card_results):
return {"decks": all_deck_results, "cards": all_card_results}, 200
else:
return {"errors": ["No results found!"]}, 401The frontend then needs to parse the data incoming from the backend. If there are results from either resource, it needs to update the store appropriately:
export const getResults = (query) => async (dispatch) => {
const response = await fetch(`/api/search/${query}`, {
headers: { "Content-Type": "application/json" }
});
const results = await response.json();
console.log("results", results)
if (response.ok) {
dispatch(load(results))
}
return results
}
const searchReducer = (state = {}, action) => {
switch (action.type) {
case LOAD: {
const decks = {}
action.results.decks.forEach((deck) => {
decks[deck.id] = deck
})
const cards = {}
action.results.cards.forEach((card) => {
cards[card.id] = card
})
return {decks, cards}
}
default: return state;
}
}If there aren't any results, it needs to receive and display the message from the backend that no results were found.
if (query) {
return dispatch(getResults(query.toLowerCase())).then(
(response) => {
if (response.errors) {
setHasResults(false)
setErrors(response.errors)
return
}});
}The website is currently functional on all screen sizes, but is styled for screens greater than 900 px in width. New smaller-scale layouts will be implemented so that the user experience on mobile or tablet devices is comparable to the desktop user experience.
Currently, all tags are stored as rows on a database. If a user types in a new tag for a deck that is not already in the database, a new tag is created. However, the addition of new tags does not currently account for spelling or capitalization variations. For example, JavaScript, Javascript, and JS would all be stored in the database as separate tags. In order to support future functionality, tag names may undergo a pattern-matching normalization process or third-party name API validation to prevent duplicate entries within our database.
Sophia Bui | Github | LinkedIn
Kreston Caldwell-McMurrin | Github | LinkedIn
Daniel LaVergne | Github | LinkedIn
Denise Li | Github | LinkedIn

