@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.
- package/dist/server/plugins/file/uploadMultiPart.d.ts.map +1 -1
- package/dist/server/plugins/file/uploadMultiPart.js +7 -4
- package/dist/server/plugins/file/utils/inspectContent.d.ts +6 -0
- package/dist/server/plugins/file/utils/inspectContent.d.ts.map +1 -0
- package/dist/server/plugins/file/utils/inspectContent.js +58 -0
- package/dist/server/plugins/pg/funcs/autoIndex.d.ts.map +1 -1
- package/dist/server/plugins/pg/funcs/autoIndex.js +8 -3
- package/dist/server/plugins/pg/funcs/pool.d.ts.map +1 -1
- package/dist/server/plugins/pg/funcs/pool.js +3 -0
- package/dist/server/plugins/table/funcs/getFilterSQL/index.js +1 -1
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/formatValue.d.ts +2 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/formatValue.d.ts.map +1 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/formatValue.js +211 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getFilterList.d.ts +8 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getFilterList.d.ts.map +1 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getFilterList.js +90 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getFilterQuery.d.ts +17 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getFilterQuery.d.ts.map +1 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getFilterQuery.js +90 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getRangeQuery.d.ts +17 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getRangeQuery.d.ts.map +1 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getRangeQuery.js +340 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getTableSql.d.ts +9 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getTableSql.d.ts.map +1 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/getTableSql.js +40 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/index.d.ts +19 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/index.d.ts.map +1 -0
- package/dist/server/plugins/table/funcs/getFilterSQLParametrized/index.js +156 -0
- package/dist/server/routes/table/functions/getData.d.ts.map +1 -1
- package/dist/server/routes/table/functions/getData.js +35 -29
- package/dist/server/routes/table/functions/prettify.d.ts +2 -0
- package/dist/server/routes/table/functions/prettify.d.ts.map +1 -0
- package/dist/server/routes/table/functions/prettify.js +11 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +2 -0
- package/package.json +1 -1
- package/dist/server/plugins/policy/funcs/checkSQL.d.ts +0 -8
- package/dist/server/plugins/policy/funcs/checkSQL.d.ts.map +0 -1
- package/dist/server/plugins/policy/funcs/checkSQL.js +0 -19
- package/dist/server/plugins/policy/sqlInjection.d.ts +0 -3
- package/dist/server/plugins/policy/sqlInjection.d.ts.map +0 -1
- package/dist/server/plugins/policy/sqlInjection.js +0 -33
- package/dist/server/plugins/table/funcs/getData.d.ts +0 -2
- package/dist/server/plugins/table/funcs/getData.d.ts.map +0 -1
- 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":"
|
|
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 (
|
|
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 @@
|
|
|
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,
|
|
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) =>
|
|
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":"
|
|
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 @@
|
|
|
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"}
|