Skip to content

Commit 1b9ec04

Browse files
Merge pull request #3 from yash-pouranik/feat/byod-core
Key Changes 1. Smart Connection Manager 2. Dynamic Model Injection 3. Refactored Data Controller 4. Security & Fault Tolerance 5. And an Updated UI
2 parents 4c1c428 + 9df1d42 commit 1b9ec04

27 files changed

Lines changed: 2170 additions & 968 deletions

‎backend/.gitignore‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules
2-
.env
2+
.env
3+
notes.txt

‎backend/app.js‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const mongoose = require('mongoose')
33
const cors = require('cors')
44
const dotenv = require('dotenv');
55
const app = express();
6+
const GC = require('./utils/GC');
67
dotenv.config();
78

89
// Middleware
@@ -53,16 +54,22 @@ app.get('/', (req, res) => {
5354
res.status(200).json({ status: "success", message: "urBackend API is running 🚀" })
5455
});
5556

56-
// 🛡️ FAULT TOLERANCE: Global Error Handler
57+
// FAULT TOLERANCE: Global Error Handler
5758
app.use((err, req, res, next) => {
59+
if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
60+
return res.status(400).json({
61+
error: "Invalid JSON format",
62+
message: "Check your request body syntax. Stray characters outside the JSON object are not allowed."
63+
});
64+
}
65+
5866
console.error("🔥 Unhandled Error:", err.stack);
5967
res.status(500).json({
6068
error: "Something went wrong!",
6169
message: err.message
6270
});
6371
});
64-
65-
// 🛡️ DB CONNECTION & SERVER START
72+
// DB CONNECTION & SERVER START
6673
// (Only connect if NOT in Test Mode)
6774
if (process.env.NODE_ENV !== 'test') {
6875

Lines changed: 78 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,236 +1,166 @@
1-
const { sanitize } = require("../utils/input.validation")
2-
const { ObjectId } = require('mongodb');
1+
const { sanitize } = require("../utils/input.validation");
32
const mongoose = require('mongoose');
43
const Project = require("../models/Project");
4+
const { getConnection } = require("../utils/connectionManager");
5+
const { getCompiledModel } = require("../utils/injectModel");
56

7+
// 1. INSERT DATA
68
module.exports.insertData = async (req, res) => {
79
try {
810
const { collectionName } = req.params;
9-
const project = req.project;
10-
11-
// Latest Project Data fetch karein (taaki accurate usage mile)
12-
const currentProject = await Project.findById(project._id);
11+
const project = req.project; // Middleware se mila project metadata
1312

14-
const collectionConfig = currentProject.collections.find(c => c.name === collectionName);
15-
if (!collectionConfig) {
16-
return res.status(404).json({
17-
error: "Collection not found",
18-
collection: collectionName
19-
});
20-
}
13+
const collectionConfig = project.collections.find(c => c.name === collectionName);
14+
if (!collectionConfig) return res.status(404).json({ error: "Collection not found" });
2115

2216
const schemaRules = collectionConfig.model;
2317
const incomingData = req.body;
2418
const cleanData = {};
2519

26-
// --- VALIDATION & SANITIZATION ---
20+
// Validation & Sanitization
2721
for (const field of schemaRules) {
2822
if (field.required && incomingData[field.key] === undefined) {
29-
return res.status(400).json({
30-
error: `Field '${field.key}' is required.`,
31-
field: field.key
32-
});
23+
return res.status(400).json({ error: `Field '${field.key}' is required.` });
3324
}
3425
if (incomingData[field.key] !== undefined) {
35-
// Type checking logic (same as before)...
26+
// Type Checks
3627
if (field.type === 'Number' && typeof incomingData[field.key] !== 'number') return res.status(400).send(`Field '${field.key}' must be a Number.`);
3728
if (field.type === 'Boolean' && typeof incomingData[field.key] !== 'boolean') return res.status(400).send(`Field '${field.key}' must be a Boolean.`);
38-
3929
cleanData[field.key] = incomingData[field.key];
4030
}
4131
}
4232

4333
const safeData = sanitize(cleanData);
4434
Object.assign(cleanData, safeData);
4535

46-
// CHECK DB LIMIT ---
47-
// Calculate approx size in bytes
48-
const docSize = Buffer.byteLength(JSON.stringify(cleanData));
49-
50-
if (currentProject.databaseUsed + docSize > currentProject.databaseLimit) {
51-
return res.status(403).send("Database limit exceeded. Delete some data to free up space.");
36+
// Usage Limit Check (Internal Only)
37+
let docSize = 0;
38+
if (!project.isExternal) {
39+
docSize = Buffer.byteLength(JSON.stringify(cleanData));
40+
if ((project.databaseUsed || 0) + docSize > project.databaseLimit) {
41+
return res.status(403).send("Database limit exceeded.");
42+
}
5243
}
5344

54-
// Insert Data
55-
const finalCollectionName = `${project._id}_${collectionName}`;
56-
const collection = mongoose.connection.db.collection(finalCollectionName);
57-
const result = await collection.insertOne(cleanData);
45+
// Get Connection & Model
46+
const connection = await getConnection(project._id);
47+
const Model = getCompiledModel(connection, collectionConfig, project._id, project.isExternal);
5848

59-
// UPDATE USAGE ---
60-
currentProject.databaseUsed += docSize;
61-
await currentProject.save();
49+
const result = await Model.create(cleanData);
6250

63-
res.status(201).json({
64-
message: "Data inserted successfully",
65-
insertedId: result.insertedId,
66-
data: cleanData
67-
});
51+
// Update Project Metadata (Internal Only)
52+
if (!project.isExternal) {
53+
project.databaseUsed = (project.databaseUsed || 0) + docSize;
54+
await project.save();
55+
}
6856

57+
res.status(201).json(result);
6958
} catch (err) {
70-
console.log(err)
71-
res.status(500).json({ error: 'Some issue at our end' });
59+
console.error("Insert Error:", err);
60+
res.status(500).json({ error: err.message });
7261
}
73-
}
74-
62+
};
7563

64+
// 2. GET ALL DATA
7665
module.exports.getAllData = async (req, res) => {
7766
try {
7867
const { collectionName } = req.params;
7968
const project = req.project;
8069

8170
const collectionConfig = project.collections.find(c => c.name === collectionName);
82-
if (!collectionConfig) {
83-
return res.status(404).json({
84-
error: "Collection not found",
85-
collection: collectionName
86-
});
87-
}
88-
const finalCollectionName = `${project._id}_${collectionName}`;
71+
if (!collectionConfig) return res.status(404).json({ error: "Collection not found" });
8972

90-
const data = await mongoose.connection.db
91-
.collection(finalCollectionName)
92-
.find({})
93-
.toArray();
73+
const connection = await getConnection(project._id);
74+
const Model = getCompiledModel(connection, collectionConfig, project._id, project.isExternal);
9475

76+
const data = await Model.find({}).limit(100);
9577
res.json(data);
96-
9778
} catch (err) {
98-
console.log(err)
99-
res.status(500).json({ error: 'Some issue at our end' });
79+
console.log("error----------------")
80+
res.status(500).json({ error: err.message });
10081
}
101-
}
82+
};
10283

84+
// 3. GET SINGLE DOC
10385
module.exports.getSingleDoc = async (req, res) => {
10486
try {
10587
const { collectionName, id } = req.params;
10688
const project = req.project;
10789

108-
// Security Check
10990
const collectionConfig = project.collections.find(c => c.name === collectionName);
110-
if (!collectionConfig) {
111-
return res.status(404).json({
112-
error: "Collection not found",
113-
collection: collectionName
114-
});
115-
}
116-
117-
const finalCollectionName = `${project._id}_${collectionName}`;
91+
if (!collectionConfig) return res.status(404).json({ error: "Collection not found" });
11892

119-
// Find document by _id
120-
const doc = await mongoose.connection.db
121-
.collection(finalCollectionName)
122-
.findOne({ _id: new ObjectId(id) }); // ID Conversion is important
93+
const connection = await getConnection(project._id);
94+
const Model = getCompiledModel(connection, collectionConfig, project._id, project.isExternal);
12395

124-
if (!doc) {
125-
return res.status(404).send("Document not found.");
126-
}
96+
const doc = await Model.findById(id);
97+
if (!doc) return res.status(404).send("Document not found.");
12798

12899
res.json(doc);
129-
130100
} catch (err) {
131-
console.log(err)
132-
res.status(500).json({ error: 'Some issue at our end' });
101+
res.status(500).json({ error: err.message });
133102
}
134-
}
135-
136-
137-
103+
};
138104

105+
// 4. UPDATE DATA
139106
module.exports.updateSingleData = async (req, res) => {
140107
try {
141108
const { collectionName, id } = req.params;
142109
const project = req.project;
143-
const incomingData = req.body; // Naya data
110+
const incomingData = req.body;
144111

145-
// Security & Config Find
146112
const collectionConfig = project.collections.find(c => c.name === collectionName);
147-
if (!collectionConfig) {
148-
return res.status(404).json({
149-
error: "Collection not found",
150-
collection: collectionName
151-
});
152-
}
153-
// --- VALIDATION REPEAT (Important) ---
113+
if (!collectionConfig) return res.status(404).json({ error: "Collection not found" });
114+
115+
const connection = await getConnection(project._id);
116+
const Model = getCompiledModel(connection, collectionConfig, project._id, project.isExternal);
117+
118+
// Basic Schema Validation
154119
const schemaRules = collectionConfig.model;
155120
for (const key in incomingData) {
156-
// 1. Check if field exists in schema
157121
const fieldRule = schemaRules.find(f => f.key === key);
158-
if (!fieldRule) {
159-
160-
// Not Found
161-
return res.status(400).send(`Field '${key}' is not defined in the schema.`);
162-
}
163-
164-
// Check Type
165-
if (fieldRule.type === 'Number' && typeof incomingData[key] !== 'number') {
166-
return res.status(400).send(`Field '${key}' must be a Number.`);
167-
}
168-
if (fieldRule.type === 'Boolean' && typeof incomingData[key] !== 'boolean') {
169-
return res.status(400).send(`Field '${key}' must be a Boolean.`);
170-
}
171-
// String check is loose in JS, usually fine.
122+
if (!fieldRule) return res.status(400).send(`Field '${key}' not in schema.`);
123+
// Type checks... (Number, Boolean etc.)
172124
}
173125

126+
const result = await Model.findByIdAndUpdate(id, { $set: incomingData }, { new: true });
127+
if (!result) return res.status(404).send("Document not found.");
174128

175-
const safeData = sanitize(incomingData);
176-
incomingData = safeData;
177-
178-
179-
const finalCollectionName = `${project._id}_${collectionName}`;
180-
181-
const result = await mongoose.connection.db
182-
.collection(finalCollectionName)
183-
.updateOne(
184-
{ _id: new ObjectId(id) },
185-
{ $set: incomingData }
186-
);
187-
188-
if (result.matchedCount === 0) {
189-
return res.status(404).send("Document not found to update.");
190-
}
191-
192-
res.json({ message: "Document updated successfully", id, updatedFields: incomingData });
193-
129+
res.json({ message: "Updated", data: result });
194130
} catch (err) {
195-
console.log(err)
196-
res.status(500).json({ error: 'Some issue at our end' });
131+
res.status(500).json({ error: err.message });
197132
}
198-
}
199-
200-
201-
133+
};
202134

135+
// 5. DELETE DATA
203136
module.exports.deleteSingleDoc = async (req, res) => {
204137
try {
205138
const { collectionName, id } = req.params;
206139
const project = req.project;
207140

208-
// Latest Project Fetch
209-
const currentProject = await Project.findById(project._id);
210-
211-
const finalCollectionName = `${project._id}_${collectionName}`;
212-
const collection = mongoose.connection.db.collection(finalCollectionName);
141+
const collectionConfig = project.collections.find(c => c.name === collectionName);
142+
if (!collectionConfig) return res.status(404).json({ error: "Collection not found" });
213143

214-
// First find the document to know size
215-
const docToDelete = await collection.findOne({ _id: new ObjectId(id) });
144+
const connection = await getConnection(project._id);
145+
const Model = getCompiledModel(connection, collectionConfig, project._id, project.isExternal);
216146

147+
const docToDelete = await Model.findById(id);
217148
if (!docToDelete) return res.status(404).send("Document not found.");
218149

219-
// Calculate size to subtract
220-
// It also counts _id field
221-
const docSize = Buffer.byteLength(JSON.stringify(docToDelete));
150+
let docSize = 0;
151+
if (!project.isExternal) {
152+
docSize = Buffer.byteLength(JSON.stringify(docToDelete));
153+
}
222154

223-
// Delete
224-
await collection.deleteOne({ _id: new ObjectId(id) });
155+
await Model.deleteOne({ _id: id });
225156

226-
// Update Usage (Ensure it doesn't go below 0)
227-
currentProject.databaseUsed = Math.max(0, currentProject.databaseUsed - docSize);
228-
await currentProject.save();
157+
if (!project.isExternal) {
158+
project.databaseUsed = Math.max(0, (project.databaseUsed || 0) - docSize);
159+
await project.save();
160+
}
229161

230162
res.json({ message: "Document deleted", id });
231-
232163
} catch (err) {
233-
console.log(err)
234-
res.status(500).json({ error: 'Some issue at our end' });
164+
res.status(500).json({ error: err.message });
235165
}
236-
}
166+
};

0 commit comments

Comments
 (0)