jxp 2.16.0 → 4.0.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.
Files changed (218) hide show
  1. package/.env.sample +31 -6
  2. package/README.md +27 -13
  3. package/dist/bin/init_db.d.ts +3 -0
  4. package/dist/bin/init_db.d.ts.map +1 -0
  5. package/dist/bin/init_db.js +48 -0
  6. package/dist/bin/init_db.js.map +1 -0
  7. package/dist/bin/server.d.ts +4 -0
  8. package/dist/bin/server.d.ts.map +1 -0
  9. package/dist/bin/server.js +132 -0
  10. package/dist/bin/server.js.map +1 -0
  11. package/dist/bin/setup.d.ts +3 -0
  12. package/dist/bin/setup.d.ts.map +1 -0
  13. package/dist/bin/setup.js +185 -0
  14. package/dist/bin/setup.js.map +1 -0
  15. package/dist/globals.d.ts +14 -0
  16. package/dist/globals.d.ts.map +1 -0
  17. package/dist/globals.js +3 -0
  18. package/dist/globals.js.map +1 -0
  19. package/dist/libs/aggregate_guard.d.ts +5 -0
  20. package/dist/libs/aggregate_guard.d.ts.map +1 -0
  21. package/dist/libs/aggregate_guard.js +58 -0
  22. package/dist/libs/aggregate_guard.js.map +1 -0
  23. package/dist/libs/bulkwrite_guard.d.ts +5 -0
  24. package/dist/libs/bulkwrite_guard.d.ts.map +1 -0
  25. package/dist/libs/bulkwrite_guard.js +44 -0
  26. package/dist/libs/bulkwrite_guard.js.map +1 -0
  27. package/dist/libs/cache.d.ts +2 -0
  28. package/dist/libs/cache.d.ts.map +1 -0
  29. package/dist/libs/cache.js +94 -0
  30. package/dist/libs/cache.js.map +1 -0
  31. package/dist/libs/call_guard.d.ts +3 -0
  32. package/dist/libs/call_guard.d.ts.map +1 -0
  33. package/dist/libs/call_guard.js +17 -0
  34. package/dist/libs/call_guard.js.map +1 -0
  35. package/dist/libs/connection_string.d.ts +3 -0
  36. package/dist/libs/connection_string.d.ts.map +1 -0
  37. package/dist/libs/connection_string.js +4 -0
  38. package/dist/libs/connection_string.js.map +1 -0
  39. package/dist/libs/datamunging.d.ts +2 -0
  40. package/dist/libs/datamunging.d.ts.map +1 -0
  41. package/dist/libs/datamunging.js +320 -0
  42. package/dist/libs/datamunging.js.map +1 -0
  43. package/dist/libs/docs.d.ts +2 -0
  44. package/dist/libs/docs.d.ts.map +1 -0
  45. package/dist/libs/docs.js +307 -0
  46. package/dist/libs/docs.js.map +1 -0
  47. package/dist/libs/groups.d.ts +2 -0
  48. package/dist/libs/groups.d.ts.map +1 -0
  49. package/dist/libs/groups.js +136 -0
  50. package/dist/libs/groups.js.map +1 -0
  51. package/dist/libs/jxp.d.ts +4 -0
  52. package/dist/libs/jxp.d.ts.map +1 -0
  53. package/dist/libs/jxp.js +1157 -0
  54. package/dist/libs/jxp.js.map +1 -0
  55. package/dist/libs/link_index.d.ts +14 -0
  56. package/dist/libs/link_index.d.ts.map +1 -0
  57. package/dist/libs/link_index.js +37 -0
  58. package/dist/libs/link_index.js.map +1 -0
  59. package/dist/libs/load-config.d.ts +16 -0
  60. package/dist/libs/load-config.d.ts.map +1 -0
  61. package/dist/libs/load-config.js +155 -0
  62. package/dist/libs/load-config.js.map +1 -0
  63. package/dist/libs/login.d.ts +2 -0
  64. package/dist/libs/login.d.ts.map +1 -0
  65. package/dist/libs/login.js +244 -0
  66. package/dist/libs/login.js.map +1 -0
  67. package/dist/libs/modeldir.d.ts +5 -0
  68. package/dist/libs/modeldir.d.ts.map +1 -0
  69. package/dist/libs/modeldir.js +15 -0
  70. package/dist/libs/modeldir.js.map +1 -0
  71. package/dist/libs/query_limits.d.ts +2 -0
  72. package/dist/libs/query_limits.d.ts.map +1 -0
  73. package/dist/libs/query_limits.js +84 -0
  74. package/dist/libs/query_limits.js.map +1 -0
  75. package/dist/libs/query_manipulation.d.ts +3 -0
  76. package/dist/libs/query_manipulation.d.ts.map +1 -0
  77. package/{libs → dist/libs}/query_manipulation.js +22 -21
  78. package/dist/libs/query_manipulation.js.map +1 -0
  79. package/dist/libs/query_sanitize.d.ts +5 -0
  80. package/dist/libs/query_sanitize.d.ts.map +1 -0
  81. package/dist/libs/query_sanitize.js +109 -0
  82. package/dist/libs/query_sanitize.js.map +1 -0
  83. package/dist/libs/response_sanitize.d.ts +7 -0
  84. package/dist/libs/response_sanitize.d.ts.map +1 -0
  85. package/dist/libs/response_sanitize.js +57 -0
  86. package/dist/libs/response_sanitize.js.map +1 -0
  87. package/dist/libs/safe_error.d.ts +3 -0
  88. package/dist/libs/safe_error.d.ts.map +1 -0
  89. package/dist/libs/safe_error.js +14 -0
  90. package/dist/libs/safe_error.js.map +1 -0
  91. package/dist/libs/schema.d.ts +14 -0
  92. package/dist/libs/schema.d.ts.map +1 -0
  93. package/dist/libs/schema.js +98 -0
  94. package/dist/libs/schema.js.map +1 -0
  95. package/dist/libs/schema_description.d.ts +3 -0
  96. package/dist/libs/schema_description.d.ts.map +1 -0
  97. package/{libs → dist/libs}/schema_description.js +12 -21
  98. package/dist/libs/schema_description.js.map +1 -0
  99. package/dist/libs/security.d.ts +2 -0
  100. package/dist/libs/security.d.ts.map +1 -0
  101. package/dist/libs/security.js +428 -0
  102. package/dist/libs/security.js.map +1 -0
  103. package/dist/libs/setup.d.ts +2 -0
  104. package/dist/libs/setup.d.ts.map +1 -0
  105. package/dist/libs/setup.js +87 -0
  106. package/dist/libs/setup.js.map +1 -0
  107. package/dist/libs/startup-deprecations.d.ts +2 -0
  108. package/dist/libs/startup-deprecations.d.ts.map +1 -0
  109. package/dist/libs/startup-deprecations.js +21 -0
  110. package/dist/libs/startup-deprecations.js.map +1 -0
  111. package/dist/libs/startup.d.ts +16 -0
  112. package/dist/libs/startup.d.ts.map +1 -0
  113. package/dist/libs/startup.js +63 -0
  114. package/dist/libs/startup.js.map +1 -0
  115. package/dist/libs/ws.d.ts +2 -0
  116. package/dist/libs/ws.d.ts.map +1 -0
  117. package/{libs → dist/libs}/ws.js +114 -73
  118. package/dist/libs/ws.js.map +1 -0
  119. package/dist/models/apikey_model.d.ts +10 -0
  120. package/dist/models/apikey_model.d.ts.map +1 -0
  121. package/dist/models/apikey_model.js +14 -0
  122. package/dist/models/apikey_model.js.map +1 -0
  123. package/dist/models/link_model.d.ts +9 -0
  124. package/dist/models/link_model.d.ts.map +1 -0
  125. package/dist/models/link_model.js +20 -0
  126. package/dist/models/link_model.js.map +1 -0
  127. package/dist/models/refreshtoken_model.d.ts +11 -0
  128. package/dist/models/refreshtoken_model.d.ts.map +1 -0
  129. package/dist/models/refreshtoken_model.js +22 -0
  130. package/dist/models/refreshtoken_model.js.map +1 -0
  131. package/dist/models/test_model.d.ts +24 -0
  132. package/dist/models/test_model.d.ts.map +1 -0
  133. package/dist/models/test_model.js +44 -0
  134. package/dist/models/test_model.js.map +1 -0
  135. package/dist/models/token_model.d.ts +14 -0
  136. package/dist/models/token_model.d.ts.map +1 -0
  137. package/dist/models/token_model.js +25 -0
  138. package/dist/models/token_model.js.map +1 -0
  139. package/dist/models/user_model.d.ts +15 -0
  140. package/dist/models/user_model.d.ts.map +1 -0
  141. package/dist/models/user_model.js +38 -0
  142. package/dist/models/user_model.js.map +1 -0
  143. package/dist/models/usergroups_model.d.ts +9 -0
  144. package/dist/models/usergroups_model.d.ts.map +1 -0
  145. package/dist/models/usergroups_model.js +18 -0
  146. package/dist/models/usergroups_model.js.map +1 -0
  147. package/dist/types/jxp-config.d.ts +134 -0
  148. package/dist/types/jxp-config.d.ts.map +1 -0
  149. package/dist/types/jxp-config.js +3 -0
  150. package/dist/types/jxp-config.js.map +1 -0
  151. package/dist/types/schema-fields.d.ts +43 -0
  152. package/dist/types/schema-fields.d.ts.map +1 -0
  153. package/dist/types/schema-fields.js +3 -0
  154. package/dist/types/schema-fields.js.map +1 -0
  155. package/docs/aggregations.md +40 -21
  156. package/docs/api.md +55 -15
  157. package/docs/authentication.md +60 -37
  158. package/docs/bulk_writes.md +11 -8
  159. package/docs/caching.md +28 -11
  160. package/docs/changelog.md +237 -31
  161. package/docs/configuration.md +99 -118
  162. package/docs/hooks.md +12 -8
  163. package/docs/index.md +17 -17
  164. package/docs/installation.md +23 -18
  165. package/docs/permissions.md +33 -19
  166. package/docs/queries.md +9 -5
  167. package/docs/schemas.md +21 -10
  168. package/docs/special.md +4 -4
  169. package/docs/typescript.md +86 -0
  170. package/docs/websocket.md +99 -1
  171. package/mkdocs.yml +1 -0
  172. package/package.json +121 -69
  173. package/templates/api.pug +32 -0
  174. package/templates/app-server.js +43 -0
  175. package/templates/app.env.sample +8 -0
  176. package/templates/assets/api-console.js +133 -0
  177. package/templates/assets/docs.css +227 -0
  178. package/templates/index.pug +62 -2
  179. package/templates/layout.pug +17 -11
  180. package/templates/md.pug +1 -1
  181. package/templates/model.pug +117 -68
  182. package/templates/navbar.pug +14 -21
  183. package/templates/sidebar.pug +30 -0
  184. package/.env +0 -9
  185. package/.eslintrc.json +0 -20
  186. package/.jshintrc +0 -4
  187. package/.vscode/launch.json +0 -29
  188. package/.vscode/settings.json +0 -3
  189. package/Dockerfile +0 -7
  190. package/bin/init_db.js +0 -54
  191. package/bin/server.js +0 -84
  192. package/bin/setup.js +0 -190
  193. package/config_sample.json +0 -10
  194. package/docker-compose.yml +0 -32
  195. package/libs/cache.js +0 -92
  196. package/libs/connection_string.js +0 -24
  197. package/libs/datamunging.js +0 -335
  198. package/libs/docs.js +0 -162
  199. package/libs/groups.js +0 -133
  200. package/libs/jxp.js +0 -1208
  201. package/libs/login.js +0 -218
  202. package/libs/modeldir.js +0 -14
  203. package/libs/schema.js +0 -99
  204. package/libs/security.js +0 -410
  205. package/libs/setup.js +0 -90
  206. package/models/apikey_model.js +0 -10
  207. package/models/link_model.js +0 -17
  208. package/models/refreshtoken_model.js +0 -20
  209. package/models/test_model.js +0 -50
  210. package/models/token_model.js +0 -23
  211. package/models/user_model.js +0 -38
  212. package/models/usergroups_model.js +0 -15
  213. package/setup.sh +0 -3
  214. package/test/.eslintrc.json +0 -13
  215. package/test/init.js +0 -92
  216. package/test/setup.test.js +0 -62
  217. package/test/test.js +0 -1527
  218. package/test/wstest.js.old +0 -124
package/package.json CHANGED
@@ -1,70 +1,122 @@
1
1
  {
2
- "name": "jxp",
3
- "description:": "An opinionated RESTful API library based on Mongoose and Restify. Make an API by just writing Mongoose models.",
4
- "version": "2.16.0",
5
- "private": false,
6
- "main": "libs/jxp.js",
7
- "scripts": {
8
- "start": "node bin/server.js",
9
- "test": "mocha --timeout 1000 -b --exit",
10
- "wstest": "mocha test/wstest",
11
- "setup": "node bin/setup.js"
12
- },
13
- "bin": {
14
- "jxp": "bin/setup.js",
15
- "jxp-setup": "bin/setup.js",
16
- "jxp-add-admin": "bin/init_db.js"
17
- },
18
- "repository": {
19
- "type": "git",
20
- "url": "git://github.com/WorkSpaceMan/jxp.git"
21
- },
22
- "keywords": [
23
- "api",
24
- "mongoose"
25
- ],
26
- "author": "Jason Norwood-Young <jason@10layer.com> (https://github.com/j-norwood-young/)",
27
- "license": "MIT",
28
- "bugs": {
29
- "url": "https://github.com/WorkSpaceMan/jxp/issues"
30
- },
31
- "dependencies": {
32
- "axios": "^1.8.4",
33
- "bcryptjs": "^3.0.2",
34
- "commander": "^13.1.0",
35
- "config": "^3.3.12",
36
- "dotenv": "^16.4.7",
37
- "glob": "11.0.1",
38
- "js-yaml": "4.1.0",
39
- "json2csv": "^5.0.7",
40
- "jsonwebtoken": "^9.0.2",
41
- "jstransformer-markdown-it": "^3.0.0",
42
- "jxp-helper": "^1.4.3",
43
- "mkdirp": "^3.0.1",
44
- "moment": "^2.30.1",
45
- "mongoose": "6.12.6",
46
- "mongoose-friendly": "^0.1.4",
47
- "morgan": "^1.10.0",
48
- "node-cache": "^5.1.2",
49
- "nodemailer": "^6.10.0",
50
- "nodemailer-smtp-transport": "^2.7.4",
51
- "path": "^0.12.7",
52
- "pug": "^3.0.3",
53
- "querystring": "^0.2.1",
54
- "rand-token": "^1.0.1",
55
- "readline-sync": "^1.4.10",
56
- "restify": "^11.1.0",
57
- "restify-cors-middleware2": "^2.2.1",
58
- "restify-errors": "^8.0.2",
59
- "traverse": "^0.6.11",
60
- "underscore": "^1.13.7",
61
- "ws": "^8.18.1"
62
- },
63
- "devDependencies": {
64
- "chai": "^4.3.10",
65
- "chai-http": "^4.4.0",
66
- "mocha": "^10.2.0",
67
- "moment": "^2.29.4",
68
- "should": "^13.2.3"
69
- }
70
- }
2
+ "name": "jxp",
3
+ "description": "An opinionated RESTful API library based on Mongoose and Restify. Make an API by just writing Mongoose models.",
4
+ "version": "4.0.0",
5
+ "private": false,
6
+ "main": "dist/libs/jxp.js",
7
+ "types": "dist/libs/jxp.d.ts",
8
+ "files": [
9
+ "dist",
10
+ ".env.sample",
11
+ "templates",
12
+ "docs",
13
+ "mkdocs.yml"
14
+ ],
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/libs/jxp.d.ts",
18
+ "default": "./dist/libs/jxp.js"
19
+ },
20
+ "./libs/schema": {
21
+ "types": "./dist/libs/schema.d.ts",
22
+ "default": "./dist/libs/schema.js"
23
+ },
24
+ "./libs/query_manipulation": {
25
+ "types": "./dist/libs/query_manipulation.d.ts",
26
+ "default": "./dist/libs/query_manipulation.js"
27
+ },
28
+ "./globals": {
29
+ "types": "./dist/globals.d.ts",
30
+ "default": "./dist/globals.js"
31
+ },
32
+ "./libs/load-config": {
33
+ "types": "./dist/libs/load-config.d.ts",
34
+ "default": "./dist/libs/load-config.js"
35
+ },
36
+ "./libs/connection_string": {
37
+ "types": "./dist/libs/connection_string.d.ts",
38
+ "default": "./dist/libs/connection_string.js"
39
+ },
40
+ "./types/jxp-config": {
41
+ "types": "./dist/types/jxp-config.d.ts"
42
+ }
43
+ },
44
+ "scripts": {
45
+ "build": "tsc -p tsconfig.build.json",
46
+ "prepublishOnly": "npm run build",
47
+ "prepare": "node -e \"require('fs').existsSync('src')&&require('child_process').execSync('npm run build',{stdio:'inherit'})\"",
48
+ "start": "node dist/bin/server.js",
49
+ "dev": "nodemon --watch src --ext ts --exec 'npm run build && node dist/bin/server.js'",
50
+ "test": "npm run build && mocha --require test/env.js --timeout 1000 -b --exit",
51
+ "setup": "node dist/bin/setup.js"
52
+ },
53
+ "bin": {
54
+ "jxp": "dist/bin/setup.js",
55
+ "jxp-setup": "dist/bin/setup.js",
56
+ "jxp-add-admin": "dist/bin/init_db.js"
57
+ },
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "git://github.com/WorkSpaceMan/jxp.git"
61
+ },
62
+ "keywords": [
63
+ "api",
64
+ "mongoose",
65
+ "typescript"
66
+ ],
67
+ "author": "Jason Norwood-Young <jason@10layer.com> (https://github.com/j-norwood-young/)",
68
+ "license": "MIT",
69
+ "bugs": {
70
+ "url": "https://github.com/WorkSpaceMan/jxp/issues"
71
+ },
72
+ "dependencies": {
73
+ "bcryptjs": "^3.0.2",
74
+ "commander": "^13.1.0",
75
+ "dotenv": "^16.5.0",
76
+ "glob": "11.1.0",
77
+ "js-yaml": "4.1.1",
78
+ "@json2csv/plainjs": "^7.0.6",
79
+ "jsonwebtoken": "^9.0.2",
80
+ "markdown-it": "^14.1.1",
81
+ "jxp-helper": "^1.4.3",
82
+ "moment": "^2.30.1",
83
+ "mongoose": "6.13.9",
84
+ "mongoose-friendly": "^0.1.4",
85
+ "morgan": "^1.10.0",
86
+ "node-cache": "^5.1.2",
87
+ "nodemailer": "^8.0.7",
88
+ "pug": "^3.0.3",
89
+ "rand-token": "^1.0.1",
90
+ "readline-sync": "^1.4.10",
91
+ "restify": "^11.1.0",
92
+ "restify-cors-middleware2": "^2.2.1",
93
+ "restify-errors": "^8.0.2",
94
+ "traverse": "^0.6.11",
95
+ "underscore": "^1.13.8",
96
+ "ws": "^8.18.1"
97
+ },
98
+ "devDependencies": {
99
+ "@types/markdown-it": "^14.1.2",
100
+ "@types/bcryptjs": "^2.4.6",
101
+ "@types/jsonwebtoken": "^9.0.9",
102
+ "@types/morgan": "^1.9.9",
103
+ "@types/node": "^22.15.21",
104
+ "@types/underscore": "^1.13.0",
105
+ "@types/ws": "^8.18.1",
106
+ "chai": "^4.3.10",
107
+ "chai-http": "^4.4.0",
108
+ "mocha": "^11.7.5",
109
+ "nodemon": "^3.1.0",
110
+ "should": "^13.2.3",
111
+ "typescript": "^5.8.3"
112
+ },
113
+ "engines": {
114
+ "node": ">=22.0.0"
115
+ },
116
+ "overrides": {
117
+ "find-my-way": "^8.2.2",
118
+ "send": "^0.19.0",
119
+ "serialize-javascript": "^7.0.5",
120
+ "diff": "^8.0.3"
121
+ }
122
+ }
@@ -0,0 +1,32 @@
1
+ extends layout
2
+
3
+ block content
4
+ h1.mb-1 API reference
5
+ p.text-muted.mb-4 Explore resources, try requests with your API key, and inspect schemas.
6
+
7
+ if model_summaries.length
8
+ .table-responsive
9
+ table.table.table-striped.table-hover.align-middle
10
+ thead
11
+ tr
12
+ th Resource
13
+ th Collection
14
+ th Permissions
15
+ th
16
+ tbody
17
+ each summary in model_summaries
18
+ tr
19
+ td
20
+ a.fw-semibold(href="/docs/model/" + summary.slug)= summary.slug
21
+ td
22
+ code.small= summary.collection
23
+ td
24
+ if summary.perms
25
+ each value, key in summary.perms
26
+ span.badge.text-bg-secondary.me-1(title=key)= key + ': ' + value
27
+ else
28
+ span.badge.text-bg-warning No perms
29
+ td.text-end
30
+ a.btn.btn-sm.btn-outline-primary(href="/docs/model/" + summary.slug) Open
31
+ else
32
+ .alert.alert-info No API resources are registered.
@@ -0,0 +1,43 @@
1
+ require("dotenv").config();
2
+ const mongoose = require("mongoose");
3
+ const JXP = require("jxp");
4
+ const { loadJxpConfig, getMongoConnectionString } = require("jxp/libs/load-config");
5
+
6
+ const apiconfig = loadJxpConfig();
7
+
8
+ apiconfig.callbacks = {
9
+ post: function () {},
10
+ put: function () {},
11
+ delete: function () {},
12
+ };
13
+
14
+ apiconfig.pre_hooks = {
15
+ login: (req, res, next) => next(),
16
+ get: (req, res, next) => next(),
17
+ getOne: (req, res, next) => next(),
18
+ post: (req, res, next) => next(),
19
+ put: (req, res, next) => next(),
20
+ delete: (req, res, next) => next(),
21
+ };
22
+
23
+ mongoose.Promise = Promise;
24
+ const mongo_options = Object.assign({}, apiconfig.mongo?.options || {}, {
25
+ useNewUrlParser: true,
26
+ useUnifiedTopology: true,
27
+ });
28
+
29
+ mongoose.connect(getMongoConnectionString(), mongo_options);
30
+
31
+ mongoose.connection.on("error", console.error.bind(console, "connection error:"));
32
+ mongoose.connection.once("open", () => {
33
+ console.log(`Connected to Mongo at: ${new Date()}`);
34
+ });
35
+
36
+ const server = JXP(apiconfig);
37
+ const port = parseInt(process.env.PORT || String(apiconfig.port || 4001), 10);
38
+
39
+ server.listen(port, function () {
40
+ console.log("%s listening at %s", server.name, server.url);
41
+ });
42
+
43
+ module.exports = server;
@@ -0,0 +1,8 @@
1
+ PORT={port}
2
+ API_URL={url}
3
+ MONGO_CONNECTION_STRING={connection_string}
4
+ SHARED_SECRET={shared_secret}
5
+ TOKEN_EXPIRY=86400
6
+ REFRESH_TOKEN_EXPIRY=2678400
7
+ MODEL_DIR=./models
8
+ LOG_FILE=./logs/access.log
@@ -0,0 +1,133 @@
1
+ (function () {
2
+ "use strict";
3
+
4
+ const STORAGE_KEY = "jxp_docs_api_key";
5
+ const STORAGE_REMEMBER = "jxp_docs_remember_key";
6
+
7
+ function getApiKey() {
8
+ const input = document.getElementById("docs-api-key");
9
+ return input ? input.value.trim() : "";
10
+ }
11
+
12
+ function loadStoredKey() {
13
+ if (localStorage.getItem(STORAGE_REMEMBER) !== "1") return;
14
+ const key = localStorage.getItem(STORAGE_KEY);
15
+ if (!key) return;
16
+ const input = document.getElementById("docs-api-key");
17
+ const remember = document.getElementById("docs-remember-key");
18
+ if (input) input.value = key;
19
+ if (remember) remember.checked = true;
20
+ }
21
+
22
+ function saveKeyIfRemembered() {
23
+ const remember = document.getElementById("docs-remember-key");
24
+ const input = document.getElementById("docs-api-key");
25
+ if (!remember || !input) return;
26
+ if (remember.checked) {
27
+ localStorage.setItem(STORAGE_REMEMBER, "1");
28
+ localStorage.setItem(STORAGE_KEY, input.value.trim());
29
+ } else {
30
+ localStorage.removeItem(STORAGE_REMEMBER);
31
+ localStorage.removeItem(STORAGE_KEY);
32
+ }
33
+ }
34
+
35
+ function formatBody(text) {
36
+ if (!text) return "";
37
+ try {
38
+ return JSON.stringify(JSON.parse(text), null, 2);
39
+ } catch {
40
+ return text;
41
+ }
42
+ }
43
+
44
+ function formatResponse(text) {
45
+ if (!text) return "(empty)";
46
+ try {
47
+ return JSON.stringify(JSON.parse(text), null, 2);
48
+ } catch {
49
+ return text;
50
+ }
51
+ }
52
+
53
+ async function sendRequest(panel) {
54
+ const method = panel.dataset.method;
55
+ const pathInput = panel.querySelector(".api-path-input");
56
+ const bodyInput = panel.querySelector(".api-body-input");
57
+ const responseEl = panel.querySelector(".api-response");
58
+ const metaEl = panel.querySelector(".api-response-meta");
59
+ const btn = panel.querySelector(".api-send-btn");
60
+
61
+ const path = pathInput ? pathInput.value.trim() : panel.dataset.defaultPath;
62
+ if (!path.startsWith("/")) {
63
+ responseEl.textContent = "Path must start with /";
64
+ responseEl.classList.remove("empty");
65
+ return;
66
+ }
67
+
68
+ const headers = { Accept: "application/json" };
69
+ const apiKey = getApiKey();
70
+ if (apiKey) headers["X-API-Key"] = apiKey;
71
+
72
+ const opts = { method, headers };
73
+ if (bodyInput && (method === "POST" || method === "PUT")) {
74
+ const raw = bodyInput.value.trim();
75
+ if (raw) {
76
+ headers["Content-Type"] = "application/json";
77
+ opts.body = raw;
78
+ }
79
+ }
80
+
81
+ btn.disabled = true;
82
+ responseEl.textContent = "Sending…";
83
+ responseEl.classList.remove("empty");
84
+ metaEl.textContent = "";
85
+
86
+ const start = performance.now();
87
+ try {
88
+ const res = await fetch(path, opts);
89
+ const elapsed = Math.round(performance.now() - start);
90
+ const text = await res.text();
91
+ responseEl.textContent = formatResponse(text);
92
+ metaEl.textContent = `HTTP ${res.status} ${res.statusText} · ${elapsed} ms`;
93
+ metaEl.className = "api-response-meta " + (res.ok ? "text-success" : "text-danger");
94
+ } catch (err) {
95
+ responseEl.textContent = String(err.message || err);
96
+ metaEl.textContent = "Request failed";
97
+ metaEl.className = "api-response-meta text-danger";
98
+ } finally {
99
+ btn.disabled = false;
100
+ }
101
+ }
102
+
103
+ function initPanel(panel) {
104
+ const sendBtn = panel.querySelector(".api-send-btn");
105
+ if (sendBtn) {
106
+ sendBtn.addEventListener("click", function () {
107
+ saveKeyIfRemembered();
108
+ sendRequest(panel);
109
+ });
110
+ }
111
+ const bodyInput = panel.querySelector(".api-body-input");
112
+ if (bodyInput) {
113
+ bodyInput.addEventListener("blur", function () {
114
+ bodyInput.value = formatBody(bodyInput.value);
115
+ });
116
+ }
117
+ }
118
+
119
+ document.addEventListener("DOMContentLoaded", function () {
120
+ loadStoredKey();
121
+
122
+ const keyInput = document.getElementById("docs-api-key");
123
+ const remember = document.getElementById("docs-remember-key");
124
+ if (keyInput) {
125
+ keyInput.addEventListener("change", saveKeyIfRemembered);
126
+ }
127
+ if (remember) {
128
+ remember.addEventListener("change", saveKeyIfRemembered);
129
+ }
130
+
131
+ document.querySelectorAll(".api-try-panel").forEach(initPanel);
132
+ });
133
+ })();
@@ -0,0 +1,227 @@
1
+ :root {
2
+ --docs-sidebar-width: 280px;
3
+ --docs-topbar-height: 56px;
4
+ --docs-accent: #0d6efd;
5
+ --docs-sidebar-bg: #f8f9fa;
6
+ --docs-border: #dee2e6;
7
+ }
8
+
9
+ body.docs-body {
10
+ min-height: 100vh;
11
+ background: #fff;
12
+ }
13
+
14
+ .docs-topbar {
15
+ height: var(--docs-topbar-height);
16
+ border-bottom: 1px solid var(--docs-border);
17
+ background: #fff;
18
+ z-index: 1030;
19
+ }
20
+
21
+ .docs-brand {
22
+ font-weight: 600;
23
+ font-size: 1.1rem;
24
+ color: #212529;
25
+ text-decoration: none;
26
+ }
27
+
28
+ .docs-brand:hover {
29
+ color: var(--docs-accent);
30
+ }
31
+
32
+ .docs-shell {
33
+ display: flex;
34
+ min-height: calc(100vh - var(--docs-topbar-height));
35
+ }
36
+
37
+ .docs-sidebar {
38
+ width: var(--docs-sidebar-width);
39
+ flex-shrink: 0;
40
+ border-right: 1px solid var(--docs-border);
41
+ background: var(--docs-sidebar-bg);
42
+ overflow-y: auto;
43
+ padding: 1rem 0;
44
+ position: sticky;
45
+ top: var(--docs-topbar-height);
46
+ height: calc(100vh - var(--docs-topbar-height));
47
+ }
48
+
49
+ .docs-main {
50
+ flex: 1;
51
+ min-width: 0;
52
+ padding: 1.5rem 2rem 3rem;
53
+ max-width: 960px;
54
+ }
55
+
56
+ .docs-sidebar-section {
57
+ padding: 0 1rem;
58
+ margin-bottom: 1.25rem;
59
+ }
60
+
61
+ .docs-sidebar-title {
62
+ font-size: 0.7rem;
63
+ font-weight: 700;
64
+ text-transform: uppercase;
65
+ letter-spacing: 0.06em;
66
+ color: #6c757d;
67
+ margin-bottom: 0.5rem;
68
+ }
69
+
70
+ .docs-nav-link {
71
+ display: block;
72
+ padding: 0.35rem 0.75rem;
73
+ font-size: 0.875rem;
74
+ color: #495057;
75
+ text-decoration: none;
76
+ border-radius: 0.375rem;
77
+ border-left: 3px solid transparent;
78
+ }
79
+
80
+ .docs-nav-link:hover {
81
+ background: rgba(13, 110, 253, 0.08);
82
+ color: var(--docs-accent);
83
+ }
84
+
85
+ .docs-nav-link.active {
86
+ background: rgba(13, 110, 253, 0.12);
87
+ color: var(--docs-accent);
88
+ border-left-color: var(--docs-accent);
89
+ font-weight: 500;
90
+ }
91
+
92
+ .docs-api-key-wrap {
93
+ max-width: 220px;
94
+ }
95
+
96
+ .docs-api-key-wrap .form-control {
97
+ font-size: 0.8rem;
98
+ font-family: ui-monospace, monospace;
99
+ }
100
+
101
+ @media (max-width: 991.98px) {
102
+ .docs-sidebar {
103
+ display: none;
104
+ }
105
+
106
+ .docs-main {
107
+ padding: 1rem;
108
+ }
109
+ }
110
+
111
+ .docs-hero {
112
+ padding: 2.5rem 0 2rem;
113
+ border-bottom: 1px solid var(--docs-border);
114
+ margin-bottom: 2rem;
115
+ }
116
+
117
+ .docs-hero h1 {
118
+ font-size: 2.25rem;
119
+ font-weight: 700;
120
+ margin-bottom: 0.5rem;
121
+ }
122
+
123
+ .docs-hero .lead {
124
+ color: #6c757d;
125
+ max-width: 36rem;
126
+ }
127
+
128
+ .docs-content h1,
129
+ .docs-content h2,
130
+ .docs-content h3 {
131
+ margin-top: 1.5rem;
132
+ margin-bottom: 0.75rem;
133
+ }
134
+
135
+ .docs-content pre {
136
+ background: #f8f9fa;
137
+ border: 1px solid var(--docs-border);
138
+ border-radius: 0.375rem;
139
+ padding: 1rem;
140
+ overflow-x: auto;
141
+ font-size: 0.85rem;
142
+ }
143
+
144
+ .docs-content code {
145
+ font-size: 0.875em;
146
+ color: #d63384;
147
+ }
148
+
149
+ .docs-content pre code {
150
+ color: inherit;
151
+ }
152
+
153
+ .docs-content table {
154
+ margin: 1rem 0;
155
+ }
156
+
157
+ .method-badge {
158
+ font-family: ui-monospace, monospace;
159
+ font-size: 0.75rem;
160
+ font-weight: 700;
161
+ min-width: 4.5rem;
162
+ text-align: center;
163
+ }
164
+
165
+ .method-get { background-color: #198754 !important; }
166
+ .method-post { background-color: #0d6efd !important; }
167
+ .method-put { background-color: #fd7e14 !important; }
168
+ .method-delete { background-color: #dc3545 !important; }
169
+
170
+ .endpoint-path {
171
+ font-family: ui-monospace, monospace;
172
+ font-size: 0.9rem;
173
+ }
174
+
175
+ .api-try-panel {
176
+ border: 1px solid var(--docs-border);
177
+ border-radius: 0.375rem;
178
+ padding: 1rem;
179
+ background: #fafbfc;
180
+ margin-top: 0.75rem;
181
+ }
182
+
183
+ .api-response {
184
+ font-family: ui-monospace, monospace;
185
+ font-size: 0.8rem;
186
+ background: #212529;
187
+ color: #e9ecef;
188
+ border-radius: 0.375rem;
189
+ padding: 1rem;
190
+ max-height: 320px;
191
+ overflow: auto;
192
+ white-space: pre-wrap;
193
+ word-break: break-word;
194
+ margin-top: 0.75rem;
195
+ }
196
+
197
+ .api-response.empty {
198
+ background: #f8f9fa;
199
+ color: #6c757d;
200
+ border: 1px dashed var(--docs-border);
201
+ }
202
+
203
+ .api-response-meta {
204
+ font-size: 0.8rem;
205
+ color: #6c757d;
206
+ margin-top: 0.5rem;
207
+ }
208
+
209
+ .field-details pre {
210
+ font-size: 0.75rem;
211
+ max-height: 200px;
212
+ overflow: auto;
213
+ }
214
+
215
+ .feature-card {
216
+ height: 100%;
217
+ border: 1px solid var(--docs-border);
218
+ transition: box-shadow 0.15s ease;
219
+ }
220
+
221
+ .feature-card:hover {
222
+ box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.08);
223
+ }
224
+
225
+ .docs-security-note {
226
+ font-size: 0.8rem;
227
+ }