@opengis/fastify-table 1.2.73 → 1.2.74

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.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/fastify-table",
3
- "version": "1.2.73",
3
+ "version": "1.2.74",
4
4
  "type": "module",
5
5
  "description": "core-plugins",
6
6
  "keywords": [
@@ -11,11 +11,11 @@ const checkInline = {};
11
11
  const defaultTable = 'crm.extra_data';
12
12
 
13
13
  function getExtraQuery(mainColumns, schema, table, pk) {
14
- const extraKeys = Object.keys(schema || {}).filter(key => !mainColumns.map(el => el?.name).includes(key));
14
+ const extraColumns = Object.keys(schema || {}).filter(key => !mainColumns.map(el => el?.name).includes(key)).map(key => ({ name: key, type: schema[key]?.type }));
15
15
 
16
16
  return {
17
- q: extraKeys.map((el) => `,(select value_text as "${el.replace(/'/g, "''")}" from ${table} where property_key='${el.replace(/'/g, "''")}' and object_id=t.${pk} limit 1)`).join(' '),
18
- extraKeys,
17
+ q: extraColumns.map((el) => `,(select value_text as "${el.name.replace(/'/g, "''")}" from ${table} where property_key='${el.name.replace(/'/g, "''")}' and object_id=t.${pk} limit 1)`).join(' '),
18
+ extraColumns,
19
19
  };
20
20
  }
21
21
 
@@ -37,7 +37,7 @@ export default async function getFilterSQL({
37
37
  : undefined;
38
38
 
39
39
  const { fields: fieldsModel = [] } = body?.table && pg.pk[body.table] ? await pg.query(`select * from ${body.table} limit 0`) : {};
40
- const { q: extraSqlColumns, extraKeys } = pg.pk?.[extraDataTable] && pg.pk?.[body.table || table]
40
+ const { q: extraSqlColumns, extraColumns = [] } = pg.pk?.[extraDataTable] && pg.pk?.[body.table || table]
41
41
  ? getExtraQuery(fieldsModel, loadTemplate?.schema, extraDataTable, pg.pk[body.table || table])
42
42
  : {};
43
43
 
@@ -66,7 +66,7 @@ export default async function getFilterSQL({
66
66
  const sqlInline = body.sql?.filter?.(el => el.inline).map(el => `,(${el.sql})`).join('');
67
67
 
68
68
  const fieldQuery = `select * ${extraSqlColumns || ''} ${sqlInline || ''} from ${body?.table || table} t ${sqlTable || ''} limit 0`;
69
- const { fields = [] } = !extra ? await pg.query(fieldQuery) : { fields: extraKeys.map(el => ({ name: el.name, dataTypeID: 25 })) };
69
+ const { fields = [] } = await pg.query(fieldQuery);
70
70
  const autoSearchColumn = fields?.filter((el) => pg.pgType?.[el.dataTypeID] === 'text')?.map((el) => el.name).join(',');
71
71
  const searchColumn = body?.search_column || body?.meta?.search || autoSearchColumn;
72
72
  const fieldsList = (fieldsModel || fields)?.map((el) => el.name);
@@ -92,8 +92,8 @@ export default async function getFilterSQL({
92
92
  )?.filter(el => el.id || el.name)?.map(async (el) => {
93
93
  el.name = el.name || el.id;
94
94
 
95
- if (el.name && extraKeys?.includes?.(el.name)) {
96
- Object.assign(el, { extra: { table: extraDataTable } });
95
+ if (el.name && extraColumns.find(item => item?.name === el?.name)) {
96
+ Object.assign(el, { extra: { table: extraDataTable, input: extraColumns.find(item => item?.name === el?.name)?.type } });
97
97
  }
98
98
  if (!el?.data) return el;
99
99
  const cls = await getTemplate(['cls', 'select'], el.data);
@@ -139,7 +139,6 @@ export default async function getFilterSQL({
139
139
  optimizedSQL,
140
140
  sqlTable,
141
141
  extraSqlColumns,
142
- extraKeys,
143
142
  tableCount,
144
143
  table: modelQuery,
145
144
  // filter parts
@@ -1,199 +1,110 @@
1
- const dateTypeList = ['date', 'timestamp', 'timestamp without time zone', 'timestamp with time zone'];
2
- const numberTypeList = ['float8', 'int4', 'int8', 'numeric', 'double precision', 'integer'];
1
+ import getRangeQuery from "./getRangeQuery.js";
3
2
 
4
- const isValidDate = (dateStr) => {
5
- if (!dateStr) return false;
6
- // iso date: 2024-01-01
7
- if (dateStr?.indexOf('-') !== -1) {
8
- const [yyyy, mm, dd] = dateStr.split('-');
9
- return new Date(mm + '/' + dd + '/' + yyyy).toString() !== 'Invalid Date';
10
- }
11
-
12
- // locale date: 01.01.2024
13
- const [dd, mm, yyyy] = dateStr.split('.');
14
- return new Date(mm + '/' + dd + '/' + yyyy).toString() !== 'Invalid Date';
15
- };
16
-
17
- function dt(y, m, d) {
18
- return new Date(Date.UTC(y, m, d)).toISOString().slice(0, 10);
19
- }
20
-
21
- const dp = {
22
- d: new Date().getDate(),
23
- w: new Date().getDate() - (new Date().getDay() || 7) + 1,
24
- m: new Date().getMonth(),
25
- q: (new Date().getMonth() / 4).toFixed() * 3,
26
- y: new Date().getFullYear(),
27
- };
28
-
29
- function formatDateISOString(date) {
30
- if (!date?.includes('.')) return date;
31
- const [day, month, year] = date.split('.');
32
- return `${year}-${month}-${day}`;
33
- }
34
-
35
- function getDates(value) {
36
- // date range, specific options: current day, week, month, year etc.
37
- if (value === 'cd') {
38
- return [dt(dp.y, dp.m, dp.d)];
39
- }
40
- if (value === 'cw') {
41
- return [dt(dp.y, dp.m, dp.w), dt(dp.y, dp.m, dp.w + 6)];
42
- }
43
- if (value === 'cm') {
44
- return [dt(dp.y, dp.m, 1), dt(dp.y, dp.m + 1, 0)];
45
- }
46
- if (value === 'cq') {
47
- return [dt(dp.y, dp.q, 1), dt(dp.y, dp.q + 3, 0)];
48
- }
49
- if (value === 'cy') {
50
- return [dt(dp.y, 0, 1), dt(dp.y, 11, 31)];
51
- }
52
-
53
- // date range, example: 01.01.2024-31.12.2024
54
- const [startDate, endDate] = value.split('-');
55
- const min = formatDateISOString(startDate);
56
- const max = formatDateISOString(endDate);
57
- return [min, max];
58
- }
59
-
60
- function formatValue({
61
- pg, table, filter = {}, name, value, operator = '=', dataTypeID, uid = 1, optimize,
3
+ export default function formatValue({
4
+ pg, table, filter = {}, name, value, dataTypeID, uid = 1, optimize,
62
5
  }) {
63
- const { extra, sql, select } = filter;
6
+ const { extra, sql, select, strict } = filter;
64
7
  const pk = pg?.pk && table ? pg.pk[table] : undefined;
65
8
 
66
- if (!dataTypeID && !extra) return {};
67
-
68
9
  const filterType = filter.type?.toLowerCase() || 'text';
69
- const fieldType = pg.pgType?.[dataTypeID] || pg.pgType?.[{ date: 1114 }[filterType] || 25];
10
+ const fieldType = pg?.pgType?.[dataTypeID] || pg?.pgType?.[{ date: 1114 }[filterType] || 25];
70
11
 
71
- if (!name || !value || !fieldType) return {};
72
-
73
- // Date filter
74
- if (['date', 'datepicker'].includes(filterType) /* && dateTypeList.includes(fieldType) */) {
75
- const [min, max] = getDates(value, name);
76
-
77
- // check date is valid
78
- if (!isValidDate(min) || (!isValidDate(max) && value !== 'cd')) {
79
- return { op: 'between', query: 'false' };
80
- }
12
+ if (!name
13
+ || !value
14
+ || !dataTypeID && !extra && !sql || extra?.input?.toLowerCase?.() === 'datatable') {
15
+ return {};
16
+ }
81
17
 
82
- // with sql subquery
83
- if (sql) {
84
- return { op: '=', query: sql.replace(/\$1/g, `'${min}'::date`).replace(/\$2/g, `'${max}'::date`) };
85
- }
18
+ // my rows
19
+ if (value === 'me' && uid) {
20
+ return { op: '=', query: `${name}::text = '${uid}'` };
21
+ }
86
22
 
87
- // preset values
88
- if (['cw', 'cm', 'cq', 'cy'].includes(value)) {
89
- return { op: 'between', query: `${name}::date between '${min}'::date and '${max}'::date` };
90
- }
23
+ const matchNull = { null: 'is null', notnull: 'is not null' }[value];
24
+ // null = true is null, not false
25
+ const matchBoolean = fieldType === 'boolean' ? { true: 'is true', false: 'is false' }[value] : null;
26
+ const matchMulti = fieldType.includes('[]') ? `::text[] && '{${value}}'::text[]` : null;
27
+ const match = matchNull || matchBoolean || matchMulti || `::text=any('{${value}}'::text[])`;
91
28
 
92
- // preset values: current day
93
- if (value === 'cd') {
94
- return { op: '=', query: `${name}::date = '${min}'::date` };
29
+ // geometry
30
+ if (['geometry'].includes(fieldType)) {
31
+ if (matchNull) {
32
+ return { op: 'nullable', query: `${name} ${matchNull}` };
95
33
  }
96
34
 
97
- // extra columns, example: crm.extra_data
98
- if (extra?.table && pk) {
99
- const query = extra?.table && pk
100
- ? `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_date::date between '${min}'::date and '${max}'::date)`
101
- : `${pk} in (select ${pk} from ${extra.table} where ${name}::date between '${min}'::date and '${max}'::date)`;
102
- return { op: 'between', query };
103
- }
35
+ const bbox = value[0].split('_');
104
36
 
105
- // same date
106
- if (max && min && min === max) {
107
- return { op: 'strict', query: `${name}::date = '${min}'::date` };
37
+ if (bbox?.length !== 4) {
38
+ return {};
108
39
  }
109
40
 
110
- // default
111
- return { op: 'between', query: `${name}::date between '${min}'::date and '${max}'::date` };
41
+ const query = ` ${name} && 'box(${bbox[0]} ${bbox[1]},${bbox[2]} ${bbox[3]})'::box2d `;
42
+ return { op: '&&', query };
112
43
  }
113
44
 
114
- // Range filter, example: 100-500
115
- if (filterType === 'range' /* && numberTypeList.includes(fieldType) */) {
116
- const [min, max] = value?.indexOf('_') !== -1 ? value.split('_') : value.split('-');
45
+ const querysql = sql?.replace?.(/= ?any\(\$1\)/g, `::text=any('{ ${value} }'::text[])`)?.replace?.(/\$1/g, `'${value}'`);
46
+ const op = filterType === 'text' && !strict ? '~' : '=';
117
47
 
48
+ // text
49
+ if (['text'].includes(filterType)) {
118
50
  // with sql subquery
119
51
  if (sql) {
120
- return { op: '=', query: sql.replace(/\$1/g, min).replace(/\$2/g, max) };
52
+ return { op: '=', query: querysql };
121
53
  }
122
54
 
123
- if (min === 'min' && max === 'max') {
124
- return { op: 'between', query: 'false' };
55
+ // extra table, example: crm.extra_data
56
+ if (extra?.table && pk) {
57
+ const query = `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text ${matchNull || `${op} '${value}'`} )`;
58
+ return { op, query };
125
59
  }
126
60
 
127
- // default
128
- const query = (max === 'max' ? `${name} > ${min}` : null) || (min === 'min' ? `${name} < ${max}` : null) || `${name} between ${min} and ${max}`;
129
- return { op: 'between', query };
130
- }
61
+ // select / cls
62
+ if (select) {
63
+ const subquery = matchNull ? `name ${matchNull}` : `lower(name::text) ~ lower('${value}')`;
64
+ return {
65
+ op,
66
+ query: `${name}::text in ( ( with q(id,name) as (${select}) select id from q where ${subquery}) )`,
67
+ };
68
+ }
131
69
 
132
- // Autocomplete, Check, Text filter with sql subquery
133
- if (sql) {
134
- // autocomplete, check
135
- if (sql?.indexOf('any($1)') !== -1) {
136
- const query = sql.replace(/= ?any\(\$1\)/g, `::text=any('{ ${value} }'::text[])`);
137
- return { op: '=', query };
70
+ // nullable
71
+ if (matchNull) {
72
+ const query = `${name} ${matchNull}`;
73
+ return { op, query };
138
74
  }
139
75
 
140
76
  // default
141
- const query = sql.replace(/\$1/g, `'${value}'`);
142
- return { op: '=', query };
143
- }
77
+ const query = `lower(${name}::text) ${op} lower('${value}')`;
144
78
 
145
- const matchNull = { null: 'is null', notnull: 'is not null' }[value];
146
- const matchBoolean = fieldType === 'boolean' ? { true: 'is true', false: 'is false' }[value] : null;
147
- const matchMulti = value?.indexOf(',') !== -1 ? `= any('{${value}}'::text[])` : null;
148
- const match = matchNull || matchBoolean || matchMulti || (operator === '=' ? `='${value}'` : `ilike '%${value}%'`);
79
+ return { op, query };
80
+ }
149
81
 
150
- const formatType = {
151
- float8: 'numeric',
152
- numeric: 'numeric',
153
- int4: 'numeric',
154
- integer: 'integer',
155
- int8: 'numeric',
156
- "double precision": 'double precision',
157
- varchar: 'text',
158
- "character varying": 'character varying',
159
- bool: 'boolean',
160
- boolean: 'boolean',
161
- geometry: 'geom',
162
- }[fieldType] || 'text';
82
+ // date: 01.01.2024-31.12.2024 / number range: 100-500
83
+ if (['date', 'datepicker', 'range'].includes(filterType)) {
84
+ const query = getRangeQuery(value, name, fieldType, filterType, sql, extra, pk);
85
+ return { op: 'between', query };
86
+ }
163
87
 
164
88
  /* select query - from admin.cls / filter options */
165
89
  if (['check', 'autocomplete', 'text', 'tags', 'avatar', 'radio'].includes(filterType)) {
90
+ // with sql subquery
91
+ if (sql) {
92
+ return { op: '=', query: querysql };
93
+ }
166
94
 
167
95
  // extra table, example: crm.extra_data
168
96
  if (extra?.table && pk) {
169
- const subquery = filterType === 'autocomplete' ? match : `in ( ( with q(id,name) as (${select}) select id from q where name::text ${match}))`;
170
- const query = select
171
- ? `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text ${subquery} )`
172
- : `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text ${match})`;
173
- return { op: operator, query };
174
- }
175
-
176
- // default
177
- const query = select && filterType === 'text'
178
- ? `${name}::text in ( ( with q(id,name) as (${select}) select id from q where name::text ${match}) )` // filter with cls
179
- : `${name}::${formatType} ${match}`; // simple filter
180
- return { op: operator, query };
181
- }
182
-
183
- // my rows
184
- if (value === 'me' && uid) {
185
- if (sql) {
186
- return { op: '=', query: sql.replace(/\$1/g, `'${uid}'`) };
97
+ const query = `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text ${matchNull || `='${value}'`} )`;
98
+ return { op: '=', query };
187
99
  }
188
100
 
189
- const query = extra?.table && pk && fieldType === 'text'
190
- ? `${pk} in (select ${pk} from ${extra.table} where uid = '${uid}')`
191
- : `${name}::text = '${uid}'`;
101
+ // nullable + default
102
+ const query = `${name} ${match}`;
192
103
  return { op: '=', query };
193
104
  }
194
105
 
195
- // sql column?
196
- if (optimize && optimize.name !== optimize.pk) {
106
+ // sql column
107
+ if (optimize && optimize.name !== optimize.pk && false) {
197
108
  const val = filterType === 'text' ? `ilike '%${value}%'` : `= any('{${value}}')`;
198
109
  return {
199
110
  op: '~',
@@ -204,60 +115,5 @@ function formatValue({
204
115
  };
205
116
  }
206
117
 
207
- // filter text array column
208
- if (fieldType?.includes('[]')) {
209
- return { op: 'in', query: `'{${value}}'::text[] && ${name}::text[]` };
210
- }
211
-
212
- // filter multiple values, example: 1,2,3
213
- if (value?.indexOf(',') !== -1) {
214
- const values = value.split(',').filter((el) => el !== 'null');
215
-
216
- // extra table, example: crm.extra_data
217
- if (extra?.table && pk) {
218
- const query = value?.indexOf('null') !== -1
219
- ? `${pk} in (select object_id from ${extra.table} where property_key='${name}' and ( value_text is null or value_text in (${values?.map((el) => `'"${el}"'`).join(',')}) ) )`
220
- : `${pk} in (select object_id from ${extra.table} where property_key='${name}' and value_text in (${values?.map((el) => `'"${el}"'`).join(',')}) )`;
221
- return { op: 'in', query };
222
- }
223
-
224
- // default
225
- const query = value?.indexOf('null') !== -1
226
- ? `( ${name} is null or ${name}::text in (${values?.map((el) => `'${el}'`).join(',')}) )`
227
- : `${name}::text in (${value.split(',')?.map((el) => `'${el}'`).join(',')})`;
228
- return { op: 'in', query };
229
- }
230
-
231
- if (['<', '>'].includes(operator)) {
232
- const query = `${name} ${operator} '${value}'::${formatType}`;
233
- return { op: operator, query };
234
- }
235
-
236
- if (operator === '=' && filterType !== 'text' && !filter?.data) {
237
- const query = {
238
- null: `${name} is null`,
239
- notnull: `${name} is not null`,
240
- }[value] || `${name}::${formatType}='${value}'::${formatType}`;
241
- return { op: '=', query };
242
- }
243
-
244
- // json
245
- if (name.includes('.')) {
246
- const [col, prop] = name.split('.');
247
- const query = ` ${col}->>'${prop}' in ('${value.join("','")}')`;
248
- return { op: 'in', query };
249
- }
250
-
251
- // geometry
252
- if (['geometry'].includes(fieldType)) {
253
- const bbox = value[0].split('_');
254
-
255
- if (bbox?.length === 4) {
256
- const query = ` ${name} && 'box(${bbox[0]} ${bbox[1]},${bbox[2]} ${bbox[3]})'::box2d `;
257
- return { op: '&&', query };
258
- }
259
- }
260
118
  return {};
261
119
  }
262
-
263
- export default formatValue;
@@ -32,7 +32,6 @@ function getQuery({
32
32
  // filter
33
33
  const filter = filterList?.find?.((el) => el.type && el.name === name) || { type: 'text' };
34
34
  const { strict, extra } = filter;
35
- const operator = filter.strict || ['check', 'autocomplete'].includes(filter.type.toLowerCase()) ? '=' : (splitby !== '=' ? splitby : '~');
36
35
 
37
36
  // find all value
38
37
  const value = filterQueryArray.filter((el) => el.split(splitby)?.[0] === name).map((el) => el.substring(name.length + 1)).join(',');
@@ -40,7 +39,9 @@ function getQuery({
40
39
  const optimize = fields?.find((el) => el.name === name) ? null : tableSQL.find((el) => el.name === name);
41
40
 
42
41
  // find field and skip not exists
43
- const { dataTypeID } = fields?.find((el) => el.name === name) || fields?.find((el) => el.name === optimize?.pk) || {};
42
+ const { dataTypeID } = fields?.find((el) => el.name === name)
43
+ || fields?.find((el) => el.name === optimize?.pk)
44
+ || {};
44
45
 
45
46
  // format query
46
47
  const {
@@ -49,12 +50,11 @@ function getQuery({
49
50
  pg,
50
51
  table,
51
52
  filter,
52
- optimize,
53
53
  name,
54
54
  value,
55
- operator,
56
55
  dataTypeID,
57
56
  uid,
57
+ optimize,
58
58
  }) || {};
59
59
  if (!query) continue;
60
60
 
@@ -0,0 +1,154 @@
1
+ const dateTypeList = ['date', 'timestamp', 'timestamp without time zone', 'timestamp with time zone'];
2
+ const numberTypeList = ['float8', 'int4', 'int8', 'numeric', 'double precision', 'integer', 'bigint'];
3
+
4
+ const isValidDate = (dateStr) => {
5
+ if (!dateStr) return false;
6
+ if (['min', 'max'].includes(dateStr)) return true;
7
+ // iso date: 2024-01-01
8
+ if (dateStr?.indexOf('-') !== -1) {
9
+ const [yyyy, mm, dd] = dateStr.split('-');
10
+ return new Date(mm + '/' + dd + '/' + yyyy).toString() !== 'Invalid Date';
11
+ }
12
+
13
+ // locale date: 01.01.2024
14
+ const [dd, mm, yyyy] = dateStr.split('.');
15
+ return new Date(mm + '/' + dd + '/' + yyyy).toString() !== 'Invalid Date';
16
+ };
17
+
18
+ const isValidNumber = (numStr) => ['min', 'max'].includes(numStr) ? true : !isNaN(numStr);
19
+
20
+ function dt(y, m, d) {
21
+ return new Date(Date.UTC(y, m, d)).toISOString().slice(0, 10);
22
+ }
23
+
24
+ const dp = {
25
+ d: new Date().getDate(),
26
+ w: new Date().getDate() - (new Date().getDay() || 7) + 1,
27
+ m: new Date().getMonth(),
28
+ q: (new Date().getMonth() / 4).toFixed() * 3,
29
+ y: new Date().getFullYear(),
30
+ };
31
+
32
+ function formatDateISOString(date) {
33
+ if (!date?.includes('.')) return date;
34
+ const [day, month, year] = date.split('.');
35
+ return `${year}-${month}-${day}`;
36
+ }
37
+
38
+ function getRangeValues(value = '', filterType = 'range') {
39
+ if (['range'].includes(filterType)) {
40
+ return value?.indexOf('_') !== -1
41
+ ? value.split('_')
42
+ : value.split('-');
43
+ }
44
+
45
+ // date range, specific options: current day, week, month, year etc.
46
+ if (value === 'cd') {
47
+ return [dt(dp.y, dp.m, dp.d)];
48
+ }
49
+ if (value === 'cw') {
50
+ return [dt(dp.y, dp.m, dp.w), dt(dp.y, dp.m, dp.w + 6)];
51
+ }
52
+ if (value === 'cm') {
53
+ return [dt(dp.y, dp.m, 1), dt(dp.y, dp.m + 1, 0)];
54
+ }
55
+ if (value === 'cq') {
56
+ return [dt(dp.y, dp.q, 1), dt(dp.y, dp.q + 3, 0)];
57
+ }
58
+ if (value === 'cy') {
59
+ return [dt(dp.y, 0, 1), dt(dp.y, 11, 31)];
60
+ }
61
+
62
+ // date range, example: 01.01.2024-31.12.2024
63
+ const [startDate, endDate] = value.split('-');
64
+ const min = startDate === 'min' ? startDate : formatDateISOString(startDate);
65
+ const max = endDate === 'max' ? endDate : formatDateISOString(endDate);
66
+ return [min, max];
67
+ }
68
+
69
+ function checkValid(value, fieldType, filterType, sql, extra) {
70
+ const [min, max] = getRangeValues(value, filterType);
71
+
72
+ if (['date', 'datepicker'].includes(filterType)) {
73
+ const isvalid = (dateTypeList.includes(fieldType) || sql || extra) && (isValidDate(min) && (isValidDate(max) || value === 'cd'));
74
+ return { min, max, isvalid };
75
+ }
76
+
77
+ if (['range'].includes(filterType)) {
78
+ const isvalid = (numberTypeList.includes(fieldType) || sql || extra) && isValidNumber(min) && isValidNumber(max);
79
+ return { min, max, isvalid };
80
+ }
81
+
82
+ return { isvalid: false };
83
+ }
84
+
85
+ export default function getRangeQuery(value, name, fieldType, filterType, sql, extra, pk) {
86
+ const { min, max, isvalid } = checkValid(value, fieldType, filterType, sql, extra);
87
+ if (!isvalid) return 'false';
88
+
89
+ // with sql subquery
90
+ if (['date', 'datepicker'].includes(filterType) && sql) {
91
+ return sql.replace(/\$1/g, `'${min}'::date`).replace(/\$2/g, `'${max}'::date`);
92
+ }
93
+
94
+ // with sql subquery
95
+ if (['range'].includes(filterType) && sql) {
96
+ return sql.replace(/\$1/g, min).replace(/\$2/g, max);
97
+ }
98
+
99
+ // preset date values
100
+ if (['date', 'datepicker'].includes(filterType) && ['cd', 'cw', 'cm', 'cq', 'cy'].includes(value)) {
101
+ return value === 'cd'
102
+ ? `${name}::date = '${min}'::date`
103
+ : `${name}::date between '${min}'::date and '${max}'::date`;
104
+ }
105
+
106
+ // extra columns, example: crm.extra_data
107
+ if (['date', 'datepicker'].includes(filterType) && extra?.table && pk) {
108
+ const subquery = (max && min && min === max ? `value_date = '${value}'::date` : null)
109
+ || (max === 'max' ? `value_date::date > '${min}'::date` : null)
110
+ || (min === 'min' ? `value_date::date < '${max}'::date` : null)
111
+ || `value_date::date between '${min}'::date and '${max}'::date`;
112
+ const query = `${pk} in (select object_id from ${extra.table} where property_key='${name}' and ${subquery})`;
113
+ return query;
114
+ }
115
+
116
+ // extra columns, example: crm.extra_data
117
+ if (['range'].includes(filterType) && extra?.table && pk) {
118
+ const subquery = (max && min && min === max ? `value_text = '${value}'` : null)
119
+ || (max === 'max' ? `value_text::numeric > ${min}` : null)
120
+ || (min === 'min' ? `value_text::numeric < ${max}` : null)
121
+ || `value_text::numeric between ${min} and ${max}`;
122
+ const query = `${pk} in (select object_id from ${extra.table} where property_key='${name}' and ${subquery} )`;
123
+ return query;
124
+ }
125
+
126
+ // same date
127
+ if (['date', 'datepicker'].includes(filterType) && max && min && min === max) {
128
+ return `${name}::date = '${min}'::date`;
129
+ }
130
+
131
+ // skip, same as 1=1
132
+ if (['range'].includes(filterType) && min === 'min' && max === 'max') {
133
+ return 'true';
134
+ }
135
+
136
+ // default
137
+ if (max === 'max') {
138
+ return ['date', 'datepicker'].includes(filterType)
139
+ ? `${name} > '${min}'::date`
140
+ : `${name} > ${min}`;
141
+ }
142
+
143
+ if (min === 'min') {
144
+ return ['date', 'datepicker'].includes(filterType)
145
+ ? `${name} < '${max}'::date`
146
+ : `${name} < ${max}`;
147
+ }
148
+
149
+ if (['date', 'datepicker'].includes(filterType)) {
150
+ return `${name}::date between '${min}'::date and '${max}'::date`;
151
+ }
152
+
153
+ return `${name} between ${min} and ${max}`;
154
+ }
@@ -1,10 +1,10 @@
1
- import { logger, autoIndex, getSelect, getFilterSQL, getTemplate, getSelectVal, pgClients } from '../../../../utils.js';
1
+ import { logger, autoIndex, getSelect, getTemplate, getSelectVal, pgClients } from '../../../../utils.js';
2
2
 
3
3
  export default async function filterAPI(req) {
4
4
  const time = Date.now();
5
5
 
6
6
  const {
7
- params, query = {}, pg = pgClients.client, user = {},
7
+ params, pg = pgClients.client, user = {},
8
8
  } = req;
9
9
 
10
10
  const loadTable = await getTemplate('table', params.table);
@@ -14,20 +14,7 @@ export default async function filterAPI(req) {
14
14
  const { fields: columns = [] } = await pg.query(`select * from ${loadTable.table} t ${sqlTable} limit 0`);
15
15
  const { fields = [] } = await pg.query(`select * from ${loadTable.table} t limit 0`);
16
16
 
17
- const {
18
- filter, custom, state, search,
19
- } = query;
20
-
21
- // const { extra } = loadTable?.form ? await getTemplate('form', loadTable?.form) || {} : {};
22
-
23
- const { optimizedSQL = `select * from ${loadTable.table}` } = false ? await getFilterSQL({
24
- pg,
25
- table: params.table,
26
- filter,
27
- custom,
28
- state,
29
- search,
30
- }) : {};
17
+ const optimizedSQL = `select * from ${loadTable.table} t ${sqlTable}`;
31
18
 
32
19
  const filters = (loadTable?.filter_list || loadTable?.filters || loadTable?.filterList || []).concat(loadTable?.filterSql || []);
33
20
 
@@ -42,7 +29,10 @@ export default async function filterAPI(req) {
42
29
  logger.file('autoindex/error', { name: params?.table, error: err.toString(), stack: err.stack });
43
30
  });
44
31
 
45
- filters?.forEach?.(el => Object.assign(el, { id: el.id || el.name }));
32
+ const loadTemplate = loadTable?.form ? await getTemplate('form', loadTable.form) : {};
33
+ const extraColumns = loadTemplate?.extra ? Object.keys(loadTemplate?.schema || {}).filter(key => !columns.find(col => col.name === key)) : [];
34
+
35
+ filters?.forEach?.(el => Object.assign(el, { id: el.id || el.name, extra: extraColumns.includes(el.id || el.name) }));
46
36
 
47
37
  await Promise.all(filters.filter((el) => el.data && el.id && el.type !== 'Autocomplete').map(async (el) => {
48
38
  const cls = await getSelect(el.data, pg);
@@ -50,13 +40,14 @@ export default async function filterAPI(req) {
50
40
  if (!cls || !loadTable.table) return;
51
41
  const { dataTypeID } = columns.find((item) => item.name === el.id) || {};
52
42
 
53
- if (el.extra && el.type === 'select' && Array.isArray(cls)) {
54
- const countArr = await pg.query(`select value_text as id, count(*) from crm.extra_data where property_key=$1 and property_entity=$2 group by value_text`, [el.id, params.table]);
43
+ if (el.extra && Array.isArray(cls?.arr || cls)) {
44
+ const countArr = await pg.query(`select value_text as id, count(*) from crm.extra_data where property_key=$1 and property_entity=$2 group by value_text`, [el.id, loadTable.table]);
55
45
  const options = countArr.rows.map(cel => {
56
- const data = cls.find(c => c.id === cel.id);
46
+ const data = (cls?.arr || cls).find(c => c.id === cel.id);
57
47
  return { ...cel, ...data };
58
48
  });
59
49
  Object.assign(el, { options });
50
+ return;
60
51
  }
61
52
 
62
53
  const q = pg.pgType[dataTypeID]?.includes('[]')
@@ -64,6 +55,7 @@ export default async function filterAPI(req) {
64
55
  : `select ${el.id}::text as id,count(*) from (${optimizedSQL})q group by ${el.id} limit 100`;
65
56
 
66
57
  const countArr = await pg.queryCache(q, { table: loadTable.table });
58
+
67
59
  if (countArr.timeout) {
68
60
  Object.assign(el, { timeout: countArr.timeout });
69
61
  console.log('timeout filter', params.table, el.id);