@opengis/fastify-table 1.4.25 → 1.4.27
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 +3 -3
- package/server/plugins/table/funcs/getFilter.js +9 -3
- package/server/plugins/table/funcs/getFilterSQL/index.js +29 -24
- package/server/routes/table/controllers/filter.js +48 -32
- package/server/routes/table/functions/getData.js +10 -3
- package/server/routes/util/controllers/user.tokens.js +9 -3
- package/server/routes/util/index.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengis/fastify-table",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.27",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "core-plugins",
|
|
6
6
|
"keywords": [
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"uglify-js": "3.19.3"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"eslint": "8.49.0",
|
|
55
|
-
"eslint-config-airbnb": "19.0.4"
|
|
54
|
+
"eslint": "^8.49.0",
|
|
55
|
+
"eslint-config-airbnb": "^19.0.4"
|
|
56
56
|
},
|
|
57
57
|
"author": "Softpro",
|
|
58
58
|
"license": "ISC"
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import routeFilter from '../../../routes/table/controllers/filter.js';
|
|
2
2
|
|
|
3
|
-
export default async function getFilter({
|
|
3
|
+
export default async function getFilter({
|
|
4
|
+
pg, table, filter, custom, state, search, user,
|
|
5
|
+
}, reply) {
|
|
4
6
|
const params = { table };
|
|
5
|
-
const query = {
|
|
6
|
-
|
|
7
|
+
const query = {
|
|
8
|
+
filter, custom, state, search,
|
|
9
|
+
};
|
|
10
|
+
const result = await routeFilter({
|
|
11
|
+
pg, params, query, user,
|
|
12
|
+
}, reply, 1);
|
|
7
13
|
return result;
|
|
8
14
|
}
|
|
@@ -7,6 +7,7 @@ import config from '../../../../../config.js';
|
|
|
7
7
|
import getTableSql from './util/getTableSql.js';
|
|
8
8
|
import getFilterQuery from './util/getFilterQuery.js';
|
|
9
9
|
import getOptimizedQuery from './util/getOptimizedQuery.js';
|
|
10
|
+
import getFilter from '../getFilter.js';
|
|
10
11
|
|
|
11
12
|
const checkInline = {};
|
|
12
13
|
const defaultTable = 'crm.extra_data';
|
|
@@ -85,30 +86,34 @@ export default async function getFilterSQL({
|
|
|
85
86
|
return pk && !fieldsList.includes(name) ? `${pk} in (select ${pk} from (${fieldQuery.replace(/limit 0/g, '')} where ${name} ${sval} )q where 1=1)` : `${name} ${sval}`;
|
|
86
87
|
}).join(' or ')} )` : '';
|
|
87
88
|
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.concat(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
89
|
+
const filterResponse = await getFilter({
|
|
90
|
+
pg, table, user: { uid },
|
|
91
|
+
}, null, 1);
|
|
92
|
+
|
|
93
|
+
const filterList1 = await Promise.all((filterList || [])
|
|
94
|
+
.concat(filterResponse?.inline || [])
|
|
95
|
+
.concat(filterResponse?.custom || [])
|
|
96
|
+
.concat(filterResponse?.state || [])
|
|
97
|
+
.concat(filterResponse?.list || [])
|
|
98
|
+
?.filter(el => el.id || el.name)
|
|
99
|
+
?.map(async (el) => {
|
|
100
|
+
el.name = el.name || el.id;
|
|
101
|
+
|
|
102
|
+
if (el.name && extraColumns.find(item => item?.name === el?.name)) {
|
|
103
|
+
Object.assign(el, { extra: { table: extraDataTable, input: extraColumns.find(item => item?.name === el?.name)?.type } });
|
|
104
|
+
}
|
|
105
|
+
if (!el?.data) return el;
|
|
106
|
+
// const cls = await getTemplate(['cls', 'select'], el.data); // only git cls
|
|
107
|
+
const cls = await getSelect(el.data, pg); // git + db cls
|
|
108
|
+
|
|
109
|
+
if (Array.isArray(cls?.arr) && cls.arr.length) {
|
|
110
|
+
Object.assign(el, { cls: el.data, options: cls, select: `select code, name from admin.cls where parent='${el.data}'` });
|
|
111
|
+
}
|
|
112
|
+
else if (typeof (cls?.sql || cls) === 'string') {
|
|
113
|
+
Object.assign(el, { select: cls?.sql || cls });
|
|
114
|
+
}
|
|
115
|
+
return el;
|
|
116
|
+
}));
|
|
112
117
|
|
|
113
118
|
const modelQuery = body?.model || body?.table || table;
|
|
114
119
|
|
|
@@ -1,26 +1,42 @@
|
|
|
1
|
-
import {
|
|
2
|
-
logger, autoIndex, getSelect, getTemplate, getSelectVal, pgClients,
|
|
3
|
-
} from '../../../../utils.js';
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/* import {
|
|
2
|
+
logger, autoIndex, getSelect, getTemplate, getSelectVal, pgClients, applyHook,
|
|
3
|
+
} from '../../../../utils.js'; */
|
|
4
|
+
|
|
5
|
+
import logger from '../../../plugins/logger/getLogger.js';
|
|
6
|
+
import autoIndex from '../../../plugins/pg/funcs/autoIndex.js';
|
|
7
|
+
import getSelect from '../../../plugins/table/funcs/getSelect.js';
|
|
8
|
+
import getSelectVal from '../../../plugins/table/funcs/metaFormat/getSelectVal.js';
|
|
9
|
+
import pgClients from '../../../plugins/pg/pgClients.js';
|
|
10
|
+
import applyHook from '../../../plugins/hook/funcs/applyHook.js';
|
|
11
|
+
import getTemplate from '../../../plugins/table/funcs/getTemplate.js';
|
|
12
|
+
|
|
13
|
+
export default async function filterAPI(req, reply, iscalled) {
|
|
6
14
|
const time = Date.now();
|
|
7
15
|
|
|
8
16
|
const {
|
|
9
17
|
params, pg = pgClients.client, user = {}, query = {},
|
|
10
18
|
} = req;
|
|
11
19
|
|
|
20
|
+
const hookData = await applyHook('preFilter', { pg, table: params.table });
|
|
12
21
|
const loadTable = await getTemplate('table', params.table);
|
|
13
|
-
if (!loadTable) { return { status: 404, message: 'not found' }; }
|
|
14
22
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
const {
|
|
24
|
+
table, sql, query: tableQuery, form, extra, filterState, filterCustom,
|
|
25
|
+
} = hookData || loadTable || {};
|
|
26
|
+
|
|
27
|
+
if (!table) {
|
|
28
|
+
return { status: 404, message: 'not found' };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const sqlTable = sql?.filter?.((el) => !el?.disabled && el?.sql?.replace)?.map?.((el, i) => ` left join lateral (${el.sql.replace('{{uid}}', user?.uid)}) ${el.name || `t${i}`} on 1=1 `)?.join?.('') || '';
|
|
32
|
+
const { fields: columns = [] } = await pg.query(`select * from ${table} t ${sqlTable} limit 0`);
|
|
33
|
+
const { fields = [] } = await pg.query(`select * from ${table} t limit 0`);
|
|
18
34
|
|
|
19
|
-
const optimizedSQL = `select * from ${
|
|
35
|
+
const optimizedSQL = `select * from ${table} t ${sqlTable} where ${tableQuery || '1=1'}`.replace(/{{uid}}/g, user?.uid);
|
|
20
36
|
|
|
21
37
|
if (query?.sql && user?.user_type === 'admin') return optimizedSQL;
|
|
22
38
|
|
|
23
|
-
const filters = (loadTable?.filter_list || loadTable?.filters || loadTable?.filterList || []).concat(loadTable?.filterSql || []);
|
|
39
|
+
const filters = (hookData?.filters || loadTable?.filter_list || loadTable?.filters || loadTable?.filterList || []).concat(loadTable?.filterSql || []);
|
|
24
40
|
|
|
25
41
|
// admin.custom_column - user filter NA-165
|
|
26
42
|
const { rows: properties = [] } = await pg.query('select column_id, name, title, format, data from admin.custom_column where entity=$1 and uid=$2 and filter', [params.table, user?.uid]);
|
|
@@ -29,25 +45,25 @@ export default async function filterAPI(req) {
|
|
|
29
45
|
}));
|
|
30
46
|
|
|
31
47
|
// KRYVYIRIH-231
|
|
32
|
-
autoIndex({ table
|
|
48
|
+
autoIndex({ table, columns: filters.filter((el) => columns?.find?.((item) => item?.name === el.name)) })
|
|
33
49
|
.catch(err => {
|
|
34
50
|
console.error(err.toString());
|
|
35
51
|
logger.file('autoindex/error', { name: params?.table, error: err.toString(), stack: err.stack });
|
|
36
52
|
});
|
|
37
53
|
|
|
38
|
-
const loadTemplate =
|
|
39
|
-
const extraColumns =
|
|
54
|
+
const loadTemplate = form ? await getTemplate('form', form) : {};
|
|
55
|
+
const extraColumns = extra ? Object.keys(loadTemplate?.schema || {}).filter(key => !columns.find(col => col.name === key)) : [];
|
|
40
56
|
|
|
41
57
|
filters?.forEach?.(el => Object.assign(el, { id: el.id || el.name, title: el.title || el.ua, extra: extraColumns.includes(el.id || el.name) }));
|
|
42
58
|
|
|
43
59
|
await Promise.all(filters.filter((el) => el.data && el.id && el.type !== 'Autocomplete' && !el.sql).map(async (el) => {
|
|
44
60
|
const cls = await getSelect(el.data, pg);
|
|
45
61
|
|
|
46
|
-
if (!cls
|
|
62
|
+
if (!cls) return;
|
|
47
63
|
const { dataTypeID } = columns.find((item) => item.name === el.id) || {};
|
|
48
64
|
|
|
49
65
|
if (el.extra && Array.isArray(cls?.arr || cls)) {
|
|
50
|
-
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,
|
|
66
|
+
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, table]);
|
|
51
67
|
const options = countArr.rows.map(cel => {
|
|
52
68
|
const data = (cls?.arr || cls).find(c => c.id === cel.id);
|
|
53
69
|
return { ...cel, ...data };
|
|
@@ -60,7 +76,7 @@ export default async function filterAPI(req) {
|
|
|
60
76
|
? `select unnest(${el.id})::text as id,count(*) from (${optimizedSQL})q group by unnest(${el.id}) limit 100`
|
|
61
77
|
: `select ${el.id}::text as id,count(*) from (${optimizedSQL})q group by ${el.id} limit 100`;
|
|
62
78
|
|
|
63
|
-
const countArr = await pg.queryCache(q, { table
|
|
79
|
+
const countArr = await pg.queryCache(q, { table });
|
|
64
80
|
|
|
65
81
|
if (countArr.timeout) {
|
|
66
82
|
Object.assign(el, { timeout: countArr.timeout });
|
|
@@ -79,13 +95,13 @@ export default async function filterAPI(req) {
|
|
|
79
95
|
Object.assign(el, { options });
|
|
80
96
|
}));
|
|
81
97
|
|
|
82
|
-
const q = ((
|
|
98
|
+
const q = ((filterState || []).concat(filterCustom || [])).filter((el) => el.name && el.sql).map((el) => `select count(*), '${el.name}' as name from (${optimizedSQL})q where ${el.sql}`).join(' union all ');
|
|
83
99
|
|
|
84
100
|
const { rows = [], timeout: timeout1 } = q ? await pg.queryCache(q) : {};
|
|
85
101
|
if (timeout1) logger.file('timeout/filter', { table: params.table, type: 'state/custom' });
|
|
86
102
|
|
|
87
103
|
if (rows?.length) {
|
|
88
|
-
((
|
|
104
|
+
((filterState || []).concat(filterCustom || [])).filter((el) => el.name && el.sql).forEach((el) => {
|
|
89
105
|
const { count } = rows.find((row) => row.name === el.name) || {};
|
|
90
106
|
Object.assign(el, { count, sql: undefined });
|
|
91
107
|
});
|
|
@@ -105,22 +121,22 @@ export default async function filterAPI(req) {
|
|
|
105
121
|
percentile_disc(0.5) within group (order by ${el.name}),
|
|
106
122
|
percentile_disc(0.75) within group (order by ${el.name}),
|
|
107
123
|
max(${el.name})
|
|
108
|
-
] as range from ${
|
|
109
|
-
{ table
|
|
110
|
-
).then(
|
|
111
|
-
if (
|
|
112
|
-
logger.file('timeout/filter', { table: params.table, type: 'Range', filter:
|
|
113
|
-
return
|
|
124
|
+
] as range from ${table} ${sqlList && false ? ` t ${sqlList}` : ''} where ${tableQuery || '1=1'}`,
|
|
125
|
+
{ table },
|
|
126
|
+
).then(res => {
|
|
127
|
+
if (res.timeout) {
|
|
128
|
+
logger.file('timeout/filter', { table: params.table, type: 'Range', filter: res.name });
|
|
129
|
+
return res;
|
|
114
130
|
}
|
|
115
|
-
return
|
|
131
|
+
return res.rows?.[0]?.range;
|
|
116
132
|
});
|
|
117
133
|
Object.assign(el, { data });
|
|
118
134
|
}));
|
|
119
135
|
|
|
120
|
-
const sqlFilters = (
|
|
121
|
-
const q1 = sqlFilters.map((el) => `select count(*), '${el.name}' as name from ${
|
|
136
|
+
const sqlFilters = (filterCustom || []).filter((el) => el.name && el.sql);
|
|
137
|
+
const q1 = sqlFilters.map((el) => `select count(*), '${el.name}' as name from ${table} where ${tableQuery || '1=1'} and ${el.sql}`).join(' union all ');
|
|
122
138
|
|
|
123
|
-
const { rows: sqlRows = [], timeout } = q1 ? await pg.queryCache(q1, { table
|
|
139
|
+
const { rows: sqlRows = [], timeout } = q1 ? await pg.queryCache(q1, { table }) : {};
|
|
124
140
|
if (timeout) logger.file('timeout/filter', { table: params.table, type: 'sqlFilters' });
|
|
125
141
|
|
|
126
142
|
if (sqlRows?.length) {
|
|
@@ -132,9 +148,9 @@ export default async function filterAPI(req) {
|
|
|
132
148
|
|
|
133
149
|
return {
|
|
134
150
|
time: Date.now() - time,
|
|
135
|
-
list: filters?.map?.(el => ({ ...el, sql: undefined })),
|
|
136
|
-
custom:
|
|
151
|
+
list: filters?.map?.(el => ({ ...el, sql: iscalled ? el.sql : undefined })),
|
|
152
|
+
custom: filterCustom,
|
|
137
153
|
inline: loadTable?.filterInline,
|
|
138
|
-
state:
|
|
154
|
+
state: filterState,
|
|
139
155
|
};
|
|
140
156
|
}
|
|
@@ -17,6 +17,14 @@ const checkInline = {};
|
|
|
17
17
|
const maxLimit = 100;
|
|
18
18
|
const defaultLimit = 20;
|
|
19
19
|
|
|
20
|
+
function getOrder(query, columnList, orderColumn, loadTable, orderDir, called) {
|
|
21
|
+
if (query?.order && called) { return `order by ${query.order}`; }
|
|
22
|
+
if (typeof orderColumn === 'string' && Array.isArray(columnList) && columnList.includes(orderColumn)) {
|
|
23
|
+
return `order by ${orderColumn} ${query?.desc || orderDir === 'desc' ? 'desc' : ''} nulls last`;
|
|
24
|
+
}
|
|
25
|
+
return `order by ${(loadTable?.order || 'true::boolean')} nulls last`;
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
export default async function dataAPI(req, reply, called) {
|
|
21
29
|
const {
|
|
22
30
|
pg = pgClients.client, params, headers = {}, query = {}, user = {}, contextQuery, sufix = true,
|
|
@@ -166,9 +174,8 @@ export default async function dataAPI(req, reply, called) {
|
|
|
166
174
|
// id, query, filter
|
|
167
175
|
const [orderColumn, orderDir] = (query.order || loadTable?.order || '').split(/[- ]/);
|
|
168
176
|
|
|
169
|
-
const order = query
|
|
170
|
-
|
|
171
|
-
: `order by ${(loadTable?.order || 'true::boolean')} nulls last`;
|
|
177
|
+
const order = getOrder(query, columnList, orderColumn, loadTable, orderDir, called);
|
|
178
|
+
|
|
172
179
|
const search = loadTable?.meta?.search && query.search
|
|
173
180
|
? `(${loadTable?.meta?.search?.split(',')?.map(el => `${el} ilike '%${query.search.replace(/%/g, '\\%').replace(/'/g, "''")}%'`).join(' or ')})`
|
|
174
181
|
: null;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
customTokens, userTokens, handlebarsSync, setOpt, getOpt, config,
|
|
3
|
+
} from '../../../../utils.js';
|
|
2
4
|
|
|
3
5
|
export default async function userTokensAPI({ params = {}, user = {} }, reply) {
|
|
4
6
|
if (!user?.uid) {
|
|
@@ -9,6 +11,10 @@ export default async function userTokensAPI({ params = {}, user = {} }, reply) {
|
|
|
9
11
|
return reply.status(400).send('not enough params: token');
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
if (!config.redis) {
|
|
15
|
+
return reply.status(500).send('empty redis');
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
if (!customTokens[params.token]) {
|
|
13
19
|
return reply.status(404).send('token not found');
|
|
14
20
|
}
|
|
@@ -22,7 +28,7 @@ export default async function userTokensAPI({ params = {}, user = {} }, reply) {
|
|
|
22
28
|
const token = setOpt(
|
|
23
29
|
{
|
|
24
30
|
...customTokens[params.token],
|
|
25
|
-
query: handlebarsSync.compile(customTokens[params.token]?.query || 'true')({ user, uid: user.uid })
|
|
31
|
+
query: handlebarsSync.compile(customTokens[params.token]?.query || 'true')({ user, uid: user.uid }),
|
|
26
32
|
},
|
|
27
33
|
user.uid,
|
|
28
34
|
);
|
|
@@ -36,4 +42,4 @@ export default async function userTokensAPI({ params = {}, user = {} }, reply) {
|
|
|
36
42
|
userTokens[user.uid][params.token] = token;
|
|
37
43
|
|
|
38
44
|
return token;
|
|
39
|
-
}
|
|
45
|
+
}
|
|
@@ -6,7 +6,7 @@ async function plugin(app, config = {}) {
|
|
|
6
6
|
const { prefix = '/api' } = config;
|
|
7
7
|
app.get(`${prefix}/next-id`, { config: { policy: ['public'] } }, nextId);
|
|
8
8
|
app.get(`${prefix}/status-monitor`, {}, statusMonitor);
|
|
9
|
-
app.get(`${prefix}/user-tokens/:token`, { config: { policy: ['user'] } }, userTokens);
|
|
9
|
+
app.get(`${prefix}/user-tokens/:token`, { config: { policy: ['user', 'site'] } }, userTokens);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export default plugin;
|