Skip to content
This repository was archived by the owner on Aug 14, 2025. It is now read-only.

Commit f05f777

Browse files
committed
feat: add Product service and database entity
1 parent 679412e commit f05f777

12 files changed

Lines changed: 397 additions & 10 deletions

File tree

deno.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"@types/cookie-parser": "npm:@types/cookie-parser@^1.4.8",
1515
"@types/cors": "npm:@types/cors@^2.8.18",
1616
"@types/express": "npm:@types/express@^5.0.1",
17+
"@types/moment": "npm:@types/moment@^2.13.0",
1718
"@types/pg": "npm:@types/pg@^8.15.1",
1819
"@types/supertest": "npm:@types/supertest@^6.0.3",
1920
"bcrypt": "npm:bcrypt@^6.0.0",
@@ -24,6 +25,7 @@
2425
"express": "npm:express@^5.1.0",
2526
"@langchain/mistralai": "npm:@langchain/mistralai@^0.2.0",
2627
"jose": "npm:jose@^6.0.11",
28+
"moment": "npm:moment@^2.30.1",
2729
"pg": "npm:pg@^8.16.0",
2830
"socket.io": "npm:socket.io@^4.8.1",
2931
"socket.io-client": "npm:socket.io-client@^4.8.1",

src/database/schema.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
boolean,
23
doublePrecision,
34
pgEnum,
45
pgTable,
@@ -52,6 +53,7 @@ export const product = pgTable("product", {
5253
name: text("name").notNull(),
5354
description: text("description"),
5455
price: doublePrecision().notNull(),
56+
available: boolean("available").notNull().default(true),
5557
createdAt: timestamp("created_at").notNull().defaultNow(),
5658
updatedAt: timestamp("updated_at").notNull().$onUpdate(() => new Date()),
5759
});

src/handlers/ProductHandler.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { ZodError } from "zod";
2+
import { IDatabase } from "../interfaces/IDatabase.ts";
3+
import { IHandlerReturn } from "../interfaces/IHandlerReturn.ts";
4+
import { IProduct } from "../interfaces/IProduct.ts";
5+
import { ProductService } from "../services/ProductService.ts";
6+
7+
export class ProductHandler {
8+
private productService: ProductService;
9+
10+
constructor(db: IDatabase) {
11+
this.productService = new ProductService(db);
12+
}
13+
14+
async createProduct(product: Partial<IProduct>): Promise<IHandlerReturn> {
15+
try {
16+
product.createdAt = new Date();
17+
product.updatedAt = new Date();
18+
const newProduct = await this.productService.createProduct(product);
19+
if (!newProduct) {
20+
return {
21+
status: 500,
22+
message: "There was an error creating the product",
23+
}
24+
}
25+
26+
return {
27+
status: 201,
28+
message: "Product created successfully",
29+
data: newProduct,
30+
}
31+
} catch (error) {
32+
if (error instanceof ZodError) {
33+
return {
34+
status: 400,
35+
message: "Invalid product data",
36+
error: error.toString(),
37+
}
38+
}
39+
40+
if (error instanceof Error) {
41+
return {
42+
status: 500,
43+
message: error.message,
44+
}
45+
}
46+
47+
return {
48+
status: 500,
49+
message: "There was an error creating the product",
50+
}
51+
}
52+
}
53+
54+
async getProducts(): Promise<IHandlerReturn> {
55+
try {
56+
const products = await this.productService.getAllProducts();
57+
if (!products) {
58+
return {
59+
status: 500,
60+
message: "There was an error getting the products",
61+
}
62+
}
63+
64+
return {
65+
status: 200,
66+
message: "Products retrieved successfully",
67+
data: products,
68+
}
69+
} catch (error) {
70+
if (error instanceof Error) {
71+
return {
72+
status: 500,
73+
message: error.message,
74+
}
75+
}
76+
77+
return {
78+
status: 500,
79+
message: "There was an error getting the products",
80+
}
81+
}
82+
}
83+
84+
async getProductById(id: number): Promise<IHandlerReturn> {
85+
try {
86+
const product = await this.productService.getProductById(id);
87+
if (!product) {
88+
return {
89+
status: 404,
90+
message: "Product not found",
91+
}
92+
}
93+
94+
return {
95+
status: 200,
96+
message: "Product retrieved successfully",
97+
data: product,
98+
}
99+
} catch (error) {
100+
if (error instanceof Error) {
101+
return {
102+
status: 500,
103+
message: error.message,
104+
}
105+
}
106+
107+
return {
108+
status: 500,
109+
message: "There was an error getting the product",
110+
}
111+
}
112+
}
113+
}

src/interfaces/IProduct.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export interface IProduct {
2+
id: number;
3+
name: string;
4+
description: string;
5+
price: number;
6+
available: boolean;
7+
createdAt: Date;
8+
updatedAt: Date;
9+
}

src/main.ts

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import cors from "npm:cors";
55
import { Server } from "npm:socket.io";
66
import { MockDatabase } from "./database/MockDatabase.ts";
77
import { OrderHandler } from "./handlers/OrderHandler.ts";
8+
import { ProductHandler } from "./handlers/ProductHandler.ts";
89
import { addMessage } from "./lc/model.ts";
910
import { ValidateJWT } from "./middlewares/ValidateJWT.ts";
1011
import { AuthenticationService } from "./services/AuthenticationService.ts";
@@ -28,6 +29,7 @@ const JWTmiddleware = new ValidateJWT(authenticationService);
2829

2930
// Instantiate the handlers
3031
export const orderHandler = new OrderHandler(db);
32+
export const productHandler = new ProductHandler(db);
3133

3234
app.use(cookieParser());
3335
app.use(express.json());
@@ -106,9 +108,9 @@ app.post("/auth/login", async (req, res) => {
106108
app.post("/order", JWTmiddleware.validateToken, async (req, res) => {
107109
const order = req.body;
108110
const userId = req.user!.id;
109-
111+
110112
const result = await orderHandler.createOrder(order, userId as number);
111-
113+
112114
res.status(result.status).json({
113115
message: result.message,
114116
data: result.data,
@@ -121,17 +123,17 @@ app.get("/order/:id", JWTmiddleware.validateToken, async (req, res) => {
121123
const userId = req.user!.id;
122124

123125
const result = await orderHandler.getOrderById(Number(id), userId as number);
124-
126+
125127
res.status(result.status).json({
126128
message: result.message,
127129
data: result.data,
128130
error: result.error,
129131
});
130132
});
131133

132-
app.get("/orders", async (_req, res) => {
134+
app.get("/orders", JWTmiddleware.validateToken, async (_req, res) => {
133135
const result = await orderHandler.getOrders();
134-
136+
135137
res.status(result.status).json({
136138
message: result.message,
137139
data: result.data,
@@ -169,6 +171,42 @@ app.patch("/order/:id", JWTmiddleware.validateToken, async (req, res) => {
169171
});
170172
});
171173

174+
// Product section
175+
app.post("/product", JWTmiddleware.validateToken, async (req, res) => {
176+
const product = req.body;
177+
178+
179+
const result = await productHandler.createProduct(product);
180+
181+
182+
res.status(result.status).json({
183+
message: result.message,
184+
data: result.data,
185+
error: result.error,
186+
});
187+
});
188+
189+
app.get("/product/:id", JWTmiddleware.validateToken, async (req, res) => {
190+
const { id } = req.params;
191+
192+
const result = await productHandler.getProductById(Number(id));
193+
res.status(result.status).json({
194+
message: result.message,
195+
data: result.data,
196+
error: result.error,
197+
});
198+
});
199+
200+
app.get("/products", JWTmiddleware.validateToken, async (_req, res) => {
201+
const result = await productHandler.getProducts();
202+
res.status(result.status).json({
203+
message: result.message,
204+
data: result.data,
205+
error: result.error,
206+
});
207+
});
208+
209+
172210
app.get("/hidden", JWTmiddleware.validateToken, (_req, res) => {
173211
res.status(200).json({ message: "This is a hidden route" });
174212
});
@@ -200,14 +238,14 @@ io.use((socket, next) => {
200238
socket.user = {
201239
id: 2,
202240
name: "Lucas",
203-
}
241+
};
204242

205243
next();
206244
});
207245

208246
io.on("connection", (socket) => {
209247
socket.on("message", async (e) => {
210-
await addMessage({message: e, user: socket.user!}).then((final) => {
248+
await addMessage({ message: e, user: socket.user! }).then((final) => {
211249
socket.emit("message", final);
212250
});
213251
});

src/mocks/Product.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { faker } from "@faker-js/faker";
2+
import { IProduct } from "../interfaces/IProduct.ts";
3+
4+
export function generateMockProduct(): Partial<IProduct> {
5+
return {
6+
name: faker.food.dish(),
7+
description: faker.food.description(),
8+
price: faker.number.float({ min: 10.0, max: 50.0 }),
9+
available: true,
10+
createdAt: faker.date.recent(),
11+
updatedAt: faker.date.future(),
12+
};
13+
}

src/schemas/zodSchema.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,13 @@ export const orderSchema = z.object({
3737
createdAt: z.date().describe("Data de criação do pedido"),
3838
updatedAt: z.date().describe("Data de atualização do pedido"),
3939
});
40+
41+
export const productSchema = z.object({
42+
id: z.number().describe("ID do produto").optional(),
43+
name: z.string().describe("Nome do produto"),
44+
price: z.number().describe("Preço do produto"),
45+
description: z.string().describe("Descrição do produto").optional(),
46+
available: z.boolean().describe("Disponibilidade do produto"),
47+
createdAt: z.date().describe("Data de criação do produto"),
48+
updatedAt: z.date().describe("Data de atualização do produto"),
49+
});

src/services/AuthenticationService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ export class AuthenticationService {
4646
await this.db.insert(saltTable, {
4747
userId: newUser.id,
4848
salt: salt,
49-
})
50-
49+
});
50+
5151
return newUser.id;
5252
}
5353

src/services/OrderService.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ export class OrderService {
2121
userId: id,
2222
};
2323

24-
const newOrder = await this.db.insert(orderTable, order as IOrder);
24+
const newOrder = await this.db.insert(orderTable, {
25+
userId: order.userId,
26+
status: order.status,
27+
total: order.total,
28+
createdAt: new Date(),
29+
updatedAt: new Date(),
30+
} as IOrder);
2531
if (!newOrder) {
2632
throw new Error("There was an error creating your order");
2733
}
@@ -73,6 +79,11 @@ export class OrderService {
7379
throw new Error("No items found for this order");
7480
}
7581

82+
for (const item of items) {
83+
//@ts-ignore yes
84+
delete item.table;
85+
}
86+
7687
return items;
7788
}
7889

@@ -100,6 +111,12 @@ export class OrderService {
100111

101112
async getOrders() {
102113
const orders = await this.db.selectAll(orderTable);
114+
for (const order of orders) {
115+
const items = await this.getOrderItems(order.id);
116+
//@ts-ignore yes
117+
order.items = items;
118+
}
119+
103120
if (!orders) {
104121
throw new Error("No orders found");
105122
}

src/services/ProductService.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { product as productTable } from "../database/schema.ts";
2+
import { IDatabase } from "../interfaces/IDatabase.ts";
3+
import { productSchema } from "../schemas/zodSchema.ts";
4+
import { validateData } from "./decorators.ts";
5+
6+
export class ProductService {
7+
private db: IDatabase;
8+
9+
constructor(db: IDatabase) {
10+
this.db = db;
11+
}
12+
13+
async getAllProducts() {
14+
const products = await this.db.selectAll(productTable);
15+
if (!products) {
16+
throw new Error("There was an error getting the products");
17+
}
18+
return products;
19+
}
20+
21+
async getProductById(id: number) {
22+
const product = await this.db.select(productTable, id);
23+
if (!product) {
24+
throw new Error("There was an error getting the product");
25+
}
26+
return product;
27+
}
28+
29+
@validateData(productSchema)
30+
async createProduct(product: Partial<typeof productTable.$inferInsert>) {
31+
const newProduct = await this.db.insert(
32+
productTable,
33+
{
34+
...product,
35+
createdAt: new Date(),
36+
updatedAt: new Date(),
37+
} as typeof productTable.$inferInsert,
38+
);
39+
if (!newProduct) {
40+
throw new Error("There was an error creating the product");
41+
}
42+
return newProduct;
43+
}
44+
}

0 commit comments

Comments
 (0)