@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.
Files changed (25) hide show
  1. package/dist/{CardIcon-Cg8bUM4G.js → CardIcon-CMRD1LLh.js} +1 -1
  2. package/dist/{EntityTablePage-DHQGlLRk.js → EntityTablePage-XNNCltAN.js} +1 -1
  3. package/dist/{ExtentOutlineLayer.vue_vue_type_script_setup_true_lang-BIL1ocLY.js → ExtentOutlineLayer.vue_vue_type_script_setup_true_lang-BqUjKTDY.js} +1 -1
  4. package/dist/{HeaderActions.vue_vue_type_script_setup_true_lang-Buff9gTO.js → HeaderActions.vue_vue_type_script_setup_true_lang-BXQ3hKwa.js} +1 -1
  5. package/dist/{MapSettings-De-z7r5t.js → MapSettings-P3h-vHn3.js} +1 -1
  6. package/dist/{RastersTablePage-Cdgkri9K.js → RastersTablePage-DnhGZLiH.js} +1 -1
  7. package/dist/{cartocss-BT50SVhy.js → cartocss-BkrOeQg4.js} +5 -5
  8. package/dist/{import-utils-CMWiuRPp.js → import-utils-BCATsbB5.js} +1 -1
  9. package/dist/{index-CHFLgCGW.js → index-cUagVqgA.js} +10 -10
  10. package/dist/index.css +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/index.umd.cjs +19 -19
  13. package/dist/{raster-dSEb1SIG.js → raster-CP0tUp3j.js} +3 -3
  14. package/dist/{register-D7Puupgo.js → register-Cr-uQcby.js} +3 -3
  15. package/dist/{service-Cz6T0O3Z.js → service-DwJaKrOd.js} +829 -832
  16. package/dist/{vs-datatable-DqVncrSg.js → vs-datatable-B9WoVk-b.js} +1 -1
  17. package/package.json +1 -1
  18. package/server/routes/map/functions/mapFormat.js +13 -12
  19. package/server/routes/registers/functions/getTableColumnMeta.js +131 -0
  20. package/server/routes/registers/functions/gis.suggest.js +284 -0
  21. package/server/routes/registers/functions/handleRegistryRequest.js +23 -3
  22. package/server/routes/registers/registers.route.js +11 -0
  23. package/server/routes/registers/registers.schema.js +24 -0
  24. package/server/routes/services/services.schema.js +10 -3
  25. 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-CHFLgCGW.js";
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/gis",
3
- "version": "0.2.175",
3
+ "version": "0.2.177",
4
4
  "type": "module",
5
5
  "author": "Softpro",
6
6
  "main": "./dist/index.js",
@@ -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
- return { error: 'not enough params: lat / lng' };
36
+ throw new Error('not enough params: lat / lng');
35
37
  }
36
38
 
37
39
  if (!cartocss.config?.length && !cartocss.card_table) {
38
- return { error: 'empty cartocss.config' };
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
- return { error: 'object not found', code: 404 };
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
- return { error: 'missing source_path', code: 400 };
111
+ throw BadRequestError('missing source_path');
110
112
  }
111
113
 
112
114
  const pk = pg.pk?.[tname];
113
115
 
114
116
  if (!pk) {
115
- return { error: 'missing pk', code: 400 };
117
+ throw BadRequestError('missing pk');
116
118
  }
117
119
 
118
120
  if (!columns) {
119
- return { error: 'columns setting not found', code: 404 };
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
- return { error: 'object not found', code: 404 };
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 { getMeta, getFilterSQL, metaFormat, getFilterSQLParametrized } from "@opengis/fastify-table/utils.js";
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
- ...el, id: el.name || el.id, type: el.type, api: `/api/suggest/${table_name}:${el.name}${el.data ? `?sel=${el.data}` : ''}`,
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: 'number' },
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
+ }