gcharanteja commited on
Commit
de703f2
·
1 Parent(s): c3f4433

feat: add Swagger documentation for API endpoints and integrate Swagger UI

Browse files

- Added Swagger UI and Swagger JSDoc dependencies to package.json
- Configured Swagger in server.js to serve API documentation
- Documented health check, product, and order endpoints with Swagger annotations
- Enhanced error handling in product endpoints

Files changed (5) hide show
  1. .env +1 -1
  2. app.py +270 -0
  3. package-lock.json +478 -1
  4. package.json +3 -1
  5. server.js +181 -1
.env CHANGED
@@ -6,4 +6,4 @@ DB_URL=https://maxxcarl-sqllite.hf.space
6
  DATABASE=ecommerce
7
 
8
  # Express server port
9
- PORT=3000
 
6
  DATABASE=ecommerce
7
 
8
  # Express server port
9
+ PORT=3001
app.py CHANGED
@@ -67,6 +67,276 @@ def read_root():
67
  </html>
68
  """
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  @app.get("/health")
71
  def health_check():
72
  try:
 
67
  </html>
68
  """
69
 
70
+ @app.get("/portal", response_class=HTMLResponse)
71
+ def portal():
72
+ return """
73
+ <!doctype html>
74
+ <html lang="en">
75
+ <head>
76
+ <meta charset="utf-8" />
77
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
78
+ <title>SQLite Portal</title>
79
+ <style>
80
+ body {
81
+ font-family: Arial, sans-serif;
82
+ margin: 24px;
83
+ color: #1f2328;
84
+ background: #f6f8fa;
85
+ }
86
+ h1 { margin: 0 0 8px; }
87
+ .meta { color: #57606a; margin-bottom: 16px; }
88
+ .grid {
89
+ display: grid;
90
+ gap: 16px;
91
+ grid-template-columns: 280px 1fr;
92
+ }
93
+ section {
94
+ background: #ffffff;
95
+ border: 1px solid #d0d7de;
96
+ border-radius: 8px;
97
+ padding: 16px;
98
+ }
99
+ textarea {
100
+ width: 100%;
101
+ min-height: 160px;
102
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
103
+ "Liberation Mono", "Courier New", monospace;
104
+ border: 1px solid #d0d7de;
105
+ border-radius: 6px;
106
+ padding: 8px;
107
+ box-sizing: border-box;
108
+ }
109
+ button {
110
+ background: #0969da;
111
+ color: #ffffff;
112
+ border: none;
113
+ border-radius: 6px;
114
+ padding: 8px 12px;
115
+ cursor: pointer;
116
+ }
117
+ button.secondary { background: #6e7781; }
118
+ #tables { list-style: none; padding: 0; margin: 12px 0 0; }
119
+ #tables li { padding: 6px 8px; border-bottom: 1px solid #d8dee4; }
120
+ #tables li:last-child { border-bottom: none; }
121
+ .actions { display: flex; gap: 8px; margin-top: 8px; }
122
+ #status { margin-top: 12px; color: #57606a; white-space: pre-wrap; }
123
+ table { width: 100%; border-collapse: collapse; margin-top: 12px; }
124
+ th, td {
125
+ border: 1px solid #d0d7de;
126
+ padding: 6px 8px;
127
+ text-align: left;
128
+ vertical-align: top;
129
+ font-size: 14px;
130
+ }
131
+ th { background: #f6f8fa; }
132
+ pre {
133
+ background: #f6f8fa;
134
+ border: 1px solid #d0d7de;
135
+ border-radius: 6px;
136
+ padding: 12px;
137
+ overflow: auto;
138
+ }
139
+ @media (max-width: 900px) {
140
+ .grid { grid-template-columns: 1fr; }
141
+ }
142
+ </style>
143
+ </head>
144
+ <body>
145
+ <h1>SQLite Portal</h1>
146
+ <div class="meta" id="meta">Loading database info...</div>
147
+ <div class="grid">
148
+ <section>
149
+ <h2>Projects</h2>
150
+ <select id="project"></select>
151
+ <div class="actions">
152
+ <button id="refresh">Refresh</button>
153
+ </div>
154
+ <h3>Tables</h3>
155
+ <ul id="tables"></ul>
156
+ </section>
157
+ <section>
158
+ <h2>SQL Editor</h2>
159
+ <textarea id="sql">SELECT * FROM products LIMIT 50;</textarea>
160
+ <div class="actions">
161
+ <button id="run">Run</button>
162
+ <button id="clear" class="secondary">Clear Output</button>
163
+ </div>
164
+ <div id="status"></div>
165
+ <div id="results"></div>
166
+ </section>
167
+ </div>
168
+
169
+ <script>
170
+ const projectEl = document.getElementById("project");
171
+ const tablesEl = document.getElementById("tables");
172
+ const metaEl = document.getElementById("meta");
173
+ const statusEl = document.getElementById("status");
174
+ const resultsEl = document.getElementById("results");
175
+ const sqlEl = document.getElementById("sql");
176
+
177
+ function setStatus(message) {
178
+ statusEl.textContent = message || "";
179
+ }
180
+
181
+ function clearResults() {
182
+ resultsEl.innerHTML = "";
183
+ setStatus("");
184
+ }
185
+
186
+ async function fetchJson(url, options) {
187
+ const response = await fetch(url, options);
188
+ const data = await response.json();
189
+ if (!response.ok) {
190
+ const errorMessage = data.detail || data.error || data.message || response.statusText;
191
+ throw new Error(errorMessage);
192
+ }
193
+ return data;
194
+ }
195
+
196
+ function renderTable(rows) {
197
+ if (!Array.isArray(rows) || rows.length === 0) {
198
+ resultsEl.innerHTML = "<em>No rows returned.</em>";
199
+ return;
200
+ }
201
+ const columns = Object.keys(rows[0]);
202
+ const table = document.createElement("table");
203
+ const thead = document.createElement("thead");
204
+ const headerRow = document.createElement("tr");
205
+ columns.forEach((col) => {
206
+ const th = document.createElement("th");
207
+ th.textContent = col;
208
+ headerRow.appendChild(th);
209
+ });
210
+ thead.appendChild(headerRow);
211
+ table.appendChild(thead);
212
+
213
+ const tbody = document.createElement("tbody");
214
+ rows.forEach((row) => {
215
+ const tr = document.createElement("tr");
216
+ columns.forEach((col) => {
217
+ const td = document.createElement("td");
218
+ const value = row[col] === null || row[col] === undefined ? "" : row[col];
219
+ td.textContent = value;
220
+ tr.appendChild(td);
221
+ });
222
+ tbody.appendChild(tr);
223
+ });
224
+ table.appendChild(tbody);
225
+ resultsEl.innerHTML = "";
226
+ resultsEl.appendChild(table);
227
+ }
228
+
229
+ function currentProject() {
230
+ return projectEl.value || "default";
231
+ }
232
+
233
+ async function loadInfo() {
234
+ try {
235
+ metaEl.textContent = `Server: ${window.location.origin} | Project: ${currentProject()}`;
236
+ } catch (err) {
237
+ metaEl.textContent = `Failed to load info: ${err.message}`;
238
+ }
239
+ }
240
+
241
+ async function loadProjects() {
242
+ projectEl.innerHTML = "";
243
+ try {
244
+ const data = await fetchJson("/api/v1/projects");
245
+ const projects = Array.isArray(data)
246
+ ? data
247
+ : data.projects || data.data || [];
248
+ const names = projects
249
+ .map((p) => (typeof p === "string" ? p : p.name))
250
+ .filter(Boolean);
251
+ const uniqueNames = names.length ? names : ["default"];
252
+ uniqueNames.forEach((name) => {
253
+ const option = document.createElement("option");
254
+ option.value = name;
255
+ option.textContent = name;
256
+ projectEl.appendChild(option);
257
+ });
258
+ } catch (err) {
259
+ const option = document.createElement("option");
260
+ option.value = "default";
261
+ option.textContent = "default";
262
+ projectEl.appendChild(option);
263
+ setStatus(`Failed to load projects: ${err.message}`);
264
+ }
265
+ }
266
+
267
+ async function loadTables() {
268
+ tablesEl.innerHTML = "";
269
+ try {
270
+ const project = currentProject();
271
+ const data = await fetchJson(`/api/v1/tables?project=${project}`);
272
+ const tables = data.tables || data.data || [];
273
+ const names = tables
274
+ .map((t) => (typeof t === "string" ? t : t.name))
275
+ .filter(Boolean);
276
+ if (!names.length) {
277
+ tablesEl.innerHTML = "<li>No tables found.</li>";
278
+ return;
279
+ }
280
+ names.forEach((name) => {
281
+ const li = document.createElement("li");
282
+ li.textContent = name;
283
+ li.addEventListener("click", () => {
284
+ sqlEl.value = `SELECT * FROM ${name} LIMIT 50;`;
285
+ });
286
+ tablesEl.appendChild(li);
287
+ });
288
+ } catch (err) {
289
+ tablesEl.innerHTML = `<li>Error: ${err.message}</li>`;
290
+ }
291
+ }
292
+
293
+ async function runSql() {
294
+ clearResults();
295
+ const sql = sqlEl.value.trim();
296
+ if (!sql) {
297
+ setStatus("Enter SQL to run.");
298
+ return;
299
+ }
300
+ setStatus("Running...");
301
+ const verb = sql.split(/\s+/)[0]?.toUpperCase();
302
+ const isQuery = ["SELECT", "PRAGMA", "WITH", "EXPLAIN"].includes(verb);
303
+ const endpoint = isQuery ? "/api/v1/query" : "/api/v1/execute";
304
+ const project = currentProject();
305
+ try {
306
+ const result = await fetchJson(endpoint, {
307
+ method: "POST",
308
+ headers: { "Content-Type": "application/json" },
309
+ body: JSON.stringify({ sql, project }),
310
+ });
311
+ if (isQuery) {
312
+ const rows = result.data || [];
313
+ setStatus(`Query returned ${rows.length} rows.`);
314
+ renderTable(rows);
315
+ } else {
316
+ setStatus(`Statement executed.`);
317
+ resultsEl.innerHTML = `<pre>${JSON.stringify(result, null, 2)}</pre>`;
318
+ }
319
+ } catch (err) {
320
+ setStatus(`Error: ${err.message}`);
321
+ }
322
+ }
323
+
324
+ document.getElementById("refresh").addEventListener("click", () => {
325
+ loadProjects().then(loadTables).then(loadInfo);
326
+ });
327
+ projectEl.addEventListener("change", () => {
328
+ loadTables();
329
+ loadInfo();
330
+ });
331
+ document.getElementById("run").addEventListener("click", runSql);
332
+ document.getElementById("clear").addEventListener("click", clearResults);
333
+
334
+ loadProjects().then(loadTables).then(loadInfo);
335
+ </script>
336
+ </body>
337
+ </html>
338
+ """
339
+
340
  @app.get("/health")
341
  def health_check():
342
  try:
package-lock.json CHANGED
@@ -10,9 +10,81 @@
10
  "dependencies": {
11
  "axios": "^1.17.0",
12
  "dotenv": "^16.0.3",
13
- "express": "^4.18.2"
 
 
14
  }
15
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  "node_modules/accepts": {
17
  "version": "1.3.8",
18
  "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -61,6 +133,42 @@
61
  "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
62
  "license": "MIT"
63
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  "node_modules/array-flatten": {
65
  "version": "1.1.1",
66
  "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@@ -85,6 +193,15 @@
85
  "proxy-from-env": "^2.1.0"
86
  }
87
  },
 
 
 
 
 
 
 
 
 
88
  "node_modules/body-parser": {
89
  "version": "1.20.5",
90
  "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
@@ -109,6 +226,18 @@
109
  "npm": "1.2.8000 || >= 1.4.16"
110
  }
111
  },
 
 
 
 
 
 
 
 
 
 
 
 
112
  "node_modules/bytes": {
113
  "version": "3.1.2",
114
  "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -147,6 +276,12 @@
147
  "url": "https://github.com/sponsors/ljharb"
148
  }
149
  },
 
 
 
 
 
 
150
  "node_modules/combined-stream": {
151
  "version": "1.0.8",
152
  "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -159,6 +294,15 @@
159
  "node": ">= 0.8"
160
  }
161
  },
 
 
 
 
 
 
 
 
 
162
  "node_modules/content-disposition": {
163
  "version": "0.5.4",
164
  "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -195,6 +339,20 @@
195
  "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
196
  "license": "MIT"
197
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  "node_modules/debug": {
199
  "version": "2.6.9",
200
  "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -232,6 +390,18 @@
232
  "npm": "1.2.8000 || >= 1.4.16"
233
  }
234
  },
 
 
 
 
 
 
 
 
 
 
 
 
235
  "node_modules/dotenv": {
236
  "version": "16.6.1",
237
  "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
@@ -324,6 +494,15 @@
324
  "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
325
  "license": "MIT"
326
  },
 
 
 
 
 
 
 
 
 
327
  "node_modules/etag": {
328
  "version": "1.8.1",
329
  "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -379,6 +558,28 @@
379
  "url": "https://opencollective.com/express"
380
  }
381
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
  "node_modules/finalhandler": {
383
  "version": "1.3.2",
384
  "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
@@ -417,6 +618,22 @@
417
  }
418
  }
419
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
  "node_modules/form-data": {
421
  "version": "4.0.5",
422
  "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
@@ -497,6 +714,30 @@
497
  "node": ">= 0.4"
498
  }
499
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
500
  "node_modules/gopd": {
501
  "version": "1.2.0",
502
  "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -631,6 +872,70 @@
631
  "node": ">= 0.10"
632
  }
633
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
634
  "node_modules/math-intrinsics": {
635
  "version": "1.1.0",
636
  "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -700,6 +1005,30 @@
700
  "node": ">= 0.6"
701
  }
702
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703
  "node_modules/ms": {
704
  "version": "2.0.0",
705
  "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -739,6 +1068,19 @@
739
  "node": ">= 0.8"
740
  }
741
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
742
  "node_modules/parseurl": {
743
  "version": "1.3.3",
744
  "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -748,6 +1090,31 @@
748
  "node": ">= 0.8"
749
  }
750
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
751
  "node_modules/path-to-regexp": {
752
  "version": "0.1.13",
753
  "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
@@ -815,6 +1182,15 @@
815
  "node": ">= 0.8"
816
  }
817
  },
 
 
 
 
 
 
 
 
 
818
  "node_modules/safe-buffer": {
819
  "version": "5.2.1",
820
  "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -892,6 +1268,27 @@
892
  "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
893
  "license": "ISC"
894
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
895
  "node_modules/side-channel": {
896
  "version": "1.1.0",
897
  "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
@@ -964,6 +1361,18 @@
964
  "url": "https://github.com/sponsors/ljharb"
965
  }
966
  },
 
 
 
 
 
 
 
 
 
 
 
 
967
  "node_modules/statuses": {
968
  "version": "2.0.2",
969
  "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@@ -973,6 +1382,50 @@
973
  "node": ">= 0.8"
974
  }
975
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
976
  "node_modules/toidentifier": {
977
  "version": "1.0.1",
978
  "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -1021,6 +1474,30 @@
1021
  "engines": {
1022
  "node": ">= 0.8"
1023
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1024
  }
1025
  }
1026
  }
 
10
  "dependencies": {
11
  "axios": "^1.17.0",
12
  "dotenv": "^16.0.3",
13
+ "express": "^4.18.2",
14
+ "swagger-jsdoc": "^6.3.0",
15
+ "swagger-ui-express": "^5.0.1"
16
  }
17
  },
18
+ "node_modules/@apidevtools/json-schema-ref-parser": {
19
+ "version": "14.0.1",
20
+ "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.0.1.tgz",
21
+ "integrity": "sha512-Oc96zvmxx1fqoSEdUmfmvvb59/KDOnUoJ7s2t7bISyAn0XEz57LCCw8k2Y4Pf3mwKaZLMciESALORLgfe2frCw==",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "@types/json-schema": "^7.0.15",
25
+ "js-yaml": "^4.1.0"
26
+ },
27
+ "engines": {
28
+ "node": ">= 16"
29
+ },
30
+ "funding": {
31
+ "url": "https://github.com/sponsors/philsturgeon"
32
+ }
33
+ },
34
+ "node_modules/@apidevtools/openapi-schemas": {
35
+ "version": "2.1.0",
36
+ "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz",
37
+ "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==",
38
+ "license": "MIT",
39
+ "engines": {
40
+ "node": ">=10"
41
+ }
42
+ },
43
+ "node_modules/@apidevtools/swagger-methods": {
44
+ "version": "3.0.2",
45
+ "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz",
46
+ "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==",
47
+ "license": "MIT"
48
+ },
49
+ "node_modules/@apidevtools/swagger-parser": {
50
+ "version": "12.1.0",
51
+ "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-12.1.0.tgz",
52
+ "integrity": "sha512-e5mJoswsnAX0jG+J09xHFYQXb/bUc5S3pLpMxUuRUA2H8T2kni3yEoyz2R3Dltw5f4A6j6rPNMpWTK+iVDFlng==",
53
+ "license": "MIT",
54
+ "dependencies": {
55
+ "@apidevtools/json-schema-ref-parser": "14.0.1",
56
+ "@apidevtools/openapi-schemas": "^2.1.0",
57
+ "@apidevtools/swagger-methods": "^3.0.2",
58
+ "ajv": "^8.17.1",
59
+ "ajv-draft-04": "^1.0.0",
60
+ "call-me-maybe": "^1.0.2"
61
+ },
62
+ "peerDependencies": {
63
+ "openapi-types": ">=7"
64
+ }
65
+ },
66
+ "node_modules/@isaacs/cliui": {
67
+ "version": "9.0.0",
68
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz",
69
+ "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==",
70
+ "license": "BlueOak-1.0.0",
71
+ "engines": {
72
+ "node": ">=18"
73
+ }
74
+ },
75
+ "node_modules/@scarf/scarf": {
76
+ "version": "1.4.0",
77
+ "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
78
+ "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
79
+ "hasInstallScript": true,
80
+ "license": "Apache-2.0"
81
+ },
82
+ "node_modules/@types/json-schema": {
83
+ "version": "7.0.15",
84
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
85
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
86
+ "license": "MIT"
87
+ },
88
  "node_modules/accepts": {
89
  "version": "1.3.8",
90
  "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
 
133
  "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
134
  "license": "MIT"
135
  },
136
+ "node_modules/ajv": {
137
+ "version": "8.20.0",
138
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
139
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
140
+ "license": "MIT",
141
+ "dependencies": {
142
+ "fast-deep-equal": "^3.1.3",
143
+ "fast-uri": "^3.0.1",
144
+ "json-schema-traverse": "^1.0.0",
145
+ "require-from-string": "^2.0.2"
146
+ },
147
+ "funding": {
148
+ "type": "github",
149
+ "url": "https://github.com/sponsors/epoberezkin"
150
+ }
151
+ },
152
+ "node_modules/ajv-draft-04": {
153
+ "version": "1.0.0",
154
+ "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
155
+ "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==",
156
+ "license": "MIT",
157
+ "peerDependencies": {
158
+ "ajv": "^8.5.0"
159
+ },
160
+ "peerDependenciesMeta": {
161
+ "ajv": {
162
+ "optional": true
163
+ }
164
+ }
165
+ },
166
+ "node_modules/argparse": {
167
+ "version": "2.0.1",
168
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
169
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
170
+ "license": "Python-2.0"
171
+ },
172
  "node_modules/array-flatten": {
173
  "version": "1.1.1",
174
  "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
 
193
  "proxy-from-env": "^2.1.0"
194
  }
195
  },
196
+ "node_modules/balanced-match": {
197
+ "version": "4.0.4",
198
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
199
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
200
+ "license": "MIT",
201
+ "engines": {
202
+ "node": "18 || 20 || >=22"
203
+ }
204
+ },
205
  "node_modules/body-parser": {
206
  "version": "1.20.5",
207
  "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
 
226
  "npm": "1.2.8000 || >= 1.4.16"
227
  }
228
  },
229
+ "node_modules/brace-expansion": {
230
+ "version": "5.0.6",
231
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
232
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
233
+ "license": "MIT",
234
+ "dependencies": {
235
+ "balanced-match": "^4.0.2"
236
+ },
237
+ "engines": {
238
+ "node": "18 || 20 || >=22"
239
+ }
240
+ },
241
  "node_modules/bytes": {
242
  "version": "3.1.2",
243
  "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
 
276
  "url": "https://github.com/sponsors/ljharb"
277
  }
278
  },
279
+ "node_modules/call-me-maybe": {
280
+ "version": "1.0.2",
281
+ "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
282
+ "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==",
283
+ "license": "MIT"
284
+ },
285
  "node_modules/combined-stream": {
286
  "version": "1.0.8",
287
  "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
 
294
  "node": ">= 0.8"
295
  }
296
  },
297
+ "node_modules/commander": {
298
+ "version": "6.2.0",
299
+ "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz",
300
+ "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==",
301
+ "license": "MIT",
302
+ "engines": {
303
+ "node": ">= 6"
304
+ }
305
+ },
306
  "node_modules/content-disposition": {
307
  "version": "0.5.4",
308
  "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
 
339
  "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
340
  "license": "MIT"
341
  },
342
+ "node_modules/cross-spawn": {
343
+ "version": "7.0.6",
344
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
345
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
346
+ "license": "MIT",
347
+ "dependencies": {
348
+ "path-key": "^3.1.0",
349
+ "shebang-command": "^2.0.0",
350
+ "which": "^2.0.1"
351
+ },
352
+ "engines": {
353
+ "node": ">= 8"
354
+ }
355
+ },
356
  "node_modules/debug": {
357
  "version": "2.6.9",
358
  "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
 
390
  "npm": "1.2.8000 || >= 1.4.16"
391
  }
392
  },
393
+ "node_modules/doctrine": {
394
+ "version": "3.0.0",
395
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
396
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
397
+ "license": "Apache-2.0",
398
+ "dependencies": {
399
+ "esutils": "^2.0.2"
400
+ },
401
+ "engines": {
402
+ "node": ">=6.0.0"
403
+ }
404
+ },
405
  "node_modules/dotenv": {
406
  "version": "16.6.1",
407
  "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
 
494
  "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
495
  "license": "MIT"
496
  },
497
+ "node_modules/esutils": {
498
+ "version": "2.0.3",
499
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
500
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
501
+ "license": "BSD-2-Clause",
502
+ "engines": {
503
+ "node": ">=0.10.0"
504
+ }
505
+ },
506
  "node_modules/etag": {
507
  "version": "1.8.1",
508
  "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
 
558
  "url": "https://opencollective.com/express"
559
  }
560
  },
561
+ "node_modules/fast-deep-equal": {
562
+ "version": "3.1.3",
563
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
564
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
565
+ "license": "MIT"
566
+ },
567
+ "node_modules/fast-uri": {
568
+ "version": "3.1.2",
569
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
570
+ "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
571
+ "funding": [
572
+ {
573
+ "type": "github",
574
+ "url": "https://github.com/sponsors/fastify"
575
+ },
576
+ {
577
+ "type": "opencollective",
578
+ "url": "https://opencollective.com/fastify"
579
+ }
580
+ ],
581
+ "license": "BSD-3-Clause"
582
+ },
583
  "node_modules/finalhandler": {
584
  "version": "1.3.2",
585
  "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
 
618
  }
619
  }
620
  },
621
+ "node_modules/foreground-child": {
622
+ "version": "3.3.1",
623
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
624
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
625
+ "license": "ISC",
626
+ "dependencies": {
627
+ "cross-spawn": "^7.0.6",
628
+ "signal-exit": "^4.0.1"
629
+ },
630
+ "engines": {
631
+ "node": ">=14"
632
+ },
633
+ "funding": {
634
+ "url": "https://github.com/sponsors/isaacs"
635
+ }
636
+ },
637
  "node_modules/form-data": {
638
  "version": "4.0.5",
639
  "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
 
714
  "node": ">= 0.4"
715
  }
716
  },
717
+ "node_modules/glob": {
718
+ "version": "11.1.0",
719
+ "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz",
720
+ "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==",
721
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
722
+ "license": "BlueOak-1.0.0",
723
+ "dependencies": {
724
+ "foreground-child": "^3.3.1",
725
+ "jackspeak": "^4.1.1",
726
+ "minimatch": "^10.1.1",
727
+ "minipass": "^7.1.2",
728
+ "package-json-from-dist": "^1.0.0",
729
+ "path-scurry": "^2.0.0"
730
+ },
731
+ "bin": {
732
+ "glob": "dist/esm/bin.mjs"
733
+ },
734
+ "engines": {
735
+ "node": "20 || >=22"
736
+ },
737
+ "funding": {
738
+ "url": "https://github.com/sponsors/isaacs"
739
+ }
740
+ },
741
  "node_modules/gopd": {
742
  "version": "1.2.0",
743
  "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
 
872
  "node": ">= 0.10"
873
  }
874
  },
875
+ "node_modules/isexe": {
876
+ "version": "2.0.0",
877
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
878
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
879
+ "license": "ISC"
880
+ },
881
+ "node_modules/jackspeak": {
882
+ "version": "4.2.3",
883
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz",
884
+ "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==",
885
+ "license": "BlueOak-1.0.0",
886
+ "dependencies": {
887
+ "@isaacs/cliui": "^9.0.0"
888
+ },
889
+ "engines": {
890
+ "node": "20 || >=22"
891
+ },
892
+ "funding": {
893
+ "url": "https://github.com/sponsors/isaacs"
894
+ }
895
+ },
896
+ "node_modules/js-yaml": {
897
+ "version": "4.2.0",
898
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
899
+ "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
900
+ "funding": [
901
+ {
902
+ "type": "github",
903
+ "url": "https://github.com/sponsors/puzrin"
904
+ },
905
+ {
906
+ "type": "github",
907
+ "url": "https://github.com/sponsors/nodeca"
908
+ }
909
+ ],
910
+ "license": "MIT",
911
+ "dependencies": {
912
+ "argparse": "^2.0.1"
913
+ },
914
+ "bin": {
915
+ "js-yaml": "bin/js-yaml.js"
916
+ }
917
+ },
918
+ "node_modules/json-schema-traverse": {
919
+ "version": "1.0.0",
920
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
921
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
922
+ "license": "MIT"
923
+ },
924
+ "node_modules/lodash.mergewith": {
925
+ "version": "4.6.2",
926
+ "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
927
+ "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==",
928
+ "license": "MIT"
929
+ },
930
+ "node_modules/lru-cache": {
931
+ "version": "11.5.1",
932
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
933
+ "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
934
+ "license": "BlueOak-1.0.0",
935
+ "engines": {
936
+ "node": "20 || >=22"
937
+ }
938
+ },
939
  "node_modules/math-intrinsics": {
940
  "version": "1.1.0",
941
  "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
 
1005
  "node": ">= 0.6"
1006
  }
1007
  },
1008
+ "node_modules/minimatch": {
1009
+ "version": "10.2.5",
1010
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
1011
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
1012
+ "license": "BlueOak-1.0.0",
1013
+ "dependencies": {
1014
+ "brace-expansion": "^5.0.5"
1015
+ },
1016
+ "engines": {
1017
+ "node": "18 || 20 || >=22"
1018
+ },
1019
+ "funding": {
1020
+ "url": "https://github.com/sponsors/isaacs"
1021
+ }
1022
+ },
1023
+ "node_modules/minipass": {
1024
+ "version": "7.1.3",
1025
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
1026
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
1027
+ "license": "BlueOak-1.0.0",
1028
+ "engines": {
1029
+ "node": ">=16 || 14 >=14.17"
1030
+ }
1031
+ },
1032
  "node_modules/ms": {
1033
  "version": "2.0.0",
1034
  "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
 
1068
  "node": ">= 0.8"
1069
  }
1070
  },
1071
+ "node_modules/openapi-types": {
1072
+ "version": "12.1.3",
1073
+ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
1074
+ "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==",
1075
+ "license": "MIT",
1076
+ "peer": true
1077
+ },
1078
+ "node_modules/package-json-from-dist": {
1079
+ "version": "1.0.1",
1080
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
1081
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
1082
+ "license": "BlueOak-1.0.0"
1083
+ },
1084
  "node_modules/parseurl": {
1085
  "version": "1.3.3",
1086
  "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
 
1090
  "node": ">= 0.8"
1091
  }
1092
  },
1093
+ "node_modules/path-key": {
1094
+ "version": "3.1.1",
1095
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
1096
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
1097
+ "license": "MIT",
1098
+ "engines": {
1099
+ "node": ">=8"
1100
+ }
1101
+ },
1102
+ "node_modules/path-scurry": {
1103
+ "version": "2.0.2",
1104
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
1105
+ "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
1106
+ "license": "BlueOak-1.0.0",
1107
+ "dependencies": {
1108
+ "lru-cache": "^11.0.0",
1109
+ "minipass": "^7.1.2"
1110
+ },
1111
+ "engines": {
1112
+ "node": "18 || 20 || >=22"
1113
+ },
1114
+ "funding": {
1115
+ "url": "https://github.com/sponsors/isaacs"
1116
+ }
1117
+ },
1118
  "node_modules/path-to-regexp": {
1119
  "version": "0.1.13",
1120
  "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
 
1182
  "node": ">= 0.8"
1183
  }
1184
  },
1185
+ "node_modules/require-from-string": {
1186
+ "version": "2.0.2",
1187
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
1188
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
1189
+ "license": "MIT",
1190
+ "engines": {
1191
+ "node": ">=0.10.0"
1192
+ }
1193
+ },
1194
  "node_modules/safe-buffer": {
1195
  "version": "5.2.1",
1196
  "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
 
1268
  "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
1269
  "license": "ISC"
1270
  },
1271
+ "node_modules/shebang-command": {
1272
+ "version": "2.0.0",
1273
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
1274
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
1275
+ "license": "MIT",
1276
+ "dependencies": {
1277
+ "shebang-regex": "^3.0.0"
1278
+ },
1279
+ "engines": {
1280
+ "node": ">=8"
1281
+ }
1282
+ },
1283
+ "node_modules/shebang-regex": {
1284
+ "version": "3.0.0",
1285
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
1286
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
1287
+ "license": "MIT",
1288
+ "engines": {
1289
+ "node": ">=8"
1290
+ }
1291
+ },
1292
  "node_modules/side-channel": {
1293
  "version": "1.1.0",
1294
  "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
 
1361
  "url": "https://github.com/sponsors/ljharb"
1362
  }
1363
  },
1364
+ "node_modules/signal-exit": {
1365
+ "version": "4.1.0",
1366
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
1367
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
1368
+ "license": "ISC",
1369
+ "engines": {
1370
+ "node": ">=14"
1371
+ },
1372
+ "funding": {
1373
+ "url": "https://github.com/sponsors/isaacs"
1374
+ }
1375
+ },
1376
  "node_modules/statuses": {
1377
  "version": "2.0.2",
1378
  "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
 
1382
  "node": ">= 0.8"
1383
  }
1384
  },
1385
+ "node_modules/swagger-jsdoc": {
1386
+ "version": "6.3.0",
1387
+ "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.3.0.tgz",
1388
+ "integrity": "sha512-I+iQjVGV3t28pOkQUJv2MncthvOtkEactOn8R76SvSYhxgtIn7FoqfDHwQaN+GBnQdXQLrhgDXseKitmJcHMsA==",
1389
+ "license": "MIT",
1390
+ "dependencies": {
1391
+ "@apidevtools/swagger-parser": "^12.1.0",
1392
+ "commander": "6.2.0",
1393
+ "doctrine": "3.0.0",
1394
+ "glob": "11.1.0",
1395
+ "lodash.mergewith": "^4.6.2",
1396
+ "yaml": "2.0.0-1"
1397
+ },
1398
+ "bin": {
1399
+ "swagger-jsdoc": "bin/swagger-jsdoc.js"
1400
+ },
1401
+ "engines": {
1402
+ "node": ">=20.0.0"
1403
+ }
1404
+ },
1405
+ "node_modules/swagger-ui-dist": {
1406
+ "version": "5.32.6",
1407
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.6.tgz",
1408
+ "integrity": "sha512-75ttZNaYCLoFPnozPZcTUU6mS3wKT8l7WLjU5zJSHFeJa23i5vtnze6IiCl4jDMPeQTXVXIgovq4M11NNfQvSA==",
1409
+ "license": "Apache-2.0",
1410
+ "dependencies": {
1411
+ "@scarf/scarf": "=1.4.0"
1412
+ }
1413
+ },
1414
+ "node_modules/swagger-ui-express": {
1415
+ "version": "5.0.1",
1416
+ "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz",
1417
+ "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==",
1418
+ "license": "MIT",
1419
+ "dependencies": {
1420
+ "swagger-ui-dist": ">=5.0.0"
1421
+ },
1422
+ "engines": {
1423
+ "node": ">= v0.10.32"
1424
+ },
1425
+ "peerDependencies": {
1426
+ "express": ">=4.0.0 || >=5.0.0-beta"
1427
+ }
1428
+ },
1429
  "node_modules/toidentifier": {
1430
  "version": "1.0.1",
1431
  "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
 
1474
  "engines": {
1475
  "node": ">= 0.8"
1476
  }
1477
+ },
1478
+ "node_modules/which": {
1479
+ "version": "2.0.2",
1480
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
1481
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
1482
+ "license": "ISC",
1483
+ "dependencies": {
1484
+ "isexe": "^2.0.0"
1485
+ },
1486
+ "bin": {
1487
+ "node-which": "bin/node-which"
1488
+ },
1489
+ "engines": {
1490
+ "node": ">= 8"
1491
+ }
1492
+ },
1493
+ "node_modules/yaml": {
1494
+ "version": "2.0.0-1",
1495
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz",
1496
+ "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==",
1497
+ "license": "ISC",
1498
+ "engines": {
1499
+ "node": ">= 6"
1500
+ }
1501
  }
1502
  }
1503
  }
package.json CHANGED
@@ -11,6 +11,8 @@
11
  "dependencies": {
12
  "axios": "^1.17.0",
13
  "dotenv": "^16.0.3",
14
- "express": "^4.18.2"
 
 
15
  }
16
  }
 
11
  "dependencies": {
12
  "axios": "^1.17.0",
13
  "dotenv": "^16.0.3",
14
+ "express": "^4.18.2",
15
+ "swagger-jsdoc": "^6.3.0",
16
+ "swagger-ui-express": "^5.0.1"
17
  }
18
  }
server.js CHANGED
@@ -1,10 +1,35 @@
1
  const express = require("express");
2
  const axios = require("axios");
 
 
3
  require("dotenv").config();
4
 
5
  const app = express();
6
  app.use(express.json());
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  // Environment variables
9
  const SQLITE_SERVER = process.env.DB_URL || "http://localhost:8000";
10
  const DATABASE = process.env.DATABASE || "default";
@@ -32,6 +57,18 @@ async function querySQLite(sql, method = "POST", endpoint = "/api/v1/query") {
32
  }
33
 
34
  // Health check
 
 
 
 
 
 
 
 
 
 
 
 
35
  app.get("/health", async (req, res) => {
36
  try {
37
  const response = await axios.get(`${SQLITE_SERVER}/api/v1/heartbeat`);
@@ -46,6 +83,18 @@ app.get("/health", async (req, res) => {
46
  });
47
 
48
  // Get all products
 
 
 
 
 
 
 
 
 
 
 
 
49
  app.get("/api/products", async (req, res) => {
50
  try {
51
  const result = await querySQLite(
@@ -55,11 +104,30 @@ app.get("/api/products", async (req, res) => {
55
  );
56
  res.status(200).json(result.data || []);
57
  } catch (err) {
58
- res.status(500).json({ error: "Failed to fetch products" });
 
59
  }
60
  });
61
 
62
  // Get product by ID
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  app.get("/api/products/:id", async (req, res) => {
64
  try {
65
  const result = await querySQLite(
@@ -78,6 +146,31 @@ app.get("/api/products/:id", async (req, res) => {
78
  });
79
 
80
  // Create product
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  app.post("/api/products", async (req, res) => {
82
  try {
83
  const { name, price, stock } = req.body;
@@ -102,6 +195,34 @@ app.post("/api/products", async (req, res) => {
102
  });
103
 
104
  // Update product
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  app.put("/api/products/:id", async (req, res) => {
106
  try {
107
  const { name, price, stock } = req.body;
@@ -129,6 +250,22 @@ app.put("/api/products/:id", async (req, res) => {
129
  });
130
 
131
  // Delete product
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  app.delete("/api/products/:id", async (req, res) => {
133
  try {
134
  const sql = `DELETE FROM products WHERE id = ${req.params.id}`;
@@ -145,6 +282,16 @@ app.delete("/api/products/:id", async (req, res) => {
145
  });
146
 
147
  // Get all orders
 
 
 
 
 
 
 
 
 
 
148
  app.get("/api/orders", async (req, res) => {
149
  try {
150
  const result = await querySQLite(
@@ -159,6 +306,29 @@ app.get("/api/orders", async (req, res) => {
159
  });
160
 
161
  // Create order
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  app.post("/api/orders", async (req, res) => {
163
  try {
164
  const { customer_name, total, status } = req.body;
@@ -183,6 +353,16 @@ app.post("/api/orders", async (req, res) => {
183
  });
184
 
185
  // Get database info
 
 
 
 
 
 
 
 
 
 
186
  app.get("/api/info", async (req, res) => {
187
  try {
188
  const response = await axios.get(
 
1
  const express = require("express");
2
  const axios = require("axios");
3
+ const swaggerUi = require("swagger-ui-express");
4
+ const swaggerJsdoc = require("swagger-jsdoc");
5
  require("dotenv").config();
6
 
7
  const app = express();
8
  app.use(express.json());
9
 
10
+ // Swagger configuration
11
+ const swaggerOptions = {
12
+ definition: {
13
+ openapi: "3.0.0",
14
+ info: {
15
+ title: "SQLite Express API",
16
+ version: "1.0.0",
17
+ description: "Express.js server connected to remote SQLite database",
18
+ },
19
+ servers: [
20
+ {
21
+ url: `http://localhost:${process.env.PORT || 3000}`,
22
+ description: "Development server",
23
+ },
24
+ ],
25
+ },
26
+ apis: ["./server.js"],
27
+ };
28
+
29
+ const swaggerSpec = swaggerJsdoc(swaggerOptions);
30
+ app.use("/swagger", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
31
+ app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
32
+
33
  // Environment variables
34
  const SQLITE_SERVER = process.env.DB_URL || "http://localhost:8000";
35
  const DATABASE = process.env.DATABASE || "default";
 
57
  }
58
 
59
  // Health check
60
+ /**
61
+ * @swagger
62
+ * /health:
63
+ * get:
64
+ * summary: Health check
65
+ * description: Check if server and database are healthy
66
+ * responses:
67
+ * 200:
68
+ * description: Server is healthy
69
+ * 500:
70
+ * description: Server error
71
+ */
72
  app.get("/health", async (req, res) => {
73
  try {
74
  const response = await axios.get(`${SQLITE_SERVER}/api/v1/heartbeat`);
 
83
  });
84
 
85
  // Get all products
86
+ /**
87
+ * @swagger
88
+ * /api/products:
89
+ * get:
90
+ * summary: Get all products
91
+ * description: Retrieve all products from the database
92
+ * responses:
93
+ * 200:
94
+ * description: List of products
95
+ * 500:
96
+ * description: Failed to fetch products
97
+ */
98
  app.get("/api/products", async (req, res) => {
99
  try {
100
  const result = await querySQLite(
 
104
  );
105
  res.status(200).json(result.data || []);
106
  } catch (err) {
107
+ console.error("GET /api/products error:", err.message);
108
+ res.status(500).json({ error: err.message || "Failed to fetch products" });
109
  }
110
  });
111
 
112
  // Get product by ID
113
+ /**
114
+ * @swagger
115
+ * /api/products/{id}:
116
+ * get:
117
+ * summary: Get product by ID
118
+ * description: Retrieve a specific product
119
+ * parameters:
120
+ * - in: path
121
+ * name: id
122
+ * required: true
123
+ * schema:
124
+ * type: integer
125
+ * responses:
126
+ * 200:
127
+ * description: Product found
128
+ * 404:
129
+ * description: Product not found
130
+ */
131
  app.get("/api/products/:id", async (req, res) => {
132
  try {
133
  const result = await querySQLite(
 
146
  });
147
 
148
  // Create product
149
+ /**
150
+ * @swagger
151
+ * /api/products:
152
+ * post:
153
+ * summary: Create a new product
154
+ * description: Add a new product to the database
155
+ * requestBody:
156
+ * required: true
157
+ * content:
158
+ * application/json:
159
+ * schema:
160
+ * type: object
161
+ * properties:
162
+ * name:
163
+ * type: string
164
+ * price:
165
+ * type: number
166
+ * stock:
167
+ * type: integer
168
+ * responses:
169
+ * 201:
170
+ * description: Product created
171
+ * 400:
172
+ * description: Missing required fields
173
+ */
174
  app.post("/api/products", async (req, res) => {
175
  try {
176
  const { name, price, stock } = req.body;
 
195
  });
196
 
197
  // Update product
198
+ /**
199
+ * @swagger
200
+ * /api/products/{id}:
201
+ * put:
202
+ * summary: Update a product
203
+ * description: Update product information
204
+ * parameters:
205
+ * - in: path
206
+ * name: id
207
+ * required: true
208
+ * schema:
209
+ * type: integer
210
+ * requestBody:
211
+ * content:
212
+ * application/json:
213
+ * schema:
214
+ * type: object
215
+ * properties:
216
+ * name:
217
+ * type: string
218
+ * price:
219
+ * type: number
220
+ * stock:
221
+ * type: integer
222
+ * responses:
223
+ * 200:
224
+ * description: Product updated
225
+ */
226
  app.put("/api/products/:id", async (req, res) => {
227
  try {
228
  const { name, price, stock } = req.body;
 
250
  });
251
 
252
  // Delete product
253
+ /**
254
+ * @swagger
255
+ * /api/products/{id}:
256
+ * delete:
257
+ * summary: Delete a product
258
+ * description: Remove a product from the database
259
+ * parameters:
260
+ * - in: path
261
+ * name: id
262
+ * required: true
263
+ * schema:
264
+ * type: integer
265
+ * responses:
266
+ * 200:
267
+ * description: Product deleted
268
+ */
269
  app.delete("/api/products/:id", async (req, res) => {
270
  try {
271
  const sql = `DELETE FROM products WHERE id = ${req.params.id}`;
 
282
  });
283
 
284
  // Get all orders
285
+ /**
286
+ * @swagger
287
+ * /api/orders:
288
+ * get:
289
+ * summary: Get all orders
290
+ * description: Retrieve all orders from the database
291
+ * responses:
292
+ * 200:
293
+ * description: List of orders
294
+ */
295
  app.get("/api/orders", async (req, res) => {
296
  try {
297
  const result = await querySQLite(
 
306
  });
307
 
308
  // Create order
309
+ /**
310
+ * @swagger
311
+ * /api/orders:
312
+ * post:
313
+ * summary: Create a new order
314
+ * description: Add a new order to the database
315
+ * requestBody:
316
+ * required: true
317
+ * content:
318
+ * application/json:
319
+ * schema:
320
+ * type: object
321
+ * properties:
322
+ * customer_name:
323
+ * type: string
324
+ * total:
325
+ * type: number
326
+ * status:
327
+ * type: string
328
+ * responses:
329
+ * 201:
330
+ * description: Order created
331
+ */
332
  app.post("/api/orders", async (req, res) => {
333
  try {
334
  const { customer_name, total, status } = req.body;
 
353
  });
354
 
355
  // Get database info
356
+ /**
357
+ * @swagger
358
+ * /api/info:
359
+ * get:
360
+ * summary: Get database info
361
+ * description: Retrieve database schema and server information
362
+ * responses:
363
+ * 200:
364
+ * description: Database information
365
+ */
366
  app.get("/api/info", async (req, res) => {
367
  try {
368
  const response = await axios.get(