Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
31e76d8
installed sequelize and did initial setup up to step before creating …
magiicloud Mar 20, 2024
97a3112
changed to sequelize-typescript and finished configuring migrations a…
magiicloud Mar 25, 2024
8fa385b
separated migrations into primary, secondary and tertiary tables
magiicloud Mar 26, 2024
a7609bd
amended migration FK reference names to table names
magiicloud Mar 26, 2024
a53a744
amended Building migration from item_size to image_size. amended mode…
magiicloud Mar 26, 2024
38d5b20
changed image url from int to string
magiicloud Mar 26, 2024
65046b7
amended the order of async down for primary seed, seeded data for sec…
magiicloud Mar 26, 2024
6be21fb
amended seed data for carts to add created and updated
magiicloud Mar 26, 2024
748c37d
installed cors
magiicloud Mar 26, 2024
b947cd6
rewrite indexjs of models. rewrote migrations models to include table…
magiicloud Mar 26, 2024
9c61c4b
amended itemsController to show data from RoomItem and Room
magiicloud Mar 27, 2024
8899c38
Edit room model params, create building router and controller to send…
patrickkok Mar 30, 2024
7aca78b
Merge pull request #1 from magiicloud/floorplan
magiicloud Mar 31, 2024
e9fd0f9
added logic to search via serial num or via pk
magiicloud Mar 31, 2024
6b27096
added update item controller and sort get all by id
magiicloud Mar 31, 2024
8b895b0
Merge pull request #2 from magiicloud/additems
patrickkok Mar 31, 2024
563b07b
added controller and route for adding new items
magiicloud Apr 1, 2024
a10a89c
Add controller method to add new building and associated rooms
patrickkok Apr 1, 2024
a383643
Merge pull request #3 from magiicloud/new-building
magiicloud Apr 2, 2024
d993e41
added route protection logic to index.ts
magiicloud Apr 2, 2024
703cb40
Merge pull request #4 from magiicloud/add-new-items
patrickkok Apr 2, 2024
b32249c
added new logic to add new items if it exist already
magiicloud Apr 2, 2024
d31f33f
Merge pull request #5 from magiicloud/add-new-items
magiicloud Apr 2, 2024
dde662d
added logic to delete item in room and entire item set
magiicloud Apr 2, 2024
5738602
edited create item such that if item exist only update par and not it…
magiicloud Apr 2, 2024
cf2cd38
Implement transactions
patrickkok Apr 2, 2024
f0cab7a
added cart router to make cart transactions
magiicloud Apr 4, 2024
0a026fb
added cart controllers to add item to cart, checkout cart and view ac…
magiicloud Apr 4, 2024
d177a9a
modified migrations, models to associate Rooms and Cart_Line_Items. r…
magiicloud Apr 4, 2024
ac7b536
amended roomId to roomSelect
magiicloud Apr 4, 2024
a25c20b
Add transactions
patrickkok Apr 6, 2024
3a0497a
Merge pull request #6 from magiicloud/transactions
magiicloud Apr 6, 2024
72859c6
Merge pull request #7 from magiicloud/deleteitem
patrickkok Apr 6, 2024
c3c6ddf
added protection to all routes except /
magiicloud Apr 6, 2024
b27630d
added delete item from cart logic
magiicloud Apr 7, 2024
93d805f
added dashboard router and controller to retrieve near expiry and bel…
magiicloud Apr 7, 2024
f5289d2
Add route and controller method to get room items
patrickkok Apr 7, 2024
bf7eea6
Add user router and controller to findOrCreate user by email, made na…
patrickkok Apr 7, 2024
8f97736
sorted getExpiringItemsCount and modified getParItems by doing rawQue…
magiicloud Apr 7, 2024
d2e81be
Merge pull request #8 from magiicloud/room-items
magiicloud Apr 8, 2024
b6656eb
Merge pull request #9 from magiicloud/add-user
magiicloud Apr 8, 2024
a4ba424
Merge branch 'main' into auth
magiicloud Apr 8, 2024
de096c2
Add building controller methods and routes to only display buildings …
patrickkok Apr 8, 2024
3253889
Merge pull request #10 from magiicloud/auth
patrickkok Apr 8, 2024
40852f4
amended the usercontroller migrations and seeder for retreival of use…
magiicloud Apr 9, 2024
b37e134
amended the default userId 1 to accept userId from params or req body
magiicloud Apr 9, 2024
df929f8
Merge pull request #11 from magiicloud/building-users
magiicloud Apr 9, 2024
390bb10
Merge pull request #12 from magiicloud/auth
patrickkok Apr 9, 2024
c45b686
Update building controller and router to accept userId instead of Use…
patrickkok Apr 10, 2024
c02bccb
Add logic to add users to buildings
patrickkok Apr 10, 2024
02f8b67
Merge pull request #13 from magiicloud/main-building
magiicloud Apr 11, 2024
34d7477
Update getItemsBelowPar and getExpiringItemsCount to only get items t…
patrickkok Apr 13, 2024
afd461e
Update getAllItems method to only return Items in Rooms the user has …
patrickkok Apr 13, 2024
9ecc8b8
Update getAllRooms to only show the rooms the user is part of
patrickkok Apr 13, 2024
fddfe39
changed back to rawQuery to sum par quantities
magiicloud Apr 14, 2024
3b925b9
Merge pull request #14 from magiicloud/get-items-by-building
magiicloud Apr 14, 2024
a06a7b1
Update readme
patrickkok Apr 14, 2024
dd88dfc
Merge pull request #15 from magiicloud/readme-update
patrickkok Apr 14, 2024
8c36f7c
added fly io deployment configs
magiicloud Apr 16, 2024
d5b1f4f
added code to deploy to fly.io and cleaned up all codes
magiicloud Apr 20, 2024
3cbb33d
Merge pull request #16 from magiicloud/deployconfig
magiicloud Apr 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
.env
dist/
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
node_modules/
node_modules/
.env
dist/
fly.toml
8 changes: 8 additions & 0 deletions .sequelizerc
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"),
};
34 changes: 34 additions & 0 deletions Dockerfile
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 && \
Copy link
Copy Markdown

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

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" ]
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Rocket Academy Coding Bootcamp: Project 3 Backend
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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.
18 changes: 18 additions & 0 deletions config/database.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require("dotenv").config();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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,
},
};
18 changes: 18 additions & 0 deletions config/database.ts
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,
},
};
100 changes: 100 additions & 0 deletions controllers/buildingController.ts
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 });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should collect the rooms into an array, and then run bulkCreate

})
);
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");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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?

}
}
}
170 changes: 170 additions & 0 deletions controllers/cartController.ts
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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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,
});
}
}
}
Loading