yaml-admin-api 0.0.3 → 0.0.7
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/package.json +11 -1
- package/src/common/util.js +58 -0
- package/src/crud/entity-api-generator.js +437 -0
- package/src/login/auth.js +24 -5
- package/src/upload/localUpload.js +44 -0
- package/src/upload/s3Upload.js +47 -0
- package/src/upload/upload-api-generator.js +248 -0
- package/src/yml-admin-api.js +25 -7
- package/src/crud/api-generator.js +0 -118
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yaml-admin-api",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
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
|
|
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 =
|
|
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
|
+
}
|
package/src/yml-admin-api.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
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');
|
|
6
8
|
|
|
7
9
|
async function registerRoutes(app, options = {}) {
|
|
8
10
|
const { yamlPath, yamlString } = options;
|
|
@@ -31,18 +33,34 @@ async function registerRoutes(app, options = {}) {
|
|
|
31
33
|
console.log('db connected')
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
|
-
const jwt_secret = yml.login["jwt-secret"]
|
|
35
|
-
app.set('jwt-secret', jwt_secret);
|
|
36
36
|
|
|
37
37
|
await generateLoginApi(app, db, yml)
|
|
38
|
-
entity && Object.keys(entity).forEach(async (
|
|
38
|
+
entity && Object.keys(entity).forEach(async (entity_name) => {
|
|
39
39
|
await generateEntityApi({
|
|
40
40
|
app, db,
|
|
41
|
-
|
|
42
|
-
entity:entity[
|
|
43
|
-
|
|
41
|
+
entity_name,
|
|
42
|
+
entity:entity[entity_name],
|
|
43
|
+
yml,
|
|
44
|
+
options,
|
|
44
45
|
})
|
|
45
46
|
})
|
|
47
|
+
|
|
48
|
+
await generateUploadApi({
|
|
49
|
+
app, db,
|
|
50
|
+
yml,
|
|
51
|
+
options,
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
//local secure download api
|
|
55
|
+
const auth = withConfig({ db, jwt_secret: yml.login["jwt-secret"] });
|
|
56
|
+
app.get('/local-secure-download', auth.isAuthenticated, async (req, res) => {
|
|
57
|
+
const {key} = req.query;
|
|
58
|
+
const a = `${yml.upload.local.path_private}/${key}`
|
|
59
|
+
const file = await fs.readFile(a)
|
|
60
|
+
res.setHeader('Content-Disposition', `attachment; filename=${key}`);
|
|
61
|
+
res.setHeader('Content-Type', 'application/octet-stream');
|
|
62
|
+
res.send(file);
|
|
63
|
+
})
|
|
46
64
|
}
|
|
47
65
|
|
|
48
66
|
async function readYml(path) {
|
|
@@ -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
|
-
}
|