UNPKG

8.64 kBJavaScriptView Raw
1"use strict"
2const {
3 pick, keys, map, reject, filter, groupBy, includes, fromPairs, omitBy, findKey, get,
4 isNull, isObject, isFunction, isString,
5 defaults } = require("lodash")
6const deepMerge = require("@iin-mdc/koa-utils/lib/deep-merge-with-symbols")
7
8function isEmpty(object) {
9 return !object || Object.getOwnPropertySymbols(object).concat(Object.keys(object)).length === 0
10}
11// query = { limit: '25', offset: '0', order: 'id DESC', 'Clients.id': '3' }
12function filterOptions(model, query) {
13 let { like, gt, or } = model.sequelize.Sequelize.Op
14 let options = {}
15 const attributes = model.rawAttributes
16
17 options.where = pick(query, keys(attributes))
18
19 if (query.q && model.searchAttributes) {
20 let search = map(model.searchAttributes, attr => {
21 let s = {}
22 if (query.q.match(/^\*|\*$/)) {
23 s[attr] = { [like]: query.q.replace(/^\*|\*$/g, "%") }
24 } else {
25 s[attr] = { [like]: `${query.q}%` }
26 }
27 return s
28 })
29 options.where = deepMerge(options.where, { [or]: search })
30 }
31
32 let { order, limit, offset, lastUpdatedAt } = query
33 if (order) {
34 let [field, dir] = order.split(" ")
35 if (field == "id") field = model.primaryKeyAttribute
36 if (attributes[field])
37 options.order = [[field, dir]]
38 else if (field.indexOf(".") > 0) {
39 const [associationName, associationField] = field.split(".")
40 if (model.associations[associationName]) {
41 const association = model.associations[associationName]
42 options.order = [[association, associationField, dir]]
43 }
44 }
45 }
46 if (limit) options.limit = parseInt(limit, 10)
47 if (offset) options.offset = parseInt(offset, 10)
48 //options.limit = Math.max(options.limit || 50, 500)
49
50 if (query.ids) {
51 options.where[model.primaryKeyAttribute] = isString(query.ids) ? query.ids.split(",") : query.ids
52 }
53 if (lastUpdatedAt) {
54 options.where.updatedAt = { [gt]: lastUpdatedAt }
55 }
56 return options
57}
58
59function getAssociationsFilter(model, query = {}, associations_filter = null) {
60 let params = getAssociationsParams(model, query, associations_filter)
61 if (params.include) {
62 return { include: filter(params.include, options => !isEmpty(options.where)) }
63 }
64 return {}
65}
66function getAssociationsParams(model, query = {}, associations_filter = null) {
67 const _keys = filter(keys(query), (k) => includes(k, "."))
68 const associationKeys = groupBy(_keys, (k) => k.split(".")[0])
69 associations_filter = associations_filter || keys(model.associations)
70
71 if (query.order && query.order.indexOf(".") > 0) {
72 const [orderAssociation, orderField, dir] = query.order.split(/[\\. ]/g)
73 if (orderAssociation && orderField && dir && !includes(associations_filter, orderAssociation)) {
74 associations_filter.push(orderAssociation)
75 }
76 }
77
78 let include = map(model.associations, (association, name) => {
79 let config = { model: association.target, as: association.as }
80 let queryKeys = associationKeys[name]
81 if (queryKeys) {
82 config.where = fromPairs(map(queryKeys, key => [key.split(".")[1], query[key]]))
83 }
84 if (config.where || includes(associations_filter, name)) {
85 return config
86 } else {
87 return null
88 }
89 })
90 include = reject(include, isNull)
91 return isEmpty(include) ? {} : { include }
92}
93function requiredIncludes(include) {
94 return include
95 .map(includeQ => {
96 if (isObject(includeQ) && includeQ.required) {
97 return ({
98 ...includeQ,
99 include: requiredIncludes(includeQ.include || [])
100 })
101 }
102 return null
103 })
104 .filter(includeQ => !isNull(includeQ))
105}
106function createCountQuery(model, query) {
107 const include = requiredIncludes(query.include || [])
108 const distinctQuery = include.length > 0 && model.primaryKeyAttribute ? {
109 distinct: true,
110 col: model.primaryKeyAttribute
111 } : {
112 col: model.primaryKeyAttribute
113 }
114
115 return ({
116 ...distinctQuery,
117 include: include,
118 where: query.where
119 })
120}
121async function fetchObject(ctx, id) {
122 let dbQuery = { where: primaryKeyValue(ctx.model, id), rejectOnEmpty: true }
123 if (ctx.associations)
124 dbQuery = deepMerge(getAssociationsParams(ctx.model, {}, ctx.associations), dbQuery)
125 return await ctx.model.findOne(await ctx.customize(ctx, dbQuery))
126}
127
128function primaryKeyValue(model, object) {
129 let value = isObject(object) ? object[model.primaryKeyAttribute] : object
130 return fromPairs([[model.primaryKeyAttribute, value]])
131}
132
133function ctxExtend(ctx, modelName, options) {
134 const model = isString(modelName) ? ctx.models[modelName] : modelName
135 let [callback, opts] = isFunction(options) ? [options, {}] : [null, options || {}]
136 let defaultOptions = {
137 model: model,
138 customize: async (_ctx, dbQuery) => dbQuery,
139 serialize: async object => omitBy(object.toJSON(), (_v, k) => k.match(/password|encrypted/)),
140 callback: callback || get(options, findKey(pick(options, "update", "create", "destroy"), isFunction)),
141 associations: opts.associations || model.preloadAssociations || []
142 }
143 return defaults(ctx, opts, defaultOptions)
144}
145const handlers = {
146 auth: (ctx, next) => {
147 if (ctx.state.allowedRoles) {
148 if (ctx.state.user && includes(ctx.state.allowedRoles, ctx.state.user.role)) {
149 return next()
150 } else {
151 ctx.status = 401
152 }
153 }
154 else
155 return next()
156 },
157 show: (...args) => async (ctx) => {
158 let { model, serialize } = ctxExtend(ctx, ...args)
159 let object = await fetchObject(ctx, ctx.params.id)
160
161 ctx.body = {
162 data: await serialize(object, ctx),
163 meta: {
164 id: model.primaryKeyAttribute
165 }
166 }
167 },
168 list: (...args) => async (ctx) => {
169 const { model, serialize, customize, associations } = ctxExtend(ctx, ...args)
170
171 const filter = filterOptions(model, ctx.request.query)
172 const associationsFilter = getAssociationsFilter(model, ctx.request.query, associations)
173 const associationsParams = getAssociationsParams(model, ctx.request.query, associations)
174
175 const dbQuery = await customize(ctx, deepMerge({}, filter, associationsParams))
176 const countQuery = createCountQuery(model, await customize(ctx, deepMerge({}, filter, associationsFilter)))
177
178 const rows = await model.findAll(dbQuery)
179 const count = await model.count(countQuery)
180
181 ctx.body = {
182 data: await Promise.all(rows.map(row => serialize(row, ctx))),
183 meta: {
184 total: count,
185 id: model.primaryKeyAttribute
186 }
187 }
188 },
189 update: (...args) => async (ctx) => {
190 let { model, serialize, callback } = ctxExtend(ctx, ...args)
191 let object = await fetchObject(ctx, ctx.params.id)
192
193 if (callback.length == 4) {
194 let afterCalback = async () => { }
195 await ctx.models.sequelize.transaction(async (transaction) => {
196 transaction.then = (callback) => afterCalback = callback
197 object = await callback(ctx, object, ctx.request.body, transaction)
198 })
199 await afterCalback()
200 } else {
201 object = await callback(ctx, object, ctx.request.body)
202 }
203 object = await fetchObject(ctx, object[model.primaryKeyAttribute])
204 ctx.body = {
205 data: await serialize(object, ctx),
206 meta: {
207 id: model.primaryKeyAttribute
208 }
209 }
210 },
211 create: (...args) => async (ctx) => {
212 let { model, serialize, callback } = ctxExtend(ctx, ...args)
213
214 let object = model.build({})
215 if (callback.length == 4) {
216 let afterCalback = async () => { }
217 await ctx.models.sequelize.transaction(async (transaction) => {
218 transaction.then = (callback) => afterCalback = callback
219 object = await callback(ctx, object, ctx.request.body, transaction)
220 })
221 await afterCalback()
222 } else {
223 object = await callback(ctx, object, ctx.request.body)
224 }
225 object = await fetchObject(ctx, object[model.primaryKeyAttribute])
226 ctx.body = {
227 data: await serialize(object, ctx),
228 meta: {
229 id: model.primaryKeyAttribute
230 }
231 }
232 },
233 destroy: (...args) => async (ctx) => {
234 let { model, callback } = ctxExtend(ctx, ...args)
235 let object = await fetchObject(ctx, ctx.params.id)
236 if (callback) {
237 if (callback.length == 3) {
238 await ctx.models.sequelize.transaction(async (transaction) => {
239 object = await callback(ctx, object, transaction)
240 })
241 } else {
242 object = await callback(ctx, object)
243 }
244 } else {
245 await object.destroy()
246 }
247 ctx.body = {
248 data: primaryKeyValue(model, object),
249 meta: {
250 id: model.primaryKeyAttribute
251 }
252 }
253 }
254}
255module.exports = { filterOptions, getAssociationsParams, handlers }
\No newline at end of file