Live Site | Project Wiki | Report Bug
Recipeople is a website where users can sign up, post recipes, and curate collections of recipes published by other users. This website was designed as a Week 13 midterm project as part of App Academy's 24-week Full Stack Software Engineering Bootcamp.
Javascript | Node.js | Express | Pug | Sequelize | PostgreSQL | Bcrypt.js
- Clone the project repository
git clone https://github.com/andrewscohen/2020.11.badReads.git
- Install dependencies
npm install
- Create a local .env file modeled after the .env.example file in the root directory
PORT=8080
DB_USERNAME=recipeople_admin
DB_PASSWORD=your_unique_password
DB_DATABASE=recipeople
DB_HOST=localhost
SESSION_SECRET=your_session_secret
- Migrate and seed the database
npx sequelize-cli db:create
npx sequelize-cli db:migrate
npx sequelize-cli db:seed:all
- Run the project with a starting script
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 MVP Feature List section of the project wiki.
New users can register for an account by entering a unique email address, a username, and a secure password.
Existing users can log in to their account by submitting their credentials via the login form.
Logged in users can select a profile picture, or link a custom image via url
Users may log out of their account by clicking the LOGOUT button on the sitewide header.
Logged-in users can create a new recipe with a name, description, an optional image, a list of ingredients, and cooking instructions.
When a new recipe is added, a new page is created for the recipe. All users can view the recipe information. Logged in users can add a review, add the recipe to a collection, or add a status. Recipe owners can edit or delete their own recipes.
When modifying a recipe, an "Edit" form will populate with the recipe's current information. A user may add, edit, or delete ingredients, and can edit the name, description, image, and instructions. If a user would like to delete the recipe, or discard their changes, they may do so from the edit form.
Users can create personalized collections in order to organize groups of curated recipes.
Users can add and remove recipes from their collections.
Users can edit and delete their collections.
Users can add a rating, an image, and a public review on any recipes, and view the reviews that others have posted.
Users can modify and delete their reviews.
A recipe's average rating is visible on the Homepage, the Recipes page, and each recipe's detail page as indicated by a scale ranging from 1 to 5 whisks.
A user can create, view, update, and remove a personalized status on any recipe in to indicating whether they "Will Cook" the recipe in the future, or whether they already "Cooked" the recipe.
After assigning a status to a recipe, the user can view the recipe on the Status section of their user page.The most recent publicly-visible recipes are visible both on the site Homepage and the Recipes page.
The Recipes page also includes a case-insensitive search where users can search for any recipe by a case-insensitive substring of the recipe title.
The full database schema is available to view as a linked chart on dbdiagram.io, or as a list of tables on the Database Schema page of the wiki.
Some tables, such as Roles are reserved for such as creation of an Administrator frontend, where certain users can have access to edit and delete content posted by other users. The Tags table is reserved for creation of recipe tags that a recipe creator can assign to their recipes, or for other users to include in search criteria. Tag categories are intended for the future development of tag grouping, such a Cuisine tag category that may have tags such as Japanese, French, or Mediterranean, which can useful to organize tags as the number and diversity of recipes continues to grow.
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 groups of recipes, accessing recipe details, and viewing profile page where users can manage their personal collections.
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.
For our first project, we overestimated the number of features that we could implement within a five-day sprint. A fully interactive front-end reflecting the database schema that we designed on the first day could not be fully implemented within the time constraints, so there were many conversations where we decided what must be prioritized and what level of completeness is satisfactory for an MVP.
As the sprint progressed, we began favoring the development of fewer features that work well 99% of the time over many features that work well 70% of the time.
Very similar code was written by multiple users as we worked on similar features independently and simultaneously. As a result, refactoring to add modularity would greatly improve readability and maintainability of the site.
The most challenging back-end task was managing the Quantity, Measurement, and Ingredient (QMI) bundles for entries on a recipe. (i.e. 2 cups flour). For the frontend Create Recipe and Edit Recipe forms, each QMI has an id in numeric order. If a user were to delete any QMI from a recipe, all ids must be reset to maintain the order.
if (validatorErrors.isEmpty()) {
let imageId
if (imageURL) {
const newImage = await Image.create({ url: imageURL })
imageId = newImage.dataValues.id ;
} else {
const randomRecipeImage = () => {
return (Math.floor(Math.random()*9) + 1).toString()
}
imageId = randomRecipeImage();
}
const recipe = Recipe.build({ name, description, steps, userId, imageId });
await recipe.save();
//checking each ingredients
for (let i = 0; i < qmiCount; i++){
const ingredientName = req.body[`ingredient-${i+1}`];
const ingredient = await Ingredient.findOne({ where: { name: ingredientName } })
if (!ingredient){
//if ingredient does not exit
const currentIngredient = await Ingredient.create({ name: ingredientName });
req.body.ingredientId = currentIngredient.id;
} else {
//else find ingredient id
req.body.ingredientId = ingredient.id;
}
//build recipeIngredient join table
const recipeId = recipe.dataValues.id;
await RecipeIngredient.create({
recipeId,
ingredientId: req.body.ingredientId,
quantity: req.body[`quantity-${i+1}`],
measurementId: req.body[`measurement-${i+1}`]
})
For the backend, we need to pass the QMI list data to the route, loop through each QMI and save it in the database. Since our QMI data is not stored directly in the Recipe Table, it must be recreated after the recipe has been added in the database.
function resetId () {
const qmiCount = qmiList.childElementCount
qmiCountInput.setAttribute('value', qmiList.childElementCount )
for (let i = 0; i < qmiCount; i++ ){
//updating quantity label for attribute
qmiList.childNodes[i].childNodes[0].childNodes[0].setAttribute('for', `quantity-${i + 1}`);
//updating quantity label input id and name
qmiList.childNodes[i].childNodes[0].childNodes[1].id = `quantity-${i + 1}`;
qmiList.childNodes[i].childNodes[0].childNodes[1].setAttribute('name', `quantity-${i + 1}`);
//updating measurement label for attribute
qmiList.childNodes[i].childNodes[1].childNodes[0].setAttribute('for', `measurement-${i + 1}`);
//updating measurement label input id and name
qmiList.childNodes[i].childNodes[1].childNodes[1].id = `measurement-${i + 1}`;
qmiList.childNodes[i].childNodes[1].childNodes[1].setAttribute('name', `measurement-${i + 1}`);
//updating ingredients label for attribute
qmiList.childNodes[i].childNodes[2].childNodes[0].setAttribute('for', `ingredient-${i + 1}`);
//updating ingredients label input id and name
qmiList.childNodes[i].childNodes[2].childNodes[1].id = `ingredient-${i + 1}`;
qmiList.childNodes[i].childNodes[2].childNodes[1].setAttribute('name', `ingredient-${i + 1}`);
}
return;
}
In order for a user to edit, delete, and view their collections, buttons added via DOM manipulation must be logically linked to the correct collection. The pug template only showed the collection name but we needed the actual collectionId
to edit, delete or view it. To resolve this issue, we assigned a collectionId
as an attribute on the button so that it could be retrieved and parsed when defining event targets.
const idArr = target.id.split("-");
const recipeId = idArr[2];
As we further develop our programming skills, we will continue to improve the maintainability and user experience of Recipeople.
- Recipe badges are visible on many parts of the website, including the
Homepage, the Recipe page, and User pages. However, these badges
contain limited information, and users must click to navigate to the
Recipe Detail page in order to add it to a collection, change its
status, or add a review. In the future, the recipe badges will be
re-designed to allow users more functionality without requiring an
additional click to the Recipe Detail page.
- 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.
- In order for an site administrator to moderate content on the
website, all modifications must be done via SQL queries to a
centralized database. To mitigate this, a new administrator role
will be added that will enable that user to edit or delete any recipe
or review posted on the website.
- Currently, all ingredients are stored as rows on a database. If a
user types in a new ingredient for a recipe that is not already in
the database, a new ingredient is created. However, the addition of
new ingredients does not currently account for spelling or
capitalization variations. For example, Tomato, tomatoes, and tomato
would all be stored in the database as separate ingredients. In order
to support future functionality, ingredient names may undergo a
pattern-matching normalization process or third-party food-name API
validation to prevent duplicate entries within our database.
- Currently, the description and instructions for each ingredient are
stored as strings. Adding additional fields such as serving size,
cook time, cook temperature, and individual recipe steps can
compartmentalize information for users, and allow for improved search
functionality.
- The database is already configured to support the addition of "Tags"
for each recipe. A user will be able to add, view, edit, and delete
tags from recipes that they have submitted, and a user can use tags
as search criteria to find new recipes. Once an admin role is
implemented, an admin will be able to add, edit and delete tags.
- Users will be able to generate a shopping list from a collection that
aggregates the ingredients from all recipes and combines them into an
organized list. Normalization of Ingredient Names must be completed
before implementation of this feature.
Aletheia Kim | Github
Denise Li | Github | LinkedIn
Mei Shih | Github | LinkedIn
Cameron Whiteside | Github | LinkedIn