diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..3718fe1
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,19 @@
+# Server
+PORT=5000
+NODE_ENV=development
+
+# MongoDB (use MONGO_URI or MONGODB_URI for rate-product transaction)
+MONGO_URI=mongodb://localhost:27017/your-db
+MONGO_DB_NAME=digital-market-place-updates
+
+# JWT
+ACCESS_TOKEN_SECRETKEY=your_access_token_secret
+JWT_REFRESH_SECRET=your_refresh_token_secret
+JWT_EXPIRES_IN=15m
+JWT_REFRESH_EXPIRES_IN=7d
+
+# Email (for password reset)
+MY_EMAIL=your-email@gmail.com
+PASSWORD=your-app-password
+
+# CORS: add allowed origins in interface-adapters/middlewares/config/allowedOrigin.js
diff --git a/.eslintrc b/.eslintrc
index 384fc3f..c2e8c12 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -12,10 +12,7 @@
],
"rules": {
"prettier/prettier": "error",
- "indent": [
- "error",
- 2
- ],
+ "indent": "off",
"no-unused-vars": "warn",
"no-console": "off"
}
diff --git a/.gitignore b/.gitignore
index 0a593f5..d5d5515 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
node_modules
.env
-
+*.log
+logs/
/interface-adapters/middlewares/logs/
/interface-adapters/controllers/examples
diff --git a/.husky/pre-push b/.husky/pre-push
index 0569d94..d0d7de5 100755
--- a/.husky/pre-push
+++ b/.husky/pre-push
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
-yarn lint && yarn format
+yarn lint && yarn format && yarn test
diff --git a/README.md b/README.md
index a1941b2..3b638b5 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,7 @@
-# Clean code Architecture pattern applied to Node.js REST API Example
+
+
+# Clean code Architecture pattern applied to Node.js REST API Example
+

@@ -52,6 +55,23 @@ routes/ # Express route definitions
public/ # Static files and HTML views
```
+
+## Features
+
+- User registration and authentication (JWT)
+- Product CRUD operations
+- Blog and rating management
+- Role-based access control (admin, blocked users)
+- Input validation and error handling
+- Modular, testable codebase
+
+## Stack
+- Express.js
+- Javascript
+- MongoDB
+- Jest
+- Mongo-client + Mongosh
+
## Getting Started
### Prerequisites
@@ -76,6 +96,7 @@ public/ # Static files and HTML views
MONGO_URI=mongodb://localhost:27017/your-db
JWT_SECRET=your_jwt_secret
```
+
4. Start the server:
```bash
yarn dev
@@ -147,8 +168,11 @@ See the `routes/` directory for all endpoints. Example:
## Troubleshooting
-- See [troubleshooting.md](./troubleshooting.md) for common issues and solutions.
+- See [troubleshooting.md](./docs/troubleshooting.md) for common issues and solutions.
+
## License
ISC License. See [LICENSE](LICENSE).
+
+
diff --git a/application-business-rules/use-cases/blogs/blog-handlers.js b/application-business-rules/use-cases/blogs/blog-handlers.js
index 07c6e4c..29915a7 100644
--- a/application-business-rules/use-cases/blogs/blog-handlers.js
+++ b/application-business-rules/use-cases/blogs/blog-handlers.js
@@ -1,6 +1,6 @@
// Blog use cases (Clean Architecture)
module.exports = {
- createBlogUseCase: ({ dbBlogHandler, makeBlogModel, logEvents, errorHandlers }) =>
+ createBlogUseCase: ({ dbBlogHandler, makeBlogModel, logEvents }) =>
async function createBlogUseCaseHandler(blogData) {
try {
const validatedBlog = await makeBlogModel({ blogData });
@@ -16,7 +16,7 @@ module.exports = {
async function findAllBlogsUseCaseHandler() {
try {
const blogs = await dbBlogHandler.findAllBlogs();
- return blogs || [];
+ return Object.freeze(blogs.flat().data);
} catch (error) {
logEvents && logEvents(error.message, 'blogUseCase.log');
throw error;
@@ -35,7 +35,7 @@ module.exports = {
}
},
- updateBlogUseCase: ({ dbBlogHandler, makeBlogModel, logEvents, errorHandlers }) =>
+ updateBlogUseCase: ({ dbBlogHandler, makeBlogModel, logEvents }) =>
async function updateBlogUseCaseHandler({ blogId, updateData }) {
try {
const existingBlog = await dbBlogHandler.findOneBlog({ blogId });
diff --git a/application-business-rules/use-cases/products/product-handlers.js b/application-business-rules/use-cases/products/product-handlers.js
index af3fd6e..5585555 100644
--- a/application-business-rules/use-cases/products/product-handlers.js
+++ b/application-business-rules/use-cases/products/product-handlers.js
@@ -1,5 +1,7 @@
+'use strict';
+
const productValidationFcts = require('../../../enterprise-business-rules/validate-models/product-validation-fcts');
-// const { findAllProductUseCaseHandler } = require('./product-handlers');
+const { log } = require('../../../interface-adapters/middlewares/loggers/logger');
/**
* Creates a new product in the database using the provided product data.
@@ -24,12 +26,14 @@ const createProductUseCase = ({ makeProductModelHandler }) =>
const newProduct = await createProductDbHandler(validatedProductData);
return Object.freeze(newProduct);
} catch (error) {
- console.log('Error from create product handler: ', error);
+ log.error('Error from create product handler:', error.message);
throw new Error(error.message);
}
};
-//find one product from DB
+/**
+ * Fetches a single product by ID.
+ */
const findOneProductUseCase = ({ productValidation }) =>
async function findOneProductUseCaseHandler({
productId,
@@ -44,25 +48,28 @@ const findOneProductUseCase = ({ productValidation }) =>
const newProduct = await findOneProductDbHandler({ productId: uuid });
return Object.freeze(newProduct);
} catch (error) {
- console.log('Error from fetch one product handler: ', error);
+ log.error('Error from fetch one product handler:', error.message);
throw new Error(error.message);
}
};
-// find all product use case handler
+/**
+ * Fetches all products with optional filters.
+ */
const findAllProductsUseCase = () =>
async function findAllProductUseCaseHandler({ dbProductHandler, filterOptions }) {
try {
const allProducts = await dbProductHandler.findAllProductsDbHandler(filterOptions);
- // console.log("from find all products use case: ", allProducts);
- return Object.freeze(allProducts);
+ return Object.freeze(allProducts.data);
} catch (e) {
- console.log('Error from fetch all product handler: ', e);
+ log.error('Error from fetch all product handler:', e.message);
throw new Error(e.message);
}
};
-// delete product use case
+/**
+ * Deletes a product by ID.
+ */
const deleteProductUseCase = () =>
async function deleteProductUseCaseHandler({ productId, dbProductHandler, errorHandlers }) {
const { findOneProductDbHandler, deleteProductDbHandler } = dbProductHandler;
@@ -83,12 +90,14 @@ const deleteProductUseCase = () =>
};
return Object.freeze(result);
} catch (error) {
- console.log('Error from delete product handler: ', error);
+ log.error('Error from delete product handler:', error.message);
throw new Error(error.message);
}
};
-// update product
+/**
+ * Updates a product by ID.
+ */
const updateProductUseCase = ({ makeProductModelHandler }) =>
async function updateProductUseCaseHandler({
productId,
@@ -113,17 +122,17 @@ const updateProductUseCase = ({ makeProductModelHandler }) =>
errorHandlers,
});
- // store product in database mongodb
const newProduct = await updateProductDbHandler({ productId, ...productData });
- console.log(' from product handler after DB: ', newProduct);
return Object.freeze(newProduct);
} catch (error) {
- console.log('Error from update product handler: ', error);
+ log.error('Error from update product handler:', error.message);
throw new Error(error.message);
}
};
-// rate product in transaction with both Rate model and Product model
+/**
+ * Rates a product (creates rating and updates product aggregates in a transaction).
+ */
const rateProductUseCase = ({ makeProductRatingModelHandler }) =>
async function rateProductUseCaseHandler({
userId,
@@ -132,16 +141,13 @@ const rateProductUseCase = ({ makeProductRatingModelHandler }) =>
dbProductHandler,
errorHandlers,
}) {
- console.log('hit rating use case handler');
const ratingData = { ratingValue, userId, productId };
try {
- /* validate and build rating model */
const ratingModel = await makeProductRatingModelHandler({ errorHandlers, ...ratingData });
const newProduct = await dbProductHandler.rateProductDbHandler(ratingModel);
- console.log(' from rating product handler after DB: ', newProduct);
return Object.freeze(newProduct);
} catch (error) {
- console.log('Error from fetch one product handler: ', error);
+ log.error('Error from rating product handler:', error.message);
throw new Error(error.message);
}
};
diff --git a/application-business-rules/use-cases/user/index.js b/application-business-rules/use-cases/user/index.js
index 8b11900..524765a 100644
--- a/application-business-rules/use-cases/user/index.js
+++ b/application-business-rules/use-cases/user/index.js
@@ -1,92 +1,100 @@
-const userUseCases = require('./user-handlers');
+const authUseCases = require('./user-auth-usecases');
+const profileUseCases = require('./user-profile-usecases');
const { dbUserHandler } = require('../../../interface-adapters/database-access');
const { makeUser, validateId } = require('../../../enterprise-business-rules/entities');
const { RequiredParameterError } = require('../../../interface-adapters/validators-errors/errors');
-const { logEvents } = require('../../../interface-adapters/middlewares/loggers/logger');
+const { logEvents, log } = require('../../../interface-adapters/middlewares/loggers/logger');
const { makeHttpError } = require('../../../interface-adapters/validators-errors/http-error');
const entityModels = require('../../../enterprise-business-rules/entities');
-const registerUserUseCaseHandler = userUseCases.registerUserUseCase({
+// Auth Use Cases
+const registerUserUseCaseHandler = authUseCases.registerUserUseCase({
dbUserHandler,
entityModels,
logEvents,
+ log,
makeHttpError,
});
-
-const loginUserUseCaseHandler = userUseCases.loginUserUseCase({
+const loginUserUseCaseHandler = authUseCases.loginUserUseCase({
dbUserHandler,
logEvents,
+ log,
makeHttpError,
});
-
-const findOneUserUseCaseHandler = userUseCases.findOneUserUseCase({
+const logoutUseCaseHandler = authUseCases.logoutUseCase({ RequiredParameterError, logEvents, log });
+const refreshTokenUseCaseHandler = authUseCases.refreshTokenUseCase({
dbUserHandler,
- validateId,
+ RequiredParameterError,
logEvents,
+ log,
});
-
-const findAllUsersUseCaseHandler = userUseCases.findAllUsersUseCase({ dbUserHandler, logEvents });
-const logoutUseCaseHandler = userUseCases.logoutUseCase({ RequiredParameterError, logEvents });
-
-const refreshTokenUseCaseHandler = userUseCases.refreshTokenUseCase({
+const forgotPasswordUseCaseHandler = authUseCases.forgotPasswordUseCase({
dbUserHandler,
- RequiredParameterError,
logEvents,
+ log,
});
-
-const updateUserUseCaseHandler = userUseCases.updateUserUseCase({
+const resetPasswordUseCaseHandler = authUseCases.resetPasswordUseCase({
dbUserHandler,
- makeUser,
- validateId,
- RequiredParameterError,
logEvents,
+ log,
makeHttpError,
});
-const deleteUserUseCaseHandler = userUseCases.deleteUserUseCase({
+const findAllUsersUseCaseHandler = profileUseCases.findAllUsersUseCase({
+ dbUserHandler,
+ logEvents,
+});
+const findOneUserUseCaseHandler = profileUseCases.findOneUserUseCase({
dbUserHandler,
validateId,
- RequiredParameterError,
logEvents,
+ log,
});
-
-const blockUserUseCaseHandler = userUseCases.blockUserUseCase({
+const updateUserUseCaseHandler = profileUseCases.updateUserUseCase({
dbUserHandler,
+ makeUser,
validateId,
RequiredParameterError,
logEvents,
+ log,
+ makeHttpError,
});
-
-const unBlockUserUseCaseHandler = userUseCases.unBlockUserUseCase({
+const deleteUserUseCaseHandler = profileUseCases.deleteUserUseCase({
dbUserHandler,
validateId,
RequiredParameterError,
logEvents,
+ log,
});
-
-const forgotPasswordUseCaseHandler = userUseCases.forgotPasswordUseCase({
+const blockUserUseCaseHandler = profileUseCases.blockUserUseCase({
dbUserHandler,
+ validateId,
+ RequiredParameterError,
logEvents,
+ log,
});
-
-const resetPasswordUseCaseHandler = userUseCases.resetPasswordUseCase({
+const unBlockUserUseCaseHandler = profileUseCases.unBlockUserUseCase({
dbUserHandler,
+ validateId,
+ RequiredParameterError,
logEvents,
- makeHttpError,
+ log,
});
module.exports = {
+ // Auth
+ registerUserUseCaseHandler,
loginUserUseCaseHandler,
logoutUseCaseHandler,
refreshTokenUseCaseHandler,
- updateUserUseCaseHandler,
- deleteUserUseCaseHandler,
+ forgotPasswordUseCaseHandler,
+ resetPasswordUseCaseHandler,
+ // Profile
findAllUsersUseCaseHandler,
findOneUserUseCaseHandler,
- registerUserUseCaseHandler,
+ updateUserUseCaseHandler,
+ deleteUserUseCaseHandler,
blockUserUseCaseHandler,
unBlockUserUseCaseHandler,
- forgotPasswordUseCaseHandler,
- resetPasswordUseCaseHandler,
};
diff --git a/application-business-rules/use-cases/user/user-auth-usecases.js b/application-business-rules/use-cases/user/user-auth-usecases.js
new file mode 100644
index 0000000..f3714fc
--- /dev/null
+++ b/application-business-rules/use-cases/user/user-auth-usecases.js
@@ -0,0 +1,8 @@
+module.exports = {
+ registerUserUseCase: require('./user-handlers').registerUserUseCase,
+ loginUserUseCase: require('./user-handlers').loginUserUseCase,
+ refreshTokenUseCase: require('./user-handlers').refreshTokenUseCase,
+ logoutUseCase: require('./user-handlers').logoutUseCase,
+ forgotPasswordUseCase: require('./user-handlers').forgotPasswordUseCase,
+ resetPasswordUseCase: require('./user-handlers').resetPasswordUseCase,
+};
diff --git a/application-business-rules/use-cases/user/user-handlers.js b/application-business-rules/use-cases/user/user-handlers.js
index 0ad9c03..714d813 100644
--- a/application-business-rules/use-cases/user/user-handlers.js
+++ b/application-business-rules/use-cases/user/user-handlers.js
@@ -7,7 +7,7 @@ module.exports = {
* @return {Promise