@opengis/fastify-table 1.1.16 → 1.1.18

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/Changelog.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # fastify-table
2
2
 
3
+ ## 1.1.18 - 01.10.2024
4
+
5
+ - custom hook support - addHook, applyHook
6
+
7
+ ## 1.1.17 - 30.09.2024
8
+
9
+ - add relkinds to pg
10
+
3
11
  ## 1.1.16 - 27.09.2024
4
12
 
5
13
  - add migrations
@@ -0,0 +1,8 @@
1
+ import hookList from './hookList.js';
2
+
3
+ export default function addHook(name, fn) {
4
+ if (!hookList[name]) {
5
+ hookList[name] = [];
6
+ }
7
+ hookList[name].push(fn);
8
+ }
@@ -0,0 +1,24 @@
1
+ import config from '../../config.js';
2
+ import hookList from './hookList.js';
3
+
4
+ export default async function applyHook(name, data) {
5
+ const debug = config.local || config.debug;
6
+ if (debug) console.log('applyHook', name);
7
+ if (!hookList[name]?.length) return null;
8
+ const result = {};
9
+ await Promise.all(hookList[name].map(async (hook) => {
10
+ const hookData = await hook({ ...data, config });
11
+ if (hookData) {
12
+ if (debug) console.log('applyHook', name, hookData);
13
+ Object.assign(result, hookData);
14
+ }
15
+ })).catch((err) => {
16
+ console.error('applyHook', name, err.toString());
17
+ });
18
+
19
+ if (Object.keys(result).length) {
20
+ return result;
21
+ }
22
+
23
+ return null;
24
+ }
@@ -0,0 +1 @@
1
+ export default {};
package/hook/index.js ADDED
@@ -0,0 +1,6 @@
1
+ import addHook from './funcs/addHook.js';
2
+ import applyHook from './funcs/applyHook.js';
3
+
4
+ export default async function plugin(fastify, opts) {
5
+ fastify.decorate('hook', { add: addHook, apply: applyHook });
6
+ }
package/index.js CHANGED
@@ -14,6 +14,7 @@ import crudPlugin from './crud/index.js';
14
14
  import policyPlugin from './policy/index.js';
15
15
  import utilPlugin from './util/index.js';
16
16
  import cronPlugin from './cron/index.js';
17
+ import hookPlugin from './hook/index.js';
17
18
 
18
19
  import pgClients from './pg/pgClients.js';
19
20
 
@@ -92,6 +93,7 @@ async function plugin(fastify, opt) {
92
93
  widgetPlugin(fastify, opt);
93
94
  utilPlugin(fastify, opt);
94
95
  cronPlugin(fastify, opt);
96
+ hookPlugin(fastify, opt);
95
97
  }
96
98
  export default fp(plugin);
97
99
  // export { rclient };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/fastify-table",
3
- "version": "1.1.16",
3
+ "version": "1.1.18",
4
4
  "type": "module",
5
5
  "description": "core-plugins",
6
6
  "main": "index.js",
@@ -21,7 +21,9 @@ export default async function getMeta(opt) {
21
21
 
22
22
  const geomAttr = fields.find((el) => pg.pgType[el.dataTypeID] === 'geometry')?.name; // change geometry text to geometry code
23
23
 
24
- const res = { pk, columns: fields, geom: geomAttr };
24
+ const res = {
25
+ pk, columns: fields, geom: geomAttr, view: pg.relkinds?.[table] === 'v',
26
+ };
25
27
  data[table] = res;
26
28
  return res;
27
29
  }
package/pg/funcs/init.js CHANGED
@@ -14,6 +14,10 @@ async function init(client) {
14
14
  const tlist = await client.query(`select array_agg((select nspname from pg_namespace where oid=relnamespace)||'.'||relname) tlist
15
15
  from pg_class where relkind in ('r','v')`).then((d) => d.rows[0].tlist);
16
16
 
17
+ const { rows = [] } = await client.query(`select (select nspname from pg_namespace where oid=relnamespace)||'.'||relname as tname, relkind
18
+ from pg_class where relkind in ('r','v')`);
19
+ const relkinds = rows.reduce((acc, curr) => Object.assign(acc, { [curr.tname]: curr.relkind }), {});
20
+
17
21
  async function one(query, param = {}) {
18
22
  const data = await client.query(query, Array.isArray(param) ? param : param.args || []);
19
23
  const result = ((Array.isArray(data) ? data.pop() : data)?.rows || [])[0] || {};
@@ -29,7 +33,8 @@ async function init(client) {
29
33
  try {
30
34
  result = await clientCb.query(query, args);
31
35
  clientCb.end();
32
- } catch (err) {
36
+ }
37
+ catch (err) {
33
38
  clientCb.end();
34
39
  cb(err.toString(), 1);
35
40
  throw err;
@@ -51,7 +56,7 @@ async function init(client) {
51
56
  }
52
57
 
53
58
  Object.assign(client, {
54
- one, pgType, pk, tlist, queryCache, queryNotice,
59
+ one, pgType, pk, tlist, relkinds, queryCache, queryNotice,
55
60
  });
56
61
  }
57
62
 
@@ -6,22 +6,35 @@ import getAccess from '../../crud/funcs/getAccess.js';
6
6
  import setToken from '../../crud/funcs/setToken.js';
7
7
  import gisIRColumn from './utils/gisIRColumn.js';
8
8
 
9
+ import { applyHook } from '../../utils.js';
10
+
9
11
  const maxLimit = 100;
10
- export default async function dataAPI({
11
- pg, params, funcs = {}, query = {}, opt = {}, uid: uid1, req, session,
12
- }) {
12
+ export default async function dataAPI(req) {
13
+ const {
14
+ pg, params, funcs = {}, query = {}, opt = {}, uid: uid1, session,
15
+ } = req;
13
16
  const time = Date.now();
14
17
 
15
18
  const uid = session?.passport?.user?.uid || uid1 || query.uid || 0;
16
19
 
17
20
  const loadTable = await getTemplate('table', params.table);
18
21
 
22
+ const check = await applyHook('preData', { req });
23
+ if (check?.message && check?.status) {
24
+ return { message: check?.message, status: check?.status };
25
+ }
26
+
19
27
  if (!loadTable) { return { message: 'template not found', status: 404 }; }
20
28
 
21
29
  const {
22
30
  table, columns, sql, cardSql, filters, form, meta, sqlColumns, ispublic,
23
31
  } = loadTable;
24
- const { pk, columns: dbColumns = [] } = await getMeta(table);
32
+ const tableMeta = await getMeta(table);
33
+ if (tableMeta?.view) {
34
+ if (!loadTable?.key) return { message: `key not found: ${table}`, status: 404 };
35
+ Object.assign(tableMeta, { pk: loadTable?.key });
36
+ }
37
+ const { pk, columns: dbColumns = [] } = tableMeta || {};
25
38
 
26
39
  if (!pk) return { message: `table not found: ${table}`, status: 404 };
27
40
 
@@ -99,5 +112,7 @@ export default async function dataAPI({
99
112
  });
100
113
  }
101
114
 
102
- return res;
115
+ const result = await applyHook('afterData', { req, table: loadTable.table, res });
116
+
117
+ return result || res;
103
118
  }
@@ -1,9 +1,11 @@
1
1
  import getTemplate from './utils/getTemplate.js';
2
2
  import getMeta from '../../pg/funcs/getMeta.js';
3
3
 
4
+ import { applyHook } from '../../utils.js';
5
+
4
6
  export default async function tableAPI(req) {
5
7
  const {
6
- pg, params = {}, query = {}, opt = {},
8
+ pg, params = {}, query = {}, opt = {}, funcs,
7
9
  } = req;
8
10
  if (!params.id) return { message: 'not enough params', status: 400 };
9
11
 
@@ -12,6 +14,11 @@ export default async function tableAPI(req) {
12
14
  if (!pg.pk?.[opt?.table || params.table]) { return { message: 'not found', status: 404 }; }
13
15
  }
14
16
 
17
+ const check = await applyHook('preTable', { req });
18
+ if (check?.message && check?.status) {
19
+ return { message: check?.message, status: check?.status };
20
+ }
21
+
15
22
  const {
16
23
  table, /* columns, */ form,
17
24
  } = loadTable;
@@ -48,5 +55,6 @@ export default async function tableAPI(req) {
48
55
  }));
49
56
  }
50
57
 
51
- return rows?.[0] || {};
58
+ const res = await applyHook('afterTable', { req, table: loadTable?.table, rows });
59
+ return res || rows?.[0] || {};
52
60
  }
@@ -0,0 +1,96 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import path from 'node:path';
4
+ import pgClients from '../../pg/pgClients.js';
5
+ import init from '../../pg/funcs/init.js';
6
+
7
+ import build from '../../helper.js';
8
+ import config from '../config.js';
9
+
10
+ import { addTemplateDir, addHook } from '../../utils.js';
11
+
12
+ const prefix = config.prefix || '/api';
13
+ const table = 'gis.dataset.table';
14
+
15
+ test('applyHook to API data/table', async (t) => {
16
+ const app = await build(t);
17
+ const cwd = process.cwd();
18
+ await init(pgClients.client);
19
+ addTemplateDir(path.join(cwd, 'test/templates'));
20
+
21
+ addHook('preData', async ({ req }) => {
22
+ if (req.params?.table === `${table}1`) {
23
+ return { message: 'access restricted by hook', status: 403 };
24
+ }
25
+ return null;
26
+ });
27
+ addHook('preTable', async ({ req }) => {
28
+ const { params = {} } = req;
29
+ if (params?.table === `${table}1`) {
30
+ return { message: 'access restricted by hook', status: 403 };
31
+ }
32
+ return null;
33
+ });
34
+
35
+ addHook('afterData', async ({ res }) => {
36
+ Object.assign(res, { test: '1' });
37
+ });
38
+ addHook('afterTable', async ({ rows = [] }) => {
39
+ Object.assign(rows[0], { count: 1 });
40
+ });
41
+
42
+ await t.test('GET /data (preData ok)', async () => {
43
+ const res = await app.inject({
44
+ method: 'GET',
45
+ url: `${prefix}/data/${table}`,
46
+ });
47
+ const json = res.json();
48
+ assert.ok(json?.time, 'api error');
49
+ });
50
+ await t.test('GET /data (preData message)', async () => {
51
+ const res = await app.inject({
52
+ method: 'GET',
53
+ url: `${prefix}/data/${table}1`,
54
+ });
55
+ const json = res.json();
56
+ assert.ok(json.message === 'access restricted by hook', 'preData hook error');
57
+ });
58
+
59
+ await t.test('GET /data (afterData)', async () => {
60
+ const res = await app.inject({
61
+ method: 'GET',
62
+ url: `${prefix}/data/${table}`,
63
+ });
64
+ const json = res.json();
65
+ assert.ok(json?.test === '1', 'afterData hook error');
66
+ });
67
+
68
+ const { id = '1' } = pgClients.client?.pk['gis.dataset']
69
+ ? await pgClients.client.query('select dataset_id as id from gis.dataset limit 1')
70
+ .then((res) => res.rows?.[0] || {}) : {};
71
+ await t.test('GET /table/:id (preTable ok)', async () => {
72
+ const res = await app.inject({
73
+ method: 'GET',
74
+ url: `${prefix}/table/${table}1/${id}`,
75
+ });
76
+ const json = res.json();
77
+ assert.ok(json.message === 'access restricted by hook', 'preTable hook error');
78
+ });
79
+ await t.test('GET /table/:id (preTable message)', async () => {
80
+ const res = await app.inject({
81
+ method: 'GET',
82
+ url: `${prefix}/table/${table}1/${id}`,
83
+ });
84
+ const json = res.json();
85
+ assert.ok(json.message === 'access restricted by hook', 'preTable hook error');
86
+ });
87
+
88
+ await t.test('GET /table/:id (afterTable)', async () => {
89
+ const res = await app.inject({
90
+ method: 'GET',
91
+ url: `${prefix}/table/${table}/${id}`,
92
+ });
93
+ const json = res.json();
94
+ assert.equal(json?.count, 1);
95
+ });
96
+ });
package/utils.js CHANGED
@@ -24,6 +24,8 @@ import gisIRColumn from './table/controllers/utils/gisIRColumn.js';
24
24
  import getMeta from './pg/funcs/getMeta.js';
25
25
  import getAccess from './crud/funcs/getAccess.js';
26
26
  import getSelectVal from './table/funcs/metaFormat/getSelectVal.js';
27
+ import applyHook from './hook/funcs/applyHook.js';
28
+ import addHook from './hook/funcs/addHook.js';
27
29
 
28
30
  export default null;
29
31
  export {
@@ -47,4 +49,6 @@ export {
47
49
  getMeta,
48
50
  getAccess,
49
51
  getSelectVal,
52
+ applyHook,
53
+ addHook,
50
54
  };