@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.
- package/.prettierrc +8 -0
- package/package.json +25 -0
- package/pnpm-lock.yaml +378 -0
- package/src/constants/backend.ts +74 -0
- package/src/constants/base.ts +11 -0
- package/src/constants/index.ts +2 -0
- package/src/helpers/convert.ts +135 -0
- package/src/helpers/data.ts +147 -0
- package/src/helpers/entities.ts +30 -0
- package/src/helpers/index.ts +381 -0
- package/src/helpers/query.ts +145 -0
- package/src/helpers/users.ts +38 -0
- package/src/helpers/withes.ts +292 -0
- package/src/index.ts +367 -0
- package/src/types/augmentation.ts +29 -0
- package/src/types/basic.ts +59 -0
- package/src/types/db.ts +24 -0
- package/src/types/index.ts +3 -0
- package/src/utils/index.ts +128 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +21 -0
|
@@ -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'
|