db-json-cli 1.0.2 → 1.0.4
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/bin/db-json-cli.js +8 -0
- package/package.json +40 -38
- package/src/index.ejs +261 -0
- package/src/server.js +61 -13
package/bin/db-json-cli.js
CHANGED
|
@@ -17,6 +17,14 @@ const argv = yargs(hideBin(process.argv))
|
|
|
17
17
|
default: "./db.json",
|
|
18
18
|
description: "Path to JSON database file",
|
|
19
19
|
})
|
|
20
|
+
.option("watch", {
|
|
21
|
+
alias: "w",
|
|
22
|
+
type: "boolean",
|
|
23
|
+
default: false,
|
|
24
|
+
description: "Watch DB file for changes",
|
|
25
|
+
})
|
|
20
26
|
.help().argv;
|
|
21
27
|
|
|
28
|
+
if (argv.watch) process.env.WATCH = "true";
|
|
29
|
+
|
|
22
30
|
startServer(argv.db, argv.port);
|
package/package.json
CHANGED
|
@@ -1,38 +1,40 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "db-json-cli",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"license": "MIT",
|
|
5
|
-
"description": "Lightweight JSON-based backend server with JWT authentication and CLI support",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "https://github.com/
|
|
9
|
-
},
|
|
10
|
-
"homepage": "https://github.com/
|
|
11
|
-
"bugs": {
|
|
12
|
-
"url": "https://github.com/
|
|
13
|
-
},
|
|
14
|
-
"keywords": [
|
|
15
|
-
"json-server",
|
|
16
|
-
"cli",
|
|
17
|
-
"jwt",
|
|
18
|
-
"mock-server",
|
|
19
|
-
"express",
|
|
20
|
-
"api"
|
|
21
|
-
],
|
|
22
|
-
"bin": {
|
|
23
|
-
"db-json-cli": "./bin/db-json-cli.js"
|
|
24
|
-
},
|
|
25
|
-
"type": "module",
|
|
26
|
-
"scripts": {
|
|
27
|
-
"start": "node bin/db-json-cli.js"
|
|
28
|
-
},
|
|
29
|
-
"dependencies": {
|
|
30
|
-
"bcryptjs": "^2.4.3",
|
|
31
|
-
"body-parser": "^1.20.2",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "db-json-cli",
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Lightweight JSON-based backend server with JWT authentication and CLI support",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/JEONG-JI-HEON/db-json-cli.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/JEONG-JI-HEON/db-json-cli#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/JEONG-JI-HEON/db-json-cli/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"json-server",
|
|
16
|
+
"cli",
|
|
17
|
+
"jwt",
|
|
18
|
+
"mock-server",
|
|
19
|
+
"express",
|
|
20
|
+
"api"
|
|
21
|
+
],
|
|
22
|
+
"bin": {
|
|
23
|
+
"db-json-cli": "./bin/db-json-cli.js"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"start": "node bin/db-json-cli.js"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"bcryptjs": "^2.4.3",
|
|
31
|
+
"body-parser": "^1.20.2",
|
|
32
|
+
"chokidar": "^4.0.3",
|
|
33
|
+
"cors": "^2.8.5",
|
|
34
|
+
"ejs": "^3.1.10",
|
|
35
|
+
"express": "^4.21.2",
|
|
36
|
+
"fs-extra": "^11.1.1",
|
|
37
|
+
"jsonwebtoken": "^9.0.0",
|
|
38
|
+
"yargs": "^18.0.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/index.ejs
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>db-json-cli API Documentation</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
body {
|
|
14
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
15
|
+
background: linear-gradient(to bottom, #1a1a2e 0%, #16213e 100%);
|
|
16
|
+
color: #333;
|
|
17
|
+
min-height: 100vh;
|
|
18
|
+
}
|
|
19
|
+
.header {
|
|
20
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
21
|
+
padding: 3rem 2rem;
|
|
22
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
23
|
+
}
|
|
24
|
+
.container {
|
|
25
|
+
max-width: 1200px;
|
|
26
|
+
margin: 0 auto;
|
|
27
|
+
}
|
|
28
|
+
.header h1 {
|
|
29
|
+
color: white;
|
|
30
|
+
font-size: 2.5rem;
|
|
31
|
+
margin-bottom: 0.5rem;
|
|
32
|
+
font-weight: 700;
|
|
33
|
+
}
|
|
34
|
+
.header p {
|
|
35
|
+
color: rgba(255, 255, 255, 0.9);
|
|
36
|
+
font-size: 1.1rem;
|
|
37
|
+
}
|
|
38
|
+
.content {
|
|
39
|
+
padding: 2rem;
|
|
40
|
+
}
|
|
41
|
+
.info-box {
|
|
42
|
+
background: white;
|
|
43
|
+
border-radius: 12px;
|
|
44
|
+
padding: 1.5rem;
|
|
45
|
+
margin-bottom: 2rem;
|
|
46
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
47
|
+
}
|
|
48
|
+
.info-box h2 {
|
|
49
|
+
color: #667eea;
|
|
50
|
+
font-size: 1.3rem;
|
|
51
|
+
margin-bottom: 0.5rem;
|
|
52
|
+
}
|
|
53
|
+
.info-box p {
|
|
54
|
+
color: #666;
|
|
55
|
+
line-height: 1.6;
|
|
56
|
+
}
|
|
57
|
+
.base-url {
|
|
58
|
+
background: #f8f9fa;
|
|
59
|
+
padding: 0.5rem 1rem;
|
|
60
|
+
border-radius: 6px;
|
|
61
|
+
font-family: "Courier New", monospace;
|
|
62
|
+
color: #333;
|
|
63
|
+
display: inline-block;
|
|
64
|
+
margin-top: 0.5rem;
|
|
65
|
+
}
|
|
66
|
+
.endpoints {
|
|
67
|
+
background: white;
|
|
68
|
+
border-radius: 12px;
|
|
69
|
+
padding: 2rem;
|
|
70
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
71
|
+
}
|
|
72
|
+
.endpoints h2 {
|
|
73
|
+
color: #333;
|
|
74
|
+
font-size: 1.5rem;
|
|
75
|
+
margin-bottom: 1.5rem;
|
|
76
|
+
padding-bottom: 0.5rem;
|
|
77
|
+
border-bottom: 2px solid #667eea;
|
|
78
|
+
}
|
|
79
|
+
.endpoint-item {
|
|
80
|
+
background: #f8f9fa;
|
|
81
|
+
border: 1px solid #e9ecef;
|
|
82
|
+
border-radius: 8px;
|
|
83
|
+
padding: 1.5rem;
|
|
84
|
+
margin-bottom: 1rem;
|
|
85
|
+
transition: all 0.3s ease;
|
|
86
|
+
}
|
|
87
|
+
.endpoint-item:hover {
|
|
88
|
+
transform: translateY(-2px);
|
|
89
|
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
|
|
90
|
+
border-color: #667eea;
|
|
91
|
+
}
|
|
92
|
+
.endpoint-header {
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
gap: 1rem;
|
|
96
|
+
margin-bottom: 1rem;
|
|
97
|
+
flex-wrap: wrap;
|
|
98
|
+
}
|
|
99
|
+
.method-badge {
|
|
100
|
+
padding: 0.3rem 0.8rem;
|
|
101
|
+
border-radius: 4px;
|
|
102
|
+
font-weight: 600;
|
|
103
|
+
font-size: 0.75rem;
|
|
104
|
+
text-transform: uppercase;
|
|
105
|
+
letter-spacing: 0.5px;
|
|
106
|
+
}
|
|
107
|
+
.method-get {
|
|
108
|
+
background: #e7f5ff;
|
|
109
|
+
color: #1971c2;
|
|
110
|
+
}
|
|
111
|
+
.method-post {
|
|
112
|
+
background: #d3f9d8;
|
|
113
|
+
color: #2f9e44;
|
|
114
|
+
}
|
|
115
|
+
.permission-badge {
|
|
116
|
+
padding: 0.3rem 0.8rem;
|
|
117
|
+
border-radius: 4px;
|
|
118
|
+
font-weight: 600;
|
|
119
|
+
font-size: 0.75rem;
|
|
120
|
+
text-transform: uppercase;
|
|
121
|
+
letter-spacing: 0.5px;
|
|
122
|
+
}
|
|
123
|
+
.permission-public {
|
|
124
|
+
background: #d3f9d8;
|
|
125
|
+
color: #2f9e44;
|
|
126
|
+
}
|
|
127
|
+
.permission-private {
|
|
128
|
+
background: #ffd43b;
|
|
129
|
+
color: #856404;
|
|
130
|
+
}
|
|
131
|
+
.endpoint-path {
|
|
132
|
+
font-family: "Courier New", monospace;
|
|
133
|
+
font-size: 1.1rem;
|
|
134
|
+
color: #333;
|
|
135
|
+
font-weight: 500;
|
|
136
|
+
}
|
|
137
|
+
.endpoint-count {
|
|
138
|
+
margin-left: auto;
|
|
139
|
+
background: #667eea;
|
|
140
|
+
color: white;
|
|
141
|
+
padding: 0.3rem 0.8rem;
|
|
142
|
+
border-radius: 20px;
|
|
143
|
+
font-size: 0.85rem;
|
|
144
|
+
font-weight: 600;
|
|
145
|
+
}
|
|
146
|
+
.endpoint-description {
|
|
147
|
+
color: #666;
|
|
148
|
+
margin-bottom: 1rem;
|
|
149
|
+
line-height: 1.5;
|
|
150
|
+
}
|
|
151
|
+
.endpoint-actions {
|
|
152
|
+
display: flex;
|
|
153
|
+
gap: 0.5rem;
|
|
154
|
+
}
|
|
155
|
+
.action-btn {
|
|
156
|
+
padding: 0.5rem 1rem;
|
|
157
|
+
border-radius: 6px;
|
|
158
|
+
text-decoration: none;
|
|
159
|
+
font-size: 0.9rem;
|
|
160
|
+
font-weight: 500;
|
|
161
|
+
transition: all 0.2s;
|
|
162
|
+
display: inline-block;
|
|
163
|
+
}
|
|
164
|
+
.btn-primary {
|
|
165
|
+
background: #667eea;
|
|
166
|
+
color: white;
|
|
167
|
+
}
|
|
168
|
+
.btn-primary:hover {
|
|
169
|
+
background: #5568d3;
|
|
170
|
+
transform: translateY(-1px);
|
|
171
|
+
}
|
|
172
|
+
.btn-secondary {
|
|
173
|
+
background: #e9ecef;
|
|
174
|
+
color: #333;
|
|
175
|
+
}
|
|
176
|
+
.btn-secondary:hover {
|
|
177
|
+
background: #dee2e6;
|
|
178
|
+
}
|
|
179
|
+
.footer {
|
|
180
|
+
text-align: center;
|
|
181
|
+
padding: 2rem;
|
|
182
|
+
color: rgba(255, 255, 255, 0.7);
|
|
183
|
+
font-size: 0.9rem;
|
|
184
|
+
}
|
|
185
|
+
</style>
|
|
186
|
+
</head>
|
|
187
|
+
<body>
|
|
188
|
+
<div class="header">
|
|
189
|
+
<div class="container">
|
|
190
|
+
<h1>🚀 db-json-cli API</h1>
|
|
191
|
+
<p>RESTful JSON API Documentation</p>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<div class="container content">
|
|
196
|
+
<div class="info-box">
|
|
197
|
+
<h2>📖 API Information</h2>
|
|
198
|
+
<p>Welcome to the db-json-cli API. This API provides RESTful endpoints for managing your data.</p>
|
|
199
|
+
<div class="base-url">Base URL: http://localhost:<%= port %></div>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
<div class="endpoints">
|
|
203
|
+
<h2>Available Endpoints</h2>
|
|
204
|
+
<% routeList.forEach(r => { %>
|
|
205
|
+
<div class="endpoint-item">
|
|
206
|
+
<div class="endpoint-header">
|
|
207
|
+
<span class="method-badge method-get">GET</span>
|
|
208
|
+
<span class="endpoint-path">/<%= r.key %></span>
|
|
209
|
+
<span class="permission-badge permission-<%= r.permission %>"><%= r.permission.toUpperCase() %></span>
|
|
210
|
+
<span class="endpoint-count"><%= r.count %> <%= r.count === 1 ? 'item' : 'items' %></span>
|
|
211
|
+
</div>
|
|
212
|
+
<div class="endpoint-description">Retrieve all <%= r.key %> or filter by ID range using ?from=1&to=10</div>
|
|
213
|
+
<div class="endpoint-actions">
|
|
214
|
+
<a href="http://localhost:<%= port %>/<%= r.key %>" target="_blank" class="action-btn btn-primary">
|
|
215
|
+
Try it out
|
|
216
|
+
</a>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<div class="endpoint-item">
|
|
221
|
+
<div class="endpoint-header">
|
|
222
|
+
<span class="method-badge method-get">GET</span>
|
|
223
|
+
<span class="endpoint-path">/<%= r.key %>/:id</span>
|
|
224
|
+
<span class="permission-badge permission-<%= r.permission %>"><%= r.permission.toUpperCase() %></span>
|
|
225
|
+
</div>
|
|
226
|
+
<div class="endpoint-description">Retrieve a specific item by ID</div>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
<div class="endpoint-item">
|
|
230
|
+
<div class="endpoint-header">
|
|
231
|
+
<span class="method-badge method-post">POST</span>
|
|
232
|
+
<span class="endpoint-path">/<%= r.key %></span>
|
|
233
|
+
<span class="permission-badge permission-<%= r.permission %>"><%= r.permission.toUpperCase() %></span>
|
|
234
|
+
</div>
|
|
235
|
+
<div class="endpoint-description">Create a new item (ID will be auto-generated)</div>
|
|
236
|
+
</div>
|
|
237
|
+
<% }) %>
|
|
238
|
+
|
|
239
|
+
<div class="endpoint-item">
|
|
240
|
+
<div class="endpoint-header">
|
|
241
|
+
<span class="method-badge method-post">POST</span>
|
|
242
|
+
<span class="endpoint-path">/register</span>
|
|
243
|
+
</div>
|
|
244
|
+
<div class="endpoint-description">Register a new user account</div>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<div class="endpoint-item">
|
|
248
|
+
<div class="endpoint-header">
|
|
249
|
+
<span class="method-badge method-post">POST</span>
|
|
250
|
+
<span class="endpoint-path">/login</span>
|
|
251
|
+
</div>
|
|
252
|
+
<div class="endpoint-description">Login and receive access tokens</div>
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
<div class="footer">
|
|
258
|
+
<p>Generated by db-json-cli • Made with ♥</p>
|
|
259
|
+
</div>
|
|
260
|
+
</body>
|
|
261
|
+
</html>
|
package/src/server.js
CHANGED
|
@@ -4,18 +4,47 @@ import bodyParser from "body-parser";
|
|
|
4
4
|
import cors from "cors";
|
|
5
5
|
import { hashPassword, comparePassword, generateTokens, authMiddleware } from "./auth.js";
|
|
6
6
|
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
|
|
7
13
|
export const startServer = async (dbPath, port = 4000) => {
|
|
8
14
|
const app = express();
|
|
9
15
|
app.use(cors());
|
|
10
16
|
app.use(bodyParser.json());
|
|
11
17
|
|
|
12
|
-
//
|
|
13
|
-
|
|
18
|
+
// EJS 설정
|
|
19
|
+
app.set("view engine", "ejs");
|
|
20
|
+
app.set("views", __dirname);
|
|
21
|
+
|
|
22
|
+
app.use(express.static("public"));
|
|
23
|
+
|
|
24
|
+
let db = {
|
|
25
|
+
users: [],
|
|
26
|
+
rules: { test1: "public", test2: "private", test3: "public" },
|
|
27
|
+
test1: [
|
|
28
|
+
{ id: 1, message: "good" },
|
|
29
|
+
{ id: 2, message: "good" },
|
|
30
|
+
{ id: 3, message: "good" },
|
|
31
|
+
],
|
|
32
|
+
test2: [
|
|
33
|
+
{ id: 1, message: "good" },
|
|
34
|
+
{ id: 2, message: "good" },
|
|
35
|
+
{ id: 3, message: "good" },
|
|
36
|
+
],
|
|
37
|
+
test3: [
|
|
38
|
+
{ id: 1, message: "good" },
|
|
39
|
+
{ id: 2, message: "good" },
|
|
40
|
+
{ id: 3, message: "good" },
|
|
41
|
+
],
|
|
42
|
+
};
|
|
14
43
|
if (fs.existsSync(dbPath)) db = await fs.readJson(dbPath);
|
|
15
44
|
|
|
16
45
|
const saveDB = async () => fs.writeJson(dbPath, db, { spaces: 2 });
|
|
17
46
|
|
|
18
|
-
//
|
|
47
|
+
// REGISTER
|
|
19
48
|
app.post("/register", async (req, res) => {
|
|
20
49
|
const { email, password, name } = req.body;
|
|
21
50
|
if (!email || !password) return res.status(400).json({ message: "Email/password required" });
|
|
@@ -33,7 +62,7 @@ export const startServer = async (dbPath, port = 4000) => {
|
|
|
33
62
|
res.json(tokens);
|
|
34
63
|
});
|
|
35
64
|
|
|
36
|
-
//
|
|
65
|
+
// LOGIN
|
|
37
66
|
app.post("/login", async (req, res) => {
|
|
38
67
|
const { email, password } = req.body;
|
|
39
68
|
const user = db.users.find((u) => u.email === email);
|
|
@@ -46,28 +75,26 @@ export const startServer = async (dbPath, port = 4000) => {
|
|
|
46
75
|
res.json(tokens);
|
|
47
76
|
});
|
|
48
77
|
|
|
49
|
-
//
|
|
78
|
+
// AUTO ROUTES
|
|
79
|
+
const routeList = [];
|
|
50
80
|
Object.keys(db).forEach((key) => {
|
|
51
81
|
if (["users", "rules"].includes(key)) return;
|
|
52
82
|
const isPrivate = db.rules?.[key] === "private";
|
|
53
|
-
|
|
54
83
|
const route = express.Router();
|
|
55
84
|
|
|
56
|
-
//
|
|
85
|
+
// GET /key
|
|
57
86
|
route.get("/", async (req, res) => {
|
|
58
87
|
const { from, to } = req.query;
|
|
59
88
|
let data = db[key];
|
|
60
|
-
|
|
61
89
|
if (from && to) {
|
|
62
90
|
const fromNum = Number(from);
|
|
63
91
|
const toNum = Number(to);
|
|
64
92
|
data = data.filter((item) => item.id >= fromNum && item.id <= toNum);
|
|
65
93
|
}
|
|
66
|
-
|
|
67
94
|
res.json(data);
|
|
68
95
|
});
|
|
69
96
|
|
|
70
|
-
//
|
|
97
|
+
// GET /key/:id
|
|
71
98
|
route.get("/:id", async (req, res) => {
|
|
72
99
|
const id = Number(req.params.id);
|
|
73
100
|
const item = db[key].find((i) => i.id === id);
|
|
@@ -75,7 +102,7 @@ export const startServer = async (dbPath, port = 4000) => {
|
|
|
75
102
|
res.json(item);
|
|
76
103
|
});
|
|
77
104
|
|
|
78
|
-
//
|
|
105
|
+
// POST /key
|
|
79
106
|
route.post("/", async (req, res) => {
|
|
80
107
|
const newItem = req.body;
|
|
81
108
|
if (!newItem || typeof newItem !== "object") return res.status(400).json({ message: "Invalid body" });
|
|
@@ -92,7 +119,28 @@ export const startServer = async (dbPath, port = 4000) => {
|
|
|
92
119
|
} else {
|
|
93
120
|
app.use(`/${key}`, route);
|
|
94
121
|
}
|
|
122
|
+
|
|
123
|
+
// routeList에 추가 (권한 정보 포함)
|
|
124
|
+
routeList.push({
|
|
125
|
+
key,
|
|
126
|
+
count: db[key].length,
|
|
127
|
+
permission: isPrivate ? "private" : "public"
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Index HTML
|
|
132
|
+
app.get("/", (req, res) => {
|
|
133
|
+
res.render("index", { routeList, port });
|
|
95
134
|
});
|
|
96
135
|
|
|
97
|
-
app.listen(port, () =>
|
|
98
|
-
|
|
136
|
+
app.listen(port, () => {
|
|
137
|
+
console.log(`✅ db-json-cli Success! ✧*。٩(ˊᗜˋ*)و✧*。\n`);
|
|
138
|
+
console.log(`Index:\nhttp://localhost:${port}/\n\n`);
|
|
139
|
+
|
|
140
|
+
console.log("Endpoints:");
|
|
141
|
+
routeList.forEach((r) => {
|
|
142
|
+
const badge = r.permission === "private" ? "🔒 PRIVATE" : "🔓 PUBLIC";
|
|
143
|
+
console.log(`${badge} - http://localhost:${port}/${r.key} (${r.count} items)`);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
};
|