yaml-admin-api 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,243 @@
1
+ ## yaml-admin-api
2
+
3
+ Build an admin API from a single YAML file. The library reads `admin.yml`, connects to MongoDB, and automatically registers login/auth and CRUD routes to your Express app. It also supports file upload (S3/local) and Excel import/export.
4
+
5
+ ---
6
+
7
+ ### Install
8
+ ```bash
9
+ npm i yaml-admin-api
10
+ ```
11
+
12
+ Required environment variables
13
+ - `MONGODB_URL`: MongoDB connection string
14
+ - `JWT_SECRET`: JWT signing key
15
+
16
+ Placeholders `${MONGODB_URL}` and `${JWT_SECRET}` inside YAML are resolved at runtime from the environment.
17
+
18
+ ---
19
+
20
+ ### Quick Start (Express)
21
+ ```js
22
+ const express = require('express');
23
+ const bodyParser = require('body-parser');
24
+ const cookieParser = require('cookie-parser');
25
+ const { registerRoutes } = require('yaml-admin-api');
26
+
27
+ (async () => {
28
+ const app = express();
29
+ app.use(cookieParser());
30
+ app.use(bodyParser.urlencoded({ extended: true, limit: '30mb' }));
31
+ app.use(bodyParser.json({ limit: '30mb' }));
32
+
33
+ // Provide admin.yml path or a YAML string
34
+ await registerRoutes(app, { yamlPath: './admin.yml' });
35
+
36
+ app.listen(6911, () => console.log('API listening on 6911'));
37
+ })();
38
+ ```
39
+
40
+ Options
41
+ ```js
42
+ await registerRoutes(app, {
43
+ yamlPath: './admin.yml', // or yamlString: '<yml text>'
44
+ password: {
45
+ // Override password hashing for fields with type: password (default: sha512)
46
+ encrypt: (plain) => require('crypto').createHash('sha512').update(plain).digest('hex')
47
+ }
48
+ });
49
+ ```
50
+
51
+ ---
52
+
53
+ ### admin.yml example
54
+ ```yaml
55
+ login:
56
+ jwt-secret: ${JWT_SECRET}
57
+ id-password:
58
+ entity: admin
59
+ id-field: email
60
+ password-field: pass
61
+ password-encoding: bcrypt
62
+ bcrypt-salt: 10
63
+
64
+ api-host:
65
+ uri: http://localhost:6911
66
+ web-host:
67
+ uri: http://localhost:6900
68
+
69
+ database:
70
+ mongodb:
71
+ uri: ${MONGODB_URL}
72
+
73
+ upload:
74
+ # For S3
75
+ # s3:
76
+ # access_key_id: ${S3_ACCESS_KEY_ID}
77
+ # secret_access_key: ${S3_SECRET_ACCESS_KEY}
78
+ # region: ${S3_REGION}
79
+ # bucket: ${S3_BUCKET}
80
+ # bucket_private: ${S3_BUCKET_PRIVATE}
81
+ # base_url: http://localhost:6911/upload
82
+ # base_url_private: http://localhost:6911/upload_private
83
+ # For local
84
+ local:
85
+ path: ./upload
86
+ path_private: ./upload_private
87
+ base_url: http://localhost:6911
88
+
89
+ entity:
90
+ member:
91
+ category: 'User'
92
+ label: 'User'
93
+ fields:
94
+ - { name: email, type: string, required: true }
95
+ - { name: pass, type: password, required: true }
96
+ - { name: name, type: string, required: true }
97
+ ```
98
+
99
+ Key field rules
100
+ - Default key is `'_id'` (objectId).
101
+ - To use a custom key, mark the field with `key: true`.
102
+ - `type: integer` + `autogenerate: true` → auto-incrementing ID
103
+ - `type: string` + `autogenerate: true` → UUID
104
+
105
+ Search rules
106
+ - Only fields declared under `entity.<name>.crud.list.search` are searchable in the list API.
107
+ - When `exact: false`, a partial match (regex) is used; integers always compare exactly.
108
+
109
+ ---
110
+
111
+ ### Auth and tokens
112
+ - A JWT is issued on successful login.
113
+ - All protected requests must include header `x-access-token: <JWT>` (query `?token=` and cookie are also accepted).
114
+
115
+ Login endpoints
116
+ ```http
117
+ GET /member/login?email={email}&pass={pass}
118
+ POST /member/login { email, pass }
119
+ GET /member/islogin // token verification
120
+ GET /member/logout
121
+ ```
122
+ Sample response
123
+ ```json
124
+ { "r": true, "token": "<JWT>", "member": { "id": "...", "email": "...", "name": "..." } }
125
+ ```
126
+
127
+ ---
128
+
129
+ ### Entity CRUD endpoints
130
+ All endpoints require authentication.
131
+
132
+ - List: `GET /<entity>`
133
+ - Paging: `_start`, `_end`
134
+ - Sort: `_sort`, `_order` (`ASC`|`DESC`)
135
+ - Field search: `?field=value` (see `crud.list.search` in the schema)
136
+ - Total count header: `X-Total-Count`
137
+
138
+ - Show: `GET /<entity>/:id`
139
+ - Create: `POST /<entity>`
140
+ - ID generation follows field definition (`key`, `type`, `autogenerate`).
141
+ - Response includes `id` (react-admin compatibility).
142
+ - Update: `PUT /<entity>/:id`
143
+ - Key field is not updated.
144
+ - Delete: `DELETE /<entity>/:id`
145
+ - Hard delete by default; customizable hook points exist.
146
+
147
+ Sensitive fields and media
148
+ - Fields with `type: password` are removed from responses and hashed on save.
149
+ - Fields with `type: image|mp4|file` include preview URLs in the response.
150
+ - Private files return secure, short-lived URLs.
151
+
152
+ ---
153
+
154
+ ### File upload
155
+ Depending on configuration, S3 or local upload APIs are enabled. All upload APIs require authentication.
156
+
157
+ Local upload
158
+ ```http
159
+ PUT /api/local/media/upload?ext=jpg // public storage path
160
+ PUT /api/local/media/upload/secure?ext=jpg // private storage path
161
+ ```
162
+ Response: `{ r: true, key }`
163
+
164
+ Secure download
165
+ - The server issues temporary URLs like `/local-secure-download?key=...&token=...`.
166
+ - The token is a short-lived (5 min) JWT; clients can use the link directly.
167
+
168
+ S3 upload (pre-signed)
169
+ ```http
170
+ GET /api/media/url/put/:ext // public bucket
171
+ GET /api/media/url/secure/put/:ext // private bucket
172
+ POST /api/media/url/secure/init/:ext // multipart init (private)
173
+ POST /api/media/url/secure/part // request part URL
174
+ POST /api/media/url/secure/complete // complete multipart
175
+ POST /api/media/url/secure/abort // abort multipart
176
+ ```
177
+ Response includes `upload_url`, storage `key`, etc.
178
+
179
+ Note: S3 usage requires `upload.s3.*` configuration.
180
+
181
+ ---
182
+
183
+ ### Excel export / import
184
+ Declare in the schema to enable endpoints:
185
+ ```yaml
186
+ entity:
187
+ member:
188
+ crud:
189
+ list:
190
+ export:
191
+ fields:
192
+ - { name: email }
193
+ - { name: name }
194
+ import:
195
+ fields:
196
+ - { name: email }
197
+ - { name: name }
198
+ upsert: true
199
+ ```
200
+
201
+ Endpoints
202
+ ```http
203
+ POST /excel/<entity>/export // with body { filter }
204
+ POST /excel/<entity>/import // with body { base64 } (Excel file as Base64)
205
+ ```
206
+ Export response
207
+ ```json
208
+ { "r": true, "url": "<secure-download-url>" }
209
+ ```
210
+ Import response
211
+ ```json
212
+ { "r": true, "msg": "Import success - <n> rows effected" }
213
+ ```
214
+
215
+ ---
216
+
217
+ ### Serverless (Lambda) example
218
+ Wrap with `serverless-http` to deploy easily. See `example/api1`.
219
+ ```js
220
+ 'use strict';
221
+ const serverless = require('serverless-http');
222
+
223
+ exports.handler = async (event, context) => {
224
+ const app = await require('./app');
225
+ const handler = serverless(app);
226
+ return await handler(event, context);
227
+ };
228
+ ```
229
+
230
+ The sample `serverless.yml` uses region `ap-northeast-2` and runtime `nodejs20.x`.
231
+
232
+ ---
233
+
234
+ ### FAQ
235
+ - Auth fails: Ensure you send `x-access-token` header. Obtain it from `/member/login`.
236
+ - Need CORS headers: Add an application-level CORS middleware as shown in the example app.
237
+ - Media field has no URL: Response adds preview URLs (`..._preview`). Private files return signed URLs.
238
+
239
+ ---
240
+
241
+ ### License
242
+ MIT
243
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yaml-admin-api",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "YAML Admin API package",
5
5
  "type": "commonjs",
6
6
  "main": "src/index.js",
@@ -9,7 +9,7 @@
9
9
  ],
10
10
  "license": "MIT",
11
11
  "scripts": {
12
- "publish": "npm publish -w yaml-admin-api --access public"
12
+ "pub": "npm publish -w yaml-admin-api --access public"
13
13
  },
14
14
  "dependencies": {
15
15
  "@aws-sdk/client-s3": "^3.596.0",
@@ -6,20 +6,32 @@ const { generateLoginApi } = require('./crud/login-api-generator');
6
6
  const { withConfig } = require('./login/auth.js');
7
7
  const { generateUploadApi } = require('./upload/upload-api-generator');
8
8
 
9
+ const changeEnv = (yamlString, env = {}) => {
10
+ if (!yamlString) return yamlString;
11
+ const mergedEnv = { ...process.env, ...env };
12
+ return yamlString.replace(/\$\{([A-Z0-9_\.\-]+)\}/g, (match, varName) => {
13
+ console.log('env', varName, mergedEnv[varName]);
14
+ const value = mergedEnv[varName];
15
+ return value !== undefined && value !== null ? String(value) : match;
16
+ });
17
+ }
18
+
9
19
  async function registerRoutes(app, options = {}) {
10
- const { yamlPath, yamlString } = options;
20
+ const { yamlPath, yamlString, env } = options;
11
21
  let yml;
12
22
  if(yamlPath) {
13
- yml = await readYml(yamlPath);
23
+ yml = await readYml(yamlPath, env);
14
24
  } else if(yamlString) {
15
- yml = yaml.parse(yamlString);
25
+ const replaced = changeEnv(yamlString, env);
26
+ yml = yaml.parse(replaced);
16
27
  } else {
17
28
  let yamlString = await fs.readFile('./admin.yml', 'utf8');
18
29
  if(!yamlString) {
19
30
  throw new Error('admin.yml is not found. yamlPath or yamlString is required.')
20
31
  }
21
- yamlString = yamlString.replace('${JWT_SECRET}', process.env.JWT_SECRET);
22
- yamlString = yamlString.replace('${MONGODB_URL}', process.env.MONGODB_URL);
32
+
33
+ yamlString = changeEnv(yamlString, env);
34
+
23
35
  yml = yaml.parse(yamlString);
24
36
  }
25
37
 
@@ -63,13 +75,10 @@ async function registerRoutes(app, options = {}) {
63
75
  })
64
76
  }
65
77
 
66
- async function readYml(path) {
78
+ async function readYml(path, env = {}) {
67
79
  let yml = await fs.readFile(path, 'utf8');
68
- yml = yml.replace('${JWT_SECRET}', process.env.JWT_SECRET);
69
- yml = yml.replace('${MONGODB_URL}', process.env.MONGODB_URL);
70
-
80
+ yml = changeEnv(yml, env);
71
81
  return yaml.parse(yml);
72
-
73
82
  }
74
83
 
75
84
  module.exports = registerRoutes;