├── README.md
├── client
├── .gitignore
├── App.js
├── Main.js
├── app.json
├── assets
│ ├── adaptive-icon.png
│ ├── favicon.png
│ ├── icon.png
│ └── splash.png
├── babel.config.js
├── components
│ ├── Banner
│ │ └── Banner.js
│ ├── Form
│ │ ├── InputBox.js
│ │ └── OrderItem.js
│ ├── Layout
│ │ ├── Footer.js
│ │ ├── Header.js
│ │ └── Layout.js
│ ├── Products
│ │ ├── Products.js
│ │ └── ProductsCard.js
│ ├── cart
│ │ ├── CartItem.js
│ │ └── PriceTable.js
│ └── category
│ │ └── Categories.js
├── data
│ ├── BannerData.js
│ ├── CartData.js
│ ├── CategoriesData.js
│ ├── OrderData.js
│ ├── ProductsData.js
│ └── userData.js
├── hooks
│ └── customeHook.js
├── package-lock.json
├── package.json
├── redux
│ ├── features
│ │ └── auth
│ │ │ ├── userActions.js
│ │ │ └── userReducer.js
│ └── store.js
└── screens
│ ├── About.js
│ ├── Account
│ ├── Account.js
│ ├── MyOrders.js
│ ├── Notifications.js
│ └── Profile.js
│ ├── Admin
│ └── Dashboard.js
│ ├── Cart.js
│ ├── Checkout.js
│ ├── Home.js
│ ├── Payments.js
│ ├── ProductDetails.js
│ └── auth
│ ├── Login.js
│ └── Register.js
└── server
├── config
└── db.js
├── controllers
├── categoryController.js
├── orderController.js
├── productController.js
├── testController.js
└── userController.js
├── middlewares
├── authMiddleware.js
└── multer.js
├── models
├── categoryModel.js
├── orderModel.js
├── productModel.js
└── userModel.js
├── package-lock.json
├── package.json
├── routes
├── categoryRoutes.js
├── orderRoutes.js
├── productRoutes.js
├── testRoutes.js
└── userRoutes.js
├── server.js
└── utils
└── features.js
/README.md:
--------------------------------------------------------------------------------
1 | # React Native Ecommerce App | Full Stack Project With MERN STACK
2 | React Native Ecommerce APP | Full Stack Project (React Native/ Node Express JS / Mongodb Database/ REDUX TOOL KIT /Payment Gateway/ Social Logins / Image Uploads etc)
3 |
4 |
5 | # Project Playlist
6 | https://www.youtube.com/playlist?list=PLuHGmgpyHfRzZy2xPF2Vn68sprpaZmCTV
7 |
8 | # please like comment and subscribe Techinfoyt Youtube Channel
9 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2 |
3 | # dependencies
4 | node_modules/
5 |
6 | # Expo
7 | .expo/
8 | dist/
9 | web-build/
10 |
11 | # Native
12 | *.orig.*
13 | *.jks
14 | *.p8
15 | *.p12
16 | *.key
17 | *.mobileprovision
18 |
19 | # Metro
20 | .metro-health-check*
21 |
22 | # debug
23 | npm-debug.*
24 | yarn-debug.*
25 | yarn-error.*
26 |
27 | # macOS
28 | .DS_Store
29 | *.pem
30 |
31 | # local env files
32 | .env*.local
33 |
34 | # typescript
35 | *.tsbuildinfo
36 |
--------------------------------------------------------------------------------
/client/App.js:
--------------------------------------------------------------------------------
1 | import { Provider } from "react-redux";
2 |
3 | import store from "./redux/store";
4 | import Main from "./Main";
5 |
6 | export default function App() {
7 | return (
8 |
9 |
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/client/Main.js:
--------------------------------------------------------------------------------
1 | import { StatusBar } from "expo-status-bar";
2 | import { StyleSheet, Text, View } from "react-native";
3 | import { NavigationContainer } from "@react-navigation/native";
4 | import { createNativeStackNavigator } from "@react-navigation/native-stack";
5 |
6 | import Home from "./screens/Home";
7 | import About from "./screens/About";
8 | import ProductDetails from "./screens/ProductDetails";
9 | import Cart from "./screens/Cart";
10 | import Checkout from "./screens/Checkout";
11 | import Payments from "./screens/Payments";
12 | import Login from "./screens/auth/Login";
13 | import Register from "./screens/auth/Register";
14 | import Account from "./screens/Account/Account";
15 | import Notifications from "./screens/Account/Notifications";
16 | import Profile from "./screens/Account/Profile";
17 | import MyOrders from "./screens/Account/MyOrders";
18 | import Dashboard from "./screens/Admin/Dashboard";
19 |
20 | import { useSelector, useDispatch } from "react-redux";
21 | import { useEffect, useState } from "react";
22 | import AsyncStorage from "@react-native-async-storage/async-storage";
23 |
24 | //routes
25 | const Stack = createNativeStackNavigator();
26 |
27 | export default function Main() {
28 | const [isAuth, setIsAuth] = useState(null);
29 | // get user
30 | useEffect(() => {
31 | const getUserLocalData = async () => {
32 | let data = await AsyncStorage.getItem("@auth");
33 | setIsAuth(data);
34 | // let loginData = JSON.parse(data);
35 | console.log("user login data ==>", data);
36 | };
37 | getUserLocalData();
38 | }, []);
39 | return (
40 | <>
41 |
42 |
43 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | {!isAuth && (
62 | <>
63 |
68 |
73 | >
74 | )}
75 |
76 |
77 | >
78 | );
79 | }
80 |
--------------------------------------------------------------------------------
/client/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "client",
4 | "slug": "client",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "userInterfaceStyle": "light",
9 | "splash": {
10 | "image": "./assets/splash.png",
11 | "resizeMode": "contain",
12 | "backgroundColor": "#ffffff"
13 | },
14 | "assetBundlePatterns": [
15 | "**/*"
16 | ],
17 | "ios": {
18 | "supportsTablet": true
19 | },
20 | "android": {
21 | "adaptiveIcon": {
22 | "foregroundImage": "./assets/adaptive-icon.png",
23 | "backgroundColor": "#ffffff"
24 | }
25 | },
26 | "web": {
27 | "favicon": "./assets/favicon.png"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/client/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techinfo-youtube/react-native-ecommerce-mern-stack-project/dd64291cbd42a44d3e7b6dc5599f5f8490ea4412/client/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/client/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techinfo-youtube/react-native-ecommerce-mern-stack-project/dd64291cbd42a44d3e7b6dc5599f5f8490ea4412/client/assets/favicon.png
--------------------------------------------------------------------------------
/client/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techinfo-youtube/react-native-ecommerce-mern-stack-project/dd64291cbd42a44d3e7b6dc5599f5f8490ea4412/client/assets/icon.png
--------------------------------------------------------------------------------
/client/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/techinfo-youtube/react-native-ecommerce-mern-stack-project/dd64291cbd42a44d3e7b6dc5599f5f8490ea4412/client/assets/splash.png
--------------------------------------------------------------------------------
/client/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/client/components/Banner/Banner.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | StyleSheet,
4 | View,
5 | Text,
6 | Image,
7 | Dimensions,
8 | Pressable,
9 | } from "react-native";
10 | import Carousel, { PaginationLight } from "react-native-x-carousel";
11 | import { BannerData } from "../../data/BannerData";
12 |
13 | const { width } = Dimensions.get("window");
14 | const Banner = () => {
15 | const renderItem = (data) => (
16 |
17 | alert(data._id)}>
18 |
19 |
20 |
26 | {data.cornerLabelText}
27 |
28 |
29 |
30 |
31 | );
32 |
33 | return (
34 |
35 |
42 |
43 | );
44 | };
45 | const styles = StyleSheet.create({
46 | container: {
47 | backgroundColor: "#fff",
48 | alignItems: "center",
49 | justifyContent: "center",
50 | },
51 | cardContainer: {
52 | alignItems: "center",
53 | justifyContent: "center",
54 | width,
55 | },
56 | cardWrapper: {
57 | // borderRadius: 8,
58 | overflow: "hidden",
59 | },
60 | card: {
61 | width: width * 1,
62 | height: width * 0.4,
63 | },
64 | cornerLabel: {
65 | position: "absolute",
66 | bottom: 0,
67 | right: 0,
68 | borderTopLeftRadius: 8,
69 | },
70 | cornerLabelText: {
71 | fontSize: 12,
72 | color: "#fff",
73 | fontWeight: "600",
74 | paddingLeft: 5,
75 | paddingRight: 5,
76 | paddingTop: 2,
77 | paddingBottom: 2,
78 | },
79 | });
80 | export default Banner;
81 |
--------------------------------------------------------------------------------
/client/components/Form/InputBox.js:
--------------------------------------------------------------------------------
1 | import { View, Text, TextInput, StyleSheet } from "react-native";
2 | import React from "react";
3 |
4 | const InputBox = ({
5 | value,
6 | setValue,
7 | autoComplete,
8 | placeholder,
9 | secureTextEntry,
10 | }) => {
11 | return (
12 |
13 | setValue(text)}
21 | />
22 |
23 | );
24 | };
25 | const styles = StyleSheet.create({
26 | container: {
27 | justifyContent: "center",
28 | alignItems: "center",
29 | marginVertical: 10,
30 | },
31 | input: {
32 | width: "80%",
33 | backgroundColor: "#ffffff",
34 | height: 40,
35 | paddingLeft: 10,
36 | borderRadius: 10,
37 | color: "#000000",
38 | borderWidth: 1,
39 | borderColor: "gray",
40 | },
41 | });
42 | export default InputBox;
43 |
--------------------------------------------------------------------------------
/client/components/Form/OrderItem.js:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet } from "react-native";
2 | import React from "react";
3 |
4 | const OrderItem = ({ order }) => {
5 | return (
6 |
7 |
8 | Order ID : {order._id}
9 | Date : {order.date}
10 |
11 | Product name : {order.productInfo.name}
12 | Price : {order.productInfo.price}
13 | Quantity : {order.productInfo.qty}
14 | Total AMount : {order.totalAmount} $
15 | Order Status : {order.status}
16 |
17 | );
18 | };
19 | const styles = StyleSheet.create({
20 | container: {
21 | backgroundColor: "#ffffff",
22 | margin: 10,
23 | padding: 10,
24 | borderRadius: 10,
25 | },
26 | orderinfo: {
27 | flexDirection: "row",
28 | justifyContent: "space-between",
29 | borderBottomWidth: 1,
30 | borderColor: "lightgray",
31 | paddingBottom: 5,
32 | },
33 | status: {
34 | borderTopWidth: 1,
35 | fontWeight: "bold",
36 | borderColor: "lightgray",
37 | padding: 5,
38 | },
39 | });
40 | export default OrderItem;
41 |
--------------------------------------------------------------------------------
/client/components/Layout/Footer.js:
--------------------------------------------------------------------------------
1 | import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
2 | import React from "react";
3 | import AntDesign from "react-native-vector-icons/AntDesign";
4 | import { useRoute, useNavigation } from "@react-navigation/native";
5 | import { useReduxStateHook } from "../../hooks/customeHook";
6 | import { useDispatch } from "react-redux";
7 | import { logout } from "../../redux/features/auth/userActions";
8 | import AsyncStorage from "@react-native-async-storage/async-storage";
9 |
10 | const Footer = () => {
11 | const route = useRoute();
12 | const navigation = useNavigation();
13 | const dispatch = useDispatch();
14 |
15 | const loading = useReduxStateHook(navigation, "login");
16 | return (
17 |
18 | navigation.navigate("home")}
21 | >
22 |
26 |
27 | Home
28 |
29 |
30 | navigation.navigate("notifications")}
33 | >
34 |
38 |
44 | notification
45 |
46 |
47 | navigation.navigate("account")}
50 | >
51 |
55 |
58 | Account
59 |
60 |
61 | navigation.navigate("cart")}
64 | >
65 |
69 |
70 | Cart
71 |
72 |
73 | {
76 | dispatch(logout());
77 | await AsyncStorage.removeItem("@auth");
78 | }}
79 | >
80 |
81 | Logout
82 |
83 |
84 | );
85 | };
86 |
87 | const styles = StyleSheet.create({
88 | container: {
89 | flexDirection: "row",
90 | justifyContent: "space-between",
91 | paddingHorizontal: 10,
92 | },
93 | menuContainer: {
94 | alignItems: "center",
95 | justifyContent: "center",
96 | },
97 | icon: {
98 | fontSize: 25,
99 | color: "#000000",
100 | },
101 | iconText: {
102 | color: "#000000",
103 | fontSize: 10,
104 | },
105 | active: {
106 | color: "blue",
107 | },
108 | });
109 | export default Footer;
110 |
--------------------------------------------------------------------------------
/client/components/Layout/Header.js:
--------------------------------------------------------------------------------
1 | import {
2 | View,
3 | Text,
4 | StyleSheet,
5 | TextInput,
6 | TouchableOpacity,
7 | } from "react-native";
8 | import React, { useState } from "react";
9 | import FontAwesome from "react-native-vector-icons/FontAwesome";
10 | const Header = () => {
11 | const [searchText, setSearchText] = useState("");
12 | //funciotn for search
13 | const handleSearch = () => {
14 | console.log(searchText);
15 | setSearchText("");
16 | };
17 | return (
18 |
24 |
25 | setSearchText(text)}
29 | />
30 |
31 |
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | const styles = StyleSheet.create({
39 | container: {
40 | display: "flex",
41 | flex: 1,
42 | flexDirection: "row",
43 | alignItems: "center",
44 | paddingHorizontal: 15,
45 | },
46 | inputBox: {
47 | borderWidth: 0.3,
48 | width: "100%",
49 | position: "absolute",
50 | left: 15,
51 | height: 40,
52 | color: "#000000",
53 | backgroundColor: "#ffffff",
54 | paddingLeft: 10,
55 | fontSize: 16,
56 | borderRadius: 5,
57 | },
58 | searchBtn: {
59 | position: "absolute",
60 | left: "95%",
61 | },
62 | icon: {
63 | color: "#000000",
64 | fontSize: 18,
65 | },
66 | });
67 |
68 | export default Header;
69 |
--------------------------------------------------------------------------------
/client/components/Layout/Layout.js:
--------------------------------------------------------------------------------
1 | import { View, Text, StatusBar, StyleSheet } from "react-native";
2 | import React from "react";
3 | import Header from "./Header";
4 | import Footer from "./Footer";
5 |
6 | const Layout = ({ children }) => {
7 | return (
8 | <>
9 |
10 | {children}
11 |
12 |
13 |
14 | >
15 | );
16 | };
17 |
18 | const styles = StyleSheet.create({
19 | footer: {
20 | display: "flex",
21 | width: "100%",
22 | flex: 1,
23 | justifyContent: "flex-end",
24 | zIndex: 100,
25 | borderTopWidth: 1,
26 | borderColor: "lightgray",
27 | position: "absolute",
28 | bottom: 0,
29 | padding: 10,
30 | },
31 | });
32 | export default Layout;
33 |
--------------------------------------------------------------------------------
/client/components/Products/Products.js:
--------------------------------------------------------------------------------
1 | import { View, Text } from "react-native";
2 | import React from "react";
3 | import ProductsCard from "./ProductsCard";
4 | import { ProductsData } from "../../data/ProductsData";
5 |
6 | const Products = () => {
7 | return (
8 |
9 | {ProductsData.map((p) => (
10 |
11 | ))}
12 |
13 | );
14 | };
15 |
16 | export default Products;
17 |
--------------------------------------------------------------------------------
/client/components/Products/ProductsCard.js:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native";
2 | import React from "react";
3 | import { useNavigation } from "@react-navigation/native";
4 |
5 | const ProductsCard = ({ p }) => {
6 | const navigation = useNavigation();
7 |
8 | //more detaisl btn
9 | const handleMoreButton = (id) => {
10 | navigation.navigate("productDetails", { _id: id });
11 | console.log(id);
12 | };
13 |
14 | //ADD TO CART
15 | const handleAddToCart = () => {
16 | alert("added to cart");
17 | };
18 | return (
19 |
20 |
21 |
22 | {p?.name}
23 |
24 | {p?.description.substring(0, 30)} ...more
25 |
26 |
27 | handleMoreButton(p._id)}
30 | >
31 | Details
32 |
33 |
34 | ADD TO CART
35 |
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | const styles = StyleSheet.create({
43 | card: {
44 | borderWidth: 1,
45 | borderColor: "lightgray",
46 | marginVertical: 5,
47 | marginHorizontal: 8,
48 | width: "45%",
49 | padding: 10,
50 | backgroundColor: "#ffffff",
51 | justifyContent: "center",
52 | },
53 | cardImage: {
54 | height: 120,
55 | width: "100%",
56 | marginBottom: 10,
57 | },
58 | cardTitle: {
59 | fontSize: 10,
60 | fontWeight: "bold",
61 | marginBottom: 5,
62 | },
63 | cardDesc: {
64 | fontSize: 10,
65 | textAlign: "left",
66 | },
67 | BtnContainer: {
68 | marginTop: 5,
69 | flexDirection: "row",
70 | justifyContent: "space-between",
71 | alignItems: "center",
72 | },
73 | btn: {
74 | backgroundColor: "#000000",
75 | height: 20,
76 | width: 75,
77 | borderRadius: 5,
78 | justifyContent: "center",
79 | },
80 | btnCart: {
81 | backgroundColor: "orange",
82 | height: 20,
83 | width: 75,
84 | borderRadius: 5,
85 | justifyContent: "center",
86 | },
87 | btnText: {
88 | color: "#ffffff",
89 | textAlign: "center",
90 | fontSize: 10,
91 | fontWeight: "bold",
92 | },
93 | });
94 |
95 | export default ProductsCard;
96 |
--------------------------------------------------------------------------------
/client/components/cart/CartItem.js:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native";
2 | import React, { useState } from "react";
3 |
4 | const Cartitem = ({ item }) => {
5 | const [qty, setQty] = useState(1);
6 | // Handle function for + -
7 | const handleAddQty = () => {
8 | if (qty === 10) return alert("you cant add more than 10 quantity");
9 | setQty((prev) => prev + 1);
10 | };
11 | const handleRemoveQty = () => {
12 | if (qty <= 1) return;
13 | setQty((prev) => prev - 1);
14 | };
15 | return (
16 |
17 |
18 |
19 | {item?.name}
20 | Price : {item?.price} $
21 |
22 |
23 |
24 | -
25 |
26 | {qty}
27 |
28 | +
29 |
30 |
31 |
32 | );
33 | };
34 | const styles = StyleSheet.create({
35 | container: {
36 | margin: 10,
37 | backgroundColor: "#ffffff",
38 | borderRadius: 10,
39 | flexDirection: "row",
40 | justifyContent: "space-evenly",
41 | alignItems: "center",
42 | },
43 | image: {
44 | height: 50,
45 | width: 50,
46 | resizeMode: "contain",
47 | },
48 | name: {
49 | fontSize: 10,
50 | },
51 | btnContainer: {
52 | flexDirection: "row",
53 | justifyContent: "center",
54 | alignItems: "center",
55 | marginVertical: 20,
56 | marginHorizontal: 10,
57 | },
58 | btnQty: {
59 | backgroundColor: "lightgray",
60 | width: 40,
61 | alignItems: "center",
62 | marginHorizontal: 10,
63 | },
64 | btnQtyText: {
65 | fontSize: 20,
66 | },
67 | });
68 | export default Cartitem;
69 |
--------------------------------------------------------------------------------
/client/components/cart/PriceTable.js:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet } from "react-native";
2 | import React from "react";
3 |
4 | const PriceTable = ({ price, title }) => {
5 | return (
6 |
7 | {title}
8 | {price} $
9 |
10 | );
11 | };
12 |
13 | const styles = StyleSheet.create({
14 | container: {
15 | flexDirection: "row",
16 | justifyContent: "space-between",
17 | paddingHorizontal: 30,
18 | alignItems: "center",
19 | },
20 | });
21 | export default PriceTable;
22 |
--------------------------------------------------------------------------------
/client/components/category/Categories.js:
--------------------------------------------------------------------------------
1 | import {
2 | View,
3 | Text,
4 | TouchableOpacity,
5 | StyleSheet,
6 | ScrollView,
7 | } from "react-native";
8 | import React from "react";
9 | import { categoriesData } from "../../data/CategoriesData";
10 | import AntDesign from "react-native-vector-icons/AntDesign";
11 | import { useNavigation } from "@react-navigation/native";
12 | const Categories = () => {
13 | const navigation = useNavigation();
14 | return (
15 |
16 |
17 | {categoriesData?.map((item) => (
18 |
19 | navigation.navigate(item.path)}
22 | >
23 |
24 | {item.name}
25 |
26 |
27 | ))}
28 |
29 |
30 | );
31 | };
32 |
33 | const styles = StyleSheet.create({
34 | container: {
35 | backgroundColor: "#ffffff",
36 | padding: 5,
37 | flexDirection: "row",
38 | },
39 | catContainer: {
40 | padding: 15,
41 | justifyContent: "center",
42 | alignItems: "center",
43 | },
44 | catIcon: {
45 | fontSize: 30,
46 | verticalAlign: "top",
47 | },
48 | catTitle: {
49 | fontSize: 12,
50 | },
51 | });
52 | export default Categories;
53 |
--------------------------------------------------------------------------------
/client/data/BannerData.js:
--------------------------------------------------------------------------------
1 | export const BannerData = [
2 | {
3 | _id: 1,
4 | coverImageUri:
5 | "https://user-images.githubusercontent.com/6414178/73920321-2357b680-4900-11ea-89d5-2e8cbecec9f6.jpg",
6 | cornerLabelColor: "#FFD300",
7 | cornerLabelText: "GOTY",
8 | },
9 | {
10 | _id: 2,
11 | coverImageUri:
12 | "https://user-images.githubusercontent.com/6414178/73920358-336f9600-4900-11ea-8eec-cc919b991e90.jpg",
13 | cornerLabelColor: "#0080ff",
14 | cornerLabelText: "NEW",
15 | },
16 | {
17 | _id: 3,
18 | coverImageUri:
19 | "https://user-images.githubusercontent.com/6414178/73927874-25744200-490d-11ea-940f-db3e5dbd8b2b.jpg",
20 | cornerLabelColor: "#2ECC40",
21 | cornerLabelText: "-75%",
22 | },
23 | {
24 | _id: 4,
25 | coverImageUri:
26 | "https://user-images.githubusercontent.com/6414178/73920399-45e9cf80-4900-11ea-9d5b-743fe5e8b9a4.jpg",
27 | cornerLabelColor: "#2ECC40",
28 | cornerLabelText: "-20%",
29 | },
30 | ];
31 |
--------------------------------------------------------------------------------
/client/data/CartData.js:
--------------------------------------------------------------------------------
1 | export const CartData = [
2 | {
3 | _id: 1,
4 | name: "Apple iPhone 15 Plus (256 GB) - Pink",
5 | description:
6 | "DYNAMIC ISLAND COMES TO IPHONE 15, INNOVATIVE DESIGN 48MP MAIN CAMERA WITH 2X TELEPHOTO DYNAMIC ISLAND COMES TO IPHONE 15, INNOVATIVE DESIGN 48MP MAIN CAMERA WITH 2X TELEPHOTO DYNAMIC ISLAND COMES TO IPHONE 15, INNOVATIVE DESIGN 48MP MAIN CAMERA WITH 2X TELEPHOTO",
7 | price: 900,
8 | quantity: 30,
9 | category: "mobile",
10 | imageUrl: "https://m.media-amazon.com/images/I/71bErtQPC3L._SL1500_.jpg",
11 | },
12 | ];
13 |
--------------------------------------------------------------------------------
/client/data/CategoriesData.js:
--------------------------------------------------------------------------------
1 | export const categoriesData = [
2 | {
3 | _id: 1,
4 | name: "Mobile",
5 | path: "mobile",
6 | icon: "mobile1",
7 | },
8 | {
9 | _id: 2,
10 | name: "Laptop",
11 | path: "laptop",
12 | icon: "laptop",
13 | },
14 | {
15 | _id: 3,
16 | name: "Desktop",
17 | path: "desktop",
18 | icon: "iconfontdesktop",
19 | },
20 | {
21 | _id: 4,
22 | name: "Tablet",
23 | path: "tablet",
24 | icon: "tablet1",
25 | },
26 | {
27 | _id: 5,
28 | name: "Headfones",
29 | path: "headfones",
30 | icon: "customerservice",
31 | },
32 | {
33 | _id: 6,
34 | name: "Accessories",
35 | path: "accessories",
36 | icon: "windowso",
37 | },
38 | ];
39 |
--------------------------------------------------------------------------------
/client/data/OrderData.js:
--------------------------------------------------------------------------------
1 | export const orderData = [
2 | {
3 | _id: 1,
4 | totalAmount: 1001,
5 | status: "processing",
6 | date: "1st Nov",
7 | paymentMode: "online",
8 | productInfo: {
9 | _id: 1,
10 | name: "Iphone 15",
11 | qty: 1,
12 | price: 999,
13 | },
14 | },
15 | ];
16 |
--------------------------------------------------------------------------------
/client/data/ProductsData.js:
--------------------------------------------------------------------------------
1 | export const ProductsData = [
2 | {
3 | _id: 1,
4 | name: "Apple iPhone 15 Plus (256 GB) - Pink",
5 | description:
6 | "DYNAMIC ISLAND COMES TO IPHONE 15, INNOVATIVE DESIGN 48MP MAIN CAMERA WITH 2X TELEPHOTO DYNAMIC ISLAND COMES TO IPHONE 15, INNOVATIVE DESIGN 48MP MAIN CAMERA WITH 2X TELEPHOTO DYNAMIC ISLAND COMES TO IPHONE 15, INNOVATIVE DESIGN 48MP MAIN CAMERA WITH 2X TELEPHOTO",
7 | price: 900,
8 | quantity: 30,
9 | category: "mobile",
10 | imageUrl: "https://m.media-amazon.com/images/I/71bErtQPC3L._SL1500_.jpg",
11 | },
12 | ];
13 |
--------------------------------------------------------------------------------
/client/data/userData.js:
--------------------------------------------------------------------------------
1 | export const userData = {
2 | _id: 1,
3 | profilePic: "https://cdn-icons-png.flaticon.com/512/149/149071.png",
4 | name: "techinfoyt",
5 | email: "t@t.com",
6 | password: "1234567890",
7 | address: "somewhere on earth",
8 | city: "mumbai",
9 | contact: "1234567890",
10 | };
11 |
--------------------------------------------------------------------------------
/client/hooks/customeHook.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { useSelector, useDispatch } from "react-redux";
3 | export const useReduxStateHook = (navigation, path = "login") => {
4 | const { loading, error, message } = useSelector((state) => state.user);
5 | const dispatch = useDispatch();
6 | useEffect(() => {
7 | if (error) {
8 | alert(error);
9 | dispatch({ type: "clearError" });
10 | }
11 | if (message) {
12 | alert(message);
13 | dispatch({ type: "clearMessage" });
14 | navigation.reset({
15 | index: 0,
16 | routes: [{ name: path }],
17 | });
18 | }
19 | }, [error, dispatch, message]);
20 | return loading;
21 | };
22 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "1.0.0",
4 | "main": "node_modules/expo/AppEntry.js",
5 | "scripts": {
6 | "start": "expo start",
7 | "android": "expo start --android",
8 | "ios": "expo start --ios",
9 | "web": "expo start --web"
10 | },
11 | "dependencies": {
12 | "@react-native-async-storage/async-storage": "^1.19.5",
13 | "@react-navigation/native": "^6.1.7",
14 | "@react-navigation/native-stack": "^6.9.13",
15 | "@reduxjs/toolkit": "^1.9.7",
16 | "axios": "^1.6.2",
17 | "expo": "~49.0.10",
18 | "expo-status-bar": "~1.6.0",
19 | "react": "18.2.0",
20 | "react-native": "0.72.4",
21 | "react-native-safe-area-context": "4.6.3",
22 | "react-native-screens": "~3.22.0",
23 | "react-native-vector-icons": "^10.0.0",
24 | "react-native-x-carousel": "^1.0.1",
25 | "react-redux": "^8.1.3"
26 | },
27 | "devDependencies": {
28 | "@babel/core": "^7.20.0"
29 | },
30 | "private": true
31 | }
32 |
--------------------------------------------------------------------------------
/client/redux/features/auth/userActions.js:
--------------------------------------------------------------------------------
1 | import { server } from "../../store";
2 | import axios from "axios";
3 | import AsyncStorage from "@react-native-async-storage/async-storage";
4 |
5 | // action login
6 | export const login = (email, password) => async (dispatch) => {
7 | try {
8 | dispatch({
9 | type: "loginRequest",
10 | });
11 | // hitting node login api request
12 | const { data } = await axios.post(
13 | `${server}/user/login`,
14 | { email, password },
15 | {
16 | headers: {
17 | "Content-Type": "application/json",
18 | },
19 | }
20 | );
21 | dispatch({
22 | type: "logingSucess",
23 | payload: data,
24 | });
25 | await AsyncStorage.setItem("@auth", data?.token);
26 | } catch (error) {
27 | dispatch({
28 | type: "loginFail",
29 | payload: error.response.data.message,
30 | });
31 | }
32 | };
33 | // register action
34 | export const register = (formData) => async (dispatch) => {
35 | try {
36 | dispatch({
37 | type: "registerRequest",
38 | });
39 | // hitapi register
40 | const { data } = await axios.post(`${server}/user/register`, formData, {
41 | headers: {
42 | "Content-Type": "application/json",
43 | },
44 | });
45 | dispatch({
46 | type: "registerSucess",
47 | payload: data.message,
48 | });
49 | } catch (error) {
50 | console.log(error);
51 | dispatch({
52 | type: "registerFail",
53 | payload: error.response.data.message,
54 | });
55 | }
56 | };
57 |
58 | // GET USER DATTA ACTION\
59 | export const getUserData = () => async (dispatch) => {
60 | try {
61 | dispatch({
62 | type: "getUserDataRequest",
63 | });
64 | // hitting node login api request
65 | const { data } = await axios.post(`${server}/user/profile`);
66 | dispatch({
67 | type: "getUserDataSucess",
68 | payload: data?.user,
69 | });
70 | } catch (error) {
71 | dispatch({
72 | type: "getUserDataFail",
73 | payload: error.response.data.message,
74 | });
75 | }
76 | };
77 | // LOGOUT ACTION
78 | export const logout = () => async (dispatch) => {
79 | try {
80 | dispatch({
81 | type: "logoutRequest",
82 | });
83 | // hitting node login api request
84 | const { data } = await axios.get(`${server}/user/logout`);
85 | dispatch({
86 | type: "logoutSucess",
87 | payload: data?.message,
88 | });
89 | } catch (error) {
90 | dispatch({
91 | type: "logoutFail",
92 | payload: error.response.data.message,
93 | });
94 | }
95 | };
96 |
--------------------------------------------------------------------------------
/client/redux/features/auth/userReducer.js:
--------------------------------------------------------------------------------
1 | import { createReducer } from "@reduxjs/toolkit";
2 |
3 | export const userReducer = createReducer({ token: null }, (builder) => {
4 | // LOGIN CASE
5 | builder.addCase("loginRequest", (state, action) => {
6 | state.loading = true;
7 | });
8 | builder.addCase("logingSucess", (state, action) => {
9 | state.loading = false;
10 | state.message = action.payload.message;
11 | state.isAuth = true;
12 | state.token = action.payload.token;
13 | });
14 | builder.addCase("loginFail", (state, action) => {
15 | state.isAuth = false;
16 | state.error = action.payload;
17 | });
18 | // ERROR MESSAGE CASE
19 | builder.addCase("clearError", (state) => {
20 | state.error = null;
21 | });
22 | builder.addCase("clearMessage", (state) => {
23 | state.message = null;
24 | });
25 | //REGISTER
26 | builder.addCase("registerRequest", (state, action) => {
27 | state.loading = true;
28 | });
29 | builder.addCase("registerSucess", (state, action) => {
30 | state.loading = false;
31 | // state.isAuth = true;
32 | state.message = action.payload;
33 | });
34 | builder.addCase("registerFail", (state, action) => {
35 | state.isAuth = false;
36 | state.error = action.payload;
37 | });
38 | // GET USER DATA
39 | builder.addCase("getUserDataRequest", (state, action) => {
40 | state.loading = true;
41 | });
42 | builder.addCase("getUserDataSucess", (state, action) => {
43 | state.loading = false;
44 | state.isAuth = true;
45 | state.user = action.payload;
46 | });
47 | builder.addCase("getUserDataFail", (state, action) => {
48 | state.isAuth = false;
49 | state.error = action.payload;
50 | });
51 | // LOGOUT
52 | builder.addCase("logoutRequest", (state, action) => {
53 | state.loading = true;
54 | });
55 | builder.addCase("logoutSucess", (state, action) => {
56 | state.loading = false;
57 | state.isAuth = false;
58 | state.user = null;
59 | state.message = action.payload;
60 | });
61 | builder.addCase("logoutFail", (state, action) => {
62 | state.isAuth = false;
63 | state.error = action.payload;
64 | });
65 | });
66 |
--------------------------------------------------------------------------------
/client/redux/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import { userReducer } from "./features/auth/userReducer";
3 |
4 | export default configureStore({
5 | reducer: {
6 | user: userReducer,
7 | },
8 | });
9 |
10 | // HOST
11 | export const server = "https://combative-cod-life-jacket.cyclic.app/api/v1";
12 |
--------------------------------------------------------------------------------
/client/screens/About.js:
--------------------------------------------------------------------------------
1 | import { View, Text } from "react-native";
2 | import React from "react";
3 |
4 | const About = () => {
5 | return (
6 |
7 | About
8 |
9 | );
10 | };
11 |
12 | export default About;
13 |
--------------------------------------------------------------------------------
/client/screens/Account/Account.js:
--------------------------------------------------------------------------------
1 | import { View, Text, Image, StyleSheet, TouchableOpacity } from "react-native";
2 | import React from "react";
3 | import Layout from "../../components/Layout/Layout";
4 | import { userData } from "../../data/userData";
5 | import AntDesign from "react-native-vector-icons/AntDesign";
6 |
7 | const Account = ({ navigation }) => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | Hi
15 | {userData.name}
16 | 👋
17 |
18 | email : {userData.email}
19 | conatct : {userData.contact}
20 |
21 |
22 | Account Setting
23 | navigation.navigate("profile", { id: userData._id })}
26 | >
27 |
28 | Edit Profile
29 |
30 |
33 | navigation.navigate("myorders", { id: userData._id })
34 | }
35 | >
36 |
37 | My Orders
38 |
39 | navigation.navigate("notifications")}
42 | >
43 |
44 | Notification
45 |
46 |
49 | navigation.navigate("adminPanel", { id: userData._id })
50 | }
51 | >
52 |
53 | Admin Panel
54 |
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | const styles = StyleSheet.create({
62 | container: {
63 | marginVertical: 20,
64 | },
65 | image: {
66 | height: 100,
67 | width: "100%",
68 | resizeMode: "contain",
69 | },
70 | name: {
71 | marginTop: 10,
72 | fontSize: 20,
73 | },
74 | btnContainer: {
75 | padding: 10,
76 | backgroundColor: "#ffffff",
77 | margin: 10,
78 | marginVertical: 20,
79 | elevation: 5,
80 | borderRadius: 10,
81 | paddingBottom: 30,
82 | },
83 | heading: {
84 | fontSize: 20,
85 | fontWeight: "bold",
86 | paddingBottom: 10,
87 | textAlign: "center",
88 | borderBottomWidth: 1,
89 | borderColor: "lightgray",
90 | },
91 | btn: {
92 | flexDirection: "row",
93 | alignItems: "center",
94 | marginVertical: 10,
95 | padding: 5,
96 | },
97 | btnText: {
98 | fontSize: 15,
99 | marginRight: 10,
100 | },
101 | });
102 | export default Account;
103 |
--------------------------------------------------------------------------------
/client/screens/Account/MyOrders.js:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet, ScrollView } from "react-native";
2 | import React from "react";
3 | import Layout from "../../components/Layout/Layout";
4 | import { orderData } from "../../data/OrderData";
5 | import OrderItem from "../../components/Form/OrderItem";
6 |
7 | const MyOrders = () => {
8 | return (
9 |
10 |
11 | My Orders
12 |
13 | {orderData.map((order) => (
14 |
15 | ))}
16 |
17 |
18 |
19 | );
20 | };
21 | const styles = StyleSheet.create({
22 | container: {
23 | marginTop: 10,
24 | },
25 | heading: {
26 | textAlign: "center",
27 | color: "gray",
28 | fontWeight: "bold",
29 | fontSize: 20,
30 | },
31 | });
32 | export default MyOrders;
33 |
--------------------------------------------------------------------------------
/client/screens/Account/Notifications.js:
--------------------------------------------------------------------------------
1 | import { View, Text } from "react-native";
2 | import React from "react";
3 | import Layout from "../../components/Layout/Layout";
4 |
5 | const Notifications = () => {
6 | return (
7 |
8 |
15 | Oops ! You dont have any notification yet
16 |
17 |
18 | );
19 | };
20 |
21 | export default Notifications;
22 |
--------------------------------------------------------------------------------
/client/screens/Account/Profile.js:
--------------------------------------------------------------------------------
1 | import {
2 | View,
3 | Text,
4 | StyleSheet,
5 | ScrollView,
6 | Image,
7 | Pressable,
8 | TouchableOpacity,
9 | } from "react-native";
10 | import React, { useState } from "react";
11 | import Layout from "../../components/Layout/Layout";
12 | import { userData } from "../../data/userData";
13 | import InputBox from "../../components/Form/InputBox";
14 |
15 | const Profile = ({ navigation }) => {
16 | //state
17 | const [email, setEamil] = useState(userData.email);
18 | const [profilePic, setProfilePic] = useState(userData.profilePic);
19 | const [password, setPassword] = useState(userData.password);
20 | const [name, setName] = useState(userData.name);
21 | const [address, setAddress] = useState(userData.address);
22 | const [city, setCity] = useState(userData.city);
23 | const [contact, setContact] = useState(userData.contact);
24 |
25 | // update profile
26 | const handleUpdate = () => {
27 | if (!email || !password || !name || !address || !city || !contact) {
28 | return alert("Please provide all fields");
29 | }
30 | alert("profile updat Successfully");
31 | navigation.navigate("account");
32 | };
33 | return (
34 |
35 |
36 |
37 |
38 |
39 | alert("profile dailogbox")}>
40 | update your profile pic
41 |
42 |
43 |
49 |
55 |
62 |
68 |
74 |
80 |
81 | UPDATE PROFILE
82 |
83 |
84 |
85 |
86 | );
87 | };
88 | const styles = StyleSheet.create({
89 | container: {
90 | marginVertical: 20,
91 | },
92 | imageContainer: {
93 | justifyContent: "center",
94 | alignItems: "center",
95 | },
96 | image: {
97 | height: 100,
98 | width: "100%",
99 | resizeMode: "contain",
100 | },
101 | btnUpdate: {
102 | backgroundColor: "#000000",
103 | height: 40,
104 | borderRadius: 20,
105 | marginHorizontal: 30,
106 | justifyContent: "center",
107 | marginTop: 10,
108 | },
109 | btnUpdateText: {
110 | color: "#ffffff",
111 | fontSize: 18,
112 | textAlign: "center",
113 | },
114 | });
115 | export default Profile;
116 |
--------------------------------------------------------------------------------
/client/screens/Admin/Dashboard.js:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
2 | import React from "react";
3 | import Layout from "../../components/Layout/Layout";
4 | import AntDesign from "react-native-vector-icons/AntDesign";
5 |
6 | const Dashboard = () => {
7 | return (
8 |
9 |
10 | Dashboard
11 |
12 |
13 |
14 | Manage Products
15 |
16 |
17 |
18 | Manage Categories
19 |
20 |
21 |
22 | Manage Users
23 |
24 |
25 |
26 | Manage Orders
27 |
28 |
29 |
30 | About App
31 |
32 |
33 |
34 |
35 | );
36 | };
37 | const styles = StyleSheet.create({
38 | main: {
39 | backgroundColor: "lightgray",
40 | height: "96%",
41 | },
42 | heading: {
43 | backgroundColor: "#000000",
44 | color: "#ffffff",
45 | textAlign: "center",
46 | padding: 10,
47 | fontSize: 20,
48 | margin: 10,
49 | borderRadius: 5,
50 | fontWeight: "bold",
51 | },
52 | btnContainer: {
53 | margin: 10,
54 | },
55 | btn: {
56 | flexDirection: "row",
57 | alignItems: "center",
58 | backgroundColor: "#ffffff",
59 | padding: 20,
60 | borderRadius: 10,
61 | elevation: 10,
62 | marginBottom: 20,
63 | },
64 | icon: {
65 | fontSize: 25,
66 | marginRight: 10,
67 | marginLeft: 10,
68 | },
69 | btnText: {
70 | fontSize: 18,
71 | },
72 | });
73 | export default Dashboard;
74 |
--------------------------------------------------------------------------------
/client/screens/Cart.js:
--------------------------------------------------------------------------------
1 | import {
2 | View,
3 | Text,
4 | StyleSheet,
5 | ScrollView,
6 | TouchableOpacity,
7 | } from "react-native";
8 | import React, { useState } from "react";
9 | import { CartData } from "../data/CartData";
10 | import PriceTable from "../components/cart/PriceTable";
11 | import Layout from "../components/Layout/Layout";
12 | import Cartitem from "../components/cart/CartItem";
13 |
14 | const Cart = ({ navigation }) => {
15 | const [cartItems, setCartItems] = useState(CartData);
16 | return (
17 |
18 |
19 | {cartItems?.length > 0
20 | ? `You Have ${cartItems?.length} Item Left In Your Cart`
21 | : "OOPS Your Cart Is EMPTY !"}
22 |
23 | {cartItems?.length > 0 && (
24 | <>
25 |
26 | {cartItems?.map((item) => (
27 |
28 | ))}
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | navigation.navigate("checkout")}
40 | >
41 | CHECKOUT
42 |
43 |
44 | >
45 | )}
46 |
47 | );
48 | };
49 | const styles = StyleSheet.create({
50 | heading: {
51 | textAlign: "center",
52 | color: "green",
53 | marginTop: 10,
54 | },
55 | grandTotal: {
56 | borderWidth: 1,
57 | borderColor: "lightgray",
58 | backgroundColor: "#ffffff",
59 | padding: 5,
60 | margin: 5,
61 | marginHorizontal: 20,
62 | },
63 | btnCheckout: {
64 | marginTop: 20,
65 | justifyContent: "center",
66 | alignItems: "center",
67 | height: 40,
68 | backgroundColor: "#000000",
69 | width: "90%",
70 | marginHorizontal: 20,
71 | borderRadius: 20,
72 | },
73 | btnCheckoutText: {
74 | color: "#ffffff",
75 | fontWeight: "bold",
76 | fontSize: 18,
77 | },
78 | });
79 | export default Cart;
80 |
--------------------------------------------------------------------------------
/client/screens/Checkout.js:
--------------------------------------------------------------------------------
1 | import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
2 | import React from "react";
3 | import Layout from "../components/Layout/Layout";
4 |
5 | const Checkout = ({ navigation }) => {
6 | const handleCOD = () => {
7 | alert("Your Order Has Been Placed Successfully");
8 | };
9 | const handleOnline = () => {
10 | alert("Your Redirecting to payment gateway");
11 | navigation.navigate("payment");
12 | };
13 | return (
14 |
15 |
16 | Payment Options
17 | Total Amount : 101$
18 |
19 | Select your Payment Mode
20 |
21 | Cash On Devlivery
22 |
23 |
24 |
25 | Online (CREDIT | DEBIT CARD)
26 |
27 |
28 |
29 |
30 |
31 | );
32 | };
33 |
34 | const Styles = StyleSheet.create({
35 | container: {
36 | alignItems: "center",
37 | justifyContent: "center",
38 | height: "90%",
39 | },
40 | heading: {
41 | fontSize: 30,
42 | fontWeight: "500",
43 | marginVertical: 10,
44 | },
45 | price: {
46 | fontSize: 20,
47 | marginBottom: 10,
48 | color: "gray",
49 | },
50 | paymentCard: {
51 | backgroundColor: "#ffffff",
52 | width: "90%",
53 | borderRadius: 10,
54 | padding: 30,
55 | marginVertical: 10,
56 | },
57 | paymentHeading: {
58 | color: "gray",
59 | marginBottom: 10,
60 | },
61 | paymentBtn: {
62 | backgroundColor: "#000000",
63 | height: 40,
64 | borderRadius: 10,
65 | justifyContent: "center",
66 | marginVertical: 10,
67 | },
68 | paymentBtnText: {
69 | color: "#ffffff",
70 | textAlign: "center",
71 | },
72 | });
73 |
74 | export default Checkout;
75 |
--------------------------------------------------------------------------------
/client/screens/Home.js:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet } from "react-native";
2 | import React, { useEffect } from "react";
3 | import Layout from "../components/Layout/Layout";
4 | import Categories from "../components/category/Categories";
5 | import Banner from "../components/Banner/Banner";
6 | import Products from "../components/Products/Products";
7 | import Header from "../components/Layout/Header";
8 | import { useSelector, useDispatch } from "react-redux";
9 | import { getUserData } from "../redux/features/auth/userActions";
10 |
11 | const Home = () => {
12 | const disptach = useDispatch();
13 |
14 | useEffect(() => {
15 | disptach(getUserData());
16 | }, [disptach]);
17 |
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 | };
27 |
28 | export default Home;
29 |
30 | const styles = StyleSheet.create({
31 | container: {
32 | flex: 1,
33 | backgroundColor: "#fff",
34 | alignItems: "center",
35 | justifyContent: "center",
36 | },
37 | });
38 |
--------------------------------------------------------------------------------
/client/screens/Payments.js:
--------------------------------------------------------------------------------
1 | import { View, Text } from "react-native";
2 | import React from "react";
3 | import Layout from "../components/Layout/Layout";
4 |
5 | const Payments = () => {
6 | return (
7 |
8 | Payments
9 |
10 | );
11 | };
12 |
13 | export default Payments;
14 |
--------------------------------------------------------------------------------
/client/screens/ProductDetails.js:
--------------------------------------------------------------------------------
1 | import { View, Text, Image, StyleSheet, TouchableOpacity } from "react-native";
2 | import React, { useEffect, useState } from "react";
3 | import { ProductsData } from "../data/ProductsData";
4 | import Layout from "../components/Layout/Layout";
5 |
6 | const ProductDetails = ({ route }) => {
7 | const [pDetails, setPDetails] = useState({});
8 | const [qty, setQty] = useState(1);
9 | // get product dfetails
10 | useEffect(() => {
11 | //find product details
12 | const getProudct = ProductsData.find((p) => {
13 | return p?._id === params?._id;
14 | });
15 | setPDetails(getProudct);
16 | }, [params?._id]);
17 | // console.log(route);
18 | // Handle function for + -
19 | const handleAddQty = () => {
20 | if (qty === 10) return alert("you cant add more than 10 quantity");
21 | setQty((prev) => prev + 1);
22 | };
23 | const handleRemoveQty = () => {
24 | if (qty <= 1) return;
25 | setQty((prev) => prev - 1);
26 | };
27 | const { params } = route;
28 | return (
29 |
30 |
31 |
32 | {pDetails?.name}
33 | Price : {pDetails?.price} $
34 | Price : {pDetails?.description} $
35 |
36 | alert(`${qty} items added to cart`)}
39 | disabled={pDetails?.quantity <= 0}
40 | >
41 |
42 | {pDetails?.quantity > 0 ? "ADD TO CART" : "OUT OF STOCK"}
43 |
44 |
45 |
46 |
47 | -
48 |
49 | {qty}
50 |
51 | +
52 |
53 |
54 |
55 |
56 |
57 | );
58 | };
59 | const styles = StyleSheet.create({
60 | image: {
61 | height: 300,
62 | width: "100%",
63 | },
64 | container: {
65 | marginVertical: 15,
66 | marginHorizontal: 10,
67 | },
68 | title: {
69 | fontSize: 18,
70 | textAlign: "left",
71 | },
72 | desc: {
73 | fontSize: 12,
74 | textTransform: "capitalize",
75 | textAlign: "justify",
76 | marginVertical: 10,
77 | },
78 | btnContainer: {
79 | flexDirection: "row",
80 | justifyContent: "center",
81 | alignItems: "center",
82 | marginVertical: 20,
83 | marginHorizontal: 10,
84 | },
85 | btnCart: {
86 | width: 180,
87 | backgroundColor: "#000000",
88 | // marginVertical: 10,
89 | borderRadius: 5,
90 | height: 40,
91 | justifyContent: "center",
92 | },
93 | btnCartText: {
94 | color: "#ffffff",
95 | fontWeight: "bold",
96 | textAlign: "center",
97 | fontSize: 16,
98 | },
99 | btnQty: {
100 | backgroundColor: "lightgray",
101 | width: 40,
102 | alignItems: "center",
103 | marginHorizontal: 10,
104 | },
105 | btnQtyText: {
106 | fontSize: 20,
107 | },
108 | });
109 | export default ProductDetails;
110 |
--------------------------------------------------------------------------------
/client/screens/auth/Login.js:
--------------------------------------------------------------------------------
1 | import {
2 | View,
3 | Text,
4 | TextInput,
5 | TouchableOpacity,
6 | Image,
7 | StyleSheet,
8 | } from "react-native";
9 | import React, { useState, useEffect } from "react";
10 | import InputBox from "../../components/Form/InputBox";
11 |
12 | //redux hooks
13 | import { login } from "../../redux/features/auth/userActions";
14 | import { useDispatch, useSelector } from "react-redux";
15 | import { useReduxStateHook } from "../../hooks/customeHook";
16 | const Login = ({ navigation }) => {
17 | const loginImage = "https://fishcopfed.coop/images/login.png";
18 | const [email, setEamil] = useState("");
19 | const [password, setPassword] = useState("");
20 | // hooks
21 | const dispatch = useDispatch();
22 | // global state
23 |
24 | const loading = useReduxStateHook(navigation, "home");
25 |
26 | // login function
27 | const handleLogin = () => {
28 | if (!email || !password) {
29 | return alert("Please add email or password");
30 | }
31 | dispatch(login(email, password));
32 | };
33 |
34 | return (
35 |
36 |
37 | {loading && loading ...}
38 |
44 |
50 |
51 |
52 | Login
53 |
54 |
55 | Not a user yet ? Please{" "}
56 | navigation.navigate("register")}
59 | >
60 | Register !
61 |
62 |
63 |
64 |
65 | );
66 | };
67 | const styles = StyleSheet.create({
68 | container: {
69 | // alignItems: "center",
70 | justifyContent: "center",
71 | height: "100%",
72 | },
73 | image: {
74 | height: 200,
75 | width: "100%",
76 | resizeMode: "contain",
77 | },
78 | btnContainer: {
79 | justifyContent: "center",
80 | alignItems: "center",
81 | },
82 | loginBtn: {
83 | backgroundColor: "#000000",
84 | width: "80%",
85 | justifyContent: "center",
86 | height: 40,
87 | borderRadius: 10,
88 | marginVertical: 20,
89 | },
90 | loginBtnText: {
91 | color: "#ffffff",
92 | textAlign: "center",
93 | textTransform: "uppercase",
94 | fontWeight: "500",
95 | fontSize: 18,
96 | },
97 | link: {
98 | color: "red",
99 | },
100 | });
101 | export default Login;
102 |
--------------------------------------------------------------------------------
/client/screens/auth/Register.js:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native";
2 | import React, { useState } from "react";
3 | import InputBox from "../../components/Form/InputBox";
4 | import { useDispatch } from "react-redux";
5 | import { register } from "../../redux/features/auth/userActions";
6 | import { useReduxStateHook } from "./../../hooks/customeHook";
7 |
8 | const Register = ({ navigation }) => {
9 | const dispatch = useDispatch();
10 |
11 | const loginImage = "https://fishcopfed.coop/images/login.png";
12 | const [email, setEamil] = useState("");
13 | const [password, setPassword] = useState("");
14 | const [name, setName] = useState("");
15 | const [address, setAddress] = useState("");
16 | const [city, setCity] = useState("");
17 | const [phone, setPhone] = useState("");
18 | const [answer, setAnswer] = useState("");
19 | const [country, setCountry] = useState("india");
20 |
21 | // login function
22 | const handleRegister = () => {
23 | // validation
24 | if (!email || !password || !name || !address || !city || !phone) {
25 | return alert("Please provide all fields client side");
26 | }
27 | const formData = {
28 | email,
29 | password,
30 | name,
31 | address,
32 | city,
33 | phone,
34 | answer,
35 | country: "India",
36 | };
37 | dispatch(register(formData));
38 | // navigation.navigate("/login");
39 | };
40 | const loading = useReduxStateHook(navigation, "login");
41 | return (
42 |
43 |
44 |
45 |
51 |
57 |
63 |
69 |
75 |
81 |
87 |
88 |
89 | Register
90 |
91 |
92 | Alredy a user please ?{" "}
93 | navigation.navigate("login")}
96 | >
97 | login !
98 |
99 |
100 |
101 |
102 | );
103 | };
104 | const styles = StyleSheet.create({
105 | container: {
106 | // alignItems: "center",
107 | justifyContent: "center",
108 | height: "100%",
109 | },
110 | image: {
111 | height: 200,
112 | width: "100%",
113 | resizeMode: "contain",
114 | },
115 | btnContainer: {
116 | justifyContent: "center",
117 | alignItems: "center",
118 | },
119 | loginBtn: {
120 | backgroundColor: "#000000",
121 | width: "80%",
122 | justifyContent: "center",
123 | height: 40,
124 | borderRadius: 10,
125 | marginVertical: 20,
126 | },
127 | loginBtnText: {
128 | color: "#ffffff",
129 | textAlign: "center",
130 | textTransform: "uppercase",
131 | fontWeight: "500",
132 | fontSize: 18,
133 | },
134 | link: {
135 | color: "red",
136 | },
137 | });
138 | export default Register;
139 |
--------------------------------------------------------------------------------
/server/config/db.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 | import colors from "colors";
3 |
4 | const connectDB = async () => {
5 | try {
6 | await mongoose.connect(process.env.MONGO_URL);
7 | console.log(`Mongodb Connected ${mongoose.connection.host}`);
8 | } catch (error) {
9 | console.log(`Mongodb Error ${error}`.bgRed.white);
10 | }
11 | };
12 |
13 | export default connectDB;
14 |
--------------------------------------------------------------------------------
/server/controllers/categoryController.js:
--------------------------------------------------------------------------------
1 | import categoryModel from "../models/categoryModel.js";
2 | import productModel from "../models/productModel.js";
3 |
4 | // CREAT CAT
5 | export const createCategory = async (req, res) => {
6 | try {
7 | const { category } = req.body;
8 | // validation
9 | if (!category) {
10 | return res.status(404).send({
11 | success: false,
12 | message: "please provide category name",
13 | });
14 | }
15 | await categoryModel.create({ category });
16 | res.status(201).send({
17 | success: true,
18 | message: `${category} category creted successfully`,
19 | });
20 | } catch (error) {
21 | console.log(error);
22 | res.status(500).send({
23 | success: false,
24 | message: "Error In Create Cat API",
25 | });
26 | }
27 | };
28 |
29 | // GET ALL CAT
30 | export const getAllCategoriesController = async (req, res) => {
31 | try {
32 | const categories = await categoryModel.find({});
33 | res.status(200).send({
34 | success: true,
35 | message: "Categories Fetch Successfully",
36 | totalCat: categories.length,
37 | categories,
38 | });
39 | } catch (error) {
40 | console.log(error);
41 | res.status(500).send({
42 | success: false,
43 | message: "Error In Get All Cat API",
44 | });
45 | }
46 | };
47 |
48 | // DELETE CATEGORY
49 | export const deleteCategoryController = async (req, res) => {
50 | try {
51 | // find category
52 | const category = await categoryModel.findById(req.params.id);
53 | //validation
54 | if (!category) {
55 | return res.status(404).send({
56 | success: false,
57 | message: "Category not found",
58 | });
59 | }
60 | // find product with this category id
61 | const products = await productModel.find({ category: category._id });
62 | // update producty category
63 | for (let i = 0; i < products.length; i++) {
64 | const product = products[i];
65 | product.category = undefined;
66 | await product.save();
67 | }
68 | // save
69 | await category.deleteOne();
70 | res.status(200).send({
71 | success: true,
72 | message: "Catgeory Deleted Successfully",
73 | });
74 | } catch (error) {
75 | console.log(error);
76 | // cast error || OBJECT ID
77 | if (error.name === "CastError") {
78 | return res.status(500).send({
79 | success: false,
80 | message: "Invalid Id",
81 | });
82 | }
83 | res.status(500).send({
84 | success: false,
85 | message: "Error In DELETE CAT API",
86 | error,
87 | });
88 | }
89 | };
90 |
91 | // UDPATE CAT
92 | export const updateCategoryController = async (req, res) => {
93 | try {
94 | // find category
95 | const category = await categoryModel.findById(req.params.id);
96 | //validation
97 | if (!category) {
98 | return res.status(404).send({
99 | success: false,
100 | message: "Category not found",
101 | });
102 | }
103 | // get new cat
104 | const { updatedCategory } = req.body;
105 | // find product with this category id
106 | const products = await productModel.find({ category: category._id });
107 | // update producty category
108 | for (let i = 0; i < products.length; i++) {
109 | const product = products[i];
110 | product.category = updatedCategory;
111 | await product.save();
112 | }
113 | if (updatedCategory) category.category = updatedCategory;
114 |
115 | // save
116 | await category.save();
117 | res.status(200).send({
118 | success: true,
119 | message: "Catgeory Updated Successfully",
120 | });
121 | } catch (error) {
122 | console.log(error);
123 | // cast error || OBJECT ID
124 | if (error.name === "CastError") {
125 | return res.status(500).send({
126 | success: false,
127 | message: "Invalid Id",
128 | });
129 | }
130 | res.status(500).send({
131 | success: false,
132 | message: "Error In UPDATE CATEGPORY API",
133 | error,
134 | });
135 | }
136 | };
137 |
--------------------------------------------------------------------------------
/server/controllers/orderController.js:
--------------------------------------------------------------------------------
1 | import orderModel from "../models/orderModel.js";
2 | import productModel from "../models/productModel.js";
3 | import { stripe } from "../server.js";
4 |
5 | // CREATE ORDERS
6 | export const createOrderController = async (req, res) => {
7 | try {
8 | const {
9 | shippingInfo,
10 | orderItems,
11 | paymentMethod,
12 | paymentInfo,
13 | itemPrice,
14 | tax,
15 | shippingCharges,
16 | totalAmount,
17 | } = req.body;
18 | //valdiation
19 | // create order
20 | await orderModel.create({
21 | user: req.user._id,
22 | shippingInfo,
23 | orderItems,
24 | paymentMethod,
25 | paymentInfo,
26 | itemPrice,
27 | tax,
28 | shippingCharges,
29 | totalAmount,
30 | });
31 |
32 | // stock update
33 | for (let i = 0; i < orderItems.length; i++) {
34 | // find product
35 | const product = await productModel.findById(orderItems[i].product);
36 | product.stock -= orderItems[i].quantity;
37 | await product.save();
38 | }
39 | res.status(201).send({
40 | success: true,
41 | message: "Order Placed Successfully",
42 | });
43 | } catch (error) {
44 | console.log(error);
45 | res.status(500).send({
46 | success: false,
47 | message: "Error In Create Order API",
48 | error,
49 | });
50 | }
51 | };
52 |
53 | // GET ALL ORDERS - MY ORDERS
54 | export const getMyOrdersCotroller = async (req, res) => {
55 | try {
56 | // find orders
57 | const orders = await orderModel.find({ user: req.user._id });
58 | //valdiation
59 | if (!orders) {
60 | return res.status(404).send({
61 | success: false,
62 | message: "no orders found",
63 | });
64 | }
65 | res.status(200).send({
66 | success: true,
67 | message: "your orders data",
68 | totalOrder: orders.length,
69 | orders,
70 | });
71 | } catch (error) {
72 | console.log(error);
73 | res.status(500).send({
74 | success: false,
75 | message: "Error In My orders Order API",
76 | error,
77 | });
78 | }
79 | };
80 |
81 | // GET SINGLE ORDER INFO
82 | export const singleOrderDetrailsController = async (req, res) => {
83 | try {
84 | // find orders
85 | const order = await orderModel.findById(req.params.id);
86 | //valdiation
87 | if (!order) {
88 | return res.status(404).send({
89 | success: false,
90 | message: "no order found",
91 | });
92 | }
93 | res.status(200).send({
94 | success: true,
95 | message: "your order fetched",
96 | order,
97 | });
98 | } catch (error) {
99 | console.log(error);
100 | // cast error || OBJECT ID
101 | if (error.name === "CastError") {
102 | return res.status(500).send({
103 | success: false,
104 | message: "Invalid Id",
105 | });
106 | }
107 | res.status(500).send({
108 | success: false,
109 | message: "Error In Get UPDATE Products API",
110 | error,
111 | });
112 | }
113 | };
114 |
115 | // ACCEPT PAYMENTS
116 | export const paymetsController = async (req, res) => {
117 | try {
118 | // get ampunt
119 | const { totalAmount } = req.body;
120 | // validation
121 | if (!totalAmount) {
122 | return res.status(404).send({
123 | success: false,
124 | message: "Total Amount is require",
125 | });
126 | }
127 | const { client_secret } = await stripe.paymentIntents.create({
128 | amount: Number(totalAmount * 100),
129 | currency: "usd",
130 | });
131 | res.status(200).send({
132 | success: true,
133 | client_secret,
134 | });
135 | } catch (error) {
136 | console.log(error);
137 | res.status(500).send({
138 | success: false,
139 | message: "Error In Get UPDATE Products API",
140 | error,
141 | });
142 | }
143 | };
144 |
145 | // ========== ADMIN SECTION =============
146 |
147 | // GET ALL ORDERS
148 | export const getAllOrdersController = async (req, res) => {
149 | try {
150 | const orders = await orderModel.find({});
151 | res.status(200).send({
152 | success: true,
153 | message: "All Orders Data",
154 | totalOrders: orders.length,
155 | orders,
156 | });
157 | } catch (error) {
158 | console.log(error);
159 | res.status(500).send({
160 | success: false,
161 | message: "Error In Get UPDATE Products API",
162 | error,
163 | });
164 | }
165 | };
166 |
167 | // CHANGE ORDER STATUS
168 | export const changeOrderStatusController = async (req, res) => {
169 | try {
170 | // find order
171 | const order = await orderModel.findById(req.params.id);
172 | // validatiom
173 | if (!order) {
174 | return res.status(404).send({
175 | success: false,
176 | message: "order not found",
177 | });
178 | }
179 | if (order.orderStatus === "processing") order.orderStatus = "shipped";
180 | else if (order.orderStatus === "shipped") {
181 | order.orderStatus = "deliverd";
182 | order.deliverdAt = Date.now();
183 | } else {
184 | return res.status(500).send({
185 | success: false,
186 | message: "order already deliverd",
187 | });
188 | }
189 | await order.save();
190 | res.status(200).send({
191 | success: true,
192 | message: "order status updated",
193 | });
194 | } catch (error) {
195 | console.log(error);
196 | // cast error || OBJECT ID
197 | if (error.name === "CastError") {
198 | return res.status(500).send({
199 | success: false,
200 | message: "Invalid Id",
201 | });
202 | }
203 | res.status(500).send({
204 | success: false,
205 | message: "Error In Get UPDATE Products API",
206 | error,
207 | });
208 | }
209 | };
210 |
--------------------------------------------------------------------------------
/server/controllers/productController.js:
--------------------------------------------------------------------------------
1 | import productModel from "../models/productModel.js";
2 |
3 | import cloudinary from "cloudinary";
4 | import { getDataUri } from "./../utils/features.js";
5 |
6 | // GET ALL PRODUCTS
7 | export const getAllProductsController = async (req, res) => {
8 | const { keyword, category } = req.query;
9 | try {
10 | const products = await productModel
11 | .find({
12 | name: {
13 | $regex: keyword ? keyword : "",
14 | $options: "i",
15 | },
16 | // category: category ? category : null,
17 | })
18 | .populate("category");
19 | res.status(200).send({
20 | success: true,
21 | message: "all products fetched successfully",
22 | totalProducts: products.length,
23 | products,
24 | });
25 | } catch (error) {
26 | console.log(error);
27 | res.status(500).send({
28 | success: false,
29 | message: "Error In Get All Products API",
30 | error,
31 | });
32 | }
33 | };
34 |
35 | // GET TOP PRODUCT
36 | export const getTopProductsController = async (req, res) => {
37 | try {
38 | const products = await productModel.find({}).sort({ rating: -1 }).limit(3);
39 | res.status(200).send({
40 | success: true,
41 | message: "top 3 products",
42 | products,
43 | });
44 | } catch (error) {
45 | console.log(error);
46 | res.status(500).send({
47 | success: false,
48 | message: "Error In Get TOP PRODUCTS API",
49 | error,
50 | });
51 | }
52 | };
53 |
54 | // GET SINGLE PRODUCT
55 | export const getSingleProductController = async (req, res) => {
56 | try {
57 | // get product id
58 | const product = await productModel.findById(req.params.id);
59 | //valdiation
60 | if (!product) {
61 | return res.status(404).send({
62 | success: false,
63 | message: "product not found",
64 | });
65 | }
66 | res.status(200).send({
67 | success: true,
68 | message: "Product Found",
69 | product,
70 | });
71 | } catch (error) {
72 | console.log(error);
73 | // cast error || OBJECT ID
74 | if (error.name === "CastError") {
75 | return res.status(500).send({
76 | success: false,
77 | message: "Invalid Id",
78 | });
79 | }
80 | res.status(500).send({
81 | success: false,
82 | message: "Error In Get single Products API",
83 | error,
84 | });
85 | }
86 | };
87 |
88 | // CREATE PRODUCT
89 | export const createProductController = async (req, res) => {
90 | try {
91 | const { name, description, price, category, stock } = req.body;
92 | // // validtion
93 | // if (!name || !description || !price || !stock) {
94 | // return res.status(500).send({
95 | // success: false,
96 | // message: "Please Provide all fields",
97 | // });
98 | // }
99 | if (!req.file) {
100 | return res.status(500).send({
101 | success: false,
102 | message: "please provide product images",
103 | });
104 | }
105 | const file = getDataUri(req.file);
106 | const cdb = await cloudinary.v2.uploader.upload(file.content);
107 | const image = {
108 | public_id: cdb.public_id,
109 | url: cdb.secure_url,
110 | };
111 |
112 | await productModel.create({
113 | name,
114 | description,
115 | price,
116 | category,
117 | stock,
118 | images: [image],
119 | });
120 |
121 | res.status(201).send({
122 | success: true,
123 | message: "product Created Successfully",
124 | });
125 | } catch (error) {
126 | console.log(error);
127 | res.status(500).send({
128 | success: false,
129 | message: "Error In Get single Products API",
130 | error,
131 | });
132 | }
133 | };
134 |
135 | // UPDATE PRODUCT
136 | export const updateProductController = async (req, res) => {
137 | try {
138 | // find product
139 | const product = await productModel.findById(req.params.id);
140 | //valdiatiuon
141 | if (!product) {
142 | return res.status(404).send({
143 | success: false,
144 | message: "Product not found",
145 | });
146 | }
147 | const { name, description, price, stock, category } = req.body;
148 | // validate and update
149 | if (name) product.name = name;
150 | if (description) product.description = description;
151 | if (price) product.price = price;
152 | if (stock) product.stock = stock;
153 | if (category) product.category = category;
154 |
155 | await product.save();
156 | res.status(200).send({
157 | success: true,
158 | message: "product details updated",
159 | });
160 | } catch (error) {
161 | console.log(error);
162 | // cast error || OBJECT ID
163 | if (error.name === "CastError") {
164 | return res.status(500).send({
165 | success: false,
166 | message: "Invalid Id",
167 | });
168 | }
169 | res.status(500).send({
170 | success: false,
171 | message: "Error In Get UPDATE Products API",
172 | error,
173 | });
174 | }
175 | };
176 |
177 | // UPDATE PRODUCT IMAGE
178 | export const updateProductImageController = async (req, res) => {
179 | try {
180 | // find product
181 | const product = await productModel.findById(req.params.id);
182 | // valdiation
183 | if (!product) {
184 | return res.status(404).send({
185 | success: false,
186 | message: "Product not found",
187 | });
188 | }
189 | // check file
190 | if (!req.file) {
191 | return res.status(404).send({
192 | success: false,
193 | message: "Product image not found",
194 | });
195 | }
196 |
197 | const file = getDataUri(req.file);
198 | const cdb = await cloudinary.v2.uploader.upload(file.content);
199 | const image = {
200 | public_id: cdb.public_id,
201 | url: cdb.secure_url,
202 | };
203 | // save
204 | product.images.push(image);
205 | await product.save();
206 | res.status(200).send({
207 | success: true,
208 | message: "product image updated",
209 | });
210 | } catch (error) {
211 | console.log(error);
212 | // cast error || OBJECT ID
213 | if (error.name === "CastError") {
214 | return res.status(500).send({
215 | success: false,
216 | message: "Invalid Id",
217 | });
218 | }
219 | res.status(500).send({
220 | success: false,
221 | message: "Error In Get UPDATE Products API",
222 | error,
223 | });
224 | }
225 | };
226 |
227 | // DELETE PROEDUCT IMAGE
228 | export const deleteProductImageController = async (req, res) => {
229 | try {
230 | // find produtc
231 | const product = await productModel.findById(req.params.id);
232 | // validatin
233 | if (!product) {
234 | return res.status(404).send({
235 | success: false,
236 | message: "Product Not Found",
237 | });
238 | }
239 |
240 | // image id find
241 | const id = req.query.id;
242 | if (!id) {
243 | return res.status(404).send({
244 | success: false,
245 | message: "product image not found",
246 | });
247 | }
248 |
249 | let isExist = -1;
250 | product.images.forEach((item, index) => {
251 | if (item._id.toString() === id.toString()) isExist = index;
252 | });
253 | if (isExist < 0) {
254 | return res.status(404).send({
255 | success: false,
256 | message: "Image Not Found",
257 | });
258 | }
259 | // DELETE PRODUCT IMAGE
260 | await cloudinary.v2.uploader.destroy(product.images[isExist].public_id);
261 | product.images.splice(isExist, 1);
262 | await product.save();
263 | return res.status(200).send({
264 | success: true,
265 | message: "Product Image Dleteed Successfully",
266 | });
267 | } catch (error) {
268 | console.log(error);
269 | // cast error || OBJECT ID
270 | if (error.name === "CastError") {
271 | return res.status(500).send({
272 | success: false,
273 | message: "Invalid Id",
274 | });
275 | }
276 | res.status(500).send({
277 | success: false,
278 | message: "Error In Get DELETE Product IMAGE API",
279 | error,
280 | });
281 | }
282 | };
283 |
284 | // DLEETE PRODUCT
285 | export const deleteProductController = async (req, res) => {
286 | try {
287 | // find product
288 | const product = await productModel.findById(req.params.id);
289 | // validation
290 | if (!product) {
291 | return res.status(404).send({
292 | success: false,
293 | message: "product not found",
294 | });
295 | }
296 | // find and delete image cloudinary
297 | for (let index = 0; index < product.images.length; index++) {
298 | await cloudinary.v2.uploader.destroy(product.images[index].public_id);
299 | }
300 | await product.deleteOne();
301 | res.status(200).send({
302 | success: true,
303 | message: "PRoduct Deleted Successfully",
304 | });
305 | } catch (error) {
306 | console.log(error);
307 | // cast error || OBJECT ID
308 | if (error.name === "CastError") {
309 | return res.status(500).send({
310 | success: false,
311 | message: "Invalid Id",
312 | });
313 | }
314 | res.status(500).send({
315 | success: false,
316 | message: "Error In Get DELETE Product IMAGE API",
317 | error,
318 | });
319 | }
320 | };
321 |
322 | // CREATE PRODUCT REVIEW AND COMMENT
323 | export const productReviewController = async (req, res) => {
324 | try {
325 | const { comment, rating } = req.body;
326 | // find product
327 | const product = await productModel.findById(req.params.id);
328 | // check previous review
329 | const alreadyReviewed = product.reviews.find(
330 | (r) => r.user.toString() === req.user._id.toString()
331 | );
332 | if (alreadyReviewed) {
333 | return res.status(400).send({
334 | success: false,
335 | message: "Product Alredy Reviewed",
336 | });
337 | }
338 | // review object
339 | const review = {
340 | name: req.user.name,
341 | rating: Number(rating),
342 | comment,
343 | user: req.user._id,
344 | };
345 | // passing review object to reviews array
346 | product.reviews.push(review);
347 | // number or reviews
348 | product.numReviews = product.reviews.length;
349 | product.rating =
350 | product.reviews.reduce((acc, item) => item.rating + acc, 0) /
351 | product.reviews.length;
352 | // save
353 | await product.save();
354 | res.status(200).send({
355 | success: true,
356 | message: "Review Added!",
357 | });
358 | } catch (error) {
359 | console.log(error);
360 | // cast error || OBJECT ID
361 | if (error.name === "CastError") {
362 | return res.status(500).send({
363 | success: false,
364 | message: "Invalid Id",
365 | });
366 | }
367 | res.status(500).send({
368 | success: false,
369 | message: "Error In Review Comment API",
370 | error,
371 | });
372 | }
373 | };
374 |
375 | // ========== PRODUCT CTRL ENDS ================
376 |
--------------------------------------------------------------------------------
/server/controllers/testController.js:
--------------------------------------------------------------------------------
1 | export const testController = (req, res) => {
2 | res.status(200).send({
3 | message: "Test Routes",
4 | success: true,
5 | });
6 | };
7 |
--------------------------------------------------------------------------------
/server/controllers/userController.js:
--------------------------------------------------------------------------------
1 | import userModel from "../models/userModel.js";
2 | import cloudinary from "cloudinary";
3 | import { getDataUri } from "../utils/Features.js";
4 | export const registerController = async (req, res) => {
5 | try {
6 | const { name, email, password, address, city, country, phone, answer } =
7 | req.body;
8 | // validation
9 | if (
10 | !name ||
11 | !email ||
12 | !password ||
13 | !city ||
14 | !address ||
15 | !country ||
16 | !phone ||
17 | !answer
18 | ) {
19 | return res.status(500).send({
20 | success: false,
21 | message: "Please Provide All Fields",
22 | });
23 | }
24 | //check exisiting user
25 | const exisitingUSer = await userModel.findOne({ email });
26 | //validation
27 | if (exisitingUSer) {
28 | return res.status(500).send({
29 | success: false,
30 | message: "email already taken",
31 | });
32 | }
33 | const user = await userModel.create({
34 | name,
35 | email,
36 | password,
37 | address,
38 | city,
39 | country,
40 | phone,
41 | answer,
42 | });
43 | res.status(201).send({
44 | success: true,
45 | message: "Registeration Success, please login",
46 | user,
47 | });
48 | } catch (error) {
49 | console.log(error);
50 | res.status(500).send({
51 | success: false,
52 | message: "Error In Register API",
53 | error,
54 | });
55 | }
56 | };
57 |
58 | //LOGIN
59 | export const loginController = async (req, res) => {
60 | try {
61 | const { email, password } = req.body;
62 | //validation
63 | if (!email || !password) {
64 | return res.status(500).send({
65 | success: false,
66 | message: "Please Add Email OR Password",
67 | });
68 | }
69 | // check user
70 | const user = await userModel.findOne({ email });
71 | //user valdiation
72 | if (!user) {
73 | return res.status(404).send({
74 | success: false,
75 | message: "USer Not Found",
76 | });
77 | }
78 | //check pass
79 | const isMatch = await user.comparePassword(password);
80 | //valdiation pass
81 | if (!isMatch) {
82 | return res.status(500).send({
83 | success: false,
84 | message: "invalid credentials",
85 | });
86 | }
87 | //teken
88 | const token = user.generateToken();
89 |
90 | res
91 | .status(200)
92 | .cookie("token", token, {
93 | expires: new Date(Date.now() + 15 * 24 * 60 * 60 * 1000),
94 | secure: process.env.NODE_ENV === "development" ? true : false,
95 | httpOnly: process.env.NODE_ENV === "development" ? true : false,
96 | sameSite: process.env.NODE_ENV === "development" ? true : false,
97 | })
98 | .send({
99 | success: true,
100 | message: "Login Successfully",
101 | token,
102 | user,
103 | });
104 | } catch (error) {
105 | console.log(error);
106 | res.status(500).send({
107 | success: "false",
108 | message: "Error In Login Api",
109 | error,
110 | });
111 | }
112 | };
113 |
114 | // GET USER PROFILE
115 | export const getUserProfileController = async (req, res) => {
116 | try {
117 | const user = await userModel.findById(req.user._id);
118 | user.password = undefined;
119 | res.status(200).send({
120 | success: true,
121 | message: "USer Prfolie Fetched Successfully",
122 | user,
123 | });
124 | } catch (error) {
125 | console.log(error);
126 | res.status(500).send({
127 | success: false,
128 | message: "Error In PRofile API",
129 | error,
130 | });
131 | }
132 | };
133 |
134 | // LOGOUT
135 | export const logoutController = async (req, res) => {
136 | try {
137 | res
138 | .status(200)
139 | .cookie("token", "", {
140 | expires: new Date(Date.now()),
141 | secure: process.env.NODE_ENV === "development" ? true : false,
142 | httpOnly: process.env.NODE_ENV === "development" ? true : false,
143 | sameSite: process.env.NODE_ENV === "development" ? true : false,
144 | })
145 | .send({
146 | success: true,
147 | message: "Logout SUccessfully",
148 | });
149 | } catch (error) {
150 | console.log(error);
151 | res.status(500).send({
152 | success: false,
153 | message: "Error In LOgout API",
154 | error,
155 | });
156 | }
157 | };
158 |
159 | // UPDATE USER PROFILE
160 | export const updateProfileController = async (req, res) => {
161 | try {
162 | const user = await userModel.findById(req.user._id);
163 | const { name, email, address, city, country, phone } = req.body;
164 | // validation + Update
165 | if (name) user.name = name;
166 | if (email) user.email = email;
167 | if (address) user.address = address;
168 | if (city) user.city = city;
169 | if (country) user.country = country;
170 | if (phone) user.phone = phone;
171 | //save user
172 | await user.save();
173 | res.status(200).send({
174 | success: true,
175 | message: "User Profile Updated",
176 | });
177 | } catch (error) {
178 | console.log(error);
179 | res.status(500).send({
180 | success: false,
181 | message: "Error In update profile API",
182 | error,
183 | });
184 | }
185 | };
186 |
187 | // update user passsword
188 | export const udpatePasswordController = async (req, res) => {
189 | try {
190 | const user = await userModel.findById(req.user._id);
191 | const { oldPassword, newPassword } = req.body;
192 | //valdiation
193 | if (!oldPassword || !newPassword) {
194 | return res.status(500).send({
195 | success: false,
196 | message: "Please provide old or new password",
197 | });
198 | }
199 | // old pass check
200 | const isMatch = await user.comparePassword(oldPassword);
201 | //validaytion
202 | if (!isMatch) {
203 | return res.status(500).send({
204 | success: false,
205 | message: "Invalid Old Password",
206 | });
207 | }
208 | user.password = newPassword;
209 | await user.save();
210 | res.status(200).send({
211 | success: true,
212 | message: "Password Updated Successfully",
213 | });
214 | } catch (error) {
215 | console.log(error);
216 | res.status(500).send({
217 | success: false,
218 | message: "Error In update password API",
219 | error,
220 | });
221 | }
222 | };
223 |
224 | /// Update user profile photo
225 | export const updateProfilePicController = async (req, res) => {
226 | try {
227 | const user = await userModel.findById(req.user._id);
228 | // file get from client photo
229 | const file = getDataUri(req.file);
230 | // delete prev image
231 | await cloudinary.v2.uploader.destroy(user.profilePic.public_id);
232 | // update
233 | const cdb = await cloudinary.v2.uploader.upload(file.content);
234 | user.profilePic = {
235 | public_id: cdb.public_id,
236 | url: cdb.secure_url,
237 | };
238 | // save func
239 | await user.save();
240 |
241 | res.status(200).send({
242 | success: true,
243 | message: "profile picture updated",
244 | });
245 | } catch (error) {
246 | console.log(error);
247 | res.status(500).send({
248 | success: false,
249 | message: "Error In update profile pic API",
250 | error,
251 | });
252 | }
253 | };
254 |
255 | // FORGOT PASSWORD
256 | export const passwordResetController = async (req, res) => {
257 | try {
258 | // user get email || newPassword || answer
259 | const { email, newPassword, answer } = req.body;
260 | // valdiation
261 | if (!email || !newPassword || !answer) {
262 | return res.status(500).send({
263 | success: false,
264 | message: "Please Provide All Fields",
265 | });
266 | }
267 | // find user
268 | const user = await userModel.findOne({ email, answer });
269 | //valdiation
270 | if (!user) {
271 | return res.status(404).send({
272 | success: false,
273 | message: "invalid user or answer",
274 | });
275 | }
276 |
277 | user.password = newPassword;
278 | await user.save();
279 | res.status(200).send({
280 | success: true,
281 | message: "Your Password Has Been Reset Please Login !",
282 | });
283 | } catch (error) {
284 | console.log(error);
285 | res.status(500).send({
286 | success: false,
287 | message: "Error In password reset API",
288 | error,
289 | });
290 | }
291 | };
292 |
--------------------------------------------------------------------------------
/server/middlewares/authMiddleware.js:
--------------------------------------------------------------------------------
1 | import JWT from "jsonwebtoken";
2 | import userMdoel from "../models/userModel.js";
3 |
4 | // USER AUTH
5 | export const isAuth = async (req, res, next) => {
6 | const { token } = req.cookies;
7 | //valdiation
8 | if (!token) {
9 | return res.status(401).send({
10 | success: false,
11 | message: "UnAuthorized User",
12 | });
13 | }
14 | const decodeData = JWT.verify(token, process.env.JWT_SECRET);
15 | req.user = await userMdoel.findById(decodeData._id);
16 | next();
17 | };
18 |
19 | // ADMIN AUTH
20 | export const isAdmin = async (req, res, next) => {
21 | if (req.user.role !== "admin") {
22 | return res.status(401).send({
23 | success: false,
24 | message: "admin only",
25 | });
26 | }
27 | next();
28 | };
29 |
--------------------------------------------------------------------------------
/server/middlewares/multer.js:
--------------------------------------------------------------------------------
1 | import multer from "multer";
2 |
3 | const storage = multer.memoryStorage();
4 |
5 | export const singleUpload = multer({ storage }).single("file");
6 |
--------------------------------------------------------------------------------
/server/models/categoryModel.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const categorySchema = new mongoose.Schema(
4 | {
5 | category: {
6 | type: String,
7 | required: [true, "category is required"],
8 | },
9 | },
10 | { timestamps: true }
11 | );
12 |
13 | export const categoryModel = mongoose.model("Category", categorySchema);
14 | export default categoryModel;
15 |
--------------------------------------------------------------------------------
/server/models/orderModel.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const orderSchema = new mongoose.Schema(
4 | {
5 | shippingInfo: {
6 | address: {
7 | type: String,
8 | required: [true, "address is required"],
9 | },
10 | city: {
11 | type: String,
12 | required: [true, "city name is required"],
13 | },
14 | country: {
15 | type: String,
16 | required: [true, " country name is require"],
17 | },
18 | },
19 | orderItems: [
20 | {
21 | name: {
22 | type: String,
23 | required: [true, "product name is require"],
24 | },
25 | price: {
26 | type: Number,
27 | required: [true, "product price is require"],
28 | },
29 | quantity: {
30 | type: Number,
31 | required: [true, "product quantity is require"],
32 | },
33 | image: {
34 | type: String,
35 | required: [true, "product image is require"],
36 | },
37 | product: {
38 | type: mongoose.Schema.Types.ObjectId,
39 | ref: "Products",
40 | required: true,
41 | },
42 | },
43 | ],
44 | paymentMethod: {
45 | type: String,
46 | enum: ["COD", "ONLINE"],
47 | default: "COD",
48 | },
49 | user: {
50 | type: mongoose.Schema.Types.ObjectId,
51 | ref: "Users",
52 | required: [true, "user id is require"],
53 | },
54 | paidAt: Date,
55 | paymentInfo: {
56 | id: String,
57 | status: String,
58 | },
59 | itemPrice: {
60 | type: Number,
61 | required: [true, "item price is require"],
62 | },
63 | tax: {
64 | type: Number,
65 | required: [true, "tax price is require"],
66 | },
67 | shippingCharges: {
68 | type: Number,
69 | required: [true, "item shippingCharges is require"],
70 | },
71 | totalAmount: {
72 | type: Number,
73 | required: [true, "item totalAmount price is require"],
74 | },
75 | orderStatus: {
76 | type: String,
77 | enum: ["processing", "shipped", "deliverd"],
78 | default: "processing",
79 | },
80 | deliverdAt: Date,
81 | },
82 | { timestamps: true }
83 | );
84 |
85 | export const orderModel = mongoose.model("Orders", orderSchema);
86 | export default orderModel;
87 |
--------------------------------------------------------------------------------
/server/models/productModel.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | // REVIEW MODAL
4 | const reviewSchema = new mongoose.Schema(
5 | {
6 | name: {
7 | type: String,
8 | required: [true, "name is require"],
9 | },
10 | rating: {
11 | type: Number,
12 | default: 0,
13 | },
14 | comment: {
15 | type: String,
16 | },
17 | user: {
18 | type: mongoose.Schema.Types.ObjectId,
19 | ref: "Users",
20 | required: [true, "user require"],
21 | },
22 | },
23 | { timestamps: true }
24 | );
25 |
26 | // PROCUCT MODAL
27 | const productSchema = new mongoose.Schema(
28 | {
29 | name: {
30 | type: String,
31 | required: [true, "product name is required"],
32 | },
33 | description: {
34 | type: String,
35 | required: [true, "produvct description is required"],
36 | },
37 | price: {
38 | type: Number,
39 | required: [true, "product price is required"],
40 | },
41 | stock: {
42 | type: Number,
43 | required: [true, "product stock required"],
44 | },
45 | // quantity: {
46 | // type: Number,
47 | // required: [true, "product quantity required"],
48 | // },
49 | category: {
50 | type: mongoose.Schema.Types.ObjectId,
51 | ref: "Category",
52 | },
53 | images: [
54 | {
55 | public_id: String,
56 | url: String,
57 | },
58 | ],
59 | reviews: [reviewSchema],
60 | rating: {
61 | type: Number,
62 | default: 0,
63 | },
64 | numReviews: {
65 | type: Number,
66 | default: 0,
67 | },
68 | },
69 | { timestamps: true }
70 | );
71 |
72 | export const productModel = mongoose.model("Products", productSchema);
73 | export default productModel;
74 |
--------------------------------------------------------------------------------
/server/models/userModel.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 | import bcrypt from "bcryptjs";
3 | import JWT from "jsonwebtoken";
4 | const userSchema = new mongoose.Schema(
5 | {
6 | name: {
7 | type: String,
8 | required: [true, "name is required"],
9 | },
10 | email: {
11 | type: String,
12 | required: [true, "email is required"],
13 | unique: [true, "email already taken"],
14 | },
15 | password: {
16 | type: String,
17 | required: [true, "password is required"],
18 | minLength: [6, "password length should be greadter then 6 character"],
19 | },
20 | address: {
21 | type: String,
22 | required: [true, "address is required"],
23 | },
24 | city: {
25 | type: String,
26 | required: [true, "city name is required"],
27 | },
28 | country: {
29 | type: String,
30 | required: [true, "country name is required"],
31 | },
32 | phone: {
33 | type: String,
34 | required: [true, "phone no is required"],
35 | },
36 | profilePic: {
37 | public_id: {
38 | type: String,
39 | },
40 | url: {
41 | type: String,
42 | },
43 | },
44 | answer: {
45 | type: String,
46 | required: [true, "answer is required"],
47 | },
48 | role: {
49 | type: String,
50 | default: "user",
51 | },
52 | },
53 | { timestamps: true }
54 | );
55 |
56 | //fuynctuions
57 | // hash func
58 | userSchema.pre("save", async function (next) {
59 | if (!this.isModified("password")) return next();
60 | this.password = await bcrypt.hash(this.password, 10);
61 | });
62 |
63 | // compare function
64 | userSchema.methods.comparePassword = async function (plainPassword) {
65 | return await bcrypt.compare(plainPassword, this.password);
66 | };
67 |
68 | //JWT TOKEN
69 | userSchema.methods.generateToken = function () {
70 | return JWT.sign({ _id: this._id }, process.env.JWT_SECRET, {
71 | expiresIn: "7d",
72 | });
73 | };
74 |
75 | export const userMdoel = mongoose.model("Users", userSchema);
76 | export default userMdoel;
77 |
--------------------------------------------------------------------------------
/server/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ecommerce",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "ecommerce",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "bcryptjs": "^2.4.3",
13 | "cloudinary": "^1.41.0",
14 | "colors": "^1.4.0",
15 | "cookie-parser": "^1.4.6",
16 | "cors": "^2.8.5",
17 | "datauri": "^4.1.0",
18 | "dotenv": "^16.3.1",
19 | "express": "^4.18.2",
20 | "express-mongo-sanitize": "^2.2.0",
21 | "express-rate-limit": "^7.1.4",
22 | "helmet": "^7.1.0",
23 | "jsonwebtoken": "^9.0.2",
24 | "mongoose": "^7.6.2",
25 | "morgan": "^1.10.0",
26 | "multer": "^1.4.5-lts.1",
27 | "stripe": "^14.3.0"
28 | }
29 | },
30 | "node_modules/@mongodb-js/saslprep": {
31 | "version": "1.1.0",
32 | "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.0.tgz",
33 | "integrity": "sha512-Xfijy7HvfzzqiOAhAepF4SGN5e9leLkMvg/OPOF97XemjfVCYN/oWa75wnkc6mltMSTwY+XlbhWgUOJmkFspSw==",
34 | "optional": true,
35 | "dependencies": {
36 | "sparse-bitfield": "^3.0.3"
37 | }
38 | },
39 | "node_modules/@types/node": {
40 | "version": "20.8.6",
41 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.6.tgz",
42 | "integrity": "sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ==",
43 | "dependencies": {
44 | "undici-types": "~5.25.1"
45 | }
46 | },
47 | "node_modules/@types/webidl-conversions": {
48 | "version": "7.0.1",
49 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.1.tgz",
50 | "integrity": "sha512-8hKOnOan+Uu+NgMaCouhg3cT9x5fFZ92Jwf+uDLXLu/MFRbXxlWwGeQY7KVHkeSft6RvY+tdxklUBuyY9eIEKg=="
51 | },
52 | "node_modules/@types/whatwg-url": {
53 | "version": "8.2.2",
54 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
55 | "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
56 | "dependencies": {
57 | "@types/node": "*",
58 | "@types/webidl-conversions": "*"
59 | }
60 | },
61 | "node_modules/accepts": {
62 | "version": "1.3.8",
63 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
64 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
65 | "dependencies": {
66 | "mime-types": "~2.1.34",
67 | "negotiator": "0.6.3"
68 | },
69 | "engines": {
70 | "node": ">= 0.6"
71 | }
72 | },
73 | "node_modules/append-field": {
74 | "version": "1.0.0",
75 | "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
76 | "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
77 | },
78 | "node_modules/array-flatten": {
79 | "version": "1.1.1",
80 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
81 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
82 | },
83 | "node_modules/basic-auth": {
84 | "version": "2.0.1",
85 | "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
86 | "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
87 | "dependencies": {
88 | "safe-buffer": "5.1.2"
89 | },
90 | "engines": {
91 | "node": ">= 0.8"
92 | }
93 | },
94 | "node_modules/basic-auth/node_modules/safe-buffer": {
95 | "version": "5.1.2",
96 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
97 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
98 | },
99 | "node_modules/bcryptjs": {
100 | "version": "2.4.3",
101 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
102 | "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
103 | },
104 | "node_modules/body-parser": {
105 | "version": "1.20.1",
106 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
107 | "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
108 | "dependencies": {
109 | "bytes": "3.1.2",
110 | "content-type": "~1.0.4",
111 | "debug": "2.6.9",
112 | "depd": "2.0.0",
113 | "destroy": "1.2.0",
114 | "http-errors": "2.0.0",
115 | "iconv-lite": "0.4.24",
116 | "on-finished": "2.4.1",
117 | "qs": "6.11.0",
118 | "raw-body": "2.5.1",
119 | "type-is": "~1.6.18",
120 | "unpipe": "1.0.0"
121 | },
122 | "engines": {
123 | "node": ">= 0.8",
124 | "npm": "1.2.8000 || >= 1.4.16"
125 | }
126 | },
127 | "node_modules/bson": {
128 | "version": "5.5.0",
129 | "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.0.tgz",
130 | "integrity": "sha512-B+QB4YmDx9RStKv8LLSl/aVIEV3nYJc3cJNNTK2Cd1TL+7P+cNpw9mAPeCgc5K+j01Dv6sxUzcITXDx7ZU3F0w==",
131 | "engines": {
132 | "node": ">=14.20.1"
133 | }
134 | },
135 | "node_modules/buffer-equal-constant-time": {
136 | "version": "1.0.1",
137 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
138 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
139 | },
140 | "node_modules/buffer-from": {
141 | "version": "1.1.2",
142 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
143 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
144 | },
145 | "node_modules/busboy": {
146 | "version": "1.6.0",
147 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
148 | "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
149 | "dependencies": {
150 | "streamsearch": "^1.1.0"
151 | },
152 | "engines": {
153 | "node": ">=10.16.0"
154 | }
155 | },
156 | "node_modules/bytes": {
157 | "version": "3.1.2",
158 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
159 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
160 | "engines": {
161 | "node": ">= 0.8"
162 | }
163 | },
164 | "node_modules/call-bind": {
165 | "version": "1.0.2",
166 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
167 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
168 | "dependencies": {
169 | "function-bind": "^1.1.1",
170 | "get-intrinsic": "^1.0.2"
171 | },
172 | "funding": {
173 | "url": "https://github.com/sponsors/ljharb"
174 | }
175 | },
176 | "node_modules/cloudinary": {
177 | "version": "1.41.0",
178 | "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-1.41.0.tgz",
179 | "integrity": "sha512-qFf2McjvILJITePf4VF1PrY/8c2zy+/q5FVV6V3VWrP/gpIZsusPqXL4QZ6ZKXibPRukzMYqsQEhaSQgJHKKow==",
180 | "dependencies": {
181 | "cloudinary-core": "^2.13.0",
182 | "core-js": "^3.30.1",
183 | "lodash": "^4.17.21",
184 | "q": "^1.5.1"
185 | },
186 | "engines": {
187 | "node": ">=0.6"
188 | }
189 | },
190 | "node_modules/cloudinary-core": {
191 | "version": "2.13.0",
192 | "resolved": "https://registry.npmjs.org/cloudinary-core/-/cloudinary-core-2.13.0.tgz",
193 | "integrity": "sha512-Nt0Q5I2FtenmJghtC4YZ3MZZbGg1wLm84SsxcuVwZ83OyJqG9CNIGp86CiI6iDv3QobaqBUpOT7vg+HqY5HxEA==",
194 | "peerDependencies": {
195 | "lodash": ">=4.0"
196 | }
197 | },
198 | "node_modules/colors": {
199 | "version": "1.4.0",
200 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
201 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
202 | "engines": {
203 | "node": ">=0.1.90"
204 | }
205 | },
206 | "node_modules/concat-stream": {
207 | "version": "1.6.2",
208 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
209 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
210 | "engines": [
211 | "node >= 0.8"
212 | ],
213 | "dependencies": {
214 | "buffer-from": "^1.0.0",
215 | "inherits": "^2.0.3",
216 | "readable-stream": "^2.2.2",
217 | "typedarray": "^0.0.6"
218 | }
219 | },
220 | "node_modules/content-disposition": {
221 | "version": "0.5.4",
222 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
223 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
224 | "dependencies": {
225 | "safe-buffer": "5.2.1"
226 | },
227 | "engines": {
228 | "node": ">= 0.6"
229 | }
230 | },
231 | "node_modules/content-type": {
232 | "version": "1.0.5",
233 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
234 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
235 | "engines": {
236 | "node": ">= 0.6"
237 | }
238 | },
239 | "node_modules/cookie": {
240 | "version": "0.5.0",
241 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
242 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
243 | "engines": {
244 | "node": ">= 0.6"
245 | }
246 | },
247 | "node_modules/cookie-parser": {
248 | "version": "1.4.6",
249 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
250 | "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
251 | "dependencies": {
252 | "cookie": "0.4.1",
253 | "cookie-signature": "1.0.6"
254 | },
255 | "engines": {
256 | "node": ">= 0.8.0"
257 | }
258 | },
259 | "node_modules/cookie-parser/node_modules/cookie": {
260 | "version": "0.4.1",
261 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
262 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
263 | "engines": {
264 | "node": ">= 0.6"
265 | }
266 | },
267 | "node_modules/cookie-signature": {
268 | "version": "1.0.6",
269 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
270 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
271 | },
272 | "node_modules/core-js": {
273 | "version": "3.33.1",
274 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.1.tgz",
275 | "integrity": "sha512-qVSq3s+d4+GsqN0teRCJtM6tdEEXyWxjzbhVrCHmBS5ZTM0FS2MOS0D13dUXAWDUN6a+lHI/N1hF9Ytz6iLl9Q==",
276 | "hasInstallScript": true,
277 | "funding": {
278 | "type": "opencollective",
279 | "url": "https://opencollective.com/core-js"
280 | }
281 | },
282 | "node_modules/core-util-is": {
283 | "version": "1.0.3",
284 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
285 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
286 | },
287 | "node_modules/cors": {
288 | "version": "2.8.5",
289 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
290 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
291 | "dependencies": {
292 | "object-assign": "^4",
293 | "vary": "^1"
294 | },
295 | "engines": {
296 | "node": ">= 0.10"
297 | }
298 | },
299 | "node_modules/datauri": {
300 | "version": "4.1.0",
301 | "resolved": "https://registry.npmjs.org/datauri/-/datauri-4.1.0.tgz",
302 | "integrity": "sha512-y17kh32+I82G+ED9MNWFkZiP/Cq/vO1hN9+tSZsT9C9qn3NrvcBnh7crSepg0AQPge1hXx2Ca44s1FRdv0gFWA==",
303 | "dependencies": {
304 | "image-size": "1.0.0",
305 | "mimer": "^2.0.2"
306 | },
307 | "engines": {
308 | "node": ">= 10"
309 | }
310 | },
311 | "node_modules/debug": {
312 | "version": "2.6.9",
313 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
314 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
315 | "dependencies": {
316 | "ms": "2.0.0"
317 | }
318 | },
319 | "node_modules/depd": {
320 | "version": "2.0.0",
321 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
322 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
323 | "engines": {
324 | "node": ">= 0.8"
325 | }
326 | },
327 | "node_modules/destroy": {
328 | "version": "1.2.0",
329 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
330 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
331 | "engines": {
332 | "node": ">= 0.8",
333 | "npm": "1.2.8000 || >= 1.4.16"
334 | }
335 | },
336 | "node_modules/dotenv": {
337 | "version": "16.3.1",
338 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
339 | "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
340 | "engines": {
341 | "node": ">=12"
342 | },
343 | "funding": {
344 | "url": "https://github.com/motdotla/dotenv?sponsor=1"
345 | }
346 | },
347 | "node_modules/ecdsa-sig-formatter": {
348 | "version": "1.0.11",
349 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
350 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
351 | "dependencies": {
352 | "safe-buffer": "^5.0.1"
353 | }
354 | },
355 | "node_modules/ee-first": {
356 | "version": "1.1.1",
357 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
358 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
359 | },
360 | "node_modules/encodeurl": {
361 | "version": "1.0.2",
362 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
363 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
364 | "engines": {
365 | "node": ">= 0.8"
366 | }
367 | },
368 | "node_modules/escape-html": {
369 | "version": "1.0.3",
370 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
371 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
372 | },
373 | "node_modules/etag": {
374 | "version": "1.8.1",
375 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
376 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
377 | "engines": {
378 | "node": ">= 0.6"
379 | }
380 | },
381 | "node_modules/express": {
382 | "version": "4.18.2",
383 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
384 | "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
385 | "dependencies": {
386 | "accepts": "~1.3.8",
387 | "array-flatten": "1.1.1",
388 | "body-parser": "1.20.1",
389 | "content-disposition": "0.5.4",
390 | "content-type": "~1.0.4",
391 | "cookie": "0.5.0",
392 | "cookie-signature": "1.0.6",
393 | "debug": "2.6.9",
394 | "depd": "2.0.0",
395 | "encodeurl": "~1.0.2",
396 | "escape-html": "~1.0.3",
397 | "etag": "~1.8.1",
398 | "finalhandler": "1.2.0",
399 | "fresh": "0.5.2",
400 | "http-errors": "2.0.0",
401 | "merge-descriptors": "1.0.1",
402 | "methods": "~1.1.2",
403 | "on-finished": "2.4.1",
404 | "parseurl": "~1.3.3",
405 | "path-to-regexp": "0.1.7",
406 | "proxy-addr": "~2.0.7",
407 | "qs": "6.11.0",
408 | "range-parser": "~1.2.1",
409 | "safe-buffer": "5.2.1",
410 | "send": "0.18.0",
411 | "serve-static": "1.15.0",
412 | "setprototypeof": "1.2.0",
413 | "statuses": "2.0.1",
414 | "type-is": "~1.6.18",
415 | "utils-merge": "1.0.1",
416 | "vary": "~1.1.2"
417 | },
418 | "engines": {
419 | "node": ">= 0.10.0"
420 | }
421 | },
422 | "node_modules/express-mongo-sanitize": {
423 | "version": "2.2.0",
424 | "resolved": "https://registry.npmjs.org/express-mongo-sanitize/-/express-mongo-sanitize-2.2.0.tgz",
425 | "integrity": "sha512-PZBs5nwhD6ek9ZuP+W2xmpvcrHwXZxD5GdieX2dsjPbAbH4azOkrHbycBud2QRU+YQF1CT+pki/lZGedHgo/dQ==",
426 | "engines": {
427 | "node": ">=10"
428 | }
429 | },
430 | "node_modules/express-rate-limit": {
431 | "version": "7.1.4",
432 | "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.1.4.tgz",
433 | "integrity": "sha512-mv/6z+EwnWpr+MjGVavMGvM4Tl8S/tHmpl9ZsDfrQeHpYy4Hfr0UYdKEf9OOTe280oIr70yPxLRmQ6MfINfJDw==",
434 | "engines": {
435 | "node": ">= 16"
436 | },
437 | "peerDependencies": {
438 | "express": "4 || 5 || ^5.0.0-beta.1"
439 | }
440 | },
441 | "node_modules/finalhandler": {
442 | "version": "1.2.0",
443 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
444 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
445 | "dependencies": {
446 | "debug": "2.6.9",
447 | "encodeurl": "~1.0.2",
448 | "escape-html": "~1.0.3",
449 | "on-finished": "2.4.1",
450 | "parseurl": "~1.3.3",
451 | "statuses": "2.0.1",
452 | "unpipe": "~1.0.0"
453 | },
454 | "engines": {
455 | "node": ">= 0.8"
456 | }
457 | },
458 | "node_modules/forwarded": {
459 | "version": "0.2.0",
460 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
461 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
462 | "engines": {
463 | "node": ">= 0.6"
464 | }
465 | },
466 | "node_modules/fresh": {
467 | "version": "0.5.2",
468 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
469 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
470 | "engines": {
471 | "node": ">= 0.6"
472 | }
473 | },
474 | "node_modules/function-bind": {
475 | "version": "1.1.1",
476 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
477 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
478 | },
479 | "node_modules/get-intrinsic": {
480 | "version": "1.2.1",
481 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
482 | "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
483 | "dependencies": {
484 | "function-bind": "^1.1.1",
485 | "has": "^1.0.3",
486 | "has-proto": "^1.0.1",
487 | "has-symbols": "^1.0.3"
488 | },
489 | "funding": {
490 | "url": "https://github.com/sponsors/ljharb"
491 | }
492 | },
493 | "node_modules/has": {
494 | "version": "1.0.4",
495 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz",
496 | "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==",
497 | "engines": {
498 | "node": ">= 0.4.0"
499 | }
500 | },
501 | "node_modules/has-proto": {
502 | "version": "1.0.1",
503 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
504 | "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
505 | "engines": {
506 | "node": ">= 0.4"
507 | },
508 | "funding": {
509 | "url": "https://github.com/sponsors/ljharb"
510 | }
511 | },
512 | "node_modules/has-symbols": {
513 | "version": "1.0.3",
514 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
515 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
516 | "engines": {
517 | "node": ">= 0.4"
518 | },
519 | "funding": {
520 | "url": "https://github.com/sponsors/ljharb"
521 | }
522 | },
523 | "node_modules/helmet": {
524 | "version": "7.1.0",
525 | "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz",
526 | "integrity": "sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg==",
527 | "engines": {
528 | "node": ">=16.0.0"
529 | }
530 | },
531 | "node_modules/http-errors": {
532 | "version": "2.0.0",
533 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
534 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
535 | "dependencies": {
536 | "depd": "2.0.0",
537 | "inherits": "2.0.4",
538 | "setprototypeof": "1.2.0",
539 | "statuses": "2.0.1",
540 | "toidentifier": "1.0.1"
541 | },
542 | "engines": {
543 | "node": ">= 0.8"
544 | }
545 | },
546 | "node_modules/iconv-lite": {
547 | "version": "0.4.24",
548 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
549 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
550 | "dependencies": {
551 | "safer-buffer": ">= 2.1.2 < 3"
552 | },
553 | "engines": {
554 | "node": ">=0.10.0"
555 | }
556 | },
557 | "node_modules/image-size": {
558 | "version": "1.0.0",
559 | "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.0.tgz",
560 | "integrity": "sha512-JLJ6OwBfO1KcA+TvJT+v8gbE6iWbj24LyDNFgFEN0lzegn6cC6a/p3NIDaepMsJjQjlUWqIC7wJv8lBFxPNjcw==",
561 | "dependencies": {
562 | "queue": "6.0.2"
563 | },
564 | "bin": {
565 | "image-size": "bin/image-size.js"
566 | },
567 | "engines": {
568 | "node": ">=12.0.0"
569 | }
570 | },
571 | "node_modules/inherits": {
572 | "version": "2.0.4",
573 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
574 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
575 | },
576 | "node_modules/ip": {
577 | "version": "2.0.0",
578 | "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
579 | "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
580 | },
581 | "node_modules/ipaddr.js": {
582 | "version": "1.9.1",
583 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
584 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
585 | "engines": {
586 | "node": ">= 0.10"
587 | }
588 | },
589 | "node_modules/isarray": {
590 | "version": "1.0.0",
591 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
592 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
593 | },
594 | "node_modules/jsonwebtoken": {
595 | "version": "9.0.2",
596 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
597 | "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
598 | "dependencies": {
599 | "jws": "^3.2.2",
600 | "lodash.includes": "^4.3.0",
601 | "lodash.isboolean": "^3.0.3",
602 | "lodash.isinteger": "^4.0.4",
603 | "lodash.isnumber": "^3.0.3",
604 | "lodash.isplainobject": "^4.0.6",
605 | "lodash.isstring": "^4.0.1",
606 | "lodash.once": "^4.0.0",
607 | "ms": "^2.1.1",
608 | "semver": "^7.5.4"
609 | },
610 | "engines": {
611 | "node": ">=12",
612 | "npm": ">=6"
613 | }
614 | },
615 | "node_modules/jsonwebtoken/node_modules/ms": {
616 | "version": "2.1.3",
617 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
618 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
619 | },
620 | "node_modules/jwa": {
621 | "version": "1.4.1",
622 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
623 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
624 | "dependencies": {
625 | "buffer-equal-constant-time": "1.0.1",
626 | "ecdsa-sig-formatter": "1.0.11",
627 | "safe-buffer": "^5.0.1"
628 | }
629 | },
630 | "node_modules/jws": {
631 | "version": "3.2.2",
632 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
633 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
634 | "dependencies": {
635 | "jwa": "^1.4.1",
636 | "safe-buffer": "^5.0.1"
637 | }
638 | },
639 | "node_modules/kareem": {
640 | "version": "2.5.1",
641 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz",
642 | "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==",
643 | "engines": {
644 | "node": ">=12.0.0"
645 | }
646 | },
647 | "node_modules/lodash": {
648 | "version": "4.17.21",
649 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
650 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
651 | },
652 | "node_modules/lodash.includes": {
653 | "version": "4.3.0",
654 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
655 | "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
656 | },
657 | "node_modules/lodash.isboolean": {
658 | "version": "3.0.3",
659 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
660 | "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
661 | },
662 | "node_modules/lodash.isinteger": {
663 | "version": "4.0.4",
664 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
665 | "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
666 | },
667 | "node_modules/lodash.isnumber": {
668 | "version": "3.0.3",
669 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
670 | "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
671 | },
672 | "node_modules/lodash.isplainobject": {
673 | "version": "4.0.6",
674 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
675 | "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
676 | },
677 | "node_modules/lodash.isstring": {
678 | "version": "4.0.1",
679 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
680 | "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
681 | },
682 | "node_modules/lodash.once": {
683 | "version": "4.1.1",
684 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
685 | "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
686 | },
687 | "node_modules/lru-cache": {
688 | "version": "6.0.0",
689 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
690 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
691 | "dependencies": {
692 | "yallist": "^4.0.0"
693 | },
694 | "engines": {
695 | "node": ">=10"
696 | }
697 | },
698 | "node_modules/media-typer": {
699 | "version": "0.3.0",
700 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
701 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
702 | "engines": {
703 | "node": ">= 0.6"
704 | }
705 | },
706 | "node_modules/memory-pager": {
707 | "version": "1.5.0",
708 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
709 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
710 | "optional": true
711 | },
712 | "node_modules/merge-descriptors": {
713 | "version": "1.0.1",
714 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
715 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
716 | },
717 | "node_modules/methods": {
718 | "version": "1.1.2",
719 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
720 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
721 | "engines": {
722 | "node": ">= 0.6"
723 | }
724 | },
725 | "node_modules/mime": {
726 | "version": "1.6.0",
727 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
728 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
729 | "bin": {
730 | "mime": "cli.js"
731 | },
732 | "engines": {
733 | "node": ">=4"
734 | }
735 | },
736 | "node_modules/mime-db": {
737 | "version": "1.52.0",
738 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
739 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
740 | "engines": {
741 | "node": ">= 0.6"
742 | }
743 | },
744 | "node_modules/mime-types": {
745 | "version": "2.1.35",
746 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
747 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
748 | "dependencies": {
749 | "mime-db": "1.52.0"
750 | },
751 | "engines": {
752 | "node": ">= 0.6"
753 | }
754 | },
755 | "node_modules/mimer": {
756 | "version": "2.0.2",
757 | "resolved": "https://registry.npmjs.org/mimer/-/mimer-2.0.2.tgz",
758 | "integrity": "sha512-izxvjsB7Ur5HrTbPu6VKTrzxSMBFBqyZQc6dWlZNQ4/wAvf886fD4lrjtFd8IQ8/WmZKdxKjUtqFFNaj3hQ52g==",
759 | "bin": {
760 | "mimer": "bin/mimer"
761 | },
762 | "engines": {
763 | "node": ">= 12"
764 | }
765 | },
766 | "node_modules/minimist": {
767 | "version": "1.2.8",
768 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
769 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
770 | "funding": {
771 | "url": "https://github.com/sponsors/ljharb"
772 | }
773 | },
774 | "node_modules/mkdirp": {
775 | "version": "0.5.6",
776 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
777 | "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
778 | "dependencies": {
779 | "minimist": "^1.2.6"
780 | },
781 | "bin": {
782 | "mkdirp": "bin/cmd.js"
783 | }
784 | },
785 | "node_modules/mongodb": {
786 | "version": "5.9.0",
787 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.0.tgz",
788 | "integrity": "sha512-g+GCMHN1CoRUA+wb1Agv0TI4YTSiWr42B5ulkiAfLLHitGK1R+PkSAf3Lr5rPZwi/3F04LiaZEW0Kxro9Fi2TA==",
789 | "dependencies": {
790 | "bson": "^5.5.0",
791 | "mongodb-connection-string-url": "^2.6.0",
792 | "socks": "^2.7.1"
793 | },
794 | "engines": {
795 | "node": ">=14.20.1"
796 | },
797 | "optionalDependencies": {
798 | "@mongodb-js/saslprep": "^1.1.0"
799 | },
800 | "peerDependencies": {
801 | "@aws-sdk/credential-providers": "^3.188.0",
802 | "@mongodb-js/zstd": "^1.0.0",
803 | "kerberos": "^1.0.0 || ^2.0.0",
804 | "mongodb-client-encryption": ">=2.3.0 <3",
805 | "snappy": "^7.2.2"
806 | },
807 | "peerDependenciesMeta": {
808 | "@aws-sdk/credential-providers": {
809 | "optional": true
810 | },
811 | "@mongodb-js/zstd": {
812 | "optional": true
813 | },
814 | "kerberos": {
815 | "optional": true
816 | },
817 | "mongodb-client-encryption": {
818 | "optional": true
819 | },
820 | "snappy": {
821 | "optional": true
822 | }
823 | }
824 | },
825 | "node_modules/mongodb-connection-string-url": {
826 | "version": "2.6.0",
827 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz",
828 | "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==",
829 | "dependencies": {
830 | "@types/whatwg-url": "^8.2.1",
831 | "whatwg-url": "^11.0.0"
832 | }
833 | },
834 | "node_modules/mongoose": {
835 | "version": "7.6.2",
836 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.6.2.tgz",
837 | "integrity": "sha512-OVx6RWbfNOzBbfTvXoOkgZmaizdXDU/B/KbBjietXQoInSg/OSULjOavXJzL51XWFkbefqkOvbeE07DfvW6FkQ==",
838 | "dependencies": {
839 | "bson": "^5.5.0",
840 | "kareem": "2.5.1",
841 | "mongodb": "5.9.0",
842 | "mpath": "0.9.0",
843 | "mquery": "5.0.0",
844 | "ms": "2.1.3",
845 | "sift": "16.0.1"
846 | },
847 | "engines": {
848 | "node": ">=14.20.1"
849 | },
850 | "funding": {
851 | "type": "opencollective",
852 | "url": "https://opencollective.com/mongoose"
853 | }
854 | },
855 | "node_modules/mongoose/node_modules/ms": {
856 | "version": "2.1.3",
857 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
858 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
859 | },
860 | "node_modules/morgan": {
861 | "version": "1.10.0",
862 | "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
863 | "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
864 | "dependencies": {
865 | "basic-auth": "~2.0.1",
866 | "debug": "2.6.9",
867 | "depd": "~2.0.0",
868 | "on-finished": "~2.3.0",
869 | "on-headers": "~1.0.2"
870 | },
871 | "engines": {
872 | "node": ">= 0.8.0"
873 | }
874 | },
875 | "node_modules/morgan/node_modules/on-finished": {
876 | "version": "2.3.0",
877 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
878 | "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
879 | "dependencies": {
880 | "ee-first": "1.1.1"
881 | },
882 | "engines": {
883 | "node": ">= 0.8"
884 | }
885 | },
886 | "node_modules/mpath": {
887 | "version": "0.9.0",
888 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
889 | "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
890 | "engines": {
891 | "node": ">=4.0.0"
892 | }
893 | },
894 | "node_modules/mquery": {
895 | "version": "5.0.0",
896 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
897 | "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
898 | "dependencies": {
899 | "debug": "4.x"
900 | },
901 | "engines": {
902 | "node": ">=14.0.0"
903 | }
904 | },
905 | "node_modules/mquery/node_modules/debug": {
906 | "version": "4.3.4",
907 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
908 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
909 | "dependencies": {
910 | "ms": "2.1.2"
911 | },
912 | "engines": {
913 | "node": ">=6.0"
914 | },
915 | "peerDependenciesMeta": {
916 | "supports-color": {
917 | "optional": true
918 | }
919 | }
920 | },
921 | "node_modules/mquery/node_modules/ms": {
922 | "version": "2.1.2",
923 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
924 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
925 | },
926 | "node_modules/ms": {
927 | "version": "2.0.0",
928 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
929 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
930 | },
931 | "node_modules/multer": {
932 | "version": "1.4.5-lts.1",
933 | "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
934 | "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
935 | "dependencies": {
936 | "append-field": "^1.0.0",
937 | "busboy": "^1.0.0",
938 | "concat-stream": "^1.5.2",
939 | "mkdirp": "^0.5.4",
940 | "object-assign": "^4.1.1",
941 | "type-is": "^1.6.4",
942 | "xtend": "^4.0.0"
943 | },
944 | "engines": {
945 | "node": ">= 6.0.0"
946 | }
947 | },
948 | "node_modules/negotiator": {
949 | "version": "0.6.3",
950 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
951 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
952 | "engines": {
953 | "node": ">= 0.6"
954 | }
955 | },
956 | "node_modules/object-assign": {
957 | "version": "4.1.1",
958 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
959 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
960 | "engines": {
961 | "node": ">=0.10.0"
962 | }
963 | },
964 | "node_modules/object-inspect": {
965 | "version": "1.12.3",
966 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
967 | "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
968 | "funding": {
969 | "url": "https://github.com/sponsors/ljharb"
970 | }
971 | },
972 | "node_modules/on-finished": {
973 | "version": "2.4.1",
974 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
975 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
976 | "dependencies": {
977 | "ee-first": "1.1.1"
978 | },
979 | "engines": {
980 | "node": ">= 0.8"
981 | }
982 | },
983 | "node_modules/on-headers": {
984 | "version": "1.0.2",
985 | "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
986 | "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
987 | "engines": {
988 | "node": ">= 0.8"
989 | }
990 | },
991 | "node_modules/parseurl": {
992 | "version": "1.3.3",
993 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
994 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
995 | "engines": {
996 | "node": ">= 0.8"
997 | }
998 | },
999 | "node_modules/path-to-regexp": {
1000 | "version": "0.1.7",
1001 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
1002 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
1003 | },
1004 | "node_modules/process-nextick-args": {
1005 | "version": "2.0.1",
1006 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
1007 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
1008 | },
1009 | "node_modules/proxy-addr": {
1010 | "version": "2.0.7",
1011 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1012 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1013 | "dependencies": {
1014 | "forwarded": "0.2.0",
1015 | "ipaddr.js": "1.9.1"
1016 | },
1017 | "engines": {
1018 | "node": ">= 0.10"
1019 | }
1020 | },
1021 | "node_modules/punycode": {
1022 | "version": "2.3.0",
1023 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
1024 | "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
1025 | "engines": {
1026 | "node": ">=6"
1027 | }
1028 | },
1029 | "node_modules/q": {
1030 | "version": "1.5.1",
1031 | "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
1032 | "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==",
1033 | "engines": {
1034 | "node": ">=0.6.0",
1035 | "teleport": ">=0.2.0"
1036 | }
1037 | },
1038 | "node_modules/qs": {
1039 | "version": "6.11.0",
1040 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
1041 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
1042 | "dependencies": {
1043 | "side-channel": "^1.0.4"
1044 | },
1045 | "engines": {
1046 | "node": ">=0.6"
1047 | },
1048 | "funding": {
1049 | "url": "https://github.com/sponsors/ljharb"
1050 | }
1051 | },
1052 | "node_modules/queue": {
1053 | "version": "6.0.2",
1054 | "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
1055 | "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
1056 | "dependencies": {
1057 | "inherits": "~2.0.3"
1058 | }
1059 | },
1060 | "node_modules/range-parser": {
1061 | "version": "1.2.1",
1062 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1063 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
1064 | "engines": {
1065 | "node": ">= 0.6"
1066 | }
1067 | },
1068 | "node_modules/raw-body": {
1069 | "version": "2.5.1",
1070 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
1071 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
1072 | "dependencies": {
1073 | "bytes": "3.1.2",
1074 | "http-errors": "2.0.0",
1075 | "iconv-lite": "0.4.24",
1076 | "unpipe": "1.0.0"
1077 | },
1078 | "engines": {
1079 | "node": ">= 0.8"
1080 | }
1081 | },
1082 | "node_modules/readable-stream": {
1083 | "version": "2.3.8",
1084 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
1085 | "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
1086 | "dependencies": {
1087 | "core-util-is": "~1.0.0",
1088 | "inherits": "~2.0.3",
1089 | "isarray": "~1.0.0",
1090 | "process-nextick-args": "~2.0.0",
1091 | "safe-buffer": "~5.1.1",
1092 | "string_decoder": "~1.1.1",
1093 | "util-deprecate": "~1.0.1"
1094 | }
1095 | },
1096 | "node_modules/readable-stream/node_modules/safe-buffer": {
1097 | "version": "5.1.2",
1098 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
1099 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
1100 | },
1101 | "node_modules/safe-buffer": {
1102 | "version": "5.2.1",
1103 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1104 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1105 | "funding": [
1106 | {
1107 | "type": "github",
1108 | "url": "https://github.com/sponsors/feross"
1109 | },
1110 | {
1111 | "type": "patreon",
1112 | "url": "https://www.patreon.com/feross"
1113 | },
1114 | {
1115 | "type": "consulting",
1116 | "url": "https://feross.org/support"
1117 | }
1118 | ]
1119 | },
1120 | "node_modules/safer-buffer": {
1121 | "version": "2.1.2",
1122 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1123 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
1124 | },
1125 | "node_modules/semver": {
1126 | "version": "7.5.4",
1127 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
1128 | "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
1129 | "dependencies": {
1130 | "lru-cache": "^6.0.0"
1131 | },
1132 | "bin": {
1133 | "semver": "bin/semver.js"
1134 | },
1135 | "engines": {
1136 | "node": ">=10"
1137 | }
1138 | },
1139 | "node_modules/send": {
1140 | "version": "0.18.0",
1141 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
1142 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
1143 | "dependencies": {
1144 | "debug": "2.6.9",
1145 | "depd": "2.0.0",
1146 | "destroy": "1.2.0",
1147 | "encodeurl": "~1.0.2",
1148 | "escape-html": "~1.0.3",
1149 | "etag": "~1.8.1",
1150 | "fresh": "0.5.2",
1151 | "http-errors": "2.0.0",
1152 | "mime": "1.6.0",
1153 | "ms": "2.1.3",
1154 | "on-finished": "2.4.1",
1155 | "range-parser": "~1.2.1",
1156 | "statuses": "2.0.1"
1157 | },
1158 | "engines": {
1159 | "node": ">= 0.8.0"
1160 | }
1161 | },
1162 | "node_modules/send/node_modules/ms": {
1163 | "version": "2.1.3",
1164 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1165 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1166 | },
1167 | "node_modules/serve-static": {
1168 | "version": "1.15.0",
1169 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
1170 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
1171 | "dependencies": {
1172 | "encodeurl": "~1.0.2",
1173 | "escape-html": "~1.0.3",
1174 | "parseurl": "~1.3.3",
1175 | "send": "0.18.0"
1176 | },
1177 | "engines": {
1178 | "node": ">= 0.8.0"
1179 | }
1180 | },
1181 | "node_modules/setprototypeof": {
1182 | "version": "1.2.0",
1183 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1184 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
1185 | },
1186 | "node_modules/side-channel": {
1187 | "version": "1.0.4",
1188 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
1189 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
1190 | "dependencies": {
1191 | "call-bind": "^1.0.0",
1192 | "get-intrinsic": "^1.0.2",
1193 | "object-inspect": "^1.9.0"
1194 | },
1195 | "funding": {
1196 | "url": "https://github.com/sponsors/ljharb"
1197 | }
1198 | },
1199 | "node_modules/sift": {
1200 | "version": "16.0.1",
1201 | "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz",
1202 | "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ=="
1203 | },
1204 | "node_modules/smart-buffer": {
1205 | "version": "4.2.0",
1206 | "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
1207 | "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
1208 | "engines": {
1209 | "node": ">= 6.0.0",
1210 | "npm": ">= 3.0.0"
1211 | }
1212 | },
1213 | "node_modules/socks": {
1214 | "version": "2.7.1",
1215 | "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz",
1216 | "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==",
1217 | "dependencies": {
1218 | "ip": "^2.0.0",
1219 | "smart-buffer": "^4.2.0"
1220 | },
1221 | "engines": {
1222 | "node": ">= 10.13.0",
1223 | "npm": ">= 3.0.0"
1224 | }
1225 | },
1226 | "node_modules/sparse-bitfield": {
1227 | "version": "3.0.3",
1228 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
1229 | "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
1230 | "optional": true,
1231 | "dependencies": {
1232 | "memory-pager": "^1.0.2"
1233 | }
1234 | },
1235 | "node_modules/statuses": {
1236 | "version": "2.0.1",
1237 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
1238 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
1239 | "engines": {
1240 | "node": ">= 0.8"
1241 | }
1242 | },
1243 | "node_modules/streamsearch": {
1244 | "version": "1.1.0",
1245 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
1246 | "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
1247 | "engines": {
1248 | "node": ">=10.0.0"
1249 | }
1250 | },
1251 | "node_modules/string_decoder": {
1252 | "version": "1.1.1",
1253 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
1254 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
1255 | "dependencies": {
1256 | "safe-buffer": "~5.1.0"
1257 | }
1258 | },
1259 | "node_modules/string_decoder/node_modules/safe-buffer": {
1260 | "version": "5.1.2",
1261 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
1262 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
1263 | },
1264 | "node_modules/stripe": {
1265 | "version": "14.3.0",
1266 | "resolved": "https://registry.npmjs.org/stripe/-/stripe-14.3.0.tgz",
1267 | "integrity": "sha512-R3s+3ONM1XFOTzbMSIML0tixbkuz+gFY/p1h1Qxd9OUftxS8m+rGeBv4ZnvoVhTUwOokArfzQtQlR2Re9XnyQw==",
1268 | "dependencies": {
1269 | "@types/node": ">=8.1.0",
1270 | "qs": "^6.11.0"
1271 | },
1272 | "engines": {
1273 | "node": ">=12.*"
1274 | }
1275 | },
1276 | "node_modules/toidentifier": {
1277 | "version": "1.0.1",
1278 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1279 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1280 | "engines": {
1281 | "node": ">=0.6"
1282 | }
1283 | },
1284 | "node_modules/tr46": {
1285 | "version": "3.0.0",
1286 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
1287 | "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
1288 | "dependencies": {
1289 | "punycode": "^2.1.1"
1290 | },
1291 | "engines": {
1292 | "node": ">=12"
1293 | }
1294 | },
1295 | "node_modules/type-is": {
1296 | "version": "1.6.18",
1297 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1298 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1299 | "dependencies": {
1300 | "media-typer": "0.3.0",
1301 | "mime-types": "~2.1.24"
1302 | },
1303 | "engines": {
1304 | "node": ">= 0.6"
1305 | }
1306 | },
1307 | "node_modules/typedarray": {
1308 | "version": "0.0.6",
1309 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
1310 | "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
1311 | },
1312 | "node_modules/undici-types": {
1313 | "version": "5.25.3",
1314 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz",
1315 | "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA=="
1316 | },
1317 | "node_modules/unpipe": {
1318 | "version": "1.0.0",
1319 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1320 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1321 | "engines": {
1322 | "node": ">= 0.8"
1323 | }
1324 | },
1325 | "node_modules/util-deprecate": {
1326 | "version": "1.0.2",
1327 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1328 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
1329 | },
1330 | "node_modules/utils-merge": {
1331 | "version": "1.0.1",
1332 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1333 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1334 | "engines": {
1335 | "node": ">= 0.4.0"
1336 | }
1337 | },
1338 | "node_modules/vary": {
1339 | "version": "1.1.2",
1340 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1341 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1342 | "engines": {
1343 | "node": ">= 0.8"
1344 | }
1345 | },
1346 | "node_modules/webidl-conversions": {
1347 | "version": "7.0.0",
1348 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
1349 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
1350 | "engines": {
1351 | "node": ">=12"
1352 | }
1353 | },
1354 | "node_modules/whatwg-url": {
1355 | "version": "11.0.0",
1356 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
1357 | "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
1358 | "dependencies": {
1359 | "tr46": "^3.0.0",
1360 | "webidl-conversions": "^7.0.0"
1361 | },
1362 | "engines": {
1363 | "node": ">=12"
1364 | }
1365 | },
1366 | "node_modules/xtend": {
1367 | "version": "4.0.2",
1368 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
1369 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
1370 | "engines": {
1371 | "node": ">=0.4"
1372 | }
1373 | },
1374 | "node_modules/yallist": {
1375 | "version": "4.0.0",
1376 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
1377 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
1378 | }
1379 | }
1380 | }
1381 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ecommerce",
3 | "version": "1.0.0",
4 | "description": "ecommerce app ",
5 | "main": "server.js",
6 | "type": "module",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "server": "nodemon server.js"
10 | },
11 | "author": "Techinfoyt",
12 | "license": "ISC",
13 | "dependencies": {
14 | "bcryptjs": "^2.4.3",
15 | "cloudinary": "^1.41.0",
16 | "colors": "^1.4.0",
17 | "cookie-parser": "^1.4.6",
18 | "cors": "^2.8.5",
19 | "datauri": "^4.1.0",
20 | "dotenv": "^16.3.1",
21 | "express": "^4.18.2",
22 | "express-mongo-sanitize": "^2.2.0",
23 | "express-rate-limit": "^7.1.4",
24 | "helmet": "^7.1.0",
25 | "jsonwebtoken": "^9.0.2",
26 | "mongoose": "^7.6.2",
27 | "morgan": "^1.10.0",
28 | "multer": "^1.4.5-lts.1",
29 | "stripe": "^14.3.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/server/routes/categoryRoutes.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import { isAdmin, isAuth } from "./../middlewares/authMiddleware.js";
3 | import {
4 | createCategory,
5 | deleteCategoryController,
6 | getAllCategoriesController,
7 | updateCategoryController,
8 | } from "../controllers/categoryController.js";
9 |
10 | const router = express.Router();
11 |
12 | //rroutes
13 | // ============== CAT ROUTES ==================
14 |
15 | // CREATE CATEGORY
16 | router.post("/create", isAuth, isAdmin, createCategory);
17 |
18 | // GET ALL CATEGORY
19 | router.get("/get-all", getAllCategoriesController);
20 |
21 | // DELETE CATEGORY
22 | router.delete("/delete/:id", isAuth, isAdmin, deleteCategoryController);
23 |
24 | // UPDATE ALL CATEGORY
25 | router.put("/update/:id", isAuth, isAdmin, updateCategoryController);
26 |
27 | // ====================================================================
28 |
29 | export default router;
30 |
--------------------------------------------------------------------------------
/server/routes/orderRoutes.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import { isAdmin, isAuth } from "./../middlewares/authMiddleware.js";
3 | import {
4 | changeOrderStatusController,
5 | createOrderController,
6 | getAllOrdersController,
7 | getMyOrdersCotroller,
8 | paymetsController,
9 | singleOrderDetrailsController,
10 | } from "../controllers/orderController.js";
11 |
12 | const router = express.Router();
13 |
14 | //rroutes
15 | // ============== ORDERS ROUTES ==================
16 |
17 | // CREATE ORDERS
18 | router.post("/create", isAuth, createOrderController);
19 |
20 | // GET ALL ORDERS
21 | router.get("/my-orders", isAuth, getMyOrdersCotroller);
22 |
23 | // GET SINGLE ORDER DETAILS
24 | router.get("/my-orders/:id", isAuth, singleOrderDetrailsController);
25 |
26 | // acceipt payments
27 | router.post("/payments", isAuth, paymetsController);
28 |
29 | /// ======== ADMIN PART ============
30 | // get all order
31 | router.get("/admin/get-all-orders", isAuth, isAdmin, getAllOrdersController);
32 |
33 | // change order status
34 | router.put("/admin/order/:id", isAuth, isAdmin, changeOrderStatusController);
35 |
36 | // ====================================================================
37 |
38 | export default router;
39 |
--------------------------------------------------------------------------------
/server/routes/productRoutes.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import { isAdmin, isAuth } from "./../middlewares/authMiddleware.js";
3 | import {
4 | createProductController,
5 | deleteProductController,
6 | deleteProductImageController,
7 | getAllProductsController,
8 | getSingleProductController,
9 | getTopProductsController,
10 | productReviewController,
11 | updateProductController,
12 | updateProductImageController,
13 | } from "../controllers/productController.js";
14 | import { singleUpload } from "../middlewares/multer.js";
15 |
16 | const router = express.Router();
17 |
18 | //rroutes
19 | // ============== PRODUCT ROUTES ==================
20 |
21 | // GET ALL PRODUCTS
22 | router.get("/get-all", getAllProductsController);
23 |
24 | // GET TOP PRODUCTS
25 | router.get("/top", getTopProductsController);
26 |
27 | // GET SINGLE PRODUCTS
28 | router.get("/:id", getSingleProductController);
29 |
30 | // CREATE PRODUCT
31 | router.post("/create", isAuth, isAdmin, singleUpload, createProductController);
32 |
33 | // UPDATE PRODUCT
34 | router.put("/:id", isAuth, isAdmin, updateProductController);
35 |
36 | // UPDATE PRODUCT IMAGE
37 | router.put(
38 | "/image/:id",
39 | isAuth,
40 | isAdmin,
41 | singleUpload,
42 | updateProductImageController
43 | );
44 |
45 | // delete product image
46 | router.delete(
47 | "/delete-image/:id",
48 | isAuth,
49 | isAdmin,
50 | deleteProductImageController
51 | );
52 |
53 | // delete product
54 | router.delete("/delete/:id", isAuth, isAdmin, deleteProductController);
55 |
56 | // REVIEW PRODUCT
57 | router.put("/:id/review", isAuth, productReviewController);
58 |
59 | // ====================================================================
60 |
61 | export default router;
62 |
--------------------------------------------------------------------------------
/server/routes/testRoutes.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import { testController } from "../controllers/testController.js";
3 |
4 | //router object
5 | const router = express.Router();
6 |
7 | //routes
8 | router.get("/test", testController);
9 |
10 | // export
11 | export default router;
12 |
--------------------------------------------------------------------------------
/server/routes/userRoutes.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import {
3 | getUserProfileController,
4 | loginController,
5 | logoutController,
6 | passwordResetController,
7 | registerController,
8 | udpatePasswordController,
9 | updateProfileController,
10 | updateProfilePicController,
11 | } from "../controllers/userController.js";
12 | import { isAuth } from "../middlewares/authMiddleware.js";
13 | import { singleUpload } from "../middlewares/multer.js";
14 | import { rateLimit } from "express-rate-limit";
15 |
16 | // RATE LIMITER
17 | const limiter = rateLimit({
18 | windowMs: 15 * 60 * 1000, // 15 minutes
19 | limit: 100, // Limit each IP to 100 requests per `window` (here, per 15 minutes).
20 | standardHeaders: "draft-7", // draft-6: `RateLimit-*` headers; draft-7: combined `RateLimit` header
21 | legacyHeaders: false, // Disable the `X-RateLimit-*` headers.
22 | // store: ... , // Use an external store for consistency across multiple server instances.
23 | });
24 |
25 | //router object
26 | const router = express.Router();
27 |
28 | //routes
29 | // register
30 | router.post("/register", limiter, registerController);
31 |
32 | //login
33 | router.post("/login", limiter, loginController);
34 |
35 | //profile
36 | router.get("/profile", isAuth, getUserProfileController);
37 |
38 | //logout
39 | router.get("/logout", isAuth, logoutController);
40 |
41 | // uopdate profile
42 | router.put("/profile-update", isAuth, updateProfileController);
43 |
44 | // updte password
45 | router.put("/update-password", isAuth, udpatePasswordController);
46 |
47 | // update profile pic
48 | router.put("/update-picture", isAuth, singleUpload, updateProfilePicController);
49 |
50 | // FORGOT PASSWORD
51 | router.post("/reset-password", passwordResetController);
52 |
53 | //export
54 | export default router;
55 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import colors from "colors";
3 | import morgan from "morgan";
4 | import cors from "cors";
5 | import dotenv from "dotenv";
6 | import cookieParser from "cookie-parser";
7 | import cloudinary from "cloudinary";
8 | import Stripe from "stripe";
9 | import helmet from "helmet";
10 | import mongoSanitize from "express-mongo-sanitize";
11 |
12 | import connectDB from "./config/db.js";
13 | // dot env config
14 | dotenv.config();
15 |
16 | //database connection
17 | connectDB();
18 |
19 | //stripe configuration
20 | export const stripe = new Stripe(process.env.STRIPE_API_SECRET);
21 |
22 | //cloudinary Config
23 | cloudinary.v2.config({
24 | cloud_name: process.env.CLOUDINARY_NAME,
25 | api_key: process.env.CLOUDINARY_API_KEY,
26 | api_secret: process.env.CLOUDINARY_SECRET,
27 | });
28 |
29 | //rest object
30 | const app = express();
31 |
32 | //middlewares
33 | app.use(helmet());
34 | app.use(mongoSanitize());
35 | app.use(morgan("dev"));
36 | app.use(express.json());
37 | app.use(cors());
38 | app.use(cookieParser());
39 |
40 | //route
41 | //routes imports
42 | import testRoutes from "./routes/testRoutes.js";
43 | import userRoutes from "./routes/userRoutes.js";
44 | import productRoutes from "./routes/productRoutes.js";
45 | import categoryRoutes from "./routes/categoryRoutes.js";
46 | import orderRoutes from "./routes/orderRoutes.js";
47 | app.use("/api/v1", testRoutes);
48 | app.use("/api/v1/user", userRoutes);
49 | app.use("/api/v1/product", productRoutes);
50 | app.use("/api/v1/cat", categoryRoutes);
51 | app.use("/api/v1/order", orderRoutes);
52 |
53 | app.get("/", (req, res) => {
54 | return res.status(200).send("
Welcome To Node server
");
55 | });
56 |
57 | //port
58 | const PORT = process.env.PORT || 8080;
59 |
60 | //listen
61 | app.listen(PORT, () => {
62 | console.log(
63 | `Server Running On PORT ${process.env.PORT} on ${process.env.NODE_ENV} Mode`
64 | .bgMagenta.white
65 | );
66 | });
67 |
--------------------------------------------------------------------------------
/server/utils/features.js:
--------------------------------------------------------------------------------
1 | import DataURIParser from "datauri/parser.js";
2 |
3 | import path from "path";
4 |
5 | export const getDataUri = (file) => {
6 | const parser = new DataURIParser();
7 | const extName = path.extname(file.originalname).toString();
8 | return parser.format(extName, file.buffer);
9 | };
10 |
--------------------------------------------------------------------------------