@opengis/fastify-table 1.1.109 → 1.1.111
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 +6 -4
- package/server/plugins/crud/funcs/dataDelete.js +5 -3
- package/server/plugins/crud/funcs/dataInsert.js +5 -2
- package/server/plugins/crud/funcs/dataUpdate.js +3 -2
- package/server/plugins/crud/funcs/getAccess.js +19 -14
- package/server/plugins/pg/funcs/init.js +26 -9
- package/server/plugins/table/funcs/metaFormat/getSelectVal.js +1 -1
- package/server/routes/crud/controllers/table.js +2 -2
- package/server/routes/table/controllers/card.js +1 -1
- package/server/routes/table/controllers/data.js +4 -4
- package/server/routes/table/controllers/filter.js +11 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengis/fastify-table",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.111",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "core-plugins",
|
|
6
6
|
"main": "index.js",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"test": "node --test",
|
|
16
16
|
"docs:dev": "vitepress dev docs",
|
|
17
17
|
"docs:build": "vitepress build docs",
|
|
18
|
-
"docs:preview": "vitepress preview docs"
|
|
18
|
+
"docs:preview": "vitepress preview docs",
|
|
19
|
+
"compress": "node compress.js"
|
|
19
20
|
},
|
|
20
21
|
"dependencies": {
|
|
21
22
|
"@fastify/sensible": "^5.0.0",
|
|
@@ -27,7 +28,8 @@
|
|
|
27
28
|
"js-yaml": "^4.1.0",
|
|
28
29
|
"pg": "^8.11.3",
|
|
29
30
|
"pino": "^9.5.0",
|
|
30
|
-
"pino-abstract-transport": "^2.0.0"
|
|
31
|
+
"pino-abstract-transport": "^2.0.0",
|
|
32
|
+
"uglify-js": "^3.19.3"
|
|
31
33
|
},
|
|
32
34
|
"devDependencies": {
|
|
33
35
|
"@panzoom/panzoom": "^4.5.1",
|
|
@@ -44,4 +46,4 @@
|
|
|
44
46
|
},
|
|
45
47
|
"author": "Softpro",
|
|
46
48
|
"license": "ISC"
|
|
47
|
-
}
|
|
49
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import getPG from '../../pg/funcs/getPG.js';
|
|
2
2
|
import getMeta from '../../pg/funcs/getMeta.js';
|
|
3
|
-
|
|
4
|
-
// import { getPG, getMeta } from '../../../../utils.js';
|
|
3
|
+
import getRedis from '../../redis/funcs/getRedis.js';
|
|
5
4
|
|
|
6
5
|
import logChanges from './utils/logChanges.js';
|
|
7
6
|
|
|
7
|
+
const rclient = getRedis();
|
|
8
|
+
|
|
8
9
|
export default async function dataDelete({
|
|
9
10
|
table, id, pg: pg1, uid,
|
|
10
11
|
}) {
|
|
@@ -13,9 +14,10 @@ export default async function dataDelete({
|
|
|
13
14
|
if (!pg.tlist?.includes(table)) return 'table not exist';
|
|
14
15
|
const delQuery = `delete from ${table} WHERE ${pk} = $1 returning *`;
|
|
15
16
|
// console.log(updateDataset);
|
|
16
|
-
const res = await pg.
|
|
17
|
+
const res = await pg.query(delQuery, [id]).then(el => el.rows?.[0] || {});
|
|
17
18
|
await logChanges({
|
|
18
19
|
pg, table, id, uid, type: 'DELETE',
|
|
19
20
|
});
|
|
21
|
+
rclient.incr(`pg:${table}:crud`);
|
|
20
22
|
return res;
|
|
21
23
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import getPG from '../../pg/funcs/getPG.js';
|
|
2
2
|
import getMeta from '../../pg/funcs/getMeta.js';
|
|
3
|
-
|
|
4
|
-
// import { getPG, getMeta } from '../../../../utils.js';
|
|
3
|
+
import getRedis from '../../redis/funcs/getRedis.js';
|
|
5
4
|
|
|
6
5
|
import logChanges from './utils/logChanges.js';
|
|
7
6
|
|
|
7
|
+
const rclient = getRedis();
|
|
8
|
+
|
|
8
9
|
export default async function dataInsert({
|
|
9
10
|
id, table, data, pg: pg1, uid,
|
|
10
11
|
}) {
|
|
@@ -38,5 +39,7 @@ export default async function dataInsert({
|
|
|
38
39
|
await logChanges({
|
|
39
40
|
pg, table, data, id: res.rows?.[0]?.[pg.pk[table]], uid, type: 'INSERT',
|
|
40
41
|
});
|
|
42
|
+
|
|
43
|
+
rclient.incr(`pg:${table}:crud`);
|
|
41
44
|
return res;
|
|
42
45
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import getPG from '../../pg/funcs/getPG.js';
|
|
2
2
|
import getMeta from '../../pg/funcs/getMeta.js';
|
|
3
|
-
|
|
4
|
-
// import { getPG, getMeta } from '../../../../utils.js';
|
|
3
|
+
import getRedis from '../../redis/funcs/getRedis.js';
|
|
5
4
|
|
|
6
5
|
import logChanges from './utils/logChanges.js';
|
|
7
6
|
|
|
7
|
+
const rclient = getRedis();
|
|
8
8
|
const srids = {};
|
|
9
9
|
|
|
10
10
|
function assignValue(key, i, srid = 4326) {
|
|
@@ -60,5 +60,6 @@ export default async function dataUpdate({
|
|
|
60
60
|
pg, table, data, id, uid, type: 'UPDATE',
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
rclient.incr(`pg:${table}:crud`);
|
|
63
64
|
return res;
|
|
64
65
|
}
|
|
@@ -4,9 +4,7 @@ import pgClients from '../../pg/pgClients.js';
|
|
|
4
4
|
import getTemplate from '../../table/funcs/getTemplate.js';
|
|
5
5
|
import applyHook from '../../hook/funcs/applyHook.js';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const q = `select a.route_id as id, coalesce(b.actions,array['get']) as actions, b.scope
|
|
7
|
+
const q = `select a.route_id as id, coalesce(b.actions,array['view']) as "userActions", b.scope as "userScope"
|
|
10
8
|
from admin.routes a
|
|
11
9
|
left join admin.role_access b on
|
|
12
10
|
a.route_id=b.route_id
|
|
@@ -20,7 +18,7 @@ left join admin.user_roles d on
|
|
|
20
18
|
then d.expiration > CURRENT_DATE
|
|
21
19
|
else 1=1
|
|
22
20
|
end )
|
|
23
|
-
where $1 in (a.route_id, a.alias) and $2 in (b.user_uid, d.user_uid)`;
|
|
21
|
+
where $1 in (a.route_id, a.alias, a.table_name) and $2 in (b.user_uid, d.user_uid)`;
|
|
24
22
|
|
|
25
23
|
export default async function getAccess({ table, user = {} }) {
|
|
26
24
|
if (!table) return null;
|
|
@@ -28,21 +26,28 @@ export default async function getAccess({ table, user = {} }) {
|
|
|
28
26
|
const hookData = await applyHook('getAccess', { table, user });
|
|
29
27
|
if (hookData) return hookData;
|
|
30
28
|
|
|
31
|
-
const { uid } = user;
|
|
29
|
+
const { uid, user_type: userType } = user;
|
|
32
30
|
const body = await getTemplate('table', table) || {};
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
if (body.access === 'admin' && !userType?.includes?.('admin')) {
|
|
33
|
+
const { userActions = [] } = uid ? await pgClients.client.query(q, [table, uid]).then(el => el.rows?.[0] || {}) : {};
|
|
34
|
+
const customActions = userActions.filter((el => ['view'].concat(body.actions || body.action_default || []).includes(el)));
|
|
35
|
+
return { actions: customActions, query: '1=1' };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const actions = uid ? ['view'].concat(body.actions || body.action_default || []) : [];
|
|
36
39
|
|
|
37
|
-
if (config.auth?.disable ||
|
|
38
|
-
return { actions
|
|
40
|
+
if (config.auth?.disable || userType?.includes?.('admin') || body?.public || body.access === 'public') {
|
|
41
|
+
return { actions, query: '1=1' };
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
const dbTable = pgClients.client?.pk?.[table] ? table : body?.table;
|
|
45
|
+
if (!uid || !dbTable) return null;
|
|
42
46
|
|
|
43
|
-
const {
|
|
47
|
+
const { userScope, userActions = [] } = await pgClients.client.query(q, [table, uid]).then(el => el.rows?.[0] || {});
|
|
48
|
+
const query = body.access === 'user' || userScope === 'my' ? `uid='${uid}'` : null;
|
|
49
|
+
const customActions = userActions.filter((el => actions.includes(el)));
|
|
50
|
+
const scope = body.access === 'user' ? 'my' : userScope;
|
|
44
51
|
|
|
45
|
-
return {
|
|
46
|
-
scope, actions, query: scope === 'my' ? `uid='${uid}` : null,
|
|
47
|
-
};
|
|
52
|
+
return { scope, actions: customActions, query };
|
|
48
53
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { createHash } from 'crypto';
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
2
|
|
|
3
3
|
import getRedis from '../../redis/funcs/getRedis.js';
|
|
4
4
|
|
|
5
|
+
const rclient = getRedis({ db: 0 });
|
|
6
|
+
|
|
5
7
|
async function init(client) {
|
|
6
8
|
const textQuery = `select
|
|
7
9
|
(select json_object_agg(conrelid::regclass ,(SELECT attname FROM pg_attribute WHERE attrelid = c.conrelid and attnum = c.conkey[1]))
|
|
@@ -40,16 +42,31 @@ async function init(client) {
|
|
|
40
42
|
return result;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
async function queryCache(query) {
|
|
44
|
-
const
|
|
45
|
+
async function queryCache(query, param = {}) {
|
|
46
|
+
const { table, args = [], time = 15 } = param;
|
|
47
|
+
const seconds = typeof time !== 'number' || time < 0 ? 15 : time * 60;
|
|
48
|
+
|
|
49
|
+
// CRUD table state
|
|
50
|
+
const keyCacheTable = `pg:${table}:crud`;
|
|
51
|
+
const crudInc = table ? await rclient.get(keyCacheTable) || 0 : 0;
|
|
52
|
+
|
|
53
|
+
//
|
|
45
54
|
const hash = createHash('sha1').update(query).digest('base64');
|
|
46
|
-
const keyCache = `pg:${hash}`;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
const keyCache = `pg:${hash}:${crudInc}`;
|
|
56
|
+
|
|
57
|
+
const cacheData = await rclient.get(keyCache);
|
|
58
|
+
|
|
59
|
+
if (cacheData) {
|
|
60
|
+
// console.log('from cache', table, query);
|
|
61
|
+
return JSON.parse(cacheData);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const data = await client.query(query, args || []);
|
|
65
|
+
|
|
66
|
+
if (seconds > 0) {
|
|
67
|
+
rclient.set(keyCache, JSON.stringify(data), 'EX', seconds);
|
|
50
68
|
}
|
|
51
|
-
|
|
52
|
-
rclient.set(keyCache, JSON.stringify(data), 'EX', 60 * 60);
|
|
69
|
+
// console.log('no cache', table, crudInc, query);
|
|
53
70
|
return data;
|
|
54
71
|
}
|
|
55
72
|
|
|
@@ -26,7 +26,7 @@ export default async function getSelectVal({
|
|
|
26
26
|
|
|
27
27
|
// cache
|
|
28
28
|
const key = `select:${name}`;
|
|
29
|
-
const cache = (await redis.hmget(key, values)).reduce((p, el, i) => ({ ...p, [values[i]]: el }), {});
|
|
29
|
+
const cache = values?.length ? (await redis.hmget(key, values)).reduce((p, el, i) => ({ ...p, [values[i]]: el }), {}) : {};
|
|
30
30
|
const filteredValues = values.filter(el => !cache[el]);
|
|
31
31
|
|
|
32
32
|
// query select
|
|
@@ -34,7 +34,7 @@ export default async function tableAPI(req) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const { actions = [], query: accessQuery } = await getAccess({
|
|
37
|
-
table:
|
|
37
|
+
table: hookData?.table || tokenData.table || params.table,
|
|
38
38
|
id,
|
|
39
39
|
user,
|
|
40
40
|
}) || {};
|
|
@@ -77,7 +77,7 @@ export default async function tableAPI(req) {
|
|
|
77
77
|
Object.assign(data, { [key]: extraRows });
|
|
78
78
|
}));
|
|
79
79
|
}
|
|
80
|
-
if (user?.uid) {
|
|
80
|
+
if (user?.uid && actions?.includes?.('edit')) {
|
|
81
81
|
data.token = tokenData?.table ? params.table : setToken({
|
|
82
82
|
ids: [JSON.stringify({ id, table: tableName, form: loadTable.form })],
|
|
83
83
|
uid: user.uid,
|
|
@@ -26,7 +26,7 @@ export default async function dataAPI(req) {
|
|
|
26
26
|
const id = hookData?.id || params?.id;
|
|
27
27
|
const { actions = [], query: accessQuery } = await getAccess({ table: hookData?.table || params.table, id, user }) || {};
|
|
28
28
|
|
|
29
|
-
if (!actions.includes('
|
|
29
|
+
if (!actions.includes('view') && !config?.local) {
|
|
30
30
|
return { message: 'access restricted', status: 403 };
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -123,11 +123,11 @@ export default async function dataAPI(req) {
|
|
|
123
123
|
.replace(/{{uid}}/g, uid);
|
|
124
124
|
|
|
125
125
|
const { total, filtered } = keyQuery || hookData?.id || params.id ? rows.length
|
|
126
|
-
: await pg.queryCache(qCount).then((el) => el?.rows[0]);
|
|
126
|
+
: await pg.queryCache(qCount, { table: loadTable.table, time: 5 }).then((el) => el?.rows[0]);
|
|
127
127
|
|
|
128
|
-
await metaFormat({ rows, table: params.table });
|
|
128
|
+
await metaFormat({ rows, table: hookData?.table || params.table });
|
|
129
129
|
const res = {
|
|
130
|
-
time: Date.now() - time, public: ispublic, card: loadTable.card, actions
|
|
130
|
+
time: Date.now() - time, public: ispublic, card: loadTable.card, actions, total, filtered, count: rows.length, pk, form, rows, meta, columns, filters,
|
|
131
131
|
};
|
|
132
132
|
|
|
133
133
|
// console.log({ add: loadTable.table, form: loadTable.form });
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getSelect, getFilterSQL, getTemplate,
|
|
3
|
-
getSelectVal,
|
|
4
|
-
} from '../../../../utils.js';
|
|
1
|
+
import { getSelect, getFilterSQL, getTemplate, getSelectVal, pgClients } from '../../../../utils.js';
|
|
5
2
|
|
|
6
3
|
export default async function filterAPI(req) {
|
|
7
4
|
const time = Date.now();
|
|
8
5
|
|
|
9
6
|
const {
|
|
10
|
-
params, query = {}, pg,
|
|
7
|
+
params, query = {}, pg = pgClients.client, user = {},
|
|
11
8
|
} = req;
|
|
9
|
+
|
|
12
10
|
const loadTable = await getTemplate('table', params.table);
|
|
13
11
|
if (!loadTable) { return { status: 404, message: 'not found' }; }
|
|
14
12
|
|
|
15
|
-
const sqlTable = loadTable.sql?.filter?.((el) => !el?.disabled && el?.sql?.replace).map((el, i) => ` left join lateral (${el.sql.replace('{{uid}}',
|
|
13
|
+
const sqlTable = loadTable.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('') || '';
|
|
16
14
|
const { fields: columns } = await pg.query(`select * from ${loadTable.table} t ${sqlTable} limit 0`);
|
|
17
15
|
|
|
18
16
|
const {
|
|
@@ -20,7 +18,7 @@ export default async function filterAPI(req) {
|
|
|
20
18
|
} = query;
|
|
21
19
|
|
|
22
20
|
const { optimizedSQL = `select * from ${loadTable.table}` } = loadTable?.sql || filter || custom || state || search ? await getFilterSQL({
|
|
23
|
-
pg
|
|
21
|
+
pg,
|
|
24
22
|
table: params.table,
|
|
25
23
|
filter,
|
|
26
24
|
custom,
|
|
@@ -30,17 +28,17 @@ export default async function filterAPI(req) {
|
|
|
30
28
|
|
|
31
29
|
const filters = loadTable?.filters || loadTable?.filterList || [];
|
|
32
30
|
await Promise.all(filters.filter((el) => el.data).map(async (el) => {
|
|
33
|
-
const cls = await getSelect(el.data,
|
|
31
|
+
const cls = await getSelect(el.data, pg);
|
|
34
32
|
|
|
35
33
|
if (!cls || !loadTable.table) return;
|
|
36
34
|
const { dataTypeID } = columns.find((item) => item.name === el.id) || {};
|
|
37
35
|
|
|
38
|
-
const countArr =
|
|
39
|
-
? await
|
|
40
|
-
: await
|
|
36
|
+
const countArr = pg.pgType[dataTypeID]?.includes('[]')
|
|
37
|
+
? await pg.queryCache(`select unnest(${el.id})::text as id,count(*) from (${optimizedSQL})q group by unnest(${el.id})`, { table: loadTable.table, time: 5 })
|
|
38
|
+
: await pg.queryCache(`select ${el.id}::text as id,count(*) from (${optimizedSQL})q group by ${el.id}`, { table: loadTable.table, time: 5 });
|
|
41
39
|
const ids = countArr.rows.map(el1 => el1.id);
|
|
42
40
|
|
|
43
|
-
const clsData = await getSelectVal({ pg
|
|
41
|
+
const clsData = await getSelectVal({ pg, values: ids, name: el.data });
|
|
44
42
|
|
|
45
43
|
const options = countArr.rows.map(cel => {
|
|
46
44
|
const data = cls?.arr?.find(c => c.id === cel.id) || { text: clsData[cel.id] };
|
|
@@ -50,7 +48,7 @@ export default async function filterAPI(req) {
|
|
|
50
48
|
}));
|
|
51
49
|
|
|
52
50
|
const q = ((loadTable?.filterState || []).concat(loadTable?.filterCustom || [])).filter((el) => el.name && el.sql).map((el) => `select count(*), '${el.name}' as name from (${optimizedSQL})q where ${el.sql}`).join(' union all ');
|
|
53
|
-
const { rows = [] } = q ? await
|
|
51
|
+
const { rows = [] } = q ? await pg.query(q) : {};
|
|
54
52
|
if (rows?.length) {
|
|
55
53
|
((loadTable?.filterState || []).concat(loadTable?.filterCustom || [])).filter((el) => el.name && el.sql).forEach((el) => {
|
|
56
54
|
const { count } = rows.find((row) => row.name === el.name) || {};
|