@opengis/gis 0.2.175 → 0.2.177
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/dist/{CardIcon-Cg8bUM4G.js → CardIcon-CMRD1LLh.js} +1 -1
- package/dist/{EntityTablePage-DHQGlLRk.js → EntityTablePage-XNNCltAN.js} +1 -1
- package/dist/{ExtentOutlineLayer.vue_vue_type_script_setup_true_lang-BIL1ocLY.js → ExtentOutlineLayer.vue_vue_type_script_setup_true_lang-BqUjKTDY.js} +1 -1
- package/dist/{HeaderActions.vue_vue_type_script_setup_true_lang-Buff9gTO.js → HeaderActions.vue_vue_type_script_setup_true_lang-BXQ3hKwa.js} +1 -1
- package/dist/{MapSettings-De-z7r5t.js → MapSettings-P3h-vHn3.js} +1 -1
- package/dist/{RastersTablePage-Cdgkri9K.js → RastersTablePage-DnhGZLiH.js} +1 -1
- package/dist/{cartocss-BT50SVhy.js → cartocss-BkrOeQg4.js} +5 -5
- package/dist/{import-utils-CMWiuRPp.js → import-utils-BCATsbB5.js} +1 -1
- package/dist/{index-CHFLgCGW.js → index-cUagVqgA.js} +10 -10
- package/dist/index.css +1 -1
- package/dist/index.js +1 -1
- package/dist/index.umd.cjs +19 -19
- package/dist/{raster-dSEb1SIG.js → raster-CP0tUp3j.js} +3 -3
- package/dist/{register-D7Puupgo.js → register-Cr-uQcby.js} +3 -3
- package/dist/{service-Cz6T0O3Z.js → service-DwJaKrOd.js} +829 -832
- package/dist/{vs-datatable-DqVncrSg.js → vs-datatable-B9WoVk-b.js} +1 -1
- package/package.json +1 -1
- package/server/routes/map/functions/mapFormat.js +13 -12
- package/server/routes/registers/functions/getTableColumnMeta.js +131 -0
- package/server/routes/registers/functions/gis.suggest.js +284 -0
- package/server/routes/registers/functions/handleRegistryRequest.js +23 -3
- package/server/routes/registers/registers.route.js +11 -0
- package/server/routes/registers/registers.schema.js +24 -0
- package/server/routes/services/services.schema.js +10 -3
- package/server/shared/listFiles.js +15 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineComponent as F, mergeModels as O, defineAsyncComponent as I, useModel as R, ref as y, watch as K, openBlock as o, createElementBlock as s, createElementVNode as e, createVNode as $, unref as x, withCtx as S, createStaticVNode as V, useSlots as P, computed as H, Fragment as b, createTextVNode as U, createCommentVNode as N, renderList as A, normalizeStyle as Q, toDisplayString as M, normalizeClass as z, withModifiers as X, createBlock as Y, resolveDynamicComponent as ee, withDirectives as te, renderSlot as ne } from "vue";
|
|
2
2
|
import { notify as E } from "@opengis/core";
|
|
3
3
|
import { Edit as le, Trash as oe } from "lucide-vue-next";
|
|
4
|
-
import { _ as u } from "./index-
|
|
4
|
+
import { _ as u } from "./index-cUagVqgA.js";
|
|
5
5
|
const se = { class: "flex items-center justify-center col-span-3 gap-2" }, re = { class: "flex justify-end p-[20px] gap-[10px] border-t w-full" }, ie = /* @__PURE__ */ F({
|
|
6
6
|
__name: "vs-datatable-controls",
|
|
7
7
|
props: /* @__PURE__ */ O({
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { BadRequestError } from '@opengis/fastify-table/errors.js';
|
|
1
|
+
import { BadRequestError, NotFoundError } from '@opengis/fastify-table/errors.js';
|
|
2
2
|
import {
|
|
3
3
|
pgClients, getTemplate, handlebars, metaFormat,
|
|
4
4
|
getMeta,
|
|
5
5
|
} from '@opengis/fastify-table/utils.js';
|
|
6
6
|
|
|
7
|
+
import listFiles from '../../../shared/listFiles.js';
|
|
8
|
+
|
|
7
9
|
const excluded = ['created_by', 'created_at', 'updated_by', 'geom', 'id', 'uid', 'updated_at', 'cdate', 'editor_date', 'editor_id'];
|
|
8
10
|
|
|
9
11
|
function getLayerTableQuery({
|
|
@@ -31,11 +33,11 @@ async function getLayersData({
|
|
|
31
33
|
id, lng, lat, cartocss, nogeom, radius,
|
|
32
34
|
}, pg = pgClients.client) {
|
|
33
35
|
if (!(lng && lat) && !id) {
|
|
34
|
-
|
|
36
|
+
throw new Error('not enough params: lat / lng');
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
if (!cartocss.config?.length && !cartocss.card_table) {
|
|
38
|
-
|
|
40
|
+
throw new Error('empty cartocss.config');
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
const point = lng && lat ? `srid=4326;point(${lng} ${lat})`.replace(/'/g, "''") : null;
|
|
@@ -65,7 +67,7 @@ async function getLayersData({
|
|
|
65
67
|
// console.timeEnd(q);
|
|
66
68
|
|
|
67
69
|
if (!rows.length) {
|
|
68
|
-
|
|
70
|
+
throw NotFoundError('object not found');
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
const layerData = rows[0].key ? cartocss.config?.find(el => el.key === rows[0].key) || { table: cartocss?.card_table, columns: cartocss?.card_columns } : {};
|
|
@@ -106,17 +108,17 @@ async function getTableData({
|
|
|
106
108
|
const columns = (card || []).filter((col) => (uid ? true : !col.is_admin));
|
|
107
109
|
|
|
108
110
|
if (!tname) {
|
|
109
|
-
|
|
111
|
+
throw BadRequestError('missing source_path');
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
const pk = pg.pk?.[tname];
|
|
113
115
|
|
|
114
116
|
if (!pk) {
|
|
115
|
-
|
|
117
|
+
throw BadRequestError('missing pk');
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
if (!columns) {
|
|
119
|
-
|
|
121
|
+
throw NotFoundError('columns setting not found');
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
const columnNames = columns.map(col => col.name);
|
|
@@ -129,7 +131,7 @@ async function getTableData({
|
|
|
129
131
|
).then(r => r.rows || []);
|
|
130
132
|
|
|
131
133
|
if (!rows.length) {
|
|
132
|
-
|
|
134
|
+
throw NotFoundError('object not found');
|
|
133
135
|
}
|
|
134
136
|
|
|
135
137
|
const classifiers = columns
|
|
@@ -181,10 +183,6 @@ export default async function mapFormat({ layer, id, lat, lng, nogeom, radius =
|
|
|
181
183
|
service, layer, id, uid, nogeom,
|
|
182
184
|
}, pg);
|
|
183
185
|
|
|
184
|
-
if (error) {
|
|
185
|
-
throw new Error(error);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
186
|
rows.forEach(row => Object.assign(row, { box2d: undefined, bounds: row.box2d ? row.box2d.replace(/[A-Z\)\(]+/g, '').split(/[ ,]+/).map(el => el - 0) : null }));
|
|
189
187
|
|
|
190
188
|
await metaFormat({
|
|
@@ -192,6 +190,8 @@ export default async function mapFormat({ layer, id, lat, lng, nogeom, radius =
|
|
|
192
190
|
});
|
|
193
191
|
const fullRow = rows[0];
|
|
194
192
|
|
|
193
|
+
const files = rows.length ? await listFiles({ /*entityType: tname,*/ entityId: fullRow.id }, { pg }) : [];
|
|
194
|
+
|
|
195
195
|
const result = [];
|
|
196
196
|
const filtered = columns.filter(col => !excluded.includes(col.name));
|
|
197
197
|
for (let i = 0; i < filtered.length; i += 1) {
|
|
@@ -222,6 +222,7 @@ export default async function mapFormat({ layer, id, lat, lng, nogeom, radius =
|
|
|
222
222
|
template,
|
|
223
223
|
html,
|
|
224
224
|
auto,
|
|
225
|
+
files,
|
|
225
226
|
};
|
|
226
227
|
if (service) {
|
|
227
228
|
res.cardInterface = service.cardInterface;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
pgClients,
|
|
3
|
+
getTemplate,
|
|
4
|
+
getMeta,
|
|
5
|
+
getSelect,
|
|
6
|
+
getFilter,
|
|
7
|
+
} from "@opengis/fastify-table/utils.js";
|
|
8
|
+
|
|
9
|
+
const defaultLimit = 50;
|
|
10
|
+
|
|
11
|
+
export default async function getTableColumnMeta(
|
|
12
|
+
{
|
|
13
|
+
table,
|
|
14
|
+
template,
|
|
15
|
+
column: queryColumn,
|
|
16
|
+
filtered,
|
|
17
|
+
startsWith,
|
|
18
|
+
key,
|
|
19
|
+
},
|
|
20
|
+
pg = pgClients.client
|
|
21
|
+
) {
|
|
22
|
+
if (!table || !queryColumn) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const metaColumns = await getMeta(
|
|
27
|
+
{
|
|
28
|
+
pg,
|
|
29
|
+
table,
|
|
30
|
+
},
|
|
31
|
+
).then((r) => r?.columns || []);
|
|
32
|
+
|
|
33
|
+
const { dataTypeID } =
|
|
34
|
+
(metaColumns || []).find((col) => col?.name === queryColumn) || {};
|
|
35
|
+
|
|
36
|
+
const { table: tableName, sql } = template
|
|
37
|
+
? await getTemplate("table", template)
|
|
38
|
+
: { table };
|
|
39
|
+
|
|
40
|
+
const filters = template
|
|
41
|
+
? await getFilter({ pg, table: template }).then((el) => el.list)
|
|
42
|
+
: null;
|
|
43
|
+
|
|
44
|
+
const { data: clsName } =
|
|
45
|
+
(filters || []).find((el) => el.name === queryColumn) || {};
|
|
46
|
+
const { arr, sql: select } =
|
|
47
|
+
(await getSelect(clsName || queryColumn, pg)) || // cls name === table:column
|
|
48
|
+
(await getSelect(queryColumn, pg)) || // cls name === column name
|
|
49
|
+
{};
|
|
50
|
+
|
|
51
|
+
const sqlTable =
|
|
52
|
+
(sql || [])
|
|
53
|
+
.filter(
|
|
54
|
+
(el) =>
|
|
55
|
+
!el.disabled && el.sql && el.sql.includes(queryColumn) && false
|
|
56
|
+
)
|
|
57
|
+
.map(
|
|
58
|
+
(el, i) =>
|
|
59
|
+
` left join lateral (${el.sql}) ${el.name || `t${i}`} on 1=1 `
|
|
60
|
+
)
|
|
61
|
+
?.join("") || "";
|
|
62
|
+
|
|
63
|
+
if (arr?.length && !arr.find((el) => el.id === "null")) {
|
|
64
|
+
arr.push({
|
|
65
|
+
color: "#B8B8B8",
|
|
66
|
+
icon: null,
|
|
67
|
+
id: "null",
|
|
68
|
+
text: "Показати порожні значення",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// w/out select
|
|
73
|
+
const searchQueryDefault =
|
|
74
|
+
startsWith && key
|
|
75
|
+
? `(left(lower("text"::text),${key.length}) = $1 )`
|
|
76
|
+
: '(lower("text"::text) ~ $1 )';
|
|
77
|
+
|
|
78
|
+
// w/ select
|
|
79
|
+
const searchQuerySelect =
|
|
80
|
+
startsWith && key
|
|
81
|
+
? `id in ( with cc(id, text) as (${select}) select id from cc where (left(lower("text"),${key.length})=$1) )`
|
|
82
|
+
: `id in ( with cc(id, text) as (${select}) select id from cc where (lower("text") ~ $1) )`;
|
|
83
|
+
|
|
84
|
+
if (pg.pgType?.[dataTypeID]?.includes("[]")) {
|
|
85
|
+
const original = filtered
|
|
86
|
+
? `with c(id,text) as (select id, id as text from (select unnest("${queryColumn.replace(
|
|
87
|
+
/"/g,
|
|
88
|
+
""
|
|
89
|
+
)}") as id from ${tableName} t ${sqlTable})q group by id) select id, text from c`
|
|
90
|
+
: `with c(id,text) as (select id, id as text, count(*) from (select unnest("${queryColumn.replace(
|
|
91
|
+
/"/g,
|
|
92
|
+
""
|
|
93
|
+
)}") as id from ${tableName} t ${sqlTable})q group by id limit ${defaultLimit}) select * from c`;
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
arr,
|
|
97
|
+
original,
|
|
98
|
+
searchQuery: select ? searchQuerySelect : searchQueryDefault,
|
|
99
|
+
pk: "id",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const original = filtered
|
|
104
|
+
? `with c(id,text) as (select "${queryColumn.replace(
|
|
105
|
+
/"/g,
|
|
106
|
+
""
|
|
107
|
+
)}" as id, "${queryColumn.replace(
|
|
108
|
+
/"/g,
|
|
109
|
+
""
|
|
110
|
+
)}" as text from ${tableName} t ${sqlTable} group by "${queryColumn.replace(
|
|
111
|
+
/"/g,
|
|
112
|
+
""
|
|
113
|
+
)}") select id, text from c`
|
|
114
|
+
: `with c(id,text) as (select "${queryColumn.replace(
|
|
115
|
+
/"/g,
|
|
116
|
+
""
|
|
117
|
+
)}" as id, "${queryColumn.replace(
|
|
118
|
+
/"/g,
|
|
119
|
+
""
|
|
120
|
+
)}" as text, count(*) from ${tableName} t ${sqlTable} group by "${queryColumn.replace(
|
|
121
|
+
/"/g,
|
|
122
|
+
""
|
|
123
|
+
)}" limit ${defaultLimit}) select * from c`;
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
arr,
|
|
127
|
+
original,
|
|
128
|
+
searchQuery: select ? searchQuerySelect : searchQueryDefault,
|
|
129
|
+
pk: "id",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { BadRequestError, NotFoundError } from "@opengis/fastify-table/errors.js";
|
|
2
|
+
import { getColumnCLS, getMeta, getRedis, metaFormat } from "@opengis/fastify-table/utils.js";
|
|
3
|
+
|
|
4
|
+
import getTableColumnMeta from "./getTableColumnMeta.js";
|
|
5
|
+
|
|
6
|
+
const rclient = getRedis();
|
|
7
|
+
|
|
8
|
+
const count = false;
|
|
9
|
+
const maxLimit = 100;
|
|
10
|
+
|
|
11
|
+
export default async function gisSuggest({ token, key, val, limit: limit1 = 50 }, pg) {
|
|
12
|
+
const time = Date.now();
|
|
13
|
+
|
|
14
|
+
const redisKey = `${pg?.options?.database}:registry-filter:${token}`;
|
|
15
|
+
|
|
16
|
+
const cache = await rclient.get(redisKey);
|
|
17
|
+
|
|
18
|
+
if (!cache) {
|
|
19
|
+
throw BadRequestError('invalid params: token');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { register, filter } = JSON.parse(cache);
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
rows: [registry],
|
|
26
|
+
} = await pg.query(
|
|
27
|
+
`SELECT register_id, table_name, filters FROM gis.registers WHERE register_id=$1`,
|
|
28
|
+
[register],
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
if (!registry) {
|
|
32
|
+
throw NotFoundError('registry not found');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!registry.table_name) {
|
|
36
|
+
throw new Error('registry table not set');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const filterData = (registry.filters).find(el => el.name === filter);
|
|
40
|
+
|
|
41
|
+
if (!filterData) {
|
|
42
|
+
throw new Error('registry filter not found');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { name, data: cls } = filterData;
|
|
46
|
+
|
|
47
|
+
const metaColumns = await getMeta({ pg, table: registry.table_name }).then((r) => r.columns || []);
|
|
48
|
+
|
|
49
|
+
const { dataTypeID } = metaColumns.find((col) => col.name === name);
|
|
50
|
+
|
|
51
|
+
if (!dataTypeID) {
|
|
52
|
+
throw NotFoundError('registry column not found: ' + name);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const clsMeta = await getTableColumnMeta({
|
|
56
|
+
table: registry.table_name,
|
|
57
|
+
column: name,
|
|
58
|
+
filtered: !!(key || val),
|
|
59
|
+
key,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const { arr, searchQuery } = clsMeta;
|
|
63
|
+
|
|
64
|
+
const limit = Math.min(limit1 || maxLimit, maxLimit);
|
|
65
|
+
|
|
66
|
+
const [schemaName, tableName] = registry.table_name.split('.');
|
|
67
|
+
|
|
68
|
+
const table = [`"${schemaName}"`, `"${tableName}"`].join('.').replace(
|
|
69
|
+
/'/g,
|
|
70
|
+
"''"
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// table:column + cls
|
|
74
|
+
if (arr && !cls) {
|
|
75
|
+
const sqlCls = count
|
|
76
|
+
? `select value, count(*) from (select ${pg.pgType?.[dataTypeID]?.includes("[]")
|
|
77
|
+
? `unnest("${name}")`
|
|
78
|
+
: `"${name}"`
|
|
79
|
+
} as value from ${table} where 1=1)q group by value`
|
|
80
|
+
: `select array_agg(distinct value)::text[] from (select ${pg.pgType?.[dataTypeID]?.includes("[]")
|
|
81
|
+
? `unnest("${name}")`
|
|
82
|
+
: `"${name}"`
|
|
83
|
+
} as value from ${table} where 1=1)q`;
|
|
84
|
+
|
|
85
|
+
if (pg?.pk?.[registry.table_name] && !cls) {
|
|
86
|
+
const qRes = await pg.queryCache(sqlCls, {
|
|
87
|
+
table: registry.table_name,
|
|
88
|
+
});
|
|
89
|
+
const vals =
|
|
90
|
+
(count
|
|
91
|
+
? qRes.rows?.map?.((el) => el.value?.toString?.() || null)
|
|
92
|
+
: qRes.rows?.[0]?.array_agg) || [];
|
|
93
|
+
|
|
94
|
+
const lower = key?.toLowerCase?.();
|
|
95
|
+
const data1 =
|
|
96
|
+
key || val
|
|
97
|
+
? arr
|
|
98
|
+
?.filter(
|
|
99
|
+
(el) =>
|
|
100
|
+
!lower ||
|
|
101
|
+
(el[lang] || el.text)?.toLowerCase()?.indexOf(lower) !== -1
|
|
102
|
+
)
|
|
103
|
+
?.filter(
|
|
104
|
+
(el) => !val || val?.split?.(",")?.includes?.(el.id)
|
|
105
|
+
)
|
|
106
|
+
: arr;
|
|
107
|
+
|
|
108
|
+
const data2 = data1.filter((el) =>
|
|
109
|
+
el.id === "null"
|
|
110
|
+
? vals.includes(null)
|
|
111
|
+
: el.id && vals.includes(el.id.toString())
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const data = data2.slice(0, limit);
|
|
115
|
+
|
|
116
|
+
if (data.length && count) {
|
|
117
|
+
const counts = qRes.rows?.reduce?.(
|
|
118
|
+
(acc, curr) => ({
|
|
119
|
+
...acc,
|
|
120
|
+
[curr.value]: curr.count,
|
|
121
|
+
}),
|
|
122
|
+
{}
|
|
123
|
+
);
|
|
124
|
+
data.forEach((el) =>
|
|
125
|
+
Object.assign(el, { count: counts?.[el.id] || 0 })
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
time: Date.now() - time,
|
|
131
|
+
limit,
|
|
132
|
+
count: data.length,
|
|
133
|
+
total: arr.length,
|
|
134
|
+
mode: "array",
|
|
135
|
+
sql: process.platform === 'win32' ? sqlCls : undefined,
|
|
136
|
+
data,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// cls
|
|
142
|
+
if (arr) {
|
|
143
|
+
const lower = key?.toLowerCase();
|
|
144
|
+
const data1 =
|
|
145
|
+
key || val
|
|
146
|
+
? arr
|
|
147
|
+
?.filter(
|
|
148
|
+
(el) =>
|
|
149
|
+
!lower ||
|
|
150
|
+
(el[lang] || el.text)?.toLowerCase()?.indexOf(lower) !== -1
|
|
151
|
+
)
|
|
152
|
+
?.filter((el) => !val || val?.split?.(",")?.includes?.(el.id))
|
|
153
|
+
: arr;
|
|
154
|
+
const data = data1.slice(0, Math.min(limit1 || limit, limit));
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
time: Date.now() - time,
|
|
158
|
+
limit,
|
|
159
|
+
count: data.length,
|
|
160
|
+
total: arr.length,
|
|
161
|
+
mode: "array",
|
|
162
|
+
data,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const args = [];
|
|
167
|
+
const where = [];
|
|
168
|
+
|
|
169
|
+
// select, with search by text support
|
|
170
|
+
if (key && typeof key === "string") {
|
|
171
|
+
args.push(key.toLowerCase());
|
|
172
|
+
where.push(searchQuery);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// pk
|
|
176
|
+
if (clsMeta.pk) {
|
|
177
|
+
where.push(`${clsMeta.pk} is not null`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// filter by value
|
|
181
|
+
if (clsMeta.pk && typeof val === "string") {
|
|
182
|
+
args.push(val.split(","));
|
|
183
|
+
where.push(`${clsMeta.pk}=any($${args.length})`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// filter
|
|
187
|
+
if (pg.pk?.[registry.table_name] && !cls && dataTypeID) {
|
|
188
|
+
where.push(
|
|
189
|
+
`id in (select ${pg.pgType[dataTypeID]?.includes("[]")
|
|
190
|
+
? `unnest("${name}")`
|
|
191
|
+
: `"${name}"`
|
|
192
|
+
} from ${(registry.table_name).replace(/'/g, "''")})`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// order by value if not specified explicitly
|
|
197
|
+
const order = clsMeta.original.toLowerCase().includes("order by")
|
|
198
|
+
? ""
|
|
199
|
+
: "order by 2";
|
|
200
|
+
|
|
201
|
+
const whereQuery = where.filter(Boolean).join(" and ") || "true";
|
|
202
|
+
|
|
203
|
+
args.push(limit);
|
|
204
|
+
|
|
205
|
+
let sqlSuggest = !cls
|
|
206
|
+
? `${clsMeta.original}
|
|
207
|
+
WHERE EXISTS ( SELECT 1 FROM ${(registry.table_name).replace(
|
|
208
|
+
/'/g,
|
|
209
|
+
"''"
|
|
210
|
+
)} o
|
|
211
|
+
WHERE ${pg.pgType[dataTypeID]?.includes("[]")
|
|
212
|
+
? `c.id = ANY(o."${name}")`
|
|
213
|
+
: `o."${name}" IS NOT DISTINCT FROM c.id`
|
|
214
|
+
} ) and ${whereQuery || "true"}
|
|
215
|
+
${order} LIMIT $${args.length}::bigint`
|
|
216
|
+
: `with c(id,text) as ( ${clsMeta.original} where ${whereQuery} ${order}) select * from c LIMIT $${args.length}::bigint`;
|
|
217
|
+
|
|
218
|
+
if (sqlSuggest.includes("{{parent}}")) {
|
|
219
|
+
args.push(parent || "");
|
|
220
|
+
sqlSuggest = sqlSuggest.replace(/(['"]?){{parent}}\1/gi, `$${args.length}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (sqlSuggest.includes("{{uid}}")) {
|
|
224
|
+
args.push(uid || "0");
|
|
225
|
+
sqlSuggest = sqlSuggest.replace(/(['"]?){{uid}}\1/gi, `$${args.length}`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const dataNew = await pg
|
|
229
|
+
.query(sqlSuggest, args)
|
|
230
|
+
.then((r) => r.rows || []);
|
|
231
|
+
|
|
232
|
+
// in case id / text = Boolean
|
|
233
|
+
const data = dataNew.filter(
|
|
234
|
+
(el) => Object.hasOwn(el, "id") && Object.hasOwn(el, "text")
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
data
|
|
238
|
+
.filter((el) => el.id === null)
|
|
239
|
+
.forEach((el) =>
|
|
240
|
+
Object.assign(el, { id: "null", text: "Показати порожні значення" })
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// table:column + select
|
|
244
|
+
if (!cls) {
|
|
245
|
+
const { name: clsName = name, type = "select" } =
|
|
246
|
+
getColumnCLS(registry.table_name, name) || {};
|
|
247
|
+
|
|
248
|
+
await metaFormat(
|
|
249
|
+
{
|
|
250
|
+
rows: data,
|
|
251
|
+
table: registry.table_name,
|
|
252
|
+
cls: { text: clsName },
|
|
253
|
+
sufix: true,
|
|
254
|
+
},
|
|
255
|
+
pg
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
time: Date.now() - time,
|
|
260
|
+
limit,
|
|
261
|
+
count: data.length,
|
|
262
|
+
total: clsMeta.count - 0,
|
|
263
|
+
mode: type === "cls" ? "array" : "sql",
|
|
264
|
+
db: clsMeta.db,
|
|
265
|
+
sql: process.platform === 'win32' ? sqlSuggest : undefined,
|
|
266
|
+
data: data.map((el) => ({
|
|
267
|
+
count: el.count,
|
|
268
|
+
id: el.id,
|
|
269
|
+
text: el.text_text || el.text,
|
|
270
|
+
...(el.text_data || {}),
|
|
271
|
+
})),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
time: Date.now() - time,
|
|
277
|
+
limit,
|
|
278
|
+
count: data.length,
|
|
279
|
+
total: clsMeta.count - 0,
|
|
280
|
+
mode: "sql",
|
|
281
|
+
sql: process.platform === 'win32' ? sqlSuggest : undefined,
|
|
282
|
+
data,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { getMeta, getRedis, metaFormat, getFilterSQLParametrized } from "@opengis/fastify-table/utils.js";
|
|
2
4
|
import { populateFilterOptions } from './classifiers.js';
|
|
3
5
|
import { extractVisibleColumns } from './columns.js';
|
|
4
6
|
|
|
@@ -12,6 +14,8 @@ const columnType = {
|
|
|
12
14
|
'timestamp with time zone': 'date',
|
|
13
15
|
};
|
|
14
16
|
|
|
17
|
+
const rclient = getRedis();
|
|
18
|
+
|
|
15
19
|
// eslint-disable-next-line import/prefer-default-export
|
|
16
20
|
export async function handleRegistryRequest({
|
|
17
21
|
settings, query, object_id, offset = 0, limit = 16, page = 1, pg, sql,
|
|
@@ -23,8 +27,24 @@ export async function handleRegistryRequest({
|
|
|
23
27
|
const parsedColumns = Array.isArray(columns) ? columns : JSON.parse(columns);
|
|
24
28
|
const parsedCardColumns = Array.isArray(card) ? card : JSON.parse(card);
|
|
25
29
|
const filtersParsed = Array.isArray(filters) ? filters : JSON.parse(filters);
|
|
26
|
-
const activeFilters = (filtersParsed || []).filter((f) => f.disabled !== true).map(el =>
|
|
27
|
-
|
|
30
|
+
const activeFilters = await Promise.all((filtersParsed || []).filter((f) => f.disabled !== true).map(async (el) => {
|
|
31
|
+
const hash = createHash('md5').update([register_id, el.name, el.data].filter(Boolean).join('-')).digest('hex');
|
|
32
|
+
const redisKey = `${pg?.options?.database}:registry-filter:${hash}`;
|
|
33
|
+
|
|
34
|
+
const r = {
|
|
35
|
+
...el, id: el.name || el.id, type: el.type, api: `/api/gis-suggest/${hash}`
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const unique = await rclient.setnx(redisKey, 1);
|
|
39
|
+
const ttl = await rclient.ttl(redisKey);
|
|
40
|
+
|
|
41
|
+
// skip subsequent calls
|
|
42
|
+
if (!unique && ttl !== -1) {
|
|
43
|
+
return r;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await rclient.set(redisKey, JSON.stringify({ register: register_id, filter: el.name }), 'EX', 15 * 60);
|
|
47
|
+
return r;
|
|
28
48
|
}));
|
|
29
49
|
const { columns: fields1, geom } = await getMeta({ pg, table: table_name });
|
|
30
50
|
|
|
@@ -4,6 +4,7 @@ import gisRegistryList from './functions/gis.registry.list.js';
|
|
|
4
4
|
import gisExport from './functions/gis.export.js';
|
|
5
5
|
import addGisRegistry from './functions/add.registry.js';
|
|
6
6
|
import deleteGisRegistry from './functions/del.registry.js';
|
|
7
|
+
import gisSuggest from './functions/gis.suggest.js';
|
|
7
8
|
|
|
8
9
|
import {
|
|
9
10
|
gisRegistrySchema,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
editGisRegistrySchema,
|
|
13
14
|
deleteGisRegistrySchema,
|
|
14
15
|
gisExportSchema,
|
|
16
|
+
gisSuggestSchema,
|
|
15
17
|
} from './registers.schema.js';
|
|
16
18
|
|
|
17
19
|
const publicParams = { config: { policy: 'L0', package: 'gis' } };
|
|
@@ -76,5 +78,14 @@ async function route(app) {
|
|
|
76
78
|
}, pg);
|
|
77
79
|
return reply.headers(result.headers).send(result.data);
|
|
78
80
|
});
|
|
81
|
+
app.get('/gis-suggest/:token', { ...publicParams, schema: gisSuggestSchema }, async ({ params, query, pg }) => {
|
|
82
|
+
const result = await gisSuggest({
|
|
83
|
+
token: params.token,
|
|
84
|
+
key: query.key,
|
|
85
|
+
val: query.val,
|
|
86
|
+
limit: query.limit,
|
|
87
|
+
}, pg);
|
|
88
|
+
return result;
|
|
89
|
+
});
|
|
79
90
|
}
|
|
80
91
|
export default route;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
const queryFilterSchema = { type: 'string', pattern: '^([\\w\\d_-]+)=([А-Яа-яҐґЄєІіЇї\\d\\w\\s\\/\\[\\]\\(\\)\\{\\}\\|,.!?;:—_=-@%#$&^*+=`~]+)$' };
|
|
2
2
|
const querySearchSchema = { type: 'string', pattern: '^([А-Яа-яҐґЄєІіЇї\\d\\w\\s\\/\\[\\]\\(\\)\\{\\}\\|,.!?;:—_=-@%#$&^*+=`~]+)$' };
|
|
3
3
|
|
|
4
|
+
const keySchema = {
|
|
5
|
+
type: "string",
|
|
6
|
+
pattern: "^([А-Яа-яҐґЄєІіЇї\\d\\w\\s_.-:—!?%@#$,'; ]+)$",
|
|
7
|
+
};
|
|
8
|
+
|
|
4
9
|
const registerSchema = {
|
|
5
10
|
type: 'object',
|
|
6
11
|
additionalProperties: false,
|
|
@@ -243,4 +248,23 @@ export const gisExportSchema = {
|
|
|
243
248
|
},
|
|
244
249
|
};
|
|
245
250
|
|
|
251
|
+
export const gisSuggestSchema = {
|
|
252
|
+
params: {
|
|
253
|
+
type: 'object',
|
|
254
|
+
required: ['token'],
|
|
255
|
+
properties: {
|
|
256
|
+
token: { type: 'string' },
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
querystring: {
|
|
260
|
+
type: 'object',
|
|
261
|
+
additionalProperties: false,
|
|
262
|
+
properties: {
|
|
263
|
+
key: keySchema,
|
|
264
|
+
val: { type: "string", pattern: "([\\d\\w]+)$" },
|
|
265
|
+
limit: { type: 'integer', minimum: 1, maximum: 100 },
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
|
|
246
270
|
export default null;
|
|
@@ -83,10 +83,17 @@ const serviceBodySchema = {
|
|
|
83
83
|
type: 'object',
|
|
84
84
|
additionalProperties: false,
|
|
85
85
|
properties: {
|
|
86
|
-
type: { type: 'string' },
|
|
86
|
+
type: { type: 'string', enum: ['point', 'line', 'polygon'] },
|
|
87
87
|
color: { type: 'string' },
|
|
88
|
-
width: { type: '
|
|
89
|
-
icon: { type: 'string' },
|
|
88
|
+
width: { type: 'integer', minimum: 0, maximum: 10 },
|
|
89
|
+
icon: { type: ['string', 'null'] },
|
|
90
|
+
iconAttr: { type: 'string', pattern: '^([\\d\\w._-]+)$' },
|
|
91
|
+
iconZoom: { type: 'integer', minimum: 0, maximum: 22 },
|
|
92
|
+
cluster: { type: 'boolean' },
|
|
93
|
+
clusterMaxZoom: { type: 'integer', minimum: 0, maximum: 22 },
|
|
94
|
+
label: { type: 'string', pattern: "^\\{[\\d\\w_.-]+\\}$", },
|
|
95
|
+
border: { type: 'number', minimum: 0, maximum: 10 },
|
|
96
|
+
opacity: { type: 'number', minimum: 0, maximum: 1 },
|
|
90
97
|
},
|
|
91
98
|
},
|
|
92
99
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const q = `SELECT
|
|
2
|
+
file_id, entity_id, entity_type, file_path, uploaded_name, ext, size,
|
|
3
|
+
uid, cdate, file_type, ismain, isverified, uid as author, file_status
|
|
4
|
+
FROM crm.files c
|
|
5
|
+
where entity_id=$1
|
|
6
|
+
and file_status<>3
|
|
7
|
+
order by cdate desc`;
|
|
8
|
+
|
|
9
|
+
export default async function listFiles({ entityId }, { pg } = {}) {
|
|
10
|
+
if (!pg) {
|
|
11
|
+
throw new Error('empty pg');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return pg.query(q, [entityId]).then((r) => r.rows);;
|
|
15
|
+
}
|