UNPKG

7.9 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("./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}
93
94async function fetchObject(ctx, id) {
95 let dbQuery = { where: primaryKeyValue(ctx.model, id), rejectOnEmpty: true }
96 if (ctx.associations)
97 dbQuery = deepMerge(getAssociationsParams(ctx.model, {}, ctx.associations), dbQuery)
98 return await ctx.model.findOne(await ctx.customize(ctx, dbQuery))
99}
100
101function primaryKeyValue(model, object) {
102 let value = isObject(object) ? object[model.primaryKeyAttribute] : object
103 return fromPairs([[model.primaryKeyAttribute, value]])
104}
105
106function ctxExtend(ctx, modelName, options) {
107 const model = isString(modelName) ? ctx.models[modelName] : modelName
108 let [callback, opts] = isFunction(options) ? [options, {}] : [null, options || {}]
109 let defaultOptions = {
110 model: model,
111 customize: async (_ctx, dbQuery) => dbQuery,
112 serialize: async object => omitBy(object.toJSON(), (_v, k) => k.match(/password|encrypted/)),
113 callback: callback || get(options, findKey(pick(options, "update", "create", "destroy"), isFunction)),
114 associations: opts.associations || model.preloadAssociations || []
115 }
116 return defaults(ctx, opts, defaultOptions)
117}
118const handlers = {
119 auth: (ctx, next) => {
120 if (ctx.state.allowedRoles) {
121 if (ctx.state.user && includes(ctx.state.allowedRoles, ctx.state.user.role)) {
122 return next()
123 } else {
124 ctx.status = 401
125 }
126 }
127 else
128 return next()
129 },
130 show: (...args) => async (ctx) => {
131 let { model, serialize } = ctxExtend(ctx, ...args)
132 let object = await fetchObject(ctx, ctx.params.id)
133
134 ctx.body = {
135 data: await serialize(object, ctx),
136 meta: {
137 id: model.primaryKeyAttribute
138 }
139 }
140 },
141 list: (...args) => async (ctx) => {
142 const { model, serialize, customize, associations } = ctxExtend(ctx, ...args)
143
144 let filter = filterOptions(model, ctx.request.query)
145 let associationsFilter = getAssociationsFilter(model, ctx.request.query, associations)
146 let associationsParams = getAssociationsParams(model, ctx.request.query, associations)
147
148 let dbQuery = await customize(ctx, deepMerge({}, filter, associationsParams))
149 let countQuery = await customize(ctx, deepMerge({}, filter, associationsFilter))
150
151 let rows = await model.findAll(dbQuery)
152 let count = await model.count(countQuery)
153
154 ctx.body = {
155 data: await Promise.all(rows.map(row => serialize(row, ctx))),
156 meta: {
157 total: count,
158 id: model.primaryKeyAttribute
159 }
160 }
161 },
162 update: (...args) => async (ctx) => {
163 let { model, serialize, callback } = ctxExtend(ctx, ...args)
164 let object = await fetchObject(ctx, ctx.params.id)
165
166 if (callback.length == 4) {
167 let afterCalback = async () => { }
168 await ctx.models.sequelize.transaction(async (transaction) => {
169 transaction.then = (callback) => afterCalback = callback
170 object = await callback(ctx, object, ctx.request.body, transaction)
171 })
172 await afterCalback()
173 } else {
174 object = await callback(ctx, object, ctx.request.body)
175 }
176 object = await fetchObject(ctx, object[model.primaryKeyAttribute])
177 ctx.body = {
178 data: await serialize(object, ctx),
179 meta: {
180 id: model.primaryKeyAttribute
181 }
182 }
183 },
184 create: (...args) => async (ctx) => {
185 let { model, serialize, callback } = ctxExtend(ctx, ...args)
186
187 let object = model.build({})
188 if (callback.length == 4) {
189 let afterCalback = async () => { }
190 await ctx.models.sequelize.transaction(async (transaction) => {
191 transaction.then = (callback) => afterCalback = callback
192 object = await callback(ctx, object, ctx.request.body, transaction)
193 })
194 await afterCalback()
195 } else {
196 object = await callback(ctx, object, ctx.request.body)
197 }
198 object = await fetchObject(ctx, object[model.primaryKeyAttribute])
199 ctx.body = {
200 data: await serialize(object, ctx),
201 meta: {
202 id: model.primaryKeyAttribute
203 }
204 }
205 },
206 destroy: (...args) => async (ctx) => {
207 let { model, callback } = ctxExtend(ctx, ...args)
208 let object = await fetchObject(ctx, ctx.params.id)
209 if (callback) {
210 if (callback.length == 3) {
211 await ctx.models.sequelize.transaction(async (transaction) => {
212 object = await callback(ctx, object, transaction)
213 })
214 } else {
215 object = await callback(ctx, object)
216 }
217 } else {
218 await object.destroy()
219 }
220 ctx.body = {
221 data: primaryKeyValue(model, object),
222 meta: {
223 id: model.primaryKeyAttribute
224 }
225 }
226 }
227}
228module.exports = { filterOptions, getAssociationsParams, handlers }
\No newline at end of file