mern-template-tool 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -0
- package/bin/index.js +112 -0
- package/package.json +33 -0
- package/templates/README.md +160 -0
- package/templates/_gitignore +8 -0
- package/templates/client/index.html +13 -0
- package/templates/client/package.json +20 -0
- package/templates/client/src/App.jsx +72 -0
- package/templates/client/src/components/Loading.jsx +14 -0
- package/templates/client/src/components/Navbar.jsx +58 -0
- package/templates/client/src/context/AuthContext.jsx +109 -0
- package/templates/client/src/index.css +244 -0
- package/templates/client/src/main.jsx +16 -0
- package/templates/client/src/pages/CreateResource.jsx +142 -0
- package/templates/client/src/pages/Dashboard.jsx +117 -0
- package/templates/client/src/pages/EditResource.jsx +166 -0
- package/templates/client/src/pages/Login.jsx +101 -0
- package/templates/client/src/pages/NotFound.jsx +21 -0
- package/templates/client/src/pages/Profile.jsx +52 -0
- package/templates/client/src/pages/Register.jsx +134 -0
- package/templates/client/src/pages/ResourceDetails.jsx +156 -0
- package/templates/client/src/pages/Resources.jsx +181 -0
- package/templates/client/src/routes/ProtectedRoute.jsx +34 -0
- package/templates/client/src/services/api.js +71 -0
- package/templates/client/vite.config.js +20 -0
- package/templates/server/.env.example +5 -0
- package/templates/server/config/db.js +13 -0
- package/templates/server/controllers/authController.js +131 -0
- package/templates/server/controllers/resourceController.js +254 -0
- package/templates/server/middleware/adminMiddleware.js +14 -0
- package/templates/server/middleware/authMiddleware.js +53 -0
- package/templates/server/middleware/errorMiddleware.js +55 -0
- package/templates/server/middleware/sessionAuthMiddleware.js +12 -0
- package/templates/server/middleware/uploadMiddleware.js +61 -0
- package/templates/server/middleware/validateMiddleware.js +61 -0
- package/templates/server/models/Resource.js +43 -0
- package/templates/server/models/User.js +48 -0
- package/templates/server/package.json +25 -0
- package/templates/server/routes/adminRoutes.js +138 -0
- package/templates/server/routes/authRoutes.js +21 -0
- package/templates/server/routes/resourceRoutes.js +27 -0
- package/templates/server/server.js +89 -0
- package/templates/server/utils/jwt.js +18 -0
- package/templates/server/views/dashboard.ejs +128 -0
- package/templates/server/views/login.ejs +48 -0
- package/templates/server/views/partials/footer.ejs +6 -0
- package/templates/server/views/partials/header.ejs +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# mern-template-tool
|
|
2
|
+
|
|
3
|
+
> An exam-oriented, offline-first MERN stack project generator for college practical examinations and fast prototyping.
|
|
4
|
+
|
|
5
|
+
Execute anywhere with:
|
|
6
|
+
```bash
|
|
7
|
+
npx mern-template-tool <project-name>
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## ⚡ What is `mern-template`?
|
|
13
|
+
|
|
14
|
+
During practical examinations, internet access is often limited to a brief window. `mern-template` provides a complete, robust, ready-to-run MERN stack boilerplate with all standard requirements pre-implemented:
|
|
15
|
+
|
|
16
|
+
- 🔒 **Dual Authentication**: JWT Authentication for React SPA & `express-session` for EJS Admin Portal.
|
|
17
|
+
- 👥 **Role-Based Authorization**: Student & Admin roles with protected routes and ownership checking.
|
|
18
|
+
- 📦 **Generic CRUD Model**: Complete MVC structure with search, filtering, and sorting (`req.query`).
|
|
19
|
+
- 🛡️ **Validation & Security**: `express-validator` rules, `bcryptjs` password hashing, safe JSON responses.
|
|
20
|
+
- 📎 **Multer File Handling**: Local disk storage, file validation, and file download endpoints.
|
|
21
|
+
- ⚛️ **Modern React Frontend**: React 18, Vite, React Router 6, React Context API state, native `fetch` service.
|
|
22
|
+
- 🔌 **Zero External Cloud Dependencies**: Local MongoDB & local filesystem storage, ready for air-gapped exam environments.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🚀 Quick Usage
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# 1. Generate project
|
|
30
|
+
npx mern-template my-exam-project
|
|
31
|
+
|
|
32
|
+
# 2. Start Backend Server
|
|
33
|
+
cd my-exam-project/server
|
|
34
|
+
cp .env.example .env
|
|
35
|
+
npm install
|
|
36
|
+
npm run dev
|
|
37
|
+
|
|
38
|
+
# 3. Start Frontend Client (in a second terminal)
|
|
39
|
+
cd ../client
|
|
40
|
+
npm install
|
|
41
|
+
npm run dev
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 📋 Generated Project Structure
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
my-exam-project/
|
|
50
|
+
├── client/ # React Single Page Application (Vite)
|
|
51
|
+
│ ├── src/
|
|
52
|
+
│ │ ├── components/ # Navbar, Loading
|
|
53
|
+
│ │ ├── context/ # AuthContext (React Context API)
|
|
54
|
+
│ │ ├── pages/ # Login, Register, Dashboard, Profile, Resources, etc.
|
|
55
|
+
│ │ ├── routes/ # ProtectedRoute
|
|
56
|
+
│ │ ├── services/ # api.js (native fetch helper)
|
|
57
|
+
│ │ ├── App.jsx # Router definition
|
|
58
|
+
│ │ ├── index.css # Modern dark UI theme
|
|
59
|
+
│ │ └── main.jsx # Entry point
|
|
60
|
+
│ ├── package.json
|
|
61
|
+
│ └── vite.config.js
|
|
62
|
+
│
|
|
63
|
+
├── server/ # Express API & EJS Admin Portal
|
|
64
|
+
│ ├── config/ # db.js (Mongoose connection)
|
|
65
|
+
│ ├── controllers/ # authController.js, resourceController.js
|
|
66
|
+
│ ├── middleware/ # authMiddleware, adminMiddleware, uploadMiddleware, validateMiddleware, errorMiddleware, sessionAuthMiddleware
|
|
67
|
+
│ ├── models/ # User.js, Resource.js
|
|
68
|
+
│ ├── routes/ # authRoutes.js, resourceRoutes.js, adminRoutes.js
|
|
69
|
+
│ ├── utils/ # jwt.js
|
|
70
|
+
│ ├── views/ # EJS admin templates (login.ejs, dashboard.ejs, partials/)
|
|
71
|
+
│ ├── uploads/ # Local Multer upload storage
|
|
72
|
+
│ ├── .env.example # Environment variables
|
|
73
|
+
│ ├── package.json
|
|
74
|
+
│ └── server.js # Express application entrypoint
|
|
75
|
+
│
|
|
76
|
+
└── README.md # Includes exam cheat sheet & modification guides
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 📦 Publishing to npm
|
|
82
|
+
|
|
83
|
+
To publish this package to npm:
|
|
84
|
+
```bash
|
|
85
|
+
npm login
|
|
86
|
+
npm pack --dry-run
|
|
87
|
+
npm publish --access public
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 📄 License
|
|
93
|
+
MIT
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function printUsage() {
|
|
7
|
+
console.log(`
|
|
8
|
+
\x1b[36m\x1b[1mMERN Template Generator\x1b[0m (Exam-Ready & Offline First)
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
npx mern-template-tool <project-name>
|
|
12
|
+
|
|
13
|
+
Examples:
|
|
14
|
+
npx mern-template-tool my-app
|
|
15
|
+
npx mern-template-tool employee-management
|
|
16
|
+
npx mern-template-tool library-app
|
|
17
|
+
`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function copyDirRecursive(source, target) {
|
|
21
|
+
if (!fs.existsSync(target)) {
|
|
22
|
+
fs.mkdirSync(target, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const entries = fs.readdirSync(source, { withFileTypes: true });
|
|
26
|
+
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
const srcPath = path.join(source, entry.name);
|
|
29
|
+
// Handle dotfiles if renamed or standard (like _gitignore -> .gitignore or .env.example)
|
|
30
|
+
let destName = entry.name;
|
|
31
|
+
if (entry.name === '_gitignore') {
|
|
32
|
+
destName = '.gitignore';
|
|
33
|
+
}
|
|
34
|
+
const destPath = path.join(target, destName);
|
|
35
|
+
|
|
36
|
+
if (entry.isDirectory()) {
|
|
37
|
+
copyDirRecursive(srcPath, destPath);
|
|
38
|
+
} else {
|
|
39
|
+
fs.copyFileSync(srcPath, destPath);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function main() {
|
|
45
|
+
const args = process.argv.slice(2);
|
|
46
|
+
|
|
47
|
+
if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
|
|
48
|
+
printUsage();
|
|
49
|
+
process.exit(args.length === 0 ? 1 : 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const projectName = args[0].trim();
|
|
53
|
+
|
|
54
|
+
if (!projectName || /^[./\\]+$/.test(projectName)) {
|
|
55
|
+
console.error('\x1b[31mError: Please provide a valid project folder name.\x1b[0m');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
60
|
+
|
|
61
|
+
if (fs.existsSync(targetDir)) {
|
|
62
|
+
const existingFiles = fs.readdirSync(targetDir);
|
|
63
|
+
if (existingFiles.length > 0) {
|
|
64
|
+
console.error(`\x1b[31mError: Directory '${projectName}' already exists and is not empty.\x1b[0m`);
|
|
65
|
+
console.error('Please specify a new directory name or empty the existing directory.');
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const templateDir = path.resolve(__dirname, '..', 'templates');
|
|
71
|
+
|
|
72
|
+
if (!fs.existsSync(templateDir)) {
|
|
73
|
+
console.error(`\x1b[31mError: Template directory not found at ${templateDir}\x1b[0m`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(`\n\x1b[32m✔\x1b[0m Generating MERN stack project in \x1b[33m${targetDir}\x1b[0m...\n`);
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
copyDirRecursive(templateDir, targetDir);
|
|
81
|
+
|
|
82
|
+
// Make sure uploads directory exists in server
|
|
83
|
+
const uploadsDir = path.join(targetDir, 'server', 'uploads');
|
|
84
|
+
if (!fs.existsSync(uploadsDir)) {
|
|
85
|
+
fs.mkdirSync(uploadsDir, { recursive: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(`\x1b[32m✔\x1b[0m Project successfully generated!\n`);
|
|
89
|
+
console.log(`\x1b[1mNext Steps to run your project:\x1b[0m`);
|
|
90
|
+
console.log(`\x1b[36m--------------------------------------------------\x1b[0m`);
|
|
91
|
+
console.log(`1. Navigate to your project directory:`);
|
|
92
|
+
console.log(` \x1b[33mcd ${projectName}\x1b[0m\n`);
|
|
93
|
+
console.log(`2. Setup and Start Backend Server:`);
|
|
94
|
+
console.log(` \x1b[33mcd server\x1b[0m`);
|
|
95
|
+
console.log(` \x1b[33mcp .env.example .env\x1b[0m (or copy .env.example to .env on Windows)`);
|
|
96
|
+
console.log(` \x1b[33mnpm install\x1b[0m`);
|
|
97
|
+
console.log(` \x1b[33mnpm run dev\x1b[0m (Runs on http://localhost:5000)\n`);
|
|
98
|
+
console.log(`3. Setup and Start React Client:`);
|
|
99
|
+
console.log(` \x1b[33mcd ../client\x1b[0m`);
|
|
100
|
+
console.log(` \x1b[33mnpm install\x1b[0m`);
|
|
101
|
+
console.log(` \x1b[33mnpm run dev\x1b[0m (Runs on http://localhost:5173)\n`);
|
|
102
|
+
console.log(`4. Access Admin EJS Application:`);
|
|
103
|
+
console.log(` Open \x1b[34mhttp://localhost:5000/admin/login\x1b[0m\n`);
|
|
104
|
+
console.log(`\x1b[36m--------------------------------------------------\x1b[0m`);
|
|
105
|
+
console.log(`\x1b[32mAll features (JWT Auth, Sessions, EJS, Multer, Generic CRUD, React Context) are ready to use!\x1b[0m\n`);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.error(`\x1b[31mError generating project:\x1b[0m`, err);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mern-template-tool",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Exam-oriented MERN stack project generator with JWT, sessions, EJS, React Context, Multer, and generic CRUD",
|
|
5
|
+
"main": "bin/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mern-template-tool": "bin/index.js",
|
|
8
|
+
"mern-template": "bin/index.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"mern",
|
|
12
|
+
"template",
|
|
13
|
+
"generator",
|
|
14
|
+
"react",
|
|
15
|
+
"express",
|
|
16
|
+
"mongodb",
|
|
17
|
+
"exam",
|
|
18
|
+
"jwt",
|
|
19
|
+
"auth",
|
|
20
|
+
"ejs",
|
|
21
|
+
"multer"
|
|
22
|
+
],
|
|
23
|
+
"author": "smit-mendapara",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"files": [
|
|
26
|
+
"bin",
|
|
27
|
+
"templates",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=16.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Generated MERN Project (Exam-Ready & Offline First)
|
|
2
|
+
|
|
3
|
+
This is a production-ready, exam-oriented full-stack MERN application generated by `mern-template`. It provides complete authentication (JWT & express-session), authorization, MVC generic resource CRUD, search/filtering/sorting, express-validator validation, Multer file upload/download, centralized error handling, React Context state management, and protected React routing.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📁 Project Structure
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
.
|
|
11
|
+
├── client/ # React Single Page Application (Vite)
|
|
12
|
+
│ ├── src/
|
|
13
|
+
│ │ ├── components/ # Reusable UI (Navbar, Loading)
|
|
14
|
+
│ │ ├── context/ # AuthContext (React Context API)
|
|
15
|
+
│ │ ├── pages/ # Login, Register, Dashboard, Profile, Resources, etc.
|
|
16
|
+
│ │ ├── routes/ # ProtectedRoute wrapper
|
|
17
|
+
│ │ ├── services/ # api.js (native fetch HTTP client)
|
|
18
|
+
│ │ ├── App.jsx # Main router
|
|
19
|
+
│ │ ├── index.css # Modern CSS styling
|
|
20
|
+
│ │ └── main.jsx # React root
|
|
21
|
+
│ ├── package.json
|
|
22
|
+
│ └── vite.config.js
|
|
23
|
+
│
|
|
24
|
+
├── server/ # Express.js REST API & EJS Admin Portal
|
|
25
|
+
│ ├── config/ # db.js (Mongoose connection)
|
|
26
|
+
│ ├── controllers/ # authController.js, resourceController.js
|
|
27
|
+
│ ├── middleware/ # authMiddleware, adminMiddleware, uploadMiddleware, validateMiddleware, errorMiddleware, sessionAuthMiddleware
|
|
28
|
+
│ ├── models/ # User.js, Resource.js
|
|
29
|
+
│ ├── routes/ # authRoutes.js, resourceRoutes.js, adminRoutes.js
|
|
30
|
+
│ ├── utils/ # jwt.js
|
|
31
|
+
│ ├── views/ # EJS admin templates (login.ejs, dashboard.ejs, partials/)
|
|
32
|
+
│ ├── uploads/ # Local file storage for Multer
|
|
33
|
+
│ ├── .env.example # Environment variables example
|
|
34
|
+
│ ├── package.json
|
|
35
|
+
│ └── server.js # Express application entrypoint
|
|
36
|
+
│
|
|
37
|
+
└── README.md
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 🚀 Quick Start Guide
|
|
43
|
+
|
|
44
|
+
### 1. Prerequisites
|
|
45
|
+
- **Node.js** (v16+)
|
|
46
|
+
- **MongoDB** running locally on `mongodb://127.0.0.1:27017`
|
|
47
|
+
|
|
48
|
+
### 2. Backend Setup
|
|
49
|
+
```bash
|
|
50
|
+
cd server
|
|
51
|
+
cp .env.example .env # (On Windows cmd: copy .env.example .env)
|
|
52
|
+
npm install
|
|
53
|
+
npm run dev # Server starts on http://localhost:5000
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 3. Frontend Setup
|
|
57
|
+
```bash
|
|
58
|
+
cd client
|
|
59
|
+
npm install
|
|
60
|
+
npm run dev # Client starts on http://localhost:5173
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 🌐 Endpoints & URLs Reference
|
|
66
|
+
|
|
67
|
+
### 🔐 React Application
|
|
68
|
+
- **URL**: `http://localhost:5173`
|
|
69
|
+
- **Features**: Registration, Login, Dashboard, Profile, Resource Listing, Create/Edit/Delete, File Upload, Instant Search & Sorting.
|
|
70
|
+
|
|
71
|
+
### 🛡️ EJS Admin Portal (Session Authentication)
|
|
72
|
+
- **Login URL**: `http://localhost:5000/admin/login`
|
|
73
|
+
- **Dashboard URL**: `http://localhost:5000/admin/dashboard`
|
|
74
|
+
- **Default Behavior**: Register an admin user via React (or seed one), then sign in through the EJS portal to manage users and inspect system statistics.
|
|
75
|
+
|
|
76
|
+
### 📡 REST API Routes
|
|
77
|
+
| Method | Endpoint | Access | Description |
|
|
78
|
+
|---|---|---|---|
|
|
79
|
+
| `POST` | `/api/auth/register` | Public | Register student or admin |
|
|
80
|
+
| `POST` | `/api/auth/login` | Public | Login & receive JWT |
|
|
81
|
+
| `GET` | `/api/auth/me` | Protected (JWT) | Get authenticated user profile |
|
|
82
|
+
| `GET` | `/api/resources` | Public | Get resources (supports `?search=&category=&sort=`) |
|
|
83
|
+
| `GET` | `/api/resources/:id` | Public | Get single resource by ID |
|
|
84
|
+
| `GET` | `/api/resources/:id/download`| Public | Download attached file |
|
|
85
|
+
| `POST` | `/api/resources` | Protected (JWT) | Create resource (`multipart/form-data` with file) |
|
|
86
|
+
| `PUT` | `/api/resources/:id` | Protected (Owner/Admin) | Update resource & replace file |
|
|
87
|
+
| `DELETE` | `/api/resources/:id` | Protected (Owner/Admin) | Delete resource & remove file |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 💡 Practical Exam Quick Reference & Cheatsheet
|
|
92
|
+
|
|
93
|
+
### 1. Express Request Objects
|
|
94
|
+
- `req.body`: Form or JSON payload sent in `POST`/`PUT` requests.
|
|
95
|
+
- `req.params`: URL parameters (e.g. `req.params.id` from `/api/resources/:id`).
|
|
96
|
+
- `req.query`: Query string parameters (e.g. `req.query.search` from `/api/resources?search=mern`).
|
|
97
|
+
- `req.file`: Attached file uploaded via Multer (`upload.single('file')`).
|
|
98
|
+
- `req.user`: User payload attached by JWT auth middleware (`req.user._id`, `req.user.role`).
|
|
99
|
+
|
|
100
|
+
### 2. Mongoose Model Methods
|
|
101
|
+
```js
|
|
102
|
+
// Find all matching criteria
|
|
103
|
+
const items = await Model.find({ category: 'Exam' }).sort({ createdAt: -1 });
|
|
104
|
+
|
|
105
|
+
// Find single document by ID
|
|
106
|
+
const item = await Model.findById(req.params.id);
|
|
107
|
+
|
|
108
|
+
// Create document
|
|
109
|
+
const newItem = await Model.create({ title: 'Sample', createdBy: req.user._id });
|
|
110
|
+
|
|
111
|
+
// Update document by ID
|
|
112
|
+
const updated = await Model.findByIdAndUpdate(req.params.id, req.body, { new: true });
|
|
113
|
+
|
|
114
|
+
// Delete document by ID
|
|
115
|
+
await Model.findByIdAndDelete(req.params.id);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 3. Password Hashing (bcryptjs)
|
|
119
|
+
```js
|
|
120
|
+
const bcrypt = require('bcryptjs');
|
|
121
|
+
// Hash password
|
|
122
|
+
const salt = await bcrypt.genSalt(10);
|
|
123
|
+
const hashedPassword = await bcrypt.hash(password, salt);
|
|
124
|
+
|
|
125
|
+
// Compare password
|
|
126
|
+
const isMatch = await bcrypt.compare(plainPassword, hashedPassword);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 4. JWT Generation & Verification
|
|
130
|
+
```js
|
|
131
|
+
const jwt = require('jsonwebtoken');
|
|
132
|
+
// Generate Token
|
|
133
|
+
const token = jwt.sign({ id: user._id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '1d' });
|
|
134
|
+
|
|
135
|
+
// Verify Token
|
|
136
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 5. React Hooks & Context
|
|
140
|
+
```jsx
|
|
141
|
+
import { useState, useEffect } from 'react';
|
|
142
|
+
import { useAuth } from '../context/AuthContext';
|
|
143
|
+
import { useNavigate, useParams } from 'react-router-dom';
|
|
144
|
+
|
|
145
|
+
const { user, isAuthenticated, logout } = useAuth();
|
|
146
|
+
const navigate = useNavigate();
|
|
147
|
+
const { id } = useParams();
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 🛠️ How to Customize for Specific Exam Questions
|
|
153
|
+
|
|
154
|
+
1. **Renaming the Resource Model**:
|
|
155
|
+
- To build a **Library / Book Management System**: Rename `Resource.js` to `Book.js`, change `title` to `bookName`, `category` to `genre`, and `description` to `author`.
|
|
156
|
+
- To build an **Employee System**: Rename `Resource.js` to `Employee.js`, fields to `name`, `department`, `salary`, `designation`.
|
|
157
|
+
2. **Adding New Routes**:
|
|
158
|
+
- Create router in `server/routes/xyzRoutes.js` and register in `server.js` using `app.use('/api/xyz', xyzRouter);`.
|
|
159
|
+
3. **Updating Frontend Form Fields**:
|
|
160
|
+
- Update `client/src/pages/CreateResource.jsx` and `EditResource.jsx` with input elements matching your new model fields.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>MERN Application</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "client",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"react": "^18.3.1",
|
|
13
|
+
"react-dom": "^18.3.1",
|
|
14
|
+
"react-router-dom": "^6.23.1"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
18
|
+
"vite": "^5.2.11"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Routes, Route, Navigate } from 'react-router-dom';
|
|
3
|
+
import Navbar from './components/Navbar';
|
|
4
|
+
import ProtectedRoute from './routes/ProtectedRoute';
|
|
5
|
+
|
|
6
|
+
// Pages
|
|
7
|
+
import Login from './pages/Login';
|
|
8
|
+
import Register from './pages/Register';
|
|
9
|
+
import Dashboard from './pages/Dashboard';
|
|
10
|
+
import Profile from './pages/Profile';
|
|
11
|
+
import Resources from './pages/Resources';
|
|
12
|
+
import ResourceDetails from './pages/ResourceDetails';
|
|
13
|
+
import CreateResource from './pages/CreateResource';
|
|
14
|
+
import EditResource from './pages/EditResource';
|
|
15
|
+
import NotFound from './pages/NotFound';
|
|
16
|
+
|
|
17
|
+
function App() {
|
|
18
|
+
return (
|
|
19
|
+
<div className="app">
|
|
20
|
+
<Navbar />
|
|
21
|
+
<main>
|
|
22
|
+
<Routes>
|
|
23
|
+
{/* Public Routes */}
|
|
24
|
+
<Route path="/" element={<Navigate to="/resources" replace />} />
|
|
25
|
+
<Route path="/login" element={<Login />} />
|
|
26
|
+
<Route path="/register" element={<Register />} />
|
|
27
|
+
<Route path="/resources" element={<Resources />} />
|
|
28
|
+
<Route path="/resources/:id" element={<ResourceDetails />} />
|
|
29
|
+
|
|
30
|
+
{/* Protected Routes (JWT required) */}
|
|
31
|
+
<Route
|
|
32
|
+
path="/dashboard"
|
|
33
|
+
element={
|
|
34
|
+
<ProtectedRoute>
|
|
35
|
+
<Dashboard />
|
|
36
|
+
</ProtectedRoute>
|
|
37
|
+
}
|
|
38
|
+
/>
|
|
39
|
+
<Route
|
|
40
|
+
path="/profile"
|
|
41
|
+
element={
|
|
42
|
+
<ProtectedRoute>
|
|
43
|
+
<Profile />
|
|
44
|
+
</ProtectedRoute>
|
|
45
|
+
}
|
|
46
|
+
/>
|
|
47
|
+
<Route
|
|
48
|
+
path="/resources/new"
|
|
49
|
+
element={
|
|
50
|
+
<ProtectedRoute>
|
|
51
|
+
<CreateResource />
|
|
52
|
+
</ProtectedRoute>
|
|
53
|
+
}
|
|
54
|
+
/>
|
|
55
|
+
<Route
|
|
56
|
+
path="/resources/:id/edit"
|
|
57
|
+
element={
|
|
58
|
+
<ProtectedRoute>
|
|
59
|
+
<EditResource />
|
|
60
|
+
</ProtectedRoute>
|
|
61
|
+
}
|
|
62
|
+
/>
|
|
63
|
+
|
|
64
|
+
{/* 404 Catch All */}
|
|
65
|
+
<Route path="*" element={<NotFound />} />
|
|
66
|
+
</Routes>
|
|
67
|
+
</main>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default App;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
const Loading = ({ message = 'Loading...' }) => {
|
|
4
|
+
return (
|
|
5
|
+
<div className="spinner-container">
|
|
6
|
+
<div style={{ textAlign: 'center' }}>
|
|
7
|
+
<div className="spinner" style={{ margin: '0 auto 1rem auto' }}></div>
|
|
8
|
+
<p style={{ color: 'var(--text-secondary)', fontSize: '0.9rem' }}>{message}</p>
|
|
9
|
+
</div>
|
|
10
|
+
</div>
|
|
11
|
+
);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default Loading;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Link, NavLink, useNavigate } from 'react-router-dom';
|
|
3
|
+
import { useAuth } from '../context/AuthContext';
|
|
4
|
+
|
|
5
|
+
const Navbar = () => {
|
|
6
|
+
const { user, isAuthenticated, logout } = useAuth();
|
|
7
|
+
const navigate = useNavigate();
|
|
8
|
+
|
|
9
|
+
const handleLogout = () => {
|
|
10
|
+
logout();
|
|
11
|
+
navigate('/login');
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<nav className="navbar">
|
|
16
|
+
<Link to="/" className="nav-brand">
|
|
17
|
+
🚀 MERN App
|
|
18
|
+
</Link>
|
|
19
|
+
|
|
20
|
+
<div className="nav-links">
|
|
21
|
+
<NavLink to="/resources">Resources</NavLink>
|
|
22
|
+
|
|
23
|
+
{isAuthenticated ? (
|
|
24
|
+
<>
|
|
25
|
+
<NavLink to="/dashboard">Dashboard</NavLink>
|
|
26
|
+
<NavLink to="/resources/new">Create Resource</NavLink>
|
|
27
|
+
<NavLink to="/profile">Profile ({user?.name})</NavLink>
|
|
28
|
+
<button
|
|
29
|
+
onClick={handleLogout}
|
|
30
|
+
className="btn btn-secondary btn-sm"
|
|
31
|
+
style={{ marginLeft: '0.5rem' }}
|
|
32
|
+
>
|
|
33
|
+
Logout
|
|
34
|
+
</button>
|
|
35
|
+
</>
|
|
36
|
+
) : (
|
|
37
|
+
<>
|
|
38
|
+
<NavLink to="/login">Login</NavLink>
|
|
39
|
+
<NavLink to="/register" className="btn btn-primary btn-sm">
|
|
40
|
+
Register
|
|
41
|
+
</NavLink>
|
|
42
|
+
</>
|
|
43
|
+
)}
|
|
44
|
+
|
|
45
|
+
<a
|
|
46
|
+
href="http://localhost:5000/admin/login"
|
|
47
|
+
target="_blank"
|
|
48
|
+
rel="noreferrer"
|
|
49
|
+
style={{ fontSize: '0.8rem', color: '#a5b4fc', borderLeft: '1px solid #334155', paddingLeft: '0.75rem' }}
|
|
50
|
+
>
|
|
51
|
+
Admin Portal (EJS) →
|
|
52
|
+
</a>
|
|
53
|
+
</div>
|
|
54
|
+
</nav>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default Navbar;
|