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
19 changes: 19 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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
5 changes: 1 addition & 4 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@
],
"rules": {
"prettier/prettier": "error",
"indent": [
"error",
2
],
"indent": "off",
"no-unused-vars": "warn",
"no-console": "off"
}
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules
.env

*.log
logs/
/interface-adapters/middlewares/logs/
/interface-adapters/controllers/examples
2 changes: 1 addition & 1 deletion .husky/pre-push
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

yarn lint && yarn format
yarn lint && yarn format && yarn test
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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


<div style="width:100%; text-align:center">
<img src="public/images/clean-code_arch.jpeg" width="600">
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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).


6 changes: 3 additions & 3 deletions application-business-rules/use-cases/blogs/blog-handlers.js
Original file line number Diff line number Diff line change
@@ -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 });
Expand All @@ -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;
Expand All @@ -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 });
Expand Down
44 changes: 25 additions & 19 deletions application-business-rules/use-cases/products/product-handlers.js
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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,
Expand All @@ -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;
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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);
}
};
Expand Down
78 changes: 43 additions & 35 deletions application-business-rules/use-cases/user/index.js
Original file line number Diff line number Diff line change
@@ -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,
};
Original file line number Diff line number Diff line change
@@ -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,
};
Loading
Loading