@opengis/fastify-table 1.4.19 → 1.4.20

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/fastify-table",
3
- "version": "1.4.19",
3
+ "version": "1.4.20",
4
4
  "type": "module",
5
5
  "description": "core-plugins",
6
6
  "keywords": [
@@ -120,6 +120,7 @@ ALTER TABLE crm.extra_data ADD COLUMN IF NOT EXISTS property_entity text;
120
120
  ALTER TABLE crm.extra_data ADD COLUMN IF NOT EXISTS object_id text;
121
121
  ALTER TABLE crm.extra_data ADD COLUMN IF NOT EXISTS value_text text;
122
122
  ALTER TABLE crm.extra_data ADD COLUMN IF NOT EXISTS value_date timestamp without time zone;
123
+ ALTER TABLE crm.extra_data ADD COLUMN IF NOT EXISTS value_array text[];
123
124
  ALTER TABLE crm.extra_data ADD COLUMN IF NOT EXISTS uid text;
124
125
  ALTER TABLE crm.extra_data ALTER COLUMN uid SET DEFAULT '1'::text;
125
126
  ALTER TABLE crm.extra_data ADD COLUMN IF NOT EXISTS cdate timestamp without time zone;
@@ -1,17 +1,18 @@
1
1
  import getPG from '../../pg/funcs/getPG.js';
2
- import getMeta from '../../pg/funcs/getMeta.js';
3
2
  import getRedis from '../../redis/funcs/getRedis.js';
4
3
  import pgClients from '../../pg/pgClients.js';
4
+ import getTemplate from '../../table/funcs/getTemplate.js';
5
5
  import config from '../../../../config.js';
6
6
 
7
7
  import logChanges from './utils/logChanges.js';
8
+ import getInsertQuery from './utils/getInsertQuery.js';
8
9
  import logger from '../../logger/getLogger.js';
9
10
  import extraData from '../../extra/extraData.js';
10
11
 
11
12
  const rclient = getRedis();
12
13
 
13
14
  export default async function dataInsert({
14
- id, table, tokenData, referer, data, pg: pg1, uid,
15
+ id, table: table1, referer, data, pg: pg1, uid, tokenData = {},
15
16
  }) {
16
17
  const pg = pg1 || getPG({ name: 'client' });
17
18
 
@@ -24,63 +25,90 @@ export default async function dataInsert({
24
25
  pg.pk = pgClients.client?.pk;
25
26
  }
26
27
 
27
- if (!data) return null;
28
- const { columns } = await getMeta({ pg, table });
29
- if (!columns) return null;
30
-
31
- const names = columns.map((el) => el.name);
32
-
33
- Object.assign(data, {
34
- ...(id && pg.pk?.[table] ? { [pg.pk?.[table]]: id } : {}),
35
- ...(table !== 'admin.users' ? { uid } : {}),
36
- editor_id: uid,
37
- created_by: uid,
38
- updated_by: uid,
39
- // editor_id: uid,
40
- });
41
-
42
- const systemColumns = ['cdate', 'editor_date', 'created_at', 'updated_at'].filter((el) => names.includes(el)).map((el) => [el, 'now()']);
43
- const systemColumnNames = systemColumns.map(el => el[0]);
44
-
45
- const filterData = Object.keys(data)
46
- .filter((el) => !systemColumnNames.includes(el) && (typeof data[el] === 'boolean' ? true : data[el]) && names.includes(el)).map((el) => [el, data[el]]);
47
-
48
- const insertQuery = `insert into ${table}
49
-
50
- ( ${filterData?.map((key) => `"${key[0]}"`).concat(systemColumnNames).join(',')})
51
-
52
- values (${filterData?.map((key, i) => (key[0] === 'geom' ? `st_setsrid(st_geomfromgeojson($${i + 1}::json),4326)` : `$${i + 1}`)).concat(systemColumns.map((el) => el[1])).join(',')})
53
-
54
- returning *`;
55
-
56
- const res = await pg.query(insertQuery, [...filterData.map((el) => (typeof el[1] === 'object' && (!Array.isArray(el[1]) || typeof el[1]?.[0] === 'object') ? JSON.stringify(el[1]) : el[1]))])
57
- .catch(err => {
58
- logger.file('crud/insert', {
59
- error: err.toString(), stack: err.stack, table, id, referer, uid, q: insertQuery,
60
- });
61
- throw err;
62
- }).then(el => el || {});
63
-
64
- const table1 = pg.pk[table] ? table : table.replace(/"/g, '');
65
-
66
- const extraRes = await extraData({
67
- table, form: tokenData?.form, id: res.rows?.[0]?.[pg.pk[table1]], data, uid,
68
- }, pg);
69
- if (extraRes && res?.rows?.[0]) {
70
- Object.assign(res.rows[0], { ...extraRes });
28
+ const { unittest } = tokenData || {};
29
+
30
+ if (config.trace || unittest) console.log('form', tokenData?.form);
31
+
32
+ const table = pg.pk[table1] ? table1 : table1.replace(/"/g, '');
33
+
34
+ const { insertQuery, args = [] } = await getInsertQuery({
35
+ pg, table, data, id, uid,
36
+ }) || {};
37
+
38
+ if (!insertQuery || !args.length) return null;
39
+
40
+ // for transactions
41
+ const client = pg?._connected ? pg : await pg.connect();
42
+
43
+ client.options = pg?.options;
44
+ client.tlist = pg?.tlist;
45
+ client.pgType = pg?.pgType;
46
+ client.relkinds = pg?.relkinds;
47
+ client.pk = pg?.pk;
48
+
49
+ try {
50
+ await client.query('begin;');
51
+ const res = await client.query(insertQuery, args).then(el => el || {});
52
+
53
+ const id1 = res.rows?.[0]?.[pg.pk[table1]];
54
+
55
+ if (!id1) return null;
56
+
57
+ await extraData({
58
+ table,
59
+ form: tokenData?.form,
60
+ id: id1,
61
+ data,
62
+ uid,
63
+ row: res.rows[0],
64
+ }, client);
65
+
66
+ // foreign key dataTable (table + parent_id)
67
+ const formData = tokenData?.form ? (await getTemplate('form', tokenData.form) || {}) : {};
68
+ const schema = formData?.schema || formData;
69
+ const parentKeys = Object.keys(schema || {})?.filter((key) => data[key]?.length && Array.isArray(data[key]) && schema?.[key]?.table && schema?.[key]?.parent_id);
70
+
71
+ if (parentKeys?.length) {
72
+ await Promise.all(parentKeys?.map(async (key) => {
73
+ const parentKey = schema[key].parent_id;
74
+ const objId = data[parentKey] || data?.id || res?.rows?.[0]?.[parentKey] || id1;
75
+ const parentRows = await Promise.all(data[key].map(async (row) => {
76
+ Object.assign(row, { [parentKey]: objId });
77
+ const parentRes = await getInsertQuery({
78
+ pg: client, table: schema[key].table, data: row, uid,
79
+ }) || {};
80
+ if (!parentRes?.insertQuery || !parentRes?.args?.length) return null;
81
+
82
+ const { rows = [] } = await client.query(parentRes.insertQuery, parentRes.args);
83
+ return rows[0];
84
+ }));
85
+ Object.assign(res.rows[0], { [key]: parentRows.filter(Boolean) });
86
+ }));
87
+ }
88
+
89
+ await logChanges({
90
+ pg: client,
91
+ table,
92
+ tokenData,
93
+ referer,
94
+ data,
95
+ id: id1,
96
+ uid,
97
+ type: 'INSERT',
98
+ });
99
+
100
+ if (config.redis) { rclient.incr(`pg:${table}:crud`); }
101
+ await client.query('commit;');
102
+ return res;
103
+ }
104
+ catch (err) {
105
+ logger.file('crud/insert', {
106
+ error: err.toString(), stack: err.stack, table, id, referer, uid, form: tokenData?.form,
107
+ });
108
+ await client.query('rollback;');
109
+ throw err;
110
+ }
111
+ finally {
112
+ client.release();
71
113
  }
72
-
73
- await logChanges({
74
- pg,
75
- table,
76
- tokenData,
77
- referer,
78
- data,
79
- id: res.rows?.[0]?.[pg.pk[table1]],
80
- uid,
81
- type: 'INSERT',
82
- });
83
-
84
- if (config.redis) { rclient.incr(`pg:${table}:crud`); }
85
- return res;
86
114
  }
@@ -2,11 +2,13 @@ import getPG from '../../pg/funcs/getPG.js';
2
2
  import getMeta from '../../pg/funcs/getMeta.js';
3
3
  import getRedis from '../../redis/funcs/getRedis.js';
4
4
  import pgClients from '../../pg/pgClients.js';
5
+ import getTemplate from '../../table/funcs/getTemplate.js';
5
6
  import config from '../../../../config.js';
6
7
 
7
8
  import extraData from '../../extra/extraData.js';
8
9
  import logChanges from './utils/logChanges.js';
9
10
  import logger from '../../logger/getLogger.js';
11
+ import getInsertQuery from './utils/getInsertQuery.js';
10
12
 
11
13
  const rclient = getRedis();
12
14
  const srids = {};
@@ -71,41 +73,89 @@ export default async function dataUpdate({
71
73
  ${filterData?.map((key, i) => assignValue(key, i, srids[table]?.[key] || 4326, pg.pgType?.[columns.find(col => col.name === key)?.dataTypeID || '']))?.join(',')}
72
74
  WHERE ${pk}::text = $1::text returning *`;
73
75
  // console.log(updateQuery, filterValue);
74
- const res = await pg.query(updateQuery, [id, ...filterValue])
75
- .catch(err => {
76
- logger.file('crud/update', {
77
- error: err.toString(),
78
- stack: err.stack,
79
- table,
80
- id,
81
- referer,
82
- uid,
83
- data,
84
- q: updateQuery,
85
- });
86
- throw err;
87
- })
88
- .then(el => el?.rows?.[0]) || {};
89
-
90
- const extraRes = await extraData({
91
- table,
92
- form: tokenData?.form,
93
- id,
94
- data,
95
- uid,
96
- }, pg);
97
-
98
- await logChanges({
99
- pg,
100
- table,
101
- tokenData,
102
- referer,
103
- data,
104
- id,
105
- uid,
106
- type: 'UPDATE',
107
- });
108
-
109
- if (config.redis) { rclient.incr(`pg:${table}:crud`); }
110
- return { ...res, ...extraRes || {} };
76
+
77
+ // for transactions
78
+ const client = pg?._connected ? pg : await pg.connect();
79
+
80
+ try {
81
+ await client.query('begin;');
82
+ const res = await client.query(updateQuery, [id, ...filterValue])
83
+ .catch(err => {
84
+ logger.file('crud/update', {
85
+ error: err.toString(),
86
+ stack: err.stack,
87
+ table,
88
+ id,
89
+ referer,
90
+ uid,
91
+ data,
92
+ q: updateQuery,
93
+ });
94
+ throw err;
95
+ })
96
+ .then(el => el?.rows?.[0]) || {};
97
+
98
+ await extraData({
99
+ table,
100
+ form: tokenData?.form,
101
+ id,
102
+ data,
103
+ uid,
104
+ row: res,
105
+ }, client);
106
+
107
+ // foreign key dataTable (table + parent_id)
108
+ const formData = tokenData?.form ? (await getTemplate('form', tokenData.form) || {}) : {};
109
+ const schema = formData?.schema || formData;
110
+
111
+ const parentKeys = Object.keys(schema || {})?.filter((key) => Array.isArray(data[key]) && schema?.[key]?.table && schema?.[key]?.parent_id /* && body[key].length */);
112
+ if (parentKeys?.length) {
113
+ await Promise.all(parentKeys?.map(async (key) => {
114
+ const objId = data[schema[key].parent_id] || data?.id || res?.[schema[key]?.parent_id] || res?.[pg.pk?.[table] || ''];
115
+ // delete old extra data
116
+ await client.query(`delete from ${schema[key].table} where ${schema[key].parent_id}=$1`, [objId]); // rewrite?
117
+ // insert new extra data
118
+ if (Array.isArray(data[key]) && data[key]?.length) {
119
+ const parentKey = schema[key].parent_id;
120
+ const extraRows = await Promise.all(data[key]?.map?.(async (row) => {
121
+ Object.assign(row, { [parentKey]: objId });
122
+ const parentRes = await getInsertQuery({
123
+ pg: client, table: schema[key].table, data: row, uid,
124
+ });
125
+ if (!parentRes?.insertQuery || !parentRes?.args?.length) return null;
126
+
127
+ const { rows = [] } = await client.query(parentRes.insertQuery, parentRes.args);
128
+ return rows[0];
129
+ }));
130
+ Object.assign(res, { [key]: extraRows.filter(Boolean) });
131
+ }
132
+ }));
133
+ }
134
+
135
+ await logChanges({
136
+ pg,
137
+ table,
138
+ tokenData,
139
+ referer,
140
+ data,
141
+ id,
142
+ uid,
143
+ type: 'UPDATE',
144
+ });
145
+
146
+ if (config.redis) { rclient.incr(`pg:${table}:crud`); }
147
+
148
+ await client.query('commit;');
149
+ return res || {};
150
+ }
151
+ catch (err) {
152
+ logger.file('crud/update', {
153
+ error: err.toString(), stack: err.stack, table, id, referer, uid, form: tokenData?.form,
154
+ });
155
+ await client.query('rollback;');
156
+ throw err;
157
+ }
158
+ finally {
159
+ client.release();
160
+ }
111
161
  }
@@ -0,0 +1,44 @@
1
+ import getMeta from '../../../pg/funcs/getMeta.js';
2
+
3
+ export default async function getInsertQuery({
4
+ pg, table, data, id, uid,
5
+ }) {
6
+ if (!pg?.pk || !data) {
7
+ return null;
8
+ }
9
+
10
+ const { columns } = await getMeta({ pg, table });
11
+
12
+ if (!columns) {
13
+ return null;
14
+ }
15
+
16
+ const names = columns.map((el) => el.name);
17
+
18
+ Object.assign(data, {
19
+ ...(id && pg.pk?.[table] ? { [pg.pk?.[table]]: id } : {}),
20
+ ...(table !== 'admin.users' ? { uid } : {}),
21
+ editor_id: uid,
22
+ created_by: uid,
23
+ updated_by: uid,
24
+ // editor_id: uid,
25
+ });
26
+
27
+ const systemColumns = ['cdate', 'editor_date', 'created_at', 'updated_at'].filter((el) => names.includes(el)).map((el) => [el, 'now()']);
28
+ const systemColumnNames = systemColumns.map(el => el[0]);
29
+
30
+ const filterData = Object.keys(data)
31
+ .filter((el) => !systemColumnNames.includes(el) && (typeof data[el] === 'boolean' ? true : data[el]) && names.includes(el)).map((el) => [el, data[el]]);
32
+
33
+ const insertQuery = `insert into ${table}
34
+
35
+ ( ${filterData?.map((key) => `"${key[0]}"`).concat(systemColumnNames).join(',')})
36
+
37
+ values (${filterData?.map((key, i) => (key[0] === 'geom' ? `st_setsrid(st_geomfromgeojson($${i + 1}::json),4326)` : `$${i + 1}`)).concat(systemColumns.map((el) => el[1])).join(',')})
38
+
39
+ returning *`;
40
+
41
+ const args = [...filterData.map((el) => (typeof el[1] === 'object' && (!Array.isArray(el[1]) || typeof el[1]?.[0] === 'object') ? JSON.stringify(el[1]) : el[1]))];
42
+
43
+ return { insertQuery, args };
44
+ }
@@ -1,6 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
 
3
- import getTemplate from '../../..//table/funcs/getTemplate.js';
3
+ import getTemplate from '../../../table/funcs/getTemplate.js';
4
4
  import metaFormat from '../../../table/funcs/metaFormat/index.js';
5
5
 
6
6
  const defaultTitles = {
@@ -84,7 +84,9 @@ export default async function logChanges({
84
84
  .filter(el => schema[el]?.data)
85
85
  .reduce((acc, curr) => Object.assign(acc, { [curr]: schema[curr].data }), {});
86
86
 
87
- const data1 = data ? await metaFormat({ rows: [data], cls, sufix: false, reassign: false }) : null;
87
+ const data1 = data ? await metaFormat({
88
+ rows: [data], cls, sufix: false, reassign: false,
89
+ }, pg) : null;
88
90
 
89
91
  const newObj = Object.fromEntries(Object.entries(data1?.[0] || {}).map(el => ([[titles[el[0]] || defaultTitles[el[0]] || el[0]], el[1]])));
90
92
  const changesData = Object.keys(newObj || {}).map(el => ({
@@ -1,4 +1,8 @@
1
- import { config, getMeta, pgClients, getTemplate, dataInsert } from '../../../utils.js';
1
+ import config from '../../../config.js';
2
+ import getTemplate from '../table/funcs/getTemplate.js';
3
+ import getMeta from '../pg/funcs/getMeta.js';
4
+ import pgClients from '../pg/pgClients.js';
5
+ import getInsertQuery from '../crud/funcs/utils/getInsertQuery.js';
2
6
 
3
7
  const defaultTable = 'crm.extra_data';
4
8
 
@@ -11,7 +15,7 @@ function format(key, value, schema) {
11
15
  }
12
16
 
13
17
  export default async function extraData({
14
- table, form, id, data, uid,
18
+ table, form, id, data, uid, row = {},
15
19
  }, pg = pgClients.client) {
16
20
  if (!id || !table) {
17
21
  return null;
@@ -20,9 +24,9 @@ export default async function extraData({
20
24
  const loadTemplate = await getTemplate('form', form);
21
25
  if (!loadTemplate?.extra) return null;
22
26
 
23
- const extraDataTable = config.extraData?.[table]
27
+ const extraTable = config.extraData?.[table]
24
28
  || config.extraData?.[table.split('.').shift()]
25
- || config.extraData?.['default']
29
+ || config.extraData?.default
26
30
  || config.extraData
27
31
  || defaultTable;
28
32
 
@@ -32,33 +36,42 @@ export default async function extraData({
32
36
  return { error: `table pk not found: ${table}`, status: 404 };
33
37
  }
34
38
 
35
- if (!pg.pk?.[extraDataTable]) {
36
- return { error: `extra table pk not found: ${extraDataTable}`, status: 404 };
39
+ if (!pg.pk?.[extraTable]) {
40
+ return { error: `extra table pk not found: ${extraTable}`, status: 404 };
37
41
  }
38
42
 
39
43
  Object.assign(data || {}, { object_id: id });
40
44
 
41
- const deleteRes = await pg.query(`delete from ${extraDataTable} where object_id=$1 and property_key = any($2::text[]) returning *`, [id, Object.keys(loadTemplate?.schema || {})]);
45
+ const deleteRes = await pg.query(`delete from ${extraTable} where object_id=$1 and property_key = any($2::text[]) returning *`, [id, Object.keys(loadTemplate?.schema || {})]);
42
46
 
43
47
  if (!data) {
44
- return deleteRes?.rows?.reduce?.((acc, curr) => Object.assign(acc, { [curr.property_key]: format(curr.property_key, curr.value_text, loadTemplate?.schema) }), {});
48
+ return deleteRes?.rows?.reduce?.((acc, curr) => Object.assign(acc, { [curr.property_key]: format(curr.property_key, curr.value_text, curr.value_array, loadTemplate?.schema) }), {});
45
49
  }
46
50
 
47
51
  const rows = Object.keys(data || {})
48
52
  .filter(key => Object.keys(loadTemplate?.schema || {}).includes(key))
49
53
  .filter(key => !mainColumns.map(el => el.name).concat('id', 'token').includes(key))
50
- .filter(key => Array.isArray(data[key]) ? data[key].length : true)
54
+ .filter(key => (Array.isArray(data[key]) ? data[key].length : true))
51
55
  .filter(key => !(loadTemplate?.schema?.[key]?.table && loadTemplate?.schema?.[key]?.parent_id))
52
56
  .map(key => ({
53
57
  object_id: id,
54
58
  property_key: key,
55
59
  property_entity: table,
56
- value_text: data[key],
60
+ value_text: Array.isArray(data[key]) ? null : data[key],
61
+ value_array: Array.isArray(data[key]) ? data[key] : null,
57
62
  }));
58
63
 
59
- const res = await Promise.all(rows.map(async (row) => dataInsert({ pg, table: extraDataTable, data: row, uid }).then(el => el.rows?.[0] || {})));
60
- return {
61
- ...res.reduce((acc, curr) => Object.assign(acc, { [curr.property_key]: format(curr.property_key, curr.value_text, loadTemplate?.schema) }), {}),
64
+ const res = await Promise.all(rows.map(async (dataRow) => {
65
+ const { insertQuery, args = [] } = await getInsertQuery({
66
+ pg, table: extraTable, data: dataRow, uid,
67
+ });
68
+ if (!insertQuery || !args?.length) return {};
69
+ return pg.query(insertQuery, args).then(el => el.rows?.[0] || {});
70
+ }));
71
+
72
+ Object.assign(row, {
73
+ ...res.reduce((acc, curr) => Object.assign(acc, { [curr.property_key]: format(curr.property_key, curr.value_text, curr.value_array, loadTemplate?.schema) }), {}),
62
74
  id: res?.[0]?.object_id,
63
- };
75
+ });
76
+ return row;
64
77
  }
@@ -1,4 +1,6 @@
1
- import { config, getTemplate, pgClients } from '../../../utils.js';
1
+ import config from '../../../config.js';
2
+ import pgClients from '../pg/pgClients.js';
3
+ import getTemplate from '../table/funcs/getTemplate.js';
2
4
 
3
5
  const defaultTable = 'crm.extra_data';
4
6
 
@@ -43,9 +45,22 @@ export default async function extraDataGet({
43
45
  Object.assign(row, {
44
46
  ...extraRows
45
47
  .filter(el => el.object_id === row.id)
46
- .reduce((acc, curr) => Object.assign(acc, {
47
- [curr.property_key]: format(curr.property_key, curr.value_text, loadTemplate?.schema),
48
- }), {}),
48
+ .reduce((acc, curr) => {
49
+ let value = null;
50
+
51
+ if (curr.value_text !== null) {
52
+ value = curr.value_text;
53
+ }
54
+ else if (curr.value_array !== null) {
55
+ value = curr.value_array;
56
+ }
57
+
58
+ if (value !== null) {
59
+ acc[curr.property_key] = format(curr.property_key, value, loadTemplate?.schema);
60
+ }
61
+
62
+ return acc;
63
+ }, {}),
49
64
  });
50
65
  });
51
66
  }
@@ -97,23 +97,6 @@ export default async function insert(req, reply) {
97
97
  await applyHook('afterInsert', {
98
98
  pg, table, token: params?.table, body, payload: res, user,
99
99
  });
100
- // form DataTable
101
- // to do: rewrite as single transaction
102
- const extraKeys = Object.keys(schema || {})?.filter((key) => body[key]?.length && Array.isArray(body[key]) && schema?.[key]?.table && schema?.[key]?.parent_id);
103
- const pkey = res?.rows?.[0]?.[loadTemplate?.key || pg.pk?.[loadTemplate?.table || table]];
104
- if (extraKeys?.length) {
105
- res.extra = {};
106
- await Promise.all(extraKeys?.map(async (key) => {
107
- const objId = body[schema[key].parent_id] || req.body?.id || res?.rows?.[0]?.[schema[key].parent_id] || pkey;
108
- const extraRows = await Promise.all(body[key].map(async (row) => {
109
- const extraRes = await dataInsert({
110
- pg, table: schema[key].table, data: { ...row, [schema[key].parent_id]: objId }, uid: user?.uid, tokenData, referer,
111
- });
112
- return extraRes?.rows?.[0];
113
- }));
114
- Object.assign(res.extra, { [key]: extraRows.filter((el) => el) });
115
- }));
116
- }
117
100
 
118
101
  const pk = pg.pk?.[loadTemplate?.table || table];
119
102
  return reply.status(200).send({ id: res?.rows?.[0]?.[pk], rows: res.rows, extra: res.extra });
@@ -99,26 +99,5 @@ export default async function update(req, reply) {
99
99
  pg, table: params?.table, body, payload: res, user,
100
100
  });
101
101
 
102
- // form DataTable
103
- const extraKeys = Object.keys(schema || {})?.filter((key) => Array.isArray(body[key]) && schema?.[key]?.table && schema?.[key]?.parent_id /* && body[key].length */);
104
- if (extraKeys?.length) {
105
- res.extra = {};
106
- await Promise.all(extraKeys?.map(async (key) => {
107
- const objId = body[schema[key].parent_id] || body?.id || res?.[schema[key]?.parent_id] || res?.[pg.pk?.[loadTemplate?.table || table] || ''];
108
- // delete old extra data
109
- await pg.query(`delete from ${schema[key].table} where ${schema[key].parent_id}=$1`, [objId]); // rewrite?
110
- // insert new extra data
111
- if (Array.isArray(body[key]) && body[key]?.length) {
112
- const extraRows = await Promise.all(body[key]?.map?.(async (row) => {
113
- const extraRes = await dataInsert({
114
- pg, table: schema[key].table, data: { ...row, [schema[key].parent_id]: objId }, uid, tokenData, referer,
115
- });
116
- return extraRes?.rows?.[0];
117
- }));
118
- Object.assign(res.extra, { [key]: extraRows.filter((el) => el) });
119
- }
120
- }));
121
- }
122
-
123
102
  return reply.status(200).send(res);
124
103
  }
@@ -1,93 +1,97 @@
1
- import path from 'node:path';
2
- import { lstat, readdir, readFile } from 'node:fs/promises';
3
- import { createReadStream, existsSync } from 'node:fs';
4
- import readline from 'node:readline';
5
-
6
- import checkUserAccess from './utils/checkUserAccess.js';
7
- import getRootDir from './utils/getRootDir.js';
8
-
9
- /**
10
- *
11
- * @method GET
12
- * @summary API для перегляду логів
13
- *
14
- */
15
-
16
- export default async function loggerFile({
17
- params = {}, user = {}, query = {}, originalUrl,
18
- }, reply) {
19
- const limit = 200000;
20
- // console.log(user);
21
- const access = checkUserAccess({ user });
22
- // log.info('Сервер запущен по адресу?'); // test!
23
-
24
- if (access?.status !== 200) return access;
25
-
26
- // absolute / relative path
27
- const rootDir = getRootDir();
28
-
29
- const filepath = path.join(rootDir, params['*'] || '');
30
-
31
- if (!existsSync(filepath)) {
32
- return { message: 'file not exists', status: 404 };
33
- }
34
- const stat = await lstat(filepath);
35
- const isFile = stat.isFile();
36
-
37
- if (query.download && isFile) {
38
- const buffer = await readFile(filepath, { buffer: true });
39
- return buffer;
40
- }
41
-
42
- if (query.full && isFile) {
43
- if (stat.size > 20 * 1000 * 1000) {
44
- return { message: 'file size > 20MB' };
45
- }
46
- const buffer = await readFile(filepath, { buffer: true });
47
- return buffer;
48
- }
49
-
50
- if (isFile) {
51
- const ext = path.extname(filepath);
52
-
53
- const lines = await new Promise((resolve) => {
54
- const rl = readline.createInterface({
55
- input: createReadStream(filepath, { start: stat.size > limit ? stat.size - limit : 0 }),
56
- });
57
- const lines1 = [];
58
- rl.on('close', () => resolve(lines1));
59
- rl.on('line', (line) => lines1.push(line));
60
- });
61
-
62
- if (ext === '.html') {
63
- const buffer = await readFile(filepath, { buffer: true });
64
- reply.headers({ 'Content-type': 'text/html; charset=UTF-8' });
65
- return buffer;
66
- }
67
-
68
- reply.headers({ 'Content-type': 'text/plain; charset=UTF-8' });
69
- return stat.size > limit && lines.length > 1
70
- ? lines.reverse().slice(0, -1).join('\n')
71
- : lines.reverse().join('\n');
72
- }
73
-
74
- // dir
75
- const files = await readdir(filepath);
76
-
77
- if (query.dir) {
78
- return files.filter((el) => !['backup', 'marker_icon', 'error', 'migration'].includes(el));
79
- }
80
-
81
- const lstatsArr = await Promise.all(files.map(async (file) => [file, await lstat(path.join(filepath, file))]));
82
- const lstats = Object.fromEntries(lstatsArr);
83
-
84
- const message = (params['*'] ? '<a href="/logger-file/">...</a><br>' : '')
85
- + files.map((file) => `<a href="${originalUrl}/${file}">${file}</a> (${lstats[file].size} bytes)`).join('</br>');
86
-
87
- reply.headers({
88
- 'Content-Type': 'text/html; charset=UTF-8',
89
- 'Content-Security-Policy': "default-src 'none'",
90
- 'X-Content-Type-Options': 'nosniff',
91
- });
92
- return message;
93
- }
1
+ import path from 'node:path';
2
+ import { lstat, readdir, readFile } from 'node:fs/promises';
3
+ import { createReadStream, existsSync } from 'node:fs';
4
+ import readline from 'node:readline';
5
+
6
+ import checkUserAccess from './utils/checkUserAccess.js';
7
+ import getRootDir from './utils/getRootDir.js';
8
+
9
+ const limit = 200000;
10
+ const rootDir = getRootDir();
11
+
12
+ /**
13
+ *
14
+ * @method GET
15
+ * @summary API для перегляду логів
16
+ *
17
+ */
18
+
19
+ export default async function loggerFile(req, reply) {
20
+ const {
21
+ params = {}, user = {}, query = {}, originalUrl,
22
+ } = req;
23
+
24
+ const access = checkUserAccess({ user, token: query.token });
25
+
26
+ if (access?.status !== 200) return reply.status(access.status).send(access.message);
27
+
28
+ // absolute / relative path
29
+
30
+ const filepath = path.join(rootDir, params['*'] || '');
31
+
32
+ if (!existsSync(filepath)) {
33
+ return reply.status(404).send('file not exists');
34
+ }
35
+
36
+ const stat = await lstat(filepath);
37
+ const isFile = stat.isFile();
38
+
39
+ if (query.download && isFile) {
40
+ const buffer = await readFile(filepath, { buffer: true });
41
+ return buffer;
42
+ }
43
+
44
+ if (query.full && isFile) {
45
+ if (stat.size > 20 * 1000 * 1000) {
46
+ return { message: 'file size > 20MB' };
47
+ }
48
+ const buffer = await readFile(filepath, { buffer: true });
49
+ return buffer;
50
+ }
51
+
52
+ if (isFile) {
53
+ const ext = path.extname(filepath);
54
+
55
+ const lines = await new Promise((resolve) => {
56
+ const rl = readline.createInterface({
57
+ input: createReadStream(filepath, { start: stat.size > limit ? stat.size - limit : 0 }),
58
+ });
59
+ const lines1 = [];
60
+ rl.on('close', () => resolve(lines1));
61
+ rl.on('line', (line) => lines1.push(line));
62
+ });
63
+
64
+ if (ext === '.html') {
65
+ const buffer = await readFile(filepath, { buffer: true });
66
+ reply.headers({ 'Content-type': 'text/html; charset=UTF-8' });
67
+ return buffer;
68
+ }
69
+
70
+ reply.headers({ 'Content-type': 'text/plain; charset=UTF-8' });
71
+ return stat.size > limit && lines.length > 1
72
+ ? lines.reverse().slice(0, -1).join('\n')
73
+ : lines.reverse().join('\n');
74
+ }
75
+
76
+ // dir
77
+ const files = await readdir(filepath);
78
+
79
+ if (query.dir) {
80
+ return files.filter((el) => !['backup', 'marker_icon', 'error', 'migration'].includes(el));
81
+ }
82
+
83
+ const lstatsArr = await Promise.all(files.map(async (file) => [file, await lstat(path.join(filepath, file))]));
84
+ const lstats = Object.fromEntries(lstatsArr);
85
+
86
+ const relpaceable = query.token ? `?token=${query.token}` : '';
87
+ const relpath = query.token ? originalUrl.replace(relpaceable, '') : originalUrl;
88
+ const message = (params['*'] ? '<a href="/logger-file/">...</a><br>' : '')
89
+ + files.map((file) => `<a href="${relpath}/${file}${relpaceable}">${file}</a> (${lstats[file].size} bytes)`).join('</br>');
90
+
91
+ reply.headers({
92
+ 'Content-Type': 'text/html; charset=UTF-8',
93
+ 'Content-Security-Policy': "default-src 'none'",
94
+ 'X-Content-Type-Options': 'nosniff',
95
+ });
96
+ return message;
97
+ }
@@ -1,19 +1,24 @@
1
- import config from '../../../../../config.js';
2
-
3
- /**
4
- *
5
- * @summary check user access to logger interface - per admin user type or user group
6
- * @returns {Object} message, status
7
- */
8
-
9
- export default function checkUserAccess({ user = {} }) {
10
- // console.log(user);
11
- if (user.user_type !== 'admin' && !config?.local && !config.auth?.disable) {
12
- return { message: 'access restricted', status: 403 };
13
- }
14
-
15
- /* if (!['admin', 'superadmin']?.includes(user.user_type) && count === '0') {
16
- return { message: 'access restricted', status: 403 };
17
- } */
18
- return { message: 'access granted', status: 200 };
19
- }
1
+ import config from '../../../../../config.js';
2
+
3
+ const { accessToken = '0NWcGQxKRP8AsRxD' } = config.auth || {};
4
+
5
+ /**
6
+ *
7
+ * @summary check user access to logger interface - per admin user type or user group
8
+ * @returns {Object} message, status
9
+ */
10
+
11
+ export default function checkUserAccess({ user = {}, token }) {
12
+ if (token && token === accessToken) {
13
+ return { message: 'access granted', status: 200 };
14
+ }
15
+ // console.log(user);
16
+ if (user.user_type !== 'admin' && !config?.local && !config.auth?.disable) {
17
+ return { message: 'access restricted', status: 403 };
18
+ }
19
+
20
+ /* if (!['admin', 'superadmin']?.includes(user.user_type) && count === '0') {
21
+ return { message: 'access restricted', status: 403 };
22
+ } */
23
+ return { message: 'access granted', status: 200 };
24
+ }
@@ -3,18 +3,20 @@ import loggerFile from './controllers/logger.file.js';
3
3
  // import loggerTest from './controllers/logger.test.api.js';
4
4
 
5
5
  const loggerSchema = {
6
- type: 'object',
6
+ querystring: {
7
+ type: 'object',
7
8
  properties: {
8
- querystring: {
9
- download: { type: 'string', pattern: '^(\\d+)$' },
10
- full: { type: 'string', pattern: '^(\\d+)$' },
11
- dir: { type: 'string', pattern: '^(\\d+)$' },
12
- },
9
+ download: { type: 'string', pattern: '^(\\d)$' },
10
+ full: { type: 'string', pattern: '^(\\d)$' },
11
+ dir: { type: 'string', pattern: '^(\\d)$' },
12
+ token: { type: 'string' },
13
+ },
14
+ additionalProperties: false,
13
15
  },
14
16
  };
15
17
 
16
18
  async function plugin(app) {
17
- app.get('/logger-file/*', { config: { policy: ['site'] }, schema: loggerSchema }, loggerFile);
19
+ app.get('/logger-file/*', { config: { policy: ['public'] }, schema: loggerSchema }, loggerFile);
18
20
  }
19
21
 
20
22
  export default plugin;
@@ -295,7 +295,7 @@ export default async function dataAPI(req, reply, called) {
295
295
  // title, count
296
296
  panels?.filter(el => el.items).forEach(async el => {
297
297
  const filtered1 = el.items.filter(item => item.count?.toLowerCase?.().includes('select'));
298
- const data = await Promise.all(filtered1.map(item => pg.query(item.count).then(item1 => item1.rows[0] || {})));
298
+ const data = await Promise.all(filtered1.map(item => pg.query(item.count.replace(/{{id}}/g, params.id)).then(item1 => item1.rows[0] || {})));
299
299
  filtered1.forEach((el1, i) => {
300
300
  Object.assign(el1, data[i] || {}, data[i].count ? {} : { count: undefined });
301
301
  });
package/utils.js CHANGED
@@ -45,6 +45,7 @@ import getFilter from './server/plugins/table/funcs/getFilter.js';
45
45
  import dataInsert from './server/plugins/crud/funcs/dataInsert.js';
46
46
  import dataUpdate from './server/plugins/crud/funcs/dataUpdate.js';
47
47
  import dataDelete from './server/plugins/crud/funcs/dataDelete.js';
48
+ import getInsertQuery from './server/plugins/crud/funcs/utils/getInsertQuery.js';
48
49
  import getToken from './server/plugins/crud/funcs/getToken.js';
49
50
  import setToken from './server/plugins/crud/funcs/setToken.js';
50
51
  import getOpt from './server/plugins/crud/funcs/getOpt.js';
@@ -149,6 +150,7 @@ export {
149
150
  dataInsert,
150
151
  dataUpdate,
151
152
  dataDelete,
153
+ getInsertQuery,
152
154
 
153
155
  // table
154
156
  autoIndex,