@cuboapp/api-backend 1.0.0

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.
@@ -0,0 +1,38 @@
1
+ import keyBy from 'lodash-es/keyBy'
2
+
3
+ import { CuboEntitiesMap, CuboUserModel } from '../../types'
4
+
5
+ import { ApiHelpers } from '.'
6
+
7
+ export class ApiHelpersUsers<T extends CuboEntitiesMap<T>> {
8
+ constructor(private helpers: ApiHelpers<T>) {}
9
+
10
+ public async getMany(force = false): Promise<CuboUserModel[]> {
11
+ const users = await this.helpers.getCached(
12
+ `users`,
13
+ async () => {
14
+ return this.helpers.api
15
+ .apiRequest<{ items: CuboUserModel[] }>(`/api/v1/users?account_id=${this.helpers.api.account.id}`)
16
+ .then((res) => res.items)
17
+ },
18
+ force
19
+ )
20
+
21
+ return users
22
+ }
23
+
24
+ public async getManyById(force = false) {
25
+ const users = await this.getMany(force)
26
+ return this.helpers.getCached(`users_by_id`, async () => keyBy(users, (i) => +i.id), force)
27
+ }
28
+
29
+ public async getByEmail(email: string): Promise<CuboUserModel | undefined> {
30
+ const entities = await this.getMany()
31
+ return entities.find((e) => e.email === email)
32
+ }
33
+
34
+ public async getById(id: number): Promise<CuboUserModel | undefined> {
35
+ const entities = await this.getManyById()
36
+ return entities[id]
37
+ }
38
+ }
@@ -0,0 +1,292 @@
1
+ import cloneDeep from 'lodash-es/cloneDeep'
2
+
3
+ import { CUBO_CRUD_DEFAULT_FIELDS, CUBO_CRUD_QUERY_KEY_REGEX } from '../../constants'
4
+ import {
5
+ CuboCrudFindQuery,
6
+ CuboCrudMethodOptions,
7
+ CuboCrudRequest,
8
+ CuboCrudWith,
9
+ CuboEntitiesMap,
10
+ CuboEntityDefaultUsersKeys,
11
+ CuboEntityFieldModel,
12
+ CuboEntityModel
13
+ } from '../../types'
14
+
15
+ import { ApiHelpers } from '.'
16
+
17
+ export class ApiHelpersWithes<T extends CuboEntitiesMap<T>> {
18
+ constructor(private helpers: ApiHelpers<T>) {}
19
+
20
+ private async getKeyWithes(
21
+ key: string,
22
+ entity: CuboEntityModel,
23
+ withes: CuboCrudWith[],
24
+ opts: {
25
+ replacements?: Record<string, any>
26
+ selects?: string[]
27
+ joins?: string[]
28
+ sorts?: string[]
29
+ conditions?: string[]
30
+ conditionKey?: string
31
+ conditionValue?: any
32
+ sortKey?: string
33
+ sortDirection?: 'asc' | 'desc'
34
+ }
35
+ ): Promise<{ withes: CuboCrudWith[]; joins: string[]; selects?: string[]; conditions?: string[]; replacements?: string[] }> {
36
+ const normalKey = key.replace(CUBO_CRUD_QUERY_KEY_REGEX, '')
37
+
38
+ if (normalKey !== key) {
39
+ throw new Error('invalid relation key: "' + key + '"')
40
+ }
41
+
42
+ withes = withes || []
43
+ opts.joins = opts.joins || []
44
+ opts.replacements = opts.replacements || {}
45
+
46
+ const needSelect = opts.selects !== undefined && Array.isArray(opts.selects)
47
+ const needConditions =
48
+ opts.conditions !== undefined &&
49
+ Array.isArray(opts.conditions) &&
50
+ typeof opts.conditionKey === 'string' &&
51
+ opts.conditionValue !== undefined
52
+
53
+ const needSorts = opts.sorts !== undefined && Array.isArray(opts.sorts)
54
+
55
+ const parts = normalKey.split('.') as (keyof T | CuboEntityDefaultUsersKeys)[]
56
+
57
+ const currentKey: string[] = []
58
+ let lastEntity: CuboEntityModel | undefined = entity
59
+ let joinIndex = (opts.joins.length || 0) + 1
60
+ let joinAlias = 0
61
+
62
+ for (const part of parts) {
63
+ const prevPartKey = currentKey.join('.')
64
+ const prevSelectKey = currentKey.join('_')
65
+
66
+ currentKey.push(part as string)
67
+
68
+ const partKey = currentKey.join('.')
69
+ const tableName = currentKey.join('_')
70
+
71
+ const joinCustomField: CuboEntityFieldModel | undefined = lastEntity?.fields?.find((f) => f.extra?.select_entity_key === part)
72
+ const joinDefaultField = CUBO_CRUD_DEFAULT_FIELDS.find((f) => f.extra?.select_entity_key === part)
73
+
74
+ const ex = withes.find((i) => i.key === partKey)
75
+
76
+ // если нашли кастомное поле
77
+ if (joinCustomField) {
78
+ const relation_entity_id: number | undefined = joinCustomField?.extra?.select_entity_id
79
+ const relation_entity_alias: keyof T | undefined = joinCustomField?.extra?.select_entity_alias as keyof T
80
+
81
+ if (!relation_entity_id && !relation_entity_alias) {
82
+ throw new Error('relation for key not found: "' + partKey + '"')
83
+ }
84
+
85
+ if (relation_entity_id) {
86
+ lastEntity = await this.helpers.entities.getById(relation_entity_id)
87
+ } else if (relation_entity_alias) {
88
+ lastEntity = await this.helpers.entities.getByAlias(relation_entity_alias)
89
+ }
90
+
91
+ if (!lastEntity) {
92
+ throw new Error('entity for relation key not found: "' + partKey + '"')
93
+ }
94
+
95
+ if (!ex) {
96
+ joinIndex += 1
97
+ joinAlias += 1
98
+
99
+ withes.push({
100
+ entity: cloneDeep(lastEntity),
101
+ full_key: partKey,
102
+ key: part as string,
103
+ table_name: tableName
104
+ })
105
+
106
+ opts.joins.push(
107
+ `
108
+ left join
109
+ ${lastEntity.alias} t_${tableName}
110
+ on
111
+ t_${tableName}.id = ${prevSelectKey ? `t_${prevSelectKey}` : 't1'}.${joinCustomField.alias}
112
+ `
113
+ )
114
+ }
115
+
116
+ if (needSelect) {
117
+ opts.selects = opts.selects || []
118
+
119
+ for (const selectKey of [
120
+ ...lastEntity.fields.map((f) => `t_${tableName}.${f.alias} as ${tableName}_${f.alias}`),
121
+ ...CUBO_CRUD_DEFAULT_FIELDS.map((f) => `t_${tableName}.${f.alias} as ${tableName}_${f.alias}`)
122
+ ]) {
123
+ if (!opts.selects.includes(selectKey)) {
124
+ opts.selects.push(selectKey)
125
+ }
126
+ }
127
+ }
128
+ } else if (joinDefaultField) {
129
+ if (ex === undefined) {
130
+ withes.push({
131
+ entity: { id: undefined, alias: 'users', fields: [] },
132
+ full_key: partKey,
133
+ key: part as string,
134
+ table_name: tableName
135
+ })
136
+ }
137
+ }
138
+ }
139
+
140
+ const ret: any = {
141
+ withes,
142
+ joins: opts.joins,
143
+ conditions: opts.conditions || undefined,
144
+ replacements: opts.replacements || undefined
145
+ }
146
+
147
+ if (needSelect) {
148
+ ret.selects = opts.selects
149
+ }
150
+
151
+ if (needConditions) {
152
+ const conditionKey = opts.conditionKey?.replace(CUBO_CRUD_QUERY_KEY_REGEX, '') || ''
153
+ const conditionKeyParts = conditionKey.split('.')
154
+ const conditionLastKey = conditionKeyParts.pop()
155
+
156
+ const conditionField =
157
+ lastEntity.fields?.find((f) => f.alias === conditionLastKey) || CUBO_CRUD_DEFAULT_FIELDS.find((f) => f.alias === conditionLastKey)
158
+
159
+ if (!conditionField) {
160
+ throw new Error('condition field "' + conditionKey + '" not found in entity "' + entity.alias + '"')
161
+ }
162
+
163
+ let conditionTableAlias = 't1'
164
+ if (conditionKeyParts.length > 0) {
165
+ const exWith = withes.find((w) => (w.full_key = conditionKeyParts.join('.')))
166
+
167
+ if (!exWith) {
168
+ throw new Error('condition field relation "' + conditionKey + '" not found')
169
+ }
170
+
171
+ conditionTableAlias = 't_' + exWith.table_name
172
+ }
173
+
174
+ opts.conditions = opts.conditions || []
175
+
176
+ const preparedCondition = this.helpers.clearFieldConditionValue(conditionField, opts.conditionValue)
177
+
178
+ const prepared = this.helpers.prepareConditionSql(
179
+ `${conditionTableAlias}.${conditionField.alias}`,
180
+ `${conditionTableAlias}_${conditionField.alias}_value`,
181
+ preparedCondition.value,
182
+ preparedCondition.condition,
183
+ preparedCondition.is_not
184
+ )
185
+
186
+ opts.conditions.push(...(prepared.conditions || []))
187
+ Object.assign(opts.replacements, prepared.replacements || {})
188
+ }
189
+
190
+ // сортировка
191
+ if (needSorts) {
192
+ const sortParts = (opts.sortKey?.replace(CUBO_CRUD_QUERY_KEY_REGEX, '') || '').split(',')
193
+
194
+ for (const sortPart of sortParts) {
195
+ const sortKeyParts = sortPart.split('.')
196
+ const sortLastKey = sortKeyParts.pop()
197
+
198
+ const sortField =
199
+ lastEntity.fields?.find((f) => f.alias === sortLastKey) || CUBO_CRUD_DEFAULT_FIELDS.find((f) => f.alias === sortLastKey)
200
+ if (!sortField) {
201
+ throw new Error('sort field "' + sortPart + '" not found')
202
+ }
203
+
204
+ opts.sorts = opts.sorts || []
205
+
206
+ let sortTableAlias = 't1'
207
+ if (sortKeyParts.length > 0) {
208
+ const exWith = withes.find((w) => (w.full_key = sortKeyParts.join('.')))
209
+
210
+ if (!exWith) {
211
+ throw new Error('sort field relation "' + sortPart + '" not found')
212
+ }
213
+
214
+ sortTableAlias = 't_' + exWith.table_name
215
+ }
216
+
217
+ opts.sorts.push(`${sortTableAlias}.${sortField.alias} ${opts.sortDirection || 'asc'}`)
218
+ }
219
+ }
220
+
221
+ return ret
222
+ }
223
+
224
+ public async prepare(req: CuboCrudRequest, entity: CuboEntityModel, opts?: CuboCrudMethodOptions): Promise<Partial<CuboCrudFindQuery>> {
225
+ const { with: __withes, sort: _sort, limit: _limit, ...other } = req.query || {}
226
+
227
+ const _withes = (__withes || '').split(',').filter((i: string) => i !== '')
228
+
229
+ if (opts?.queryOptions?.withes?.length) {
230
+ _withes.push(...(opts?.queryOptions?.withes || []).filter((i: string) => i !== ''))
231
+ }
232
+
233
+ const joins: string[] = [...(opts?.queryOptions?.joins || [])]
234
+ const selects: string[] = [...(opts?.queryOptions?.selects || [])]
235
+ const conditions: string[] = [...(opts?.queryOptions?.conditions || [])]
236
+ const replacements: Record<string, any> = { ...(opts?.queryOptions?.replacements || {}) }
237
+
238
+ const withes: CuboCrudWith[] = []
239
+ const sorts: string[] = []
240
+
241
+ // from sort key
242
+ if (_sort) {
243
+ const sortKeyParts = _sort.split('.')
244
+
245
+ if (sortKeyParts.length > 1) {
246
+ const sortDirection = _sort[0] === '-' ? 'desc' : 'asc'
247
+ const sortAlias = sortDirection === 'desc' ? sortKeyParts.join('.').slice(1) : sortKeyParts.join('.')
248
+
249
+ await this.getKeyWithes(sortAlias, entity, withes, {
250
+ joins,
251
+ replacements,
252
+ sorts,
253
+ sortKey: _sort,
254
+ sortDirection
255
+ })
256
+ }
257
+ }
258
+
259
+ // from query-string filters
260
+ if (Object.keys(other || {}).length) {
261
+ for (const key in other) {
262
+ const keyParts = key.split('.')
263
+
264
+ if (keyParts.length > 1) {
265
+ const conditionValue = other[key]
266
+
267
+ await this.getKeyWithes(keyParts.join('.'), entity, withes, {
268
+ joins,
269
+ replacements,
270
+ conditions,
271
+ conditionKey: key,
272
+ conditionValue
273
+ })
274
+ }
275
+ }
276
+ }
277
+
278
+ // from ?with query param
279
+ if (Object.keys(_withes || {}).length) {
280
+ for (const key of _withes) {
281
+ await this.getKeyWithes(key, entity, withes, {
282
+ joins,
283
+ selects
284
+ })
285
+ }
286
+ }
287
+
288
+ // console.log({ sorts, withes, joins, selects, conditions, replacements })
289
+
290
+ return { sorts, withes, joins, selects, conditions, replacements }
291
+ }
292
+ }
package/src/index.ts ADDED
@@ -0,0 +1,367 @@
1
+ import { type CuboConfigEntity, CuboConfig } from '@cuboapp/config'
2
+ import { CuboAccount, CuboApiEntitiesMap, CuboUser } from '@cuboapp/types'
3
+ import { QueryTypes } from 'sequelize'
4
+
5
+ import { ApiHelpers } from './helpers'
6
+ import { CuboCrudAugmentationsStore, CuboCrudGetManyResponse, CuboCrudMethodOptions, CuboCrudRequest } from './types'
7
+ import { dbPrepareInsertQueryString, dbPrepareUpdateQueryString } from './utils'
8
+
9
+ export * from './types'
10
+ export * from './utils'
11
+
12
+ export type CuboBackendApiOptions<T> = {
13
+ entities?: CuboConfigEntity[]
14
+ }
15
+
16
+ export class CuboBackendApi<T extends CuboApiEntitiesMap<T>> {
17
+ public augmentations: Partial<CuboCrudAugmentationsStore<T>> = {}
18
+ public options: CuboBackendApiOptions<T> = {}
19
+
20
+ constructor(public config: CuboConfig, augmentations?: Partial<CuboCrudAugmentationsStore<T>>, options?: CuboBackendApiOptions<T>) {
21
+ this.augmentations = augmentations || {}
22
+ this.options = options || {}
23
+ }
24
+
25
+ // добавил потому что в хелперах он при getConnection берет id аккаунта и делает cacheKey
26
+ public account: CuboAccount = {
27
+ id: 0,
28
+ name: '',
29
+ subdomain: '',
30
+ domain: '',
31
+ location: '',
32
+ language: '',
33
+ favicon: undefined
34
+ }
35
+
36
+ public user: CuboUser
37
+ public helpers = new ApiHelpers<T>(this)
38
+
39
+ async init() {
40
+ const auth = await this.authRequest<{ account: CuboAccount; user: CuboUser }>('/me')
41
+
42
+ this.account = auth.account
43
+ this.user = auth.user
44
+
45
+ if (!this.account) {
46
+ throw new Error('Account not found')
47
+ }
48
+ }
49
+
50
+ async apiRequest<T>(url: string, opts?: RequestInit) {
51
+ const subdomain = this.account.subdomain
52
+ const rootDomain = this.config.api?.rootDomain || 'cuboapp.ru'
53
+ const baseUrl = `https://${subdomain}.${rootDomain}`
54
+
55
+ return this.request<T>(baseUrl, url, opts)
56
+ }
57
+
58
+ async authRequest<T>(url: string, opts?: RequestInit) {
59
+ const baseUrl = this.config.api?.authDomain || 'https://auth.cuboapp.ru'
60
+
61
+ return this.request<T>(baseUrl, url, opts)
62
+ }
63
+
64
+ private async request<T>(baseUrl: string, url: string, opts?: RequestInit & { withoutContentType?: boolean }) {
65
+ const headers: Record<string, any> = {
66
+ ...(opts?.headers || {}),
67
+ Authorization: this.config.api?.authToken || ''
68
+ }
69
+
70
+ if (!headers['Content-Type'] && !opts?.withoutContentType) {
71
+ headers['Accept'] = 'application/json'
72
+ headers['Content-Type'] = 'application/json'
73
+ }
74
+
75
+ const response = await fetch(baseUrl.replace(/\/$/, '') + '/' + url.replace(/^\//, ''), {
76
+ ...(opts || {}),
77
+ headers
78
+ })
79
+
80
+ if ([201, 200].includes(response.status)) {
81
+ try {
82
+ const json = await response.json()
83
+
84
+ return json as Promise<T>
85
+ } catch (e) {
86
+ const text = await response.text()
87
+
88
+ try {
89
+ const json = text ? JSON.parse(text) : null
90
+
91
+ return json as Promise<T>
92
+ } catch {
93
+ throw { status: 500, error: 'Invalid response', text }
94
+ }
95
+ }
96
+ } else {
97
+ throw {
98
+ status: response.status,
99
+ error: response.statusText,
100
+ text: await response.text()
101
+ }
102
+ }
103
+ }
104
+
105
+ private async getEntity(entityAlias: Extract<keyof T, string>) {
106
+ const entity = await this.helpers.entities.getByAlias(entityAlias)
107
+ if (!entity) {
108
+ throw new Error('Entity no found: "' + String(entityAlias) + '"')
109
+ }
110
+
111
+ return entity
112
+ }
113
+
114
+ async getMany<K extends Extract<keyof T, string> = Extract<keyof T, string>>(
115
+ entityAlias: K,
116
+ req?: CuboCrudRequest,
117
+ opts?: CuboCrudMethodOptions
118
+ ): Promise<CuboCrudGetManyResponse<T[K]>> {
119
+ opts = opts || {}
120
+ req = req || {}
121
+ const entity = await this.getEntity(entityAlias)
122
+ const augmentation = this.augmentations?.[entityAlias]
123
+
124
+ if (augmentation?.beforeGetMany) {
125
+ opts.queryOptions = await augmentation.beforeGetMany(req, opts)
126
+ }
127
+
128
+ // get all joins
129
+ const queryDto = await this.helpers.withes.prepare(req, entity, opts)
130
+ queryDto.withDeleted = opts.queryOptions?.withDeleted
131
+
132
+ // create find query
133
+ const regularQuery = await this.helpers.query.createFindQuery(req, entity, queryDto)
134
+ const countQuery = await this.helpers.query.createFindQuery(req, entity, queryDto, true)
135
+
136
+ const db = await this.helpers.getConnection('read')
137
+
138
+ if (opts?.log) {
139
+ console.log(`QUERY:getMany to "${String(entityAlias)}":`, regularQuery.sql, regularQuery.replacements, { req, opts, queryDto })
140
+ }
141
+
142
+ const rows = await db.query<any>(regularQuery.sql!, {
143
+ type: QueryTypes.SELECT,
144
+ replacements: regularQuery.replacements,
145
+ transaction: opts?.transaction
146
+ })
147
+ const [totals] = await db.query<any>(countQuery.sql!, {
148
+ type: QueryTypes.SELECT,
149
+ replacements: countQuery.replacements,
150
+ transaction: opts?.transaction
151
+ })
152
+
153
+ const response = {
154
+ rows: await this.helpers.convert.prepareAll(req, entity, rows, regularQuery),
155
+ totals: {
156
+ count: totals.total !== undefined ? +totals.total : 0
157
+ }
158
+ }
159
+
160
+ if (augmentation?.afterGetMany && !opts?.excludeHooks?.includes('afterGetMany')) {
161
+ return augmentation.afterGetMany(response, req, opts)
162
+ }
163
+
164
+ return response
165
+ }
166
+
167
+ async getOne<K extends Extract<keyof T, string> = Extract<keyof T, string>>(
168
+ entityAlias: K,
169
+ req: CuboCrudRequest,
170
+ opts?: CuboCrudMethodOptions
171
+ ): Promise<T[K] | undefined> {
172
+ opts = opts || {}
173
+
174
+ const entity = await this.getEntity(entityAlias)
175
+ const augmentation = this.augmentations?.[entityAlias]
176
+ if (augmentation?.beforeGetOne) {
177
+ opts.queryOptions = await augmentation.beforeGetOne(req, opts)
178
+ }
179
+
180
+ // get all joins
181
+ const queryDto = await this.helpers.withes.prepare(req, entity, opts)
182
+ queryDto.withDeleted = opts.queryOptions?.withDeleted
183
+ queryDto.limit = 1
184
+
185
+ const query = await this.helpers.query.createFindQuery(req, entity, queryDto)
186
+
187
+ const db = await this.helpers.getConnection('read')
188
+
189
+ const [row] = await db.query<any>(query.sql!, {
190
+ type: QueryTypes.SELECT,
191
+ replacements: query.replacements,
192
+ transaction: opts?.transaction
193
+ })
194
+
195
+ if (opts?.log) {
196
+ console.log(`QUERY:getOne to "${String(entityAlias)}":`, query.sql, query.replacements)
197
+ }
198
+
199
+ if (!row) {
200
+ return null as any
201
+ // throw { status: 404, message: 'Element "' + entityAlias + '" not found', details: req }
202
+ }
203
+
204
+ const response = await this.helpers.convert.prepareOne(req, entity, row, query)
205
+
206
+ if (augmentation?.afterGetOne && !opts?.excludeHooks?.includes('afterGetOne')) {
207
+ return augmentation.afterGetOne(response, req, opts)
208
+ }
209
+
210
+ return response
211
+ }
212
+
213
+ async createOne<K extends Extract<keyof T, string> = Extract<keyof T, string>>(
214
+ entityAlias: K,
215
+ req: CuboCrudRequest,
216
+ opts?: CuboCrudMethodOptions
217
+ ): Promise<T[K]> {
218
+ opts = opts || {}
219
+ const entity = await this.helpers.entities.getByAlias(entityAlias)
220
+ const augmentation = this.augmentations?.[entityAlias]
221
+
222
+ if (augmentation?.beforeCreate) {
223
+ opts.queryOptions = await augmentation.beforeCreate(req, opts)
224
+ }
225
+
226
+ const dto = await this.helpers.data.prepare('create', entity!.fields, req.body, opts?.user_id || 0)
227
+ if (!Object.keys(dto)) {
228
+ throw { status: 400, text: 'No keys to update' }
229
+ }
230
+
231
+ const prepared = dbPrepareInsertQueryString(dto)
232
+ const db = await this.helpers.getConnection('write')
233
+
234
+ const sql = `
235
+ insert into ${entity!.alias} (${prepared.keys})
236
+ values (${prepared.values})
237
+ returning id
238
+ `
239
+
240
+ const created_id = await db
241
+ .query(sql, {
242
+ type: QueryTypes.INSERT,
243
+ replacements: prepared.replacements || {},
244
+ transaction: opts?.transaction
245
+ })
246
+ .then((res: any) => {
247
+ return +res?.[0]?.[0]?.id
248
+ })
249
+
250
+ if (!created_id) {
251
+ throw { status: 400, text: 'Unable to create entity' }
252
+ }
253
+
254
+ const response = await this.getOne<K>(entityAlias, { query: { id: created_id } }, opts)
255
+
256
+ if (!response) {
257
+ throw { status: 400, text: 'Unable to find entity after create' }
258
+ }
259
+
260
+ if (augmentation?.afterCreate && !opts?.excludeHooks?.includes('afterCreate')) {
261
+ return augmentation.afterCreate(response, req, opts)
262
+ }
263
+
264
+ return response
265
+ }
266
+
267
+ async updateOne<K extends Extract<keyof T, string> = Extract<keyof T, string>>(
268
+ entityAlias: K,
269
+ req: CuboCrudRequest,
270
+ opts?: CuboCrudMethodOptions
271
+ ): Promise<T[K] | undefined> {
272
+ opts = opts || {}
273
+ const entity = await this.helpers.entities.getByAlias(entityAlias)
274
+ const augmentation = this.augmentations?.[entityAlias]
275
+
276
+ if (augmentation?.beforeUpdate) {
277
+ opts.queryOptions = await augmentation.beforeUpdate(req, opts)
278
+ }
279
+
280
+ const item: any = await this.getOne(entityAlias, req, opts)
281
+
282
+ if (!item) {
283
+ throw { status: 404, text: 'Entity not found' }
284
+ }
285
+
286
+ const dto = await this.helpers.data.prepare('update', entity!.fields, req.body, opts?.user_id || 0)
287
+ if (!Object.keys(dto)) {
288
+ throw { status: 400, text: 'No keys to update' }
289
+ }
290
+
291
+ // console.log(dto)
292
+
293
+ const prepared = dbPrepareUpdateQueryString(dto)
294
+ const db = await this.helpers.getConnection('write')
295
+
296
+ const sql = `
297
+ update ${entity!.alias}
298
+ set ${prepared.query}
299
+ where id = ${item.id}
300
+ `
301
+
302
+ await db.query(sql, {
303
+ type: QueryTypes.UPDATE,
304
+ replacements: {
305
+ ...(prepared.replacements || {})
306
+ },
307
+ transaction: opts?.transaction
308
+ })
309
+
310
+ const response = await this.getOne<K>(entityAlias, req, opts)
311
+ if (!response) {
312
+ throw { status: 400, text: 'Unable to find entity after update' }
313
+ }
314
+
315
+ if (augmentation?.afterUpdate && !opts?.excludeHooks?.includes('afterUpdate')) {
316
+ return augmentation.afterUpdate(response, req, opts)
317
+ }
318
+
319
+ return response
320
+ }
321
+
322
+ async deleteOne<K extends Extract<keyof T, string>>(
323
+ entityAlias: K,
324
+ req: CuboCrudRequest,
325
+ opts?: CuboCrudMethodOptions
326
+ ): Promise<boolean> {
327
+ opts = opts || {}
328
+ const entity = await this.helpers.entities.getByAlias(entityAlias)
329
+ const augmentation = this.augmentations?.[entityAlias]
330
+
331
+ if (augmentation?.beforeDelete) {
332
+ opts.queryOptions = await augmentation.beforeDelete(req, opts)
333
+ }
334
+
335
+ const item: any = await this.getOne(entityAlias, req, opts)
336
+
337
+ const dto = await this.helpers.data.prepare('delete', entity!.fields, req.body || {}, opts?.user_id)
338
+ if (!Object.keys(dto)) {
339
+ throw { status: 400, text: 'No keys to update' }
340
+ }
341
+
342
+ // console.log(dto)
343
+
344
+ const prepared = dbPrepareUpdateQueryString(dto)
345
+ const db = await this.helpers.getConnection('write')
346
+
347
+ const sql = `
348
+ update ${entity!.alias}
349
+ set ${prepared.query}
350
+ where id = ${item.id}
351
+ `
352
+
353
+ await db.query(sql, {
354
+ type: QueryTypes.UPDATE,
355
+ replacements: prepared.replacements || {},
356
+ transaction: opts?.transaction
357
+ })
358
+
359
+ if (augmentation?.afterDelete && !opts?.excludeHooks?.includes('afterDelete')) {
360
+ return augmentation.afterDelete(item, req, opts)
361
+ }
362
+
363
+ return true
364
+ }
365
+ }
366
+
367
+ export * from './helpers'