-
Notifications
You must be signed in to change notification settings - Fork 49
Patrick-HongYun-Project3-Backend #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
31e76d8
97a3112
8fa385b
a7609bd
a53a744
38d5b20
65046b7
6be21fb
748c37d
b947cd6
9c61c4b
8899c38
7aca78b
e9fd0f9
6b27096
8b895b0
563b07b
a10a89c
a383643
d993e41
703cb40
b32249c
d31f33f
dde662d
5738602
cf2cd38
f0cab7a
0a026fb
d177a9a
ac7b536
a25c20b
3a0497a
72859c6
c3c6ddf
b27630d
93d805f
f5289d2
bf7eea6
8f97736
d2e81be
b6656eb
a4ba424
de096c2
3253889
40852f4
b37e134
df929f8
390bb10
c45b686
c02bccb
02f8b67
34d7477
afd461e
9ecc8b8
fddfe39
3b925b9
a06a7b1
dd88dfc
8c36f7c
d5b1f4f
3cbb33d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| node_modules/ | ||
| .env | ||
| dist/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,4 @@ | ||
| node_modules/ | ||
| node_modules/ | ||
| .env | ||
| dist/ | ||
| fly.toml |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| const path = require("path"); | ||
|
|
||
| module.exports = { | ||
| config: path.resolve("dist", "config", "database.js"), | ||
| "models-path": path.resolve("dist", "db", "models"), | ||
| "seeders-path": path.resolve("dist", "db", "seeders"), | ||
| "migrations-path": path.resolve("dist", "db", "migrations"), | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| FROM debian:bullseye as builder | ||
|
|
||
| ENV PATH=/usr/local/node/bin:$PATH | ||
| ARG NODE_VERSION=16.15.1 | ||
|
|
||
| RUN apt-get update; apt install -y curl python-is-python3 pkg-config build-essential && \ | ||
| curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \ | ||
| /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \ | ||
| rm -rf /tmp/node-build-master | ||
|
|
||
| RUN mkdir /app | ||
| WORKDIR /app | ||
|
|
||
| COPY . . | ||
|
|
||
| RUN npm install | ||
| RUN npm run build | ||
|
|
||
| FROM debian:bullseye-slim | ||
|
|
||
| LABEL fly_launch_runtime="nodejs" | ||
|
|
||
| COPY --from=builder /usr/local/node /usr/local/node | ||
| COPY --from=builder /app /app | ||
| COPY release.sh /release.sh | ||
|
|
||
| WORKDIR /app | ||
| ENV NODE_ENV production | ||
| ENV PATH /usr/local/node/bin:$PATH | ||
|
|
||
| # Debug: List contents of app directory | ||
| RUN ls -al /app | ||
|
|
||
| CMD [ "npm", "run", "start" ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| # Rocket Academy Coding Bootcamp: Project 3 Backend | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could really provide a bit more info here guys. How about what the BE really does, and what about setting up the local db for dev environment, including environment variables necessary etc? |
||
|
|
||
| Run `npm i` to install NPM packages, then `npm start` to start the Express server. | ||
| The backend for Stock Trackr, a project by Patrick Kok and Hong Yun | ||
|
|
||
| Run `npm i` to install NPM packages, then `npm run build` followed by `npm run start` to start the Express server. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| require("dotenv").config(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need this file if we got the .ts file? |
||
|
|
||
| module.exports = { | ||
| development: { | ||
| username: process.env.DB_USERNAME, | ||
| password: process.env.DB_PASSWORD, | ||
| database: process.env.DB_NAME, | ||
| host: process.env.DB_HOST, | ||
| dialect: process.env.DB_DIALECT, | ||
| }, | ||
| production: { | ||
| username: process.env.USERNAME, | ||
| password: process.env.PASSWORD, | ||
| database: process.env.DATABASE, | ||
| host: process.env.HOST, | ||
| dialect: process.env.DIALECT, | ||
| }, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| require("dotenv").config(); | ||
|
|
||
| module.exports = { | ||
| development: { | ||
| username: process.env.DB_USERNAME, | ||
| password: process.env.DB_PASSWORD, | ||
| database: process.env.DB_NAME, | ||
| host: process.env.DB_HOST, | ||
| dialect: process.env.DB_DIALECT, | ||
| }, | ||
| production: { | ||
| username: process.env.USERNAME, | ||
| password: process.env.PASSWORD, | ||
| database: process.env.DATABASE, | ||
| host: process.env.HOST, | ||
| dialect: process.env.DIALECT, | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import { Request, Response } from "express"; | ||
| import { Building, BuildingUser, User, Room, sequelize } from "../db/models"; | ||
|
|
||
| interface RoomAttributes { | ||
| name: string; | ||
| left: Float64Array; | ||
| top: Float64Array; | ||
| width: Float64Array; | ||
| height: Float64Array; | ||
| building_id: number; | ||
| } | ||
|
|
||
| export class BuildingsController { | ||
| async getBuildingsByUser(req: Request, res: Response) { | ||
| const userId = req.params.userId; | ||
| try { | ||
| const output = await Building.findAll({ | ||
| include: [ | ||
| { | ||
| model: Room, | ||
| }, | ||
| { model: BuildingUser, where: { user_id: userId } }, | ||
| ], | ||
| }); | ||
| return res.json(output); | ||
| } catch (err) { | ||
| return res.status(400).json({ error: true, msg: (err as Error).message }); | ||
| } | ||
| } | ||
| async getAllBuildings(req: Request, res: Response) { | ||
| try { | ||
| const output = await Building.findAll({ | ||
| include: [ | ||
| { | ||
| model: Room, | ||
| }, | ||
| ], | ||
| }); | ||
| return res.json(output); | ||
| } catch (err) { | ||
| return res.status(400).json({ error: true, msg: (err as Error).message }); | ||
| } | ||
| } | ||
|
|
||
| async AddNewBuilding(req: Request, res: Response) { | ||
| const building = req.body.building; | ||
| const rooms = req.body.rooms; | ||
| const userId = req.body.userId; | ||
| try { | ||
| const result = await sequelize.transaction(async (t) => { | ||
| const newBuilding = await Building.create(building, { | ||
| transaction: t, | ||
| }); | ||
| await Promise.all( | ||
| rooms.map(async (obj: RoomAttributes) => { | ||
| const newRoom = { ...obj }; | ||
| newRoom["building_id"] = newBuilding.id; | ||
| await Room.create(newRoom, { transaction: t }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should collect the rooms into an array, and then run |
||
| }) | ||
| ); | ||
| const newBuildingUser = { | ||
| building_id: newBuilding.id, | ||
| user_id: userId, | ||
| admin: true, | ||
| }; | ||
| await BuildingUser.create(newBuildingUser, { transaction: t }); | ||
| return newBuilding; | ||
| }); | ||
| return res.json(result); | ||
| } catch (err) { | ||
| return res.status(400).json({ error: true, msg: err }); | ||
| } | ||
| } | ||
|
|
||
| async AddBuildingUser(req: Request, res: Response) { | ||
| const { buildingId, newUserEmail, admin } = req.body; | ||
| try { | ||
| const result = await sequelize.transaction(async (t) => { | ||
| const user = await User.findOne({ where: { email: newUserEmail } }); | ||
| if (!user) { | ||
| throw new Error("User not found"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not just respond early here instead of leaving it up to the catch statement? I think catch makes most sense in unhandled error scenarios! |
||
| } else { | ||
| const newBuildingUser = { | ||
| building_id: buildingId, | ||
| user_id: user.id, | ||
| admin: admin, | ||
| }; | ||
| return await BuildingUser.create(newBuildingUser, { transaction: t }); | ||
| } | ||
| }); | ||
| return res.json(result); | ||
| } catch (err) { | ||
| if (err instanceof Error) { | ||
| return res.status(400).json({ error: true, msg: err.message }); | ||
| } else { | ||
| return res.status(400).json({ error: true, msg: err }); | ||
| } | ||
|
Comment on lines
+93
to
+97
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the difference here aside from the msg being an actual string in the first statement? |
||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| import { Request, Response } from "express"; | ||
| import { Item, Cart, CartLineItem, RoomItem } from "../db/models"; | ||
| import { sequelize } from "../db/models"; | ||
|
|
||
| export class CartController { | ||
| async retrieveActiveCart(req: Request, res: Response) { | ||
| const { userId } = req.params; | ||
|
|
||
| try { | ||
| const cart = await Cart.findOne({ | ||
| where: { user_id: userId, active: true }, | ||
| include: [ | ||
| { | ||
| model: CartLineItem, | ||
| include: [Item], // Include details of items in the cart | ||
| }, | ||
| ], | ||
| }); | ||
|
|
||
| if (!cart) { | ||
| return res | ||
| .status(404) | ||
| .json({ message: "No active cart found for the user." }); | ||
| } | ||
|
|
||
| res.json(cart); | ||
| } catch (err) { | ||
| console.error("Error retrieving cart:", err); | ||
| res.status(500).json({ message: "Failed to retrieve cart." }); | ||
| } | ||
| } | ||
|
|
||
| async addItemToCart(req: Request, res: Response) { | ||
| const { serialNum, quantity, expiryDate, roomSelect, userId } = req.body; | ||
|
|
||
| try { | ||
| // Step 1: Find the item by serial number to get the itemId | ||
| const item = await Item.findOne({ where: { serial_num: serialNum } }); | ||
| if (!item) { | ||
| return res | ||
| .status(404) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be for debate. Client provided a serial number, means it exists on the client-side. So this could mean, this could be bad request 400 as well. Or 204, content not found. There is no exact right or wrong here, just up for discussion. I recommend googling a bit about it to find out more and see more opinions on this :) |
||
| .json({ error: true, message: "Item not found." }); | ||
| } | ||
|
|
||
| // Step 2: Ensure the user has a cart | ||
| const [cart] = await Cart.findOrCreate({ | ||
| where: { user_id: userId, active: true }, | ||
| defaults: { user_id: userId, active: true }, | ||
| }); | ||
|
|
||
| // Step 3: Add or update the item in the cart | ||
| const [itemInCart, itemCreated] = await CartLineItem.findOrCreate({ | ||
| where: { | ||
| cart_id: cart.id, | ||
| item_id: item.id, // Use the item's id obtained in Step 1 | ||
| room_id: roomSelect, | ||
| }, | ||
| defaults: { | ||
| cart_id: cart.id, | ||
| item_id: item.id, | ||
| room_id: roomSelect, | ||
| quantity: quantity, | ||
| expiry_date: expiryDate, | ||
| }, | ||
| }); | ||
|
|
||
| if (!itemCreated) { | ||
| // The item exists in the cart, so update its quantity and possibly other details | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about if the database connection is lost or anything. Could we possibly create a new card, but not attach any item to it? In this case we might want to rollback the cart creation as well. Possibly a place to use a transaction here |
||
| itemInCart.quantity = quantity; | ||
| itemInCart.expiry_date = expiryDate; | ||
| // Update other fields as necessary | ||
| await itemInCart.save(); | ||
| } | ||
|
|
||
| res.json({ | ||
| success: true, | ||
| message: "Item added to cart successfully", | ||
| cartItem: itemInCart, | ||
| }); | ||
| } catch (err) { | ||
| console.error("Failed to add item to cart:", err); | ||
| res.status(500).json({ | ||
| success: false, | ||
| message: "Failed to add item to cart", | ||
| error: (err as Error).message, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| async deleteItemInCart(req: Request, res: Response) { | ||
| const { cartLineItemId } = req.body; | ||
|
|
||
| try { | ||
| // Step 1: Find the CartLineItem by PK | ||
| const cartLineItem = await CartLineItem.findByPk(cartLineItemId); | ||
| if (!cartLineItem) { | ||
| return res | ||
| .status(404) | ||
| .json({ error: true, message: "Cart item not found." }); | ||
| } | ||
| // Step 2: Delete the CartLineItem | ||
| await cartLineItem.destroy(); | ||
|
|
||
| res.json({ | ||
| success: true, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Success should be signified by a 200 response :) |
||
| message: "Item removed from cart successfully.", | ||
| }); | ||
| } catch (err) { | ||
| console.error("Failed to delete item from cart:", err); | ||
| res.status(500).json({ | ||
| success: false, | ||
| message: "Failed to delete item from cart", | ||
| error: (err as Error).message, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| async checkoutCart(req: Request, res: Response) { | ||
| const { userId } = req.body; | ||
|
|
||
| try { | ||
| // Sequelize managed transaction | ||
| await sequelize.transaction(async (transaction) => { | ||
| // Step 1: Retrieve the active cart for the user | ||
| const cart = await Cart.findOne({ | ||
| where: { user_id: userId, active: true }, | ||
| include: [{ model: CartLineItem, include: [Item] }], | ||
| transaction, | ||
| }); | ||
|
|
||
| if (!cart) { | ||
| throw new Error("Active cart not found."); | ||
| } | ||
|
|
||
| // Step 2: Batch update inventory quantities based on cart items | ||
| for (const cartLineItem of cart.cartLineItems) { | ||
| await RoomItem.update( | ||
| { | ||
| quantity: cartLineItem.quantity, | ||
| expiry_date: cartLineItem.expiry_date, | ||
| }, | ||
| { | ||
| where: { | ||
| room_id: cartLineItem.room_id, | ||
| item_id: cartLineItem.item_id, | ||
| }, | ||
| transaction, | ||
| } | ||
| ); | ||
| } | ||
|
|
||
| // Step 3: Mark the cart as inactive or clear cart items | ||
| await Cart.update( | ||
| { active: false }, | ||
| { where: { id: cart.id }, transaction } | ||
| ); | ||
| }); | ||
|
|
||
| // If the transaction is successful, respond with success | ||
| res.json({ success: true, message: "Checkout successful." }); | ||
| } catch (err) { | ||
| console.error("Checkout process failed:", err); | ||
| res.status(500).json({ | ||
| success: false, | ||
| message: "Checkout process failed", | ||
| error: (err as Error).message, | ||
| }); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need python to build a node project? Seems a bit overkill. I am pretty sure there must be an easier way