@opengis/fastify-table 1.0.46 → 1.0.48

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.0.48 - 26.06.2024
4
+
5
+ - search api no specified table mode support
6
+
7
+ ## 1.0.47 - 25.06.2024
8
+
9
+ - add extra properties support
10
+
3
11
  ## 1.0.46 - 24.06.2024
4
12
 
5
13
  - fix data api edit geometry
@@ -0,0 +1,51 @@
1
+ import dataInsert from "../funcs/dataInsert.js";
2
+
3
+ const table = 'crm.properties';
4
+
5
+ function checkKeyType({ body, key }) {
6
+ if (typeof body[key] === 'number' && !body[key].toString().includes('.')) {
7
+ return { [key]: 'int' };
8
+ } else if (typeof body[key] === 'object') {
9
+ return { [key]: 'json' };
10
+ } else if (Date.parse(body[key], 'yyyy/MM/ddTHH:mm:ss.000Z')) {
11
+ return { [key]: 'date' };
12
+ }
13
+ return { [key]: 'text' };
14
+ }
15
+
16
+ export default async function addExtraProperties({
17
+ pg, params = {}, body = {},
18
+ }) {
19
+ const { id } = params;
20
+ if (!id) {
21
+ return { message: 'not enougn params: 1', status: 400 };
22
+ }
23
+ const extraProperties = Object.keys(body);
24
+ if (!extraProperties.length) {
25
+ return { message: 'not enougn params: 2', status: 400 };
26
+ }
27
+
28
+ if (!pg.pk?.[table]) {
29
+ return { message: 'table not found: crm.properties', status: 400 };
30
+ }
31
+
32
+ try {
33
+ await pg.query(`delete from crm.properties where object_id=$1`, [id]);
34
+ const keyTypeMatch = extraProperties.filter((key) => body[key]).reduce((acc, curr) => Object.assign(acc, checkKeyType({ body, key: curr })), {});
35
+ const res = await Promise.all(Object.keys(keyTypeMatch).map(async (key) => {
36
+ const propertyType = keyTypeMatch[key];
37
+ const { rows = [] } = await dataInsert({
38
+ pg, table, data: {
39
+ property_type: propertyType,
40
+ property_key: key,
41
+ object_id: id,
42
+ [`property_${propertyType}`]: body[key],
43
+ }
44
+ });
45
+ return { id: rows[0]?.property_id, type: propertyType, value: body[key] };
46
+ }));
47
+ return { message: { rows: res }, status: 200 };
48
+ } catch (err) {
49
+ return { error: err.toString(), status: 500 };
50
+ }
51
+ }
@@ -0,0 +1,20 @@
1
+ export default async function getExtraProperties({
2
+ pg, params = {},
3
+ }) {
4
+ const { id } = params;
5
+ if (!id) {
6
+ return { message: 'not enougn params', status: 400 };
7
+ }
8
+ try {
9
+ const { rows = [] } = pg.pk?.['crm.properties']
10
+ ? await pg.query(`select property_key, property_type, property_text, property_int,
11
+ property_json, property_date from crm.properties where property_key is not null and object_id=$1`, [id])
12
+ : {};
13
+ if (!rows.length) return {};
14
+
15
+ const data = rows.reduce((acc, curr) => Object.assign(acc, { [curr.property_key]: curr[`property_${curr.property_type}`] }), {});
16
+ return { message: data, status: 200 };
17
+ } catch (err) {
18
+ return { error: err.toString(), status: 500 };
19
+ }
20
+ }
package/crud/index.js CHANGED
@@ -8,6 +8,9 @@ import update from './controllers/update.js';
8
8
  import insert from './controllers/insert.js';
9
9
  import deleteCrud from './controllers/deleteCrud.js';
10
10
 
11
+ import getExtraProperties from './controllers/properties.get.js';
12
+ import addExtraProperties from './controllers/properties.add.js';
13
+
11
14
  // import config from '../config.js';
12
15
 
13
16
  async function plugin(fastify, config = {}) {
@@ -24,6 +27,9 @@ async function plugin(fastify, config = {}) {
24
27
  fastify.put(`${prefix}/table/:table/:id`, {}, update);
25
28
  fastify.delete(`${prefix}/table/:table/:id`, {}, deleteCrud);
26
29
  fastify.post(`${prefix}/table/:table`, {}, insert);
30
+
31
+ fastify.get(`${prefix}/properties/:id`, {}, getExtraProperties);
32
+ fastify.post(`${prefix}/properties/:id`, {}, addExtraProperties);
27
33
  }
28
34
 
29
35
  export default plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/fastify-table",
3
- "version": "1.0.46",
3
+ "version": "1.0.48",
4
4
  "type": "module",
5
5
  "description": "core-plugins",
6
6
  "main": "index.js",
@@ -115,4 +115,34 @@ ALTER TABLE crm.cls ADD COLUMN IF NOT EXISTS cdate timestamp without time zone D
115
115
  ALTER TABLE crm.cls ADD COLUMN IF NOT EXISTS editor_id text;
116
116
  ALTER TABLE crm.cls ADD COLUMN IF NOT EXISTS editor_date timestamp without time zone;
117
117
  ALTER TABLE crm.cls ADD CONSTRAINT crm_cls_pkey PRIMARY KEY (cls_id);
118
- ALTER TABLE crm.cls ADD CONSTRAINT crm_cls_unique UNIQUE (code, parent);
118
+ ALTER TABLE crm.cls ADD CONSTRAINT crm_cls_unique UNIQUE (code, parent);
119
+
120
+ -- DROP TABLE crm.properties;
121
+ CREATE TABLE IF NOT EXISTS crm.properties();
122
+ ALTER TABLE crm.properties DROP CONSTRAINT IF EXISTS crm_properties_pkey;
123
+ ALTER TABLE crm.properties ADD COLUMN IF NOT EXISTS property_id text NOT NULL DEFAULT next_id();
124
+
125
+ ALTER TABLE crm.properties ADD COLUMN IF NOT EXISTS table_name text;
126
+ COMMENT ON COLUMN crm.properties.table_name IS 'Таблиця';
127
+ ALTER TABLE crm.properties ADD COLUMN IF NOT EXISTS object_id text;
128
+ COMMENT ON COLUMN crm.properties.object_id IS 'ID Об''єкту';
129
+ ALTER TABLE crm.properties ADD COLUMN IF NOT EXISTS property_key text;
130
+ COMMENT ON COLUMN crm.properties.object_id IS 'Назва атрибуту';
131
+ ALTER TABLE crm.properties ADD COLUMN IF NOT EXISTS property_text text;
132
+ COMMENT ON COLUMN crm.properties.property_text IS 'Текст';
133
+ ALTER TABLE crm.properties ADD COLUMN IF NOT EXISTS property_int integer;
134
+ COMMENT ON COLUMN crm.properties.property_int IS 'Integer';
135
+ ALTER TABLE crm.properties ADD COLUMN IF NOT EXISTS property_json json;
136
+ COMMENT ON COLUMN crm.properties.property_json IS 'JSON';
137
+ ALTER TABLE crm.properties ADD COLUMN IF NOT EXISTS property_date date;
138
+ COMMENT ON COLUMN crm.properties.property_json IS 'Дата';
139
+ ALTER TABLE crm.properties ADD COLUMN IF NOT EXISTS property_type text;
140
+ COMMENT ON COLUMN crm.properties.property_type IS 'Тип даних';
141
+
142
+ ALTER TABLE crm.properties ADD COLUMN IF NOT EXISTS uid text NOT NULL DEFAULT '1'::text;
143
+ ALTER TABLE crm.properties ADD COLUMN IF NOT EXISTS editor_id text;
144
+ ALTER TABLE crm.properties ADD COLUMN IF NOT EXISTS editor_date timestamp without time zone;
145
+ ALTER TABLE crm.properties ADD COLUMN IF NOT EXISTS cdate timestamp without time zone DEFAULT now();
146
+ ALTER TABLE crm.properties ADD COLUMN IF NOT EXISTS files json;
147
+
148
+ ALTER TABLE crm.properties ADD CONSTRAINT crm_properties_pkey PRIMARY KEY(property_id);
@@ -0,0 +1,41 @@
1
+ import getTemplate from './utils/getTemplate.js';
2
+ import getMeta from '../../pg/funcs/getMeta.js';
3
+ import metaFormat from '../funcs/metaFormat/index.js';
4
+
5
+ export default async function card(req) {
6
+ const time = Date.now();
7
+ const {
8
+ pg, params, query = {},
9
+ } = req;
10
+
11
+ const loadTable = await getTemplate('table', params.table);
12
+
13
+ if (!loadTable) { return { status: 404, message: 'not found' }; }
14
+
15
+ const { table, columns, meta, sql, cardSql } = loadTable;
16
+ const { pk, columns: dbColumns = [] } = await getMeta(table);
17
+
18
+ if (!pk) return { message: `table not found: ${table}`, status: 404 };
19
+
20
+ const cols = columns.map((el) => el.name || el).join(',');
21
+ const columnList = dbColumns.map((el) => el.name || el).join(',');
22
+ const sqlTable = sql?.filter?.((el) => !el?.disabled && el?.sql?.replace).map((el, i) => ` left join lateral (${el.sql}) ${el.name || `t${i}`} on 1=1 `)?.join('') || '';
23
+ const cardSqlFiltered = params.id ? cardSql?.filter?.((el) => !el?.disabled && el?.name && el?.sql?.replace) : [];
24
+ const cardSqlTable = cardSqlFiltered.length ? cardSqlFiltered.map((el, i) => ` left join lateral (select json_agg(row_to_json(q)) as ${el.name} from (${el.sql})q) ct${i} on 1=1 `).join('') || '' : '';
25
+
26
+ const where = [`"${pk}" = $1`, loadTable.query].filter((el) => el);
27
+ const cardColumns = cardSqlFiltered.length ? `,${cardSqlFiltered.map((el) => el.name)}` : '';
28
+ const q = `select ${pk ? `"${pk}" as id,` : ''} ${columnList.includes('geom') ? `st_asgeojson(geom)::json as geom,` : ''} ${cols || '*'} ${cardColumns} from ${table} t ${sqlTable} ${cardSqlTable}
29
+ where ${where.join(' and ') || 'true'} limit 1`;
30
+
31
+ if (query.sql === '1') { return q; }
32
+
33
+ const { rows } = await pg.query(q, [params.id]);
34
+
35
+ await metaFormat({ rows, table: params.table });
36
+
37
+ const data = meta.card?.length ? meta.card.reduce((acc, curr) => Object.assign(acc, { [columns.find((col) => col.name === curr)?.ua || '']: rows[0][curr] }), {}) : {};
38
+ return {
39
+ time: Date.now() - time, data,
40
+ };
41
+ }
@@ -1,17 +1,20 @@
1
1
  import getTemplate from './utils/getTemplate.js';
2
2
  import getMeta from '../../pg/funcs/getMeta.js';
3
3
  import metaFormat from '../funcs/metaFormat/index.js';
4
+ import getTemplates from './utils/getTemplates.js';
4
5
 
5
- const maxLimit = 100;
6
+ function sequence(tables, data, fn) {
7
+ return tables.reduce((promise, table) => promise.then(() => fn({
8
+ ...data, tableName: table.replace('.json', ''),
9
+ })), Promise.resolve());
10
+ }
6
11
 
7
- export default async function data({
8
- pg, query = {},
12
+ async function getData({
13
+ pg, tableName, query = {}, maxLimit, res,
9
14
  }) {
10
- const time = Date.now();
15
+ const loadTable = await getTemplate('table', tableName);
11
16
 
12
- const loadTable = await getTemplate('table', query.table);
13
-
14
- if (!loadTable) { return { message: 'not found', status: 404 }; }
17
+ if (!loadTable) { return { message: 'not found', status: 404 }; }
15
18
 
16
19
  const { table, columns, meta } = loadTable;
17
20
  const { pk } = await getMeta(table);
@@ -20,22 +23,46 @@ export default async function data({
20
23
  const [orderColumn, orderDir] = (query.order || loadTable.order || '').split('-');
21
24
  const order = cols.includes(orderColumn) && orderColumn?.length ? `order by ${orderColumn} ${query.desc || orderDir === 'desc' ? 'desc' : ''}` : '';
22
25
 
23
- const limit = Math.min(maxLimit, +(query.limit || 10));
26
+ const limit = Math.max(maxLimit - res.rows.length, 0);
27
+ // Math.max(query.offset - res.rows.length,0)
24
28
  const offset = query.page && query.page > 0 ? ` offset ${(query.page - 1) * limit}` : '';
25
29
 
26
30
  const search = meta?.search && query.key ? `(${meta?.search.concat(meta?.title ? `,${meta?.title}` : '').split(',').map(el => `${el} ilike '%${query.key}%'`).join(' or ')})` : null;
27
31
 
28
32
  const where = [loadTable.query, search].filter((el) => el);
29
33
  const q = `select ${pk ? `"${pk}" as id,` : ''} * from ${table} t where ${where.join(' and ') || 'true'} ${order} ${offset} limit ${limit}`;
30
-
31
- if (query.sql === '1') return q;
34
+ if (query.sql) {
35
+ res.sql.push(q);
36
+ return;
37
+ }
32
38
 
33
39
  const { rows } = await pg.query(q);
34
40
 
35
- const total = await pg.queryCache(`select count(*) from ${table} t where ${where.join(' and ') || 'true'}`).then((el) => el?.rows[0]?.count);
41
+ const total = await pg.queryCache(`select count(*) from ${table} t where ${where.join(' and ') || 'true'}`).then((el) => el?.rows[0]?.count) || 0;
42
+
43
+ await metaFormat({ rows, table: tableName });
44
+ res.total += +total;
45
+ rows.forEach((row) => res.rows.push({ ...row, register: tableName }));
46
+ }
47
+
48
+ export default async function data({
49
+ pg, query = {},
50
+ }) {
51
+ const time = Date.now();
52
+
53
+ try {
54
+ const tables = query.table ? [query.table] : await getTemplates('table');
55
+ const res = { rows: [], sql: [], total: 0 };
56
+
57
+ const maxLimit = Math.min(100, query.limit || '16');
58
+ await sequence(tables, { pg, query, maxLimit, res }, getData);
59
+
60
+ if (query.sql) return res.sql.join(';\n');
36
61
 
37
- await metaFormat({ rows, table: query.table });
38
- return {
39
- time: Date.now() - time, total, count: rows.length, rows,
40
- };
62
+ return {
63
+ time: Date.now() - time, total: res.total, count: res.rows.length, rows: res.rows,
64
+ };
65
+ } catch (err) {
66
+ return { error: err.toString(), status: 500 };
67
+ }
41
68
  }
@@ -0,0 +1,18 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import config from '../../../config.js';
4
+
5
+ const loadTemplate = {};
6
+
7
+ export default async function getTemplateDir(type) {
8
+ if (!type) return null;
9
+
10
+ const cwd = process.cwd();
11
+ const typeDir = path.join(cwd, (config.templateDir || 'server/templates'), type);
12
+
13
+ if (!loadTemplate[type]) {
14
+ const typeList = fs.existsSync(typeDir) ? fs.readdirSync(typeDir) : [];
15
+ loadTemplate[type] = typeList;
16
+ }
17
+ return loadTemplate[type];
18
+ }
package/table/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import suggest from './controllers/suggest.js';
2
2
  import data from './controllers/data.js';
3
+ import card from './controllers/card.js';
3
4
  import search from './controllers/search.js';
4
5
  import filter from './controllers/filter.js';
5
6
  import form from './controllers/form.js';
@@ -34,6 +35,7 @@ async function plugin(fastify, config = {}) {
34
35
 
35
36
  fastify.get(`${prefix}/suggest/:data`, {}, suggest);
36
37
  fastify.get(`${prefix}/data/:table/:id?`, { schema: tableSchema }, data); // vs.crm.data.api с node
38
+ fastify.get(`${prefix}/card/:table/:id`, { schema: tableSchema }, card);
37
39
  fastify.get(`${prefix}/search`, { schema: searchTableSchema }, search);
38
40
  fastify.get(`${prefix}/filter/:table`, {}, filter);
39
41
  fastify.get(`${prefix}/form/:form`, {}, form);
@@ -26,6 +26,26 @@ test('api crud', async (t) => {
26
26
  assert.ok(rep.rows ? rep.rows[0]?.map_id : rep.map_id);
27
27
  });
28
28
 
29
+ await t.test('POST /properties/:id', async () => {
30
+ const res = await app.inject({
31
+ method: 'POST',
32
+ url: `${prefix}/properties/5400000`,
33
+ body: {
34
+ custom_alias: 'testMap',
35
+ custom_ord: 5,
36
+ },
37
+ });
38
+ assert.ok(res.json().message.rows.length, 'not ok');
39
+ });
40
+
41
+ await t.test('GET /properties/:id', async () => {
42
+ const res = await app.inject({
43
+ method: 'GET',
44
+ url: `${prefix}/properties/5400000`,
45
+ });
46
+ assert.ok(res.json().message.custom_alias, 'not ok');
47
+ });
48
+
29
49
  await t.test('PUT /update', async () => {
30
50
  const res = await app.inject({
31
51
  method: 'PUT',