@opengis/fastify-table 1.4.10 → 1.4.12
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 +1 -1
- package/server/plugins/crud/funcs/getAccess.js +22 -16
- package/server/plugins/table/funcs/metaFormat/getSelectVal.js +12 -4
- package/server/routes/crud/controllers/deleteCrud.js +1 -1
- package/server/routes/crud/controllers/insert.js +2 -2
- package/server/routes/crud/controllers/table.js +18 -19
- package/server/routes/crud/controllers/update.js +4 -2
- package/server/routes/table/functions/getData.js +1 -1
package/package.json
CHANGED
|
@@ -2,16 +2,7 @@ import pgClients from '../../pg/pgClients.js';
|
|
|
2
2
|
import getTemplate from '../../table/funcs/getTemplate.js';
|
|
3
3
|
import applyHook from '../../hook/funcs/applyHook.js';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
* @param {Array} user_roles.actions Actions - user actions <> group actions
|
|
7
|
-
* @param {Array} role_access.actions Actions - user actions = group actions
|
|
8
|
-
* @param {String} body.actions Actions from table template
|
|
9
|
-
* @param {String} table Table name / Interface alias
|
|
10
|
-
* @param {Object} user User object
|
|
11
|
-
* @param {String} user.uid User ID
|
|
12
|
-
* @param {String} user.user_type User type
|
|
13
|
-
* @returns { scope: String, actions: String[], query: String }
|
|
14
|
-
*/
|
|
5
|
+
const allActions = ['view', 'edit', 'add', 'del'];
|
|
15
6
|
|
|
16
7
|
const q = `select a.route_id as id, d.actions as user_roles, d.actions as role_actions, coalesce(b.actions, array['view']) as interface_actions, b.scope, c.role_id
|
|
17
8
|
from admin.routes a
|
|
@@ -29,7 +20,18 @@ left join admin.user_roles d on
|
|
|
29
20
|
end )
|
|
30
21
|
where $1 in (a.route_id, a.alias, a.table_name) and $2 in (b.user_uid, d.user_uid)`;
|
|
31
22
|
|
|
32
|
-
|
|
23
|
+
/**
|
|
24
|
+
* @param {Array} user_roles.actions Actions - user actions <> group actions
|
|
25
|
+
* @param {Array} role_access.actions Actions - user actions = group actions
|
|
26
|
+
* @param {String} body.actions Actions from table template
|
|
27
|
+
* @param {String} table Table name / Interface alias
|
|
28
|
+
* @param {Object} user User object
|
|
29
|
+
* @param {String} user.uid User ID
|
|
30
|
+
* @param {String} user.user_type User type
|
|
31
|
+
* @returns { scope: String, roles: String[], actions: String[], query: String }
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
export default async function getAccess({ table, form, user = {} }, pg = pgClients.client) {
|
|
33
35
|
if (!table) return null;
|
|
34
36
|
|
|
35
37
|
const hookData = await applyHook('getAccess', { table, user, pg });
|
|
@@ -38,15 +40,19 @@ export default async function getAccess({ table, user = {} }, pg = pgClients.cli
|
|
|
38
40
|
const { uid, user_type: userType = 'regular' } = user;
|
|
39
41
|
|
|
40
42
|
if (userType === 'superadmin') {
|
|
41
|
-
return { actions:
|
|
43
|
+
return { actions: allActions, query: '1=1' };
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
const body = await getTemplate('table', table);
|
|
45
|
-
const tableActions =
|
|
47
|
+
const tableActions = !body && form
|
|
48
|
+
? allActions // if db table and form => full access (token)
|
|
49
|
+
: ['view'].concat(body?.actions || body?.action_default || []);
|
|
46
50
|
|
|
47
51
|
if (userType === 'admin') {
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
if (!(body?.actions || body?.action_default) && (body?.form || form)) {
|
|
53
|
+
return { actions: allActions, query: '1=1' };
|
|
54
|
+
}
|
|
55
|
+
return { actions: tableActions, query: '1=1' };
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
if (body?.public || body?.access === 'public') {
|
|
@@ -76,7 +82,7 @@ export default async function getAccess({ table, user = {} }, pg = pgClients.cli
|
|
|
76
82
|
const query = userAccess?.scope === 'my' ? `uid='${uid}'` : '1=1';
|
|
77
83
|
const actions = userAccess?.interface_actions
|
|
78
84
|
?.filter?.((el, idx, arr) => arr.indexOf(el) === idx)
|
|
79
|
-
?.filter(el => userAccess?.role_actions?.length ? userAccess?.role_actions.includes(el) : true)
|
|
85
|
+
?.filter(el => (userAccess?.role_actions?.length ? userAccess?.role_actions.includes(el) : true))
|
|
80
86
|
?.filter(el => tableActions.includes(el));
|
|
81
87
|
return {
|
|
82
88
|
scope: userAccess?.scope,
|
|
@@ -22,19 +22,27 @@ export default async function getSelectVal({
|
|
|
22
22
|
if (!cls?.sql) return null;
|
|
23
23
|
|
|
24
24
|
// select id column name
|
|
25
|
-
if (!selectIds[name])
|
|
25
|
+
if (!selectIds[name]) {
|
|
26
|
+
selectIds[name] = await pg.queryCache(`select * from (${cls.sql})q limit 0`)
|
|
27
|
+
.then((res) => res.fields?.[0]?.name)
|
|
28
|
+
.catch(err => console.error('getSelectVal error: 1', name, cls.sql, err.toString()));
|
|
29
|
+
}
|
|
26
30
|
const id = selectIds[name];
|
|
27
31
|
|
|
28
32
|
// cache
|
|
29
33
|
const key = `select:${name}`;
|
|
34
|
+
|
|
30
35
|
const cache = values?.length && config.redis ? (await rclient.hmget(key, values)).reduce((p, el, i) => ({ ...p, [values[i]]: el }), {}) : {};
|
|
31
36
|
const filteredValues = values.filter(el => !cache[el]);
|
|
32
37
|
|
|
33
38
|
// query select
|
|
39
|
+
const q = `with c(id,text) as (select * from (${cls.sql})q where ${id} = any('{${filteredValues.join(',').replace(/\"/g, '\\"').replace(/'/g, "''")}}')) select * from c`;
|
|
34
40
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
const data = filteredValues.length
|
|
42
|
+
? await pg.query(q)
|
|
43
|
+
.then(el => el.rows)
|
|
44
|
+
.catch(err => console.error('getSelectVal error: 2', name, q, err.toString()))
|
|
45
|
+
: [];
|
|
38
46
|
|
|
39
47
|
const clsObj = { ...cache, ...data.reduce((p, el) => ({ ...p, [el.id.toString()]: el.color ? el : el.text }), {}) };
|
|
40
48
|
|
|
@@ -28,7 +28,7 @@ export default async function deleteCrud(req, reply) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
if (!actions.includes('del') && !config?.local) {
|
|
31
|
-
return reply.status(403).send('access restricted');
|
|
31
|
+
return reply.status(403).send('access restricted: actions');
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const loadTemplate = await getTemplate('table', del);
|
|
@@ -26,14 +26,14 @@ export default async function insert(req, reply) {
|
|
|
26
26
|
|
|
27
27
|
const { form, table: add } = hookData || tokenData || (config.security?.disableToken || config.local || config.auth?.disable ? req.params : {});
|
|
28
28
|
|
|
29
|
-
const { actions = [] } = await getAccess({ table: add, user }, pg) || {};
|
|
29
|
+
const { actions = [] } = await getAccess({ table: add, form, user }, pg) || {};
|
|
30
30
|
|
|
31
31
|
if (!tokenData && !config.local && !config.security?.disableToken && !config.auth?.disable) {
|
|
32
32
|
return reply.status(400).send('invalid token');
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
if (!actions.includes('add') && !config.local) {
|
|
36
|
-
return reply.status(403).send('access restricted');
|
|
36
|
+
return reply.status(403).send('access restricted: actions');
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
if (!add) {
|
|
@@ -17,57 +17,56 @@ export default async function tableAPI(req, reply) {
|
|
|
17
17
|
if (hookData?.message && hookData?.status) {
|
|
18
18
|
return { message: hookData?.message, status: hookData?.status };
|
|
19
19
|
}
|
|
20
|
-
const
|
|
20
|
+
const templateName = hookData?.table || tokenData.table || params.table;
|
|
21
|
+
|
|
22
|
+
const loadTable = await getTemplate('table', templateName);
|
|
21
23
|
|
|
22
|
-
const loadTable = await getTemplate('table', tableName1) || {};
|
|
23
24
|
if (!loadTable && !pg.pk?.[tokenData.table]) {
|
|
24
25
|
return { message: 'not found', status: 404 };
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
const { table, /* columns, */ form } = loadTable;
|
|
28
|
-
|
|
29
|
-
const templateName = hookData?.table || tokenData.table || params.table;
|
|
30
|
-
const tableName = table || templateName;
|
|
28
|
+
const { table = params.table, /* columns, */ form } = hookData || loadTable || tokenData;
|
|
31
29
|
|
|
32
30
|
const id = hookData?.id || tokenData.id || params.id;
|
|
33
31
|
|
|
34
32
|
if (tokenData && !id) return { message: {} };
|
|
35
|
-
if (!
|
|
33
|
+
if (!table && !id) {
|
|
36
34
|
return reply.status(400).send('not enough params');
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
const { actions = [], query: accessQuery } = await getAccess({
|
|
37
|
+
const { actions = [], query: accessQuery } = await getAccess({
|
|
38
|
+
table, form, id, user,
|
|
39
|
+
}, pg) || {};
|
|
40
40
|
|
|
41
41
|
if (!tokenData && !config?.local && !config.security?.disableToken) {
|
|
42
42
|
return reply.status(400).send('invalid token');
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
if (!actions.includes('edit') && !config?.local) {
|
|
46
|
-
return reply.status(403).send('access restricted');
|
|
46
|
+
return reply.status(403).send('access restricted: actions');
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
const { pk, columns: dbColumns = [] } = await getMeta({ pg, table
|
|
49
|
+
const { pk, columns: dbColumns = [] } = await getMeta({ pg, table });
|
|
50
50
|
|
|
51
51
|
if (!pk) {
|
|
52
52
|
return reply.status(404).send(`table not found: ${table}`);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
// const cols = columns.map((el) => el.name || el).join(',');
|
|
56
|
-
const
|
|
57
|
-
const formData = await getTemplate('form', formName) || {};
|
|
56
|
+
const formData = await getTemplate('form', form) || {};
|
|
58
57
|
const schema = formData?.schema || formData || {};
|
|
59
58
|
// skip DataTable from another table
|
|
60
59
|
const extraKeys = Object.keys(schema).filter((key) => schema[key]?.table && schema[key]?.parent_id && Object.hasOwn(schema[key], 'colModel'));
|
|
61
60
|
// skip non-existing columns
|
|
62
61
|
const columnList = dbColumns.map((el) => el.name || el).join(',');
|
|
63
62
|
|
|
64
|
-
const { fields = [] } = !loadTable?.table ? await pg.query(`select * from ${
|
|
63
|
+
const { fields = [] } = !loadTable?.table ? await pg.query(`select * from ${table} limit 0`) : {};
|
|
65
64
|
const cols = loadTable?.table
|
|
66
65
|
? Object.keys(schema || {}).filter((col) => columnList.includes(col) && !extraKeys.includes(col))?.map((col) => (col?.includes('geom') && schema?.[col]?.type === 'Geom' ? `st_asgeojson(${col})::json as "${col}"` : `"${col}"`))?.join(',')
|
|
67
66
|
: fields.map((el) => (el?.name?.includes('geom') && pg.pgType[el?.dataTypeID] === 'geometry' ? `st_asgeojson(${el.name})::json as "${el.name}"` : `"${el?.name}"`)).join(',');
|
|
68
|
-
const where = [`"${pk}" = $1`, loadTable
|
|
67
|
+
const where = [`"${pk}" = $1`, loadTable?.query, accessQuery].filter(Boolean).filter((el) => (user?.user_type === 'superadmin' ? !el.includes('{{uid}}') : true));
|
|
69
68
|
const geom = dbColumns.find((el) => el.name === 'geom' && pg.pgType[el.dataTypeID] === 'geometry') ? ',st_asgeojson(geom)::json as geom' : '';
|
|
70
|
-
const q = `select "${pk}" as id, ${cols || '*'} ${geom} from ${
|
|
69
|
+
const q = `select "${pk}" as id, ${cols || '*'} ${geom} from ${table} t where ${where.join(' and ') || 'true'} limit 1`;
|
|
71
70
|
|
|
72
71
|
if (query?.sql === '1') {
|
|
73
72
|
const extraQ = extraKeys?.map((key) => {
|
|
@@ -93,23 +92,23 @@ export default async function tableAPI(req, reply) {
|
|
|
93
92
|
const { colModel, table: extraTable, parent_id: parentId } = schema[key];
|
|
94
93
|
const colModel1 = Array.isArray(colModel) ? colModel : Object.values(colModel || {});
|
|
95
94
|
const q1 = `select ${parentId} as parent, ${colModel1.map((col) => col.name || col.key).join(',')} from ${extraTable} a where ${parentId}=$1`;
|
|
96
|
-
// console.log(
|
|
95
|
+
// console.log(table, formName, q1);
|
|
97
96
|
const { rows: extraRows } = await pg.query(q1, [id]);
|
|
98
97
|
Object.assign(data, { [key]: extraRows });
|
|
99
98
|
}));
|
|
100
99
|
}
|
|
101
100
|
if (user?.uid && !config.security?.disableToken && actions.includes('edit')) {
|
|
102
101
|
data.token = tokenData?.table ? params.table : setToken({
|
|
103
|
-
ids: [JSON.stringify({ id, table
|
|
102
|
+
ids: [JSON.stringify({ id, table, form })],
|
|
104
103
|
uid: user.uid,
|
|
105
104
|
array: 1,
|
|
106
105
|
})?.[0];
|
|
107
106
|
}
|
|
108
107
|
|
|
109
|
-
await extraDataGet({ rows: [data], table
|
|
108
|
+
await extraDataGet({ rows: [data], table, form }, pg);
|
|
110
109
|
|
|
111
110
|
const res = await applyHook('afterTable', {
|
|
112
|
-
pg, table
|
|
111
|
+
pg, table, payload: [data], user,
|
|
113
112
|
});
|
|
114
113
|
|
|
115
114
|
return res || data || {};
|
|
@@ -28,14 +28,16 @@ export default async function update(req, reply) {
|
|
|
28
28
|
|
|
29
29
|
const { form, table: edit, id } = hookData || tokenData || (config.security?.disableToken || config.local || config.auth?.disable ? params : {});
|
|
30
30
|
|
|
31
|
-
const { actions = [] } = await getAccess({
|
|
31
|
+
const { actions = [] } = await getAccess({
|
|
32
|
+
table: edit, form, id, user,
|
|
33
|
+
}, pg) || {};
|
|
32
34
|
|
|
33
35
|
if (!tokenData && !config.local && !config.security?.disableToken && !config.auth?.disable) {
|
|
34
36
|
return reply.status(400).send('invalid token');
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
if (!actions.includes('edit') && !config.local) {
|
|
38
|
-
return reply.status(403).send('access restricted');
|
|
40
|
+
return reply.status(403).send('access restricted: actions');
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
if (!edit) {
|
|
@@ -376,7 +376,7 @@ export default async function dataAPI(req, reply, called) {
|
|
|
376
376
|
uid,
|
|
377
377
|
array: 1,
|
|
378
378
|
});
|
|
379
|
-
Object.assign(res, { addToken: addTokens[0] });
|
|
379
|
+
Object.assign(res, { addToken: addTokens?.[0] });
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
const result = await applyHook('afterData', {
|