quar 1.2.0 → 1.2.2
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 +3 -1
- package/package.json +3 -2
- package/public/scripts/insertTab.js +53 -1
- package/public/scripts/main.js +12 -11
- package/public/styles/insertTab.css +19 -2
- package/server.js +2 -1
- package/utils/loadModels.js +2 -0
- package/views/pages/index.zare +2 -2
- package/models/user.model.js +0 -71
- package/seeds/user.seed.js +0 -68
package/index.js
CHANGED
|
@@ -51,9 +51,11 @@ try {
|
|
|
51
51
|
(async () => {
|
|
52
52
|
|
|
53
53
|
await mongoose.connect(`${args.uri || "mongodb://localhost:27017/"}${args.db}`)
|
|
54
|
+
console.log( chalk.blue.bold('[INFO]'), chalk.gray("Quar: Database connection established"))
|
|
55
|
+
|
|
54
56
|
app.listen(app.get('port'), () => {
|
|
55
57
|
const url = `http://127.0.0.1:${app.get('port')}`;
|
|
56
|
-
console.log(chalk.green.bold('[SUCCESS]') + ` Server is running on ${chalk.underline(url)}`);
|
|
58
|
+
console.log(chalk.green.bold('[SUCCESS]') + chalk.gray(` Server is running on ${chalk.underline(url)}`));
|
|
57
59
|
open(url);
|
|
58
60
|
});
|
|
59
61
|
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quar",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "This will load all Mongoose models from the folder and start a local web UI to Create, view, update, and delete documents.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"quar": "./index.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"start": "node index.js"
|
|
11
|
+
"start": "node index.js",
|
|
12
|
+
"dev": "node .\\index.js --model .\\models\\ --db shopDB"
|
|
12
13
|
},
|
|
13
14
|
"author": "@IsmailBinMujeeb (IsmailBinMujeeb@gmail.com)",
|
|
14
15
|
"license": "MIT",
|
|
@@ -25,6 +25,18 @@ async function toggleInsertTab() {
|
|
|
25
25
|
const formData = new FormData(e.target);
|
|
26
26
|
const data = Object.fromEntries(formData.entries());
|
|
27
27
|
|
|
28
|
+
const checkboxes = e.target.querySelectorAll('input[type="checkbox"]');
|
|
29
|
+
checkboxes.forEach(cb => {
|
|
30
|
+
data[cb.name] = cb.checked;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const textareas = e.target.querySelectorAll('textarea');
|
|
34
|
+
textareas.forEach(async tarea => {
|
|
35
|
+
|
|
36
|
+
data[tarea.name] = JSON.parse(tarea.value)
|
|
37
|
+
|
|
38
|
+
})
|
|
39
|
+
|
|
28
40
|
const res = await fetch(`/insert/${modelName}`, {
|
|
29
41
|
method: 'POST',
|
|
30
42
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -40,8 +52,12 @@ async function toggleInsertTab() {
|
|
|
40
52
|
loadDocuments();
|
|
41
53
|
});
|
|
42
54
|
|
|
55
|
+
|
|
43
56
|
for (const key in schema) {
|
|
57
|
+
|
|
58
|
+
if (/\.\$\*$/.test(key)) continue;
|
|
44
59
|
const field = schema[key];
|
|
60
|
+
let inputType = null;
|
|
45
61
|
|
|
46
62
|
const label = document.createElement('label');
|
|
47
63
|
label.innerHTML = `${key}:`;
|
|
@@ -73,8 +89,44 @@ async function toggleInsertTab() {
|
|
|
73
89
|
continue;
|
|
74
90
|
}
|
|
75
91
|
|
|
92
|
+
if (field.type === 'String') inputType = 'text';
|
|
93
|
+
else if (field.type === 'Number') inputType = 'number';
|
|
94
|
+
else if (field.type === 'Boolean') inputType = 'checkbox';
|
|
95
|
+
else if (field.type === 'Date') inputType = 'date';
|
|
96
|
+
else if (field.type === 'Array' || field.type === "Map" || field.type === "Object" || field.type == "Mixed" || field.type === "Buffer") {
|
|
97
|
+
const input = document.createElement('textarea');
|
|
98
|
+
|
|
99
|
+
input.placeholder = field.type;
|
|
100
|
+
input.id = key;
|
|
101
|
+
input.name = key;
|
|
102
|
+
input.className = 'input';
|
|
103
|
+
field.type === "Array" ? input.textContent = "[ ]" : input.textContent = "{ }";
|
|
104
|
+
input.required = isRequired(field.require);
|
|
105
|
+
if (field.default !== undefined) input.value = field.default;
|
|
106
|
+
|
|
107
|
+
form.append(label, input);
|
|
108
|
+
continue;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (field.enum) {
|
|
112
|
+
const select = document.createElement('select');
|
|
113
|
+
select.id = key;
|
|
114
|
+
select.name = key;
|
|
115
|
+
select.className = 'input';
|
|
116
|
+
|
|
117
|
+
field.enum.forEach(id => {
|
|
118
|
+
const option = document.createElement('option');
|
|
119
|
+
option.value = id;
|
|
120
|
+
option.innerText = id;
|
|
121
|
+
select.appendChild(option);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
form.append(label, select);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
76
128
|
const input = document.createElement('input');
|
|
77
|
-
input.type =
|
|
129
|
+
input.type = inputType || 'text';
|
|
78
130
|
input.placeholder = field.type;
|
|
79
131
|
input.id = key;
|
|
80
132
|
input.name = key;
|
package/public/scripts/main.js
CHANGED
|
@@ -35,6 +35,8 @@ async function activateTab(modelName) {
|
|
|
35
35
|
|
|
36
36
|
content.dataset.modelName = modelName;
|
|
37
37
|
document.getElementById("page-value").innerText = 1;
|
|
38
|
+
document.querySelectorAll(".operations .btn")?.forEach( btn => btn.disabled = false )
|
|
39
|
+
document.querySelector(".operations select").disabled = false;
|
|
38
40
|
|
|
39
41
|
const insertTab = document.querySelector('.insert-tab');
|
|
40
42
|
insertTab.classList.remove('active');
|
|
@@ -62,9 +64,18 @@ function closeTab(event, modelName) {
|
|
|
62
64
|
const keys = Object.keys(openTabs);
|
|
63
65
|
const modelName = keys[keys.length - 1] || null;
|
|
64
66
|
if (modelName) activateTab(modelName);
|
|
67
|
+
else disableAllOptions();
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
|
|
71
|
+
function disableAllOptions() {
|
|
72
|
+
content.dataset.modelName = '';
|
|
73
|
+
|
|
74
|
+
document.querySelectorAll(".operations .btn")?.forEach( btn => btn.disabled = true );
|
|
75
|
+
document.getElementById("document-count").innerText = 0;
|
|
76
|
+
document.querySelector(".operations select").disabled = true;
|
|
77
|
+
}
|
|
78
|
+
|
|
68
79
|
async function loadDocuments() {
|
|
69
80
|
|
|
70
81
|
content.innerHTML = "";
|
|
@@ -100,12 +111,9 @@ async function loadDocuments() {
|
|
|
100
111
|
data.documents.forEach((doc, index) => {
|
|
101
112
|
const wrapper = document.createElement('div');
|
|
102
113
|
const divDocument = document.createElement('div');
|
|
103
|
-
wrapper.innerHTML += `<div class="top-container"><button class="editDocument" onclick="toggleEditMode('${doc._id}')">Edit</button></div>`;
|
|
104
114
|
divDocument.innerHTML += Object.entries(doc).map(([key, value]) => renderData(key, value, index)).join('');
|
|
105
|
-
wrapper.innerHTML += `<textarea class="json-data hidden" id="${doc._id}-json-data" oninput="autoResize(this)">${JSON.stringify(doc, null, 2)}</textarea>`
|
|
106
115
|
|
|
107
116
|
divDocument.classList.add('document');
|
|
108
|
-
divDocument.id = `${doc._id}-html-data`;
|
|
109
117
|
|
|
110
118
|
wrapper.appendChild(divDocument);
|
|
111
119
|
wrapper.innerHTML += `<div class="document-actions"><button class="updateDocument" onclick="updateDocument(${index})">Update</button><button class="deleteDocument" onclick="deleteDoc('${modelName}', '${doc._id}')">Delete</button></div>`;
|
|
@@ -191,7 +199,6 @@ document.addEventListener('click', function (e) {
|
|
|
191
199
|
function setNestedValue(obj, path, value, parentDataType) {
|
|
192
200
|
const keys = path.split(".");
|
|
193
201
|
let current = obj;
|
|
194
|
-
console.log(value)
|
|
195
202
|
|
|
196
203
|
keys.forEach((key, index) => {
|
|
197
204
|
if (index === keys.length - 1) {
|
|
@@ -243,8 +250,6 @@ async function updateDocument(index) {
|
|
|
243
250
|
|
|
244
251
|
updatedDoc = { ...updatedDoc, ...updatedDoc[""] }
|
|
245
252
|
|
|
246
|
-
console.log(updatedDoc)
|
|
247
|
-
|
|
248
253
|
const res = await fetch(`/update/${currentModelName}/${id}`, {
|
|
249
254
|
method: "PUT",
|
|
250
255
|
headers: { "Content-Type": "application/json" },
|
|
@@ -273,6 +278,7 @@ async function deleteDoc(modelName, id) {
|
|
|
273
278
|
openModel(modelName);
|
|
274
279
|
const data = await res.json();
|
|
275
280
|
document.getElementById(`${modelName}-doc-count`).innerText = data.count || 0;
|
|
281
|
+
loadDocuments()
|
|
276
282
|
} else {
|
|
277
283
|
showModal('error', 'Delete Failed', await res.json().error || 'Delete failed, please try again.');
|
|
278
284
|
}
|
|
@@ -356,9 +362,4 @@ async function refreshSideBar() {
|
|
|
356
362
|
} catch (error) {
|
|
357
363
|
showModal('error', 'Error Occurred!', error.message || 'Something went wrong, please try again.');
|
|
358
364
|
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function autoResize(textarea) {
|
|
362
|
-
textarea.style.height = 'auto'; // Reset the height
|
|
363
|
-
textarea.style.height = textarea.scrollHeight + 'px'; // Set to scrollHeight
|
|
364
365
|
}
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
color: var(--text-color);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
#insert-form input,
|
|
34
|
+
#insert-form input,
|
|
35
|
+
select,
|
|
36
|
+
textarea {
|
|
35
37
|
width: 100%;
|
|
36
38
|
padding: 10px;
|
|
37
39
|
border: 1px solid var(--muted-color);
|
|
@@ -40,6 +42,21 @@
|
|
|
40
42
|
color: var(--text-color);
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
#insert-form textarea {
|
|
46
|
+
resize: vertical;
|
|
47
|
+
min-height: fit-content;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#insert-form input[type="checkbox"] {
|
|
51
|
+
width: 16px;
|
|
52
|
+
height: 16px;
|
|
53
|
+
accent-color: var(--green-color);
|
|
54
|
+
background-color: var(--bg-color);
|
|
55
|
+
border: 1px solid var(--muted-color);
|
|
56
|
+
border-radius: 10px;
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
}
|
|
59
|
+
|
|
43
60
|
#insert-form .btn {
|
|
44
61
|
background: transparent;
|
|
45
62
|
color: var(--text-color);
|
|
@@ -54,4 +71,4 @@
|
|
|
54
71
|
background: var(--blue-color);
|
|
55
72
|
color: var(--bg-color);
|
|
56
73
|
}
|
|
57
|
-
}
|
|
74
|
+
}
|
package/server.js
CHANGED
|
@@ -2,7 +2,7 @@ import express from 'express';
|
|
|
2
2
|
import mongoose from 'mongoose';
|
|
3
3
|
import path from "path"
|
|
4
4
|
import loadModels from './utils/loadModels.js';
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
const app = express();
|
|
7
7
|
const PORT = 8319;
|
|
8
8
|
|
|
@@ -108,6 +108,7 @@ app.get("/schema/:modelName", async (req, res) => {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
getSchema(paths)
|
|
111
|
+
|
|
111
112
|
res.status(200).json(schema);
|
|
112
113
|
} catch (error) {
|
|
113
114
|
res.status(500).json({ error: error.message || "Intenral Server Error" })
|
package/utils/loadModels.js
CHANGED
package/views/pages/index.zare
CHANGED
|
@@ -37,12 +37,12 @@ serve (
|
|
|
37
37
|
<div class="text">Count</div>
|
|
38
38
|
<div class="count-div" id="document-count">0</div>
|
|
39
39
|
</div>
|
|
40
|
-
<button class="btn" id="previous-page" onclick="gotoPreviousPage()">Previous</button>
|
|
40
|
+
<button class="btn" id="previous-page" onclick="gotoPreviousPage()" disabled>Previous</button>
|
|
41
41
|
<div class="page-wrapper" id="page" data-page="1">
|
|
42
42
|
<div class="text">Page</div>
|
|
43
43
|
<div class="page-div" id="page-value">1</div>
|
|
44
44
|
</div>
|
|
45
|
-
<button class="btn" id="next-page" onclick="gotoNextPage()">Next</button>
|
|
45
|
+
<button class="btn" id="next-page" onclick="gotoNextPage()" disabled>Next</button>
|
|
46
46
|
<button class="btn" onclick="toggleInsertTab()">Add Record</button>
|
|
47
47
|
<button class="btn" onclick="deleteAllDocs()">🗑</button>
|
|
48
48
|
</div>
|
package/models/user.model.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import mongoose from 'mongoose';
|
|
2
|
-
|
|
3
|
-
const PlanetSchema = new mongoose.Schema({
|
|
4
|
-
name: String,
|
|
5
|
-
description: String,
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
const userSchema = new mongoose.Schema({
|
|
9
|
-
// String types
|
|
10
|
-
name: String,
|
|
11
|
-
email: { type: String, required: true, unique: true },
|
|
12
|
-
password: { type: String, required: true },
|
|
13
|
-
|
|
14
|
-
// Number types
|
|
15
|
-
age: Number,
|
|
16
|
-
score: { type: Number, default: 0 },
|
|
17
|
-
|
|
18
|
-
// Date types
|
|
19
|
-
birthDate: Date,
|
|
20
|
-
createdAt: { type: Date, default: Date.now },
|
|
21
|
-
|
|
22
|
-
// Boolean type
|
|
23
|
-
isActive: { type: Boolean, default: true },
|
|
24
|
-
|
|
25
|
-
// Array types
|
|
26
|
-
interests: [String],
|
|
27
|
-
scores: [Number],
|
|
28
|
-
|
|
29
|
-
planet: { type: PlanetSchema, required: true },
|
|
30
|
-
|
|
31
|
-
// Nested object
|
|
32
|
-
address: {
|
|
33
|
-
street: String,
|
|
34
|
-
city: String,
|
|
35
|
-
country: String,
|
|
36
|
-
zipCode: String
|
|
37
|
-
},
|
|
38
|
-
|
|
39
|
-
// ObjectId reference
|
|
40
|
-
friends: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
|
|
41
|
-
|
|
42
|
-
// Mixed type (any data)
|
|
43
|
-
additionalInfo: mongoose.Schema.Types.Mixed,
|
|
44
|
-
|
|
45
|
-
// Buffer type (for binary data)
|
|
46
|
-
profilePicture: Buffer,
|
|
47
|
-
|
|
48
|
-
// Map type
|
|
49
|
-
metadata: {
|
|
50
|
-
type: Map,
|
|
51
|
-
of: String
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// Add indexes
|
|
56
|
-
userSchema.index({ email: 1 });
|
|
57
|
-
userSchema.index({ name: 1, age: -1 });
|
|
58
|
-
|
|
59
|
-
// Add a virtual property
|
|
60
|
-
userSchema.virtual('fullName').get(function () {
|
|
61
|
-
return `${this.firstName} ${this.lastName}`;
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// Add an instance method
|
|
65
|
-
userSchema.methods.getAge = function () {
|
|
66
|
-
return Math.floor((Date.now() - this.birthDate.getTime()) / 31557600000);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const User = mongoose.model('User', userSchema);
|
|
70
|
-
|
|
71
|
-
export default User;
|
package/seeds/user.seed.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import User from '../models/user.model.js';
|
|
2
|
-
|
|
3
|
-
;(async function seedUsers() {
|
|
4
|
-
try {
|
|
5
|
-
// Clear existing users
|
|
6
|
-
await User.deleteMany({});
|
|
7
|
-
|
|
8
|
-
// Create two users
|
|
9
|
-
const user1 = await User.create({
|
|
10
|
-
firstName: 'John',
|
|
11
|
-
lastName: 'Doe',
|
|
12
|
-
email: 'john.doe@example.com',
|
|
13
|
-
password: 'password123',
|
|
14
|
-
birthDate: new Date('1990-01-15'),
|
|
15
|
-
isActive: true,
|
|
16
|
-
interests: ['reading', 'hiking', 'photography'],
|
|
17
|
-
scores: [85, 92, 78],
|
|
18
|
-
planet: {
|
|
19
|
-
name: 'Earth',
|
|
20
|
-
description: 'Our home planet'
|
|
21
|
-
},
|
|
22
|
-
address: {
|
|
23
|
-
street: '123 Main St',
|
|
24
|
-
city: 'Boston',
|
|
25
|
-
country: 'USA',
|
|
26
|
-
zipCode: '02108'
|
|
27
|
-
},
|
|
28
|
-
metadata: new Map([
|
|
29
|
-
['occupation', 'Software Engineer'],
|
|
30
|
-
['department', 'Engineering']
|
|
31
|
-
])
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const user2 = await User.create({
|
|
35
|
-
firstName: 'Jane',
|
|
36
|
-
lastName: 'Smith',
|
|
37
|
-
email: 'jane.smith@example.com',
|
|
38
|
-
password: 'password456',
|
|
39
|
-
birthDate: new Date('1988-06-22'),
|
|
40
|
-
isActive: true,
|
|
41
|
-
interests: ['painting', 'music', 'travel'],
|
|
42
|
-
scores: [95, 88, 90],
|
|
43
|
-
planet: {
|
|
44
|
-
name: 'Mars',
|
|
45
|
-
description: 'The red planet'
|
|
46
|
-
},
|
|
47
|
-
address: {
|
|
48
|
-
street: '456 Park Ave',
|
|
49
|
-
city: 'New York',
|
|
50
|
-
country: 'USA',
|
|
51
|
-
zipCode: '10022'
|
|
52
|
-
},
|
|
53
|
-
metadata: new Map([
|
|
54
|
-
['occupation', 'Product Manager'],
|
|
55
|
-
['department', 'Product']
|
|
56
|
-
])
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
console.log('Users seeded successfully');
|
|
60
|
-
return [user1, user2];
|
|
61
|
-
} catch (error) {
|
|
62
|
-
console.error('Error seeding users:', error);
|
|
63
|
-
throw error;
|
|
64
|
-
}
|
|
65
|
-
})();
|
|
66
|
-
|
|
67
|
-
export default 1;
|
|
68
|
-
|