These are the major features we’re looking for at each code review, in addition to your other progress.
If a route has a (*) next to it, it means that it should require a logged in user to be present, if a route has a (**) next to it, the logged in user should be the owner of the modified object. If a route has (*admin) next to it, the logged in user must be an admin user (user.isAdmin === true). Any (**) route should also be accessible by any (*admin) user.
Using the same notation above. If a user is authorized, show the component. If the user is not authorized, either (1) display an error message or (2) redirect the user to a different route/component (any component that would be appropriate)
Details
- Create a
productstable definition with the following information:- id - serial; primary key
- name - not null
- description - not null
- price - not null
- imageURL - with a default value
- inStock - not null; default value false
- category - not null
- Create a
userstable definition with the following information:- id - serial; primary key
- firstName - not null
- lastName - not null
- email - unique; not null; must be a valid email format
- imageURL - with a default value
- username - unique; not null
- password - unique; not null
- "isAdmin" - not null; default value false
- Create an
orderstable definition with the following information:- id - serial; primary key
- status - default value 'created'. (can be created, cancelled, completed) - also optionally, processing
- NOTE: An order with an
orders.status = 'created'is synonymous with a "cart"
- NOTE: An order with an
- "userId" - references users(id)
- "datePlaced" - date
- Create an
order_productstable definition with the following information:- id - serial; primary key
- "productId" - references products(id)
- "orderId" - references orders(id)
- price - not null
- quantity - not null; default value 0
-
getProductByIdgetProductById(id)- return the product
-
getAllProducts- select and return an array of all products
-
createProductcreateProduct(product)- return the new product
-
GET /products- Send back a list of all products in the database
-
GET /product/:productId- Look up a product by id and send it back
- Write a component for a single product
- Display the single product component when the url matches
/product/:productId - Add links to the navbar that can be used to navigate to the
/product/:productIdroute - Write a component to display a list of all products (you might be able to reuse the single product component)
- Display the all-products component when the url matches
/products - Add links to the navbar that can be used to navigate to the
/productsroute
Congrats! You have completed your first vertical slice! Make sure to commit -m "feat(products): Add All Products and Single Products" before moving on!
Details
-
createUsercreateUser({ username, password })- make sure to hash the password before storing it to the database
-
getUsergetUser({ username, password })- this should be able to verify the password against the hashed password
-
getAllUsersgetAllUsers()- select all users. Return the user objects.
- do NOT return the passwords
-
getUserByIdgetUserById(id)- select a user using the user's ID. Return the user object.
- do NOT return the password
-
getUserByUsernamegetUserByUsername(username)- select a user using the user's username. Return the user object.
-
POST /users/register- Create a new user. Require
usernameandpassword, and hash password before saving user to DB. Require all passwords to be at least 8 characters long. - Throw errors for duplicate
username, or password-too-short.
- Create a new user. Require
-
POST /users/login- Log in the user. Require
usernameandpassword, and verify that plaintext login password matches the saved hashed password before returning a JSON Web Token. - Keep the
idandusernamein the token.
- Log in the user. Require
-
GET /users/me(*)- Send back the logged-in user's data if a valid token is supplied in the header.
- Write a component for login
- Write a component for register
- Display the login/register components when the user is not logged in (either when url matches
/account/loginor/account/registerOR as a modal, or just at the top of the page). - Add links to the navbar that can be used to navigate to the
/account/loginor/account/registercomponents - Add a logout button that removes the token/user data from state and localstorage.
- Write a component for a single user's data
- Display the single user component when the url matches
/account(*)
Nice! You have completed another vertical slice! Make sure to commit -m "feat(users): Login/Register" before moving on!
-
getOrderByIdgetOrderById(id)- return the order, include the order's products
-
getAllOrders- select and return an array of orders, include their products
-
getOrdersByUsergetOrdersByUser({ username })- select and return an array of orders made by user, include their products
-
getOrdersByProductgetOrdersByProduct({ id })- select and return an array of orders which have a specific
productIdin theirorder_productsjoin, include their products
-
getCartByUsergetCartByUser({ id })orgetCartByUser(user)- select one user's order (look up by
orders."userId") - ...an order that that has status = created
- return the order, include the order's products
-
createOrdercreateOrder({ status, userId })- create and return the new order
-
GET /orders(*admin)- Return a list of orders, include the products with them
-
GET /orders/cart(*)- Return the current user's order with
status='created'(synonymous to a 'cart'). Use database adaptergetPendingOrderByUser
- Return the current user's order with
-
POST /orders(*)- Create a new order. Should initially be status = created.
-
GET /users/:userId/orders(**)- Get a list of orders for a particular user.
- Write a component for a single order's data
- Display the single order component when the url matches
/orders/:orderId(**) - Display the cart (using the single order component with the current user's in-progress order. Use the api call
GET /orders/cart) when the url matches/cart(*) - Add "view cart" button to the navbar that can be used to navigate to the
/cartroute(*) - Add Cart persistence
- for authenticated (logged in) users, using the database.
- for unauthenticated (guest) using localStorage.
- bonus: add ability to "merge" the localStorage cart with the database cart once a user logs in.
Details
-
getOrderProductByIdgetOrderProductById(id)- return the
order_products
-
addProductToOrderaddProductToOrder({ orderId, productId, price, quantity })- if the
productIdis NOT on theorderyet, create a neworder_products - update the
order_productsquantity (add passed-in quantity to the currentorder_productsquantity) - update the
order_productsprice - return the
order_products
-
updateOrderProductupdateOrderProduct({ id, price, quantity })- Find the order with
idequal to the passed inid - Update the
priceorquantityas necessary
-
destroyOrderProductdestroyOrderProduct(id)- remove the single identified
order_productsfrom database
-
POST /orders/:orderId/products(**)- Add a single product to an order (using
order_products). Prevent duplication on("orderId", "productId")pair. If product already exists on order, increment quantity and update price.
- Add a single product to an order (using
-
PATCH /order_products/:orderProductId(**)- Update the quantity or price on the order product
-
DELETE /order_products/:orderProductId(**)- Remove a product from a order, use hard delete
- For each product NOT in cart
- Create add-to-cart button
- Up to you if you want this to increment previously-existing product quantity.
- Up to you if you want this to increment previously-existing product quantity.
- Create add-to-cart button
- For each product CURRENTLY in cart
- Create remove-from-cart button
- Create edit quantity drop-down
-
updateOrderupdateOrder({ id, status, userId })- Find the order with
idequal to the passed inid - Don't update the order
id, but do update thestatusand/oruserId, as necessary - Return the updated order
-
completeOrdercompleteOrder({ id })- Find the order with
idequal to the passed inid - Only update the
statustocompleted - Return the updated order
-
cancelOrdercancelOrder(id)- Update the order's status to
cancelled
-
PATCH /orders/:orderId(**)- Update an order, notably change status
-
DELETE /orders/:orderId(**)- Update the order's status to
cancelled
- Update the order's status to
- Write a component to display a checkout experience
- Display user data (perhaps reusing the single-user component)
- Display cart data (perhaps reusing the single-order component)
- Create a "Complete Order" button
- Updates the order status to completed
- Credit Card integration is in a future tier
- Display a success message, confirming the order status is now completed.
- Create a "Cancel Order" button
- Updates the order status to cancelled
- Display a success message, confirming the order is cancelled.
- Optionally, redirect user to another route (home?)
- Display the checkout component when the url matches
/cart/checkout(*)
- Integrate Stripe
- For client side, use Stripe's prebuilt Checkout form, ideally with the "Custom" strategy. We recommend react-stripe-checkout in this case. Build a custom form and communicate with Stripe & your server via Stripe.js.
- For server side, use the
stripenpm library (API docs here, tutorial here) to accept tokens from your front-end app and send charges via the Stripe API.
Details
-
destroyProductdestroyProduct({ id })- hard delete a product.
- make sure to delete all the
order_productswhose product is the one being deleted. - make sure the
ordersfor theorder_productsbeing deleted do not have a status = completed
-
updateProductupdateProduct(product)- don't try to update the
id - do update the other fields (name, description, etc)
- return the updated product
-
updateUserupdateUser(user)- don't try to update the
id - do update the other fields (name, email, "isAdmin" etc)
- return the updated user
-
GET /users(*admin)- Send back all users
-
POST /products(*admin)Only admins can create a new product -
DELETE /products/:productId(*admin)Only admins can delete a product -
PATCH /products/:productId(*admin)Only admins can update a product -
GET /products/:productId/orders(*admin)Get a list of all orders which have that product in them -
PATCH /users/:userId(*admin)Only admins can update a user
- Admin - Users
- Write a component to display a list of all users
- Display the all-users component when the url matches
/users(*admin) - Add links to the navbar that can be used to navigate to the
/userscomponent(*admin)
- Display the all-users component when the url matches
- Write a component to display a single user
- Add a form to update the user in the component (notably, add ability to change "isAdmin" on any user)
- Display the single-user component when the url matches
/users/:userId(*admin) - Make a username clickable in the users list that can be used to navigate to the
/users/:userIdcomponent(*admin)
- Write a form component to add a single user
- Display the add-user component when the url matches
/users/add(*admin)
- Display the add-user component when the url matches
- Write a component to display a list of all users
- Admin - Orders - Display the multiple orders component (already written) when the url matches
/orders, this time showing ALL users' orders(*admin) - Admin - Products
- Write and display a component to create a new product
- Write and display a component to edit a product
- Add "delete product" button to products list
- Create a
reviewstable definition with the following information:- id - serial; primary key
- title - not null
- content - not null - must be at least x characters and no longer than y characters
- stars - integer not null (0 through 5)
- "userId" - references users(id)
- "productId" - references products(id)
- Ability to create/update/delete reviews for authenticated users
- For each product, in list view
- Display number ofreviews
- Display average stars
- For each product, in single view
- Display list of reviews, each showing title, content, user, stars
- Loading messages for all fetches
- Paginate product data
- Logged In Users - Orders
- Write a component to display all orders pertaining to the user (order history)
- Display the all-orders component when the url matches
/orders(*) - Email Confirmation
- Filter products with a search field
- Admin
- Trigger password reset for user