Skip to content

Commit d9fc900

Browse files
committed
add products
1 parent c5bf862 commit d9fc900

13 files changed

+106
-13
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"zod": "^3.23.8"
2929
},
3030
"devDependencies": {
31+
"@faker-js/faker": "^9.3.0",
3132
"@types/bcrypt": "^5.0.2",
3233
"@types/cookie-parser": "^1.4.7",
3334
"@types/cors": "^2.8.17",

src/db/seed.ts

+24-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
import pool from "config/database";
22
import bcrypt from "bcrypt";
33
import { initializeDatabase } from "./setup";
4+
import { RegisterPayload } from "auth/auth.validation";
5+
import { CreateProductPayload } from "products/products.validation";
6+
import { faker } from "@faker-js/faker";
47

5-
const seedData = [
6-
{
7-
username: "Mateo",
8-
9-
password: "123456"
10-
}
11-
];
8+
const users: RegisterPayload[] = Array.from({ length: 2 }, () => ({
9+
username: faker.internet.displayName(),
10+
email: faker.internet.email(),
11+
password: "123456"
12+
}));
13+
14+
const products: CreateProductPayload[] = Array.from({ length: 2 }, () => ({
15+
name: faker.commerce.productName(),
16+
description: faker.commerce.productDescription(),
17+
price: +faker.commerce.price({ min: 100, max: 10000, dec: 0 })
18+
}));
1219

1320
export async function seedDatabase() {
1421
await initializeDatabase();
@@ -19,15 +26,24 @@ export async function seedDatabase() {
1926

2027
// Clear the users table
2128
await client.query("TRUNCATE TABLE users RESTART IDENTITY CASCADE");
29+
await client.query("TRUNCATE TABLE products RESTART IDENTITY CASCADE");
2230

23-
for (const { username, email, password } of seedData) {
31+
for (const { username, email, password } of users) {
2432
const hashedPassword = await bcrypt.hash(password, 10);
2533
await client.query(`INSERT INTO users (username, email, password) VALUES ($1, $2, $3)`, [
2634
username,
2735
email,
2836
hashedPassword
2937
]);
3038
}
39+
40+
for (const { name, description, price } of products) {
41+
await client.query(`INSERT INTO products (name, description, price) VALUES ($1, $2, $3)`, [
42+
name,
43+
description,
44+
price
45+
]);
46+
}
3147
await client.query("COMMIT");
3248
} catch (error) {
3349
await client.query("ROLLBACK");

src/db/setup.ts

+12
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ async function createTables() {
1111
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1212
);
1313
`);
14+
15+
// Price is in cents to avoid floating point arithmetic issues
16+
await pool.query(`
17+
CREATE TABLE IF NOT EXISTS products (
18+
id SERIAL PRIMARY KEY,
19+
name VARCHAR(100),
20+
description TEXT,
21+
price INT,
22+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
23+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
24+
);
25+
`);
1426
}
1527

1628
// Run the table creation and seeding process

src/interfaces/db.interface.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface DBModel {
2+
id: number;
3+
created_at: Date;
4+
updated_at: Date;
5+
}

src/products/products.controller.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Controller } from "interfaces/controller.interface";
2+
import { ProductService } from "./products.service";
3+
import { Request, Response } from "express";
4+
5+
export class ProductController extends Controller {
6+
private productService = new ProductService();
7+
8+
constructor() {
9+
super("/products");
10+
this.initializeRoutes();
11+
}
12+
13+
protected initializeRoutes() {
14+
this.router.get(`${this.path}`, this.getProducts);
15+
}
16+
17+
private getProducts = async (_request: Request, response: Response) => {
18+
const products = await this.productService.getProducts();
19+
response.json({ data: products });
20+
};
21+
}

src/products/products.model.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { CreateProductPayload } from "./products.validation";
2+
import { DBModel } from "interfaces/db.interface";
3+
4+
export interface Product extends DBModel, CreateProductPayload {}

src/products/products.repository.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import pool from "config/database";
2+
import { Product } from "./products.model";
3+
4+
export class ProductRepository {
5+
public async getProducts() {
6+
const result = await pool.query<Product[]>("SELECT id, name, description, price FROM products");
7+
return result.rows;
8+
}
9+
}

src/products/products.service.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ProductRepository } from "./products.repository";
2+
3+
export class ProductService {
4+
private productRepository = new ProductRepository();
5+
6+
public async getProducts() {
7+
return this.productRepository.getProducts();
8+
}
9+
}

src/products/products.validation.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { z } from "zod";
2+
3+
export const createProductSchema = z.object({
4+
body: z.object({
5+
name: z.string().min(3),
6+
description: z.string().min(10),
7+
price: z.number().int().positive()
8+
})
9+
});
10+
11+
export type CreateProductPayload = z.infer<typeof createProductSchema>["body"];

src/server.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { AuthController } from "auth/auth.controller";
22
import App from "./app";
33
import UsersController from "users/users.controller";
4+
import { ProductController } from "products/products.controller";
45

5-
const app = new App([new AuthController(), new UsersController()]);
6+
const app = new App([new AuthController(), new ProductController(), new UsersController()]);
67
app.listen();

src/users/users.controller.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class UsersController extends Controller {
1212
this.router.get(this.path, this.getUsers);
1313
}
1414

15+
// TODO: For testing purposes only. Remove this in production.
1516
private getUsers = async (_: Request, response: Response, next: NextFunction) => {
1617
try {
1718
const result = await pool.query("SELECT id, username, email FROM users");

src/users/users.model.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import { RegisterPayload } from "auth/auth.validation";
2+
import { DBModel } from "interfaces/db.interface";
23

3-
export interface User extends RegisterPayload {
4-
id: number;
5-
created_at: Date;
6-
}
4+
export interface User extends DBModel, RegisterPayload {}

yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@
180180
dependencies:
181181
levn "^0.4.1"
182182

183+
"@faker-js/faker@^9.3.0":
184+
version "9.3.0"
185+
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.3.0.tgz#ef398dab34c67faaa0e348318c905eae3564fa58"
186+
integrity sha512-r0tJ3ZOkMd9xsu3VRfqlFR6cz0V/jFYRswAIpC+m/DIfAUXq7g8N7wTAlhSANySXYGKzGryfDXwtwsY8TxEIDw==
187+
183188
"@humanfs/core@^0.19.0":
184189
version "0.19.0"
185190
resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.0.tgz#08db7a8c73bb07673d9ebd925f2dad746411fcec"

0 commit comments

Comments
 (0)