@opengis/fastify-table 1.4.6 → 1.4.8

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/config.js CHANGED
@@ -1,11 +1,33 @@
1
- import fs from 'fs';
1
+ import dotenv from 'dotenv';
2
2
 
3
- const fileName = ['config.json', '/data/local/config.json'].find(el => (fs.existsSync(el) ? el : null));
4
- const config = fileName ? JSON.parse(fs.readFileSync(fileName)) : {};
3
+ import { existsSync, readFileSync } from 'node:fs';
5
4
 
5
+ import unflattenObject from './server/plugins/util/funcs/unflattenObject.js';
6
+
7
+ const fileName = ['config.json', '/data/local/config.json'].find(el => (existsSync(el) ? el : null));
8
+ const config = fileName ? JSON.parse(readFileSync(fileName)) : {};
9
+
10
+ // npm run dev === cross-env NODE_ENV=development
11
+ // alt: node --env=development
6
12
  Object.assign(config, {
7
13
  allTemplates: config?.allTemplates || {},
8
14
  skipCheckPolicyRoutes: [],
15
+ env: process.env?.NODE_ENV || process.argv[2]?.split?.('=')?.pop?.(),
9
16
  });
10
17
 
18
+ if (config.env && existsSync(`.env.${config.env}`)) {
19
+ const { parsed } = dotenv.config({ path: `.env.${config.env}` });
20
+ console.log('start with env:', config.env);
21
+
22
+ const obj = unflattenObject(parsed);
23
+
24
+ Object.keys(obj)
25
+ .filter(key => typeof obj[key] === 'string'
26
+ && (obj[key].startsWith('[') || ['true', 'false'].includes(obj[key]))) // json array / boolean
27
+ .forEach(key => {
28
+ obj[key] = JSON.parse(obj[key]);
29
+ });
30
+ Object.assign(config, { ...obj });
31
+ }
32
+
11
33
  export default config;
package/index.js CHANGED
@@ -113,5 +113,6 @@ async function plugin(fastify, opt) {
113
113
  templatesRoutes(fastify, opt);
114
114
 
115
115
  fastify.get('/api/test-proxy', {}, (req) => ({ ...req.headers || {}, sessionId: req.session?.sessionId }));
116
+ fastify.get('/api/config', { config: { policy: ['admin', 'site'] } }, () => config);
116
117
  }
117
118
  export default fp(plugin);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/fastify-table",
3
- "version": "1.4.6",
3
+ "version": "1.4.8",
4
4
  "type": "module",
5
5
  "description": "core-plugins",
6
6
  "keywords": [
@@ -25,10 +25,12 @@
25
25
  "test:helpers": "node --test .\\test\\helpers",
26
26
  "test:routes": "node --test .\\test\\routes",
27
27
  "test:functions": "node --test .\\test\\functions",
28
- "compress": "node compress.js"
28
+ "compress": "node compress.js",
29
+ "dev": "cross-env NODE_ENV=development node server.js"
29
30
  },
30
31
  "dependencies": {
31
32
  "@fastify/http-proxy": "11.1.2",
33
+ "dotenv": "16.5.0",
32
34
  "fastify": "5.3.3",
33
35
  "fastify-plugin": "5.0.1",
34
36
  "handlebars": "4.7.8",
@@ -43,6 +45,7 @@
43
45
  "uglify-js": "3.19.3"
44
46
  },
45
47
  "devDependencies": {
48
+ "cross-env": "7.0.3",
46
49
  "eslint": "8.49.0",
47
50
  "eslint-config-airbnb": "19.0.4"
48
51
  },
@@ -46,7 +46,7 @@ export default async function dataDelete({
46
46
  });
47
47
  throw err;
48
48
  })
49
- .then(el => el.rows?.[0] || {});
49
+ .then(el => (el.rows?.[0] ? { rowCount: 1, ...el.rows[0] } : {}));
50
50
 
51
51
  await logChanges({
52
52
  pg, table, tokenData, referer, id, uid, type: 'DELETE',
@@ -1,4 +1,4 @@
1
- import { config, getTemplate, pgClients } from "../../../utils.js";
1
+ import { config, getTemplate, pgClients } from '../../../utils.js';
2
2
 
3
3
  const defaultTable = 'crm.extra_data';
4
4
 
@@ -24,7 +24,7 @@ export default async function extraDataGet({
24
24
 
25
25
  const extraDataTable = config.extraData?.[table]
26
26
  || config.extraData?.[table.split('.').shift()]
27
- || config.extraData?.['default']
27
+ || config.extraData?.default
28
28
  || config.extraData
29
29
  || defaultTable;
30
30
 
@@ -44,9 +44,10 @@ export default async function extraDataGet({
44
44
  ...extraRows
45
45
  .filter(el => el.object_id === row.id)
46
46
  .reduce((acc, curr) => Object.assign(acc, {
47
- [curr.property_key]: format(curr.property_key, curr.value_text, loadTemplate?.schema)
48
- }), {})
47
+ [curr.property_key]: format(curr.property_key, curr.value_text, loadTemplate?.schema),
48
+ }), {}),
49
49
  });
50
50
  });
51
51
  }
52
- };
52
+ return null;
53
+ }
@@ -1,8 +1,14 @@
1
1
  import routeData from '../../../routes/table/controllers/tableData.js';
2
2
 
3
- export default async function getData({ id, table, pg, filter, state, limit, page, search, user, order, sql, contextQuery, sufix }, reply, called) {
3
+ export default async function getData({
4
+ id, table, pg, headers, filter, state, limit, page, search, user, order, sql, contextQuery, sufix,
5
+ }, reply, called) {
4
6
  const params = { table, id };
5
- const query = { filter, limit, page, search, sql, state, order };
6
- const result = await routeData({ pg, params, query, user, contextQuery, sufix }, reply, called);
7
+ const query = {
8
+ filter, limit, page, search, sql, state, order,
9
+ };
10
+ const result = await routeData({
11
+ pg, headers, params, query, user, contextQuery, sufix,
12
+ }, reply, called);
7
13
  return result;
8
14
  }
@@ -0,0 +1,11 @@
1
+ export default function flattenObject(obj = {}, keys, parent = '') {
2
+ return Object.keys(obj).reduce((acc, key) => {
3
+ const newKey = parent ? `${parent}.${key}` : key;
4
+ if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
5
+ Object.assign(acc, flattenObject(obj[key], keys, newKey));
6
+ } else if (keys ? keys.includes(newKey) : true) {
7
+ acc[newKey] = obj[key];
8
+ }
9
+ return acc;
10
+ }, {});
11
+ }
@@ -0,0 +1,14 @@
1
+ export default function unflattenObject(flatObj) {
2
+ return Object.keys(flatObj).reduce((acc, key) => {
3
+ const keys = key.split('.');
4
+ keys.reduce((nestedObj, part, index) => {
5
+ if (index === keys.length - 1) {
6
+ nestedObj[part] = flatObj[key];
7
+ } else {
8
+ nestedObj[part] = nestedObj[part] || {};
9
+ }
10
+ return nestedObj[part];
11
+ }, acc);
12
+ return acc;
13
+ }, {});
14
+ }
@@ -17,21 +17,31 @@ export default async function deleteCrud(req, reply) {
17
17
 
18
18
  const { referer } = headers;
19
19
  const tokenData = await getToken({
20
- uid: user.uid, token: params.table, json: 1,
20
+ uid: user.uid, token: params.id || params.table, json: 1,
21
21
  });
22
22
 
23
- const { table: del, id } = hookData || tokenData || (config.auth?.disable ? req.params : {});
23
+ const { table: del, id } = hookData || tokenData || (config.security?.disableToken || config.local || config.auth?.disable ? req.params : {});
24
24
  const { actions = [] } = await getAccess({ table: del, id, user }, pg) || {};
25
25
 
26
- if (!actions.includes('del') && !config?.local && !tokenData) {
26
+ if (!tokenData && !config?.local && !config.security?.disableToken && !config.auth?.disable) {
27
+ return reply.status(400).send('invalid token');
28
+ }
29
+
30
+ if (!actions.includes('del') && !config?.local) {
27
31
  return reply.status(403).send('access restricted');
28
32
  }
33
+
29
34
  const loadTemplate = await getTemplate('table', del);
30
35
 
31
36
  const { table } = loadTemplate || hookData || tokenData || req.params || {};
32
37
 
33
- if (!table) reply.status(404).send('table is required');
34
- if (!id) reply.status(404).send('id is required');
38
+ if (!table) {
39
+ return reply.status(404).send('table is required');
40
+ }
41
+
42
+ if (!id) {
43
+ return reply.status(404).send('id is required');
44
+ }
35
45
 
36
46
  const data = await dataDelete({
37
47
  pg, table, id, uid: user?.uid, tokenData, referer,
@@ -19,15 +19,20 @@ export default async function insert(req, reply) {
19
19
  }
20
20
 
21
21
  const { referer } = headers;
22
+
22
23
  const tokenData = await getToken({
23
24
  uid: user?.uid, token: params.table, mode: 'a', json: 1,
24
25
  });
25
26
 
26
- const { form, table: add } = hookData || tokenData || (config.auth?.disable ? req.params : {});
27
+ const { form, table: add } = hookData || tokenData || (config.security?.disableToken || config.local || config.auth?.disable ? req.params : {});
27
28
 
28
29
  const { actions = [] } = await getAccess({ table: add, user }, pg) || {};
29
30
 
30
- if (!actions.includes('add') && !config.local && !config.debug && !tokenData) {
31
+ if (!tokenData && !config.local && !config.security?.disableToken && !config.auth?.disable) {
32
+ return reply.status(400).send('invalid token');
33
+ }
34
+
35
+ if (!actions.includes('add') && !config.local) {
31
36
  return reply.status(403).send('access restricted');
32
37
  }
33
38
 
@@ -37,8 +42,9 @@ export default async function insert(req, reply) {
37
42
 
38
43
  const loadTemplate = await getTemplate('table', add);
39
44
  const { table } = loadTemplate || hookData || tokenData || req.params || {};
45
+
40
46
  if (!table) {
41
- return { message: 'table not found', status: 404 };
47
+ return reply.status(404).send('table not found');
42
48
  }
43
49
 
44
50
  const formData = form || loadTemplate?.form ? (await getTemplate('form', form || loadTemplate?.form) || {}) : {};
@@ -110,5 +116,5 @@ export default async function insert(req, reply) {
110
116
  }
111
117
 
112
118
  const pk = pg.pk?.[loadTemplate?.table || table];
113
- return { id: res?.rows?.[0]?.[pk], rows: res.rows, extra: res.extra };
119
+ return reply.status(200).send({ id: res?.rows?.[0]?.[pk], rows: res.rows, extra: res.extra });
114
120
  }
@@ -4,7 +4,7 @@ import {
4
4
 
5
5
  import extraDataGet from '../../../plugins/extra/extraDataGet.js';
6
6
 
7
- export default async function tableAPI(req) {
7
+ export default async function tableAPI(req, reply) {
8
8
  const {
9
9
  pg = pgClients.client, params, user = {}, query = {},
10
10
  } = req;
@@ -38,7 +38,11 @@ export default async function tableAPI(req) {
38
38
 
39
39
  const { actions = [], query: accessQuery } = await getAccess({ table: templateName, id, user }, pg) || {};
40
40
 
41
- if (!actions.includes('edit') && !config?.local && !tokenData) {
41
+ if (!tokenData && !config?.local && !config.security?.disableToken) {
42
+ return reply.status(400).send('invalid token');
43
+ }
44
+
45
+ if (!actions.includes('edit') && !config?.local) {
42
46
  return reply.status(403).send('access restricted');
43
47
  }
44
48
 
@@ -94,7 +98,7 @@ export default async function tableAPI(req) {
94
98
  Object.assign(data, { [key]: extraRows });
95
99
  }));
96
100
  }
97
- if (user?.uid && actions?.includes?.('edit')) {
101
+ if (user?.uid && !config.security?.disableToken && actions.includes('edit')) {
98
102
  data.token = tokenData?.table ? params.table : setToken({
99
103
  ids: [JSON.stringify({ id, table: tableName, form: loadTable.form })],
100
104
  uid: user.uid,
@@ -23,14 +23,18 @@ export default async function update(req, reply) {
23
23
 
24
24
  const { referer } = headers;
25
25
  const tokenData = await getToken({
26
- uid: user.uid, token: body.token || params.table, mode: 'w', json: 1,
26
+ uid: user.uid, token: body.token || params.id || params.table, mode: 'w', json: 1,
27
27
  });
28
28
 
29
- const { form, table: edit, id } = hookData || tokenData || (config.auth?.disable ? params : {});
29
+ const { form, table: edit, id } = hookData || tokenData || (config.security?.disableToken || config.local || config.auth?.disable ? params : {});
30
30
 
31
31
  const { actions = [] } = await getAccess({ table: edit, id, user }, pg) || {};
32
32
 
33
- if (!actions.includes('edit') && !config.local && !config.debug && !tokenData) {
33
+ if (!tokenData && !config.local && !config.security?.disableToken && !config.auth?.disable) {
34
+ return reply.status(400).send('invalid token');
35
+ }
36
+
37
+ if (!actions.includes('edit') && !config.local) {
34
38
  return reply.status(403).send('access restricted');
35
39
  }
36
40
 
@@ -114,5 +118,5 @@ export default async function update(req, reply) {
114
118
  }));
115
119
  }
116
120
 
117
- return res;
121
+ return reply.status(200).send(res);
118
122
  }
@@ -5,7 +5,7 @@ import { join } from 'node:path';
5
5
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
6
6
 
7
7
  import {
8
- menuDirs, pgClients, applyHook, config,
8
+ menuDirs, pgClients, applyHook, config,
9
9
  } from '../../../../utils.js';
10
10
 
11
11
  const menuCache = [];
@@ -63,7 +63,7 @@ export default async function adminMenu({
63
63
  };
64
64
  await applyHook('userMenu', result);
65
65
 
66
- if (session && user?.uid && !user.user_type?.includes?.('admin') && !user.type?.includes?.('admin') && pg.pk['admin.role_access']) {
66
+ if (session && user?.uid && !user.user_type?.includes?.('admin') && !user.type?.includes?.('admin') && pg.pk?.['admin.role_access']) {
67
67
  const { type, gl = [], routes = [] } = await pg.query(`select user_type as type, b.gl,routes from admin.users a
68
68
  left join lateral (
69
69
  select array_agg(role_id) as gl from admin.user_roles
@@ -2,10 +2,9 @@
2
2
  import path from 'node:path';
3
3
 
4
4
  import {
5
- getAccess, handlebars, setOpt, setToken, getTemplate, handlebarsSync, applyHook,
5
+ config, getAccess, handlebars, setOpt, setToken, getTemplate, handlebarsSync, applyHook, getData,
6
6
  } from '../../../../utils.js';
7
7
 
8
- import getTableData from './tableData.js';
9
8
  import conditions from './utils/conditions.js';
10
9
 
11
10
  const components = {
@@ -15,8 +14,9 @@ const components = {
15
14
 
16
15
  export default async function getCardData(req, reply) {
17
16
  const {
18
- pg, params = {}, session = {}, user = {},
17
+ pg, headers, params = {}, user = {},
19
18
  } = req;
19
+
20
20
  const { table, id } = params;
21
21
  const { uid } = user;
22
22
 
@@ -36,15 +36,21 @@ export default async function getCardData(req, reply) {
36
36
 
37
37
  const index = template?.find(el => el[0] === 'index.yml')?.[1] || {};
38
38
 
39
- const { message, rows = [] } = index.table && index.query
39
+ const result = index.table && index.query
40
40
  ? await pg.query(
41
41
  `select * from ${index.table} where ${handlebarsSync.compile(index.query)({ uid, user })}`,
42
42
  )
43
- : await getTableData({
44
- pg, params: { table, id }, session, user,
45
- });
43
+ : await getData({
44
+ pg, table, id, user, headers,
45
+ }, reply);
46
+
47
+ if (result?.message) return result?.message;
48
+
49
+ if (!result?.rows?.length) {
50
+ return reply.status(403).send('access restricted: empty rows');
51
+ }
46
52
 
47
- if (message) return { message };
53
+ const { rows = [] } = result;
48
54
 
49
55
  // conditions
50
56
  index.panels?.filter(el => el.items).forEach(el1 => {
@@ -72,7 +78,7 @@ export default async function getCardData(req, reply) {
72
78
 
73
79
  // tokens result
74
80
  const tokens = {};
75
- if (index?.tokens && typeof index?.tokens === 'object' && !Array.isArray(index?.tokens)) {
81
+ if (!config.security?.disableToken && index?.tokens && typeof index?.tokens === 'object' && !Array.isArray(index?.tokens)) {
76
82
  Object.keys(index.tokens || {})
77
83
  .filter(key => index?.tokens[key]?.public
78
84
  || access.actions?.includes?.('edit')
@@ -6,7 +6,7 @@ import getData from '../functions/getData.js';
6
6
 
7
7
  export default async function getTableData(req, reply, called) {
8
8
  const {
9
- user = {}, params = {}, query = {}, pg = pgClients.client, contextQuery: contextQuery1, sufix = true,
9
+ user = {}, params = {}, headers = {}, query = {}, pg = pgClients.client, contextQuery: contextQuery1, sufix = true,
10
10
  } = req;
11
11
 
12
12
  const { id } = params || {};
@@ -28,7 +28,7 @@ export default async function getTableData(req, reply, called) {
28
28
  }
29
29
 
30
30
  const resp = await getData({
31
- pg, params: { id, table: tokenData.table }, query, user, contextQuery: [contextQuery1, tokenData.query].filter(Boolean).join(' and '), sufix,
31
+ pg, params: { id, table: tokenData.table }, headers, query, user, contextQuery: [contextQuery1, tokenData.query].filter(Boolean).join(' and '), sufix,
32
32
  }, reply, called);
33
33
  if (resp?.addToken && tokenData.obj) { Object.assign(resp, { addToken: params.table }); }
34
34
  return resp;
@@ -50,7 +50,7 @@ export default async function getTableData(req, reply, called) {
50
50
  const contextQuery = [contextQuery1, interfaceQuery, context].filter(Boolean).join(' and ') || ' 2=2 ';
51
51
 
52
52
  const res = await getData({
53
- pg, params, query, user, contextQuery, sufix,
53
+ pg, params, query, headers, user, contextQuery, sufix,
54
54
  }, reply, called);
55
55
 
56
56
  return res;
@@ -19,7 +19,7 @@ const defaultLimit = 20;
19
19
 
20
20
  export default async function dataAPI(req, reply, called) {
21
21
  const {
22
- pg = pgClients.client, params, query = {}, user = {}, contextQuery, sufix = true,
22
+ pg = pgClients.client, params, headers = {}, query = {}, user = {}, contextQuery, sufix = true,
23
23
  } = req;
24
24
 
25
25
  const time = Date.now();
@@ -186,9 +186,13 @@ export default async function dataAPI(req, reply, called) {
186
186
  throw new Error(err.toString());
187
187
  });
188
188
 
189
+ if (!rows.length && headers?.referer?.includes?.('/card/')) {
190
+ return reply.status(403).send('access restricted: empty rows');
191
+ }
192
+
189
193
  timeArr.push(Date.now());
190
194
 
191
- if (uid && rows.length && editable) {
195
+ if (uid && rows.length && !config.security?.disableToken && (editable || actions.includes('edit') || actions.includes('del'))) {
192
196
  rows.forEach(row => {
193
197
  row.token = setToken({
194
198
  ids: [JSON.stringify({ id: row.id, table: tokenData?.table || hookData?.table || params.table, form: loadTable?.form })],
@@ -245,7 +249,7 @@ export default async function dataAPI(req, reply, called) {
245
249
  const tokens = {};
246
250
  if (template && objectId) {
247
251
  // tokens result
248
- if (index?.tokens && typeof index?.tokens === 'object' && !Array.isArray(index?.tokens)) {
252
+ if (!config.security?.disableToken && index?.tokens && typeof index?.tokens === 'object' && !Array.isArray(index?.tokens)) {
249
253
  Object.keys(index.tokens || {})
250
254
  .filter(key => index?.tokens[key]?.public
251
255
  || actions?.includes?.('edit')
@@ -347,7 +351,7 @@ export default async function dataAPI(req, reply, called) {
347
351
  }
348
352
 
349
353
  // console.log({ add: loadTable.table, form: loadTable.form });
350
- if (uid && actions.includes('add')) {
354
+ if (uid && !config.security?.disableToken && actions.includes('add')) {
351
355
  const addTokens = setToken({
352
356
  ids: [
353
357
  JSON.stringify({
package/utils.js CHANGED
@@ -83,6 +83,9 @@ import json2yml from './server/plugins/yml/funcs/json2yml.js';
83
83
  import formatMdoc from './server/plugins/md/funcs/formatMdoc.js';
84
84
  import mdToHTML from './server/plugins/md/funcs/mdToHTML.js';
85
85
 
86
+ import flattenObject from './server/plugins/util/funcs/flattenObject.js';
87
+ import unflattenObject from './server/plugins/util/funcs/unflattenObject.js';
88
+
86
89
  export default null;
87
90
  export {
88
91
  config,
@@ -160,4 +163,7 @@ export {
160
163
 
161
164
  formatMdoc,
162
165
  mdToHTML,
166
+
167
+ flattenObject,
168
+ unflattenObject,
163
169
  };