├── .gitignore ├── loadEnv.js ├── middleware ├── sessionConfig.js ├── logger.js └── verifyToken.js ├── db └── connectDB.js ├── models ├── user.js ├── personalLibrary.js ├── userLibrary.js ├── submission.js └── main.js ├── index.js ├── routes ├── auth.js ├── dashboard.js ├── media.js └── users.js ├── README.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules -------------------------------------------------------------------------------- /loadEnv.js: -------------------------------------------------------------------------------- 1 | import dotenv from "dotenv"; 2 | dotenv.config(); -------------------------------------------------------------------------------- /middleware/sessionConfig.js: -------------------------------------------------------------------------------- 1 | const sessionConfig = { 2 | sessionExpiration: '1h', // Example: 1 hour session expiration 3 | }; 4 | 5 | module.exports = sessionConfig; 6 | -------------------------------------------------------------------------------- /db/connectDB.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const connectDB = async () => { 4 | try { 5 | mongoose.set("strictQuery", false); 6 | const conn = await mongoose.connect(process.env.ATLAS_URI); 7 | console.log(`MongoDB connection ${conn.connection.host}`); 8 | } catch (error) { 9 | console.log(error); 10 | process.exit(1); 11 | } 12 | }; 13 | 14 | module.exports = connectDB; -------------------------------------------------------------------------------- /middleware/logger.js: -------------------------------------------------------------------------------- 1 | const winston = require('winston'); 2 | 3 | // Configure the Winston logger 4 | const logger = winston.createLogger({ 5 | transports: [ 6 | new winston.transports.Console(), 7 | // Add additional transports as needed (e.g., file transport for storing logs) 8 | ], 9 | format: winston.format.combine( 10 | winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), 11 | winston.format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`), 12 | ), 13 | }); 14 | 15 | module.exports = logger; 16 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const userSchema = new mongoose.Schema({ 4 | username: { type: String, required: true }, 5 | email: { type: String, required: true }, 6 | password: { type: String, required: true, min: 8 }, 7 | biography: { type: String }, 8 | profileImage: { type: String }, 9 | isAdmin: { type: Boolean, default: false}, 10 | createdAt: { type: Date, default: Date.now }, 11 | }, { collection: 'users' }); 12 | 13 | const User = mongoose.model('User', userSchema); 14 | 15 | module.exports = User; 16 | -------------------------------------------------------------------------------- /models/personalLibrary.js: -------------------------------------------------------------------------------- 1 | // import mongoose from 'mongoose'; 2 | 3 | // const personalLibrarySchema = new mongoose.Schema({ 4 | // userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, 5 | // name: { type: String, required: true }, 6 | // creationDate: { type: Date }, 7 | // description: { type: String }, 8 | // isPublic: { type: Boolean }, 9 | // createdAt: { type: Date, default: Date.now }, 10 | // updatedAt: { type: Date } 11 | // }); 12 | 13 | // const PersonalLibrary = mongoose.model('PersonalLibrary', personalLibrarySchema); 14 | 15 | // export default PersonalLibrary; 16 | -------------------------------------------------------------------------------- /models/userLibrary.js: -------------------------------------------------------------------------------- 1 | // import mongoose from 'mongoose'; 2 | 3 | // const userLibrarySchema = new mongoose.Schema({ 4 | // userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, 5 | // entryId: { type: mongoose.Schema.Types.ObjectId, ref: 'Main' }, 6 | // additionalColumns: { type: Object }, // Define additional columns here 7 | // isPublic: { type: Boolean }, 8 | // createdAt: { type: Date, default: Date.now }, 9 | // updatedAt: { type: Date } 10 | // }); 11 | 12 | // const UserLibrary = mongoose.model('UserLibrary', userLibrarySchema); 13 | 14 | // export default UserLibrary; 15 | -------------------------------------------------------------------------------- /models/submission.js: -------------------------------------------------------------------------------- 1 | // import mongoose from 'mongoose'; 2 | 3 | // const submissionSchema = new mongoose.Schema({ 4 | // title: { type: String, required: true }, 5 | // type: { type: String, required: true }, 6 | // volumeSubtitle: { type: String }, 7 | // format: { type: String }, 8 | // studios: { type: [String] }, 9 | // releaseDate: { type: Date }, 10 | // upcIsbn: { type: String }, 11 | // createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, 12 | // createdAt: { type: Date, default: Date.now } 13 | // }); 14 | 15 | // const Submission = mongoose.model('Submission', submissionSchema); 16 | 17 | // export default Submission; 18 | -------------------------------------------------------------------------------- /middleware/verifyToken.js: -------------------------------------------------------------------------------- 1 | // verifyToken.js 2 | 3 | const jwt = require('jsonwebtoken'); 4 | 5 | const verifyToken = (req, res, next) => { 6 | // Check for the token being sent in a header or as a query parameter 7 | let token = req.get('Authorization') || req.query.token; 8 | 9 | if (token) { 10 | // Remove the 'Bearer ' if it was included in the token header 11 | token = token.replace('Bearer ', ''); 12 | 13 | // Verify the token 14 | jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { 15 | if (err) { 16 | return res.status(401).json({ error: 'Unauthorized' }); 17 | } else { 18 | // If valid token, attach decoded payload to request object 19 | req.userId = decoded.userId; 20 | next(); 21 | } 22 | }); 23 | } else { 24 | // No token was sent 25 | res.status(401).json({ error: 'Unauthorized' }); 26 | } 27 | }; 28 | 29 | module.exports = verifyToken; 30 | -------------------------------------------------------------------------------- /models/main.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const mainSchema = new mongoose.Schema({ 4 | title: { type: String, required: true }, 5 | type: { type: String, required: true }, 6 | volumeSubtitle: { type: String }, 7 | format: { type: String }, 8 | studios: { type: [String] }, 9 | releaseDate: { 10 | type: Date, 11 | validate: { 12 | validator: function(valid) { 13 | // Regular expression to match YYYY-MM-DD format 14 | return /\d{4}-\d{2}-\d{2}/.test(valid); 15 | }, 16 | message: props => `${props.value} is not a valid date in the format YYYY-MM-DD!` 17 | } 18 | }, 19 | upcIsbn: { type: String }, 20 | // createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, 21 | // createdAt: { type: Date, default: Date.now }, 22 | // updatedAt: { type: Date }, 23 | }, { collection: 'main' }); 24 | 25 | const Main = mongoose.model('Main', mainSchema); 26 | 27 | module.exports = Main; 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const cors = require('cors'); 3 | const express = require('express'); 4 | const connectDB = require('./db/connectDB.js'); 5 | const userRoutes = require('./routes/users.js'); 6 | const mediaRoutes = require('./routes/media.js'); 7 | const dashboardRoutes = require('./routes/dashboard.js'); // Import dashboard routes 8 | 9 | const app = express(); 10 | const port = process.env.PORT || 9001; 11 | 12 | // Connect to MongoDB 13 | connectDB(); 14 | 15 | // Middleware 16 | app.use(cors()); 17 | app.use(express.urlencoded({ extended: true })); 18 | app.use(express.json()); 19 | 20 | // Routes 21 | app.use('/api', userRoutes); 22 | app.use('/api', mediaRoutes); 23 | app.use('/api', dashboardRoutes); // Mount the dashboard routes 24 | 25 | // Handle invalid routes 26 | app.use((req, res) => { 27 | res.status(404).json({ error: 'Not Found' }); 28 | }); 29 | 30 | // Error handling middleware 31 | app.use((err, req, res, next) => { 32 | console.error(err.stack); 33 | res.status(500).json({ error: 'Internal Server Error' }); 34 | }); 35 | 36 | // Start the server 37 | app.listen(port, () => { 38 | console.log(`Server is running on port: ${port}`); 39 | }); 40 | -------------------------------------------------------------------------------- /routes/auth.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const jwt = require('jsonwebtoken'); 4 | const User = require('../models/user'); 5 | const sessionConfig = require('../middleware/sessionConfig'); 6 | 7 | // Login Route 8 | router.post('/login', async (req, res) => { 9 | const { username, password } = req.body; 10 | try { 11 | const user = await User.findOne({ username, password }); 12 | if (user) { 13 | const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: sessionConfig.sessionExpiration }); 14 | res.status(200).json({ message: 'Login successful', token }); 15 | } else { 16 | res.status(401).json({ error: 'Invalid credentials' }); 17 | } 18 | } catch (error) { 19 | res.status(500).json({ error: 'An error occurred' }); 20 | } 21 | }); 22 | 23 | // // Change password route (requires authentication) 24 | // authRoutes.post("/change-password", verifyToken, async (req, res) => { 25 | // console.log(req.user); 26 | // res.send("Token verified!"); 27 | // }); 28 | 29 | // User logout route 30 | authRoutes.post('/logout', (req, res) => { 31 | // Clear the JWT token from client-side storage 32 | res.clearCookie('token'); 33 | // Respond with a success message or any other relevant response 34 | res.status(200).json({ message: 'Logged out successfully' }); 35 | }); 36 | 37 | module.exports = router; 38 | -------------------------------------------------------------------------------- /routes/dashboard.js: -------------------------------------------------------------------------------- 1 | // routes/dashboard.js 2 | const express = require('express'); 3 | const dashboardRoutes = express.Router(); 4 | const User = require('./users'); 5 | const Media = require('./media'); 6 | 7 | // Main Library Route 8 | dashboardRoutes.get('/main-library', async (req, res) => { 9 | try { 10 | const allMedia = await Media.find(); 11 | res.json(allMedia); 12 | } catch (error) { 13 | console.error('Error fetching media data:', error); 14 | res.status(500).json({ error: 'Internal Server Error' }); 15 | } 16 | }); 17 | 18 | // Admin Dashboard Route 19 | dashboardRoutes.get('/admindash/:username', async (req, res) => { 20 | try { 21 | const { username } = req.params; 22 | const adminProfile = await User.findOne({ username }); 23 | if (!adminProfile) { 24 | return res.status(404).json({ error: 'Admin profile not found' }); 25 | } 26 | res.json(adminProfile); 27 | } catch (error) { 28 | console.error('Error fetching admin profile:', error); 29 | res.status(500).json({ error: 'Internal Server Error' }); 30 | } 31 | }); 32 | 33 | // User Dashboard Route 34 | dashboardRoutes.get('/userdash/:username', async (req, res) => { 35 | try { 36 | const { username } = req.params; 37 | const userProfile = await User.findOne({ username }); 38 | if (!userProfile) { 39 | return res.status(404).json({ error: 'User profile not found' }); 40 | } 41 | res.json(userProfile); 42 | } catch (error) { 43 | console.error('Error fetching user profile:', error); 44 | res.status(500).json({ error: 'Internal Server Error' }); 45 | } 46 | }); 47 | 48 | module.exports = dashboardRoutes; 49 | -------------------------------------------------------------------------------- /routes/media.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const mediaRoutes = express.Router(); 3 | const Main = require("../models/main.js"); 4 | 5 | // Middleware to handle errors 6 | function handleError(res, err) { 7 | console.error(err); 8 | res.status(500).json({ error: err.message }); 9 | } 10 | 11 | // Get all main media 12 | mediaRoutes.route("/media").get(async function(req, res) { 13 | try { 14 | const mainMedia = await Main.find(); 15 | res.json(mainMedia); 16 | } catch (err) { 17 | handleError(res, err); 18 | } 19 | }); 20 | 21 | // Get a single main media by id 22 | mediaRoutes.route("/media/:id").get(async function(req, res) { 23 | try { 24 | const mainMedia = await Main.findById(req.params.id); 25 | if (!mainMedia) { 26 | return res.status(404).json({ error: 'Main media not found' }); 27 | } 28 | res.json(mainMedia); 29 | } catch (err) { 30 | handleError(res, err); 31 | } 32 | }); 33 | 34 | // Create a new main media 35 | mediaRoutes.route("/media/add").post(async function(req, res) { 36 | try { 37 | const newMainMedia = new Main(req.body); 38 | await newMainMedia.save(); 39 | res.status(201).json(newMainMedia); 40 | } catch (err) { 41 | handleError(res, err); 42 | } 43 | }); 44 | 45 | // Update a main media by id 46 | mediaRoutes.route("/media/update/:id").put(async function(req, res) { 47 | try { 48 | const updatedMainMedia = await Main.findByIdAndUpdate(req.params.id, req.body, { new: true }); 49 | if (!updatedMainMedia) { 50 | return res.status(404).json({ error: 'Main media not found' }); 51 | } 52 | res.json(updatedMainMedia); 53 | } catch (err) { 54 | handleError(res, err); 55 | } 56 | }); 57 | 58 | // Delete a main media by id 59 | mediaRoutes.route("/media/delete/:id").delete(async (req, res) => { 60 | try { 61 | const deletedMainMedia = await Main.findByIdAndDelete(req.params.id); 62 | if (!deletedMainMedia) { 63 | return res.status(404).json({ error: 'Main media not found' }); 64 | } 65 | res.json({ message: 'Main media deleted successfully' }); 66 | } catch (err) { 67 | handleError(res, err); 68 | } 69 | }); 70 | 71 | module.exports = mediaRoutes; 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Toshokanime Media Library Software 2 | PS Capstone 3 | 4 | ## Links: 5 | - [Planning](https://trello.com/b/UIVMJCXR/toshokanime) 6 | - [Backend Repository](https://github.com/Castien/Toshokanime-backend) 7 | - [Frontend Repository](https://github.com/Castien/Toshokanime-frontend) 8 | 9 | ## Summary: 10 | This project aims to develop a comprehensive media library software for managing video media, focusing on VHS, DVD, and Blu-ray formats. It provides functionalities for both administrators and users to manage, organize, and interact with the media entries effectively. 11 | 12 | ## Planned Improvements, Updates, Stretch Goals: 13 | - UPC/ISBN scan integration for seamless entry addition. 14 | - Implementation of personal libraries with enhanced features such as multiple libraries creation, library customization, and sharing options. 15 | - Introduction of an admin/user messaging system for streamlined communication. 16 | - Enhanced privacy options for personal libraries, allowing users to choose public or private visibility. 17 | 18 | ## Techstack: 19 | - [Visual Studio Code](https://code.visualstudio.com/) 20 | - [MongoDB](https://www.mongodb.com/) 21 | - [Mongoose](https://mongoosejs.com/) 22 | - [Bootstrap](https://getbootstrap.com/) 23 | - [Javascript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) 24 | - [Node.js](https://nodejs.org/) 25 | - [Express.js](https://expressjs.com/) 26 | - [React](https://reactjs.org/) 27 | - [Typescript](https://www.typescriptlang.org/) 28 | 29 | ## Resources: 30 | - [JWT Token Authentication Repository](https://github.com/AbeTavarez/cohort107/tree/tk/nodejs/auth-jwt-token) 31 | - [Video Tutorial 1](https://www.youtube.com/watch?v=dX_LteE0NFM) 32 | - [Video Tutorial 2](https://www.youtube.com/watch?v=nI8PYZNFtac) 33 | - [Video Tutorial 3](https://www.youtube.com/watch?v=hZ4cvCTwyTY) 34 | 35 | 36 | ## Admin Functionalities: 37 | 38 | Completed: 39 | - Create an account with admin privileges. 40 | - Utilize a specific code to unlock admin privileges. 41 | - Log in and out of the admin account. 42 | 43 | In Progress: 44 | - Edit a publicly viewable profile with username, optional biography, and optional profile image. 45 | - Edit non-publicly viewable email and password. 46 | - CRUD operations for user data. (working on backend) 47 | - CRUD operations for main library entries. (working on backend) 48 | - Search, filter, and view entries in the main library. 49 | 50 | 51 | ## User Functionalities: 52 | 53 | Completed: 54 | - Create an account. 55 | - Log in and out of the user account. 56 | 57 | In Progress: 58 | - Edit a publicly viewable profile with username, optional biography, and optional profile image. 59 | - Edit non-publicly viewable email and password. 60 | - Search, filter, and view entries in the main library. 61 | - Retrieve specific entries from the database to add to the personal library. 62 | - API Access 63 | 64 | 65 | 66 | ## Stretch Goals: 67 | - Integration of UPC/ISBN scan feature. 68 | - Creation of multiple personal libraries with customizations. 69 | - Management functionalities for personal libraries. 70 | - Implementation of admin/user messaging system. 71 | - Enhanced privacy settings for personal libraries. 72 | - Receive user submissions for main library entries. 73 | - Approve, edit, or reject user submissions. 74 | - Customize columns in the personal library. 75 | - Add, search, view, delete, and edit additional data in the personal library. 76 | - Submit entries for the main database review. 77 | 78 | ## Blockers: 79 | - Authentication Logic 80 | - Unable to load components and pages intended to appear on frontend. 81 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); // Load environment variables from .env file 2 | const express = require("express"); 3 | const User = require("../models/user.js"); // Correct the path to the User model 4 | const jwt = require("jsonwebtoken"); 5 | const verifyToken = require("../middleware/verifyToken.js"); 6 | 7 | const userRoutes = express.Router(); 8 | 9 | // Error handling middleware 10 | const handleError = (res, error) => { 11 | console.error("Error:", error); 12 | res.status(500).json({ error: "Internal Server Error" }); 13 | }; 14 | 15 | // Get all users 16 | userRoutes.get("/users", verifyToken, async (req, res) => { 17 | try { 18 | const users = await User.find(); 19 | res.json(users); 20 | } catch (error) { 21 | handleError(res, error); 22 | } 23 | }); 24 | 25 | // Get a user by username 26 | userRoutes.get("/users/:username", verifyToken, async (req, res) => { 27 | try { 28 | const username = req.params.username; 29 | const user = await User.findOne({ username }); 30 | if (user) { 31 | res.json(user); 32 | } else { 33 | res.status(404).json({ error: "User not found" }); 34 | } 35 | } catch (error) { 36 | handleError(res, error); 37 | } 38 | }); 39 | 40 | // Create a new user 41 | userRoutes.post("/users", verifyToken, async (req, res) => { 42 | try { 43 | const newUser = new User(req.body); 44 | await newUser.save(); 45 | res.status(201).json(newUser); 46 | } catch (error) { 47 | handleError(res, error); 48 | } 49 | }); 50 | 51 | // Update a user by ID 52 | userRoutes.put("/users/:id", verifyToken, async (req, res) => { 53 | try { 54 | const userId = req.params.id; 55 | const updatedUser = await User.findByIdAndUpdate(userId, req.body, { new: true }); 56 | res.json(updatedUser); 57 | } catch (error) { 58 | handleError(res, error); 59 | } 60 | }); 61 | 62 | // Delete a user by ID 63 | userRoutes.delete("/users/:id", verifyToken, async (req, res) => { 64 | try { 65 | const userId = req.params.id; 66 | await User.findByIdAndDelete(userId); 67 | res.json({ message: "User deleted successfully" }); 68 | } catch (error) { 69 | handleError(res, error); 70 | } 71 | }); 72 | 73 | userRoutes.post("/register", async (req, res) => { 74 | const { username, email, password, adminKey } = req.body; 75 | const isAdmin = adminKey === process.env.ADMIN_KEY; 76 | 77 | try { 78 | // Check if username or email already exists 79 | const existingUser = await User.findOne({ $or: [{ username }, { email }] }); 80 | if (existingUser) { 81 | return res.status(400).json({ error: "Username or email already exists" }); 82 | } 83 | 84 | // Create a new user 85 | const newUser = new User({ 86 | username, 87 | email, 88 | password, 89 | isAdmin, 90 | }); 91 | await newUser.save(); 92 | res.status(201).json({ message: "User registered successfully" }); 93 | } catch (error) { 94 | handleError(res, error); 95 | } 96 | }); 97 | 98 | userRoutes.post("/login", async (req, res) => { 99 | const { username, password } = req.body; 100 | 101 | try { 102 | const user = await User.findOne({ username, password }); 103 | if (user) { 104 | // Generate JWT token 105 | const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: "1h" }); 106 | let isAdmin = user.isAdmin; // Assuming isAdmin is a field in your User schema 107 | res.status(200).json({ message: "Login successful", isAdmin, token }); 108 | } else { 109 | res.status(401).json({ error: "Invalid credentials" }); 110 | } 111 | } catch (error) { 112 | res.status(500).json({ error: "An error occurred" }); 113 | } 114 | }); 115 | 116 | userRoutes.post("/api/users/:userId/profile", verifyToken, async (req, res) => { 117 | const userId = req.params.userId; 118 | const profileData = req.body; 119 | 120 | try { 121 | await User.findByIdAndUpdate(userId, profileData); 122 | res.status(200).json({ message: "User profile updated successfully" }); 123 | } catch (error) { 124 | handleError(res, error); 125 | } 126 | }); 127 | 128 | module.exports = userRoutes; 129 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "toshokanime-backend", 3 | "version": "1.0.0", 4 | "proxy": "http://localhost:9001", 5 | "description": "PS Capstone", 6 | "main": "index.js", 7 | "dependencies": { 8 | "abbrev": "^1.1.1", 9 | "accepts": "^1.3.8", 10 | "agent-base": "^6.0.2", 11 | "ansi-regex": "^5.0.1", 12 | "anymatch": "^3.1.3", 13 | "aproba": "^2.0.0", 14 | "are-we-there-yet": "^2.0.0", 15 | "array-flatten": "^1.1.1", 16 | "balanced-match": "^1.0.2", 17 | "basic-auth": "^2.0.1", 18 | "bcrypt": "^5.1.1", 19 | "binary-extensions": "^2.2.0", 20 | "body-parser": "^1.20.2", 21 | "brace-expansion": "^1.1.11", 22 | "braces": "^3.0.2", 23 | "bson": "^6.3.0", 24 | "buffer-equal-constant-time": "^1.0.1", 25 | "bytes": "^3.1.2", 26 | "call-bind": "^1.0.6", 27 | "chokidar": "^3.6.0", 28 | "chownr": "^2.0.0", 29 | "color-support": "^1.1.3", 30 | "concat-map": "^0.0.1", 31 | "console-control-strings": "^1.1.0", 32 | "content-disposition": "^0.5.4", 33 | "content-type": "^1.0.5", 34 | "cookie": "^0.5.0", 35 | "cookie-parser": "^1.4.6", 36 | "cookie-signature": "^1.0.6", 37 | "cors": "^2.8.5", 38 | "debug": "^2.6.9", 39 | "define-data-property": "^1.1.2", 40 | "delegates": "^1.0.0", 41 | "depd": "^2.0.0", 42 | "destroy": "^1.2.0", 43 | "detect-libc": "^2.0.2", 44 | "dotenv": "^16.4.4", 45 | "ecdsa-sig-formatter": "^1.0.11", 46 | "ee-first": "^1.1.1", 47 | "emoji-regex": "^8.0.0", 48 | "encodeurl": "^1.0.2", 49 | "es-errors": "^1.3.0", 50 | "escape-html": "^1.0.3", 51 | "etag": "^1.8.1", 52 | "express": "^4.18.2", 53 | "express-session": "^1.18.0", 54 | "fill-range": "^7.0.1", 55 | "finalhandler": "^1.2.0", 56 | "forwarded": "^0.2.0", 57 | "fresh": "^0.5.2", 58 | "fs-minipass": "^2.1.0", 59 | "fs.realpath": "^1.0.0", 60 | "function-bind": "^1.1.2", 61 | "gauge": "^3.0.2", 62 | "get-intrinsic": "^1.2.4", 63 | "glob": "^7.2.3", 64 | "glob-parent": "^5.1.2", 65 | "gopd": "^1.0.1", 66 | "has-flag": "^3.0.0", 67 | "has-property-descriptors": "^1.0.1", 68 | "has-proto": "^1.0.1", 69 | "has-symbols": "^1.0.3", 70 | "has-unicode": "^2.0.1", 71 | "hasown": "^2.0.0", 72 | "http-errors": "^2.0.0", 73 | "https-proxy-agent": "^5.0.1", 74 | "iconv-lite": "^0.4.24", 75 | "ignore-by-default": "^1.0.1", 76 | "inflight": "^1.0.6", 77 | "inherits": "^2.0.4", 78 | "ipaddr.js": "^1.9.1", 79 | "is-binary-path": "^2.1.0", 80 | "is-extglob": "^2.1.1", 81 | "is-fullwidth-code-point": "^3.0.0", 82 | "is-glob": "^4.0.3", 83 | "is-number": "^7.0.0", 84 | "js-tokens": "^4.0.0", 85 | "jsonwebtoken": "^9.0.2", 86 | "jwa": "^1.4.1", 87 | "jws": "^3.2.2", 88 | "kareem": "^2.5.1", 89 | "lodash.includes": "^4.3.0", 90 | "lodash.isboolean": "^3.0.3", 91 | "lodash.isinteger": "^4.0.4", 92 | "lodash.isnumber": "^3.0.3", 93 | "lodash.isplainobject": "^4.0.6", 94 | "lodash.isstring": "^4.0.1", 95 | "lodash.once": "^4.1.1", 96 | "loose-envify": "^1.4.0", 97 | "lru-cache": "^6.0.0", 98 | "make-dir": "^3.1.0", 99 | "media-typer": "^0.3.0", 100 | "memory-pager": "^1.5.0", 101 | "merge-descriptors": "^1.0.1", 102 | "methods": "^1.1.2", 103 | "mime": "^1.6.0", 104 | "mime-db": "^1.52.0", 105 | "mime-types": "^2.1.35", 106 | "minimatch": "^3.1.2", 107 | "minipass": "^5.0.0", 108 | "minizlib": "^2.1.2", 109 | "mkdirp": "^1.0.4", 110 | "mongodb": "^6.3.0", 111 | "mongodb-connection-string-url": "^3.0.0", 112 | "mongoose": "^8.1.2", 113 | "morgan": "^1.10.0", 114 | "mpath": "^0.9.0", 115 | "mquery": "^5.0.0", 116 | "ms": "^2.0.0", 117 | "negotiator": "^0.6.3", 118 | "node-addon-api": "^5.1.0", 119 | "node-fetch": "^2.7.0", 120 | "nopt": "^1.0.10", 121 | "normalize-path": "^3.0.0", 122 | "npmlog": "^5.0.1", 123 | "object-assign": "^4.1.1", 124 | "object-inspect": "^1.13.1", 125 | "on-finished": "^2.4.1", 126 | "on-headers": "^1.0.2", 127 | "once": "^1.4.0", 128 | "parseurl": "^1.3.3", 129 | "path-is-absolute": "^1.0.1", 130 | "path-to-regexp": "^0.1.7", 131 | "picomatch": "^2.3.1", 132 | "proxy-addr": "^2.0.7", 133 | "pstree.remy": "^1.1.8", 134 | "punycode": "^2.3.1", 135 | "qs": "^6.11.0", 136 | "random-bytes": "^1.0.0", 137 | "range-parser": "^1.2.1", 138 | "raw-body": "^2.5.2", 139 | "react": "^18.2.0", 140 | "react-dom": "^18.2.0", 141 | "react-redux": "^9.1.0", 142 | "react-router": "^6.22.0", 143 | "react-router-dom": "^6.22.0", 144 | "readable-stream": "^3.6.2", 145 | "readdirp": "^3.6.0", 146 | "rimraf": "^3.0.2", 147 | "safe-buffer": "^5.2.1", 148 | "safer-buffer": "^2.1.2", 149 | "scheduler": "^0.23.0", 150 | "semver": "^7.6.0", 151 | "send": "^0.18.0", 152 | "serve-static": "^1.15.0", 153 | "set-blocking": "^2.0.0", 154 | "set-function-length": "^1.2.1", 155 | "setprototypeof": "^1.2.0", 156 | "side-channel": "^1.0.5", 157 | "sift": "^16.0.1", 158 | "signal-exit": "^3.0.7", 159 | "simple-update-notifier": "^2.0.0", 160 | "sparse-bitfield": "^3.0.3", 161 | "statuses": "^2.0.1", 162 | "string_decoder": "^1.3.0", 163 | "string-width": "^4.2.3", 164 | "strip-ansi": "^6.0.1", 165 | "supports-color": "^5.5.0", 166 | "tar": "^6.2.0", 167 | "to-regex-range": "^5.0.1", 168 | "toidentifier": "^1.0.1", 169 | "touch": "^3.1.0", 170 | "tr46": "^4.1.1", 171 | "type-is": "^1.6.18", 172 | "uid-safe": "^2.1.5", 173 | "undefsafe": "^2.0.5", 174 | "unpipe": "^1.0.0", 175 | "util-deprecate": "^1.0.2", 176 | "utils-merge": "^1.0.1", 177 | "vary": "^1.1.2", 178 | "webidl-conversions": "^7.0.0", 179 | "whatwg-url": "^13.0.0", 180 | "wide-align": "^1.1.5", 181 | "wrappy": "^1.0.2", 182 | "yallist": "^4.0.0" 183 | }, 184 | "scripts": { 185 | "test": "echo \"Error: no test specified\" && exit 1", 186 | "start": "node index.js", 187 | "dev": "nodemon index.js" 188 | }, 189 | "keywords": [], 190 | "author": "", 191 | "license": "ISC", 192 | "devDependencies": { 193 | "nodemon": "^3.0.3" 194 | } 195 | } 196 | --------------------------------------------------------------------------------