Skip to content

Commit 0216ad0

Browse files
committed
done for basic CRUD for products
1 parent 4bc235d commit 0216ad0

10 files changed

Lines changed: 125 additions & 64 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
node_modules
22
.env
33

4-
/interface-adapters/middlewares/logs
4+
/interface-adapters/middlewares/logs/
5+
/interface-adapters/controllers/examples

application-business-rules/use-cases/products/product-handlers.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ const findOneProductUseCase = ({ productValidation }) => async function
5151

5252

5353
// find all product use case handler
54-
const findAllProductsUseCase = () => findAllProductUseCaseHandler = async ({ dbProductHandler, logEvents }) => {
54+
const findAllProductsUseCase = () => findAllProductUseCaseHandler = async ({ dbProductHandler, logEvents, filterOptions }) => {
5555

5656
try {
5757

58-
const allProducts = await dbProductHandler.findAllProductsDbHandler();
58+
const allProducts = await dbProductHandler.findAllProductsDbHandler(filterOptions);
5959
// console.log("from find all products use case: ", allProducts);
6060
return Object.freeze(allProducts)
6161
} catch (e) {

enterprise-business-rules/validate-models/product-validation-fcts.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ const basicProductValidation = ({ productData, errorHandlers }) => {
284284
resultingProductData.rateAverage = 0;
285285
resultingProductData.lastModified = new Date().toISOString();
286286
resultingProductData.instock = Boolean(resultingProductData.inventory && resultingProductData.inventory > 0)
287-
287+
resultingProductData.brands = productData.brands || [];
288288

289289
if (errors.length) {
290290
throw new RequiredParameterError(errors.join(', '));

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const errorHandler = require('./interface-adapters/middlewares/loggers/errorHand
88
const userAndAuthRouter = require('./routes/auth-user.router.js');
99
const { logger } = require('./interface-adapters/middlewares/loggers/logger.js');
1010
const productRouter = require('./routes/product.routes.js');
11+
const createIndexFn = require('./interface-adapters/database-access/db-indexes.js');
1112

1213
const app = express();
1314

@@ -19,6 +20,7 @@ const corsOptions = require('./interface-adapters/middlewares/config/corsOptions
1920
// databae connetion call function
2021
dbconnection().then((db) => {
2122
console.log("database connected: ", db.databaseName);
23+
createIndexFn();
2224
});
2325

2426
app.use(logger);

interface-adapters/controllers/examples.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
}
2727
],
2828
highlights: [ 'Comfortable', 'Stylish', '100% Cotton' ],
29-
already in variations-----> ---------> specifications: { material: 'Cotton', fit: 'Regular' },
29+
already in variations -----> specifications: { material: 'Cotton', fit: 'Regular' },
3030
seoKeywords: [ 'Comfortable', 'Stylish', '100% Cotton' ],
3131
salePrice: 19,
3232
slug: 't-shirt',
@@ -57,6 +57,8 @@
5757
// const uri = 'mongodb://mongodb0.example.com:27017,mongodb1.example.com:27017/?replicaSet=myRepl'
5858
// For a sharded cluster, connect to the mongos instances; e.g.
5959
// const uri = 'mongodb://mongos0.example.com:27017,mongos1.example.com:27017/'
60+
61+
6062
const client = new MongoClient(uri);
6163
await client.connect();
6264
// Prereq: Create collections.
@@ -84,7 +86,7 @@
8486
await session.withTransaction(async () => {
8587
const coll1 = client.db('mydb1').collection('foo');
8688
const coll2 = client.db('mydb2').collection('bar');
87-
89+
8890
// Important:: You must pass the session to the operations
8991
await coll1.insertOne({ abc: 1 }, { session });
9092
await coll2.insertOne({ xyz: 999 }, { session });

interface-adapters/controllers/products/product-controller.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,14 @@ const findOneProductController = ({
119119

120120

121121
// find all product controller
122-
const findAllProductController = ({ dbProductHandler, findAllProductUseCaseHandler, logEvents }) => async function findAllProductControllerHandler() {
122+
const findAllProductController = ({ dbProductHandler, findAllProductUseCaseHandler, logEvents }) => async function findAllProductControllerHandler(httpRequest) {
123123

124-
console.log("from find all product controller")
125124

125+
const filterOptions = httpRequest.query;
126126
return findAllProductUseCaseHandler({
127127
dbProductHandler,
128-
logEvents
128+
logEvents,
129+
filterOptions
129130
}).then((products) => {
130131

131132
// console.log("products from findAllProductController: ", products);
@@ -240,8 +241,6 @@ const rateProductController = ({ dbProductHandler, rateProductUseCaseHandler, ma
240241
productId,
241242
} = httpRequest.body;
242243

243-
console.log("hit rating product controller")
244-
245244
if (!productId || !userId || !ratingValue) {
246245
return makeHttpError({
247246
statusCode: 400,
@@ -278,7 +277,7 @@ const rateProductController = ({ dbProductHandler, rateProductUseCaseHandler, ma
278277
`${e.no}:${e.type}\t${e.name}\t${e.message}`,
279278
"controllerHandlerErr.log"
280279
);
281-
console.log("error from rateProductController controller handler: ", e);
280+
console.error("error from rateProductController controller handler: ", e);
282281
if (e.name === 'RangeError')
283282
return makeHttpError({ errorMessage: e.message, statusCode: e.statusCode || 404 })
284283
return makeHttpError({ errorMessage: e.message, statusCode: e.statusCode || 500 });

interface-adapters/database-access/db-connection.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ const { MongoServerSelectionError, MongoServerClosedError, MongoServerError } =
33
const { logEvents } = require("../../interface-adapters/middlewares/loggers/logger")
44
module.exports = {
55

6+
/**
7+
* Establishes a connection to the MongoDB database and returns a reference to the database.
8+
*
9+
* @return {Promise<Db>} A promise that resolves to a reference to the MongoDB database.
10+
*/
611
dbconnection: async () => {
712
// The MongoClient is the object that references the connection to our
813
// datastore (Atlas, for example)
@@ -34,6 +39,7 @@ module.exports = {
3439
const database = client.db(datastoreName);
3540
// const userCollection = database.collection("users");
3641

42+
3743
return database;
3844
}
3945
}
Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,53 @@
1-
import { makeDb } from '../src/data-access'
2-
import dotenv from 'dotenv'
3-
dotenv.config();
1+
const { dbconnection } = require('./db-connection');
2+
require('dotenv').config();
43

5-
// database collection will automatically be created if it does not exist
6-
// indexes will only be added if they don't exist
7-
//Although indexes improve query performance, adding an index has negative performance impact for write operations
8-
(async function setupDb() {
4+
// all the collections stated here are created if not exist.
5+
module.exports = async function setupDb() {
96
console.log('Setting up database...')
7+
const db = await dbconnection();
108

11-
const db = await makeDb()
12-
const result = await db
13-
.collection('users')
14-
.createIndexes([
15-
{ key: { id: 1 }, name: 'userId_idx' },
16-
{ key: { email: 1 }, name: 'email_idx' },
17-
{ background: true, sparse: true }
18-
], function (err, result) {
19-
console.log(result);
20-
callback(result);
21-
});
9+
const allProductsIndexName = await db.collection("products").listIndexes().toArray();
2210

23-
db.collection("Rating")
24-
.createIndex([{ userId: 1 }, { postId: 1 }, { background: true, sparse: true }], function (err, result) {
25-
console.log(result);
26-
callback(result);
27-
})
2811

12+
let indexArr = [];
13+
// create indexes only if not exist
14+
allProductsIndexName.forEach(element => {
15+
if (element.name === 'productTextIndex' || element.name === 'productUniqueIndex') {
16+
return;
17+
}
18+
indexArr = [...indexArr, db.collection('products').createIndexes([
19+
{
20+
key: { "title": "text", "description": "text", "category": "text", "seoKeywords": "text", "origin": "text", "variations": "text", "highlights": "text", "brand": "text" },
21+
name: 'productTextIndex',
22+
default_language: 'english',
23+
weights: { description: 10, title: 3 }
24+
},
25+
{ key: { title: 1, }, name: 'productUniqueIndex' }
26+
])]
27+
});
2928

30-
process.exit()
31-
})()
29+
const allUsersIndexName = await db.collection("users").listIndexes().toArray();
30+
allUsersIndexName.forEach(element => {
31+
if (element.name === 'userUniqueIndex') {
32+
return;
33+
}
34+
indexArr = [...indexArr, db.collection('users').createIndexes([
35+
{ key: { email: 1, }, unique: true, name: 'userUniqueIndex' }
36+
])]
37+
});
38+
39+
const allRatingsIndexName = await db.collection("ratings").listIndexes().toArray();
40+
allRatingsIndexName.forEach(element => {
41+
if (element.name === 'ratingsUniqueIndex') {
42+
// db.collection('ratings').dropIndex('ratingsUniqueIndex');
43+
return;
44+
}
45+
indexArr = [...indexArr, db.collection('ratings').createIndexes([
46+
{ key: { userId: 1, postId: 1 }, name: 'ratingUniqueIndex' }
47+
])]
48+
});
49+
50+
await Promise.all([
51+
...indexArr
52+
]);
53+
}

interface-adapters/database-access/store-product.js

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -57,24 +57,48 @@ const findOneProduct = async ({ productId, dbconnection }) => {
5757
}
5858

5959
// find all products from the database
60-
const findAllProducts = async ({ dbconnection, logEvents }) => {
60+
const findAllProducts = async ({ dbconnection, logEvents, ...filterOptions }) => {
61+
const { category, minPrice, maxPrice, page, perPage, searchTerm } = filterOptions;
6162

62-
const db = await dbconnection();
63-
try {
64-
const allProducts = await db.collection('products').find({}, {
65-
projection: {
66-
_id: 1, title: 1, description: 1, price: 1, category: 1, brand: 1, inventory: 1, creationDate: 1, expirationDate: 1, origin: 1, variations: 1, salePrice: 1, slug: 1, totalRatings: 1, totalReviews: 1, totalSales: 1, rateAverage: 1,
67-
lastModified: 1,
68-
instock: 1
69-
}
70-
}).toArray()
63+
//TODO: id necessary add limiting fields. this affect the projection props
7164

72-
return allProducts.map((product) => {
73-
const id = product._id.toString();
74-
delete product._id;
75-
return { ...product, id };
76-
});
65+
const filter = {};
66+
if (category) filter.category = category;
67+
if (minPrice) filter.price = { $gte: parseFloat(minPrice) };
68+
if (maxPrice) filter.price = { $lte: parseFloat(maxPrice) };
69+
if (searchTerm) filter.$text = { $search: searchTerm };
7770

71+
const projection = {
72+
_id: 0, title: 1, description: 1, price: 1, category: 1, brand: 1, creationDate: 1, expirationDate: 1, origin: 1, variations: 1, salePrice: 1, slug: 1,
73+
lastModified: 1,
74+
instock: 1
75+
};
76+
77+
const offset = (page - 1) * perPage;
78+
const db = await dbconnection();
79+
80+
try {
81+
const allProducts = await db
82+
.collection('products')
83+
.find(filter)
84+
.project(projection)
85+
.skip(offset)
86+
.limit(Number(perPage))
87+
.toArray();
88+
89+
90+
const totalProducts = await db
91+
.collection('products').countDocuments(filter);
92+
const totalPages = Math.ceil(totalProducts / perPage);
93+
const products = allProducts.map(product => ({ ...product, id: product._id.toString() }));
94+
95+
return {
96+
data: products,
97+
totalProducts,
98+
totalPages,
99+
page,
100+
perPage
101+
};
78102
} catch (error) {
79103
console.log("Error from product DB handler: ", error);
80104
logEvents(
@@ -83,7 +107,7 @@ const findAllProducts = async ({ dbconnection, logEvents }) => {
83107
);
84108
return [];
85109
}
86-
}
110+
};
87111

88112
// update existing product
89113
const updateProduct = async ({ productId, productData, dbconnection, logEvents }) => {
@@ -169,8 +193,10 @@ const rateProduct = async ({ logEvents, ...ratingModel }) => {
169193
const productCollection = client.db("digital-market-place-updates").collection("products");
170194
const ratingCollection = client.db("digital-market-place-updates").collection("ratings");
171195

196+
// check if the product has been rated before.
172197
const existingProduct = await productCollection.findOne({ _id: new ObjectId(ratingModel.productId) }, { session });
173-
if (!existingProduct) {
198+
if (!existingProduct) {// cannot rate ghost product.
199+
session.abortTransaction();
174200
return {
175201
error: {
176202
code: 404,
@@ -182,6 +208,7 @@ const rateProduct = async ({ logEvents, ...ratingModel }) => {
182208
/* find first if this user has already rate this existing product*/
183209
const existingRating = await ratingCollection.findOne({ userId: ratingModel.userId, productId: ratingModel.productId }, { session });
184210
if (existingRating) {
211+
session.abortTransaction();
185212
return {
186213
error: {
187214
code: 409,
@@ -190,17 +217,15 @@ const rateProduct = async ({ logEvents, ...ratingModel }) => {
190217
};
191218
}
192219

193-
console.log("before insertion")
194220
/* create a new rating document */
195221
const newRating = await ratingCollection.insertOne(ratingModel, { session });
196-
console.log("after insertion")
197222
const { totalRatings } = existingProduct;
198223
const totalReviews = totalRatings?.reduce((sum, rating) => sum + rating, 0) || 0;
199-
const newAverage = totalRatings?.reduce((sum, rating, index) => sum + rating * (index + 1), 0) / (totalReviews || 1);
224+
const newAverage = totalReviews ? totalRatings?.reduce((sum, rating, index) => sum + rating * (index + 1), 0) / totalReviews : existingProduct.rateAverage;
200225

201226
/* increase the new rating value in the totalRatings array */
202227
for (let index = 0; index < 5; index++) {
203-
if ((ratingModel.ratingValue === (index + 1))) {
228+
if (ratingModel.ratingValue === (index + 1)) {
204229
totalRatings[index] = totalRatings[index] + 1;
205230
}
206231
}
@@ -224,11 +249,8 @@ const rateProduct = async ({ logEvents, ...ratingModel }) => {
224249
},
225250
{ session }
226251
);
227-
228-
await session.commitTransaction();
229-
console.log("Product rated successfully!", updatedProduct);
252+
// await session.commitTransaction();
230253
return { updatedProduct, newRating };
231-
232254
}, transactionOptions);
233255

234256
} catch (error) {
@@ -244,7 +266,6 @@ const rateProduct = async ({ logEvents, ...ratingModel }) => {
244266
await client.close();
245267
}
246268
}
247-
248269
// find one rating for a product based on product id and user id
249270
const findOneRating = async ({ productId, userId }) => {
250271

@@ -267,7 +288,7 @@ module.exports = ({ dbconnection, logEvents }) => {
267288
return Object.freeze({
268289
createProductDbHandler: async (productData) => createProduct(productData, dbconnection, logEvents),
269290
findOneProductDbHandler: async ({ productId }) => findOneProduct({ productId, dbconnection, logEvents }),
270-
findAllProductsDbHandler: async () => findAllProducts({ dbconnection, logEvents }),
291+
findAllProductsDbHandler: async (filterOptions) => findAllProducts({ dbconnection, logEvents, ...filterOptions }),
271292
updateProductDbHandler: async ({ productId, productData }) => updateProduct({ productId, productData, dbconnection, logEvents }),
272293
deleteProductDbHandler: async ({ productId }) => deleteProduct({ productId, dbconnection, logEvents }),
273294
updateProductDbHandler: async ({ productId, ...productData }) => updatedProduct({ productId, dbconnection, logEvents, ...productData }),

interface-adapters/middlewares/logs/mongoErrLog.log

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,11 @@
125125
undefined undefined
126126
2024-07-04 20:30:49 627dba76-ad4f-494d-9748-486930a8eb79 undefined:40388ADB40740000:error:0A000438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error:../deps/openssl/openssl/ssl/record/rec_layer_s3.c:1590:SSL alert number 80
127127
undefined undefined
128+
2024-07-12 13:33:59 a56141b4-1fc5-4efd-87d7-e1541598def8 undefined:40B8665EC9770000:error:0A000438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error:../deps/openssl/openssl/ssl/record/rec_layer_s3.c:1590:SSL alert number 80
129+
undefined undefined
130+
2024-07-12 13:34:56 7bc5ff36-49d3-48d2-a2c4-adb47ce952ff undefined:4068F109A47B0000:error:0A000438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error:../deps/openssl/openssl/ssl/record/rec_layer_s3.c:1590:SSL alert number 80
131+
undefined undefined
132+
2024-07-12 13:48:31 f2a555f9-bd54-4555-8c8a-9ca886fc1084 undefined:4038EB82E7720000:error:0A000438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error:../deps/openssl/openssl/ssl/record/rec_layer_s3.c:1590:SSL alert number 80
133+
undefined undefined
134+
2024-07-12 13:49:14 c99b1035-de1b-4e3f-9843-46d4d9b91e80 undefined:40C84CEB5B7C0000:error:0A000438:SSL routines:ssl3_read_bytes:tlsv1 alert internal error:../deps/openssl/openssl/ssl/record/rec_layer_s3.c:1590:SSL alert number 80
135+
undefined undefined

0 commit comments

Comments
 (0)