@bhudevswayam/blueprint-engine 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/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // Inside your library: index.js
2
+ const swayamauth = require('./src/routes/auth');
3
+
4
+ module.exports = { swayamauth };
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@bhudevswayam/blueprint-engine",
3
+ "version": "1.0.0",
4
+ "description": "Domain-Driven Architectural Store for Node.js and MongoDB.",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "keywords": ["architecture", "mongodb", "express", "auth", "blueprint"],
10
+ "author": "Swayam Pandya",
11
+ "license": "MIT",
12
+ "dependencies": {
13
+ "bcryptjs": "^3.0.3",
14
+ "express-async-handler": "^1.2.0",
15
+ "jsonwebtoken": "^9.0.3"
16
+ },
17
+ "peerDependencies": {
18
+ "express": "^5.2.1",
19
+ "mongoose": "^9.2.1"
20
+ }
21
+ }
package/readme.md ADDED
@@ -0,0 +1,117 @@
1
+ # blueprint-engine 🏗️
2
+
3
+ **blueprint-engine** is a high-level architectural framework for Node.js and MongoDB. It is designed to eliminate the "90% boilerplate tax" by providing pre-configured, domain-specific backend blueprints.
4
+
5
+ Instead of writing repetitive logic for authentication, multi-tenancy, or industry-specific workflows, you simply inject your Mongoose models into a blueprint and mount the resulting engine.
6
+
7
+ ---
8
+
9
+ ## 🌟 The Vision
10
+ This library acts as a **Domain-Driven Architectural Store**. It allows developers to deploy complex backend structures using standardized "blueprints."
11
+ - **v1.0.x**: Multi-tenant Authentication & Authorization (Current).
12
+
13
+ ---
14
+
15
+ ## 🚀 Features (Auth Blueprint)
16
+ - **Zero-Boilerplate**: Fully automated `/login` and `/register` routes.
17
+ - **Security-as-a-Code**: Built-in Bcrypt hashing and JWT session management.
18
+ - **Dynamic Multi-Tenancy**: Automated `tenantId` generation and logical data isolation.
19
+ - **Inversion of Control**: Pass your own Mongoose models; the engine handles the persistence logic.
20
+
21
+ ---
22
+
23
+ ## 📦 Installation
24
+
25
+ ```bash
26
+ npm install blueprint-engine
27
+ ```
28
+ Note: Ensure you have express and mongoose installed in your host project.
29
+
30
+ ---
31
+
32
+ ## 🛠️ Quick Start (Auth Blueprint)
33
+ ## 1. Define your Models
34
+ Your models can contain any custom fields, but the engine requires email, password, and tenantId to function.
35
+
36
+ ### User Model
37
+
38
+ ``` javascript
39
+ // models/User.js
40
+ const mongoose = require("mongoose");
41
+
42
+ const UserSchema = new mongoose.Schema({
43
+ name: { type: String, required: true },
44
+ email: { type: String, unique: true, required: true },
45
+ password: { type: String, required: true },
46
+ role: { type: String, enum: ["superadmin", "business", "user"], default: "user" },
47
+ phoneNo: { type: Number },
48
+ addressLine1: { type: String },
49
+ addressLine2: { type: String },
50
+ city: { type: String },
51
+ state: { type: String },
52
+ zipCode: { type: Number },
53
+ tenantId: { type: String, required: true },
54
+
55
+ // Optional: quick access to bookings (can also just query Booking collection)
56
+ bookings: [{ type: mongoose.Schema.Types.ObjectId, ref: "Booking" }]
57
+ }, { timestamps: true });
58
+
59
+ module.exports = mongoose.model("User", UserSchema);
60
+ ```
61
+
62
+ ### Tenant Model
63
+
64
+ ```javascript
65
+ // models/User.js
66
+ const mongoose = require('mongoose');
67
+
68
+ const TenantSchema = new mongoose.Schema({
69
+ name: { type: String, required: true },
70
+ tenantId: { type: String },
71
+ meta: { type: Object, default: {} }
72
+ }, { timestamps: true });
73
+
74
+ module.exports = mongoose.model('Tenant', TenantSchema);
75
+
76
+ ```
77
+
78
+ ## 2. Mount the Engine
79
+ Inject your models and your JWT secret into the swayamauth blueprint.
80
+
81
+ ``` JavaScript
82
+ const express = require('express');
83
+ const mongoose = require('mongoose');
84
+ const { swayamauth } = require('blueprint-engine');
85
+
86
+ const User = require('./models/User');
87
+ const Tenant = require('./models/Tenant');
88
+
89
+ const app = express();
90
+ app.use(express.json());
91
+
92
+ // Database Connection
93
+ mongoose.connect('mongodb://localhost:27017/your_db');
94
+
95
+ // Mount the Blueprint - One line handles all Auth logic
96
+ app.use('/api/auth', swayamauth(User, Tenant, 'YOUR_JWT_SECRET_KEY'));
97
+
98
+ app.listen(4000, () => console.log('Engine running on port 4000'));
99
+ ```
100
+ ## 📡 API Reference
101
+ ### POST /register
102
+ Creates a new Tenant and a User account. Automatically hashes the password and generates a unique tenantId.
103
+ ```json
104
+ Body: { "name": "Name",
105
+ "email": "admin@test.com",
106
+ "password": "password123",
107
+ ...customFields
108
+ }
109
+ ```
110
+
111
+ ### POST /login
112
+ Authenticates the user and returns a signed JWT.
113
+ ```json
114
+ Body: { "email": "admin@test.com",
115
+ "password": "password123"
116
+ }
117
+ ```
@@ -0,0 +1,13 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const connectDB = async (mongoUri) => {
4
+ try {
5
+ await mongoose.connect(mongoUri);
6
+ console.log('MongoDB connected');
7
+ } catch (err) {
8
+ console.error(err);
9
+ process.exit(1);
10
+ }
11
+ };
12
+
13
+ module.exports = connectDB;
@@ -0,0 +1,98 @@
1
+ const asyncHandler = require('express-async-handler');
2
+ const bcrypt = require('bcryptjs');
3
+ const jwt = require('jsonwebtoken');
4
+
5
+ /**
6
+ * createHandlers Factory
7
+ * @param {Object} User - The Mongoose User Model passed from the app
8
+ * @param {Object} Tenant - The Mongoose Tenant Model passed from the app
9
+ * @param {String} jwtSecret - The secret key for signing tokens
10
+ */
11
+ const createHandlers = (User, Tenant, jwtSecret) => {
12
+
13
+ // Internal helper for token generation
14
+ const generateToken = (user) => {
15
+ return jwt.sign(
16
+ { id: user._id, role: user.role },
17
+ jwtSecret,
18
+ { expiresIn: '7d' }
19
+ );
20
+ };
21
+
22
+ const register = asyncHandler(async (req, res) => {
23
+ // We capture specific fields and use ...extra for any custom fields the user added
24
+ const { name, email, password, role, ...extra } = req.body;
25
+
26
+ if (!name || !email || !password) {
27
+ res.status(400);
28
+ throw new Error('Missing required fields: name, email, and password are required.');
29
+ }
30
+
31
+ const exists = await User.findOne({ email });
32
+ if (exists) {
33
+ res.status(400);
34
+ throw new Error('User already exists for this email');
35
+ }
36
+
37
+ // Automatic Tenant Creation
38
+ const tenantId = `${name.toLowerCase().replace(/\s+/g, "_")}_${Date.now()}`;
39
+ await Tenant.create({ name, tenantId });
40
+
41
+ // Internal Security Handling (Bcrypt bypass)
42
+ const salt = await bcrypt.genSalt(10);
43
+ const hashed = await bcrypt.hash(password, salt);
44
+
45
+ // Create User with dynamic fields
46
+ const user = await User.create({
47
+ name,
48
+ email,
49
+ password: hashed,
50
+ role: role || 'user',
51
+ tenantId,
52
+ ...extra // This allows the user to pass 'city', 'phoneNo', etc.
53
+ });
54
+
55
+ res.status(201).json({
56
+ id: user._id,
57
+ name: user.name,
58
+ email: user.email,
59
+ role: user.role,
60
+ tenantId: user.tenantId,
61
+ token: generateToken(user)
62
+ });
63
+ });
64
+
65
+ const login = asyncHandler(async (req, res) => {
66
+ const { email, password } = req.body;
67
+
68
+ if (!email || !password) {
69
+ res.status(400);
70
+ throw new Error('Missing fields');
71
+ }
72
+
73
+ const user = await User.findOne({ email });
74
+ if (!user) {
75
+ res.status(401);
76
+ throw new Error('Invalid credentials');
77
+ }
78
+
79
+ const match = await bcrypt.compare(password, user.password);
80
+ if (!match) {
81
+ res.status(401);
82
+ throw new Error('Invalid credentials');
83
+ }
84
+
85
+ res.json({
86
+ id: user._id,
87
+ name: user.name,
88
+ email: user.email,
89
+ role: user.role,
90
+ tenantId: user.tenantId,
91
+ token: generateToken(user)
92
+ });
93
+ });
94
+
95
+ return { register, login };
96
+ };
97
+
98
+ module.exports = { createHandlers };
@@ -0,0 +1,67 @@
1
+ const jwt = require('jsonwebtoken');
2
+ const asyncHandler = require('express-async-handler');
3
+ const User = require('../models/User');
4
+ const Business = require('../models/Business');
5
+
6
+ // Protect routes
7
+ const protect = asyncHandler(async (req, res, next) => {
8
+ const auth = req.headers.authorization;
9
+ console.log(auth);
10
+
11
+ if (!auth || !auth.startsWith('Bearer ')) {
12
+ res.status(401);
13
+ throw new Error('Not authorized, token missing');
14
+ }
15
+
16
+ const token = auth.split(' ')[1];
17
+
18
+ try {
19
+ const decoded = jwt.verify(token, process.env.JWT_SECRET);
20
+
21
+ console.log(decoded);
22
+ // Try finding in User
23
+ let user = await User.findById(decoded.id);
24
+
25
+ // If not found, try Business
26
+ if (!user) {
27
+ user = await Business.findById(decoded.id);
28
+ }
29
+ console.log(user);
30
+
31
+
32
+
33
+ if (!user) {
34
+ res.status(401);
35
+ throw new Error('Not authorized, user is not found');
36
+ }
37
+
38
+ // Allow normal users if tenantId matches OR if superadmin
39
+ if (req.tenantId && user.tenantId !== req.tenantId && user.role !== 'superadmin') {
40
+ res.status(403);
41
+ throw new Error('Tenant mismatch');
42
+ }
43
+
44
+ req.user = user; // attach user to request
45
+ next();
46
+ } catch (err) {
47
+ console.log(err);
48
+
49
+ res.status(401);
50
+ throw new Error('Not authorized, token is this the end invalid');
51
+ }
52
+ });
53
+
54
+ // Role-based authorization middleware
55
+ const authorize = (...roles) => (req, res, next) => {
56
+ if (!req.user) {
57
+ return res.status(401).json({ message: 'Not authenticated' });
58
+ }
59
+
60
+ if (!roles.includes(req.user.role)) {
61
+ return res.status(403).json({ message: 'Forbidden - insufficient role' });
62
+ }
63
+
64
+ next();
65
+ };
66
+
67
+ module.exports = { protect, authorize };
@@ -0,0 +1,10 @@
1
+ const errorHandler = (err, req, res, next) => {
2
+ console.error(err);
3
+ const statusCode = res.statusCode && res.statusCode !== 200 ? res.statusCode : 500;
4
+ res.status(statusCode).json({
5
+ message: err.message || 'Internal Server Error',
6
+ stack: process.env.NODE_ENV === 'production' ? undefined : err.stack
7
+ });
8
+ };
9
+
10
+ module.exports = errorHandler;
@@ -0,0 +1,11 @@
1
+ // middleware/tenant.js
2
+ const setTenant = (req, res, next) => {
3
+ const tenantId = req.header('x-tenant-id');
4
+ if (!tenantId) {
5
+ return res.status(400).json({ message: 'Missing x-tenant-id header' });
6
+ }
7
+ req.tenantId = tenantId;
8
+ next();
9
+ };
10
+
11
+ module.exports = setTenant;
@@ -0,0 +1,9 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const TenantSchema = new mongoose.Schema({
4
+ name: { type: String, required: true },
5
+ tenantId: { type: String },
6
+ meta: { type: Object, default: {} }
7
+ }, { timestamps: true });
8
+
9
+ module.exports = mongoose.model('Tenant', TenantSchema);
@@ -0,0 +1,20 @@
1
+ const mongoose = require("mongoose");
2
+
3
+ const UserSchema = new mongoose.Schema({
4
+ name: { type: String, required: true },
5
+ email: { type: String, unique: true, required: true },
6
+ password: { type: String, required: true },
7
+ role: { type: String, enum: ["superadmin", "business", "user"], default: "user" },
8
+ phoneNo: { type: Number },
9
+ addressLine1: { type: String },
10
+ addressLine2: { type: String },
11
+ city: { type: String },
12
+ state: { type: String },
13
+ zipCode: { type: Number },
14
+ tenantId: { type: String, required: true },
15
+
16
+ // Optional: quick access to bookings (can also just query Booking collection)
17
+ bookings: [{ type: mongoose.Schema.Types.ObjectId, ref: "Booking" }]
18
+ }, { timestamps: true });
19
+
20
+ module.exports = mongoose.model("User", UserSchema);
@@ -0,0 +1,17 @@
1
+ // Inside your library: src/routes/auth.js
2
+ const express = require('express');
3
+ const { createHandlers } = require('../controllers/authController.js');
4
+
5
+ const swayamauth = (User, Tenant, jwtSecret) => {
6
+ const router = express.Router();
7
+
8
+ // We pass the models into the controller factory
9
+ const { register, login } = createHandlers(User, Tenant, jwtSecret);
10
+
11
+ router.post('/register', register);
12
+ router.post('/login', login);
13
+
14
+ return router;
15
+ };
16
+
17
+ module.exports = swayamauth;