@things-factory/shell 8.0.0-beta.9 → 8.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.
Files changed (67) hide show
  1. package/client/themes/calendar-theme.css +1 -3
  2. package/client/themes/index.css +1 -2
  3. package/dist-server/server-dev.js +4 -2
  4. package/dist-server/server-dev.js.map +1 -1
  5. package/dist-server/server.js +4 -2
  6. package/dist-server/server.js.map +1 -1
  7. package/dist-server/tsconfig.tsbuildinfo +1 -1
  8. package/dist-server/typeorm/json5-transform.js +2 -2
  9. package/dist-server/typeorm/json5-transform.js.map +1 -1
  10. package/package.json +14 -14
  11. package/server/graphql-local-client.ts +59 -0
  12. package/server/index.ts +13 -0
  13. package/server/initializers/database.ts +96 -0
  14. package/server/initializers/naming-strategy.ts +14 -0
  15. package/server/middlewares/domain-middleware.ts +60 -0
  16. package/server/middlewares/index.ts +43 -0
  17. package/server/migrations/1000000000000-SeedDomain.ts +37 -0
  18. package/server/migrations/index.ts +9 -0
  19. package/server/pubsub-log-transport.ts +59 -0
  20. package/server/pubsub.ts +84 -0
  21. package/server/routers/domain-router.ts +13 -0
  22. package/server/routers/global-router.ts +76 -0
  23. package/server/routers/graphql-router.ts +3 -0
  24. package/server/routers/index.ts +3 -0
  25. package/server/schema.ts +163 -0
  26. package/server/server-dev.ts +305 -0
  27. package/server/server.ts +296 -0
  28. package/server/service/attribute-set/attribute-set-item-type.ts +65 -0
  29. package/server/service/attribute-set/attribute-set-mutation.ts +125 -0
  30. package/server/service/attribute-set/attribute-set-query.ts +36 -0
  31. package/server/service/attribute-set/attribute-set-type.ts +46 -0
  32. package/server/service/attribute-set/attribute-set.ts +35 -0
  33. package/server/service/attribute-set/index.ts +6 -0
  34. package/server/service/common-types/index.ts +6 -0
  35. package/server/service/common-types/list-param.ts +61 -0
  36. package/server/service/common-types/log.ts +17 -0
  37. package/server/service/common-types/object-ref.ts +13 -0
  38. package/server/service/common-types/scalar-any.ts +44 -0
  39. package/server/service/common-types/scalar-date.ts +22 -0
  40. package/server/service/common-types/scalar-object.ts +15 -0
  41. package/server/service/directive-transaction/index.ts +1 -0
  42. package/server/service/directive-transaction/transaction.ts +40 -0
  43. package/server/service/domain/domain-mutation.ts +120 -0
  44. package/server/service/domain/domain-query.ts +48 -0
  45. package/server/service/domain/domain-types.ts +63 -0
  46. package/server/service/domain/domain.ts +147 -0
  47. package/server/service/domain/index.ts +6 -0
  48. package/server/service/index.ts +32 -0
  49. package/server/service/subscription-data/data-resolver.ts +37 -0
  50. package/server/service/subscription-data/data-types.ts +16 -0
  51. package/server/service/subscription-data/index.ts +4 -0
  52. package/server/typeorm/encrypt-transform.ts +70 -0
  53. package/server/typeorm/get-data-encryption-key.ts +13 -0
  54. package/server/typeorm/json5-transform.ts +26 -0
  55. package/server/typeorm/round-transform.ts +20 -0
  56. package/server/utils/condition-builder.ts +145 -0
  57. package/server/utils/get-domain.ts +226 -0
  58. package/server/utils/get-query-builder-from-list-params.ts +469 -0
  59. package/server/utils/get-times-for-period.ts +60 -0
  60. package/server/utils/index.ts +8 -0
  61. package/server/utils/list-param-adjuster.ts +21 -0
  62. package/server/utils/list-params-converter.ts +200 -0
  63. package/server/utils/list-query-builder.ts +120 -0
  64. package/server/utils/publish-progress.ts +23 -0
  65. package/dist-server/process-cleaner.d.ts +0 -1
  66. package/dist-server/process-cleaner.js +0 -92
  67. package/dist-server/process-cleaner.js.map +0 -1
@@ -0,0 +1,469 @@
1
+ import { Brackets, EntityMetadata, Repository, SelectQueryBuilder, WhereExpressionBuilder } from 'typeorm'
2
+ import { RelationMetadata } from 'typeorm/metadata/RelationMetadata'
3
+ import { Filter, Sorting, Pagination, ListParam, InheritedValueType } from '../service/common-types/list-param'
4
+ import { Domain } from '../service/domain/domain'
5
+
6
+ /**
7
+ * Creates a TypeORM SelectQueryBuilder based on the provided parameters.
8
+ *
9
+ * @param options - An object containing the query building options.
10
+ * @param options.repository - The TypeORM repository for database operations.
11
+ * @param options.params - The ListParam object containing filters, sortings, and pagination.
12
+ * @param [options.domain] - Optional domain object for applying domain-specific filters.
13
+ * @param [options.alias] - The alias to be used in the SQL queries.
14
+ * @param [options.searchables] - List of columns that are searchable.
15
+ * @param [options.filtersMap] - Mapping of filter names to their corresponding columns or relation columns.
16
+ * @returns {SelectQueryBuilder<Type>} - The constructed SelectQueryBuilder instance.
17
+ */
18
+ export function getQueryBuilderFromListParams<Type>(options: {
19
+ repository: Repository<Type>
20
+ params: ListParam
21
+ domain?: Domain
22
+ alias?: string
23
+ searchables?: string[]
24
+ filtersMap?: { [name: string]: { columnName: string; relationColumn?: string } }
25
+ }): SelectQueryBuilder<Type> {
26
+ const { repository, params, domain, alias, searchables, filtersMap = {} } = options
27
+ const { inherited = InheritedValueType.None } = params || {}
28
+
29
+ const selectQueryBuilder = repository.createQueryBuilder(alias)
30
+ const entityAlias = selectQueryBuilder.alias
31
+
32
+ // Apply filters to the query
33
+ const columnFilters =
34
+ params.filters?.filter(filter => {
35
+ if (filter.operator === 'search') {
36
+ return false
37
+ }
38
+ if (filter.operator.toLowerCase().includes('like') && (!searchables || !searchables.includes(filter.name))) {
39
+ console.warn('"searchables" setting is required for LIKE searches to avoid heavy database load', filter.name)
40
+ return false
41
+ }
42
+ return true
43
+ }) || []
44
+
45
+ const searchFilters =
46
+ searchables instanceof Array
47
+ ? params.filters?.filter(filter => {
48
+ if (filter.operator !== 'search') {
49
+ return false
50
+ }
51
+ if (!searchables.includes(filter.name)) {
52
+ console.warn(
53
+ '"searchables" setting is required for LIKE searches to avoid heavy database load',
54
+ filter.name
55
+ )
56
+ return false
57
+ }
58
+ return true
59
+ }) || []
60
+ : []
61
+
62
+ const pagination = params.pagination
63
+ const sortings = params.sortings
64
+ const metadata = repository.metadata
65
+
66
+ // Apply column filters
67
+ if (columnFilters.length > 0) {
68
+ columnFilters.forEach(filter => {
69
+ addCondition(metadata, selectQueryBuilder, selectQueryBuilder, filter, filtersMap, true)
70
+ })
71
+ }
72
+
73
+ // Apply search filters
74
+ if (searchFilters.length > 0) {
75
+ selectQueryBuilder.andWhere(
76
+ new Brackets(qb => {
77
+ searchFilters.forEach(filter => {
78
+ addCondition(metadata, selectQueryBuilder, qb, filter, filtersMap, false)
79
+ })
80
+ })
81
+ )
82
+ }
83
+
84
+ // Apply domain filters
85
+ if (domain) {
86
+ if (!inherited || inherited === InheritedValueType.None) {
87
+ selectQueryBuilder.andWhere(`${entityAlias}.domain = :domain`, { domain: domain.id })
88
+ } else if (inherited === InheritedValueType.Include) {
89
+ selectQueryBuilder.andWhere(`${entityAlias}.domain IN (:...domains)`, {
90
+ domains: [domain.id, domain.parentId].filter(Boolean)
91
+ })
92
+ } else if (inherited === InheritedValueType.Only) {
93
+ selectQueryBuilder.andWhere(`${entityAlias}.domain = :domain`, { domain: domain.parentId || 'Impossible' })
94
+ } else {
95
+ selectQueryBuilder.andWhere(`${entityAlias}.domain = :domain`, { domain: 'Impossible' })
96
+ }
97
+ }
98
+
99
+ // Apply pagination
100
+ addPagination(selectQueryBuilder, pagination)
101
+
102
+ // Apply sorting
103
+ if (sortings && sortings.length > 0) {
104
+ addSorting(selectQueryBuilder, sortings, entityAlias, filtersMap, metadata)
105
+ }
106
+
107
+ return selectQueryBuilder
108
+ }
109
+
110
+ /**
111
+ * Adds pagination to the SelectQueryBuilder based on the provided Pagination object.
112
+ *
113
+ * @param selectQueryBuilder - The SelectQueryBuilder to which pagination should be applied.
114
+ * @param pagination - The Pagination object containing page and limit information.
115
+ */
116
+ function addPagination<T>(selectQueryBuilder: SelectQueryBuilder<T>, pagination?: Pagination) {
117
+ if (pagination) {
118
+ const { page, limit } = pagination
119
+ if (page && limit && page > 0 && limit > 0) {
120
+ selectQueryBuilder.skip(limit * (page - 1)).take(limit)
121
+ } else if (limit && limit > 0) {
122
+ selectQueryBuilder.take(limit)
123
+ }
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Adds a filtering condition to the SelectQueryBuilder based on the provided filter and mapping options.
129
+ *
130
+ * @param metadata - The EntityMetadata of the TypeORM entity.
131
+ * @param selectQueryBuilder - The SelectQueryBuilder to which the condition will be added.
132
+ * @param whereExpressionBuilder - The WhereExpressionBuilder to construct the where clause.
133
+ * @param filter - The Filter object containing the filter criteria.
134
+ * @param filtersMap - A mapping of filter names to column names and relation column names.
135
+ * @param andCondition - A flag indicating whether to use "AND" or "OR" for combining conditions.
136
+ */
137
+ function addCondition<T>(
138
+ metadata: EntityMetadata,
139
+ selectQueryBuilder: SelectQueryBuilder<T>,
140
+ whereExpressionBuilder: WhereExpressionBuilder,
141
+ filter: Filter,
142
+ filtersMap: { [name: string]: { columnName: string; relationColumn?: string } } = {},
143
+ andCondition: boolean
144
+ ): void {
145
+ const { name, operator, value } = filter
146
+ var entityAlias = selectQueryBuilder.alias
147
+
148
+ var { relationColumn, columnName } = filtersMap[name] || {}
149
+ /*
150
+ 1. relationColumn과 columnName이 지정된 경우
151
+ - relation inverse 테이블에서, columnName을 찾는다.
152
+ 2. relationColumn만 지정된 경우는 없어야 한다.
153
+ - 이 경우 columnName 은 'name' 이라고 판단한다.
154
+ 3. columnName이 지정된 경우.
155
+ - 이 경우는 columnName 만 적용한다.
156
+ */
157
+ if (relationColumn) {
158
+ const columns = relationColumn.split('.')
159
+ var entityMetadata: EntityMetadata
160
+ var relation: RelationMetadata
161
+
162
+ for (const rcolumn of columns) {
163
+ if (relation) {
164
+ const { propertyName } = relationColumnMeta
165
+ const property = `${entityAlias}.${propertyName}`
166
+
167
+ entityAlias = `${entityAlias}-${entityMetadata.tableName}-for-${columnName || 'name'}` as string
168
+
169
+ if (andCondition) {
170
+ selectQueryBuilder.innerJoin(property, entityAlias)
171
+ } else {
172
+ selectQueryBuilder.leftJoin(property, entityAlias)
173
+ }
174
+ } else {
175
+ entityMetadata = metadata
176
+ }
177
+
178
+ var relationColumnMeta = entityMetadata.columns.find(column => column.propertyName === rcolumn)
179
+ if (!relationColumnMeta) {
180
+ console.warn(`relationColumn "${relationColumn}" in filtersMap for "${name}" is not a relation column`)
181
+ return
182
+ }
183
+
184
+ relation = relationColumnMeta.relationMetadata
185
+ entityMetadata = relation.inverseEntityMetadata
186
+ }
187
+
188
+ var columnMeta = entityMetadata.columns.find(column => column.propertyName === (columnName || 'name'))
189
+ if (!columnMeta) {
190
+ console.warn(`columnName "${columnName}" in filtersMap for "${name}" is not a column`)
191
+ return
192
+ }
193
+ } else {
194
+ var columnMeta = metadata.columns.find(column => column.propertyName === (columnName || name))
195
+ if (!columnMeta) {
196
+ /* relationId 에 대한 필터링은 해당 컬럼값 자체의 비교로 한다. */
197
+ var relationIdMeta = metadata.relationIds.find(relationId => relationId.propertyName === (columnName || name))
198
+ if (relationIdMeta) {
199
+ columnMeta = relationIdMeta.relation.joinColumns[0]
200
+ } else {
201
+ columnName
202
+ ? console.warn(`columnName "${columnName}" in filtersMap for "${name}" is not a column`)
203
+ : console.warn(`name "${name}" is not a column`)
204
+ }
205
+ } else {
206
+ var relation = columnMeta.relationMetadata
207
+ }
208
+
209
+ if (relation) {
210
+ /* filterMap에 의해서 relationColumn 이 지정되지 않았더라도, name 또는 columnName의 column이 relation인 경우에는
211
+ - 조건절 구성을 위한 타겟필드명은 'name' 으로만 한정된다.
212
+ */
213
+ var relationColumnMeta = columnMeta
214
+ var entityMetadata = relation.inverseEntityMetadata
215
+ columnMeta = entityMetadata.columns.find(column => column.propertyName === 'name')
216
+ if (!columnMeta) {
217
+ console.warn(`relation column "${columnName || name}" does not have "name" column`)
218
+ return
219
+ }
220
+ }
221
+ }
222
+
223
+ const dbNameForColumn = columnMeta.databaseName
224
+ const alias = relationColumnMeta ? `${name}-filter` : entityAlias
225
+
226
+ /* relation columne인 경우 name을 alias로 사용한다. */
227
+ const field = `${alias}.${dbNameForColumn}`
228
+
229
+ var { clause, parameters } = getClauseAndParameters(field, name, operator, value)
230
+
231
+ if (relationColumnMeta) {
232
+ const { propertyName } = relationColumnMeta
233
+ const property = `${entityAlias}.${propertyName}`
234
+ if (andCondition) {
235
+ selectQueryBuilder.innerJoin(property, alias, clause, parameters)
236
+ } else {
237
+ selectQueryBuilder.leftJoin(property, alias)
238
+ whereExpressionBuilder.orWhere(clause, parameters)
239
+ }
240
+ } else {
241
+ andCondition
242
+ ? whereExpressionBuilder.andWhere(clause, parameters)
243
+ : whereExpressionBuilder.orWhere(clause, parameters)
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Adds sorting to the SelectQueryBuilder based on the provided Sorting objects.
249
+ *
250
+ * @param selectQueryBuilder - The SelectQueryBuilder to which sorting should be applied.
251
+ * @param sortings - An array of Sorting objects defining the sort order.
252
+ * @param entityAlias - The alias of the entity in the query.
253
+ * @param filtersMap - A mapping of filter names to column names and relation column names.
254
+ * @param metadata - The EntityMetadata of the TypeORM entity.
255
+ */
256
+ function addSorting<T>(
257
+ selectQueryBuilder: SelectQueryBuilder<T>,
258
+ sortings: Sorting[],
259
+ entityAlias: string,
260
+ filtersMap: { [name: string]: { columnName: string; relationColumn?: string } },
261
+ metadata: EntityMetadata
262
+ ) {
263
+ sortings.forEach((sorting, index) => {
264
+ const sortField = determineSortField(sorting.name, entityAlias, filtersMap, selectQueryBuilder, metadata)
265
+ if (index === 0) {
266
+ selectQueryBuilder.orderBy(sortField, sorting.desc ? 'DESC' : 'ASC')
267
+ } else {
268
+ selectQueryBuilder.addOrderBy(sortField, sorting.desc ? 'DESC' : 'ASC')
269
+ }
270
+ })
271
+ }
272
+
273
+ /**
274
+ * Determines the sorting field for a given sorting name, considering possible relation columns.
275
+ *
276
+ * @param sortingName - The name of the field to sort by.
277
+ * @param entityAlias - The alias of the entity in the query.
278
+ * @param filtersMap - A mapping of filter names to column names and relation column names.
279
+ * @param selectQueryBuilder - The SelectQueryBuilder instance to apply sorting to.
280
+ * @param metadata - The EntityMetadata of the TypeORM entity.
281
+ * @returns {string} - The fully qualified sorting field.
282
+ */
283
+ function determineSortField<T>(
284
+ sortingName: string,
285
+ entityAlias: string,
286
+ filtersMap: { [name: string]: { columnName: string; relationColumn?: string } },
287
+ selectQueryBuilder: SelectQueryBuilder<T>,
288
+ metadata: EntityMetadata
289
+ ): string {
290
+ const filter = filtersMap[sortingName]
291
+
292
+ if (!filter) {
293
+ return sortingName.split('.').length > 1 ? sortingName : `${entityAlias}.${sortingName}`
294
+ }
295
+
296
+ const { columnName, relationColumn } = filter
297
+
298
+ if (relationColumn) {
299
+ const relationAlias = applyJoins(
300
+ selectQueryBuilder,
301
+ entityAlias,
302
+ relationColumn,
303
+ metadata,
304
+ 'leftJoin',
305
+ columnName || sortingName,
306
+ true
307
+ )
308
+ return `${relationAlias}.${columnName}`
309
+ } else {
310
+ return `${entityAlias}.${columnName}`
311
+ }
312
+ }
313
+
314
+ /**
315
+ * Applies the necessary joins to the SelectQueryBuilder based on the relation column.
316
+ *
317
+ * @param selectQueryBuilder - The SelectQueryBuilder where the joins will be applied.
318
+ * @param entityAlias - The current alias of the entity in the query.
319
+ * @param relationColumn - The dot-notated string representing the relation chain (e.g., "user.profile.address").
320
+ * @param metadata - The EntityMetadata of the entity.
321
+ * @param joinType - The type of join to use ("innerJoin" or "leftJoin").
322
+ * @param columnName - The name of the column used for filtering or sorting, default to 'name'.
323
+ * @param selectField - Whether to include the field in the SELECT clause.
324
+ * @returns {string} - The alias to be used for the final field in the relation chain.
325
+ */
326
+ function applyJoins<T>(
327
+ selectQueryBuilder: SelectQueryBuilder<T>,
328
+ entityAlias: string,
329
+ relationColumn: string,
330
+ metadata: EntityMetadata,
331
+ joinType: 'innerJoin' | 'leftJoin' = 'leftJoin',
332
+ columnName: string = 'name',
333
+ selectField: boolean = false
334
+ ): string {
335
+ const columns = relationColumn.split('.')
336
+ let currentAlias = entityAlias
337
+ let currentMetadata = metadata
338
+
339
+ for (const column of columns) {
340
+ const relation = currentMetadata.relations.find(rel => rel.propertyName === column)
341
+
342
+ if (!relation) {
343
+ throw new Error(`Relation not found for column: ${column}`)
344
+ }
345
+
346
+ const nextAlias = `${currentAlias}_${relation.inverseEntityMetadata.tableName}_for_${columnName}`
347
+
348
+ if (!selectQueryBuilder.expressionMap.aliases.some(alias => alias.name === nextAlias)) {
349
+ selectQueryBuilder[joinType](`${currentAlias}.${column}`, nextAlias)
350
+ }
351
+ if (selectField && columns.at(-1) == column /* 최종 alias만 추가 */) {
352
+ selectQueryBuilder.addSelect(`${nextAlias}.${columnName}`, `${nextAlias}_${columnName}`)
353
+ }
354
+
355
+ currentAlias = nextAlias
356
+ currentMetadata = relation.inverseEntityMetadata
357
+ }
358
+
359
+ return currentAlias
360
+ }
361
+
362
+ /**
363
+ * Generates the SQL clause and parameters based on the provided filter.
364
+ *
365
+ * @param field - The database field to filter on.
366
+ * @param name - The name of the filter.
367
+ * @param operator - The operator to use in the filter.
368
+ * @param value - The value to filter with.
369
+ * @returns An object containing the SQL clause and the parameters.
370
+ */
371
+ function getClauseAndParameters(
372
+ field: string,
373
+ name: string,
374
+ operator: string,
375
+ value: any
376
+ ): { clause: string; parameters: { [key: string]: any } } {
377
+ const values = value instanceof Array ? value : [value]
378
+ let clause = ''
379
+ let parameters: { [key: string]: any } = {}
380
+
381
+ switch (operator) {
382
+ case 'eq':
383
+ clause = `${field} = :${name}`
384
+ parameters = { [name]: value }
385
+ break
386
+ case 'like':
387
+ clause = `${field} LIKE :${name}`
388
+ parameters = { [name]: `%${value}%` }
389
+ break
390
+ case 'search':
391
+ case 'i_like':
392
+ clause = `LOWER(${field}) LIKE :${name}`
393
+ parameters = { [name]: `%${String(value).toLowerCase()}%` }
394
+ break
395
+ case 'nlike':
396
+ clause = `${field} NOT LIKE :${name}`
397
+ parameters = { [name]: `%${value}%` }
398
+ break
399
+ case 'i_nlike':
400
+ clause = `LOWER(${field}) NOT LIKE :${name}`
401
+ parameters = { [name]: `%${String(value).toLowerCase()}%` }
402
+ break
403
+ case 'lt':
404
+ clause = `${field} < :${name}`
405
+ parameters = { [name]: value }
406
+ break
407
+ case 'gt':
408
+ clause = `${field} > :${name}`
409
+ parameters = { [name]: value }
410
+ break
411
+ case 'lte':
412
+ clause = `${field} <= :${name}`
413
+ parameters = { [name]: value }
414
+ break
415
+ case 'gte':
416
+ clause = `${field} >= :${name}`
417
+ parameters = { [name]: value }
418
+ break
419
+ case 'noteq':
420
+ clause = `${field} != :${name}`
421
+ parameters = { [name]: value }
422
+ break
423
+ case 'in':
424
+ clause = `${field} IN (:...${name})`
425
+ parameters = { [name]: values }
426
+ break
427
+ case 'notin':
428
+ clause = `${field} NOT IN (:...${name})`
429
+ parameters = { [name]: values }
430
+ break
431
+ case 'notin_with_null':
432
+ clause = `${field} IS NULL OR ${field} NOT IN (:...${name})`
433
+ parameters = { [name]: values }
434
+ break
435
+ case 'is_null':
436
+ clause = `${field} IS NULL`
437
+ break
438
+ case 'is_not_null':
439
+ clause = `${field} IS NOT NULL`
440
+ break
441
+ case 'is_false':
442
+ clause = `${field} IS FALSE`
443
+ break
444
+ case 'is_true':
445
+ clause = `${field} IS TRUE`
446
+ break
447
+ case 'is_not_false':
448
+ clause = `${field} IS NOT FALSE`
449
+ break
450
+ case 'is_not_true':
451
+ clause = `${field} IS NOT TRUE`
452
+ break
453
+ case 'is_present':
454
+ clause = `${field} IS PRESENT`
455
+ break
456
+ case 'is_blank':
457
+ clause = `${field} IS BLANK`
458
+ break
459
+ case 'is_empty_num_id':
460
+ clause = `${field} IS EMPTY NUMERIC ID`
461
+ break
462
+ case 'between':
463
+ clause = `${field} BETWEEN :${name}_1 AND :${name}_2`
464
+ parameters = { [`${name}_1`]: values[0], [`${name}_2`]: values[1] }
465
+ break
466
+ }
467
+
468
+ return { clause, parameters }
469
+ }
@@ -0,0 +1,60 @@
1
+ import moment from 'moment-timezone'
2
+
3
+ /**
4
+ * Get the time range for the specified period relative to the current moment in a specific domain's timezone.
5
+ *
6
+ * @param {string} period - The time period to calculate the range for. Valid options are 'today', 'this month', '30 days', 'this year', '12 months'.
7
+ * @param {Object} context - The context object containing domain information.
8
+ * @returns {Promise<{ from: string; to: string }>} - A Promise that resolves to an object containing 'from' and 'to' ISO date strings representing the time range.
9
+ */
10
+ export async function getTimesForPeriod(period: 'today' | 'this month' | '30 days' | 'this year' | '12 months', context: any): Promise<{ from: string; to: string }> {
11
+ const { domain } = context.state
12
+ const theDate = moment.tz(new Date(), domain.timezone)
13
+
14
+ if (period == 'today') {
15
+ const from = theDate.clone().format('YYYY-MM-DD')
16
+ const to = theDate.clone().add(1, 'day').startOf('day').format('YYYY-MM-DD')
17
+
18
+ return { from, to }
19
+ } else if (period == 'this month') {
20
+ const from = theDate.clone().startOf('month').format('YYYY-MM-DD')
21
+ const to = theDate.clone().add(1, 'month').startOf('month').format('YYYY-MM-DD')
22
+
23
+ return { from, to }
24
+ } else if (period == '30 days') {
25
+ const from = theDate.clone().subtract(30, 'day').format('YYYY-MM-DD')
26
+ const to = theDate.clone().add(1, 'day').startOf('day').format('YYYY-MM-DD')
27
+
28
+ return { from, to }
29
+ } else if (period == 'this year') {
30
+ const from = theDate.clone().startOf('year').format('YYYY-MM-DD')
31
+ const to = theDate.clone().add(1, 'year').startOf('year').format('YYYY-MM-DD')
32
+
33
+ return { from, to }
34
+ } else if (period == '12 months') {
35
+ const from = theDate.clone().subtract(12, 'month').startOf('month').format('YYYY-MM-DD')
36
+ const to = theDate.clone().add(1, 'month').startOf('month').format('YYYY-MM-DD')
37
+
38
+ return { from, to }
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Get the ISO date strings for the specified period relative to the current moment in a specific domain's timezone.
44
+ *
45
+ * @param {string} period - The time period to calculate the range for. Valid options are 'today', 'this month', '30 days', 'this year', '12 months'.
46
+ * @param {Object} context - The context object containing domain information.
47
+ * @returns {Promise<{ from: string; to: string }>} - A Promise that resolves to an object containing 'from' and 'to' ISO date strings representing the time range.
48
+ */
49
+ export async function getISOStringsForPeriod(
50
+ period: 'today' | 'this month' | '30 days' | 'this year' | '12 months',
51
+ context: any
52
+ ): Promise<{ from: string; to: string }> {
53
+ const { domain } = context.state
54
+ const { from, to } = await getTimesForPeriod(period, context)
55
+
56
+ return {
57
+ from: moment.tz(from, domain.timezone).toISOString(),
58
+ to: moment.tz(to, domain.timezone).toISOString()
59
+ }
60
+ }
@@ -0,0 +1,8 @@
1
+ export * from './get-domain'
2
+ export * from './condition-builder'
3
+ export * from './list-query-builder'
4
+ export * from './list-params-converter'
5
+ export * from './publish-progress'
6
+ export * from './get-query-builder-from-list-params'
7
+ export * from './list-param-adjuster'
8
+ export * from './get-times-for-period'
@@ -0,0 +1,21 @@
1
+ import { Filter } from '../service/common-types'
2
+
3
+ /**
4
+ * Adjust the given array of filters based on the provided changes.
5
+ * If a filter with the same name exists in the original array, it will be replaced with the new filter;
6
+ * otherwise, the new filter will be added to the array.
7
+ *
8
+ * @param {Filter[]} filters - The original array of filters to be adjusted.
9
+ * @param {Filter[]} filtersChange - The array of filter changes to be applied.
10
+ * @returns {Filter[]} - The adjusted array of filters.
11
+ */
12
+ export function adjustFilters(filters: Filter[], filtersChange: Filter[]): Filter[] {
13
+ var filtersNew = [...filters]
14
+
15
+ filtersChange.forEach(change => {
16
+ const idx = (filtersNew || []).findIndex(f => f.name === change.name)
17
+ idx !== -1 ? filtersNew.splice(idx, 1, change) : filtersNew.push(change)
18
+ })
19
+
20
+ return filtersNew
21
+ }