@opengis/fastify-table 2.5.21 → 2.5.23

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 (46) hide show
  1. package/dist/server/plugins/file/uploadMultiPart.d.ts.map +1 -1
  2. package/dist/server/plugins/file/uploadMultiPart.js +7 -4
  3. package/dist/server/plugins/file/utils/inspectContent.d.ts +6 -0
  4. package/dist/server/plugins/file/utils/inspectContent.d.ts.map +1 -0
  5. package/dist/server/plugins/file/utils/inspectContent.js +58 -0
  6. package/dist/server/plugins/pg/funcs/autoIndex.d.ts.map +1 -1
  7. package/dist/server/plugins/pg/funcs/autoIndex.js +8 -3
  8. package/dist/server/plugins/pg/funcs/pool.d.ts.map +1 -1
  9. package/dist/server/plugins/pg/funcs/pool.js +3 -0
  10. package/dist/server/plugins/table/funcs/getFilterSQL/index.js +1 -1
  11. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/formatValue.d.ts +2 -0
  12. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/formatValue.d.ts.map +1 -0
  13. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/formatValue.js +211 -0
  14. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getFilterList.d.ts +8 -0
  15. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getFilterList.d.ts.map +1 -0
  16. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getFilterList.js +90 -0
  17. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getFilterQuery.d.ts +17 -0
  18. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getFilterQuery.d.ts.map +1 -0
  19. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getFilterQuery.js +90 -0
  20. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getRangeQuery.d.ts +17 -0
  21. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getRangeQuery.d.ts.map +1 -0
  22. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getRangeQuery.js +340 -0
  23. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getTableSql.d.ts +9 -0
  24. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getTableSql.d.ts.map +1 -0
  25. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getTableSql.js +40 -0
  26. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/index.d.ts +19 -0
  27. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/index.d.ts.map +1 -0
  28. package/dist/server/plugins/table/funcs/getFilterSQLParametrized/index.js +156 -0
  29. package/dist/server/routes/table/functions/getData.d.ts.map +1 -1
  30. package/dist/server/routes/table/functions/getData.js +35 -29
  31. package/dist/server/routes/table/functions/prettify.d.ts +2 -0
  32. package/dist/server/routes/table/functions/prettify.d.ts.map +1 -0
  33. package/dist/server/routes/table/functions/prettify.js +11 -0
  34. package/dist/utils.d.ts +2 -0
  35. package/dist/utils.d.ts.map +1 -1
  36. package/dist/utils.js +2 -0
  37. package/package.json +1 -1
  38. package/dist/server/plugins/policy/funcs/checkSQL.d.ts +0 -8
  39. package/dist/server/plugins/policy/funcs/checkSQL.d.ts.map +0 -1
  40. package/dist/server/plugins/policy/funcs/checkSQL.js +0 -19
  41. package/dist/server/plugins/policy/sqlInjection.d.ts +0 -3
  42. package/dist/server/plugins/policy/sqlInjection.d.ts.map +0 -1
  43. package/dist/server/plugins/policy/sqlInjection.js +0 -33
  44. package/dist/server/plugins/table/funcs/getData.d.ts +0 -2
  45. package/dist/server/plugins/table/funcs/getData.d.ts.map +0 -1
  46. package/dist/server/plugins/table/funcs/getData.js +0 -25
@@ -1 +1 @@
1
- {"version":3,"file":"uploadMultiPart.d.ts","sourceRoot":"","sources":["../../../../server/plugins/file/uploadMultiPart.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,KAAK;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA2CD,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,mBAkBvE;AAED,wBAAsB,gBAAgB,CACpC,EACE,IAAI,EAAE,QAAQ,EACd,MAA2C,EAC3C,MAAM,GACP,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACpD,MAAM,MAAW,mBA+BlB;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,GAAG,EACR,EACE,UAAU,EACV,GAAG,EACH,MAAM,EACN,gBAAwB,GACzB,GAAE;IACD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CACvB,GACL,OAAO,CAAC,KAAK,CAAC,CAwHhB;AAED,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"uploadMultiPart.d.ts","sourceRoot":"","sources":["../../../../server/plugins/file/uploadMultiPart.ts"],"names":[],"mappings":"AA8BA,MAAM,WAAW,KAAK;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA2CD,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,mBAkBvE;AAED,wBAAsB,gBAAgB,CACpC,EACE,IAAI,EAAE,QAAQ,EACd,MAA2C,EAC3C,MAAM,GACP,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACpD,MAAM,MAAW,mBA+BlB;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,GAAG,EACR,EACE,UAAU,EACV,GAAG,EACH,MAAM,EACN,gBAAwB,GACzB,GAAE;IACD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CACvB,GACL,OAAO,CAAC,KAAK,CAAC,CA0HhB;AAED,eAAe,eAAe,CAAC"}
@@ -4,7 +4,6 @@ import { existsSync } from "node:fs";
4
4
  import { mkdir, writeFile, readdir } from "node:fs/promises";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { imageSize } from "image-size";
7
- import { fileTypeFromBuffer } from "file-type";
8
7
  import { ListObjectsV2Command } from "@aws-sdk/client-s3";
9
8
  import config from "../../../config.js";
10
9
  import s3Client from "./providers/s3/client.js";
@@ -12,7 +11,8 @@ import providers from "./providers/index.js";
12
11
  import { all, images } from "./utils/allowedExtensions.js";
13
12
  import grpc from "../grpc/grpc.js";
14
13
  import getFileType from "./utils/getFileType.js";
15
- import { BadRequestError, ConflictError, PayloadTooLargeError, } from "../../../errors.js";
14
+ import { BadRequestError, ForbiddenError, ConflictError, PayloadTooLargeError, } from "../../../errors.js";
15
+ import inspectContent from "./utils/inspectContent.js";
16
16
  const { resizeImage } = grpc();
17
17
  const { resizeImageMinSize = 5 } = config; // resize images >= 5 MB by default
18
18
  async function writeFileToDisk(file, buffer) {
@@ -82,6 +82,9 @@ export async function uploadMultiPart(req, { extensions, raw, subdir, originalFi
82
82
  throw new Error("parts already processed");
83
83
  }
84
84
  const value = part?.fields?.file || part;
85
+ if (Array.isArray(value)) {
86
+ throw new ForbiddenError("upload of multiple files at once not allowed");
87
+ }
85
88
  if (!value?.file?.on || !value?.filename) {
86
89
  throw new Error("upload error");
87
90
  }
@@ -108,8 +111,8 @@ export async function uploadMultiPart(req, { extensions, raw, subdir, originalFi
108
111
  if (!fileBuffer?.length) {
109
112
  throw new BadRequestError("file buffer is empty");
110
113
  }
111
- const { ext: fileExtension } = (await fileTypeFromBuffer(fileBuffer)) || {};
112
114
  const nameExt = ext.substring(1);
115
+ const { isValid } = await inspectContent(fileBuffer, nameExt);
113
116
  // return buffer for custom upload
114
117
  if (raw) {
115
118
  return {
@@ -120,7 +123,7 @@ export async function uploadMultiPart(req, { extensions, raw, subdir, originalFi
120
123
  fields: value.fields,
121
124
  };
122
125
  }
123
- if (fileExtension !== nameExt) {
126
+ if (!isValid) {
124
127
  throw new ConflictError("file type spoofing attempt detected");
125
128
  }
126
129
  const yearMonthDay = subdir != null ? "" : new Date().toISOString().split("T")[0];
@@ -0,0 +1,6 @@
1
+ export default function inspectContent(buffer: Buffer, expectedExtension: string): Promise<{
2
+ ext: any;
3
+ mime: any;
4
+ isValid: boolean;
5
+ }>;
6
+ //# sourceMappingURL=inspectContent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspectContent.d.ts","sourceRoot":"","sources":["../../../../../server/plugins/file/utils/inspectContent.ts"],"names":[],"mappings":"AAwCA,wBAA8B,cAAc,CAC1C,MAAM,EAAE,MAAM,EACd,iBAAiB,EAAE,MAAM;;;;GA+B1B"}
@@ -0,0 +1,58 @@
1
+ import { fileTypeFromBuffer } from "file-type";
2
+ import { BadRequestError } from "../../../../errors.js";
3
+ import applyHook from "../../hook/applyHook.js";
4
+ const handlers = {
5
+ pdf: (buffer) => {
6
+ const head = buffer.subarray(0, 1024).toString("latin1");
7
+ if (head.includes("%PDF-")) {
8
+ return {
9
+ ext: "pdf",
10
+ mime: "application/pdf",
11
+ };
12
+ }
13
+ },
14
+ json: (buffer) => {
15
+ const head = buffer.subarray(0, 1).toString("utf8").trimStart();
16
+ if (["{", "["].includes(head)) {
17
+ return {
18
+ ext: "json",
19
+ mime: "application/json",
20
+ };
21
+ }
22
+ },
23
+ geojson: (buffer) => {
24
+ const text = buffer.subarray(0, 64).toString("utf8").trimStart();
25
+ if (text.startsWith("{") &&
26
+ text.includes("features") &&
27
+ text.includes("FeatureCollection")) {
28
+ return {
29
+ ext: "geojson",
30
+ mime: "application/geo+json",
31
+ };
32
+ }
33
+ },
34
+ };
35
+ export default async function inspectContent(buffer, expectedExtension) {
36
+ if (!expectedExtension) {
37
+ throw new BadRequestError("not enough params: expectedExtension");
38
+ }
39
+ if (!Buffer.isBuffer(buffer) || !buffer.length) {
40
+ throw new BadRequestError("Invalid buffer");
41
+ }
42
+ // custom inspection of extensions like kml, svg etc
43
+ const hookData = (await applyHook("onInspectContent", {
44
+ buffer,
45
+ expectedExtension,
46
+ }));
47
+ if (hookData) {
48
+ return hookData;
49
+ }
50
+ const { ext, mime } = (await fileTypeFromBuffer(buffer)) || {};
51
+ // ? geojson, signed pdf, kml, svg etc - not recognized - inspecting the content
52
+ if (!ext && handlers[expectedExtension]) {
53
+ const { ext: ext2, mime: mime2 } = (await handlers[expectedExtension](buffer)) || {};
54
+ return { ext: ext2, mime: mime2, isValid: ext2 === expectedExtension };
55
+ }
56
+ // ? simple xml, pdf are recognized via file-type
57
+ return { ext, mime, isValid: ext === expectedExtension };
58
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"autoIndex.d.ts","sourceRoot":"","sources":["../../../../../server/plugins/pg/funcs/autoIndex.ts"],"names":[],"mappings":"AAaA,wBAA8B,SAAS,CAAC,EACtC,KAAK,EACL,OAAO,EAAE,OAAO,EAChB,EAAoB,EACpB,GAAG,GACJ,EAAE,GAAG,6CAwJL"}
1
+ {"version":3,"file":"autoIndex.d.ts","sourceRoot":"","sources":["../../../../../server/plugins/pg/funcs/autoIndex.ts"],"names":[],"mappings":"AAaA,wBAA8B,SAAS,CAAC,EACtC,KAAK,EACL,OAAO,EAAE,OAAO,EAChB,EAAoB,EACpB,GAAG,GACJ,EAAE,GAAG,6CAmKL"}
@@ -73,8 +73,8 @@ export default async function autoIndex({ table, columns: filter1, pg = pgClient
73
73
  if (exists?.length <= (isGin ? 2 : 1)) {
74
74
  // console.log(`${name} - ${el.datatype}`);
75
75
  const suffix = isGin ? "gin_trgm_ops" : "";
76
- qIndex.push(`CREATE INDEX if not exists ${name} ON ${el.nspname}.${el.relname} USING ${indexUsing} (${el.aname} ${suffix})`);
77
- if (isGin) {
76
+ qIndex.push(`CREATE INDEX if not exists ${name} ON ${el.nspname}.${el.relname} USING ${indexUsing} (${el.aname} ${el.datatype?.endsWith?.("[]") ? "" : suffix})`);
77
+ if (isGin && !el.datatype?.endsWith?.("[]")) {
78
78
  qIndex.push(`CREATE INDEX if not exists ${name}_lower ON ${el.nspname}.${el.relname} USING ${indexUsing} (lower(${el.aname}) ${suffix})`);
79
79
  }
80
80
  if (indexUsing === "gist") {
@@ -92,8 +92,13 @@ export default async function autoIndex({ table, columns: filter1, pg = pgClient
92
92
  loadedIndex[redisKey] = 1;
93
93
  if (!qIndex.length)
94
94
  return null;
95
+ if (process.env.trace === "true") {
96
+ console.log("autoindex", qIndex.join(";\n"));
97
+ }
95
98
  qIndex
96
99
  .filter((el, idx, arr) => arr.indexOf(el) === idx)
97
- .map((el) => pg.query(el).catch((err) => console.log(err.toString())));
100
+ .map((el) => pg.query(el).catch((err) => {
101
+ console.error("autoindex error", el, err.toString());
102
+ }));
98
103
  return "ok";
99
104
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../../../../server/plugins/pg/funcs/pool.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,CAAC;yBAE7B,QAAO,GAAQ;AAA/B,wBAgEE"}
1
+ {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../../../../server/plugins/pg/funcs/pool.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,CAAC;yBAE7B,QAAO,GAAQ;AAA/B,wBAgEE"}
@@ -2,7 +2,10 @@ import pg from "pg";
2
2
  import logger from "../../../plugins/logger/getLogger.js";
3
3
  const { types } = pg;
4
4
  types.setTypeParser(1082, (stringValue) => stringValue);
5
+ // ALTER ROLE myuser SET timezone = 'Europe/Kyiv'; - for GET requests
5
6
  types.setTypeParser(1114, (stringValue) => stringValue);
7
+ // optional - now frontend does not support timestamp in such format
8
+ // types.setTypeParser(1184, (stringValue) => stringValue);
6
9
  import pgClients from "../pgClients.js";
7
10
  import init from "./init.js";
8
11
  import config from "../../../../config.js";
@@ -170,7 +170,7 @@ export default async function getFilterSQL({ table, filter, pg = pgClients.clien
170
170
  if (Array.isArray(cls?.arr) && cls.arr.length) {
171
171
  Object.assign(el, {
172
172
  cls: el.data,
173
- options: cls,
173
+ options: cls.arr,
174
174
  select: `select code, name from admin.cls where parent='${el.data}'`,
175
175
  });
176
176
  }
@@ -0,0 +1,2 @@
1
+ export default function formatValue({ table, filter, name, value, dataTypeID, uid, optimize, args: argsParams, }: any, pg?: any): any;
2
+ //# sourceMappingURL=formatValue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatValue.d.ts","sourceRoot":"","sources":["../../../../../../server/plugins/table/funcs/getFilterSQLParametrized/formatValue.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,OAAO,UAAU,WAAW,CACjC,EACE,KAAK,EACL,MAAW,EACX,IAAI,EACJ,KAAK,EACL,UAAU,EACV,GAAS,EACT,QAAQ,EACR,IAAI,EAAE,UAAU,GACjB,EAAE,GAAG,EACN,EAAE,MAAmB,OAyPtB"}
@@ -0,0 +1,211 @@
1
+ import pgClients from "../../../pg/pgClients.js";
2
+ import applyHookSync from "../../../hook/applyHookSync.js";
3
+ import getRangeQuery from "./getRangeQuery.js";
4
+ const selectFilterTypes = [
5
+ "check",
6
+ "autocomplete",
7
+ "tags",
8
+ "avatar",
9
+ "radio",
10
+ "select",
11
+ "button",
12
+ ];
13
+ export default function formatValue({ table, filter = {}, name, value, dataTypeID, uid = "1", optimize, args: argsParams, }, pg = pgClients.client) {
14
+ const { extra, sql, select, strict, options, optionsFromColumns } = filter;
15
+ const pk = pg?.pk && table ? pg.pk[table] : undefined;
16
+ const filterType = filter.type?.toLowerCase?.() || "text";
17
+ const args = Array.from(argsParams || []);
18
+ const sval = typeof value === "string"
19
+ ? `%${decodeURIComponent(value.replace(/%/g, "%25")).replace(/%/g, "\\%")}%`
20
+ : undefined;
21
+ if (filterType === "text" && typeof value === "string" && filter?.columns) {
22
+ args.push(sval);
23
+ const columns = filter.columns.split(",");
24
+ const query = `(${columns
25
+ .map((el) => `"${el}"::text ilike $${args.length}`)
26
+ .join(" or ")})`;
27
+ return { op: "~", query, values: [sval] };
28
+ }
29
+ const fieldType = pg?.pgType?.[dataTypeID] ||
30
+ pg?.pgType?.[{ date: 1114 }[filterType] || 25];
31
+ const hookData = applyHookSync("onFormatValue", {
32
+ pg,
33
+ pk,
34
+ table,
35
+ name,
36
+ value,
37
+ filterType,
38
+ fieldType,
39
+ filter,
40
+ dataTypeID,
41
+ uid,
42
+ optimize,
43
+ });
44
+ if (hookData) {
45
+ return hookData?.query ? hookData : { op: "=", query: hookData };
46
+ }
47
+ if (!name ||
48
+ !value ||
49
+ (!dataTypeID && !extra && !sql && !options) ||
50
+ extra?.input?.toLowerCase?.() === "datatable" ||
51
+ (!selectFilterTypes.includes(filterType) &&
52
+ options?.find?.((el) => el?.sql))) {
53
+ return {};
54
+ }
55
+ // my rows
56
+ if (value === "me" && uid) {
57
+ args.push(uid);
58
+ return { op: "=", query: `${name}::text = $${args.length}`, values: [uid] };
59
+ }
60
+ const matchNull = { null: "is null", notnull: "is not null" }[value];
61
+ // geometry
62
+ if (["geometry"].includes(fieldType || "")) {
63
+ if (matchNull) {
64
+ return { op: "nullable", query: `${name} ${matchNull}` };
65
+ }
66
+ const bbox = value?.[0]?.split?.("_");
67
+ if (bbox?.length !== 4) {
68
+ return {};
69
+ }
70
+ const query = ` ${name} && 'box(${bbox[0]} ${bbox[1]},${bbox[2]} ${bbox[3]})'::box2d `;
71
+ return { op: "&&", query };
72
+ }
73
+ const op = filterType === "text" && !strict ? "~" : "=";
74
+ // text
75
+ if (["text"].includes(filterType)) {
76
+ // with sql subquery
77
+ if (typeof sql === "string") {
78
+ args.push(value);
79
+ const querysql = sql
80
+ .replace(/= ?any\(\$1\)/g, `::text=any($${args.length}::text[])`)
81
+ .replace(/\$1/g, `$${args.length}`);
82
+ return { op: "=", query: querysql, values: [[value]] };
83
+ }
84
+ // extra table, example: crm.extra_data
85
+ if (extra?.table && pk) {
86
+ args.push(name);
87
+ if (!matchNull)
88
+ args.push(value);
89
+ const query = `${pk} in (select object_id from ${extra.table} where property_key=$${args.length - 1} and value_text ${matchNull || `${op} $${args.length}`} )`;
90
+ return { op, query, values: matchNull ? [name] : [name, value] };
91
+ }
92
+ // default
93
+ if (!select) {
94
+ const value1 = op === "~" ? `%${value}%` : value;
95
+ args.push(value1);
96
+ const query = `${name}::text ${op === "~" ? "ilike" : "="} $${args.length}`;
97
+ return {
98
+ op,
99
+ query,
100
+ values: [value1],
101
+ };
102
+ }
103
+ }
104
+ // nullable
105
+ if (matchNull) {
106
+ const query = `${name} ${matchNull}`;
107
+ return { op, query };
108
+ }
109
+ // boolean
110
+ if (fieldType === "boolean" && ["true", "false"].includes(value)) {
111
+ const query = `${name} IS ${value === "true" ? "TRUE" : "FALSE"}`;
112
+ return { op, query };
113
+ }
114
+ // select / cls - any filter type w/ out sql subquery
115
+ if (value &&
116
+ select &&
117
+ Array.isArray(options) &&
118
+ !options.find((el) => el?.sql)) {
119
+ const value1 = value.split(",");
120
+ // by id (checkbox)
121
+ if (options.find((el) => el?.id && value1.includes(el.id))) {
122
+ args.push(value1);
123
+ if (fieldType && fieldType.endsWith("[]")) {
124
+ const query = `EXISTS ( SELECT 1 FROM unnest(${name}) AS elem WHERE elem in (with q(id, name) as (${select}) select id from q where id = any($${args.length}) ) )`;
125
+ return {
126
+ op,
127
+ query,
128
+ values: [value1],
129
+ };
130
+ }
131
+ const query = `${name}::text in ( ( with q(id,name) as (${select}) select id from q where id = any($${args.length}) ) )`;
132
+ return {
133
+ op,
134
+ query,
135
+ values: [value1],
136
+ };
137
+ }
138
+ // by text
139
+ args.push(`%${value1[0]}%`);
140
+ return {
141
+ op,
142
+ query: `${name}::text in ( ( with q(id,name) as (${select}) select id from q where name::text ilike $${args.length} ) )`,
143
+ values: [`%${value1[0]}%`],
144
+ };
145
+ }
146
+ // date: 01.01.2024-31.12.2024 / number range: 100-500
147
+ if (["date", "datepicker", "range"].includes(filterType)) {
148
+ const { query, values } = getRangeQuery({
149
+ value,
150
+ name,
151
+ fieldType,
152
+ filterType,
153
+ sql,
154
+ extra,
155
+ pk,
156
+ args,
157
+ });
158
+ return { op: "between", query, values };
159
+ }
160
+ /* select query - from admin.cls / filter options */
161
+ if (selectFilterTypes.includes(filterType)) {
162
+ // multiple checks with predefined query
163
+ if (options?.find?.((el) => el?.sql)) {
164
+ const query = options
165
+ .filter((el) => value.split(",").includes(el.id?.toString?.()))
166
+ .map((el) => el.sql || "false")
167
+ .join(" and ") || "false";
168
+ return { op: "=", query };
169
+ }
170
+ // with sql subquery
171
+ if (sql) {
172
+ args.push(value);
173
+ const querysql = sql
174
+ .replace(/= ?any\(\$1\)/g, `::text=any($${args.length}::text[])`)
175
+ .replace(/\$1/g, `$${args.length}`);
176
+ return { op: "=", query: querysql, values: [[value]] };
177
+ }
178
+ // extra table, example: crm.extra_data
179
+ if (extra?.table && pk) {
180
+ args.push(name);
181
+ if (!matchNull)
182
+ args.push(value);
183
+ const query = `${pk} in (select object_id from ${extra.table} where property_key=$${args.length - 1} and value_text ${matchNull || `=$${args.length}`} )`;
184
+ return { op: "=", query, values: matchNull ? [name] : [name, value] };
185
+ }
186
+ // if pseudo-suggest column contain commas
187
+ if (optionsFromColumns && options?.length) {
188
+ const optionValues = options.map((e) => e.id);
189
+ const vals = optionValues.filter((e) => value.includes(e));
190
+ args.push(vals);
191
+ return {
192
+ op: "=",
193
+ query: `${name}::text=any($${args.length}::text[])`,
194
+ values: vals,
195
+ };
196
+ }
197
+ // default
198
+ args.push(value);
199
+ if (fieldType?.includes("[]")) {
200
+ const query = `${name}::text[] && $${args.length}::text[]`;
201
+ return {
202
+ op: "=",
203
+ query,
204
+ values: [value],
205
+ };
206
+ }
207
+ const query = `${name}::text=any($${args.length})`;
208
+ return { op: "=", query, values: [[value]] };
209
+ }
210
+ return {};
211
+ }
@@ -0,0 +1,8 @@
1
+ export default function getFilterList({ table, uid, filterList, extraDataTable, extraColumns, }: {
2
+ table: string;
3
+ uid?: string;
4
+ filterList?: Record<string, any>[];
5
+ extraDataTable?: string;
6
+ extraColumns: Record<string, any>[];
7
+ }, pg?: any): Promise<any[]>;
8
+ //# sourceMappingURL=getFilterList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getFilterList.d.ts","sourceRoot":"","sources":["../../../../../../server/plugins/table/funcs/getFilterSQLParametrized/getFilterList.ts"],"names":[],"mappings":"AAyBA,wBAA8B,aAAa,CACzC,EACE,KAAK,EACL,GAAG,EACH,UAAU,EACV,cAAc,EACd,YAAiB,GAClB,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;CACrC,EACD,EAAE,MAAmB,kBAqFtB"}
@@ -0,0 +1,90 @@
1
+ import getSelect from "../getSelect.js";
2
+ import getTemplate from "../getTemplate.js";
3
+ import getMeta from "../../../pg/funcs/getMeta.js";
4
+ import pgClients from "../../../pg/pgClients.js";
5
+ import { getTableColumnMeta } from "../../../../routes/table/controllers/suggest.js";
6
+ import getFilter from "../getFilter.js";
7
+ const reply1 = {
8
+ response: {},
9
+ // redirect: (txt) => txt,
10
+ redirect: (txt) => Object.assign(reply1.response, {
11
+ body: txt,
12
+ statusCode: reply1.response.statusCode || 200,
13
+ }),
14
+ status: (statusCode) => {
15
+ Object.assign(reply1.response, { status: statusCode });
16
+ return reply1;
17
+ },
18
+ send: (txt) => Object.assign(reply1.response, txt),
19
+ };
20
+ export default async function getFilterList({ table, uid, filterList, extraDataTable, extraColumns = [], }, pg = pgClients.client) {
21
+ const filterResponse = await getFilter({
22
+ pg,
23
+ table,
24
+ user: { uid },
25
+ }, null);
26
+ return Promise.all((filterList || [])
27
+ .concat(filterResponse?.inline || [])
28
+ .concat(filterResponse?.custom || [])
29
+ .concat(filterResponse?.state || [])
30
+ .concat(filterResponse?.list || [])
31
+ ?.filter((el) => el.id || el.name)
32
+ ?.map(async (el) => {
33
+ Object.assign(el, { name: el.name || el.id });
34
+ if (el.name && extraColumns.find((item) => item?.name === el?.name)) {
35
+ Object.assign(el, {
36
+ extra: {
37
+ table: extraDataTable,
38
+ input: extraColumns.find((item) => item?.name === el?.name)?.type,
39
+ },
40
+ });
41
+ }
42
+ if (el.api?.split?.("/")?.[2] === "suggest" &&
43
+ el.api?.split?.("/")?.[3]?.split?.(":")?.length === 2 &&
44
+ !el.api.includes("?") // skip /suggest/table:column?sel=selectName
45
+ ) {
46
+ const [table, column] = el.api.split("/")[3].split(":");
47
+ const body = await getTemplate("table", table);
48
+ const columns = await getMeta({ pg, table }).then((el) => el?.columns || []);
49
+ const { arr, original } = columns?.find((el) => el.name === column)
50
+ ? (await getTableColumnMeta({
51
+ table: body?.table || table,
52
+ template: body ? table : undefined,
53
+ column: column?.split?.("?")?.shift?.(),
54
+ selectName: body?.columns?.find?.((el) => el.name === column)
55
+ ?.data ||
56
+ body?.meta?.cls?.[column] ||
57
+ column?.split?.("?")?.pop?.(),
58
+ pg,
59
+ })) || {}
60
+ : { arr: null, original: null };
61
+ const arr1 = arr ||
62
+ (original && pg?.queryCache
63
+ ? await pg
64
+ .queryCache(original, { table: body?.table || table })
65
+ .catch((err) => console.error("getFilterSQL error", err.toString()))
66
+ .then((el) => el?.rows || [])
67
+ : undefined);
68
+ Object.assign(el, {
69
+ optionsFromColumns: true,
70
+ options: arr1,
71
+ select: original,
72
+ });
73
+ }
74
+ if (!el?.data)
75
+ return el;
76
+ // const cls = await getTemplate(['cls', 'select'], el.data); // only git cls
77
+ const cls = await getSelect(el.data, pg); // git + db cls
78
+ if (Array.isArray(cls?.arr) && cls.arr.length) {
79
+ Object.assign(el, {
80
+ cls: el.data,
81
+ options: cls.arr,
82
+ select: `select code, name from admin.cls where parent='${el.data}'`,
83
+ });
84
+ }
85
+ else if (typeof (cls?.sql || cls) === "string") {
86
+ Object.assign(el, { select: cls?.sql || cls });
87
+ }
88
+ return el;
89
+ }));
90
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @param {Number} opt.json - (1|0) 1 - Результат - Object, 0 - String
3
+ * @param {String} opt.query - запит до таблиці
4
+ * @param {String} opt.hash - інформація з хешу по запиту
5
+ */
6
+ declare function getFilterQuery({ filter: filterStr, table, tableSQL, fields, filterList, uid, objectId, args, }: {
7
+ filter: string | undefined;
8
+ table: string;
9
+ tableSQL: any[];
10
+ fields?: any[];
11
+ filterList?: any[];
12
+ uid?: string | number;
13
+ objectId?: string;
14
+ args?: any[];
15
+ }, pg?: any): any;
16
+ export default getFilterQuery;
17
+ //# sourceMappingURL=getFilterQuery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getFilterQuery.d.ts","sourceRoot":"","sources":["../../../../../../server/plugins/table/funcs/getFilterSQLParametrized/getFilterQuery.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AAEH,iBAAS,cAAc,CACrB,EACE,MAAM,EAAE,SAAc,EACtB,KAAU,EACV,QAAa,EACb,MAAM,EACN,UAAU,EACV,GAAG,EACH,QAAQ,EACR,IAAI,GACL,EAAE;IACD,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,GAAG,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;IACf,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;CACd,EACD,EAAE,MAAmB,OA4GtB;AAED,eAAe,cAAc,CAAC"}
@@ -0,0 +1,90 @@
1
+ /* eslint-disable no-continue */
2
+ import pgClients from "../../../pg/pgClients.js";
3
+ import formatValue from "./formatValue.js";
4
+ /**
5
+ * @param {Number} opt.json - (1|0) 1 - Результат - Object, 0 - String
6
+ * @param {String} opt.query - запит до таблиці
7
+ * @param {String} opt.hash - інформація з хешу по запиту
8
+ */
9
+ function getFilterQuery({ filter: filterStr = "", table = "", tableSQL = [], fields, filterList, uid, objectId, args, }, pg = pgClients.client) {
10
+ // if (!filterStr) return null; // filter list API
11
+ const mainOperators = ["=", "~", ">", "<"];
12
+ const filterQueryArray = decodeURIComponent(filterStr
13
+ ?.replace?.(/%/g, "%25")
14
+ ?.replace?.(/%/g, "\\%")
15
+ ?.replace?.(/(^,)|(,$)/g, ""))
16
+ ?.replace?.(/'/g, "''")
17
+ ?.split?.(/[;|]/) || [];
18
+ // default filter value form checkboxes, autocomplete or sql-defined only - if not called from card
19
+ const filterDefaultQueryArray = !objectId
20
+ ? filterList
21
+ ?.filter?.((el) => el.name &&
22
+ !filterStr?.includes?.(`${el.name}=`) &&
23
+ el.default &&
24
+ (el.options || el.sql))
25
+ ?.map?.((el) => `${el.name}=${el.default}`) || []
26
+ : [];
27
+ // concat default + request filters
28
+ const arr = filterQueryArray.concat(filterDefaultQueryArray).filter(Boolean);
29
+ const resultList = [];
30
+ for (let i = 0; i < arr.length; i += 1) {
31
+ const item = arr[i];
32
+ if (!item) {
33
+ continue;
34
+ }
35
+ const splitby = mainOperators?.find((el) => item.indexOf(el) !== -1) || "=";
36
+ const [name] = item.split(splitby);
37
+ // skip already added filter
38
+ if (resultList.find((el) => el.name === name)) {
39
+ continue;
40
+ }
41
+ // filter
42
+ const filter = filterList?.find?.((el) => el.type && el.name === name) || {
43
+ type: "text",
44
+ };
45
+ const { strict, extra } = filter;
46
+ // find all value
47
+ const value = arr
48
+ .filter((el) => el.split(splitby)?.[0] === name)
49
+ .map((el) => el.substring((name?.length || 0) + 1))
50
+ .join(",");
51
+ const optimize = fields?.find((el) => el.name === name)
52
+ ? null
53
+ : tableSQL.find((el) => el.name === name);
54
+ // find field and skip not exists
55
+ const { dataTypeID } = fields?.find((el) => el.name === name) ||
56
+ fields?.find((el) => el.name === optimize?.pk) ||
57
+ {};
58
+ // format query
59
+ const { op, query, values: filterValues, } = formatValue({
60
+ table,
61
+ filter,
62
+ name,
63
+ value,
64
+ dataTypeID,
65
+ uid,
66
+ optimize,
67
+ args,
68
+ }, pg) || {};
69
+ if (!query) {
70
+ continue;
71
+ }
72
+ if (args &&
73
+ filterValues &&
74
+ Array.isArray(filterValues) &&
75
+ filterValues.length) {
76
+ filterValues.forEach((val) => args.push(val));
77
+ }
78
+ resultList.push({
79
+ name,
80
+ value,
81
+ values: filterValues,
82
+ query,
83
+ operator: op,
84
+ strict,
85
+ extra,
86
+ });
87
+ }
88
+ return resultList;
89
+ }
90
+ export default getFilterQuery;
@@ -0,0 +1,17 @@
1
+ export default function getRangeQuery({ value, name, fieldType, filterType, sql, extra, pk, args: argsParams, }: {
2
+ value?: any;
3
+ name?: string;
4
+ fieldType?: string;
5
+ filterType?: string;
6
+ sql?: any;
7
+ extra?: any;
8
+ pk?: string;
9
+ args: any[];
10
+ }): {
11
+ query: string;
12
+ values?: undefined;
13
+ } | {
14
+ query: any;
15
+ values: any[];
16
+ };
17
+ //# sourceMappingURL=getRangeQuery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getRangeQuery.d.ts","sourceRoot":"","sources":["../../../../../../server/plugins/table/funcs/getFilterSQLParametrized/getRangeQuery.ts"],"names":[],"mappings":"AA0NA,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EACpC,KAAK,EACL,IAAI,EACJ,SAAS,EACT,UAAU,EACV,GAAG,EACH,KAAK,EACL,EAAE,EACF,IAAI,EAAE,UAAU,GACjB,EAAE;IACD,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,GAAG,EAAE,CAAC;CACb;;;;;;EAkNA"}