quar 1.2.6 → 1.3.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 +3 -1
- package/dummy_models/posts.model.js +52 -0
- package/dummy_models/user.model.js +44 -0
- package/index.js +2 -2
- package/package.json +32 -32
- package/public/images/icon.png +0 -0
- package/public/scripts/main.js +2 -2
- package/public/styles/sidebar.css +34 -0
- package/public/styles/style.css +2 -37
- package/server.js +23 -22
- package/utils/loadModels.js +3 -3
- package/views/base.zare +2 -2
- package/views/components/insertTab.zare +2 -2
- package/views/components/popup.zare +2 -2
- package/views/components/sidebar.zare +12 -0
- package/views/pages/index.zare +8 -15
package/README.md
CHANGED
|
@@ -90,4 +90,6 @@ export default mongoose.model("Post", postSchema);
|
|
|
90
90
|
- Modular and extendable
|
|
91
91
|
|
|
92
92
|
## Contribute
|
|
93
|
-
Pull requests, suggestions, and ideas are welcome!
|
|
93
|
+
Pull requests, suggestions, and ideas are welcome!
|
|
94
|
+
|
|
95
|
+
***Icon: <a href="https://www.flaticon.com/free-icons/thunder" title="thunder icons">Thunder icons created by Hexagon075 - Flaticon</a>***
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
|
|
3
|
+
const postSchema = new mongoose.Schema(
|
|
4
|
+
{
|
|
5
|
+
author: {
|
|
6
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
7
|
+
ref: "User",
|
|
8
|
+
required: true,
|
|
9
|
+
},
|
|
10
|
+
title: {
|
|
11
|
+
type: String,
|
|
12
|
+
required: [true, "Post title is required"],
|
|
13
|
+
trim: true,
|
|
14
|
+
maxlength: [120, "Title cannot exceed 120 characters"],
|
|
15
|
+
},
|
|
16
|
+
content: {
|
|
17
|
+
type: String,
|
|
18
|
+
required: [true, "Post content is required"],
|
|
19
|
+
},
|
|
20
|
+
image: {
|
|
21
|
+
type: String,
|
|
22
|
+
default: null,
|
|
23
|
+
},
|
|
24
|
+
likes: [
|
|
25
|
+
{
|
|
26
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
27
|
+
ref: "User",
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
// comments: [
|
|
31
|
+
// {
|
|
32
|
+
// user: {
|
|
33
|
+
// type: mongoose.Schema.Types.ObjectId,
|
|
34
|
+
// ref: "User",
|
|
35
|
+
// },
|
|
36
|
+
// text: {
|
|
37
|
+
// type: String,
|
|
38
|
+
// required: true,
|
|
39
|
+
// },
|
|
40
|
+
// createdAt: {
|
|
41
|
+
// type: Date,
|
|
42
|
+
// default: Date.now,
|
|
43
|
+
// },
|
|
44
|
+
// },
|
|
45
|
+
// ],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
timestamps: true,
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
export default mongoose.model("Post", postSchema);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import mongoose from "mongoose";
|
|
2
|
+
|
|
3
|
+
const userSchema = new mongoose.Schema(
|
|
4
|
+
{
|
|
5
|
+
username: {
|
|
6
|
+
type: String,
|
|
7
|
+
required: [true, "Username is required"],
|
|
8
|
+
unique: true,
|
|
9
|
+
trim: true,
|
|
10
|
+
minlength: [3, "Username must be at least 3 characters long"],
|
|
11
|
+
},
|
|
12
|
+
email: {
|
|
13
|
+
type: String,
|
|
14
|
+
required: [true, "Email is required"],
|
|
15
|
+
unique: true,
|
|
16
|
+
lowercase: true,
|
|
17
|
+
match: [/.+@.+\..+/, "Please enter a valid email address"],
|
|
18
|
+
},
|
|
19
|
+
password: {
|
|
20
|
+
type: String,
|
|
21
|
+
required: [true, "Password is required"],
|
|
22
|
+
minlength: [6, "Password must be at least 6 characters long"],
|
|
23
|
+
select: false,
|
|
24
|
+
},
|
|
25
|
+
avatar: {
|
|
26
|
+
type: String,
|
|
27
|
+
},
|
|
28
|
+
bio: {
|
|
29
|
+
type: String,
|
|
30
|
+
maxlength: [160, "Bio cannot exceed 160 characters"],
|
|
31
|
+
},
|
|
32
|
+
posts: [
|
|
33
|
+
{
|
|
34
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
35
|
+
ref: "Post",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
timestamps: true,
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
export default mongoose.model("User", userSchema);
|
package/index.js
CHANGED
|
@@ -51,12 +51,12 @@ try {
|
|
|
51
51
|
(async () => {
|
|
52
52
|
|
|
53
53
|
await mongoose.connect(`${args.uri || "mongodb://localhost:27017/"}${args.db}`)
|
|
54
|
-
console.log(
|
|
54
|
+
console.log(chalk.blue.bold('[INFO]'), chalk.gray("Quar: Database connection established"))
|
|
55
55
|
|
|
56
56
|
app.listen(app.get('port'), () => {
|
|
57
57
|
const url = `http://127.0.0.1:${app.get('port')}`;
|
|
58
58
|
console.log(chalk.green.bold('[SUCCESS]') + chalk.gray(` Server is running on ${chalk.underline(url)}`));
|
|
59
|
-
open(url);
|
|
59
|
+
if (args.open) open(url);
|
|
60
60
|
});
|
|
61
61
|
})();
|
|
62
62
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "quar",
|
|
3
|
-
"version": "1.
|
|
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
|
-
"main": "index.js",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"bin": {
|
|
8
|
-
"quar": "./index.js"
|
|
9
|
-
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"start": "node index.js",
|
|
12
|
-
"dev": "node
|
|
13
|
-
},
|
|
14
|
-
"author": "@IsmailBinMujeeb (IsmailBinMujeeb@gmail.com)",
|
|
15
|
-
"license": "MIT",
|
|
16
|
-
"dependencies": {
|
|
17
|
-
"chalk": "^5.4.1",
|
|
18
|
-
"express": "^5.1.0",
|
|
19
|
-
"minimist": "^1.2.8",
|
|
20
|
-
"mongoose": "^8.14.1",
|
|
21
|
-
"open": "^10.1.2",
|
|
22
|
-
"zare": "
|
|
23
|
-
},
|
|
24
|
-
"repository": {
|
|
25
|
-
"type": "git",
|
|
26
|
-
"url": "https://github.com/IsmailBinMujeeb/quar-studio.git"
|
|
27
|
-
},
|
|
28
|
-
"bugs": {
|
|
29
|
-
"url": "https://github.com/IsmailBinMujeeb/quar-studio/issues"
|
|
30
|
-
},
|
|
31
|
-
"homepage": "https://github.com/IsmailBinMujeeb/quar-studio#readme"
|
|
32
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "quar",
|
|
3
|
+
"version": "1.3.0",
|
|
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
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"quar": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js",
|
|
12
|
+
"dev": "node index.js --model ./dummy_models --db quarDB"
|
|
13
|
+
},
|
|
14
|
+
"author": "@IsmailBinMujeeb (IsmailBinMujeeb@gmail.com)",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"chalk": "^5.4.1",
|
|
18
|
+
"express": "^5.1.0",
|
|
19
|
+
"minimist": "^1.2.8",
|
|
20
|
+
"mongoose": "^8.14.1",
|
|
21
|
+
"open": "^10.1.2",
|
|
22
|
+
"zare": "2.6.0"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/IsmailBinMujeeb/quar-studio.git"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/IsmailBinMujeeb/quar-studio/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/IsmailBinMujeeb/quar-studio#readme"
|
|
32
|
+
}
|
package/public/images/icon.png
CHANGED
|
Binary file
|
package/public/scripts/main.js
CHANGED
|
@@ -35,7 +35,7 @@ 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(
|
|
38
|
+
document.querySelectorAll(".operations .btn")?.forEach(btn => btn.disabled = false)
|
|
39
39
|
document.querySelector(".operations select").disabled = false;
|
|
40
40
|
|
|
41
41
|
const insertTab = document.querySelector('.insert-tab');
|
|
@@ -71,7 +71,7 @@ function closeTab(event, modelName) {
|
|
|
71
71
|
function disableAllOptions() {
|
|
72
72
|
content.dataset.modelName = '';
|
|
73
73
|
|
|
74
|
-
document.querySelectorAll(".operations .btn")?.forEach(
|
|
74
|
+
document.querySelectorAll(".operations .btn")?.forEach(btn => btn.disabled = true);
|
|
75
75
|
document.getElementById("document-count").innerText = 0;
|
|
76
76
|
document.querySelector(".operations select").disabled = true;
|
|
77
77
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
.sidebar {
|
|
2
|
+
position: fixed;
|
|
3
|
+
top: 0;
|
|
4
|
+
width: 15vw;
|
|
5
|
+
background: #282b30;
|
|
6
|
+
border-right: 1px solid var(--muted-color);
|
|
7
|
+
height: 100vh;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.sidebar-header {
|
|
11
|
+
font-size: 13px;
|
|
12
|
+
font-weight: 600;
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: space-between;
|
|
16
|
+
background: #1e2124;
|
|
17
|
+
padding: 10px;
|
|
18
|
+
border-bottom: 1px solid var(--muted-color);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.sidebar-header .btn {
|
|
22
|
+
color: #E0E0E0;
|
|
23
|
+
background-color: transparent;
|
|
24
|
+
border: none;
|
|
25
|
+
font-size: 12px;
|
|
26
|
+
padding: 0 4px;
|
|
27
|
+
border-radius: 5px;
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
transition: background 0.1s ease-in-out;
|
|
30
|
+
|
|
31
|
+
&:hover {
|
|
32
|
+
background: #2E3A59;
|
|
33
|
+
}
|
|
34
|
+
}
|
package/public/styles/style.css
CHANGED
|
@@ -14,41 +14,6 @@ body {
|
|
|
14
14
|
color: #E0E0E0;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
.sidebar {
|
|
18
|
-
position: fixed;
|
|
19
|
-
top: 0;
|
|
20
|
-
width: 15vw;
|
|
21
|
-
background: #282b30;
|
|
22
|
-
border-right: 1px solid var(--muted-color);
|
|
23
|
-
height: 100vh;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.sidebar-header {
|
|
27
|
-
font-size: 13px;
|
|
28
|
-
font-weight: 600;
|
|
29
|
-
display: flex;
|
|
30
|
-
align-items: center;
|
|
31
|
-
justify-content: space-between;
|
|
32
|
-
background: #1e2124;
|
|
33
|
-
padding: 10px;
|
|
34
|
-
border-bottom: 1px solid var(--muted-color);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.sidebar-header .btn {
|
|
38
|
-
color: #E0E0E0;
|
|
39
|
-
background-color: transparent;
|
|
40
|
-
border: none;
|
|
41
|
-
font-size: 12px;
|
|
42
|
-
padding: 0 4px;
|
|
43
|
-
border-radius: 5px;
|
|
44
|
-
cursor: pointer;
|
|
45
|
-
transition: background 0.1s ease-in-out;
|
|
46
|
-
|
|
47
|
-
&:hover {
|
|
48
|
-
background: #2E3A59;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
17
|
.model-wrapper {
|
|
53
18
|
padding: 10px;
|
|
54
19
|
}
|
|
@@ -217,7 +182,7 @@ body {
|
|
|
217
182
|
display: none;
|
|
218
183
|
}
|
|
219
184
|
|
|
220
|
-
summary::marker{
|
|
185
|
+
summary::marker {
|
|
221
186
|
color: var(--muted-color);
|
|
222
187
|
font-size: 10px;
|
|
223
188
|
margin-right: 10px;
|
|
@@ -322,7 +287,7 @@ summary::marker{
|
|
|
322
287
|
display: flex;
|
|
323
288
|
justify-content: left;
|
|
324
289
|
width: 100%;
|
|
325
|
-
padding:
|
|
290
|
+
padding: 0 10px;
|
|
326
291
|
}
|
|
327
292
|
|
|
328
293
|
.top-container .editDocument {
|
package/server.js
CHANGED
|
@@ -42,7 +42,7 @@ app.get("/models", async (_, res) => {
|
|
|
42
42
|
loadModels(app.locals.modelPath)
|
|
43
43
|
const modelNames = mongoose.modelNames();
|
|
44
44
|
modelNames.sort((a, b) => a.localeCompare(b));
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
const result = await Promise.all(
|
|
47
47
|
modelNames.map(async name => {
|
|
48
48
|
const model = mongoose.model(name);
|
|
@@ -50,7 +50,7 @@ app.get("/models", async (_, res) => {
|
|
|
50
50
|
return { name, count };
|
|
51
51
|
})
|
|
52
52
|
);
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
res.status(200).json({ models: result });
|
|
55
55
|
} catch (error) {
|
|
56
56
|
res.status(500).json({ error: error.message || "Internal server error" });
|
|
@@ -71,6 +71,7 @@ app.get("/models/:modelName", async (req, res) => {
|
|
|
71
71
|
|
|
72
72
|
res.status(200).json({ documents, schema: modelSchema, totalCount, count, totalPages });
|
|
73
73
|
} catch (error) {
|
|
74
|
+
console.log(error)
|
|
74
75
|
res.status(500).json({ error: error.message || "Internal server error" });
|
|
75
76
|
}
|
|
76
77
|
});
|
|
@@ -138,56 +139,56 @@ app.post("/insert/:modelName", async (req, res) => {
|
|
|
138
139
|
try {
|
|
139
140
|
const { modelName } = req.params;
|
|
140
141
|
const body = req.body;
|
|
141
|
-
|
|
142
|
+
|
|
142
143
|
const Model = mongoose.model(modelName);
|
|
143
|
-
|
|
144
|
+
|
|
144
145
|
if (!Model) return res.status(404).json({ error: "model not found" });
|
|
145
|
-
|
|
146
|
+
|
|
146
147
|
const paths = Model.schema.paths;
|
|
147
|
-
|
|
148
|
+
|
|
148
149
|
function getSchema(paths) {
|
|
149
|
-
|
|
150
|
+
|
|
150
151
|
let schema = {};
|
|
151
152
|
for (const path in paths) {
|
|
152
|
-
|
|
153
|
+
|
|
153
154
|
if (path === '_id' || path === '__v') continue;
|
|
154
|
-
|
|
155
|
+
|
|
155
156
|
if (paths[path].instance === 'Embedded') {
|
|
156
157
|
schema[path] = getSchema(paths[path].schema.paths);
|
|
157
158
|
continue;
|
|
158
159
|
};
|
|
159
|
-
|
|
160
|
+
|
|
160
161
|
schema[path] = paths[path].instance;
|
|
161
162
|
}
|
|
162
|
-
|
|
163
|
+
|
|
163
164
|
return schema;
|
|
164
165
|
}
|
|
165
|
-
|
|
166
|
+
|
|
166
167
|
const schema = getSchema(paths)
|
|
167
|
-
|
|
168
|
+
|
|
168
169
|
function getData(body, schema) {
|
|
169
|
-
|
|
170
|
+
|
|
170
171
|
let data = {};
|
|
171
|
-
|
|
172
|
+
|
|
172
173
|
for (const key in schema) {
|
|
173
174
|
if (typeof schema[key] === 'object' && schema[key] !== null) {
|
|
174
|
-
|
|
175
|
+
|
|
175
176
|
data[key] = getData(body, schema[key]);
|
|
176
177
|
continue;
|
|
177
178
|
}
|
|
178
|
-
|
|
179
|
+
|
|
179
180
|
if (Object.keys(body).includes(key)) {
|
|
180
181
|
data[key] = body[key];
|
|
181
182
|
}
|
|
182
183
|
}
|
|
183
|
-
|
|
184
|
+
|
|
184
185
|
return data;
|
|
185
186
|
}
|
|
186
|
-
|
|
187
|
+
|
|
187
188
|
const data = getData(body, schema)
|
|
188
|
-
|
|
189
|
+
|
|
189
190
|
const result = await Model.create(data)
|
|
190
|
-
|
|
191
|
+
|
|
191
192
|
res.status(200).json(result)
|
|
192
193
|
} catch (error) {
|
|
193
194
|
res.status(500).json({ error: error.message || "Internal server error" })
|
|
@@ -199,7 +200,7 @@ app.put("/update/:modelName/:id", async (req, res) => {
|
|
|
199
200
|
const { modelName, id } = req.params;
|
|
200
201
|
const model = mongoose.model(modelName);
|
|
201
202
|
const updatedDoc = req.body;
|
|
202
|
-
|
|
203
|
+
|
|
203
204
|
delete updatedDoc._id;
|
|
204
205
|
const result = await model.findByIdAndUpdate(id, updatedDoc, { new: true });
|
|
205
206
|
|
package/utils/loadModels.js
CHANGED
|
@@ -8,13 +8,13 @@ export default (modelPath) => {
|
|
|
8
8
|
if (!fs.existsSync(modelPath)) {
|
|
9
9
|
console.log(
|
|
10
10
|
chalk.red.bold('[ERROR]') +
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
' The specified model path does not exist:\n ' +
|
|
12
|
+
chalk.gray(modelPath)
|
|
13
13
|
);
|
|
14
14
|
console.log('Tip: Check the path or create the folder first.');
|
|
15
15
|
process.exit(1);
|
|
16
16
|
}
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
fs.readdirSync(modelPath).forEach(async (file) => {
|
|
19
19
|
if (file.endsWith('.js')) {
|
|
20
20
|
const filePath = path.join(modelPath, file);
|
package/views/base.zare
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "/scripts/keyboardCommands"
|
|
2
2
|
|
|
3
3
|
serve (
|
|
4
4
|
<!DOCTYPE html>
|
|
@@ -7,7 +7,7 @@ serve (
|
|
|
7
7
|
<meta charset="UTF-8"/>
|
|
8
8
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
9
9
|
<title>Quar Studio</title>
|
|
10
|
-
<link rel="
|
|
10
|
+
<link rel="icon" href="/images/icon.png" type="image/x-icon"/>
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
@(slot)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
link "/styles/sidebar"
|
|
2
|
+
|
|
3
|
+
serve (
|
|
4
|
+
<div class="sidebar">
|
|
5
|
+
<div class="sidebar-header">All Models <button onclick="refreshSideBar()" class="btn">⭮</button></div>
|
|
6
|
+
<div class="model-wrapper">
|
|
7
|
+
@each (models:model) {
|
|
8
|
+
<div class="model" onclick="openModel('@(model.name)')">@(model.name)<span id="@(model.name)-doc-count">@(model.count)</span></div>
|
|
9
|
+
}
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
)
|
package/views/pages/index.zare
CHANGED
|
@@ -1,22 +1,15 @@
|
|
|
1
|
-
as Base import "../base
|
|
1
|
+
as Base import "../base"
|
|
2
2
|
|
|
3
|
-
as
|
|
4
|
-
as
|
|
3
|
+
as Sidebar import "../components/sidebar"
|
|
4
|
+
as Popup import "../components/popup"
|
|
5
|
+
as InsertTab import "../components/insertTab"
|
|
5
6
|
|
|
6
|
-
link
|
|
7
|
-
import
|
|
7
|
+
link "/styles/style"
|
|
8
|
+
import "/scripts/main"
|
|
8
9
|
|
|
9
10
|
serve (
|
|
10
11
|
<Base>
|
|
11
|
-
<
|
|
12
|
-
<div class="sidebar-header">All Models <button onclick="refreshSideBar()" class="btn">⭮</button></div>
|
|
13
|
-
<div class="model-wrapper">
|
|
14
|
-
@each (models:model) {
|
|
15
|
-
<div class="model" onclick="openModel('@(model.name)')">@(model.name)<span id="@(model.name)-doc-count">@(model.count)</span></div>
|
|
16
|
-
}
|
|
17
|
-
</div>
|
|
18
|
-
</div>
|
|
19
|
-
|
|
12
|
+
<Sidebar></Sidebar>
|
|
20
13
|
<div class="main">
|
|
21
14
|
<div class="tabs" id="tabs">
|
|
22
15
|
<div class="tab" id="tab-quar-studio"><img src="/images/icon.png" alt="uv dex icon" width="24px"/></div>
|
|
@@ -52,4 +45,4 @@ serve (
|
|
|
52
45
|
<Popup/>
|
|
53
46
|
<InsertTab />
|
|
54
47
|
</Base>
|
|
55
|
-
)
|
|
48
|
+
)
|