Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
import express from "express";
import usersRouter from "./routes/users.js";
import productsRouter from "./routes/products.js";
import ordersRouter from "./routes/orders.js";
import getUserFromToken from "./middleware/getUserFromToken.js";

const app = express();

app.use(express.json());
app.use(getUserFromToken);

app.use("/users", usersRouter);
app.use("/products", productsRouter);
app.use("/orders", ordersRouter);

export default app;
27 changes: 26 additions & 1 deletion db/schema.sql
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@
-- TODO
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL
);

CREATE TABLE orders (
id SERIAL PRIMARY KEY,
date DATE NOT NULL,
note TEXT,
user_id INT NOT NULL REFERENCES users(id)
);

CREATE TABLE products (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
description TEXT NOT NULL,
price DECIMAL NOT NULL
);

CREATE TABLE orders_products (
order_id INT NOT NULL REFERENCES orders(id),
product_id INT NOT NULL REFERENCES products(id),
quantity INT NOT NULL,
PRIMARY KEY (order_id, product_id)
);
59 changes: 57 additions & 2 deletions db/seed.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import "dotenv/config";
import bcrypt from "bcrypt";
import db from "#db/client";

await db.connect();
Expand All @@ -6,5 +8,58 @@ await db.end();
console.log("🌱 Database seeded.");

async function seed() {
// TODO
}
const products = [
{ title: "PC Case", description: "ATX mid-tower computer case with tempered glass side panel", price: 89.99 },
{ title: "Power Supply Unit", description: "750W 80+ Gold certified fully modular PSU", price: 119.99 },
{ title: "Graphics Card", description: "High-performance GPU suitable for gaming and video editing", price: 599.99 },
{ title: "CPU", description: "8-core, 16-thread processor for multitasking and gaming", price: 329.99 },
{ title: "Motherboard", description: "ATX motherboard with Wi-Fi and RGB support", price: 159.99 },
{ title: "RAM", description: "16GB DDR4 3200MHz memory kit", price: 69.99 },
{ title: "SSD", description: "1TB NVMe SSD with fast read/write speeds", price: 94.99 },
{ title: "CPU Cooler", description: "Dual-fan air cooler for optimal thermal performance", price: 49.99 },
{ title: "Case Fans", description: "RGB 120mm cooling fan pack (3-pack)", price: 39.99 },
{ title: "Wi-Fi Card", description: "PCIe Wi-Fi 6 wireless adapter", price: 29.99 }
];


for (const product of products) {
await db.query(
`INSERT INTO products (title, description, price)
VALUES ($1, $2, $3);`,
[product.title, product.description, product.price]
);
}


const hashedPassword = await bcrypt.hash("password123", 10);
const userResult = await db.query(
`INSERT INTO users (username, password)
VALUES ($1, $2)
RETURNING id;`,
["testuser", hashedPassword]
);
const userId = userResult.rows[0].id;


const orderResult = await db.query(
`INSERT INTO orders (date, note, user_id)
VALUES (NOW(), 'First order', $1)
RETURNING id;`,
[userId]
);
const orderId = orderResult.rows[0].id;


const productsResult = await db.query(
`SELECT id FROM products ORDER BY id LIMIT 5;`
);
const productIds = productsResult.rows.map((row) => row.id);

for (const productId of productIds) {
await db.query(
`INSERT INTO orders_products (order_id, product_id, quantity)
VALUES ($1, $2, $3);`,
[orderId, productId, 1]
);
}
}
2 changes: 0 additions & 2 deletions example.env

This file was deleted.

20 changes: 16 additions & 4 deletions middleware/getUserFromToken.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
import { getUserById } from "#db/queries/users";
import { verifyToken } from "#utils/jwt";
import db from "../db/client.js";
import { verifyToken } from "../utils/jwt.js";

/** Attaches the user to the request if a valid token is provided */
export default async function getUserFromToken(req, res, next) {
const authorization = req.get("authorization");

if (!authorization || !authorization.startsWith("Bearer ")) return next();

const token = authorization.split(" ")[1];

try {
const { id } = verifyToken(token);
const user = await getUserById(id);

const result = await db.query(
`SELECT id, username FROM users WHERE id = $1`,
[id]
);

const user = result.rows[0];

if (!user) {
return res.status(401).send("Invalid token.");
}

req.user = user;
next();
} catch (e) {
Expand Down
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
},
"dependencies": {
"bcrypt": "^5.1.1",
"dotenv": "^17.2.3",
"express": "^5.1.0",
"jsonwebtoken": "^9.0.2",
"pg": "^8.14.1"
Expand Down
163 changes: 163 additions & 0 deletions routes/orders.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import express from "express";
import db from "../db/client.js";
import requireUser from "../middleware/requireUser.js";

const router = express.Router();

router.get("/", requireUser, async (req, res, next) => {
try {
const userId = req.user.id;

const result = await db.query(
`
SELECT id, date, note, user_id
FROM orders
WHERE user_id = $1;
`,
[userId]
);

res.send(result.rows);
} catch (err) {
next(err);
}
});


router.post("/", requireUser, async (req, res, next) => {
try {
const userId = req.user.id;
const { note } = req.body;

const {
rows: [order],
} = await db.query(
`
INSERT INTO orders (date, note, user_id)
VALUES (NOW(), $1, $2)
RETURNING id, date, note, user_id;
`,
[note, userId]
);


res.status(201).send(order);
} catch (err) {
next(err);
}
});


router.get("/:id", requireUser, async (req, res, next) => {
try {
const userId = req.user.id;
const orderId = req.params.id;

const {
rows: [order],
} = await db.query(
`
SELECT id, date, note, user_id
FROM orders
WHERE id = $1
AND user_id = $2;
`,
[orderId, userId]
);

if (!order) {
return res.status(404).send({ message: "Order not found" });
}

res.send(order);
} catch (err) {
next(err);
}
});


router.post("/:id/products", requireUser, async (req, res, next) => {
try {
const userId = req.user.id;
const orderId = req.params.id;
const { product_id, quantity } = req.body;

const {
rows: [order],
} = await db.query(
`
SELECT id, user_id
FROM orders
WHERE id = $1
AND user_id = $2;
`,
[orderId, userId]
);

if (!order) {
return res.status(404).send({ message: "Order not found" });
}

const {
rows: [orderProduct],
} = await db.query(
`
INSERT INTO orders_products (order_id, product_id, quantity)
VALUES ($1, $2, $3)
RETURNING order_id, product_id, quantity;
`,
[orderId, product_id, quantity]
);


res.status(201).send(orderProduct);
} catch (err) {
next(err);
}
});


router.get("/:id/products", requireUser, async (req, res, next) => {
try {
const userId = req.user.id;
const orderId = req.params.id;

const {
rows: [order],
} = await db.query(
`
SELECT id, user_id
FROM orders
WHERE id = $1
AND user_id = $2;
`,
[orderId, userId]
);

if (!order) {
return res.status(404).send({ message: "Order not found" });
}

const result = await db.query(
`
SELECT
products.id,
products.title,
products.description,
products.price,
orders_products.quantity
FROM orders_products
JOIN products
ON products.id = orders_products.product_id
WHERE orders_products.order_id = $1;
`,
[orderId]
);

res.send(result.rows);
} catch (err) {
next(err);
}
});

export default router;
Loading