├── Front
├── .gitignore
├── src
│ ├── assets
│ │ ├── css
│ │ │ ├── addbook.css
│ │ │ ├── bookcard.css
│ │ │ ├── index.css
│ │ │ ├── Login.css
│ │ │ ├── footer.css
│ │ │ ├── bookbtn.css
│ │ │ ├── navbar.css
│ │ │ └── profile.css
│ │ └── images
│ │ │ ├── cover.jpg
│ │ │ ├── logowa.png
│ │ │ ├── logowacopy.png
│ │ │ └── brickwork-covers-top.jpg
│ ├── components
│ │ ├── Sign
│ │ │ ├── casino-background_2x-removebg-preview.png
│ │ │ ├── Sign.js
│ │ │ ├── Sign_in_up_component
│ │ │ │ ├── Sign_in.js
│ │ │ │ └── Sign_up.js
│ │ │ └── Sign.module.css
│ │ ├── admin
│ │ │ ├── Dashboard.jsx
│ │ │ ├── TrafficSource.jsx
│ │ │ ├── SummaryCards.jsx
│ │ │ └── Usermanagement.jsx
│ │ ├── Category.jsx
│ │ ├── BooksPage.jsx
│ │ ├── favorites.jsx
│ │ ├── BookCard.jsx
│ │ ├── SearchResult.jsx
│ │ ├── AboutUS.jsx
│ │ ├── Navbar.jsx
│ │ ├── Footer.jsx
│ │ ├── Login.jsx
│ │ ├── AddBook.jsx
│ │ ├── OneBook.jsx
│ │ └── Profile.jsx
│ ├── pages
│ │ ├── Book.jsx
│ │ ├── AllBooks.jsx
│ │ ├── HomePage.jsx
│ │ └── NotFound.jsx
│ ├── index.jsx
│ ├── ProtectedRoute.js
│ ├── AdminRoute.jsx
│ ├── layouts
│ │ └── MainLayout.jsx
│ ├── AuthContext.js
│ └── App.jsx
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── favicon.png
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
├── package.json
└── README.md
├── back
├── .gitignore
├── middelware
│ └── jwt.js
├── models
│ ├── User.js
│ └── Book.js
├── package.json
├── app.js
├── routes
│ ├── adminRoutes.js
│ ├── bookRoutes.js
│ └── auth.js
└── package-lock.json
└── README.md
/Front/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.env
--------------------------------------------------------------------------------
/back/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.env
--------------------------------------------------------------------------------
/Front/src/assets/css/addbook.css:
--------------------------------------------------------------------------------
1 | .addbook{
2 | margin-bottom: 100px;
3 | }
--------------------------------------------------------------------------------
/Front/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/Front/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlaeBara/Electronic-Library-Full-Stack/HEAD/Front/public/favicon.ico
--------------------------------------------------------------------------------
/Front/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlaeBara/Electronic-Library-Full-Stack/HEAD/Front/public/favicon.png
--------------------------------------------------------------------------------
/Front/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlaeBara/Electronic-Library-Full-Stack/HEAD/Front/public/logo192.png
--------------------------------------------------------------------------------
/Front/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlaeBara/Electronic-Library-Full-Stack/HEAD/Front/public/logo512.png
--------------------------------------------------------------------------------
/Front/src/assets/images/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlaeBara/Electronic-Library-Full-Stack/HEAD/Front/src/assets/images/cover.jpg
--------------------------------------------------------------------------------
/Front/src/assets/images/logowa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlaeBara/Electronic-Library-Full-Stack/HEAD/Front/src/assets/images/logowa.png
--------------------------------------------------------------------------------
/Front/src/assets/css/bookcard.css:
--------------------------------------------------------------------------------
1 | .clicked {
2 | transform: scale(0.95);
3 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
4 | }
5 |
--------------------------------------------------------------------------------
/Front/src/assets/images/logowacopy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlaeBara/Electronic-Library-Full-Stack/HEAD/Front/src/assets/images/logowacopy.png
--------------------------------------------------------------------------------
/Front/src/assets/images/brickwork-covers-top.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlaeBara/Electronic-Library-Full-Stack/HEAD/Front/src/assets/images/brickwork-covers-top.jpg
--------------------------------------------------------------------------------
/Front/src/components/Sign/casino-background_2x-removebg-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlaeBara/Electronic-Library-Full-Stack/HEAD/Front/src/components/Sign/casino-background_2x-removebg-preview.png
--------------------------------------------------------------------------------
/Front/src/pages/Book.jsx:
--------------------------------------------------------------------------------
1 | import OneBook from '../components/OneBook'
2 | import React from 'react';
3 |
4 |
5 | const AllBooks = () => {
6 | return (
7 | <>
8 |
9 | >
10 | )
11 | }
12 |
13 | export default AllBooks;
--------------------------------------------------------------------------------
/Front/src/pages/AllBooks.jsx:
--------------------------------------------------------------------------------
1 | import BooksPage from '../components/BooksPage'
2 | import React from 'react';
3 |
4 |
5 | const AllBooks = () => {
6 | return (
7 | <>
8 |
9 | >
10 | )
11 | }
12 |
13 | export default AllBooks;
--------------------------------------------------------------------------------
/Front/src/pages/HomePage.jsx:
--------------------------------------------------------------------------------
1 | import Allcategories from '../components/Categories'
2 | import React from 'react';
3 |
4 |
5 | const HomePage = () => {
6 | return (
7 | <>
8 |
9 | >
10 | )
11 | }
12 |
13 | export default HomePage;
--------------------------------------------------------------------------------
/Front/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import App from './App';
4 |
5 |
6 | const root = ReactDOM.createRoot(document.getElementById('root'));
7 | root.render(
8 |
9 |
10 |
11 | );
12 |
--------------------------------------------------------------------------------
/Front/src/ProtectedRoute.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Navigate } from 'react-router-dom';
3 | import { useAuth } from './AuthContext';
4 |
5 | const ProtectedRoute = ({ children }) => {
6 | const { isLoggedIn } = useAuth();
7 |
8 | if (!isLoggedIn) {
9 | return ;
10 | }
11 |
12 | return children;
13 | };
14 |
15 | export default ProtectedRoute;
--------------------------------------------------------------------------------
/Front/src/AdminRoute.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Navigate } from 'react-router-dom';
3 | import { useAuth } from './AuthContext';
4 |
5 | const AdminRoute = ({ children }) => {
6 | const { isAdmin, isLoading } = useAuth();
7 |
8 | if (isLoading) {
9 | return
Loading...
; // Or any loading indicator
10 | }
11 |
12 | if (!isAdmin) {
13 | return ;
14 | }
15 |
16 | return children;
17 | };
18 |
19 | export default AdminRoute;
--------------------------------------------------------------------------------
/back/middelware/jwt.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 |
3 | const jwtMiddleware = (req, res, next) => {
4 | const token = req.cookies.token;
5 | if (!token) return res.status(401).json({ msg: 'No token, authorization denied' });
6 |
7 | try {
8 | const decoded = jwt.verify(token, process.env.JWT_SECRET);
9 | req.user = decoded;
10 | next();
11 | } catch (err) {
12 | res.status(403).json({ msg: 'Token is not valid' });
13 | }
14 | };
15 |
16 |
17 | module.exports={jwtMiddleware}
--------------------------------------------------------------------------------
/Front/src/components/admin/Dashboard.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SummaryCards from './SummaryCards';
3 | import Usermanagement from './Usermanagement';
4 | import { Container, Row, Col } from 'react-bootstrap';
5 |
6 | const Dashboard = () => {
7 | return (
8 |
9 | Admin Dashboard
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default Dashboard;
--------------------------------------------------------------------------------
/back/models/User.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | // The users mongo db schema
4 | const UserSchema = new mongoose.Schema({
5 | username: { type: String, required: true },
6 | phone: { type: String },
7 | address: { type: String},
8 | country: { type: String },
9 | email: { type: String, required: true, unique: true },
10 | password: { type: String, required: true },
11 | profileImage: { type: String },
12 | role: { type: String, enum: ['user', 'admin'], default: 'user' }
13 | });
14 |
15 | const User = mongoose.model('User', UserSchema);
16 |
17 | module.exports = User;
18 |
--------------------------------------------------------------------------------
/Front/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/back/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "back",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "test": "echo \"Error: no test specified\" && exit 1",
7 | "start": "nodemon app.js"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "description": "",
13 | "dependencies": {
14 | "bcryptjs": "^2.4.3",
15 | "body-parser": "^1.20.2",
16 | "cloudinary": "^2.3.0",
17 | "cookie-parser": "^1.4.6",
18 | "cors": "^2.8.5",
19 | "dotenv": "^16.4.5",
20 | "express": "^4.19.2",
21 | "jsonwebtoken": "^9.0.2",
22 | "mongoose": "^8.5.1",
23 | "nodemon": "^3.1.4"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Front/src/assets/css/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 | body {
15 | font-family: 'Roboto', sans-serif;
16 | }
17 | @media (max-width: 768px) {
18 | h1 {
19 | font-size: 2rem !important;
20 | }
21 | h2 {
22 | font-size: 1.5rem !important;
23 | }
24 | }
--------------------------------------------------------------------------------
/Front/src/layouts/MainLayout.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Outlet } from 'react-router-dom';
3 | import Navbar from '../components/Navbar';
4 | import Footer from '../components/Footer';
5 | import { useAuth } from '../AuthContext';
6 |
7 | const MainLayout = () => {
8 | const { isLoggedIn, setIsLoggedIn, checkAuthStatus } = useAuth();
9 |
10 | return (
11 | <>
12 |
17 |
18 |
19 | >
20 | );
21 | };
22 |
23 | export default MainLayout;
--------------------------------------------------------------------------------
/back/models/Book.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | // book table
4 | const bookSchema = new mongoose.Schema({
5 | id_client: {
6 | type: String,
7 | required: true,
8 | },
9 | title: {
10 | type: String,
11 | required: true,
12 | trim: true
13 | },
14 | author: {
15 | type: String,
16 | required: true,
17 | trim: true
18 | },
19 | description: {
20 | type: String,
21 | required: true,
22 | trim: true
23 | },
24 | cover: {
25 | type: String,
26 | required: true,
27 | trim: true
28 | },
29 | category: {
30 | type: String,
31 | required: true,
32 | enum: ["Adventure", "Romance", "Thriller", "Memoir", "Travel", "Health", "Poetry", "Cooking"],
33 | },
34 | createdAt: {
35 | type: Date,
36 | default: Date.now
37 | },
38 | pdfUrl: {
39 | type: String,
40 | trim: true
41 | },
42 | });
43 |
44 | module.exports = mongoose.model('Book', bookSchema);
45 |
--------------------------------------------------------------------------------
/back/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const bodyParser = require('body-parser');
3 | const cookieParser = require('cookie-parser');
4 | const mongoose = require('mongoose');
5 | const dotenv = require('dotenv');
6 | const cors = require('cors'); // Add this line
7 |
8 | dotenv.config();
9 |
10 | const app = express();
11 |
12 | // Middlewares
13 | app.use(bodyParser.json());
14 | app.use(cookieParser());
15 |
16 | // CORS configuration
17 | const corsOptions = {
18 | origin: 'http://localhost:3000',
19 | credentials: true,
20 | };
21 | app.use(cors(corsOptions));
22 | // Connect to MongoDB
23 | mongoose.connect(process.env.MONGO_URI)
24 | .then(() => console.log('MongoDB connected'))
25 | .catch((err) => console.log(err));
26 |
27 | app.use('/api/auth', require('./routes/auth'));
28 | app.use('/api', require('./routes/bookRoutes'));
29 | app.use('/api/admin', require('./routes/adminRoutes'));
30 |
31 |
32 | const PORT = process.env.PORT || 5000;
33 | app.listen(PORT, () => {
34 | console.log(`Server running on port ${PORT}`);
35 | });
36 |
--------------------------------------------------------------------------------
/Front/src/pages/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Container, Row, Col, Button } from 'react-bootstrap';
3 | import { Link } from 'react-router-dom';
4 |
5 | const NotFound = () => {
6 | return (
7 |
8 |
9 |
10 | 404
11 | Oops! Page Not Found
12 |
13 | The page you're looking for doesn't exist or has been moved.
14 |
15 |
16 |
17 | Go Back to Home
18 |
19 |
20 |
21 |
22 |
23 | );
24 | }
25 |
26 | export default NotFound;
--------------------------------------------------------------------------------
/Front/src/components/Category.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Card } from 'react-bootstrap';
3 |
4 | const Category = ({ name, color, icon }) => (
5 | {
14 | e.currentTarget.style.transform = 'translateY(-5px)';
15 | e.currentTarget.style.boxShadow = '0 4px 15px rgba(0,0,0,0.1)';
16 | }}
17 | onMouseOut={e => {
18 | e.currentTarget.style.transform = 'translateY(0)';
19 | e.currentTarget.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
20 | }}
21 | >
22 |
23 | {icon}
24 | {name}
25 |
26 |
27 | );
28 |
29 | export default Category;
--------------------------------------------------------------------------------
/Front/src/components/admin/TrafficSource.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { Card } from 'react-bootstrap';
4 | import { PieChart, Pie, Cell, ResponsiveContainer, Legend } from 'recharts';
5 |
6 | const data = [
7 | { name: 'Desktop', value: 63 },
8 | { name: 'Tablet', value: 15 },
9 | { name: 'Phone', value: 22 },
10 | ];
11 |
12 | const COLORS = ['#8884d8', '#82ca9d', '#ffc658'];
13 |
14 | function TrafficSource() {
15 | return (
16 |
17 |
18 | Traffic source
19 |
20 |
21 |
31 | {data.map((entry, index) => (
32 | |
33 | ))}
34 |
35 |
36 |
37 |
38 |
39 |
40 | );
41 | }
42 |
43 | export default TrafficSource;
44 |
--------------------------------------------------------------------------------
/Front/src/assets/css/Login.css:
--------------------------------------------------------------------------------
1 | .login-form {
2 | transition: all 0.3s ease;
3 | margin-top: 50px;
4 | }
5 |
6 | .login-form:hover {
7 | transform: translateY(-5px);
8 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
9 | }
10 |
11 | .login-logo {
12 | width: 80px;
13 | margin-bottom: 1.5rem;
14 | }
15 |
16 | .login-title {
17 | color: #070707;
18 | font-weight: bold;
19 | margin-bottom: 1.5rem;
20 | }
21 |
22 | .login-input {
23 | border: 1px solid #ced4da;
24 | border-radius: 5px;
25 | padding: 10px 15px;
26 | transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
27 | }
28 |
29 | .login-input:focus {
30 | border-color: #E74C3C;
31 | box-shadow: 0 0 0 0.2rem rgba(14, 121, 170, 0.25);
32 | }
33 |
34 | .login-button {
35 | background-color: #E74C3C;
36 | border: none;
37 | padding: 10px 0;
38 | font-size: 1.1rem;
39 | font-weight: 500;
40 | transition: background-color 0.3s ease;
41 | }
42 |
43 | .login-button:hover:not(:disabled) {
44 | background-color: #950c0c;
45 | }
46 |
47 | .login-button:disabled {
48 | background-color: #6c757d;
49 | cursor: not-allowed;
50 | }
51 |
52 | @media (max-width: 576px) {
53 | .login-form {
54 | padding: 1.5rem !important;
55 | }
56 |
57 | .login-title {
58 | font-size: 1.5rem;
59 | }
60 |
61 | .login-button {
62 | font-size: 1rem;
63 | }
64 | }
--------------------------------------------------------------------------------
/Front/src/assets/css/footer.css:
--------------------------------------------------------------------------------
1 | .footer {
2 | background-color: #2B3A42;
3 | text-align: center;
4 | padding: 3rem 0;
5 | margin-top: auto;
6 |
7 | }
8 |
9 | .footer-content {
10 | display: flex;
11 | flex-direction: column;
12 | align-items: center;
13 | }
14 |
15 | .footer-logo, .footer-links, .footer-social {
16 | margin-bottom: 1.5rem;
17 | }
18 |
19 | .footer-icons {
20 | display: flex;
21 | justify-content: center;
22 | gap: 20px;
23 | }
24 |
25 | .social-icon-link {
26 | transition: transform 0.3s ease, color 0.3s ease;
27 | }
28 |
29 | .social-icon-link:hover {
30 | transform: translateY(-3px);
31 | color: #4a90e2 !important;
32 | }
33 |
34 | .footer-links ul {
35 | padding: 0;
36 | }
37 |
38 | .footer-links li {
39 | margin-bottom: 0.75rem;
40 | }
41 |
42 | .hover-effect {
43 | transition: color 0.3s ease;
44 | }
45 |
46 | .hover-effect:hover {
47 | color: #4a90e2 !important;
48 | }
49 |
50 | #root {
51 | display: flex;
52 | flex-direction: column;
53 | min-height: 100vh;
54 | }
55 |
56 | .main-content {
57 | flex: 1;
58 | }
59 |
60 | @media (min-width: 768px) {
61 | .footer-content {
62 | flex-direction: row;
63 | justify-content: space-between;
64 | align-items: flex-start;
65 | }
66 |
67 | .footer-logo,
68 | .footer-links,
69 | .footer-social {
70 | margin-bottom: 0;
71 | }
72 |
73 | .footer-links {
74 | text-align: left;
75 | }
76 | }
--------------------------------------------------------------------------------
/Front/src/AuthContext.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useState, useContext, useEffect } from 'react';
2 | import axios from 'axios';
3 |
4 | const AuthContext = createContext(null);
5 |
6 | export const AuthProvider = ({ children }) => {
7 | const [isLoggedIn, setIsLoggedIn] = useState(false);
8 | const [user, setUser] = useState(null);
9 | const [isAdmin, setIsAdmin] = useState(false);
10 | const [isLoading, setIsLoading] = useState(true);
11 |
12 | const checkAuthStatus = async () => {
13 | setIsLoading(true);
14 | try {
15 | const response = await axios.post('http://localhost:5000/api/auth/check_authenticateToken', {}, {
16 | withCredentials: true
17 | });
18 | if (response.status === 200) {
19 | setIsLoggedIn(true);
20 | setUser(response.data.user);
21 | setIsAdmin(response.data.user.role === 'admin');
22 | } else {
23 | setIsLoggedIn(false);
24 | setUser(null);
25 | setIsAdmin(false);
26 | }
27 | } catch (error) {
28 | console.error('Error checking auth status:', error);
29 | setIsLoggedIn(false);
30 | setUser(null);
31 | setIsAdmin(false);
32 | }
33 | setIsLoading(false);
34 | };
35 |
36 | useEffect(() => {
37 | checkAuthStatus();
38 | }, []);
39 |
40 | return (
41 |
42 | {children}
43 |
44 | );
45 | };
46 |
47 | export const useAuth = () => useContext(AuthContext);
--------------------------------------------------------------------------------
/Front/src/assets/css/bookbtn.css:
--------------------------------------------------------------------------------
1 | .read-book-btn {
2 | background-color: #dd411e;
3 | border-color: #d34120;
4 | width: 100%;
5 | margin-top: 1rem;
6 | margin-bottom: 1rem;
7 | }
8 |
9 | .read-book-btn:hover {
10 | background-color: #E74C3C;
11 | border-color: #E74C3C;
12 | }
13 |
14 | /* Add to Favorites Button */
15 | .add-to-favorites-btn {
16 | background-color: #11b442;
17 | border-color: #10d84c;
18 | width: 100%;
19 | margin-bottom: 1rem;
20 | }
21 |
22 | .read-book-btn:hover {
23 | background-color: #E74C3C;
24 | border-color: #E74C3C;
25 | }
26 | /* bookbtn.css */
27 | .hover-zoom:hover {
28 | transform: scale(1.02);
29 | }
30 |
31 | .image-overlay {
32 | position: absolute;
33 | bottom: 0;
34 | left: 0;
35 | right: 0;
36 | background: rgba(0, 0, 0, 0.5);
37 | color: white;
38 | padding: 8px;
39 | text-align: center;
40 | opacity: 0;
41 | transition: opacity 0.3s ease;
42 | }
43 |
44 | .card:hover .image-overlay {
45 | opacity: 1;
46 | }
47 |
48 | .click-to-zoom {
49 | font-size: 14px;
50 | font-weight: 500;
51 | }
52 |
53 | .book-modal .modal-content {
54 | border-radius: 1rem;
55 | border: none;
56 | box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
57 | }
58 |
59 | .read-book-btn:hover,
60 | .add-to-favorites-btn:hover {
61 | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
62 | }
63 |
64 | .badge {
65 | font-weight: 500;
66 | font-size: 0.9rem;
67 | }
68 |
69 | .image-overlay-container img {
70 | box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
71 | }
--------------------------------------------------------------------------------
/Front/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "library-project",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@fortawesome/free-brands-svg-icons": "^6.6.0",
7 | "@fortawesome/free-solid-svg-icons": "^6.6.0",
8 | "@fortawesome/react-fontawesome": "^0.2.2",
9 | "@radix-ui/react-avatar": "^1.1.1",
10 | "@testing-library/jest-dom": "^5.17.0",
11 | "@testing-library/react": "^13.4.0",
12 | "@testing-library/user-event": "^13.5.0",
13 | "axios": "^1.7.2",
14 | "bootstrap": "^5.3.3",
15 | "cloudinary-react": "^1.8.1",
16 | "js-cookie": "^3.0.5",
17 | "json-server": "^1.0.0-beta.1",
18 | "lucide-react": "^0.453.0",
19 | "react": "^18.3.1",
20 | "react-bootstrap": "^2.10.4",
21 | "react-dom": "^18.3.1",
22 | "react-icons": "^5.2.1",
23 | "react-router-dom": "^6.24.1",
24 | "react-scripts": "^3.0.1",
25 | "shadcn-ui": "^0.9.2",
26 | "web-vitals": "^2.1.4"
27 | },
28 | "scripts": {
29 | "start": "react-scripts --openssl-legacy-provider start",
30 | "build": "react-scripts build",
31 | "test": "react-scripts test",
32 | "eject": "react-scripts eject",
33 | "server": "json-server --watch src/books.json --port 8000"
34 | },
35 | "eslintConfig": {
36 | "extends": [
37 | "react-app",
38 | "react-app/jest"
39 | ]
40 | },
41 | "browserslist": {
42 | "production": [
43 | ">0.2%",
44 | "not dead",
45 | "not op_mini all"
46 | ],
47 | "development": [
48 | "last 1 chrome version",
49 | "last 1 firefox version",
50 | "last 1 safari version"
51 | ]
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/back/routes/adminRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const User = require('../models/User');
4 | const Book = require('../models/Book');
5 | const { jwtMiddleware } = require('../middelware/jwt');
6 |
7 |
8 | // Middleware to check if user is admin
9 | const isAdmin = (req, res, next) => {
10 | if (req.user && req.user.role === 'admin') {
11 | next();
12 | } else {
13 | res.status(403).json({ message: 'Access denied. Admin only.' });
14 | }
15 | };
16 |
17 | // Get all users
18 | router.get('/users', jwtMiddleware, isAdmin, async (req, res) => {
19 | try {
20 | const users = await User.find().select('-password');
21 | res.json(users);
22 | } catch (err) {
23 | res.status(500).json({ message: err.message });
24 | }
25 | });
26 |
27 | // Delete a user
28 | router.delete('/users/:id', jwtMiddleware, isAdmin, async (req, res) => {
29 | try {
30 | const deletedUser = await User.findByIdAndDelete(req.params.id);
31 | if (deletedUser) {
32 | res.json({ message: 'User deleted', user: deletedUser });
33 | } else {
34 | res.status(404).json({ message: 'User not found' });
35 | }
36 | } catch (err) {
37 | res.status(500).json({ message: err.message });
38 | }
39 | });
40 |
41 | // Get summary data
42 | router.get('/summary', jwtMiddleware, isAdmin, async (req, res) => {
43 | try {
44 | const totalBooks = await Book.countDocuments();
45 | const totalUsers = await User.countDocuments();
46 | res.json({ totalBooks, totalUsers });
47 | } catch (err) {
48 | res.status(500).json({ message: err.message });
49 | }
50 | });
51 |
52 | module.exports = router;
53 |
--------------------------------------------------------------------------------
/Front/src/components/BooksPage.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Container, Row, Col } from 'react-bootstrap';
3 | import BookCard from './BookCard';
4 |
5 | const BooksPage = () => {
6 | const [books, setBooks] = useState([]);
7 | const [loading, setLoading] = useState(true);
8 | const [error, setError] = useState(null);
9 |
10 | useEffect(() => {
11 | const fetchAllBooks = async () => {
12 | try {
13 | const response = await fetch('http://localhost:5000/api/books');
14 | if (!response.ok) {
15 | throw new Error(`HTTP error! status: ${response.status}`);
16 | }
17 | const data = await response.json();
18 | setBooks(data);
19 | setLoading(false);
20 | } catch (error) {
21 | console.error("Failed to fetch books:", error);
22 | setError("Failed to load books. Please try again later.");
23 | setLoading(false);
24 | }
25 | };
26 |
27 | fetchAllBooks();
28 | }, []);
29 |
30 | if (loading) {
31 | return Loading...
;
32 | }
33 |
34 | if (error) {
35 | return {error}
;
36 | }
37 |
38 | return (
39 |
40 |
41 | Available Books
42 |
43 |
44 | {books.map((book) => (
45 |
46 |
47 |
48 | ))}
49 |
50 |
51 | );
52 | };
53 |
54 | export default BooksPage;
55 |
--------------------------------------------------------------------------------
/Front/src/components/admin/SummaryCards.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { Row, Col, Card } from 'react-bootstrap';
3 | import { FaBook, FaUsers } from 'react-icons/fa';
4 | import axios from 'axios';
5 |
6 | function SummaryCards() {
7 | const [summary, setSummary] = useState({
8 | totalBooks: 0,
9 | totalUsers: 0,
10 | });
11 |
12 | useEffect(() => {
13 | const fetchSummary = async () => {
14 | try {
15 | const response = await axios.get('http://localhost:5000/api/admin//summary', { withCredentials: true });
16 | setSummary({
17 | totalBooks: response.data.totalBooks,
18 | totalUsers: response.data.totalUsers,
19 | });
20 | } catch (error) {
21 | console.error('Error fetching summary data:', error);
22 | }
23 | };
24 |
25 | fetchSummary();
26 | }, []);
27 |
28 | const cardData = [
29 | { title: 'TOTAL BOOKS', value: summary.totalBooks, icon: FaBook },
30 | { title: 'TOTAL USERS', value: summary.totalUsers, icon: FaUsers },
31 | ];
32 |
33 | return (
34 |
35 | {cardData.map((card, index) => (
36 |
37 |
38 |
39 |
40 | {card.title}
41 |
42 |
43 | {card.value}
44 |
45 |
46 |
47 | ))}
48 |
49 | );
50 | }
51 |
52 | export default SummaryCards;
53 |
--------------------------------------------------------------------------------
/Front/src/components/favorites.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Container, Row, Col, Alert } from 'react-bootstrap';
3 | import BookCard from './BookCard';
4 |
5 | const Favorites = () => {
6 | const [favorites, setFavorites] = useState([]);
7 |
8 | useEffect(() => {
9 | const storedFavorites = JSON.parse(localStorage.getItem('favorites')) || [];
10 | setFavorites(storedFavorites);
11 | }, []);
12 |
13 | const removeFavorite = (bookId) => {
14 | const updatedFavorites = favorites.filter(book => book._id !== bookId);
15 | console.log('Updated favorites:', updatedFavorites);
16 | setFavorites(updatedFavorites);
17 | localStorage.setItem('favorites', JSON.stringify(updatedFavorites));
18 | };
19 |
20 | return (
21 |
22 |
23 | Your Favorite Books
24 |
25 | {favorites.length === 0 ? (
26 | You haven't added any books to your favorites yet.
27 | ) : (
28 |
29 | {favorites.map((book) => (
30 |
31 |
32 |
33 | removeFavorite(book._id)}
36 | >
37 | Remove
38 |
39 |
40 |
41 | ))}
42 |
43 | )}
44 |
45 | );
46 | };
47 |
48 | export default Favorites;
--------------------------------------------------------------------------------
/Front/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | EBookHub
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Front/src/components/admin/Usermanagement.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Table, Button, Container, Alert } from 'react-bootstrap';
3 | import axios from 'axios';
4 |
5 | const Usermanagement = () => {
6 | const [users, setUsers] = useState([]);
7 | const [error, setError] = useState(null);
8 |
9 |
10 | const fetchUsers = async () => {
11 | try {
12 | const response = await axios.get('http://localhost:5000/api/admin//users', { withCredentials: true });
13 | setUsers(response.data);
14 | } catch (err) {
15 | setError('Failed to fetch users. Please try again.');
16 | }
17 | };
18 |
19 | useEffect(() => {
20 | fetchUsers();
21 | }, []);
22 |
23 |
24 |
25 | const handleDelete = async (userId) => {
26 | try {
27 | if (window.confirm('Are you sure you want to delete this user?')) {
28 | await axios.delete(`http://localhost:5000/api/admin/users/${userId}`, { withCredentials: true });
29 | fetchUsers();
30 | }
31 | } catch (err) {
32 | setError('Failed to delete the user. Please try again.');
33 | }
34 | };
35 |
36 | return (
37 |
38 | User Management
39 | {error && {error} }
40 |
41 |
42 |
43 | ID
44 | Username
45 | Email
46 | Actions
47 |
48 |
49 |
50 | {users.map(user => (
51 |
52 | {user._id}
53 | {user.username}
54 | {user.email}
55 |
56 | handleDelete(user._id)}>
57 | Delete
58 |
59 |
60 |
61 | ))}
62 |
63 |
64 |
65 | );
66 | }
67 |
68 | export default Usermanagement;
69 |
--------------------------------------------------------------------------------
/Front/src/components/BookCard.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Card } from 'react-bootstrap';
3 | import { Link } from 'react-router-dom';
4 |
5 | const BookCard = ({ book }) => {
6 | const [isClicked, setIsClicked] = useState(false);
7 |
8 | const handleClick = () => {
9 | setIsClicked(!isClicked);
10 | };
11 |
12 | return (
13 |
18 |
24 |
25 | {book.title}
26 | Author: {book.author}
27 | {
36 | e.currentTarget.style.backgroundColor = '#C0392B';
37 | e.currentTarget.style.borderColor = '#C0392B';
38 | e.currentTarget.style.transform = 'translateY(-2px)';
39 | e.currentTarget.style.boxShadow = '0 4px 8px rgba(0,0,0,0.2)';
40 | }}
41 | onMouseOut={(e) => {
42 | e.currentTarget.style.backgroundColor = '#E74C3C';
43 | e.currentTarget.style.borderColor = '#E74C3C';
44 | e.currentTarget.style.transform = 'translateY(0)';
45 | e.currentTarget.style.boxShadow = 'none';
46 | }}
47 | >
48 | View Details
49 |
50 |
51 |
52 | );
53 | };
54 |
55 | export default BookCard;
--------------------------------------------------------------------------------
/Front/src/components/SearchResult.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { useLocation } from 'react-router-dom';
3 | import { Container, Row, Col, Alert } from 'react-bootstrap';
4 | import BookCard from './BookCard';
5 | import axios from 'axios';
6 |
7 | const SearchResults = () => {
8 | const location = useLocation();
9 | const searchParams = new URLSearchParams(location.search);
10 | const query = searchParams.get('q');
11 | const [searchResults, setSearchResults] = useState([]);
12 | const [loading, setLoading] = useState(true);
13 | const [error, setError] = useState(null);
14 |
15 | useEffect(() => {
16 | const fetchSearchResults = async () => {
17 | setLoading(true);
18 | try {
19 | const response = await axios.get(`http://localhost:5000/api/search?q=${query}`);
20 | setSearchResults(response.data);
21 | setLoading(false);
22 | } catch (err) {
23 | console.error("Failed to fetch search results:", err);
24 | setError("Failed to load search results. Please try again later.");
25 | setLoading(false);
26 | }
27 | };
28 |
29 | if (query) {
30 | fetchSearchResults();
31 | }
32 | }, [query]);
33 |
34 | if (loading) {
35 | return Loading...
;
36 | }
37 |
38 | if (error) {
39 | return {error} ;
40 | }
41 |
42 | return (
43 |
44 |
45 | Search Results for "{query}"
46 |
47 | {searchResults.length === 0 ? (
48 | No books found matching your search.
49 | ) : (
50 |
51 | {searchResults.map((book) => (
52 |
53 |
54 |
55 | ))}
56 |
57 | )}
58 |
59 | );
60 | };
61 |
62 | export default SearchResults;
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 📚 Electronic-Library
2 |
3 | A Full web application for publishing and reading books online and managing user profiles with authentication and admin functionalities
4 |
5 | ## 🎨 Layout
6 | 
7 |
8 | 
9 |
10 | 
11 |
12 |
13 | ## 🖥️ Technologies
14 | - MongoDB / Mongoose
15 | - Express.js
16 | - React.js
17 | - Node.js
18 | - JWT Authentication
19 | - Cloudinary for Image Handling
20 |
21 | ## 🚀 Getting Started
22 |
23 | ### Prerequisites
24 | - Node.js
25 | - MongoDB
26 |
27 | ### Installation
28 | 1. Clone the repo:
29 | ```bash
30 | git clone https://github.com/AlaeBara/BookHub_Front-Back.git
31 | ```
32 | 2. Navigate to the project directory:
33 | ```bash
34 | cd BookHub_Front-Back
35 | ```
36 | 3. Install backend dependencies:
37 | ```bash
38 | npm install
39 | ```
40 | 4. Set up environment variables (create a `.env` file):
41 | ```env
42 | MONGO_URI=your_mongodb_connection_string
43 | JWT_SECRET=your_jwt_secret
44 | CLOUDINARY_CLOUD_NAME=your_cloud_name
45 | CLOUDINARY_API_KEY=your_api_key
46 | CLOUDINARY_API_SECRET=your_api_secret
47 | ```
48 |
49 | 5. Start the backend server:
50 | ```bash
51 | npm start
52 | ```
53 |
54 | 6. Navigate to the `client` directory:
55 | ```bash
56 | cd client
57 | ```
58 |
59 | 7. Install frontend dependencies:
60 | ```bash
61 | npm install
62 | ```
63 |
64 | 8. Start the frontend development server:
65 | ```bash
66 | npm start
67 | ```
68 |
69 | ## 📦 Features
70 | - User authentication with JWT
71 | - Secure password hashing
72 | - Create, update, and delete books
73 | - Admin dashboard
74 | - Responsive and user-friendly interface
75 |
76 | ## 📄 Documentation
77 | - [Express.js Documentation](https://expressjs.com/)
78 | - [Mongoose Documentation](https://mongoosejs.com/)
79 | - [React.js Documentation](https://reactjs.org/)
80 | - [Cloudinary Documentation](https://cloudinary.com/documentation)
81 |
82 | ## 📫 Authors
83 | - Abdennacer kaddouri
84 | - Alaeddine Bara
85 |
--------------------------------------------------------------------------------
/Front/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, createBrowserRouter, createRoutesFromElements, RouterProvider } from 'react-router-dom';
3 | import 'bootstrap/dist/css/bootstrap.min.css';
4 | import MainLayout from './layouts/MainLayout';
5 | import AllBooks from './pages/AllBooks';
6 | import NotFound from './pages/NotFound'
7 | import OneBook from './components/OneBook';
8 | import AddBook from './components/AddBook';
9 | import SearchResults from './components/SearchResult';
10 | import AboutUs from './components/AboutUS'
11 | import Favorites from './components/favorites'
12 | import Profile from './components/Profile'
13 | import Sign from './components/Sign/Sign'
14 | import ProtectedRoute from './ProtectedRoute';
15 | import Dashboard from './components/admin/Dashboard';
16 | import { AuthProvider, useAuth } from './AuthContext';
17 | import AdminRoute from './AdminRoute';
18 |
19 | const AppContent = () => {
20 | const { isLoading } = useAuth();
21 |
22 | if (isLoading) {
23 | return Loading...
; // Or any loading indicator
24 | }
25 |
26 | const router = createBrowserRouter(
27 | createRoutesFromElements(
28 | }>
29 | }/>~
30 | } />
31 | } />
32 | } />
33 | } />
34 | } />
35 |
36 | {/* routes admin */}
37 | } />
38 |
39 | {/* routes client */}
40 | } />
41 | } />
42 | } />
43 |
44 | )
45 | );
46 |
47 | return ;
48 | };
49 |
50 | const App = () => {
51 | return (
52 |
53 |
54 |
55 | );
56 | };
57 |
58 | export default App;
--------------------------------------------------------------------------------
/Front/src/components/Sign/Sign.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import styles from './Sign.module.css';
3 | import SignInForm from './Sign_in_up_component/Sign_in';
4 | import SignUpForm from './Sign_in_up_component/Sign_up';
5 | import dda from './casino-background_2x-removebg-preview.png';
6 |
7 |
8 | const AuthContainer = () => {
9 | const [isSignUpMode, setIsSignUpMode] = useState(false);
10 |
11 | const handleSignUpClick = () => {
12 | setIsSignUpMode(true);
13 | };
14 |
15 | const handleSignInClick = () => {
16 | setIsSignUpMode(false);
17 | };
18 |
19 | return (
20 |
21 |
27 |
28 |
29 |
30 |
New to our ebook library?
31 |
32 | Discover a world of knowledge and entertainment with our extensive collection of ebooks. Join us today and start your reading journey!
33 |
34 |
35 | Sign up
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
Already a member?
44 |
45 | Welcome back! Sign in to continue exploring our collection and enjoy personalized recommendations based on your reading history.
46 |
47 |
48 | Sign in
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | );
58 | };
59 |
60 | export default AuthContainer;
--------------------------------------------------------------------------------
/Front/src/components/AboutUS.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Container, Row, Col, Image, Button } from 'react-bootstrap';
3 | import { useNavigate } from 'react-router-dom';
4 | import brickworkImage from '../assets/images/brickwork-covers-top.jpg';
5 |
6 | const AboutUs = () => {
7 | const navigate = useNavigate();
8 |
9 | return (
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 | About Our Library
24 |
25 | Welcome to our library! We are dedicated to providing a vast collection of resources and a comfortable space for all your reading and research needs. Explore our diverse collection and enjoy a quiet place to immerse yourself in the world of books.
26 |
27 | navigate('/')}
32 | style={{
33 | transition: 'all 0.3s ease',
34 | }}
35 | onMouseOver={(e) => {
36 | e.currentTarget.style.transform = 'translateY(-2px)';
37 | e.currentTarget.style.boxShadow = '0 4px 12px rgba(220, 53, 69, 0.3)';
38 | e.currentTarget.style.backgroundColor = '#c82333';
39 | e.currentTarget.style.borderColor = '#c82333';
40 | }}
41 | onMouseOut={(e) => {
42 | e.currentTarget.style.transform = 'translateY(0)';
43 | e.currentTarget.style.boxShadow = 'none';
44 | e.currentTarget.style.backgroundColor = '#dc3545';
45 | e.currentTarget.style.borderColor = '#dc3545';
46 | }}
47 | >
48 | Explore Our Books
49 |
50 |
51 |
52 |
53 |
54 |
60 |
61 |
62 |
63 | );
64 | };
65 |
66 | export default AboutUs;
--------------------------------------------------------------------------------
/Front/src/components/Sign/Sign_in_up_component/Sign_in.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3 | import { faTwitter } from '@fortawesome/free-brands-svg-icons';
4 | import styles from '../Sign.module.css';
5 | import axios from 'axios';
6 | import { useNavigate } from "react-router-dom";
7 | import { useAuth } from '../../../AuthContext';
8 | import { User , LockKeyhole} from 'lucide-react';
9 |
10 | const SignInForm = () => {
11 | const { checkAuthStatus } = useAuth();
12 | const [formData, setFormData] = useState({
13 | email: '',
14 | password: ''
15 | });
16 | const [message, setMessage] = useState('');
17 | const navigate = useNavigate();
18 |
19 | const handleChange = (e) => {
20 | setFormData({ ...formData, [e.target.name]: e.target.value });
21 | };
22 |
23 | const handleSubmit = async (e) => {
24 | e.preventDefault();
25 | try {
26 | const response = await axios.post('http://localhost:5000/api/auth/signin', formData, { withCredentials: true });
27 | setMessage(response.data.message);
28 | await checkAuthStatus(); // Re-check auth status after login
29 | // Navigate based on the user role
30 | if (response.data.role === 'admin') {
31 | navigate("/admin");
32 | } else {
33 | navigate("/profile");
34 | }
35 | } catch (error) {
36 | setMessage(error.response?.data?.message || 'An error occurred during sign-in');
37 | }
38 | };
39 |
40 | return (
41 |
75 | );
76 | };
77 |
78 | export default SignInForm;
--------------------------------------------------------------------------------
/back/routes/bookRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const Book = require('../models/Book');
4 | const {jwtMiddleware} = require('../middelware/jwt');
5 |
6 |
7 | // Get all books
8 | router.get('/books', async (req, res) => {
9 | try {
10 | const books = await Book.find();
11 | res.json(books);
12 | } catch (err) {
13 | res.status(500).json({ message: err.message });
14 | }
15 | });
16 |
17 | // Get a single book by category and id
18 | router.get('/:category/:id', async (req, res) => {
19 | try {
20 | const book = await Book.findOne({ category: req.params.category, _id: req.params.id });
21 | if (book) {
22 | res.json(book);
23 | } else {
24 | res.status(404).json({ message: 'Book not found' });
25 | }
26 | } catch (err) {
27 | res.status(500).json({ message: err.message });
28 | }
29 | });
30 |
31 |
32 |
33 | // Add a new book
34 | router.post('/addbook', jwtMiddleware,async (req, res) => {
35 | const book = new Book({
36 | id_client:req.user.id,
37 | title: req.body.title,
38 | author: req.body.author,
39 | description: req.body.description,
40 | cover: req.body.cover,
41 | category: req.body.category,
42 | pdfUrl: req.body.pdfUrl
43 | });
44 |
45 | try {
46 | const newBook = await book.save();
47 | res.status(201).json(newBook);
48 | } catch (err) {
49 | res.status(400).json({ message: err.message });
50 | }
51 | });
52 |
53 |
54 |
55 | // Delete a book
56 | router.delete('/books/:id', async (req, res) => {
57 | try {
58 | const deletedBook = await Book.findOneAndDelete({_id: req.params.id });
59 | if (deletedBook) {
60 | res.json({ message: 'Book deleted', book: deletedBook });
61 | } else {
62 | res.status(404).json({ message: 'Book not found' });
63 | }
64 | } catch (err) {
65 | res.status(500).json({ message: err.message });
66 | }
67 | });
68 |
69 | // seach bar
70 | router.get('/search', async (req, res) => {
71 | const { q } = req.query;
72 | try {
73 | const books = await Book.find({
74 | $or: [
75 | { title: { $regex: q, $options: 'i' } },
76 | { author: { $regex: q, $options: 'i' } },
77 | { description: { $regex: q, $options: 'i' } }
78 | ]
79 | });
80 | res.json(books);
81 | } catch (err) {
82 | res.status(500).json({ message: err.message });
83 | }
84 | });
85 |
86 | // fetshing exact user book for shing them in profile
87 | router.get('/user-books', jwtMiddleware, async (req, res) => {
88 | try {
89 | const books = await Book.find({ id_client: req.user.id });
90 | res.json(books);
91 | } catch (err) {
92 | res.status(500).json({ message: err.message });
93 | }
94 | });
95 |
96 |
97 |
98 | module.exports = router;
99 |
--------------------------------------------------------------------------------
/Front/src/components/Sign/Sign_in_up_component/Sign_up.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3 | import { faTwitter } from '@fortawesome/free-brands-svg-icons';
4 | import styles from '../Sign.module.css';
5 | import axios from 'axios';
6 | import { User , LockKeyhole , Mail} from 'lucide-react';
7 |
8 | const SignUpForm = ({onSignUpSuccess}) => {
9 | const [formData, setFormData] = useState({
10 | username: '',
11 | email: '',
12 | password: ''
13 | });
14 |
15 | const [message, setMessage] = useState('');
16 |
17 | const handleChange = (e) => {
18 | setFormData({ ...formData, [e.target.name]: e.target.value });
19 | };
20 |
21 | const handleSubmit = async (e) => {
22 | e.preventDefault();
23 | try {
24 | const response = await axios.post('http://localhost:5000/api/auth/signup', formData,{ withCredentials: true });
25 | setMessage(response.data.message);
26 | onSignUpSuccess();
27 | setFormData({
28 | username: '',
29 | email: '',
30 | password: ''
31 | });
32 | } catch (error) {
33 | setMessage(error.response?.data?.message || 'An error occurred during signup');
34 | }
35 | };
36 |
37 | return (
38 |
90 | );
91 | };
92 |
93 | export default SignUpForm;
--------------------------------------------------------------------------------
/Front/src/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Navbar, Container, Nav, Form, FormControl, Button } from 'react-bootstrap';
3 | import { useNavigate, Link } from 'react-router-dom';
4 | import logo from '../assets/images/logowa.png';
5 | import axios from 'axios';
6 | import '../assets/css/navbar.css';
7 |
8 | const CustomNavbar = ({ isLoggedIn, setIsLoggedIn }) => {
9 | const [searchTerm, setSearchTerm] = useState('');
10 | const navigate = useNavigate();
11 |
12 | const handleSearch = (e) => {
13 | e.preventDefault();
14 | if (searchTerm.trim()) {
15 | navigate(`/search?q=${encodeURIComponent(searchTerm.trim())}`);
16 | setSearchTerm('');
17 | }
18 | };
19 |
20 | const handleLogout = async () => {
21 | try {
22 | await axios.get('http://localhost:5000/api/auth/logout', { withCredentials: true });
23 | setIsLoggedIn(false);
24 | navigate('/');
25 | } catch (error) {
26 | console.error('Logout failed:', error);
27 | alert('Logout failed');
28 | }
29 | };
30 |
31 | return (
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
53 |
54 |
55 | {isLoggedIn ? (
56 | <>
57 | Favorite
58 | Add Book
59 | Profile
60 | Logout
61 | >
62 | ) : (
63 | Login
64 | )}
65 |
66 |
67 |
68 |
69 | );
70 | };
71 |
72 | export default CustomNavbar;
73 |
--------------------------------------------------------------------------------
/Front/src/assets/css/navbar.css:
--------------------------------------------------------------------------------
1 | .navbar-custom {
2 | background-color: #ffffff;
3 | padding: 10px 0;
4 | box-shadow: 0 3px 4px rgba(0, 0, 0, 0.1);
5 | }
6 |
7 | /* Logo */
8 | .navbar-logo {
9 | height: 50px;
10 | width: auto;
11 | margin-left: 10px;
12 | }
13 |
14 | /* Navbar brand styles */
15 | .navbar-brand-custom {
16 | color: #BF4F36 !important;
17 | font-size: 1.5rem;
18 | font-weight: bold;
19 | text-decoration: none !important;
20 | }
21 |
22 | .navbar-brand-custom:hover {
23 | color: #BF4F36;
24 | opacity: 0.8;
25 | }
26 |
27 | /* Center the search form */
28 | .search-form {
29 | display: flex;
30 | justify-content: center;
31 | align-items: center;
32 | width: 100%;
33 | max-width: 600px;
34 | }
35 |
36 | .search-input {
37 | height: 44px;
38 | padding: 10px;
39 | font-size: 16px;
40 | border: 1px solid #ddd;
41 | border-radius: 4px 0 0 4px;
42 | width: 100%;
43 | max-width: 70%; /* Prevents button from getting larger than input */
44 | flex-grow: 1;
45 | }
46 |
47 | .search-button {
48 | height: 44px;
49 | padding: 10px 15px;
50 | font-size: 16px;
51 | background-color: #E74C3C;
52 | color: white;
53 | border: none;
54 | border-radius: 0 4px 4px 0;
55 | cursor: pointer;
56 | transition: background-color 0.3s ease;
57 | max-width: 30%;
58 | flex-grow: 0;
59 | }
60 |
61 | .search-button:hover {
62 | background-color: #f01800;
63 | }
64 |
65 | /* Navbar links styles */
66 | .navbar-link-custom {
67 | color: #000000;
68 | text-decoration: none !important;
69 | margin-right: 15px;
70 | }
71 |
72 | .navbar-link-custom:hover {
73 | color: #E74C3C;
74 | opacity: 0.8;
75 | }
76 |
77 | /* Logout button styles */
78 | .logina {
79 | border: 1px solid #2B3A42;
80 | background-color: #2B3A42;
81 | color: white !important;
82 | font-weight: bold;
83 | border-radius: 10px;
84 | }
85 |
86 | /* Responsive adjustments */
87 | @media (max-width: 991px) {
88 | .navbar-custom .container-fluid {
89 | padding: 0 15px;
90 | }
91 |
92 | .navbar-collapse {
93 | background-color: #ffffff;
94 | padding: 15px;
95 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
96 | }
97 |
98 | .navbar-nav {
99 | align-items: center;
100 | }
101 |
102 | .navbar-link-custom {
103 | margin: 5px 0;
104 | }
105 |
106 | .logina {
107 | margin-top: 10px;
108 | padding: 8px 15px !important;
109 | }
110 |
111 | .search-form {
112 | margin: 15px 0;
113 | max-width: 100%; /* Full width on small screens */
114 | }
115 |
116 | .search-input {
117 | width: 65%; /* Ensures proper width for input and button */
118 | }
119 |
120 | .search-button {
121 | width: 35%; /* Smaller button width to fit better */
122 | }
123 |
124 | .navbar-logo {
125 | height: 30px;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/Front/src/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../assets/css/footer.css';
3 | import { Container, Row, Col } from 'react-bootstrap';
4 | import { FaInstagram, FaTwitter, FaFacebook } from "react-icons/fa";
5 | import logo from '../assets/images/logowacopy.png'
6 |
7 | const Footer = () => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {[
20 | { href: "/privacy", text: "Privacy Policy" },
21 | { href: "/terms", text: "Terms of Service" },
22 | { href: "/aboutus", text: "About Us" },
23 | { href: "/contactus", text: "Contact Us" },
24 | ].map((link) => (
25 |
26 |
27 | {link.text}
28 |
29 |
30 | ))}
31 |
32 |
33 |
34 |
35 | {[
36 | { Icon: FaInstagram, href: "https://www.instagram.com/" },
37 | { Icon: FaFacebook, href: "https://www.facebook.com/" },
38 | { Icon: FaTwitter, href: "https://twitter.com/" },
39 | ].map(({ Icon, href }) => (
40 |
41 |
42 |
43 |
44 |
45 | ))}
46 |
47 |
48 |
49 |
50 |
51 | © {new Date().getFullYear()} Alaeddine & Abdennacer .All rights reserved.
52 |
53 |
54 |
55 |
56 | );
57 | };
58 |
59 | export default Footer;
--------------------------------------------------------------------------------
/Front/src/components/Login.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Form, Button, Alert, Container, Row, Col } from "react-bootstrap";
3 | import logowa from "../assets/images/logowa.png";
4 | import "../assets/css/Login.css";
5 |
6 | const Login = () => {
7 | const [inputUsername, setInputUsername] = useState("");
8 | const [inputPassword, setInputPassword] = useState("");
9 | const [show, setShow] = useState(false);
10 | const [loading, setLoading] = useState(false);
11 |
12 | const handleSubmit = async (event) => {
13 | event.preventDefault();
14 | setLoading(true);
15 | await new Promise((resolve) => setTimeout(resolve, 500));
16 | console.log(`Username: ${inputUsername}, Password: ${inputPassword}`);
17 | setLoading(false);
18 |
19 | // Example sign-in logic
20 | if (inputUsername !== "admin" || inputPassword !== "admin") {
21 | setShow(true);
22 | } else {
23 | setShow(false);
24 | // Redirect to dashboard or home page
25 | }
26 | };
27 |
28 | return (
29 |
30 |
31 |
32 |
33 |
34 |
Sign In
35 | {show && (
36 |
setShow(false)} dismissible>
37 | Incorrect username or password.
38 |
39 | )}
40 |
42 | Username
43 | setInputUsername(e.target.value)}
48 | required
49 | className="login-input"
50 | />
51 |
52 |
53 | Password
54 | setInputPassword(e.target.value)}
59 | required
60 | className="login-input"
61 | />
62 |
63 |
69 | {loading ? "Logging In..." : "Log In"}
70 |
71 |
72 |
73 |
74 |
75 |
76 | );
77 | };
78 |
79 | export default Login;
--------------------------------------------------------------------------------
/Front/src/assets/css/profile.css:
--------------------------------------------------------------------------------
1 | /* profile.css */
2 | .profile-section {
3 | background-color: #f8f9fa;
4 | min-height: 100vh;
5 | }
6 |
7 | .profile-image-container {
8 | width: 120px;
9 | height: 120px;
10 | border-radius: 50%;
11 | overflow: hidden;
12 | margin: 0 auto;
13 | background-color: #f8f9fa;
14 | display: flex;
15 | align-items: center;
16 | justify-content: center;
17 | }
18 |
19 | .profile-image {
20 | width: 100%;
21 | height: 100%;
22 | object-fit: cover;
23 | }
24 |
25 | .upload-overlay {
26 | position: absolute;
27 | bottom: 0;
28 | right: 0;
29 | }
30 |
31 | .upload-button {
32 | background-color: #fff;
33 | border-radius: 50%;
34 | padding: 8px;
35 | cursor: pointer;
36 | box-shadow: 0 2px 4px rgba(0,0,0,0.1);
37 | transition: all 0.3s ease;
38 | }
39 |
40 | .upload-button:hover {
41 | transform: translateY(-2px);
42 | box-shadow: 0 4px 8px rgba(0,0,0,0.2);
43 | }
44 |
45 | .profile-edit-btn {
46 | transition: all 0.3s ease;
47 | }
48 |
49 | .profile-edit-btn:hover {
50 | transform: translateY(-2px);
51 | box-shadow: 0 4px 8px rgba(0,0,0,0.1);
52 | }
53 |
54 | .profile-form .form-control {
55 | border-radius: 8px;
56 | border: 1px solid #dee2e6;
57 | padding: 12px;
58 | transition: all 0.3s ease;
59 | }
60 |
61 | .profile-form .form-control:focus {
62 | box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.25);
63 | border-color: #E74C3C;
64 | }
65 |
66 | .detail-item {
67 | display: flex;
68 | align-items: flex-start;
69 | gap: 1rem;
70 | padding: 1rem;
71 | border-radius: 8px;
72 | margin-bottom: 1rem;
73 | background-color: #f8f9fa;
74 | transition: all 0.3s ease;
75 | }
76 |
77 | .detail-item:hover {
78 | background-color: #f1f3f5;
79 | transform: translateX(4px);
80 | }
81 |
82 | .book-card {
83 | transition: all 0.3s ease;
84 | border-radius: 12px;
85 | overflow: hidden;
86 | }
87 |
88 | .book-card:hover {
89 | transform: translateY(-4px);
90 | box-shadow: 0 8px 16px rgba(0,0,0,0.1) !important;
91 | }
92 |
93 | .book-cover {
94 | height: 200px;
95 | object-fit: cover;
96 | }
97 |
98 | .delete-btn {
99 | position: absolute;
100 | top: 8px;
101 | right: 8px;
102 | border-radius: 50%;
103 | width: 32px;
104 | height: 32px;
105 | padding: 0;
106 | display: flex;
107 | align-items: center;
108 | justify-content: center;
109 | opacity: 0;
110 | transition: all 0.3s ease;
111 | }
112 |
113 | .book-card:hover .delete-btn {
114 | opacity: 1;
115 | }
116 |
117 | .view-details-btn {
118 | width: 100%;
119 | background-color: #E74C3C;
120 | border-color: #E74C3C;
121 | transition: all 0.3s ease;
122 | }
123 |
124 | .view-details-btn:hover {
125 | background-color: #c0392b;
126 | border-color: #c0392b;
127 | transform: translateY(-2px);
128 | box-shadow: 0 4px 8px rgba(231, 76, 60, 0.25);
129 | }
130 |
131 | .delete-modal .modal-content {
132 | border-radius: 16px;
133 | border: none;
134 | }
135 |
136 | .submit-btn {
137 | background-color: #E74C3C;
138 | border-color: #E74C3C;
139 | padding: 12px 24px;
140 | border-radius: 8px;
141 | transition: all 0.3s ease;
142 | }
143 |
144 | .submit-btn:hover {
145 | background-color: #c0392b;
146 | border-color: #c0392b;
147 | transform: translateY(-2px);
148 | box-shadow: 0 4px 8px rgba(231, 76, 60, 0.25);
149 | }
--------------------------------------------------------------------------------
/back/routes/auth.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const bcrypt = require('bcryptjs');
3 | const jwt = require('jsonwebtoken');
4 | const cloudinary = require('cloudinary').v2
5 | const router = express.Router();
6 | const {jwtMiddleware} = require('../middelware/jwt');
7 | const User = require('../models/User');
8 |
9 |
10 | // intergration with cloudinary
11 | cloudinary.config({
12 | cloud_name: 'djux9krem',
13 | api_key: '639144162891629',
14 | api_secret: 'cqldqET6lDIs4iM9WAkf5DV4Adg'
15 | });
16 |
17 | router.get('/signature', (req, res) => {
18 | const timestamp = Math.round((new Date).getTime()/1000);
19 | const signature = cloudinary.utils.api_sign_request({
20 | timestamp: timestamp,
21 | folder: 'user_uploads'
22 | }, cloudinary.config().api_secret);
23 | res.json({ timestamp, signature });
24 | });
25 |
26 | // sign up
27 | router.post('/signup', async (req, res) => {
28 | const { username, email, password } = req.body;
29 |
30 | try {
31 | const existingUser = await User.findOne({ email });
32 | if (existingUser) {
33 | return res.status(400).json({ message: 'User already exists' });
34 | }
35 |
36 | const hashedPassword = await bcrypt.hash(password, 10);
37 | const newUser = new User({ username:username, email:email, password: hashedPassword });
38 | await newUser.save();
39 |
40 | res.status(201).json({ message: 'User created successfully' });
41 | } catch (error) {
42 | res.status(500).json({ message: 'Server error' });
43 | }
44 | });
45 |
46 | // sign in
47 | router.post('/signin', async (req, res) => {
48 | const { email, password } = req.body;
49 |
50 | try {
51 | const user = await User.findOne({ email });
52 |
53 | if (!user) {
54 | return res.status(400).json({ message: 'Invalid credentials' });
55 | }
56 |
57 | const isMatch = await bcrypt.compare(password, user.password);
58 | if (!isMatch) {
59 | return res.status(400).json({ message: 'Invalid credentials' });
60 | }
61 |
62 | const token = jwt.sign(
63 | { id: user._id, role: user.role },
64 | process.env.JWT_SECRET,
65 | { expiresIn: '1w' }
66 | );
67 |
68 | res.cookie('token', token, { httpOnly: true });
69 |
70 | res.status(200).json({
71 | message: `${user.role === 'admin' ? 'Admin' : 'User'} signed in successfully`,
72 | token: token,
73 | role: user.role
74 | });
75 | } catch (error) {
76 | console.error('Signin error:', error);
77 | res.status(500).json({ message: 'Server error', error: error.message });
78 | }
79 | });
80 |
81 | // getting the profile
82 | router.get('/profile', jwtMiddleware, async (req, res) => {
83 | try {
84 | const user = await User.findById(req.user.id).select('-password');
85 | if (!user) {
86 | return res.status(404).json({ msg: 'User not found' });
87 | }
88 | res.json(user);
89 | } catch (err) {
90 | console.error('Profile fetch error:', err);
91 | res.status(500).json({ message: 'Server Error', error: err.message });
92 | }
93 | });
94 |
95 | // editing the profile
96 | router.put('/profile', jwtMiddleware, async (req, res) => {
97 | const { username, phone, address, country, profileImage } = req.body;
98 |
99 | try {
100 | let user = await User.findById(req.user.id);
101 | if (!user) {
102 | return res.status(404).json({ msg: 'User not found' });
103 | }
104 |
105 | user.username = username || user.username;
106 | user.phone = phone || user.phone;
107 | user.address = address || user.address;
108 | user.country = country || user.country;
109 | user.profileImage = profileImage || user.profileImage;
110 |
111 | await user.save();
112 | res.json(user);
113 | } catch (err) {
114 | console.error(err.message);
115 | res.status(500).send('Server Error');
116 | }
117 | });
118 |
119 | // logging out
120 | router.get('/logout', (req, res) => {
121 | res.clearCookie('token');
122 | res.status(200).json({ message: 'Logged out successfully' });
123 | });
124 |
125 | // checks if u are authenticated
126 | router.post('/check_authenticateToken', jwtMiddleware, async (req, res) => {
127 | try {
128 | const user = await User.findById(req.user.id).select('-password');
129 | res.status(200).json({ user });
130 | } catch (error) {
131 | res.status(500).json({ message: 'Server error' });
132 | }
133 | });
134 |
135 | module.exports = router;
136 |
--------------------------------------------------------------------------------
/Front/README.md:
--------------------------------------------------------------------------------
1 | ```markdown
2 | # 📚 EBook - Online Book Store and Community
3 |
4 | [Technologies Getting Started](#technologies-getting-started)
5 |
6 | A comprehensive web application for book enthusiasts to discover, read, delete and and share their favorite books while building a vibrant reading community online.
7 |
8 | ## 👥 Authors
9 | - Hafsa [ (Login/Sign Up Pages never been delivered) ]
10 | - Yasser Assou [ Footer + About Us + Book page ]
11 | - Youssef El Melh( Add Book page / (Contact us page not delivered yet))
12 | - Abdennacer Kaddouri
13 |
14 | ## 🎨 Layout
15 | 
16 |
17 | ## 🖥️ Technologies Getting Started
18 |
19 | ### Technologies Used
20 | - React.js
21 | - React Router
22 | - React Bootstrap
23 | - Bootstrap
24 | - CSS3
25 | - React Icons
26 |
27 | ## 🚀 Getting Started
28 |
29 | ### Prerequisites
30 | - Node.js (v14.0.0 or later)
31 | - npm (v6.0.0 or later)
32 |
33 | ### Installation
34 | 1. Clone the repo:
35 | ```bash
36 | git clone https://github.com/nacceree/lib-team.git
37 | ```
38 | 2. Navigate to the project directory:
39 | ```bash
40 | cd lib-team
41 | ```
42 | 3. Install dependencies:
43 | ```bash
44 | npm install
45 | ```
46 | 4. Start the development server:
47 | ```bash
48 | npm start
49 | ```
50 | 5. Visit http://localhost:3000 in your browser.
51 |
52 | ## 📦 Features
53 | - User authentication (Login functionality) // not added yet
54 | - Add new books to the platform // not added yet
55 | - Responsive design for seamless mobile experience
56 | - Interactive navigation bar with search functionality
57 | - Footer with social media integration and quick links
58 | - Browes books based on their categories
59 |
60 | ## 🏗️ Project Structure
61 | ```
62 | Lib-team/
63 | ├── public/
64 | ├── src/
65 | │ ├── assets/
66 | │ │ ├── css/
67 | │ │ │ ├── navbar.css
68 | │ │ │ ├── footer.css
69 | │ │ │ ├── index.css
70 | │ │ │ ├── login.css
71 | │ │ │ ├── bookcard.css
72 | │ │ └── images/
73 | │ │ ├── logowa.png
74 | │ │ └── logowacopy.png
75 | │ ├── components/
76 | │ │ ├── Aboutus.jsx
77 | │ │ ├── addbook.jsx
78 | │ │ ├── bookcard.jsx
79 | │ │ ├── bookspage.jsx
80 | │ │ ├── categories.jsx
81 | │ │ ├── category.jsx
82 | │ │ ├── footer.jsx
83 | │ │ ├── navbar.jsx
84 | │ │ ├── login.jsx
85 | │ │ ├── onebook.jsx
86 | │ │ ├── searchresults.jsx
87 | │ │ ├── jsx
88 | │ ├── App.jsx
89 | │ └── index.jsx
90 | ├── package.json
91 | └── README.md
92 | ```
93 |
94 | ## 🧩 Components
95 |
96 | ## 🧩 Components
97 |
98 | ### Aboutus
99 | - Provides information about the library team and its mission
100 | - Engaging layout with team member profiles and descriptions
101 |
102 | ### AddBook
103 | - Form for adding new books to the library collection
104 | - Input fields for book image, name, author, and description
105 | - Validation for required fields
106 |
107 | ### BookCard
108 | - Displays individual book details in a card format
109 | - Includes book image, name, author, and a short description
110 | - Custom styling for a polished look
111 |
112 | ### BooksPage
113 | - Lists all books available in the library
114 | - Integrates the `BookCard` component for each book
115 | - Pagination for easy navigation through the book collection
116 |
117 | ### Categories
118 | - Displays different book categories available in the library
119 | - Links to filtered views of books by category
120 |
121 | ### Category
122 | - Shows books belonging to a selected category
123 | - Integrates the `BookCard` component for each book in the category
124 |
125 | ### Footer
126 | - Comprehensive footer with quick links to essential pages
127 | - Social media integration (Instagram, Facebook, Twitter)
128 | - Consistent branding with logo display
129 |
130 | ### CustomNavbar
131 | - Sleek navigation bar with integrated search functionality
132 | - Dynamic links for "Add Book" and "Login" pages
133 | - Responsive design for various screen sizes
134 |
135 | ### Login
136 | - User login form with fields for email and password
137 | - Validation for required fields
138 | - Redirects to the homepage upon successful login
139 |
140 | ### OneBook
141 | - Detailed view of a single book's information
142 | - Displays book image, name, author, full description, and additional details
143 |
144 | ### SearchResults
145 | - Displays search results based on user queries
146 | - Integrates the `BookCard` component for each result
147 |
148 | ## 🎨 Styling
149 |
150 | The project utilizes a combination of React Bootstrap and custom CSS for a polished look:
151 |
152 | - `navbar.css`: Custom styles for the navigation component
153 | - `footer.css`: Dedicated styles for the footer component
154 | - `index.css`: General styles for the entire application
155 | - `login.css`: Styles for the login form
156 | - `bookcard.css`: Custom styles for the book cards
157 |
158 | Color Palette:
159 | - Primary Background: #2B3A42
160 | - Accent Color: #BF4F36
161 | - Call-to-Action: #E74C3C
162 |
163 | ## 📚 Documentation
164 | - [React Documentation](https://reactjs.org/docs/getting-started.html)
165 | - [React Bootstrap Documentation](https://react-bootstrap.github.io/)
166 | - [React Router Documentation](https://reactrouter.com/web/guides/quick-start)
167 |
--------------------------------------------------------------------------------
/Front/src/components/AddBook.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from "react";
2 | import { Container, Form, Button } from "react-bootstrap";
3 | import '../assets/css/addbook.css';
4 | import axios from 'axios';
5 |
6 | const categoryNames = [
7 | "Adventure", "Romance", "Thriller", "Memoir",
8 | "Travel", "Health", "Poetry", "Cooking"
9 | ];
10 |
11 | const AddBook = () => {
12 | const [bookImage, setBookImage] = useState(null);
13 | const [bookName, setBookName] = useState("");
14 | const [bookAuthor, setBookAuthor] = useState("");
15 | const [bookDescription, setBookDescription] = useState("");
16 | const [category, setCategory] = useState("");
17 | const fileInputRef = useRef(null); // Create a ref for the file input
18 | const [bookPdf, setBookPdf] = useState(null);
19 |
20 |
21 | const handleImageUpload = async (e) => {
22 | const file = e.target.files[0];
23 | const formData = new FormData();
24 | formData.append('file', file);
25 | formData.append('upload_preset', 'y1f5bwss');
26 | try {
27 | const res = await axios.post('https://api.cloudinary.com/v1_1/djux9krem/image/upload', formData);
28 | setBookImage(res.data.secure_url);
29 | } catch (err) {
30 | console.error('Error uploading image: ', err);
31 | }
32 | };
33 |
34 | const handlePdfUpload = async (e) => {
35 | const file = e.target.files[0];
36 | const formData = new FormData();
37 | formData.append('file', file);
38 | formData.append('upload_preset', 'pdf_uploads');
39 | try {
40 | const res = await axios.post('https://api.cloudinary.com/v1_1/djux9krem/raw/upload', formData);
41 | setBookPdf(res.data.secure_url);
42 | } catch (err) {
43 | console.error('Error uploading PDF: ', err);
44 | }
45 | };
46 |
47 | const handleSubmit = async (event) => {
48 | event.preventDefault();
49 |
50 | try {
51 | const newBook = {
52 | title: bookName,
53 | author: bookAuthor,
54 | description: bookDescription,
55 | cover: bookImage,
56 | category: category,
57 | pdfUrl: bookPdf
58 | };
59 | const response = await fetch(`http://localhost:5000/api/addbook`, {
60 | method: "POST",
61 | headers: {
62 | "Content-Type": "application/json",
63 | },
64 | body: JSON.stringify(newBook),
65 | credentials: "include" // This will include credentials in the request
66 | });
67 |
68 | if (!response.ok) {
69 | throw new Error("Failed to add book");
70 | }
71 |
72 | // Clear form after successful submission
73 | setBookImage(null);
74 | setBookName("");
75 | setBookAuthor("");
76 | setBookDescription("");
77 | setCategory("");
78 | fileInputRef.current.value = null; // Clear the file input
79 |
80 | alert("Book added successfully!");
81 | } catch (error) {
82 | console.error("Error adding book:", error);
83 | alert("Failed to add book. Please try again.");
84 | }
85 | };
86 |
87 |
88 | return (
89 |
90 |
93 | Book Cover
94 |
100 |
101 | {bookImage && (
102 |
103 | )}
104 |
105 | Book Name
106 | setBookName(e.target.value)}
111 | required
112 | />
113 |
114 |
115 | Book PDF
116 |
121 |
122 |
123 | Book Author
124 | setBookAuthor(e.target.value)}
129 | required
130 | />
131 |
132 |
133 | Book Description
134 | setBookDescription(e.target.value)}
140 | required
141 | />
142 |
143 |
144 | Book Category
145 | setCategory(e.target.value)}
148 | required
149 | >
150 | Select a category
151 | {categoryNames.map((cat) => (
152 | {cat}
153 | ))}
154 |
155 |
156 |
157 | Add Book
158 |
159 |
160 |
161 | );
162 | };
163 |
164 | export default AddBook;
165 |
--------------------------------------------------------------------------------
/Front/src/components/OneBook.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Container, Row, Col, Button, Card, Modal } from 'react-bootstrap';
3 | import { useParams } from 'react-router-dom';
4 | import { Book, Heart, X } from 'lucide-react'; // Import icons if you have lucide-react
5 | import '../assets/css/bookbtn.css';
6 |
7 | const OneBook = () => {
8 | const openPdf = () => {
9 | if (book.pdfUrl) {
10 | window.open(book.pdfUrl, '_blank');
11 | } else {
12 | alert('PDF not available for this book.');
13 | }
14 | handleCloseModal();
15 | };
16 |
17 | const { category, bookId } = useParams();
18 | const [isImageLarge, setIsImageLarge] = useState(false);
19 | const [book, setBook] = useState(null);
20 | const [loading, setLoading] = useState(true);
21 | const [error, setError] = useState(null);
22 | const [showModal, setShowModal] = useState(false);
23 |
24 | const addToFavorites = () => {
25 | const favorites = JSON.parse(localStorage.getItem('favorites')) || [];
26 | if (!favorites.some(fav => fav._id === book._id)) {
27 | favorites.push(book);
28 | localStorage.setItem('favorites', JSON.stringify(favorites));
29 | alert('Book added to favorites!');
30 | } else {
31 | alert('This book is already in your favorites!');
32 | }
33 | };
34 |
35 | useEffect(() => {
36 | const fetchBook = async () => {
37 | try {
38 | const response = await fetch(`http://localhost:5000/api/${category}/${bookId}`);
39 | if (!response.ok) {
40 | throw new Error(`HTTP error! status: ${response.status}`);
41 | }
42 | const data = await response.json();
43 | setBook(data);
44 | } catch (error) {
45 | console.error("Could not fetch book:", error);
46 | setError('Failed to load book. Please try again later.');
47 | } finally {
48 | setLoading(false);
49 | }
50 | };
51 |
52 | fetchBook();
53 | }, [category, bookId]);
54 |
55 | const handleImageClick = () => {
56 | setIsImageLarge(!isImageLarge);
57 | };
58 |
59 | const handleShowModal = () => setShowModal(true);
60 | const handleCloseModal = () => setShowModal(false);
61 |
62 |
63 | if (loading) {
64 | return Loading...
;
65 | }
66 |
67 | if (error) {
68 | return {error}
;
69 | }
70 |
71 | if (!book) {
72 | return Book not found
;
73 | }
74 |
75 | return (
76 |
77 |
78 | {/* Left Column - Book Image and Buttons */}
79 |
80 |
81 |
86 |
98 |
99 | Click to zoom
100 |
101 |
102 |
103 |
104 | {
113 | e.currentTarget.style.backgroundColor = '#27ae60';
114 | e.currentTarget.style.transform = 'translateY(-2px)';
115 | }}
116 | onMouseOut={(e) => {
117 | e.currentTarget.style.backgroundColor = '#2ecc71';
118 | e.currentTarget.style.transform = 'translateY(0)';
119 | }}
120 | >
121 | Read This Book
122 |
123 | {
132 | e.currentTarget.style.backgroundColor = '#c0392b';
133 | e.currentTarget.style.transform = 'translateY(-2px)';
134 | }}
135 | onMouseOut={(e) => {
136 | e.currentTarget.style.backgroundColor = '#e74c3c';
137 | e.currentTarget.style.transform = 'translateY(0)';
138 | }}
139 | >
140 | Add To Favorite
141 |
142 |
143 |
144 |
145 | {/* Right Column - Book Details */}
146 |
147 |
148 |
{book.title}
149 |
by {book.author}
150 |
151 |
Description:
152 |
{book.description}
153 |
154 |
155 |
156 | Category: {category}
157 | {/* Add more badges for additional book metadata if available */}
158 |
159 |
160 |
161 |
162 |
163 |
164 | {/* Enhanced Modal */}
165 |
171 |
172 | Open The Book
173 |
178 |
179 |
180 |
181 |
182 |
183 | Click Yes To View "{book.title}" Content
184 |
185 |
186 |
191 | Cancel
192 |
193 |
198 | YES
199 |
200 |
201 |
202 |
203 | {/* Enhanced Image Overlay */}
204 | {isImageLarge && (
205 |
222 |
226 |
227 |
228 |
238 |
239 | )}
240 |
241 | );
242 | };
243 |
244 | export default OneBook;
--------------------------------------------------------------------------------
/Front/src/components/Sign/Sign.module.css:
--------------------------------------------------------------------------------
1 | /* Sign.module.css */
2 |
3 | /* Existing global styles and imports */
4 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700;800&display=swap");
5 |
6 | .container {
7 | position: relative;
8 | width: 100%;
9 | background-color: #fff;
10 | min-height: 100vh;
11 | overflow: hidden;
12 | }
13 |
14 | .forms-container {
15 | position: absolute;
16 | width: 100%;
17 | height: 100%;
18 | top: 0;
19 | left: 0;
20 | }
21 |
22 | .signin-signup {
23 | position: absolute;
24 | top: 50%;
25 | transform: translate(-50%, -50%);
26 | left: 75%;
27 | width: 50%;
28 | transition: 1s 0.7s ease-in-out;
29 | display: grid;
30 | grid-template-columns: 1fr;
31 | z-index: 5;
32 | }
33 |
34 | /* Remove the generic form styling and use specific selectors */
35 | form.sign-in-form,
36 | form.sign-up-form {
37 | display: flex;
38 | align-items: center;
39 | justify-content: center;
40 | flex-direction: column;
41 | padding: 0rem 5rem;
42 | transition: all 0.2s 0.7s;
43 | overflow: hidden;
44 | grid-column: 1 / 2;
45 | grid-row: 1 / 2;
46 | }
47 |
48 | form.sign-up-form {
49 | opacity: 0;
50 | z-index: 1;
51 | }
52 |
53 | form.sign-in-form {
54 | z-index: 2;
55 | }
56 |
57 | .title {
58 | font-size: 2.2rem;
59 | color: #444;
60 | margin-bottom: 10px;
61 | }
62 |
63 | .input-field {
64 | max-width: 380px;
65 | width: 100%;
66 | background-color: #f0f0f0;
67 | margin: 10px 0;
68 | height: 55px;
69 | border-radius: 55px;
70 | display: grid;
71 | grid-template-columns: 15% 85%;
72 | padding: 0 0.4rem;
73 | position: relative;
74 | }
75 |
76 | .input-field i {
77 | text-align: center;
78 | line-height: 55px;
79 | color: #000000;
80 | transition: 0.5s;
81 | font-size: 1.4rem;
82 | }
83 |
84 | .input-field input {
85 | background: none;
86 | outline: none;
87 | border: none;
88 | line-height: 1;
89 | font-weight: 600;
90 | font-size: 1.1rem;
91 | color: #333;
92 | }
93 | .ccc{
94 | margin-left: 20px;
95 | margin-top: 15px;
96 | }
97 |
98 | .input-field input:focus {
99 | border-color: #000000;
100 | box-shadow: 0 0 0px rgba(0, 0, 0, 0.5);
101 | }
102 |
103 | .input-field input::placeholder {
104 | color: #aaa;
105 | font-weight: 500;
106 | }
107 |
108 | .social-text {
109 | padding: 0.7rem 0;
110 | font-size: 1rem;
111 | }
112 |
113 | .social-media {
114 | display: flex;
115 | justify-content: center;
116 | }
117 |
118 | .social-icon {
119 | height: 46px;
120 | width: 46px;
121 | display: flex;
122 | justify-content: center;
123 | align-items: center;
124 | margin: 0 0.45rem;
125 | color: #333;
126 | border-radius: 50%;
127 | border: 1px solid #333;
128 | text-decoration: none;
129 | font-size: 1.1rem;
130 | transition: 0.3s;
131 | }
132 |
133 | .social-icon:hover {
134 | color: #4481eb;
135 | border-color: #4481eb;
136 | }
137 |
138 | .message {
139 | margin-bottom: 1rem;
140 | padding: 0.5rem;
141 | border-radius: 4px;
142 | color: #E74C3C;
143 | text-align: center;
144 | }
145 |
146 | .btn {
147 | width: 150px;
148 | background-color: #2B3A42;
149 | border: none;
150 | outline: none;
151 | height: 49px;
152 | border-radius: 49px;
153 | color: #fff;
154 | text-transform: uppercase;
155 | font-weight: 600;
156 | margin: 10px 0;
157 | cursor: pointer;
158 | transition: 0.5s;
159 | }
160 |
161 | .btn:hover {
162 | background-color: #242f35;
163 | }
164 |
165 | .panels-container {
166 | position: absolute;
167 | height: 100%;
168 | width: 100%;
169 | top: 0;
170 | left: 0;
171 | display: grid;
172 | grid-template-columns: repeat(2, 1fr);
173 | }
174 |
175 | .container:before {
176 | content: "";
177 | position: absolute;
178 | height: 2000px;
179 | width: 2000px;
180 | top: -10%;
181 | right: 48%;
182 | transform: translateY(-50%);
183 | background: linear-gradient(90deg, #E74C3C 30%, #E74C3C 74%);
184 | transition: 1.8s ease-in-out;
185 | border-radius: 50%;
186 | z-index: 6;
187 | }
188 |
189 | .image {
190 | width: 100%;
191 | transition: transform 1.1s ease-in-out;
192 | transition-delay: 0.4s;
193 | }
194 |
195 | .panel {
196 | display: flex;
197 | flex-direction: column;
198 | align-items: flex-end;
199 | justify-content: space-around;
200 | text-align: center;
201 | z-index: 6;
202 | }
203 |
204 | .left-panel {
205 | pointer-events: all;
206 | padding: 3rem 17% 2rem 12%;
207 | }
208 |
209 | .right-panel {
210 | pointer-events: none;
211 | padding: 3rem 12% 2rem 17%;
212 | }
213 |
214 | .panel .content {
215 | color: #fff;
216 | transition: transform 0.9s ease-in-out;
217 | transition-delay: 0.6s;
218 | }
219 |
220 | .panel h3 {
221 | font-weight: 600;
222 | line-height: 1;
223 | font-size: 1.5rem;
224 | }
225 |
226 | .panel p {
227 | font-size: 0.95rem;
228 | padding: 0.7rem 0;
229 | }
230 |
231 | .btn.transparent {
232 | margin: 0;
233 | background: none;
234 | border: 2px solid #242f35;
235 | width: 130px;
236 | height: 41px;
237 | font-weight: 600;
238 | font-size: 0.8rem;
239 | background-color: #242f35;
240 | cursor: pointer;
241 | }
242 |
243 | .right-panel .image,
244 | .right-panel .content {
245 | transform: translateX(800px);
246 | }
247 |
248 | /* ANIMATION */
249 |
250 | .container.sign-up-mode:before {
251 | transform: translate(100%, -50%);
252 | right: 52%;
253 | }
254 |
255 | .container.sign-up-mode .left-panel .image,
256 | .container.sign-up-mode .left-panel .content {
257 | transform: translateX(-800px);
258 | }
259 |
260 | .container.sign-up-mode .signin-signup {
261 | left: 25%;
262 | }
263 |
264 | .container.sign-up-mode form.sign-up-form {
265 | opacity: 1;
266 | z-index: 2;
267 | }
268 |
269 | .container.sign-up-mode form.sign-in-form {
270 | opacity: 0;
271 | z-index: 1;
272 | }
273 |
274 | .container.sign-up-mode .right-panel .image,
275 | .container.sign-up-mode .right-panel .content {
276 | transform: translateX(0%);
277 | }
278 |
279 | .container.sign-up-mode .left-panel {
280 | pointer-events: none;
281 | }
282 |
283 | .container.sign-up-mode .right-panel {
284 | pointer-events: all;
285 | }
286 |
287 | @media (max-width: 870px) {
288 | .container {
289 | min-height: 800px;
290 | height: 100vh;
291 | }
292 | .signin-signup {
293 | width: 100%;
294 | top: 95%;
295 | transform: translate(-50%, -100%);
296 | transition: 1s 0.8s ease-in-out;
297 | }
298 |
299 | .signin-signup,
300 | .container.sign-up-mode .signin-signup {
301 | left: 50%;
302 | }
303 |
304 | .panels-container {
305 | grid-template-columns: 1fr;
306 | grid-template-rows: 1fr 2fr 1fr;
307 | }
308 |
309 | .panel {
310 | flex-direction: row;
311 | justify-content: space-around;
312 | align-items: center;
313 | padding: 2.5rem 8%;
314 | grid-column: 1 / 2;
315 | }
316 |
317 | .right-panel {
318 | grid-row: 3 / 4;
319 | }
320 |
321 | .left-panel {
322 | grid-row: 1 / 2;
323 | }
324 |
325 | .image {
326 | width: 200px;
327 | transition: transform 0.9s ease-in-out;
328 | transition-delay: 0.6s;
329 | }
330 |
331 | .panel .content {
332 | padding-right: 15%;
333 | transition: transform 0.9s ease-in-out;
334 | transition-delay: 0.8s;
335 | }
336 |
337 | .panel h3 {
338 | font-size: 1.2rem;
339 | }
340 |
341 | .panel p {
342 | font-size: 0.7rem;
343 | padding: 0.5rem 0;
344 | }
345 |
346 | .btn.transparent {
347 | width: 110px;
348 | height: 35px;
349 | font-size: 0.7rem;
350 | }
351 |
352 | .container:before {
353 | width: 1500px;
354 | height: 1500px;
355 | transform: translateX(-50%);
356 | left: 30%;
357 | bottom: 68%;
358 | right: initial;
359 | top: initial;
360 | transition: 2s ease-in-out;
361 | }
362 |
363 | .container.sign-up-mode:before {
364 | transform: translate(-50%, 100%);
365 | bottom: 32%;
366 | right: initial;
367 | }
368 |
369 | .container.sign-up-mode .left-panel .image,
370 | .container.sign-up-mode .left-panel .content {
371 | transform: translateY(-300px);
372 | }
373 |
374 | .right-panel .image,
375 | .right-panel .content {
376 | transform: translateY(300px);
377 | }
378 |
379 | .container.sign-up-mode .right-panel .image,
380 | .container.sign-up-mode .right-panel .content {
381 | transform: translateY(0px);
382 | }
383 |
384 | .container.sign-up-mode .signin-signup {
385 | top: 5%;
386 | transform: translate(-50%, 0);
387 | }
388 | }
389 |
390 | @media (max-width: 570px) {
391 | form.sign-in-form,
392 | form.sign-up-form {
393 | padding: 0 1.5rem;
394 | }
395 |
396 | .image {
397 | display: none;
398 | }
399 | .panel .content {
400 | padding: 0.5rem 1rem;
401 | }
402 | .container {
403 | padding: 1.5rem;
404 | }
405 |
406 | .container:before {
407 | bottom: 72%;
408 | left: 50%;
409 | }
410 |
411 | .container.sign-up-mode:before {
412 | bottom: 28%;
413 | left: 50%;
414 | }
415 | }
--------------------------------------------------------------------------------
/Front/src/components/Profile.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Container, Row, Col, Card, Button, Form, Modal } from 'react-bootstrap';
3 | import axios from 'axios';
4 |
5 | const Profile = () => {
6 | const [profileData, setProfileData] = useState(null);
7 | const [userBooks, setUserBooks] = useState([]);
8 | const [loading, setLoading] = useState(true);
9 | const [error, setError] = useState(null);
10 | const [isEditing, setIsEditing] = useState(false);
11 | const [showDeleteModal, setShowDeleteModal] = useState(false);
12 | const [bookToDelete, setBookToDelete] = useState(null);
13 | const [formData, setFormData] = useState({
14 | username: '',
15 | phone: '',
16 | address: '',
17 | country: '',
18 | profileImage: ''
19 | });
20 |
21 | useEffect(() => {
22 | const fetchData = async () => {
23 | try {
24 | const profileResponse = await axios.get('http://localhost:5000/api/auth/profile', {
25 | withCredentials: true
26 | });
27 | setProfileData(profileResponse.data);
28 | setFormData({
29 | username: profileResponse.data.username,
30 | phone: profileResponse.data.phone,
31 | address: profileResponse.data.address,
32 | country: profileResponse.data.country,
33 | profileImage: profileResponse.data.profileImage || ''
34 | });
35 |
36 | const booksResponse = await axios.get('http://localhost:5000/api/user-books', {
37 | withCredentials: true
38 | });
39 | setUserBooks(booksResponse.data);
40 |
41 | setLoading(false);
42 | } catch (err) {
43 | setError('Failed to fetch profile data or user books');
44 | setLoading(false);
45 | }
46 | };
47 |
48 | fetchData();
49 | }, []);
50 |
51 | const handleChange = (e) => {
52 | setFormData({ ...formData, [e.target.name]: e.target.value });
53 | };
54 |
55 | const handleSubmit = async (e) => {
56 | e.preventDefault();
57 | try {
58 | const response = await axios.put('http://localhost:5000/api/auth/profile', formData, {
59 | withCredentials: true
60 | });
61 | setProfileData(response.data);
62 | setIsEditing(false);
63 | } catch (err) {
64 | setError('Failed to update profile');
65 | }
66 | };
67 |
68 | const handleImageUpload = async (e) => {
69 | const file = e.target.files[0];
70 | const formData = new FormData();
71 | formData.append('file', file);
72 | formData.append('upload_preset', 'y1f5bwss');
73 |
74 | try {
75 | const res = await axios.post('https://api.cloudinary.com/v1_1/djux9krem/image/upload', formData);
76 | setFormData(prevState => ({ ...prevState, profileImage: res.data.secure_url }));
77 | } catch (err) {
78 | console.error('Error uploading image: ', err);
79 | }
80 | };
81 |
82 | const handleDeleteClick = (book) => {
83 | setBookToDelete(book);
84 | setShowDeleteModal(true);
85 | };
86 |
87 | const handleDeleteConfirm = async () => {
88 | try {
89 | await axios.delete(`http://localhost:5000/api/books/${bookToDelete._id}`, {
90 | withCredentials: true
91 | });
92 | setUserBooks(userBooks.filter(book => book._id !== bookToDelete._id));
93 | setShowDeleteModal(false);
94 | } catch (err) {
95 | console.error('Error deleting book:', err);
96 | }
97 | };
98 |
99 | if (loading) return Loading...
;
100 | if (error) return {error}
;
101 | if (!profileData) return No profile data available
;
102 |
103 | return (
104 |
105 |
106 |
107 |
108 |
109 |
110 | {profileData.profileImage ? (
111 |
113 | ) : (
114 |
116 | )}
117 | {profileData.username}
118 | User
119 | {profileData.country}
120 |
121 | setIsEditing(!isEditing)}
125 | >
126 | {isEditing ? 'Cancel Edit' : 'Edit Profile'}
127 |
128 |
129 |
130 |
131 |
132 |
133 | {isEditing ? (
134 |
135 |
136 |
138 | Profile Image
139 |
140 |
144 |
145 |
146 |
147 | Username
148 |
149 |
155 |
156 |
157 |
158 | Phone
159 |
160 |
166 |
167 |
168 |
169 | Address
170 |
171 |
177 |
178 |
179 |
180 | Country
181 |
182 |
188 |
189 |
190 | Save Changes
191 |
192 |
193 |
194 | ) : (
195 |
196 |
197 |
198 |
199 | Username
200 |
201 |
202 | {profileData.username}
203 |
204 |
205 |
206 |
207 |
208 | Email
209 |
210 |
211 | {profileData.email}
212 |
213 |
214 |
215 |
216 |
217 | Phone
218 |
219 |
220 | {profileData.phone}
221 |
222 |
223 |
224 |
225 |
226 | Address
227 |
228 |
229 | {profileData.address}
230 |
231 |
232 |
233 |
234 |
235 | Country
236 |
237 |
238 | {profileData.country}
239 |
240 |
241 |
242 |
243 | )}
244 |
245 |
246 |
247 |
248 | Your Uploaded Books
249 | {userBooks.length === 0 ? (
250 | You haven't uploaded any books yet.
251 | ) : (
252 |
253 | {userBooks.map((book) => (
254 |
255 |
256 |
257 |
258 | handleDeleteClick(book)}
263 | >
264 | X
265 |
266 |
267 |
268 | {book.title}
269 | {book.author}
270 |
275 | View Details
276 |
277 |
278 |
279 |
280 | ))}
281 |
282 | )}
283 |
284 |
285 |
286 |
287 | setShowDeleteModal(false)}>
288 |
289 | Confirm Deletion
290 |
291 |
292 | Are you sure you want to delete "{bookToDelete?.title}"?
293 |
294 |
295 | setShowDeleteModal(false)}>
296 | No
297 |
298 |
299 | Yes
300 |
301 |
302 |
303 |
304 | );
305 | };
306 |
307 | export default Profile;
--------------------------------------------------------------------------------
/back/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "back",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "back",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "bcryptjs": "^2.4.3",
13 | "body-parser": "^1.20.2",
14 | "cloudinary": "^2.3.0",
15 | "cookie-parser": "^1.4.6",
16 | "cors": "^2.8.5",
17 | "dotenv": "^16.4.5",
18 | "express": "^4.19.2",
19 | "jsonwebtoken": "^9.0.2",
20 | "mongoose": "^8.5.1",
21 | "nodemon": "^3.1.4"
22 | }
23 | },
24 | "node_modules/@mongodb-js/saslprep": {
25 | "version": "1.1.8",
26 | "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.8.tgz",
27 | "integrity": "sha512-qKwC/M/nNNaKUBMQ0nuzm47b7ZYWQHN3pcXq4IIcoSBc2hOIrflAxJduIvvqmhoz3gR2TacTAs8vlsCVPkiEdQ==",
28 | "license": "MIT",
29 | "dependencies": {
30 | "sparse-bitfield": "^3.0.3"
31 | }
32 | },
33 | "node_modules/@types/webidl-conversions": {
34 | "version": "7.0.3",
35 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
36 | "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
37 | "license": "MIT"
38 | },
39 | "node_modules/@types/whatwg-url": {
40 | "version": "11.0.5",
41 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
42 | "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
43 | "license": "MIT",
44 | "dependencies": {
45 | "@types/webidl-conversions": "*"
46 | }
47 | },
48 | "node_modules/accepts": {
49 | "version": "1.3.8",
50 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
51 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
52 | "license": "MIT",
53 | "dependencies": {
54 | "mime-types": "~2.1.34",
55 | "negotiator": "0.6.3"
56 | },
57 | "engines": {
58 | "node": ">= 0.6"
59 | }
60 | },
61 | "node_modules/anymatch": {
62 | "version": "3.1.3",
63 | "license": "ISC",
64 | "dependencies": {
65 | "normalize-path": "^3.0.0",
66 | "picomatch": "^2.0.4"
67 | },
68 | "engines": {
69 | "node": ">= 8"
70 | }
71 | },
72 | "node_modules/array-flatten": {
73 | "version": "1.1.1",
74 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
75 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
76 | "license": "MIT"
77 | },
78 | "node_modules/balanced-match": {
79 | "version": "1.0.2",
80 | "license": "MIT"
81 | },
82 | "node_modules/bcryptjs": {
83 | "version": "2.4.3",
84 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
85 | "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==",
86 | "license": "MIT"
87 | },
88 | "node_modules/binary-extensions": {
89 | "version": "2.3.0",
90 | "license": "MIT",
91 | "engines": {
92 | "node": ">=8"
93 | },
94 | "funding": {
95 | "url": "https://github.com/sponsors/sindresorhus"
96 | }
97 | },
98 | "node_modules/body-parser": {
99 | "version": "1.20.2",
100 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
101 | "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
102 | "license": "MIT",
103 | "dependencies": {
104 | "bytes": "3.1.2",
105 | "content-type": "~1.0.5",
106 | "debug": "2.6.9",
107 | "depd": "2.0.0",
108 | "destroy": "1.2.0",
109 | "http-errors": "2.0.0",
110 | "iconv-lite": "0.4.24",
111 | "on-finished": "2.4.1",
112 | "qs": "6.11.0",
113 | "raw-body": "2.5.2",
114 | "type-is": "~1.6.18",
115 | "unpipe": "1.0.0"
116 | },
117 | "engines": {
118 | "node": ">= 0.8",
119 | "npm": "1.2.8000 || >= 1.4.16"
120 | }
121 | },
122 | "node_modules/brace-expansion": {
123 | "version": "1.1.11",
124 | "license": "MIT",
125 | "dependencies": {
126 | "balanced-match": "^1.0.0",
127 | "concat-map": "0.0.1"
128 | }
129 | },
130 | "node_modules/braces": {
131 | "version": "3.0.3",
132 | "license": "MIT",
133 | "dependencies": {
134 | "fill-range": "^7.1.1"
135 | },
136 | "engines": {
137 | "node": ">=8"
138 | }
139 | },
140 | "node_modules/bson": {
141 | "version": "6.8.0",
142 | "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz",
143 | "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==",
144 | "license": "Apache-2.0",
145 | "engines": {
146 | "node": ">=16.20.1"
147 | }
148 | },
149 | "node_modules/buffer-equal-constant-time": {
150 | "version": "1.0.1",
151 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
152 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
153 | "license": "BSD-3-Clause"
154 | },
155 | "node_modules/bytes": {
156 | "version": "3.1.2",
157 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
158 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
159 | "license": "MIT",
160 | "engines": {
161 | "node": ">= 0.8"
162 | }
163 | },
164 | "node_modules/call-bind": {
165 | "version": "1.0.7",
166 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
167 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
168 | "license": "MIT",
169 | "dependencies": {
170 | "es-define-property": "^1.0.0",
171 | "es-errors": "^1.3.0",
172 | "function-bind": "^1.1.2",
173 | "get-intrinsic": "^1.2.4",
174 | "set-function-length": "^1.2.1"
175 | },
176 | "engines": {
177 | "node": ">= 0.4"
178 | },
179 | "funding": {
180 | "url": "https://github.com/sponsors/ljharb"
181 | }
182 | },
183 | "node_modules/chokidar": {
184 | "version": "3.6.0",
185 | "license": "MIT",
186 | "dependencies": {
187 | "anymatch": "~3.1.2",
188 | "braces": "~3.0.2",
189 | "glob-parent": "~5.1.2",
190 | "is-binary-path": "~2.1.0",
191 | "is-glob": "~4.0.1",
192 | "normalize-path": "~3.0.0",
193 | "readdirp": "~3.6.0"
194 | },
195 | "engines": {
196 | "node": ">= 8.10.0"
197 | },
198 | "funding": {
199 | "url": "https://paulmillr.com/funding/"
200 | },
201 | "optionalDependencies": {
202 | "fsevents": "~2.3.2"
203 | }
204 | },
205 | "node_modules/cloudinary": {
206 | "version": "2.3.0",
207 | "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.3.0.tgz",
208 | "integrity": "sha512-QBa/ePVVfVcVOB1Vut236rjAbTZAArzOm0e2IWUkQJSZFS65Sjf+i3DyRGen4QX8GZzrcbzvKI9b8BTHAv1zqQ==",
209 | "dependencies": {
210 | "lodash": "^4.17.21",
211 | "q": "^1.5.1"
212 | },
213 | "engines": {
214 | "node": ">=9"
215 | }
216 | },
217 | "node_modules/concat-map": {
218 | "version": "0.0.1",
219 | "license": "MIT"
220 | },
221 | "node_modules/content-disposition": {
222 | "version": "0.5.4",
223 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
224 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
225 | "license": "MIT",
226 | "dependencies": {
227 | "safe-buffer": "5.2.1"
228 | },
229 | "engines": {
230 | "node": ">= 0.6"
231 | }
232 | },
233 | "node_modules/content-type": {
234 | "version": "1.0.5",
235 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
236 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
237 | "license": "MIT",
238 | "engines": {
239 | "node": ">= 0.6"
240 | }
241 | },
242 | "node_modules/cookie": {
243 | "version": "0.4.1",
244 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
245 | "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
246 | "license": "MIT",
247 | "engines": {
248 | "node": ">= 0.6"
249 | }
250 | },
251 | "node_modules/cookie-parser": {
252 | "version": "1.4.6",
253 | "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
254 | "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
255 | "license": "MIT",
256 | "dependencies": {
257 | "cookie": "0.4.1",
258 | "cookie-signature": "1.0.6"
259 | },
260 | "engines": {
261 | "node": ">= 0.8.0"
262 | }
263 | },
264 | "node_modules/cookie-signature": {
265 | "version": "1.0.6",
266 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
267 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
268 | "license": "MIT"
269 | },
270 | "node_modules/cors": {
271 | "version": "2.8.5",
272 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
273 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
274 | "license": "MIT",
275 | "dependencies": {
276 | "object-assign": "^4",
277 | "vary": "^1"
278 | },
279 | "engines": {
280 | "node": ">= 0.10"
281 | }
282 | },
283 | "node_modules/debug": {
284 | "version": "2.6.9",
285 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
286 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
287 | "license": "MIT",
288 | "dependencies": {
289 | "ms": "2.0.0"
290 | }
291 | },
292 | "node_modules/define-data-property": {
293 | "version": "1.1.4",
294 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
295 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
296 | "license": "MIT",
297 | "dependencies": {
298 | "es-define-property": "^1.0.0",
299 | "es-errors": "^1.3.0",
300 | "gopd": "^1.0.1"
301 | },
302 | "engines": {
303 | "node": ">= 0.4"
304 | },
305 | "funding": {
306 | "url": "https://github.com/sponsors/ljharb"
307 | }
308 | },
309 | "node_modules/depd": {
310 | "version": "2.0.0",
311 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
312 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
313 | "license": "MIT",
314 | "engines": {
315 | "node": ">= 0.8"
316 | }
317 | },
318 | "node_modules/destroy": {
319 | "version": "1.2.0",
320 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
321 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
322 | "license": "MIT",
323 | "engines": {
324 | "node": ">= 0.8",
325 | "npm": "1.2.8000 || >= 1.4.16"
326 | }
327 | },
328 | "node_modules/dotenv": {
329 | "version": "16.4.5",
330 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
331 | "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
332 | "engines": {
333 | "node": ">=12"
334 | },
335 | "funding": {
336 | "url": "https://dotenvx.com"
337 | }
338 | },
339 | "node_modules/ecdsa-sig-formatter": {
340 | "version": "1.0.11",
341 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
342 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
343 | "license": "Apache-2.0",
344 | "dependencies": {
345 | "safe-buffer": "^5.0.1"
346 | }
347 | },
348 | "node_modules/ee-first": {
349 | "version": "1.1.1",
350 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
351 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
352 | "license": "MIT"
353 | },
354 | "node_modules/encodeurl": {
355 | "version": "1.0.2",
356 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
357 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
358 | "license": "MIT",
359 | "engines": {
360 | "node": ">= 0.8"
361 | }
362 | },
363 | "node_modules/es-define-property": {
364 | "version": "1.0.0",
365 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
366 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
367 | "license": "MIT",
368 | "dependencies": {
369 | "get-intrinsic": "^1.2.4"
370 | },
371 | "engines": {
372 | "node": ">= 0.4"
373 | }
374 | },
375 | "node_modules/es-errors": {
376 | "version": "1.3.0",
377 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
378 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
379 | "license": "MIT",
380 | "engines": {
381 | "node": ">= 0.4"
382 | }
383 | },
384 | "node_modules/escape-html": {
385 | "version": "1.0.3",
386 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
387 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
388 | "license": "MIT"
389 | },
390 | "node_modules/etag": {
391 | "version": "1.8.1",
392 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
393 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
394 | "license": "MIT",
395 | "engines": {
396 | "node": ">= 0.6"
397 | }
398 | },
399 | "node_modules/express": {
400 | "version": "4.19.2",
401 | "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
402 | "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
403 | "license": "MIT",
404 | "dependencies": {
405 | "accepts": "~1.3.8",
406 | "array-flatten": "1.1.1",
407 | "body-parser": "1.20.2",
408 | "content-disposition": "0.5.4",
409 | "content-type": "~1.0.4",
410 | "cookie": "0.6.0",
411 | "cookie-signature": "1.0.6",
412 | "debug": "2.6.9",
413 | "depd": "2.0.0",
414 | "encodeurl": "~1.0.2",
415 | "escape-html": "~1.0.3",
416 | "etag": "~1.8.1",
417 | "finalhandler": "1.2.0",
418 | "fresh": "0.5.2",
419 | "http-errors": "2.0.0",
420 | "merge-descriptors": "1.0.1",
421 | "methods": "~1.1.2",
422 | "on-finished": "2.4.1",
423 | "parseurl": "~1.3.3",
424 | "path-to-regexp": "0.1.7",
425 | "proxy-addr": "~2.0.7",
426 | "qs": "6.11.0",
427 | "range-parser": "~1.2.1",
428 | "safe-buffer": "5.2.1",
429 | "send": "0.18.0",
430 | "serve-static": "1.15.0",
431 | "setprototypeof": "1.2.0",
432 | "statuses": "2.0.1",
433 | "type-is": "~1.6.18",
434 | "utils-merge": "1.0.1",
435 | "vary": "~1.1.2"
436 | },
437 | "engines": {
438 | "node": ">= 0.10.0"
439 | }
440 | },
441 | "node_modules/express/node_modules/cookie": {
442 | "version": "0.6.0",
443 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
444 | "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
445 | "license": "MIT",
446 | "engines": {
447 | "node": ">= 0.6"
448 | }
449 | },
450 | "node_modules/fill-range": {
451 | "version": "7.1.1",
452 | "license": "MIT",
453 | "dependencies": {
454 | "to-regex-range": "^5.0.1"
455 | },
456 | "engines": {
457 | "node": ">=8"
458 | }
459 | },
460 | "node_modules/finalhandler": {
461 | "version": "1.2.0",
462 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
463 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
464 | "license": "MIT",
465 | "dependencies": {
466 | "debug": "2.6.9",
467 | "encodeurl": "~1.0.2",
468 | "escape-html": "~1.0.3",
469 | "on-finished": "2.4.1",
470 | "parseurl": "~1.3.3",
471 | "statuses": "2.0.1",
472 | "unpipe": "~1.0.0"
473 | },
474 | "engines": {
475 | "node": ">= 0.8"
476 | }
477 | },
478 | "node_modules/forwarded": {
479 | "version": "0.2.0",
480 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
481 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
482 | "license": "MIT",
483 | "engines": {
484 | "node": ">= 0.6"
485 | }
486 | },
487 | "node_modules/fresh": {
488 | "version": "0.5.2",
489 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
490 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
491 | "license": "MIT",
492 | "engines": {
493 | "node": ">= 0.6"
494 | }
495 | },
496 | "node_modules/function-bind": {
497 | "version": "1.1.2",
498 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
499 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
500 | "license": "MIT",
501 | "funding": {
502 | "url": "https://github.com/sponsors/ljharb"
503 | }
504 | },
505 | "node_modules/get-intrinsic": {
506 | "version": "1.2.4",
507 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
508 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
509 | "license": "MIT",
510 | "dependencies": {
511 | "es-errors": "^1.3.0",
512 | "function-bind": "^1.1.2",
513 | "has-proto": "^1.0.1",
514 | "has-symbols": "^1.0.3",
515 | "hasown": "^2.0.0"
516 | },
517 | "engines": {
518 | "node": ">= 0.4"
519 | },
520 | "funding": {
521 | "url": "https://github.com/sponsors/ljharb"
522 | }
523 | },
524 | "node_modules/glob-parent": {
525 | "version": "5.1.2",
526 | "license": "ISC",
527 | "dependencies": {
528 | "is-glob": "^4.0.1"
529 | },
530 | "engines": {
531 | "node": ">= 6"
532 | }
533 | },
534 | "node_modules/gopd": {
535 | "version": "1.0.1",
536 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
537 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
538 | "license": "MIT",
539 | "dependencies": {
540 | "get-intrinsic": "^1.1.3"
541 | },
542 | "funding": {
543 | "url": "https://github.com/sponsors/ljharb"
544 | }
545 | },
546 | "node_modules/has-flag": {
547 | "version": "3.0.0",
548 | "license": "MIT",
549 | "engines": {
550 | "node": ">=4"
551 | }
552 | },
553 | "node_modules/has-property-descriptors": {
554 | "version": "1.0.2",
555 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
556 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
557 | "license": "MIT",
558 | "dependencies": {
559 | "es-define-property": "^1.0.0"
560 | },
561 | "funding": {
562 | "url": "https://github.com/sponsors/ljharb"
563 | }
564 | },
565 | "node_modules/has-proto": {
566 | "version": "1.0.3",
567 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
568 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
569 | "license": "MIT",
570 | "engines": {
571 | "node": ">= 0.4"
572 | },
573 | "funding": {
574 | "url": "https://github.com/sponsors/ljharb"
575 | }
576 | },
577 | "node_modules/has-symbols": {
578 | "version": "1.0.3",
579 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
580 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
581 | "license": "MIT",
582 | "engines": {
583 | "node": ">= 0.4"
584 | },
585 | "funding": {
586 | "url": "https://github.com/sponsors/ljharb"
587 | }
588 | },
589 | "node_modules/hasown": {
590 | "version": "2.0.2",
591 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
592 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
593 | "license": "MIT",
594 | "dependencies": {
595 | "function-bind": "^1.1.2"
596 | },
597 | "engines": {
598 | "node": ">= 0.4"
599 | }
600 | },
601 | "node_modules/http-errors": {
602 | "version": "2.0.0",
603 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
604 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
605 | "license": "MIT",
606 | "dependencies": {
607 | "depd": "2.0.0",
608 | "inherits": "2.0.4",
609 | "setprototypeof": "1.2.0",
610 | "statuses": "2.0.1",
611 | "toidentifier": "1.0.1"
612 | },
613 | "engines": {
614 | "node": ">= 0.8"
615 | }
616 | },
617 | "node_modules/iconv-lite": {
618 | "version": "0.4.24",
619 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
620 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
621 | "license": "MIT",
622 | "dependencies": {
623 | "safer-buffer": ">= 2.1.2 < 3"
624 | },
625 | "engines": {
626 | "node": ">=0.10.0"
627 | }
628 | },
629 | "node_modules/ignore-by-default": {
630 | "version": "1.0.1",
631 | "license": "ISC"
632 | },
633 | "node_modules/inherits": {
634 | "version": "2.0.4",
635 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
636 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
637 | "license": "ISC"
638 | },
639 | "node_modules/ipaddr.js": {
640 | "version": "1.9.1",
641 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
642 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
643 | "license": "MIT",
644 | "engines": {
645 | "node": ">= 0.10"
646 | }
647 | },
648 | "node_modules/is-binary-path": {
649 | "version": "2.1.0",
650 | "license": "MIT",
651 | "dependencies": {
652 | "binary-extensions": "^2.0.0"
653 | },
654 | "engines": {
655 | "node": ">=8"
656 | }
657 | },
658 | "node_modules/is-extglob": {
659 | "version": "2.1.1",
660 | "license": "MIT",
661 | "engines": {
662 | "node": ">=0.10.0"
663 | }
664 | },
665 | "node_modules/is-glob": {
666 | "version": "4.0.3",
667 | "license": "MIT",
668 | "dependencies": {
669 | "is-extglob": "^2.1.1"
670 | },
671 | "engines": {
672 | "node": ">=0.10.0"
673 | }
674 | },
675 | "node_modules/is-number": {
676 | "version": "7.0.0",
677 | "license": "MIT",
678 | "engines": {
679 | "node": ">=0.12.0"
680 | }
681 | },
682 | "node_modules/jsonwebtoken": {
683 | "version": "9.0.2",
684 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
685 | "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
686 | "license": "MIT",
687 | "dependencies": {
688 | "jws": "^3.2.2",
689 | "lodash.includes": "^4.3.0",
690 | "lodash.isboolean": "^3.0.3",
691 | "lodash.isinteger": "^4.0.4",
692 | "lodash.isnumber": "^3.0.3",
693 | "lodash.isplainobject": "^4.0.6",
694 | "lodash.isstring": "^4.0.1",
695 | "lodash.once": "^4.0.0",
696 | "ms": "^2.1.1",
697 | "semver": "^7.5.4"
698 | },
699 | "engines": {
700 | "node": ">=12",
701 | "npm": ">=6"
702 | }
703 | },
704 | "node_modules/jsonwebtoken/node_modules/ms": {
705 | "version": "2.1.3",
706 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
707 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
708 | "license": "MIT"
709 | },
710 | "node_modules/jwa": {
711 | "version": "1.4.1",
712 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
713 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
714 | "license": "MIT",
715 | "dependencies": {
716 | "buffer-equal-constant-time": "1.0.1",
717 | "ecdsa-sig-formatter": "1.0.11",
718 | "safe-buffer": "^5.0.1"
719 | }
720 | },
721 | "node_modules/jws": {
722 | "version": "3.2.2",
723 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
724 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
725 | "license": "MIT",
726 | "dependencies": {
727 | "jwa": "^1.4.1",
728 | "safe-buffer": "^5.0.1"
729 | }
730 | },
731 | "node_modules/kareem": {
732 | "version": "2.6.3",
733 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
734 | "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
735 | "license": "Apache-2.0",
736 | "engines": {
737 | "node": ">=12.0.0"
738 | }
739 | },
740 | "node_modules/lodash": {
741 | "version": "4.17.21",
742 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
743 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
744 | },
745 | "node_modules/lodash.includes": {
746 | "version": "4.3.0",
747 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
748 | "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
749 | "license": "MIT"
750 | },
751 | "node_modules/lodash.isboolean": {
752 | "version": "3.0.3",
753 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
754 | "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
755 | "license": "MIT"
756 | },
757 | "node_modules/lodash.isinteger": {
758 | "version": "4.0.4",
759 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
760 | "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
761 | "license": "MIT"
762 | },
763 | "node_modules/lodash.isnumber": {
764 | "version": "3.0.3",
765 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
766 | "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
767 | "license": "MIT"
768 | },
769 | "node_modules/lodash.isplainobject": {
770 | "version": "4.0.6",
771 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
772 | "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
773 | "license": "MIT"
774 | },
775 | "node_modules/lodash.isstring": {
776 | "version": "4.0.1",
777 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
778 | "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
779 | "license": "MIT"
780 | },
781 | "node_modules/lodash.once": {
782 | "version": "4.1.1",
783 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
784 | "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
785 | "license": "MIT"
786 | },
787 | "node_modules/media-typer": {
788 | "version": "0.3.0",
789 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
790 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
791 | "license": "MIT",
792 | "engines": {
793 | "node": ">= 0.6"
794 | }
795 | },
796 | "node_modules/memory-pager": {
797 | "version": "1.5.0",
798 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
799 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
800 | "license": "MIT"
801 | },
802 | "node_modules/merge-descriptors": {
803 | "version": "1.0.1",
804 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
805 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==",
806 | "license": "MIT"
807 | },
808 | "node_modules/methods": {
809 | "version": "1.1.2",
810 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
811 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
812 | "license": "MIT",
813 | "engines": {
814 | "node": ">= 0.6"
815 | }
816 | },
817 | "node_modules/mime": {
818 | "version": "1.6.0",
819 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
820 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
821 | "license": "MIT",
822 | "bin": {
823 | "mime": "cli.js"
824 | },
825 | "engines": {
826 | "node": ">=4"
827 | }
828 | },
829 | "node_modules/mime-db": {
830 | "version": "1.52.0",
831 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
832 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
833 | "license": "MIT",
834 | "engines": {
835 | "node": ">= 0.6"
836 | }
837 | },
838 | "node_modules/mime-types": {
839 | "version": "2.1.35",
840 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
841 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
842 | "license": "MIT",
843 | "dependencies": {
844 | "mime-db": "1.52.0"
845 | },
846 | "engines": {
847 | "node": ">= 0.6"
848 | }
849 | },
850 | "node_modules/minimatch": {
851 | "version": "3.1.2",
852 | "license": "ISC",
853 | "dependencies": {
854 | "brace-expansion": "^1.1.7"
855 | },
856 | "engines": {
857 | "node": "*"
858 | }
859 | },
860 | "node_modules/mongodb": {
861 | "version": "6.7.0",
862 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.7.0.tgz",
863 | "integrity": "sha512-TMKyHdtMcO0fYBNORiYdmM25ijsHs+Njs963r4Tro4OQZzqYigAzYQouwWRg4OIaiLRUEGUh/1UAcH5lxdSLIA==",
864 | "license": "Apache-2.0",
865 | "dependencies": {
866 | "@mongodb-js/saslprep": "^1.1.5",
867 | "bson": "^6.7.0",
868 | "mongodb-connection-string-url": "^3.0.0"
869 | },
870 | "engines": {
871 | "node": ">=16.20.1"
872 | },
873 | "peerDependencies": {
874 | "@aws-sdk/credential-providers": "^3.188.0",
875 | "@mongodb-js/zstd": "^1.1.0",
876 | "gcp-metadata": "^5.2.0",
877 | "kerberos": "^2.0.1",
878 | "mongodb-client-encryption": ">=6.0.0 <7",
879 | "snappy": "^7.2.2",
880 | "socks": "^2.7.1"
881 | },
882 | "peerDependenciesMeta": {
883 | "@aws-sdk/credential-providers": {
884 | "optional": true
885 | },
886 | "@mongodb-js/zstd": {
887 | "optional": true
888 | },
889 | "gcp-metadata": {
890 | "optional": true
891 | },
892 | "kerberos": {
893 | "optional": true
894 | },
895 | "mongodb-client-encryption": {
896 | "optional": true
897 | },
898 | "snappy": {
899 | "optional": true
900 | },
901 | "socks": {
902 | "optional": true
903 | }
904 | }
905 | },
906 | "node_modules/mongodb-connection-string-url": {
907 | "version": "3.0.1",
908 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz",
909 | "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==",
910 | "license": "Apache-2.0",
911 | "dependencies": {
912 | "@types/whatwg-url": "^11.0.2",
913 | "whatwg-url": "^13.0.0"
914 | }
915 | },
916 | "node_modules/mongoose": {
917 | "version": "8.5.1",
918 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.5.1.tgz",
919 | "integrity": "sha512-OhVcwVl91A1G6+XpjDcpkGP7l7ikZkxa0DylX7NT/lcEqAjggzSdqDxb48A+xsDxqNAr0ntSJ1yiE3+KJTOd5Q==",
920 | "license": "MIT",
921 | "dependencies": {
922 | "bson": "^6.7.0",
923 | "kareem": "2.6.3",
924 | "mongodb": "6.7.0",
925 | "mpath": "0.9.0",
926 | "mquery": "5.0.0",
927 | "ms": "2.1.3",
928 | "sift": "17.1.3"
929 | },
930 | "engines": {
931 | "node": ">=16.20.1"
932 | },
933 | "funding": {
934 | "type": "opencollective",
935 | "url": "https://opencollective.com/mongoose"
936 | }
937 | },
938 | "node_modules/mongoose/node_modules/ms": {
939 | "version": "2.1.3",
940 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
941 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
942 | "license": "MIT"
943 | },
944 | "node_modules/mpath": {
945 | "version": "0.9.0",
946 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
947 | "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
948 | "license": "MIT",
949 | "engines": {
950 | "node": ">=4.0.0"
951 | }
952 | },
953 | "node_modules/mquery": {
954 | "version": "5.0.0",
955 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
956 | "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
957 | "license": "MIT",
958 | "dependencies": {
959 | "debug": "4.x"
960 | },
961 | "engines": {
962 | "node": ">=14.0.0"
963 | }
964 | },
965 | "node_modules/mquery/node_modules/debug": {
966 | "version": "4.3.5",
967 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
968 | "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
969 | "license": "MIT",
970 | "dependencies": {
971 | "ms": "2.1.2"
972 | },
973 | "engines": {
974 | "node": ">=6.0"
975 | },
976 | "peerDependenciesMeta": {
977 | "supports-color": {
978 | "optional": true
979 | }
980 | }
981 | },
982 | "node_modules/mquery/node_modules/ms": {
983 | "version": "2.1.2",
984 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
985 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
986 | "license": "MIT"
987 | },
988 | "node_modules/ms": {
989 | "version": "2.0.0",
990 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
991 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
992 | "license": "MIT"
993 | },
994 | "node_modules/negotiator": {
995 | "version": "0.6.3",
996 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
997 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
998 | "license": "MIT",
999 | "engines": {
1000 | "node": ">= 0.6"
1001 | }
1002 | },
1003 | "node_modules/nodemon": {
1004 | "version": "3.1.4",
1005 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz",
1006 | "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==",
1007 | "dependencies": {
1008 | "chokidar": "^3.5.2",
1009 | "debug": "^4",
1010 | "ignore-by-default": "^1.0.1",
1011 | "minimatch": "^3.1.2",
1012 | "pstree.remy": "^1.1.8",
1013 | "semver": "^7.5.3",
1014 | "simple-update-notifier": "^2.0.0",
1015 | "supports-color": "^5.5.0",
1016 | "touch": "^3.1.0",
1017 | "undefsafe": "^2.0.5"
1018 | },
1019 | "bin": {
1020 | "nodemon": "bin/nodemon.js"
1021 | },
1022 | "engines": {
1023 | "node": ">=10"
1024 | },
1025 | "funding": {
1026 | "type": "opencollective",
1027 | "url": "https://opencollective.com/nodemon"
1028 | }
1029 | },
1030 | "node_modules/nodemon/node_modules/debug": {
1031 | "version": "4.3.5",
1032 | "license": "MIT",
1033 | "dependencies": {
1034 | "ms": "2.1.2"
1035 | },
1036 | "engines": {
1037 | "node": ">=6.0"
1038 | },
1039 | "peerDependenciesMeta": {
1040 | "supports-color": {
1041 | "optional": true
1042 | }
1043 | }
1044 | },
1045 | "node_modules/nodemon/node_modules/ms": {
1046 | "version": "2.1.2",
1047 | "license": "MIT"
1048 | },
1049 | "node_modules/normalize-path": {
1050 | "version": "3.0.0",
1051 | "license": "MIT",
1052 | "engines": {
1053 | "node": ">=0.10.0"
1054 | }
1055 | },
1056 | "node_modules/object-assign": {
1057 | "version": "4.1.1",
1058 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1059 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
1060 | "license": "MIT",
1061 | "engines": {
1062 | "node": ">=0.10.0"
1063 | }
1064 | },
1065 | "node_modules/object-inspect": {
1066 | "version": "1.13.2",
1067 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
1068 | "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
1069 | "license": "MIT",
1070 | "engines": {
1071 | "node": ">= 0.4"
1072 | },
1073 | "funding": {
1074 | "url": "https://github.com/sponsors/ljharb"
1075 | }
1076 | },
1077 | "node_modules/on-finished": {
1078 | "version": "2.4.1",
1079 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
1080 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
1081 | "license": "MIT",
1082 | "dependencies": {
1083 | "ee-first": "1.1.1"
1084 | },
1085 | "engines": {
1086 | "node": ">= 0.8"
1087 | }
1088 | },
1089 | "node_modules/parseurl": {
1090 | "version": "1.3.3",
1091 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
1092 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
1093 | "license": "MIT",
1094 | "engines": {
1095 | "node": ">= 0.8"
1096 | }
1097 | },
1098 | "node_modules/path-to-regexp": {
1099 | "version": "0.1.7",
1100 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
1101 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==",
1102 | "license": "MIT"
1103 | },
1104 | "node_modules/picomatch": {
1105 | "version": "2.3.1",
1106 | "license": "MIT",
1107 | "engines": {
1108 | "node": ">=8.6"
1109 | },
1110 | "funding": {
1111 | "url": "https://github.com/sponsors/jonschlinkert"
1112 | }
1113 | },
1114 | "node_modules/proxy-addr": {
1115 | "version": "2.0.7",
1116 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1117 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1118 | "license": "MIT",
1119 | "dependencies": {
1120 | "forwarded": "0.2.0",
1121 | "ipaddr.js": "1.9.1"
1122 | },
1123 | "engines": {
1124 | "node": ">= 0.10"
1125 | }
1126 | },
1127 | "node_modules/pstree.remy": {
1128 | "version": "1.1.8",
1129 | "license": "MIT"
1130 | },
1131 | "node_modules/punycode": {
1132 | "version": "2.3.1",
1133 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
1134 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
1135 | "license": "MIT",
1136 | "engines": {
1137 | "node": ">=6"
1138 | }
1139 | },
1140 | "node_modules/q": {
1141 | "version": "1.5.1",
1142 | "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
1143 | "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==",
1144 | "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)",
1145 | "engines": {
1146 | "node": ">=0.6.0",
1147 | "teleport": ">=0.2.0"
1148 | }
1149 | },
1150 | "node_modules/qs": {
1151 | "version": "6.11.0",
1152 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
1153 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
1154 | "license": "BSD-3-Clause",
1155 | "dependencies": {
1156 | "side-channel": "^1.0.4"
1157 | },
1158 | "engines": {
1159 | "node": ">=0.6"
1160 | },
1161 | "funding": {
1162 | "url": "https://github.com/sponsors/ljharb"
1163 | }
1164 | },
1165 | "node_modules/range-parser": {
1166 | "version": "1.2.1",
1167 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1168 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
1169 | "license": "MIT",
1170 | "engines": {
1171 | "node": ">= 0.6"
1172 | }
1173 | },
1174 | "node_modules/raw-body": {
1175 | "version": "2.5.2",
1176 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
1177 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
1178 | "license": "MIT",
1179 | "dependencies": {
1180 | "bytes": "3.1.2",
1181 | "http-errors": "2.0.0",
1182 | "iconv-lite": "0.4.24",
1183 | "unpipe": "1.0.0"
1184 | },
1185 | "engines": {
1186 | "node": ">= 0.8"
1187 | }
1188 | },
1189 | "node_modules/readdirp": {
1190 | "version": "3.6.0",
1191 | "license": "MIT",
1192 | "dependencies": {
1193 | "picomatch": "^2.2.1"
1194 | },
1195 | "engines": {
1196 | "node": ">=8.10.0"
1197 | }
1198 | },
1199 | "node_modules/safe-buffer": {
1200 | "version": "5.2.1",
1201 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1202 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1203 | "funding": [
1204 | {
1205 | "type": "github",
1206 | "url": "https://github.com/sponsors/feross"
1207 | },
1208 | {
1209 | "type": "patreon",
1210 | "url": "https://www.patreon.com/feross"
1211 | },
1212 | {
1213 | "type": "consulting",
1214 | "url": "https://feross.org/support"
1215 | }
1216 | ],
1217 | "license": "MIT"
1218 | },
1219 | "node_modules/safer-buffer": {
1220 | "version": "2.1.2",
1221 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1222 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1223 | "license": "MIT"
1224 | },
1225 | "node_modules/semver": {
1226 | "version": "7.6.3",
1227 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
1228 | "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
1229 | "license": "ISC",
1230 | "bin": {
1231 | "semver": "bin/semver.js"
1232 | },
1233 | "engines": {
1234 | "node": ">=10"
1235 | }
1236 | },
1237 | "node_modules/send": {
1238 | "version": "0.18.0",
1239 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
1240 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
1241 | "license": "MIT",
1242 | "dependencies": {
1243 | "debug": "2.6.9",
1244 | "depd": "2.0.0",
1245 | "destroy": "1.2.0",
1246 | "encodeurl": "~1.0.2",
1247 | "escape-html": "~1.0.3",
1248 | "etag": "~1.8.1",
1249 | "fresh": "0.5.2",
1250 | "http-errors": "2.0.0",
1251 | "mime": "1.6.0",
1252 | "ms": "2.1.3",
1253 | "on-finished": "2.4.1",
1254 | "range-parser": "~1.2.1",
1255 | "statuses": "2.0.1"
1256 | },
1257 | "engines": {
1258 | "node": ">= 0.8.0"
1259 | }
1260 | },
1261 | "node_modules/send/node_modules/ms": {
1262 | "version": "2.1.3",
1263 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1264 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1265 | "license": "MIT"
1266 | },
1267 | "node_modules/serve-static": {
1268 | "version": "1.15.0",
1269 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
1270 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
1271 | "license": "MIT",
1272 | "dependencies": {
1273 | "encodeurl": "~1.0.2",
1274 | "escape-html": "~1.0.3",
1275 | "parseurl": "~1.3.3",
1276 | "send": "0.18.0"
1277 | },
1278 | "engines": {
1279 | "node": ">= 0.8.0"
1280 | }
1281 | },
1282 | "node_modules/set-function-length": {
1283 | "version": "1.2.2",
1284 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
1285 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
1286 | "license": "MIT",
1287 | "dependencies": {
1288 | "define-data-property": "^1.1.4",
1289 | "es-errors": "^1.3.0",
1290 | "function-bind": "^1.1.2",
1291 | "get-intrinsic": "^1.2.4",
1292 | "gopd": "^1.0.1",
1293 | "has-property-descriptors": "^1.0.2"
1294 | },
1295 | "engines": {
1296 | "node": ">= 0.4"
1297 | }
1298 | },
1299 | "node_modules/setprototypeof": {
1300 | "version": "1.2.0",
1301 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1302 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
1303 | "license": "ISC"
1304 | },
1305 | "node_modules/side-channel": {
1306 | "version": "1.0.6",
1307 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
1308 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
1309 | "license": "MIT",
1310 | "dependencies": {
1311 | "call-bind": "^1.0.7",
1312 | "es-errors": "^1.3.0",
1313 | "get-intrinsic": "^1.2.4",
1314 | "object-inspect": "^1.13.1"
1315 | },
1316 | "engines": {
1317 | "node": ">= 0.4"
1318 | },
1319 | "funding": {
1320 | "url": "https://github.com/sponsors/ljharb"
1321 | }
1322 | },
1323 | "node_modules/sift": {
1324 | "version": "17.1.3",
1325 | "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
1326 | "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==",
1327 | "license": "MIT"
1328 | },
1329 | "node_modules/simple-update-notifier": {
1330 | "version": "2.0.0",
1331 | "license": "MIT",
1332 | "dependencies": {
1333 | "semver": "^7.5.3"
1334 | },
1335 | "engines": {
1336 | "node": ">=10"
1337 | }
1338 | },
1339 | "node_modules/sparse-bitfield": {
1340 | "version": "3.0.3",
1341 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
1342 | "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
1343 | "license": "MIT",
1344 | "dependencies": {
1345 | "memory-pager": "^1.0.2"
1346 | }
1347 | },
1348 | "node_modules/statuses": {
1349 | "version": "2.0.1",
1350 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
1351 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
1352 | "license": "MIT",
1353 | "engines": {
1354 | "node": ">= 0.8"
1355 | }
1356 | },
1357 | "node_modules/supports-color": {
1358 | "version": "5.5.0",
1359 | "license": "MIT",
1360 | "dependencies": {
1361 | "has-flag": "^3.0.0"
1362 | },
1363 | "engines": {
1364 | "node": ">=4"
1365 | }
1366 | },
1367 | "node_modules/to-regex-range": {
1368 | "version": "5.0.1",
1369 | "license": "MIT",
1370 | "dependencies": {
1371 | "is-number": "^7.0.0"
1372 | },
1373 | "engines": {
1374 | "node": ">=8.0"
1375 | }
1376 | },
1377 | "node_modules/toidentifier": {
1378 | "version": "1.0.1",
1379 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1380 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1381 | "license": "MIT",
1382 | "engines": {
1383 | "node": ">=0.6"
1384 | }
1385 | },
1386 | "node_modules/touch": {
1387 | "version": "3.1.1",
1388 | "license": "ISC",
1389 | "bin": {
1390 | "nodetouch": "bin/nodetouch.js"
1391 | }
1392 | },
1393 | "node_modules/tr46": {
1394 | "version": "4.1.1",
1395 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
1396 | "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
1397 | "license": "MIT",
1398 | "dependencies": {
1399 | "punycode": "^2.3.0"
1400 | },
1401 | "engines": {
1402 | "node": ">=14"
1403 | }
1404 | },
1405 | "node_modules/type-is": {
1406 | "version": "1.6.18",
1407 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1408 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1409 | "license": "MIT",
1410 | "dependencies": {
1411 | "media-typer": "0.3.0",
1412 | "mime-types": "~2.1.24"
1413 | },
1414 | "engines": {
1415 | "node": ">= 0.6"
1416 | }
1417 | },
1418 | "node_modules/undefsafe": {
1419 | "version": "2.0.5",
1420 | "license": "MIT"
1421 | },
1422 | "node_modules/unpipe": {
1423 | "version": "1.0.0",
1424 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1425 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1426 | "license": "MIT",
1427 | "engines": {
1428 | "node": ">= 0.8"
1429 | }
1430 | },
1431 | "node_modules/utils-merge": {
1432 | "version": "1.0.1",
1433 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1434 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1435 | "license": "MIT",
1436 | "engines": {
1437 | "node": ">= 0.4.0"
1438 | }
1439 | },
1440 | "node_modules/vary": {
1441 | "version": "1.1.2",
1442 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1443 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1444 | "license": "MIT",
1445 | "engines": {
1446 | "node": ">= 0.8"
1447 | }
1448 | },
1449 | "node_modules/webidl-conversions": {
1450 | "version": "7.0.0",
1451 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
1452 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
1453 | "license": "BSD-2-Clause",
1454 | "engines": {
1455 | "node": ">=12"
1456 | }
1457 | },
1458 | "node_modules/whatwg-url": {
1459 | "version": "13.0.0",
1460 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
1461 | "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
1462 | "license": "MIT",
1463 | "dependencies": {
1464 | "tr46": "^4.1.1",
1465 | "webidl-conversions": "^7.0.0"
1466 | },
1467 | "engines": {
1468 | "node": ">=16"
1469 | }
1470 | }
1471 | }
1472 | }
1473 |
--------------------------------------------------------------------------------