@cuboapp/api-backend 1.0.8 → 1.0.9

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.
@@ -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)
@@ -1,11 +0,0 @@
1
- export const CUBO_ENTITY_FIELD_TYPE = {
2
- TEXT: 1,
3
- NUMBER: 2,
4
- TEXTAREA: 3,
5
- BOOLEAN: 4,
6
- SELECT: 5,
7
- DATE: 6,
8
- TIMESTAMP: 7,
9
- FILE: 8,
10
- JSON: 9
11
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }
@@ -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
- }