@cuboapp/api-backend 1.0.8 → 1.0.11
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-lock.json +104 -0
- package/package.json +7 -12
- package/src/constants/index.ts +73 -2
- package/src/helpers/convert.ts +24 -49
- package/src/helpers/data.ts +3 -4
- package/src/helpers/index.ts +189 -47
- package/src/helpers/withes.ts +3 -3
- package/src/index.ts +45 -104
- package/src/types/basic.ts +1 -1
- package/src/types/db.ts +3 -1
- package/pnpm-lock.yaml +0 -378
- package/src/constants/backend.ts +0 -74
- package/src/constants/base.ts +0 -11
- package/src/helpers/entities.ts +0 -30
- package/src/helpers/query.ts +0 -157
- package/src/helpers/users.ts +0 -37
- package/src/utils/index.ts +0 -128
package/src/constants/backend.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { CuboEntityField } from '@cuboapp/types'
|
|
2
|
-
|
|
3
|
-
import { CUBO_ENTITY_FIELD_TYPE } from './base'
|
|
4
|
-
|
|
5
|
-
export const CUBO_CRUD_QUERY_KEY_REGEX = /[^a-zA-Z\.\_]/g
|
|
6
|
-
|
|
7
|
-
export const CUBO_CRUD_QUERY_SYMBOL_NOT = 'not'
|
|
8
|
-
|
|
9
|
-
export const CUBO_CRUD_DEFAULT_PAGE_LIMIT = 25
|
|
10
|
-
export const CUBO_CRUD_MAX_PAGE_LIMIT = 500
|
|
11
|
-
|
|
12
|
-
export const CUBO_CRUD_QUERY_CONDITION = {
|
|
13
|
-
EQ: 'eq',
|
|
14
|
-
EX: 'ex',
|
|
15
|
-
LIKE: 'like',
|
|
16
|
-
ILIKE: 'ilike',
|
|
17
|
-
IN: 'in',
|
|
18
|
-
NIN: 'nin',
|
|
19
|
-
GT: 'gt',
|
|
20
|
-
LT: 'lt',
|
|
21
|
-
GTE: 'gte',
|
|
22
|
-
LTE: 'lte'
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export const CUBO_CRUD_DEFAULT_FIELDS: Pick<CuboEntityField, 'type_id' | 'alias' | 'extra'>[] = [
|
|
26
|
-
{
|
|
27
|
-
type_id: CUBO_ENTITY_FIELD_TYPE.NUMBER,
|
|
28
|
-
alias: 'id',
|
|
29
|
-
extra: {
|
|
30
|
-
number_subtype: 'int'
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
type_id: CUBO_ENTITY_FIELD_TYPE.TIMESTAMP,
|
|
35
|
-
alias: 'created_at',
|
|
36
|
-
extra: {}
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
type_id: CUBO_ENTITY_FIELD_TYPE.SELECT,
|
|
40
|
-
alias: 'created_by',
|
|
41
|
-
extra: {
|
|
42
|
-
select_subtype: 'user',
|
|
43
|
-
select_entity_key: 'created_by'
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
type_id: CUBO_ENTITY_FIELD_TYPE.TIMESTAMP,
|
|
48
|
-
alias: 'modified_at',
|
|
49
|
-
extra: {}
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
type_id: CUBO_ENTITY_FIELD_TYPE.SELECT,
|
|
53
|
-
alias: 'modified_by',
|
|
54
|
-
extra: {
|
|
55
|
-
select_subtype: 'user',
|
|
56
|
-
select_entity_key: 'modified_by'
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
type_id: CUBO_ENTITY_FIELD_TYPE.TIMESTAMP,
|
|
61
|
-
alias: 'deleted_at',
|
|
62
|
-
extra: {}
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
type_id: CUBO_ENTITY_FIELD_TYPE.SELECT,
|
|
66
|
-
alias: 'deleted_by',
|
|
67
|
-
extra: {
|
|
68
|
-
select_subtype: 'user',
|
|
69
|
-
select_entity_key: 'modified_by'
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
]
|
|
73
|
-
|
|
74
|
-
export const CUBO_CRUD_DEFAULT_FIELDS_ALIASES = CUBO_CRUD_DEFAULT_FIELDS.map((i) => i.alias)
|
package/src/constants/base.ts
DELETED
package/src/helpers/entities.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { CuboApiEntitiesMap, CuboEntity } from '@cuboapp/types'
|
|
2
|
-
|
|
3
|
-
import { ApiHelpers } from '.'
|
|
4
|
-
|
|
5
|
-
export class ApiHelpersEntities<T extends CuboApiEntitiesMap<T>> {
|
|
6
|
-
constructor(private helpers: ApiHelpers<T>) {}
|
|
7
|
-
|
|
8
|
-
public async getMany(force = false): Promise<CuboEntity[]> {
|
|
9
|
-
return (
|
|
10
|
-
this.helpers?.api?.options?.entities ??
|
|
11
|
-
this.helpers.getCached(
|
|
12
|
-
`get_entities`,
|
|
13
|
-
() => this.helpers.api.apiRequest<{ items: CuboEntity[] }>(`/api/v1/entities?with=fields`).then((res) => res.items),
|
|
14
|
-
force
|
|
15
|
-
)
|
|
16
|
-
)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
public async getByAlias(alias: keyof T): Promise<CuboEntity | undefined> {
|
|
20
|
-
const entities = await this.getMany()
|
|
21
|
-
|
|
22
|
-
return entities.find((e) => e.alias === alias)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
public async getById(id: number): Promise<CuboEntity | undefined> {
|
|
26
|
-
const entities = await this.getMany()
|
|
27
|
-
|
|
28
|
-
return entities.find((e) => e.id === id)
|
|
29
|
-
}
|
|
30
|
-
}
|
package/src/helpers/query.ts
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { CuboApiEntitiesMap, CuboEntity } from '@cuboapp/types'
|
|
2
|
-
|
|
3
|
-
import { CUBO_CRUD_DEFAULT_FIELDS, CUBO_CRUD_DEFAULT_PAGE_LIMIT, CUBO_CRUD_MAX_PAGE_LIMIT, CUBO_CRUD_QUERY_KEY_REGEX } from '../constants'
|
|
4
|
-
import { CuboCrudFindQuery, CuboCrudRequest } from '../types'
|
|
5
|
-
import { dbPrepareUpdateQueryString } from '../utils'
|
|
6
|
-
|
|
7
|
-
import { ApiHelpers } from '.'
|
|
8
|
-
|
|
9
|
-
export class ApiHelpersQuery<T extends CuboApiEntitiesMap<T>> {
|
|
10
|
-
constructor(private helpers: ApiHelpers<T>) {}
|
|
11
|
-
|
|
12
|
-
public async createFindQuery(
|
|
13
|
-
req: CuboCrudRequest,
|
|
14
|
-
entity: CuboEntity,
|
|
15
|
-
queryDto?: Partial<CuboCrudFindQuery>,
|
|
16
|
-
isCount?: boolean
|
|
17
|
-
): Promise<Partial<CuboCrudFindQuery>> {
|
|
18
|
-
const withes = queryDto?.withes !== undefined ? queryDto.withes : []
|
|
19
|
-
const selects = queryDto?.selects !== undefined ? queryDto.selects : []
|
|
20
|
-
const joins = queryDto?.joins !== undefined ? queryDto.joins : []
|
|
21
|
-
const conditions = queryDto?.conditions !== undefined ? queryDto.conditions : []
|
|
22
|
-
const havings = queryDto?.havings !== undefined ? queryDto.havings : []
|
|
23
|
-
const sorts = queryDto?.sorts !== undefined ? queryDto.sorts : []
|
|
24
|
-
const replacements = queryDto?.replacements !== undefined ? queryDto?.replacements : {}
|
|
25
|
-
const groupBy = queryDto?.groupBy !== undefined ? queryDto?.groupBy : undefined
|
|
26
|
-
|
|
27
|
-
const { sort, limit: _limit, page, with: _withes, ...query } = req.query || {}
|
|
28
|
-
|
|
29
|
-
// plain conditions (nested are in withes.ts)
|
|
30
|
-
for (const key in query || {}) {
|
|
31
|
-
const parts = key.replace(CUBO_CRUD_QUERY_KEY_REGEX, '')?.split('.')
|
|
32
|
-
|
|
33
|
-
if (parts.length === 1) {
|
|
34
|
-
const conditionField = entity.fields?.find((f) => f.alias === parts[0])
|
|
35
|
-
const defaultField = CUBO_CRUD_DEFAULT_FIELDS.find((f) => f.alias === parts[0])
|
|
36
|
-
|
|
37
|
-
if (!conditionField && !defaultField) {
|
|
38
|
-
throw new Error('condition field "' + parts[0] + '" not found in entity "' + entity.alias + '"')
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const fieldAlias = conditionField ? conditionField.alias : defaultField!.alias
|
|
42
|
-
const preparedCondition = this.helpers.clearFieldConditionValue(conditionField ? conditionField : defaultField!, query[key])
|
|
43
|
-
const prepared = this.helpers.prepareConditionSql(
|
|
44
|
-
`t1.${fieldAlias}`,
|
|
45
|
-
`c_1_${fieldAlias}_value`,
|
|
46
|
-
preparedCondition.value,
|
|
47
|
-
preparedCondition.condition,
|
|
48
|
-
preparedCondition.is_not
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
conditions.push(...(prepared.conditions || []))
|
|
52
|
-
Object.assign(replacements, prepared.replacements || {})
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// sort
|
|
57
|
-
if (!sorts.length) {
|
|
58
|
-
if (sort !== undefined && typeof sort === 'string') {
|
|
59
|
-
const sortParts = sort.split(',')
|
|
60
|
-
|
|
61
|
-
for (const sortPart of sortParts) {
|
|
62
|
-
const parts = sortPart.replace(CUBO_CRUD_QUERY_KEY_REGEX, '')?.split('.')
|
|
63
|
-
|
|
64
|
-
if (parts.length === 1) {
|
|
65
|
-
const sortDirection = sortPart[0] === '-' ? 'desc' : 'asc'
|
|
66
|
-
const sortKey = sortPart[0] === '-' ? sortPart.slice(1) : sortPart
|
|
67
|
-
|
|
68
|
-
const conditionField = entity.fields?.find((f) => f.alias === sortKey)
|
|
69
|
-
const defaultField = CUBO_CRUD_DEFAULT_FIELDS.find((f) => f.alias === sortKey)
|
|
70
|
-
|
|
71
|
-
if (!conditionField && !defaultField) {
|
|
72
|
-
throw new Error('sortPart field "' + sortKey + '" not found')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
sorts.push('t1.' + sortKey + ' ' + sortDirection)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
} else {
|
|
79
|
-
sorts.push('t1.id desc')
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// limit & offset
|
|
84
|
-
let perPage = _limit !== undefined && !isNaN(+_limit) ? _limit : CUBO_CRUD_DEFAULT_PAGE_LIMIT
|
|
85
|
-
let pageNumber = page !== undefined && !isNaN(+page) ? +page : 1
|
|
86
|
-
if (pageNumber < 1) {
|
|
87
|
-
pageNumber = 1
|
|
88
|
-
}
|
|
89
|
-
if (perPage > CUBO_CRUD_MAX_PAGE_LIMIT) {
|
|
90
|
-
perPage = CUBO_CRUD_MAX_PAGE_LIMIT
|
|
91
|
-
}
|
|
92
|
-
if (perPage < 0) {
|
|
93
|
-
perPage = 0
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const filtersLimit = perPage
|
|
97
|
-
const filtersOffset = (pageNumber - 1) * perPage
|
|
98
|
-
|
|
99
|
-
let offsetString = ''
|
|
100
|
-
let limitString = ''
|
|
101
|
-
let groupByString = queryDto?.groupBy ?? ''
|
|
102
|
-
|
|
103
|
-
if (!isCount && perPage > 0) {
|
|
104
|
-
if (filtersOffset > 0) {
|
|
105
|
-
offsetString = `offset ${filtersOffset}`
|
|
106
|
-
}
|
|
107
|
-
limitString = `limit ${filtersLimit}`
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
selects.push(...CUBO_CRUD_DEFAULT_FIELDS.map((f) => `t1.${f.alias}`))
|
|
111
|
-
selects.push(...entity.fields.map((f) => `t1.${f.alias}`))
|
|
112
|
-
|
|
113
|
-
// remove deleted rows
|
|
114
|
-
if (!queryDto?.withDeleted) {
|
|
115
|
-
conditions.push('t1.deleted_at is null')
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const sql = `
|
|
119
|
-
select
|
|
120
|
-
${isCount ? 'count(t1.id) total' : selects.join(',\n')}
|
|
121
|
-
from
|
|
122
|
-
${entity.alias} t1
|
|
123
|
-
${joins.join('\n')}
|
|
124
|
-
${conditions.length > 0 ? `where (${conditions.join(') and (')})` : ''}
|
|
125
|
-
${groupByString}
|
|
126
|
-
${havings.length > 0 ? `having (${havings.join(') and (')})` : ''}
|
|
127
|
-
${sorts.length > 0 && !isCount ? 'order by ' + sorts.join(', ') : ''}
|
|
128
|
-
${limitString}
|
|
129
|
-
${offsetString}
|
|
130
|
-
`
|
|
131
|
-
|
|
132
|
-
// console.log('conditions', { conditions })
|
|
133
|
-
// console.log('sql', sql)
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
withes,
|
|
137
|
-
selects,
|
|
138
|
-
joins,
|
|
139
|
-
conditions,
|
|
140
|
-
groupBy,
|
|
141
|
-
havings,
|
|
142
|
-
sorts,
|
|
143
|
-
replacements,
|
|
144
|
-
sql
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
public async createUpdateQuery(req: CuboCrudRequest, entity: CuboEntity, queryDto?: Partial<CuboCrudFindQuery>) {
|
|
149
|
-
const dto = await this.helpers.data.prepare('update', entity.fields, req.body)
|
|
150
|
-
|
|
151
|
-
if (!Object.keys(dto)) {
|
|
152
|
-
throw { status: 400, text: 'No keys to update' }
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return dbPrepareUpdateQueryString(dto)
|
|
156
|
-
}
|
|
157
|
-
}
|
package/src/helpers/users.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { CuboApiEntitiesMap, CuboUser } from '@cuboapp/types'
|
|
2
|
-
import keyBy from 'lodash-es/keyBy'
|
|
3
|
-
|
|
4
|
-
import { ApiHelpers } from '.'
|
|
5
|
-
|
|
6
|
-
export class ApiHelpersUsers<T extends CuboApiEntitiesMap<T>> {
|
|
7
|
-
constructor(private helpers: ApiHelpers<T>) {}
|
|
8
|
-
|
|
9
|
-
public async getMany(force = false): Promise<CuboUser[]> {
|
|
10
|
-
const users = await this.helpers.getCached(
|
|
11
|
-
`users`,
|
|
12
|
-
async () => {
|
|
13
|
-
return this.helpers.api
|
|
14
|
-
.apiRequest<{ items: CuboUser[] }>(`/api/v1/users?account_id=${this.helpers.api.account.id}`)
|
|
15
|
-
.then((res) => res.items)
|
|
16
|
-
},
|
|
17
|
-
force
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
return users
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
public async getManyById(force = false) {
|
|
24
|
-
const users = await this.getMany(force)
|
|
25
|
-
return this.helpers.getCached(`users_by_id`, async () => keyBy(users, (i) => +i.id), force)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
public async getByEmail(email: string): Promise<CuboUser | undefined> {
|
|
29
|
-
const entities = await this.getMany()
|
|
30
|
-
return entities.find((e) => e.email === email)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
public async getById(id: number): Promise<CuboUser | undefined> {
|
|
34
|
-
const entities = await this.getManyById()
|
|
35
|
-
return entities[id]
|
|
36
|
-
}
|
|
37
|
-
}
|
package/src/utils/index.ts
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { QueryTypes, Sequelize, Transaction } from 'sequelize'
|
|
2
|
-
|
|
3
|
-
export function dbPrepareUpdateQueryString(obj: Record<string, any>) {
|
|
4
|
-
const keys: string[] = []
|
|
5
|
-
const replacements: Record<string, any> = {}
|
|
6
|
-
|
|
7
|
-
for (const key in obj) {
|
|
8
|
-
const value = obj[key]
|
|
9
|
-
|
|
10
|
-
keys.push(`${key} = :${key}`)
|
|
11
|
-
|
|
12
|
-
let val = null
|
|
13
|
-
|
|
14
|
-
if (typeof value === 'string') {
|
|
15
|
-
val = !!value ? value : null
|
|
16
|
-
} else if (typeof value === 'number') {
|
|
17
|
-
val = !isNaN(value) ? value : null
|
|
18
|
-
} else if (typeof value === 'boolean') {
|
|
19
|
-
val = value
|
|
20
|
-
} else if (Array.isArray(value) || typeof value === 'object') {
|
|
21
|
-
val = value ? JSON.stringify(value) : null
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
replacements[key] = val
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return { query: keys.join(', '), replacements }
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function dbPrepareInsertQueryString(obj: Record<string, any>) {
|
|
31
|
-
const values: string[] = []
|
|
32
|
-
const replacements: Record<string, any> = {}
|
|
33
|
-
|
|
34
|
-
for (const key in obj) {
|
|
35
|
-
const value = obj[key]
|
|
36
|
-
|
|
37
|
-
values.push(`:${key}`)
|
|
38
|
-
|
|
39
|
-
let val = null
|
|
40
|
-
|
|
41
|
-
if (typeof value === 'string') {
|
|
42
|
-
val = !!value ? value : null
|
|
43
|
-
} else if (typeof value === 'number') {
|
|
44
|
-
val = !isNaN(value) ? value : null
|
|
45
|
-
} else if (typeof value === 'boolean') {
|
|
46
|
-
val = value
|
|
47
|
-
} else if (Array.isArray(value) || typeof value === 'object') {
|
|
48
|
-
val = value ? JSON.stringify(value) : null
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
replacements[key] = val
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return { keys: Object.keys(obj).join(', '), values: values.join(','), replacements }
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export async function dbCreate(
|
|
58
|
-
db: Sequelize,
|
|
59
|
-
table: string,
|
|
60
|
-
arInsert: any,
|
|
61
|
-
opts?: { log?: boolean; transaction?: Transaction }
|
|
62
|
-
): Promise<{ id: number }> {
|
|
63
|
-
if (Object.keys(arInsert).length > 0) {
|
|
64
|
-
const dialect = db.getDialect()
|
|
65
|
-
|
|
66
|
-
const { keys, values, replacements } = dbPrepareInsertQueryString(arInsert)
|
|
67
|
-
|
|
68
|
-
let sql = ''
|
|
69
|
-
|
|
70
|
-
switch (dialect) {
|
|
71
|
-
case 'postgres':
|
|
72
|
-
sql = `insert into ${table} (${keys}) values (${values}) returning id`
|
|
73
|
-
break
|
|
74
|
-
case 'mysql':
|
|
75
|
-
sql = `insert into ${table} (${keys}) values (${values})`
|
|
76
|
-
break
|
|
77
|
-
default:
|
|
78
|
-
throw ''
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (opts?.log) {
|
|
82
|
-
console.warn(`QUERY: ${sql} [${JSON.stringify(replacements)}]`)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return db
|
|
86
|
-
.query(sql, {
|
|
87
|
-
type: QueryTypes.INSERT,
|
|
88
|
-
replacements,
|
|
89
|
-
transaction: opts?.transaction
|
|
90
|
-
})
|
|
91
|
-
.then((res: any) => {
|
|
92
|
-
switch (dialect) {
|
|
93
|
-
case 'postgres':
|
|
94
|
-
return { id: +res?.[0]?.[0].id }
|
|
95
|
-
case 'mysql':
|
|
96
|
-
return { id: +res[0] }
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
throw new Error('QUERY: db create array empty')
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export async function dbUpdate(
|
|
105
|
-
db: Sequelize,
|
|
106
|
-
table: string,
|
|
107
|
-
where: string,
|
|
108
|
-
replacements: Record<string, any>,
|
|
109
|
-
arUpdate: any,
|
|
110
|
-
opts?: { log?: boolean; transaction?: Transaction }
|
|
111
|
-
) {
|
|
112
|
-
if (Object.keys(arUpdate).length > 0) {
|
|
113
|
-
const prepared = dbPrepareUpdateQueryString(arUpdate)
|
|
114
|
-
|
|
115
|
-
if (opts?.log) {
|
|
116
|
-
console.warn(`QUERY: update ${table} set ${prepared.query} where ${where} [${JSON.stringify(replacements)}]`)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
await db.query(`update ${table} set ${prepared.query} where ${where}`, {
|
|
120
|
-
type: QueryTypes.UPDATE,
|
|
121
|
-
replacements: {
|
|
122
|
-
...(replacements || {}),
|
|
123
|
-
...(prepared.replacements || {})
|
|
124
|
-
},
|
|
125
|
-
transaction: opts?.transaction
|
|
126
|
-
})
|
|
127
|
-
}
|
|
128
|
-
}
|