@things-factory/shell 5.0.0-alpha.5 → 5.0.0-alpha.52

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.
Files changed (33) hide show
  1. package/client/themes/tooltip-theme.css +4 -3
  2. package/config/config.production.js +13 -0
  3. package/dist-server/middlewares/index.js +2 -2
  4. package/dist-server/middlewares/index.js.map +1 -1
  5. package/dist-server/pubsub.js +2 -2
  6. package/dist-server/pubsub.js.map +1 -1
  7. package/dist-server/server-dev.js +18 -16
  8. package/dist-server/server-dev.js.map +1 -1
  9. package/dist-server/server.js +15 -14
  10. package/dist-server/server.js.map +1 -1
  11. package/dist-server/service/domain/domain-resolver.js +4 -1
  12. package/dist-server/service/domain/domain-resolver.js.map +1 -1
  13. package/dist-server/service/domain/domain.js +34 -1
  14. package/dist-server/service/domain/domain.js.map +1 -1
  15. package/dist-server/utils/condition-builder.js +10 -11
  16. package/dist-server/utils/condition-builder.js.map +1 -1
  17. package/dist-server/utils/list-params-converter.js +29 -17
  18. package/dist-server/utils/list-params-converter.js.map +1 -1
  19. package/dist-server/utils/list-query-builder.js +92 -3
  20. package/dist-server/utils/list-query-builder.js.map +1 -1
  21. package/dist-server/utils/where-clause-builder.js +159 -0
  22. package/dist-server/utils/where-clause-builder.js.map +1 -0
  23. package/package.json +26 -25
  24. package/server/middlewares/index.ts +3 -2
  25. package/server/pubsub.ts +3 -3
  26. package/server/server-dev.ts +20 -17
  27. package/server/server.ts +17 -15
  28. package/server/service/domain/domain-resolver.ts +5 -1
  29. package/server/service/domain/domain.ts +37 -2
  30. package/server/utils/condition-builder.ts +12 -12
  31. package/server/utils/list-params-converter.ts +61 -25
  32. package/server/utils/list-query-builder.ts +123 -3
  33. package/server/utils/where-clause-builder.ts +200 -0
@@ -5,12 +5,13 @@ export const buildCondition = function (
5
5
  fieldName: string,
6
6
  operator: string,
7
7
  value: any,
8
- relation: boolean,
8
+ relation: boolean | string,
9
9
  seq: number
10
10
  ) {
11
11
  seq++
12
12
 
13
13
  fieldName = _.snakeCase(fieldName)
14
+ const values = value instanceof Array ? value : [value]
14
15
 
15
16
  switch (operator) {
16
17
  case 'eq':
@@ -75,24 +76,23 @@ export const buildCondition = function (
75
76
  }
76
77
 
77
78
  case 'in':
78
- const clause = relation ? `${fieldName}.id IN (:...args${seq})` : `${alias}.${fieldName} IN (:...args${seq})`
79
- value = value?.length ? value : [value]
80
79
  return {
81
- clause,
82
- parameters: { [`args${seq}`]: value }
80
+ clause: relation ? `${fieldName}.id IN (:...args${seq})` : `${alias}.${fieldName} IN (:...args${seq})`,
81
+ parameters: { [`args${seq}`]: values }
83
82
  }
83
+
84
84
  case 'notin':
85
- value = value?.length ? value : [value]
86
85
  return {
87
- clause: `${alias}.${fieldName} NOT IN (:...args${seq})`,
88
- parameters: { [`args${seq}`]: value }
86
+ clause: relation ? `${fieldName}.id NOT IN (:...args${seq})` : `${alias}.${fieldName} NOT IN (:...args${seq})`,
87
+ parameters: { [`args${seq}`]: values }
89
88
  }
90
89
 
91
90
  case 'notin_with_null':
92
- value = value?.length ? value : [value]
93
91
  return {
94
- clause: `(${alias}.${fieldName} IS NULL OR ${alias}.${fieldName} NOT IN (:...args${seq}))`,
95
- parameters: { [`args${seq}`]: value }
92
+ clause: relation
93
+ ? `(${fieldName}.id IS NULL OR ${fieldName}.id NOT IN (:...args${seq})`
94
+ : `${alias}.${fieldName} IS NULL OR ${alias}.${fieldName} NOT IN (:...args${seq}))`,
95
+ parameters: { [`args${seq}`]: values }
96
96
  }
97
97
 
98
98
  case 'is_null':
@@ -135,7 +135,7 @@ export const buildCondition = function (
135
135
  case 'between':
136
136
  return {
137
137
  clause: `${alias}.${fieldName} BETWEEN :args${seq}_1 AND :args${seq}_2`,
138
- parameters: { [`args${seq}_1`]: value[0], [`args${seq}_2`]: value[1] }
138
+ parameters: { [`args${seq}_1`]: values[0], [`args${seq}_2`]: values[1] }
139
139
  }
140
140
  }
141
141
  }
@@ -1,8 +1,9 @@
1
- import { Between, Equal, ILike, In, IsNull, Like, Not, Raw } from 'typeorm'
1
+ import { Between, Equal, FindOperator, ILike, In, IsNull, Like, Not, Raw } from 'typeorm'
2
2
 
3
- import { ListParam } from '../service/common-types'
3
+ import { Filter, ListParam, Pagination, Sorting } from '../service/common-types'
4
+ import { Domain } from '../service/domain/domain'
4
5
 
5
- const OPERATION_FUNCTION_MAP = {
6
+ const OPERATION_FUNCTION_MAP: { [operator: string]: (value: any) => FindOperator<any> } = {
6
7
  search: value => ILike(value),
7
8
  eq: value => Equal(value),
8
9
  noteq: value => Not(Equal(value)),
@@ -27,12 +28,12 @@ const OPERATION_FUNCTION_MAP = {
27
28
  between: value => Between(value[0], value[1])
28
29
  }
29
30
 
30
- function getOperatorFunction({ operator, name, value, dataType }) {
31
+ function getOperatorFunction({ operator, value }: { operator: string; value: any }): FindOperator<any> {
31
32
  return OPERATION_FUNCTION_MAP[operator](value)
32
33
  }
33
34
 
34
- function makePaginationParams(pagination) {
35
- var jsonParams = {}
35
+ function makePaginationParams(pagination: Pagination): { skip?: number; take?: number } {
36
+ var result = {} as { skip?: number; take?: number }
36
37
  if (pagination) {
37
38
  var { page = 0, limit = 0 } = pagination
38
39
  var skip = 0
@@ -41,41 +42,50 @@ function makePaginationParams(pagination) {
41
42
  if (limit > 0) {
42
43
  skip = Math.max(page - 1, 0) * limit
43
44
  take = limit
44
- Object.assign(jsonParams, {
45
+ Object.assign(result, {
45
46
  skip,
46
47
  take
47
48
  })
48
49
  }
49
50
  }
50
51
 
51
- return jsonParams
52
+ return result
52
53
  }
53
54
 
54
- function makeSortingParams(sortings) {
55
- var jsonParams = {}
55
+ function makeSortingParams(sortings: Sorting[]): { order?: { [name: string]: 'DESC' | 'ASC' } } {
56
+ var result = {} as { order?: { [name: string]: 'DESC' | 'ASC' } }
56
57
  if (sortings) {
57
- var order = {}
58
+ var order = {} as { [name: string]: 'DESC' | 'ASC' }
58
59
  sortings.forEach(s => {
59
60
  order[s.name] = s.desc ? 'DESC' : 'ASC'
60
61
  })
61
62
 
62
- Object.assign(jsonParams, {
63
+ Object.assign(result, {
63
64
  order
64
65
  })
65
66
  }
66
67
 
67
- return jsonParams
68
+ return result
68
69
  }
69
70
 
70
- function makeFilterParams(filters) {
71
+ function makeFilterParams(
72
+ filters: Filter[],
73
+ searchables?: string[]
74
+ ): {
75
+ where: { [name: string]: FindOperator<any> } | { [name: string]: FindOperator<any> }[]
76
+ } {
71
77
  /* for where AND clauses */
72
78
  const columnFilters = filters.filter(filter => filter.operator !== 'search')
73
79
  const columnWhere = columnFilters.reduce((where, f) => {
74
80
  where[f.name] = getOperatorFunction(f)
75
81
  return where
76
- }, {})
82
+ }, {} as { [name: string]: FindOperator<any> })
83
+
84
+ const searchFilters =
85
+ searchables instanceof Array
86
+ ? filters.filter(filter => filter.operator === 'search' && searchables.includes(filter.name))
87
+ : []
77
88
 
78
- const searchFilters = filters.filter(filter => filter.operator === 'search')
79
89
  if (searchFilters.length === 0) {
80
90
  return {
81
91
  where: columnWhere
@@ -95,21 +105,47 @@ function makeFilterParams(filters) {
95
105
  }
96
106
  }
97
107
 
98
- export function convertListParams(params: typeof ListParam, domain?: String) {
99
- var { pagination, filters = [], sortings } = params as any
100
- var jsonParams = {}
108
+ export function convertListParams(
109
+ params: ListParam,
110
+ options?:
111
+ | string
112
+ | {
113
+ domain?: string | Domain
114
+ searchables?: string[]
115
+ }
116
+ ): {
117
+ where?: { [name: string]: FindOperator<any> }
118
+ order?: {
119
+ [name: string]: 'DESC' | 'ASC'
120
+ }
121
+ skip?: number
122
+ take?: number
123
+ } {
124
+ var domainId: string | undefined
125
+
126
+ if (options) {
127
+ if (typeof options === 'string' /* for 하위 호환성 */) {
128
+ var domainId = options
129
+ } else {
130
+ var { domain, searchables } = options
131
+ var domainId = typeof domain === 'string' ? domain : domain?.id
132
+ }
133
+ }
134
+
135
+ var { pagination, filters = [], sortings } = params
136
+ var result = {}
101
137
 
102
- if (domain) {
138
+ if (domainId) {
103
139
  filters.push({
104
140
  name: 'domain',
105
141
  operator: 'eq',
106
- value: domain
142
+ value: domainId
107
143
  })
108
144
  }
109
145
 
110
- if (pagination) Object.assign(jsonParams, makePaginationParams(pagination))
111
- if (sortings) Object.assign(jsonParams, makeSortingParams(sortings))
112
- if (filters) Object.assign(jsonParams, makeFilterParams(filters))
146
+ if (pagination) Object.assign(result, makePaginationParams(pagination))
147
+ if (sortings) Object.assign(result, makeSortingParams(sortings))
148
+ if (filters) Object.assign(result, makeFilterParams(filters, searchables))
113
149
 
114
- return jsonParams
150
+ return result
115
151
  }
@@ -1,10 +1,37 @@
1
- import { Brackets } from 'typeorm'
1
+ import { Brackets, Repository, SelectQueryBuilder } from 'typeorm'
2
+ import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata'
2
3
 
4
+ import { Domain } from '../service/domain/domain'
3
5
  import { buildCondition } from './condition-builder'
6
+ import { buildWhereClause } from './where-clause-builder'
7
+
8
+ /**
9
+ * @deprecated The name of this function does not imply an exact purpose and it is not possible to correspond to a foreign key using the meta information of the table column, so it is recommended to replace it with the A function.
10
+ */
11
+ export const buildQuery = function (
12
+ queryBuilder: any,
13
+ params: any,
14
+ context: any,
15
+ options?:
16
+ | boolean
17
+ | {
18
+ domainRef?: boolean
19
+ searchables?: string[]
20
+ }
21
+ ) {
22
+ /* default value of domainRef is 'true' */
23
+ var domainRef = typeof options === 'boolean' ? options : true
24
+
25
+ /* for backwards compatibility of function spec */
26
+ if (typeof options === 'object') {
27
+ var { domainRef = true, searchables } = options
28
+ }
4
29
 
5
- export const buildQuery = function (queryBuilder: any, params: any, context: any, domainRef: Boolean = true) {
6
30
  const columnFilters = params.filters?.filter(filter => filter.operator !== 'search') || []
7
- const searchFilters = params.filters?.filter(filter => filter.operator === 'search') || []
31
+ const searchFilters =
32
+ searchables instanceof Array
33
+ ? params.filters?.filter(filter => filter.operator === 'search' && searchables.includes(filter.name)) || []
34
+ : []
8
35
  const pagination = params.pagination
9
36
  const sortings = params.sortings
10
37
  const domainId = context && context.state.domain && context.state.domain.id
@@ -58,3 +85,96 @@ export const buildQuery = function (queryBuilder: any, params: any, context: any
58
85
  })
59
86
  }
60
87
  }
88
+
89
+ export function getQueryBuilderFromListParams<Type>(options: {
90
+ repository: Repository<Type>
91
+ params: any
92
+ domain?: Domain
93
+ alias?: string
94
+ searchables?: string[]
95
+ }): SelectQueryBuilder<Type> {
96
+ var { repository, params, domain, alias, searchables } = options
97
+ const selectQueryBuilder = repository.createQueryBuilder(alias)
98
+ alias = selectQueryBuilder.alias
99
+
100
+ const columnFilters = params.filters?.filter(filter => filter.operator !== 'search') || []
101
+ const searchFilters =
102
+ searchables instanceof Array
103
+ ? params.filters?.filter(filter => filter.operator === 'search' && searchables.includes(filter.name)) || []
104
+ : []
105
+ const pagination = params.pagination
106
+ const sortings = params.sortings
107
+
108
+ const metadata = repository.metadata
109
+ const columnMetas = params.filters
110
+ .map(filter => filter.name)
111
+ .reduce((sum, name) => {
112
+ sum[name] = metadata.columns.find(column => column.propertyName === name)
113
+ return sum
114
+ }, {} as { [name: string]: ColumnMetadata })
115
+
116
+ if (columnFilters && columnFilters.length > 0) {
117
+ columnFilters.forEach(filter => {
118
+ const { name, operator, value } = filter
119
+
120
+ const condition = buildWhereClause({
121
+ alias,
122
+ columnMeta: columnMetas[name],
123
+ operator,
124
+ value,
125
+ seq: Object.keys(selectQueryBuilder.getParameters()).length + 1,
126
+ domain,
127
+ selectQueryBuilder
128
+ })
129
+
130
+ if (condition?.clause) selectQueryBuilder.andWhere(condition.clause)
131
+ if (condition?.parameters) selectQueryBuilder.setParameters(condition.parameters)
132
+ })
133
+ }
134
+
135
+ if (searchFilters.length > 0) {
136
+ selectQueryBuilder.andWhere(
137
+ new Brackets(qb => {
138
+ searchFilters.forEach(filter => {
139
+ const { name, operator, value } = filter
140
+
141
+ const condition = buildWhereClause({
142
+ alias,
143
+ columnMeta: columnMetas[name],
144
+ operator /* has to be 'search' */,
145
+ value,
146
+ seq: Object.keys(selectQueryBuilder.getParameters()).length + 1,
147
+ domain,
148
+ selectQueryBuilder
149
+ })
150
+
151
+ if (condition?.clause) qb.orWhere(condition.clause)
152
+ if (condition?.parameters) selectQueryBuilder.setParameters(condition.parameters)
153
+ })
154
+ })
155
+ )
156
+ }
157
+
158
+ if (domain) {
159
+ selectQueryBuilder.andWhere(`${selectQueryBuilder.alias}.domain = :domain`, { domain: domain.id })
160
+ }
161
+
162
+ if (pagination && pagination.page > 0 && pagination.limit > 0) {
163
+ selectQueryBuilder.skip(pagination.limit * (pagination.page - 1))
164
+ selectQueryBuilder.take(pagination.limit)
165
+ }
166
+
167
+ if (sortings && sortings.length > 0) {
168
+ sortings.forEach((sorting, index) => {
169
+ const sortField =
170
+ sorting.name.split('.').length > 1 ? sorting.name : `${selectQueryBuilder.alias}.${sorting.name}`
171
+ if (index === 0) {
172
+ selectQueryBuilder.orderBy(sortField, sorting.desc ? 'DESC' : 'ASC')
173
+ } else {
174
+ selectQueryBuilder.addOrderBy(sortField, sorting.desc ? 'DESC' : 'ASC')
175
+ }
176
+ })
177
+ }
178
+
179
+ return selectQueryBuilder
180
+ }
@@ -0,0 +1,200 @@
1
+ import { SelectQueryBuilder } from 'typeorm'
2
+ import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata'
3
+
4
+ import { Domain } from '../service/domain/domain'
5
+
6
+ function getClause(
7
+ selectQueryBuilder: SelectQueryBuilder<any>,
8
+ columnMeta: ColumnMetadata,
9
+ operator: string,
10
+ field: string,
11
+ pname: string
12
+ ): string {
13
+ const relation = columnMeta.relationMetadata
14
+
15
+ if (!relation) {
16
+ switch (operator) {
17
+ case 'like':
18
+ return `${field} LIKE :${pname}`
19
+
20
+ case 'search':
21
+ case 'i_like':
22
+ return `LOWER(${field}) LIKE :${pname}`
23
+
24
+ case 'nlike':
25
+ return `${field} NOT LIKE :${pname}`
26
+
27
+ case 'i_nlike':
28
+ return `LOWER(${field}) NOT LIKE :${pname}`
29
+ }
30
+ }
31
+
32
+ const inverseEntityMetadata = relation.inverseEntityMetadata
33
+ const { target, tableName, ownColumns } = inverseEntityMetadata
34
+ var subquery = selectQueryBuilder.subQuery().select('id').from(target, tableName)
35
+
36
+ switch (operator) {
37
+ case 'like':
38
+ subquery = subquery.where(`${tableName}.name LIKE :${pname}`)
39
+ break
40
+
41
+ case 'search':
42
+ case 'i_like':
43
+ subquery = subquery.where(`LOWER(${tableName}.name) LIKE :${pname}`)
44
+ break
45
+
46
+ case 'nlike':
47
+ subquery = subquery.where(`${tableName}.name NOT LIKE :${pname}`)
48
+ break
49
+
50
+ case 'i_nlike':
51
+ subquery = subquery.where(`LOWER(${tableName}.name) NOT LIKE :${pname}`)
52
+ break
53
+ }
54
+
55
+ if (ownColumns.find(column => column.propertyName === 'domain')) {
56
+ subquery.andWhere(`${tableName}.domain_id = :domain`)
57
+ }
58
+
59
+ return `${field} IN ${subquery.getQuery()}`
60
+ }
61
+
62
+ export const buildWhereClause = function (options: {
63
+ alias: string
64
+ columnMeta: ColumnMetadata
65
+ operator: string
66
+ value: any
67
+ seq: number
68
+ domain: Domain
69
+ selectQueryBuilder: SelectQueryBuilder<any>
70
+ }) {
71
+ const { alias, columnMeta, operator, value, seq, selectQueryBuilder, domain } = options
72
+ const values = value instanceof Array ? value : [value]
73
+ const { propertyName: name, propertyAliasName, propertyPath: path, databaseName } = columnMeta
74
+ const field = `${alias}.${databaseName}`
75
+ const pname = `args${seq}`
76
+
77
+ switch (operator) {
78
+ case 'eq':
79
+ return {
80
+ clause: `${field} = :${pname}`,
81
+ parameters: { [pname]: value }
82
+ }
83
+
84
+ case 'like':
85
+ return {
86
+ clause: getClause(selectQueryBuilder, columnMeta, operator, field, pname),
87
+ parameters: { [pname]: `%${value}%` }
88
+ }
89
+
90
+ case 'search':
91
+ case 'i_like':
92
+ return {
93
+ clause: getClause(selectQueryBuilder, columnMeta, operator, field, pname),
94
+ parameters: { [pname]: `%${String(value).toLowerCase()}%` }
95
+ }
96
+
97
+ case 'nlike':
98
+ return {
99
+ clause: getClause(selectQueryBuilder, columnMeta, operator, field, pname),
100
+ value: { [pname]: `%${value}%` }
101
+ }
102
+
103
+ case 'i_nlike':
104
+ return {
105
+ clause: getClause(selectQueryBuilder, columnMeta, operator, field, pname),
106
+ value: { [pname]: `%${String(value).toLowerCase()}%` }
107
+ }
108
+
109
+ case 'lt':
110
+ return {
111
+ clause: `${field} < :${pname}`,
112
+ parameters: { [pname]: value }
113
+ }
114
+
115
+ case 'gt':
116
+ return {
117
+ clause: `${field} > :${pname}`,
118
+ parameters: { [pname]: value }
119
+ }
120
+
121
+ case 'lte':
122
+ return {
123
+ clause: `${field} <= :${pname}`,
124
+ parameters: { [pname]: value }
125
+ }
126
+
127
+ case 'gte':
128
+ return {
129
+ clause: `${field} >= :${pname}`,
130
+ parameters: { [pname]: value }
131
+ }
132
+
133
+ case 'noteq':
134
+ return {
135
+ clause: `${field} != :${pname}`,
136
+ parameters: { [pname]: value }
137
+ }
138
+
139
+ case 'in':
140
+ return {
141
+ clause: `${field} IN (:...${pname})`,
142
+ parameters: { [pname]: values }
143
+ }
144
+
145
+ case 'notin':
146
+ return {
147
+ clause: `${field} NOT IN (:...${pname})`,
148
+ parameters: { [pname]: values }
149
+ }
150
+
151
+ case 'notin_with_null':
152
+ return {
153
+ clause: `${field} IS NULL OR ${field} NOT IN (:...${pname}))`,
154
+ parameters: { [pname]: values }
155
+ }
156
+
157
+ case 'is_null':
158
+ return {
159
+ clause: `${field} IS NULL`
160
+ }
161
+ case 'is_not_null':
162
+ return {
163
+ clause: `${field} IS NOT NULL`
164
+ }
165
+ case 'is_false':
166
+ return {
167
+ clause: `${field} IS FALSE`
168
+ }
169
+ case 'is_true':
170
+ return {
171
+ clause: `${field} IS TRUE`
172
+ }
173
+ case 'is_not_false':
174
+ return {
175
+ clause: `${field} IS NOT FALSE`
176
+ }
177
+ case 'is_not_true':
178
+ return {
179
+ clause: `${field} IS NOT TRUE`
180
+ }
181
+ case 'is_present':
182
+ return {
183
+ clause: `${field} IS PRESENT`
184
+ }
185
+ case 'is_blank':
186
+ return {
187
+ clause: `${field} IS BLANK`
188
+ }
189
+ case 'is_empty_num_id':
190
+ return {
191
+ clause: `${field} IS EMPTY NUMERIC ID`
192
+ }
193
+
194
+ case 'between':
195
+ return {
196
+ clause: `${field} BETWEEN :${pname}_1 AND :${pname}_2`,
197
+ parameters: { [`args${seq}_1`]: values[0], [`args${seq}_2`]: values[1] }
198
+ }
199
+ }
200
+ }