yaml-admin-api 0.0.3 → 0.0.8

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.3",
3
+ "version": "0.0.8",
4
4
  "description": "YAML Admin API package",
5
5
  "type": "commonjs",
6
6
  "main": "src/index.js",
@@ -8,11 +8,21 @@
8
8
  "src"
9
9
  ],
10
10
  "license": "MIT",
11
+ "scripts": {
12
+ "publish": "npm publish -w yaml-admin-api --access public"
13
+ },
11
14
  "dependencies": {
15
+ "@aws-sdk/client-s3": "^3.596.0",
16
+ "@aws-sdk/client-ses": "^3.855.0",
17
+ "@aws-sdk/s3-presigned-post": "^3.596.0",
18
+ "@aws-sdk/s3-request-presigner": "^3.596.0",
12
19
  "bcryptjs": "^3.0.2",
13
20
  "jsonwebtoken": "^9.0.2",
21
+ "moment": "^2.30.1",
14
22
  "mongodb": "^6.18.0",
15
23
  "request": "^2.88.2",
24
+ "uuid": "^11.1.0",
25
+ "xlsx": "^0.18.5",
16
26
  "yaml": "^2.8.1"
17
27
  }
18
28
  }
@@ -0,0 +1,58 @@
1
+ const genEntityId = async function(db){
2
+
3
+ let ret = await db.collection('counters').findOneAndUpdate(
4
+ { _id: 'seq' },
5
+ {$inc:{seq:1}},
6
+ {
7
+ new :true,
8
+ upsert:true,
9
+ returnNewDocument:true
10
+ }
11
+ );
12
+ if(!ret?.seq){
13
+ ret = await db.collection('counters').findOneAndUpdate(
14
+ { _id: 'seq' },
15
+ {$inc:{seq:1}},
16
+ {
17
+ new :true,
18
+ upsert:true,
19
+ returnNewDocument:true
20
+ }
21
+ );
22
+ }
23
+ return ret.seq
24
+ }
25
+
26
+ const genEntityIdWithKey = async function(db, key){
27
+
28
+ let ret = await db.collection('counters').findOneAndUpdate(
29
+ { _id: key },
30
+ {$inc:{seq:1}},
31
+ {
32
+ new :true,
33
+ upsert:true,
34
+ returnNewDocument:true
35
+ }
36
+ );
37
+
38
+ if(!ret?.seq){
39
+ ret = await db.collection('counters').findOneAndUpdate(
40
+ { _id: key },
41
+ {$inc:{seq:1}},
42
+ {
43
+ new :true,
44
+ upsert:true,
45
+ returnNewDocument:true
46
+ }
47
+ );
48
+ }
49
+
50
+ return ret.seq
51
+ }
52
+
53
+ module.exports = {
54
+ genEntityId,
55
+ genEntityIdWithKey
56
+ };
57
+
58
+
@@ -0,0 +1,437 @@
1
+ const { withConfig } = require('../login/auth.js');
2
+ const { genEntityIdWithKey } = require('../common/util.js');
3
+ const { v4: uuidv4 } = require('uuid');
4
+ const { ObjectId } = require('mongodb');
5
+ const crypto = require('crypto');
6
+ const XLSX = require('xlsx');
7
+ const moment = require('moment');
8
+ const { withConfigLocal } = require('../upload/localUpload.js');
9
+ const { withConfigS3 } = require('../upload/s3Upload.js');
10
+
11
+ const generateCrud = async ({ app, db, entity_name, yml_entity, yml, options }) => {
12
+
13
+ const auth = withConfig({ db, jwt_secret: yml.login["jwt-secret"] });
14
+ const api_host = yml["api-host"].uri;
15
+ let isS3 = yml.upload.s3
16
+ let host_image = isS3 ? yml.upload.s3.base_url : yml.upload.local.base_url
17
+ const uploader = yml.upload.s3 ? withConfigS3({
18
+ access_key_id: yml.upload.s3.access_key_id,
19
+ secret_access_key: yml.upload.s3.secret_access_key,
20
+ bucket: yml.upload.s3.bucket,
21
+ bucket_private: yml.upload.s3.bucket_private,
22
+ base_url: yml.upload.s3.base_url,
23
+ }) : withConfigLocal({
24
+ path: yml.upload.local.path,
25
+ path_private: yml.upload.local.path_private,
26
+ base_url: yml.upload.local.base_url,
27
+ api_host,
28
+ })
29
+
30
+ let key_field = yml_entity.fields?.find(field => field.key)
31
+ if (!key_field) {
32
+ key_field = {
33
+ name: '_id',
34
+ type: 'objectId',
35
+ key: true,
36
+ autogenerate: true
37
+ }
38
+ }
39
+
40
+ const generateKey = async () => {
41
+ if (key_field.type == 'integer')
42
+ return await genEntityIdWithKey(db, key_field.name)
43
+ else if (key_field.type == 'string')
44
+ return uuidv4()
45
+ return null
46
+ }
47
+
48
+ const getKeyFromEntity = (entity) => {
49
+ const keyValue = entity[key_field.name]
50
+ if (key_field.type == 'objectId' && keyValue)
51
+ return keyValue.toString()
52
+ return keyValue
53
+ }
54
+
55
+ const parseKey = (key) => {
56
+ if (key_field.type == 'integer')
57
+ return parseInt(key)
58
+ else if (key_field.type == 'string')
59
+ return key
60
+ else if (key_field.type == 'objectId')
61
+ return ObjectId.isValid(key) ? new ObjectId(key) : key
62
+ return key
63
+ }
64
+
65
+ const parseValueByType = (value, field) => {
66
+ const { type, reference_entity, reference_field } = field
67
+ if (type == 'reference') {
68
+ const referenceEntity = yml.entity[reference_entity]
69
+ const referenceField = referenceEntity.fields.find(f => f.name == reference_field)
70
+ console.log('referenceField', referenceField)
71
+ return parseValueByTypeCore(value, referenceField)
72
+ } else {
73
+ return parseValueByTypeCore(value, field)
74
+ }
75
+ }
76
+ const parseValueByTypeCore = (value, field) => {
77
+ const { type } = field
78
+ if (type == 'integer')
79
+ return parseInt(value)
80
+ else if (type == 'string')
81
+ return value
82
+ else if (type == 'objectId')
83
+ return ObjectId.isValid(value) ? new ObjectId(value) : value
84
+ return value
85
+ }
86
+
87
+ const passwordEncrypt = (value) => {
88
+ if (options?.password?.encrypt) {
89
+ return options.password.encrypt(value)
90
+ } else {
91
+ return crypto.createHash('sha512').update(value).digest('hex')
92
+ }
93
+ }
94
+
95
+ const addInfo = async (db, list) => {
96
+ let passwordFields = yml_entity.fields.filter(f => f.type == 'password').map(f => f.name)
97
+ list.forEach(m => {
98
+ passwordFields.forEach(f => {
99
+ delete m[f]
100
+ })
101
+ })
102
+
103
+ let mediaFields = yml_entity.fields.filter(f => ['image', 'mp4', 'file'].includes(f.type))
104
+ for(let m of list) {
105
+ for(let field of mediaFields) {
106
+ m[field.name] = await mediaToFront(m[field.name], field.private)
107
+ }
108
+ }
109
+ }
110
+
111
+ const mediaKeyToFullUrl = async (key, private) => {
112
+
113
+ let url = key
114
+ if(url && !url.startsWith('http'))
115
+ url= host_image + '/' + url
116
+
117
+ if(private) {
118
+ url = await uploader.getUrlSecure(key, auth);
119
+ }
120
+
121
+ return url
122
+ }
123
+
124
+ const mediaToFront = async (media, private) => {
125
+ if (media && typeof media == 'string') {
126
+ media= { src: url }
127
+ media.image_preview = await mediaKeyToFullUrl(url, private)
128
+ } else if (media && typeof media == 'object') {
129
+ let { image, video, src } = media
130
+ let url = image || src
131
+ media.image_preview = await mediaKeyToFullUrl(url, private)
132
+ if (video) {
133
+ media.video_preview = await mediaKeyToFullUrl(video, private)
134
+ }
135
+ }
136
+ return media
137
+ }
138
+
139
+
140
+ //list
141
+ app.get(`/${entity_name}`, auth.isAuthenticated, async (req, res) => {
142
+ var s = {};
143
+ var _sort = req.query._sort;
144
+ var _order = req.query._order;
145
+ if (_sort != null)
146
+ s[_sort] = (_order == 'ASC' ? 1 : -1);
147
+
148
+ var _end = req.query._end;
149
+ var _start = req.query._start;
150
+ var l = _end - _start;
151
+
152
+ //검색 파라미터
153
+ const f = {};
154
+ yml_entity.fields?.forEach(field => {
155
+ const q = req.query[field.name];
156
+ if (q) {
157
+ const search = yml_entity.crud?.list?.search?.find(m => m.name == field.name)
158
+ if (Array.isArray(q)) {
159
+ f[field.name] = { $in: q.map(v => parseValueByType(v, field)) };
160
+ } else {
161
+ if (search?.exact != false || field.type == 'integer')
162
+ f[field.name] = parseValueByType(q, field)
163
+ else
164
+ f[field.name] = { $regex: ".*" + q + ".*" };
165
+ }
166
+ }
167
+ })
168
+
169
+ console.log('f', f, req.query)
170
+
171
+ var name = req.query.name;
172
+ if (name == null && req.query.q)
173
+ name = req.query.q;
174
+ if (name != null)
175
+ f['name'] = { $regex: ".*" + name + ".*" };
176
+ f.remove = { $ne: true }
177
+
178
+ //Custom f list Start
179
+
180
+ //Custom f list End
181
+
182
+ const projection = (key_field.name == '_id' ? {} : { _id: false })
183
+ var count = await db.collection(entity_name).find(f).project(projection).sort(s).count();
184
+ let list = await db.collection(entity_name).find(f).project(projection).sort(s).skip(parseInt(_start)).limit(l).toArray()
185
+ list.map(m => {
186
+ m.id = getKeyFromEntity(m)
187
+ })
188
+ //Custom list Start
189
+
190
+ //Custom list End
191
+ await addInfo(db, list)
192
+ res.header('X-Total-Count', count);
193
+ res.json(list);
194
+ });
195
+
196
+
197
+ const constructEntity = async (req, entityId) => {
198
+ var entity = {};
199
+
200
+ if (entityId)
201
+ entity[key_field.name] = entityId
202
+
203
+ yml_entity.fields.forEach(field => {
204
+ if (!field.key)
205
+ entity[field.name] = req.body[field.name]
206
+ })
207
+ entity['update_date'] = new Date()
208
+
209
+ let passwordFields = yml_entity.fields.filter(f => f.type == 'password').map(f => f.name)
210
+ passwordFields.forEach(f => {
211
+ entity[f] = passwordEncrypt(req.body[f])
212
+ })
213
+ //Custom ConstructEntity Start
214
+
215
+ //Custom ConstructEntity End
216
+
217
+ return entity;
218
+ };
219
+
220
+ //create
221
+ app.post(`/${entity_name}`, auth.isAuthenticated, async (req, res) => {
222
+ let entityId
223
+ if (key_field.autogenerate)
224
+ entityId = await generateKey()
225
+ else
226
+ entityId = parseKey(req.body[key_field.name])
227
+
228
+ if (entityId) {
229
+ let f = {}
230
+ f[key_field.name] = entityId
231
+ let already = await db.collection(entity_name).findOne(f)
232
+ if (already)
233
+ return res.status(400).json({ status: 400, statusText: 'error', message: "duplicate key [" + entityId + "]" });
234
+ }
235
+
236
+ const entity = await constructEntity(req, entityId);
237
+ entity['update_date'] = entity['create_date'] = new Date()
238
+ entity['create_admin_id'] = req.user.id
239
+
240
+ //Custom Create Start
241
+
242
+ //Custom Create End
243
+
244
+ var r = await db.collection(entity_name).insertOne(entity);
245
+ //Custom Create Tail Start
246
+
247
+ //Custom Create Tail End
248
+
249
+ const generatedId = entityId || r.insertedId
250
+ entity.id = (key_field.type == 'objectId') ? generatedId?.toString() : generatedId;
251
+
252
+ res.json(entity);
253
+ });
254
+
255
+
256
+ //edit
257
+ app.put(`/${entity_name}/:id`, auth.isAuthenticated, async (req, res) => {
258
+ let entityId = parseKey(req.params.id)
259
+
260
+ const entity = await constructEntity(req, entityId);
261
+ entity['update_date'] = new Date()
262
+ // Do not attempt to set the key field during update (immutable `_id` etc.)
263
+ if (entity[key_field.name] !== undefined)
264
+ delete entity[key_field.name]
265
+
266
+
267
+ //Custom Create Start
268
+
269
+ //Custom Create End
270
+
271
+ let f = {}
272
+ f[key_field.name] = entityId
273
+
274
+ for(let field of yml_entity.fields) {
275
+ if(['mp4', 'image', 'file'].includes(field.type)) {
276
+ let a = entity[field.name]
277
+ delete a.image_preview
278
+ delete a.video_preview
279
+ }
280
+ }
281
+
282
+ await db.collection(entity_name).updateOne(f, { $set: entity });
283
+
284
+ //Custom Create Tail Start
285
+
286
+ //Custom Create Tail End
287
+
288
+ // Ensure React-Admin receives an `id` in the response
289
+ entity.id = (key_field.type == 'objectId') ? entityId?.toString() : entityId
290
+
291
+ res.json(entity);
292
+ });
293
+
294
+ //view
295
+ app.get(`/${entity_name}/:id`, auth.isAuthenticated, async (req, res) => {
296
+ let f = {}
297
+ f[key_field.name] = parseKey(req.params.id)
298
+ const m = await db.collection(entity_name).findOne(f);
299
+ if (!m)
300
+ return res.status(404).send('Not found');
301
+
302
+ m.id = getKeyFromEntity(m)
303
+ await addInfo(db, [m])
304
+
305
+ res.json(m);
306
+ })
307
+
308
+ //delete
309
+ app.delete(`/${entity_name}/:id`, auth.isAuthenticated, async function (req, res) {
310
+ let f = {}
311
+ f[key_field.name] = parseKey(req.params.id)
312
+ const entity = await db.collection(entity_name).findOne(f);
313
+ if (!entity)
314
+ return res.status(404).send('Not found');
315
+
316
+ entity.id = getKeyFromEntity(entity)
317
+
318
+ let customDelete = false
319
+ let softDelete = false
320
+ //Custom Delete Api Start
321
+
322
+ //Custom Delete Api End
323
+
324
+ if (customDelete)
325
+ ;
326
+ else if (softDelete)
327
+ await db.collection(entity_name).updateOne(f, { $set: { remove: true } });
328
+ else
329
+ await db.collection(entity_name).deleteOne(f);
330
+
331
+ res.json(entity);
332
+ });
333
+
334
+ if (yml_entity.crud?.list?.export) {
335
+ app.post(`/excel/${entity_name}/export`, auth.isAuthenticated, async (req, res) => {
336
+ const filename = `${entity_name}_`
337
+ const fields = yml_entity.crud.list.export.fields.map(field => ({
338
+ label: field.name,
339
+ value: field.name,
340
+ }))
341
+ //{ label: '상품', value: row => row.product_list?.map(m=>m.total_name).join(',') },
342
+
343
+ let f = req.body.filter || {}
344
+ const list = await db.collection(entity_name).find(f).project({
345
+ _id: false,
346
+ }).toArray();
347
+
348
+ if (list.length == 0)
349
+ return res.json({ r: false, msg: 'No Data' });
350
+
351
+ await addInfo(db, list)
352
+
353
+ const data = list.map(row => {
354
+ let obj = {};
355
+ fields.forEach(field => {
356
+ obj[field.label] = typeof field.value === 'function' ? field.value(row) : row[field.value];
357
+ });
358
+ return obj;
359
+ });
360
+
361
+ const worksheet = XLSX.utils.json_to_sheet(data);
362
+ const workbook = XLSX.utils.book_new();
363
+ XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
364
+ const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'buffer' });
365
+
366
+ const currentTime = moment().format('YYYYMMDD_HHmmss');
367
+ const key = `${filename}${currentTime}.xlsx`;
368
+ await uploader.uploadSecure(key, excelBuffer);
369
+ let url = await uploader.getUrlSecure(key, auth);
370
+ return res.json({ r: true, url });
371
+ })
372
+ }
373
+
374
+ if (yml_entity.crud?.list?.import) {
375
+ app.post(`/excel/${entity_name}/import`, auth.isAuthenticated, async (req, res) => {
376
+ const { base64 } = req.body
377
+ const buf = Buffer.from(base64, 'base64');
378
+ const workbook = XLSX.read(buf, { type: 'buffer' });
379
+ const sheet = workbook.Sheets[workbook.SheetNames[0]];
380
+ let list = XLSX.utils.sheet_to_json(sheet, { header: 1 });
381
+ //엑셀 첫번째 행 타이틀 데이터 제거
382
+ let header = list[0]
383
+ list.shift();
384
+
385
+ let upsert = yml_entity.crud.list.import.upsert || true
386
+
387
+ const fields = yml_entity.crud.list.import.fields.map(m => m)
388
+ fields.map(field => {
389
+ let original = yml_entity.fields.find(f => f.name == field.name)
390
+ field.type = original.type
391
+ })
392
+
393
+ let key_field = yml_entity.fields.find(f => f.key)
394
+ let bulk = []
395
+ list.map(m => {
396
+ let f = {}
397
+
398
+ let m_obj = {}
399
+ header.map((h, index) => {
400
+ m_obj[h] = m[index]
401
+ })
402
+
403
+ f[key_field.name] = getKeyFromEntity(m_obj)
404
+ if (!f[key_field.name])
405
+ return
406
+ let entity = {}
407
+ fields.forEach(field => {
408
+ if (field.type == 'integer')
409
+ entity[field.name] = parseInt(m_obj[field.name])
410
+ else if (field.type == 'password')
411
+ entity[field.name] = passwordEncrypt(m_obj[field.name] + '')
412
+ else
413
+ entity[field.name] = m_obj[field.name] + ''
414
+ })
415
+
416
+ bulk.push({
417
+ updateOne: {
418
+ filter: f,
419
+ update: { $set: entity },
420
+ upsert: upsert
421
+ }
422
+ })
423
+ })
424
+
425
+ let result = await db.collection('delivery').bulkWrite(bulk);
426
+ res.json({ r: true, msg: 'Import success - ' + result.upsertedCount + ' rows effected' });
427
+ })
428
+ }
429
+ }
430
+
431
+ const generateEntityApi = async ({ app, db, entity_name, entity, yml, options }) => {
432
+ await generateCrud({ app, db, entity_name, yml_entity: entity, yml, options })
433
+ }
434
+
435
+ module.exports = {
436
+ generateEntityApi
437
+ }
package/src/login/auth.js CHANGED
@@ -12,7 +12,25 @@ const withConfig = (config) => {
12
12
  });
13
13
  };
14
14
 
15
- const authenticateSuccess = function (req, res, user, next) {
15
+ const genenrateShortToken = () => {
16
+ return new Promise((resolve, reject) => {
17
+ jwt.sign(
18
+ {},
19
+ jwt_secret,
20
+ {
21
+ expiresIn: '5m',
22
+ subject: 'shortToken'
23
+ }, (err, token) => {
24
+ if (err)
25
+ reject(err);
26
+ else
27
+ resolve(token);
28
+ }
29
+ );
30
+ })
31
+ }
32
+
33
+ const authenticateSuccess = (req, res, user, next) => {
16
34
  jwt.sign(
17
35
  user,
18
36
  jwt_secret,
@@ -30,15 +48,15 @@ const withConfig = (config) => {
30
48
  );
31
49
  };
32
50
 
33
- const isAuthenticated = function (req, res, next) {
34
- const token = req.headers['x-access-token'] || req.query.token || req.cookies.token;
51
+ const isAuthenticated = (req, res, next) => {
35
52
 
53
+ const token = req.headers['x-access-token'] || req.query.token || req.cookies.token;
36
54
  if (token == null)
37
- res.json({ r: false, err: { code: 666 }, msg: '로그인 필요' });
55
+ res.json({ r: false, err: { code: 666 }, msg: 'No authentication' });
38
56
  else
39
57
  jwt.verify(token, jwt_secret, (err, decoded) => {
40
58
  if (err) {
41
- res.json({ r: false, err: { code: 666 }, msg: '로그인 필요' });
59
+ res.json({ r: false, err: { code: 666 }, msg: 'No authentication' });
42
60
  return;
43
61
  }
44
62
  req.user = decoded;
@@ -78,6 +96,7 @@ const withConfig = (config) => {
78
96
  return {
79
97
  isAuthenticated,
80
98
  authenticate,
99
+ genenrateShortToken,
81
100
  }
82
101
  }
83
102
 
@@ -0,0 +1,44 @@
1
+ const moment = require('moment')
2
+ const fs = require('fs')
3
+
4
+ const getUrl = async (Key) => {
5
+ //console.log('getSignedUrl')
6
+ let r = await s3.getSignedUrl('getObject', {
7
+ Bucket:aws_bucket_private,
8
+ Key,
9
+ })
10
+
11
+ //console.log(r)
12
+ return r
13
+ }
14
+
15
+ const withConfigLocal = ({path, path_private, base_url, api_host}) => {
16
+
17
+ const upload = async (key, stream) => {
18
+ return await fs.writeFileSync(path + '/' + key, stream)
19
+ }
20
+
21
+ const uploadSecure = async (key, stream) => {
22
+ return await fs.writeFileSync(path_private + '/' + key, stream)
23
+ }
24
+
25
+ const getUrlSecure = async (Key, auth) => {
26
+ let r = `${api_host}/local-secure-download?key=${Key}`
27
+ let shortToken = await auth.genenrateShortToken()
28
+ r += `&token=${shortToken}`
29
+ return r
30
+ }
31
+
32
+ return {
33
+ upload,
34
+ uploadSecure,
35
+ getUrl: async (key) => {
36
+ return await getUrl(key)
37
+ },
38
+ getUrlSecure,
39
+ }
40
+ }
41
+
42
+ module.exports = {
43
+ withConfigLocal
44
+ };
@@ -0,0 +1,47 @@
1
+ const moment = require('moment')
2
+ const { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand } = require('@aws-sdk/client-s3')
3
+ const { getSignedUrl } = require('@aws-sdk/s3-request-presigner')
4
+
5
+
6
+ const upload = async (key, stream) => {
7
+ if(!key){
8
+ throw new Error('필수값이 없습니다')
9
+ }
10
+ //console.log('uploadExcel')
11
+ return await s3.upload({
12
+ Bucket: aws_bucket_private,
13
+ Key: key,
14
+ Body: stream,
15
+ ACL: 'private',
16
+ Expires: moment().add(10, 'minute').toISOString(),
17
+ ContentType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
18
+ }).promise()
19
+ }
20
+
21
+ const getUrl = async (Key) => {
22
+ //console.log('getSignedUrl')
23
+ let r = await s3.getSignedUrl('getObject', {
24
+ Bucket:aws_bucket_private,
25
+ Key,
26
+ })
27
+
28
+ //console.log(r)
29
+ return r
30
+ }
31
+
32
+ const getSecureUrl = async (Key) => {
33
+ //console.log('getSignedUrl')
34
+ let r = await s3.getSignedUrl('getObject', {
35
+ Bucket:aws_bucket_private,
36
+ Key,
37
+ })
38
+
39
+ //console.log(r)
40
+ return r
41
+ }
42
+
43
+ module.exports = {
44
+ upload,
45
+ getUrl,
46
+ getSecureUrl,
47
+ };
@@ -0,0 +1,248 @@
1
+ const { withConfig } = require('../login/auth.js');
2
+ const { PutObjectCommand, CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand, AbortMultipartUploadCommand } = require('@aws-sdk/client-s3')
3
+ const { getSignedUrl } = require('@aws-sdk/s3-request-presigner')
4
+ const { genEntityIdWithKey } = require('../common/util.js');
5
+ const { S3Client } = require('@aws-sdk/client-s3')
6
+ const fs = require('fs')
7
+
8
+ const getContentType = (ext) => {
9
+ let contentType = 'image/jpeg'
10
+ if(ext == 'mp4')
11
+ contentType = 'video/mp4'
12
+ else if(ext == 'mov')
13
+ contentType = 'video/quicktime'
14
+ else if(ext == 'png')
15
+ contentType = 'image/png'
16
+ return contentType
17
+ }
18
+
19
+
20
+ const generateS3UploadApi = async ({ app, db, yml, options }) => {
21
+ const auth = withConfig({ db, jwt_secret: yml.login["jwt-secret"] });
22
+ const { region, access_key_id, secret_access_key, bucket, bucket_private } = yml.upload.s3;
23
+ const getS3 = () => {
24
+ let s3 = new S3Client({
25
+ region: region,
26
+ credentials: {
27
+ accessKeyId: access_key_id,
28
+ secretAccessKey: secret_access_key
29
+ }
30
+ })
31
+ return s3
32
+ }
33
+
34
+ app.get('/api/media/url/put/:ext', auth.isAuthenticated, async function(req, res){
35
+ let s3 = getS3()
36
+ let {member_no} = req.user;
37
+ let fileName = await genEntityIdWithKey(db, 'file');
38
+ let ext = req.params.ext;
39
+
40
+ let contentType = getContentType(ext)
41
+ let key = `media/${member_no}/${fileName}.${ext}`
42
+ const uploadUrl = await getSignedUrl(s3, new PutObjectCommand({Bucket: aws_bucket_image,
43
+ ContentType: contentType,
44
+ Key: key}), { expiresIn: 300 });
45
+
46
+ let r = {upload_url:uploadUrl, key, fileName:`${fileName}.${ext}`, member_no, contentType}
47
+ console.log(r)
48
+ res.json(r);
49
+ });
50
+
51
+ app.get('/api/media/url/secure/put/:ext', auth.isAuthenticated, async function(req, res){
52
+ let s3 = getS3()
53
+ let {member_no} = req.user;
54
+ let fileName = await genEntityIdWithKey(db, 'file');
55
+ let ext = req.params.ext;
56
+
57
+ let contentType = getContentType(ext)
58
+ let key = `media/${member_no}/${fileName}.${ext}`
59
+ const uploadUrl = await getSignedUrl(s3, new PutObjectCommand({Bucket: aws_bucket_private,
60
+ ContentType: contentType,
61
+ Key: key}), { expiresIn: 300 });
62
+
63
+ let r = {upload_url:uploadUrl, key, fileName:`${fileName}.${ext}`, member_no, contentType}
64
+
65
+ res.json(r);
66
+ });
67
+
68
+ // request uploadId
69
+ app.get('/api/media/url/secure/init/:ext', auth.isAuthenticated, async function (req, res) {
70
+ let s3 = getS3();
71
+ let member_no = req.user.member_no || req.user.id;
72
+ let fileName = await genEntityIdWithKey(db, 'file');
73
+ let ext = req.params.ext;
74
+
75
+ let key = `media/${member_no}/${fileName}.${ext}`;
76
+ let contentType = getContentType(ext);
77
+
78
+ const createMultipartUpload = await s3.send(new CreateMultipartUploadCommand({
79
+ Bucket: aws_bucket_private,
80
+ Key: key,
81
+ ContentType: contentType
82
+ }));
83
+
84
+ res.json({ uploadId: createMultipartUpload.UploadId, key, contentType });
85
+ });
86
+
87
+ // request presigned url
88
+ app.post('/api/media/url/secure/part', auth.isAuthenticated, async function (req, res) {
89
+ let s3 = getS3();
90
+ let { key, uploadId, partNumber } = req.body;
91
+
92
+ const command = new UploadPartCommand({
93
+ Bucket: aws_bucket_private,
94
+ Key: key,
95
+ UploadId: uploadId,
96
+ PartNumber: partNumber
97
+ });
98
+
99
+ const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 300*10 });
100
+
101
+ res.json({ uploadUrl });
102
+ });
103
+
104
+ // merge file
105
+ app.post('/api/media/url/secure/complete', auth.isAuthenticated, async function (req, res) {
106
+ let s3 = getS3();
107
+ let { key, uploadId, parts } = req.body;
108
+
109
+ parts.sort((a, b) => a.PartNumber - b.PartNumber);
110
+
111
+ await s3.send(new CompleteMultipartUploadCommand({
112
+ Bucket: aws_bucket_private,
113
+ Key: key,
114
+ UploadId: uploadId,
115
+ MultipartUpload: { Parts: parts }
116
+ }));
117
+
118
+ res.json({ key });
119
+ });
120
+
121
+ // 청크파일 삭제
122
+ app.post('/api/media/url/secure/abort', auth.isAuthenticated, async (req, res) => {
123
+ try {
124
+ const { key, uploadId } = req.body;
125
+ const s3 = getS3();
126
+
127
+ await s3.send(new AbortMultipartUploadCommand({
128
+ Bucket: aws_bucket_private,
129
+ Key: key,
130
+ UploadId: uploadId
131
+ }));
132
+
133
+ res.json({ r: true });
134
+ } catch (error) {
135
+ console.log(error);
136
+ res.status(500).json({ r: false });
137
+ }
138
+ });
139
+
140
+ app.post('/api/media/url/put', auth.isAuthenticated, async function(req, res) {
141
+ let s3 = getS3()
142
+ let {member_no} = req.user
143
+ const {ext_list} = req.body
144
+
145
+ let r = {list:[], r:true}
146
+ for(let ext of ext_list) {
147
+ let fileName = await genEntityIdWithKey(db, 'file')
148
+ let key = `media/${member_no}/${fileName}.${ext}`
149
+ let contentType = getContentType(ext)
150
+ const upload_url = await getSignedUrl(s3, new PutObjectCommand({Bucket: aws_bucket_image,
151
+ ContentType: contentType,
152
+ Key: key}), { expiresIn: 300 });
153
+
154
+ r.list.push({upload_url, key, fileName:`${fileName}.${ext}`, member_no, contentType})
155
+ }
156
+
157
+ res.json(r);
158
+ })
159
+
160
+ app.post('/api/media/url/secure/put', auth.isAuthenticated, async function(req, res) {
161
+ let s3 = getS3()
162
+ let {member_no} = req.user
163
+ const {ext_list} = req.body
164
+
165
+ let r = {list:[], r:true}
166
+ for(let ext of ext_list) {
167
+ let fileName = await genEntityIdWithKey(db, 'file')
168
+ let key = `media/${member_no}/${fileName}.${ext}`
169
+ let contentType = getContentType(ext)
170
+ const upload_url = await getSignedUrl(s3, new PutObjectCommand({Bucket: aws_bucket_private,
171
+ ContentType: contentType,
172
+ Key: key}), { expiresIn: 300 });
173
+
174
+ r.list.push({upload_url, key, fileName:`${fileName}.${ext}`, member_no, contentType})
175
+ }
176
+
177
+ res.json(r);
178
+ })
179
+ }
180
+
181
+ const generateLocalUploadApi = async ({ app, db, yml, options }) => {
182
+ const auth = withConfig({ db, jwt_secret: yml.login["jwt-secret"] });
183
+ const { path, path_private } = yml.upload.local;
184
+
185
+ // Accept raw binary for local upload and stream to disk
186
+ app.put('/api/local/media/upload', auth.isAuthenticated, async function(req, res) {
187
+ let member_no = req.user.member_no || req.user.id;
188
+ let {ext, name} = req.query
189
+ let fileName = await genEntityIdWithKey(db, 'file')
190
+ let key = `media/${member_no}/${fileName}.${ext}`
191
+
192
+ // Ensure directory exists
193
+ const fullPath = `${path}/${key}`;
194
+ fs.mkdirSync(require('path').dirname(fullPath), { recursive: true });
195
+
196
+ const writeStream = fs.createWriteStream(fullPath);
197
+ req.pipe(writeStream);
198
+ writeStream.on('finish', () => {
199
+ res.json({ r: true, key })
200
+ });
201
+ writeStream.on('error', (err) => {
202
+ console.error(err);
203
+ res.status(500).json({ r: false, msg:err.message })
204
+ });
205
+ })
206
+
207
+ app.put('/api/local/media/upload/secure', auth.isAuthenticated, async function(req, res) {
208
+ let member_no = req.user.member_no || req.user.id;
209
+ let {ext, name} = req.query
210
+
211
+ let fileName = await genEntityIdWithKey(db, 'file')
212
+ let key = `media/${member_no}/${fileName}.${ext}`
213
+
214
+ try {
215
+ // Ensure directory exists
216
+ const fullPath = `${path_private}/${key}`;
217
+ fs.mkdirSync(require('path').dirname(fullPath), { recursive: true });
218
+
219
+ const writeStream = fs.createWriteStream(fullPath);
220
+ req.pipe(writeStream);
221
+ writeStream.on('finish', () => {
222
+ res.json({ r: true, key })
223
+ });
224
+ writeStream.on('error', (err) => {
225
+ console.error(err);
226
+ res.status(500).json({ r: false, msg:err.message })
227
+ });
228
+ } catch (e) {
229
+ console.error(e)
230
+ res.status(500).json({ r:false })
231
+ }
232
+ })
233
+ }
234
+
235
+ const generateUploadApi = async ({ app, db, yml, options }) => {
236
+ if(yml.upload.s3) {
237
+ await generateS3UploadApi({ app, db, yml, options })
238
+ }
239
+
240
+ if(yml.upload.local) {
241
+ await generateLocalUploadApi({ app, db, yml, options })
242
+ }
243
+
244
+ }
245
+
246
+ module.exports = {
247
+ generateUploadApi
248
+ }
@@ -1,23 +1,37 @@
1
1
  const { MongoClient } = require('mongodb');
2
2
  const fs = require('fs').promises;
3
3
  const yaml = require('yaml');
4
- const { generateEntityApi } = require('./crud/api-generator');
4
+ const { generateEntityApi } = require('./crud/entity-api-generator');
5
5
  const { generateLoginApi } = require('./crud/login-api-generator');
6
+ const { withConfig } = require('./login/auth.js');
7
+ const { generateUploadApi } = require('./upload/upload-api-generator');
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
+ }
6
18
 
7
19
  async function registerRoutes(app, options = {}) {
8
- const { yamlPath, yamlString } = options;
20
+ const { yamlPath, yamlString, env } = options;
9
21
  let yml;
10
22
  if(yamlPath) {
11
- yml = await readYml(yamlPath);
23
+ yml = await readYml(yamlPath, env);
12
24
  } else if(yamlString) {
13
- yml = yaml.parse(yamlString);
25
+ const replaced = changeEnv(yamlString, env);
26
+ yml = yaml.parse(replaced);
14
27
  } else {
15
28
  let yamlString = await fs.readFile('./admin.yml', 'utf8');
16
29
  if(!yamlString) {
17
30
  throw new Error('admin.yml is not found. yamlPath or yamlString is required.')
18
31
  }
19
- yamlString = yamlString.replace('${JWT_SECRET}', process.env.JWT_SECRET);
20
- yamlString = yamlString.replace('${MONGODB_URL}', process.env.MONGODB_URL);
32
+
33
+ yamlString = changeEnv(yamlString, env);
34
+
21
35
  yml = yaml.parse(yamlString);
22
36
  }
23
37
 
@@ -31,27 +45,40 @@ async function registerRoutes(app, options = {}) {
31
45
  console.log('db connected')
32
46
  }
33
47
  }
34
- const jwt_secret = yml.login["jwt-secret"]
35
- app.set('jwt-secret', jwt_secret);
36
48
 
37
49
  await generateLoginApi(app, db, yml)
38
- entity && Object.keys(entity).forEach(async (entityName) => {
50
+ entity && Object.keys(entity).forEach(async (entity_name) => {
39
51
  await generateEntityApi({
40
52
  app, db,
41
- name:entityName,
42
- entity:entity[entityName],
43
- jwt_secret
53
+ entity_name,
54
+ entity:entity[entity_name],
55
+ yml,
56
+ options,
44
57
  })
45
58
  })
59
+
60
+ await generateUploadApi({
61
+ app, db,
62
+ yml,
63
+ options,
64
+ })
65
+
66
+ //local secure download api
67
+ const auth = withConfig({ db, jwt_secret: yml.login["jwt-secret"] });
68
+ app.get('/local-secure-download', auth.isAuthenticated, async (req, res) => {
69
+ const {key} = req.query;
70
+ const a = `${yml.upload.local.path_private}/${key}`
71
+ const file = await fs.readFile(a)
72
+ res.setHeader('Content-Disposition', `attachment; filename=${key}`);
73
+ res.setHeader('Content-Type', 'application/octet-stream');
74
+ res.send(file);
75
+ })
46
76
  }
47
77
 
48
- async function readYml(path) {
78
+ async function readYml(path, env = {}) {
49
79
  let yml = await fs.readFile(path, 'utf8');
50
- yml = yml.replace('${JWT_SECRET}', process.env.JWT_SECRET);
51
- yml = yml.replace('${MONGODB_URL}', process.env.MONGODB_URL);
52
-
80
+ yml = changeEnv(yml, env);
53
81
  return yaml.parse(yml);
54
-
55
82
  }
56
83
 
57
84
  module.exports = registerRoutes;
@@ -1,118 +0,0 @@
1
- const isAuthenticated = (jwt_secret) => {
2
- return async (req, res, next) => {
3
- const token = req.headers['x-access-token'] || req.query.token || req.cookies.token;
4
- if (token == null)
5
- res.json({ r: false, err: { code: 666 }, msg: '로그인 필요' });
6
- else {
7
- jwt.verify(token, jwt_secret, (err, decoded) => {
8
- if (err) {
9
- res.json({ r: false, err: { code: 666 }, msg: '로그인 필요' });
10
- return;
11
- }
12
- req.user = decoded;
13
- delete req.user.password;
14
- next();
15
- })
16
- }
17
- }
18
- }
19
-
20
- const generateList = async ({app, db, name, entity, jwt_secret}) => {
21
- //list
22
- app.get(`/${name}`, isAuthenticated(jwt_secret), async function (req, res) {
23
- var s = {};
24
- var _sort = req.query._sort;
25
- var _order = req.query._order;
26
- if (_sort != null)
27
- s[_sort] = (_order == 'ASC' ? 1 : -1);
28
-
29
- var _end = req.query._end;
30
- var _start = req.query._start;
31
- var l = _end - _start;
32
-
33
- //검색 파라미터
34
- var f = {};
35
- var id = req.query.id;
36
- if (id) {
37
- if (Array.isArray(id))
38
- f['id'] = { $in: id.map(m => parseInt(m)) };
39
- else
40
- f['id'] = parseInt(id);
41
- }
42
-
43
- // ${
44
- // entity.property.filter(f=>f.search && f.type != 'divider').map(m=>{
45
- // if(m.type == 'bool') {
46
- // return `
47
- // if (req.query.${m.name})
48
- // f['${m.name}'] = req.query.${m.name} == 'true'
49
- // `
50
- // } else if(m.type == 'text') {
51
- // return `
52
- // if (req.query.${m.name})
53
- // f['${m.name}'] = { $regex: ".*" + req.query.${m.name} + ".*" }
54
- // `
55
- // } else if(m.type == 'reference') {
56
- // //reference의 키는 integer인지 string인지 모른다. 그리고 member_no같이 string으로 취급되어야할 integer도 있다
57
- // return `
58
- // if (req.query.${m.name})
59
- // f['${m.name}'] = isNaN(Number(req.query.${m.name}))|| parseInt(req.query.${m.name}) > 1569340492095?req.query.${m.name}:parseInt(req.query.${m.name})
60
- // `
61
- // } else if(m.type == 'date') {
62
- // return `
63
- // if (req.query.date) {
64
- // if(req.query.date.replace(/-/g, '').length == 8) {
65
- // //해당 날짜의 시작Date와 끝Date로 검색
66
- // f['date'] = {
67
- // $gte: moment(req.query.date).startOf('day').toDate(),
68
- // $lte: moment(req.query.date).endOf('day').toDate()
69
- // }
70
- // }
71
- // }
72
- // `
73
- // } else {
74
- // return `
75
- // if (req.query.${m.name})
76
- // f['${m.name}'] = req.query.${m.name}
77
- // `
78
- // }
79
-
80
- // }).join('\n')
81
- // }
82
-
83
- var name = req.query.name;
84
- if (name == null && req.query.q)
85
- name = req.query.q;
86
- if (name != null)
87
- f['name'] = { $regex: ".*" + name + ".*" };
88
- f.remove = { $ne: true }
89
-
90
- //Custom f list Start
91
-
92
- //Custom f list End
93
-
94
- var count = await db.collection('${name}').find(f).project({ _id: false }).sort(s).count();
95
- let list = await db.collection('${name}').find(f).project({ _id: false }).sort(s).skip(parseInt(_start)).limit(l).toArray()
96
- console.log('entity', entity)
97
- list.map(m => {
98
- m.id = entity.key
99
- })
100
- //Custom list Start
101
-
102
- //Custom list End
103
- await addInfo(db, list)
104
- res.header('X-Total-Count', count);
105
- res.json(list);
106
- });
107
- }
108
-
109
- const generateEntityApi = async ({app, db, name, entity, jwt_secret}) => {
110
- const { fields } = entity;
111
- console.log('generateEntityApi', name)
112
-
113
- await generateList({app, db, name, entity, jwt_secret})
114
- }
115
-
116
- module.exports = {
117
- generateEntityApi
118
- }