@opengis/fastify-table 2.4.4 → 2.4.6
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/migrations/crm.sql +6 -1
- package/dist/server/plugins/upload/startUpload.d.ts +2 -1
- package/dist/server/plugins/upload/startUpload.d.ts.map +1 -1
- package/dist/server/plugins/upload/startUpload.js +7 -3
- package/dist/server/routes/widget/controllers/widget.set.d.ts.map +1 -1
- package/dist/server/routes/widget/controllers/widget.set.js +5 -0
- package/dist/server/routes/widget/hook/onWidgetSet.d.ts +1 -1
- package/dist/server/routes/widget/hook/onWidgetSet.d.ts.map +1 -1
- package/dist/server/routes/widget/hook/onWidgetSet.js +8 -5
- package/dist/server/routes/widget/index.d.ts +1 -1
- package/dist/server/routes/widget/index.d.ts.map +1 -1
- package/dist/server/routes/widget/index.js +79 -3
- package/dist/server/routes/widget/services/widget.entity.del.d.ts +26 -0
- package/dist/server/routes/widget/services/widget.entity.del.d.ts.map +1 -0
- package/dist/server/routes/widget/services/widget.entity.del.js +79 -0
- package/dist/server/routes/widget/services/widget.entity.get.d.ts +39 -0
- package/dist/server/routes/widget/services/widget.entity.get.d.ts.map +1 -0
- package/dist/server/routes/widget/services/widget.entity.get.js +148 -0
- package/dist/server/routes/widget/services/widget.entity.set.d.ts +22 -0
- package/dist/server/routes/widget/services/widget.entity.set.d.ts.map +1 -0
- package/dist/server/routes/widget/services/widget.entity.set.js +131 -0
- package/package.json +1 -1
|
@@ -46,6 +46,7 @@ COMMENT ON COLUMN crm.notifications.body is 'Зміст повідомлення
|
|
|
46
46
|
COMMENT ON COLUMN crm.notifications.link is 'Посилання на об''єкт';
|
|
47
47
|
COMMENT ON COLUMN crm.notifications.author_id is 'ID користувача автора повідомлення';
|
|
48
48
|
COMMENT ON COLUMN crm.notifications.entity_id is 'ID на об''єкту';
|
|
49
|
+
COMMENT ON COLUMN crm.notifications.entity_id is 'Сутність об''єкту';
|
|
49
50
|
|
|
50
51
|
-- crm.files
|
|
51
52
|
-- DROP TABLE IF EXISTS crm.files;
|
|
@@ -159,7 +160,11 @@ CREATE TABLE if not exists crm.reactions (
|
|
|
159
160
|
reaction_id text default next_id() PRIMARY KEY,
|
|
160
161
|
created_by text NOT NULL,
|
|
161
162
|
entity_id text NOT NULL,
|
|
163
|
+
entity_type text,
|
|
162
164
|
reaction_type text NOT NULL check (reaction_type in ('like', 'love', 'hate', 'wow', 'sad', 'angry')),
|
|
163
165
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
164
166
|
FOREIGN KEY (created_by) REFERENCES admin.users(uid)
|
|
165
|
-
);
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
alter table crm.reactions add column if not exists entity_type text;
|
|
170
|
+
alter table crm.notifications add column if not exists entity_type text;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export default function startUpload({ host, id, fileName, size, subdir, }: {
|
|
1
|
+
export default function startUpload({ host, id, fileName, size, subdir, originalFilename, }: {
|
|
2
2
|
host?: string;
|
|
3
3
|
id?: string;
|
|
4
4
|
fileName: string;
|
|
5
5
|
size: number;
|
|
6
6
|
subdir?: string;
|
|
7
|
+
originalFilename?: boolean;
|
|
7
8
|
}): Promise<any>;
|
|
8
9
|
//# sourceMappingURL=startUpload.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"startUpload.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/startUpload.ts"],"names":[],"mappings":"AAoBA,wBAA8B,WAAW,CAAC,EACxC,IAAI,EACJ,EAAE,EACF,QAAQ,EACR,IAAI,EACJ,MAAM,
|
|
1
|
+
{"version":3,"file":"startUpload.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/startUpload.ts"],"names":[],"mappings":"AAoBA,wBAA8B,WAAW,CAAC,EACxC,IAAI,EACJ,EAAE,EACF,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,gBAAwB,GACzB,EAAE;IACD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,gBA6FA"}
|
|
@@ -6,17 +6,21 @@ import getUploadStatus from "./getUploadStatus.js";
|
|
|
6
6
|
import { prefix, fileDir, metaDir, uploadChunkDirectory, fetchTimeoutMs, } from "./index.js";
|
|
7
7
|
import mimes from "../file/providers/mime/mimes.js";
|
|
8
8
|
import { startUpload as startUploadS3 } from "./s3.js";
|
|
9
|
-
import { BadRequestError } from "../../../errors.js";
|
|
9
|
+
import { BadRequestError, NotImplementedError } from "../../../errors.js";
|
|
10
10
|
const { chunkSize = 5242880 } = config;
|
|
11
|
-
export default async function startUpload({ host, id, fileName, size, subdir, }) {
|
|
11
|
+
export default async function startUpload({ host, id, fileName, size, subdir, originalFilename = false, }) {
|
|
12
12
|
if (subdir && (typeof subdir !== "string" || subdir.includes(".."))) {
|
|
13
13
|
throw BadRequestError("invalid params: subdir");
|
|
14
14
|
}
|
|
15
|
+
if (originalFilename) {
|
|
16
|
+
throw NotImplementedError("currently not supported");
|
|
17
|
+
}
|
|
15
18
|
if (!host) {
|
|
16
19
|
const id1 = id || randomUUID();
|
|
17
20
|
const extension = path.extname(fileName).substring(1);
|
|
21
|
+
const yearMonthDay = subdir ? "" : new Date().toISOString().split("T")[0];
|
|
18
22
|
const relativeDirpath = path
|
|
19
|
-
.join(uploadChunkDirectory, subdir || "")
|
|
23
|
+
.join(uploadChunkDirectory, subdir || "", yearMonthDay || "")
|
|
20
24
|
.replace(/\\/g, "/");
|
|
21
25
|
if (config.s3) {
|
|
22
26
|
const filepath = path.posix.join(config.folder || "", relativeDirpath, `${id1}.${extension}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widget.set.d.ts","sourceRoot":"","sources":["../../../../../server/routes/widget/controllers/widget.set.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAqC5C,wBAA8B,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY;;;;;;
|
|
1
|
+
{"version":3,"file":"widget.set.d.ts","sourceRoot":"","sources":["../../../../../server/routes/widget/controllers/widget.set.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAqC5C,wBAA8B,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY;;;;;;GA8HpE"}
|
|
@@ -109,6 +109,11 @@ export default async function widgetSet(req, reply) {
|
|
|
109
109
|
data,
|
|
110
110
|
uid: user?.uid,
|
|
111
111
|
});
|
|
112
|
+
// put w/ out file
|
|
113
|
+
if (type === "gallery" && body?.ismain && id) {
|
|
114
|
+
await pg.query(`update crm.files set ismain=false
|
|
115
|
+
where entity_id=$1 and file_id<>$2`, [objectid, id]);
|
|
116
|
+
}
|
|
112
117
|
return reply.status(200).send({
|
|
113
118
|
rowCount: result.rowCount,
|
|
114
119
|
data: "ok",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export default function onWidgetSet({ pg, id, objectid, type, payload }: any): Promise<null>;
|
|
1
|
+
export default function onWidgetSet({ pg, id, objectid, entityType, type, payload, }: any): Promise<null>;
|
|
2
2
|
//# sourceMappingURL=onWidgetSet.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"onWidgetSet.d.ts","sourceRoot":"","sources":["../../../../../server/routes/widget/hook/onWidgetSet.ts"],"names":[],"mappings":"AAEA,wBAA8B,WAAW,CAAC,EACxC,EAAqB,EACrB,EAAE,EACF,QAAQ,EACR,IAAI,EACJ,OAAY,
|
|
1
|
+
{"version":3,"file":"onWidgetSet.d.ts","sourceRoot":"","sources":["../../../../../server/routes/widget/hook/onWidgetSet.ts"],"names":[],"mappings":"AAEA,wBAA8B,WAAW,CAAC,EACxC,EAAqB,EACrB,EAAE,EACF,QAAQ,EACR,UAAU,EACV,IAAI,EACJ,OAAY,GACb,EAAE,GAAG,iBAiBL"}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import pgClients from
|
|
2
|
-
export default async function onWidgetSet({ pg = pgClients.client, id, objectid, type, payload = {} }) {
|
|
3
|
-
if (!id || !objectid || type !==
|
|
1
|
+
import pgClients from "../../../plugins/pg/pgClients.js";
|
|
2
|
+
export default async function onWidgetSet({ pg = pgClients.client, id, objectid, entityType, type, payload = {}, }) {
|
|
3
|
+
if (!id || !objectid || type !== "gallery") {
|
|
4
4
|
return null;
|
|
5
5
|
}
|
|
6
|
-
if (payload?.ismain) {
|
|
7
|
-
await pg.query(
|
|
6
|
+
if (payload?.ismain && !entityType) {
|
|
7
|
+
await pg.query("update crm.files set ismain=false where entity_id=$1 and file_id<>$2", [objectid, id]);
|
|
8
|
+
}
|
|
9
|
+
if (payload?.ismain && entityType) {
|
|
10
|
+
await pg.query("update crm.files set ismain=false where entity_id=$1 and entity_type=$2 and file_id<>$3", [objectid, entityType, id]);
|
|
8
11
|
}
|
|
9
12
|
return null;
|
|
10
13
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export default function route(app: any
|
|
1
|
+
export default function route(app: any): void;
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../server/routes/widget/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../server/routes/widget/index.ts"],"names":[],"mappings":"AAkDA,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,GAAG,EAAE,GAAG,QAmHrC"}
|
|
@@ -4,6 +4,10 @@ import widgetSet from "./controllers/widget.set.js";
|
|
|
4
4
|
import widgetGet from "./controllers/widget.get.js";
|
|
5
5
|
import fileEdit from "./controllers/file.edit.js";
|
|
6
6
|
import onWidgetSet from "./hook/onWidgetSet.js";
|
|
7
|
+
import widgetEntityGet from "./services/widget.entity.get.js";
|
|
8
|
+
import widgetEntitySet from "./services/widget.entity.set.js";
|
|
9
|
+
import widgetEntityDel from "./services/widget.entity.del.js";
|
|
10
|
+
import uploadMultiPart from "../../plugins/file/uploadMultiPart.js";
|
|
7
11
|
const tags = ["core", "widget"];
|
|
8
12
|
const tableSchema = {
|
|
9
13
|
params: {
|
|
@@ -20,6 +24,7 @@ const tableSchema = {
|
|
|
20
24
|
"reaction",
|
|
21
25
|
],
|
|
22
26
|
},
|
|
27
|
+
entityType: { type: "string", pattern: "^([\\w\\d_.]+)$" },
|
|
23
28
|
objectid: { type: "string", pattern: "^([\\d\\w]+)$" },
|
|
24
29
|
id: { type: "string", pattern: "^([\\d\\w]+)$" },
|
|
25
30
|
},
|
|
@@ -34,9 +39,80 @@ const tableSchema = {
|
|
|
34
39
|
addHook("onWidgetSet", onWidgetSet);
|
|
35
40
|
const policy = "L0";
|
|
36
41
|
const params = { config: { tags, policy }, schema: tableSchema };
|
|
37
|
-
export default function route(app
|
|
42
|
+
export default function route(app) {
|
|
43
|
+
app.get("/widget/:type/:entityType/:objectid", params, async (req) => {
|
|
44
|
+
const result = await widgetEntityGet({
|
|
45
|
+
type: req.params.type,
|
|
46
|
+
entityType: req.params.entityType,
|
|
47
|
+
objectid: req.params.objectid,
|
|
48
|
+
uid: req.user?.uid,
|
|
49
|
+
userName: req.user?.user_name,
|
|
50
|
+
debug: req.query.debug && req.user?.user_type?.includes?.("admin"),
|
|
51
|
+
}, req.pg);
|
|
52
|
+
return result;
|
|
53
|
+
});
|
|
54
|
+
app.post("/widget/:type/:entityType/:objectid", params, async (req) => {
|
|
55
|
+
if (["gallery", "file"].includes(req.params.type) &&
|
|
56
|
+
req.headers["content-type"]?.startsWith("multipart/form-data")) {
|
|
57
|
+
const file = await uploadMultiPart(req);
|
|
58
|
+
const result = await widgetEntitySet({
|
|
59
|
+
type: req.params.type,
|
|
60
|
+
entityType: req.params.entityType,
|
|
61
|
+
objectid: req.params.objectid,
|
|
62
|
+
uid: req.user?.uid,
|
|
63
|
+
file,
|
|
64
|
+
}, req.pg);
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
const result = await widgetEntitySet({
|
|
68
|
+
type: req.params.type,
|
|
69
|
+
entityType: req.params.entityType,
|
|
70
|
+
objectid: req.params.objectid,
|
|
71
|
+
uid: req.user?.uid,
|
|
72
|
+
reqPath: req.path,
|
|
73
|
+
body: req.body,
|
|
74
|
+
}, req.pg);
|
|
75
|
+
return result;
|
|
76
|
+
});
|
|
77
|
+
app.put("/widget/:type/:entityType/:objectid/:id", params, async (req) => {
|
|
78
|
+
if (["gallery", "file"].includes(req.params.type) &&
|
|
79
|
+
req.headers["content-type"]?.startsWith("multipart/form-data")) {
|
|
80
|
+
const file = await uploadMultiPart(req);
|
|
81
|
+
const result = await widgetEntitySet({
|
|
82
|
+
type: req.params.type,
|
|
83
|
+
entityType: req.params.entityType,
|
|
84
|
+
objectid: req.params.objectid,
|
|
85
|
+
id: req.params.id,
|
|
86
|
+
uid: req.user?.uid,
|
|
87
|
+
file,
|
|
88
|
+
}, req.pg);
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
const result = await widgetEntitySet({
|
|
92
|
+
type: req.params.type,
|
|
93
|
+
entityType: req.params.entityType,
|
|
94
|
+
objectid: req.params.objectid,
|
|
95
|
+
id: req.params.id,
|
|
96
|
+
uid: req.user?.uid,
|
|
97
|
+
reqPath: req.path,
|
|
98
|
+
body: req.body,
|
|
99
|
+
}, req.pg);
|
|
100
|
+
return result;
|
|
101
|
+
});
|
|
102
|
+
app.delete("/widget/:type/:entityType/:objectid/:id", params, async (req) => {
|
|
103
|
+
const result = await widgetEntityDel({
|
|
104
|
+
type: req.params.type,
|
|
105
|
+
entityType: req.params.entityType,
|
|
106
|
+
objectid: req.params.objectid,
|
|
107
|
+
id: req.params.id,
|
|
108
|
+
uid: req.user?.uid,
|
|
109
|
+
userName: req.user?.user_name,
|
|
110
|
+
}, req.pg);
|
|
111
|
+
return result;
|
|
112
|
+
});
|
|
113
|
+
app.get("/widget/:type/:objectid", params, widgetGet);
|
|
114
|
+
app.post("/widget/:type/:objectid", params, widgetSet);
|
|
115
|
+
app.put("/widget/:type/:objectid/:id", params, widgetSet);
|
|
38
116
|
app.delete("/widget/:type/:objectid/:id", params, widgetDel);
|
|
39
|
-
app.post("/widget/:type/:objectid/:id?", params, widgetSet);
|
|
40
117
|
app.put("/file-edit/:id", params, fileEdit);
|
|
41
|
-
app.get("/widget/:type/:objectid", params, widgetGet);
|
|
42
118
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @method DELETE
|
|
4
|
+
* @summary CRM дані для обраного віджета.
|
|
5
|
+
* @priority 2
|
|
6
|
+
* @tag table
|
|
7
|
+
* @type api
|
|
8
|
+
* @param {String} id Ідентифікатор child рядка (файлу, зображення, коментаря тощо)
|
|
9
|
+
* @param {Any} objectid ID parent рядка в БД, primary key
|
|
10
|
+
* @param {Any} entityType Сутність / таблиця, для збереження унікальності при використанні objectid default serial
|
|
11
|
+
* @param {String} type Тип віджету
|
|
12
|
+
* @errors 400, 500
|
|
13
|
+
* @returns {Number} status Номер помилки
|
|
14
|
+
* @returns {String|Object} error Опис помилки
|
|
15
|
+
* @returns {String|Object} message Повідомлення про успішне виконання або об'єкт з параметрами
|
|
16
|
+
*/
|
|
17
|
+
export default function widgetEntityDel({ type, entityType, objectid, id, uid, userName }: any, pg?: any): Promise<{
|
|
18
|
+
data: {
|
|
19
|
+
id: any;
|
|
20
|
+
};
|
|
21
|
+
user: {
|
|
22
|
+
uid: any;
|
|
23
|
+
name: any;
|
|
24
|
+
};
|
|
25
|
+
}>;
|
|
26
|
+
//# sourceMappingURL=widget.entity.del.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget.entity.del.d.ts","sourceRoot":"","sources":["../../../../../server/routes/widget/services/widget.entity.del.ts"],"names":[],"mappings":"AAiCA;;;;;;;;;;;;;;;GAeG;AAEH,wBAA8B,eAAe,CAC3C,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,GAAG,EACtD,EAAE,MAAmB;;;;;;;;GAwEtB"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import config from "../../../../config.js";
|
|
2
|
+
import isFileExists from "../../../plugins/file/isFileExists.js";
|
|
3
|
+
import logChanges from "../../../plugins/crud/funcs/utils/logChanges.js";
|
|
4
|
+
import pgClients from "../../../plugins/pg/pgClients.js";
|
|
5
|
+
import { BadRequestError, ForbiddenError, UnauthorizedError, } from "../../../../errors.js";
|
|
6
|
+
const isAdmin = () => process.env.NODE_ENV === "admin" || config.admin;
|
|
7
|
+
async function checkAccess({ pg, objectid, entityType, id, }) {
|
|
8
|
+
const { uid, filepath } = await pg
|
|
9
|
+
.query("select uid, file_path as filepath from crm.files where entity_id=$1 and entity_type=$2 and entity_type=$2 and file_id=$3", [objectid, entityType, id])
|
|
10
|
+
.then((el) => el.rows?.[0] || {});
|
|
11
|
+
return { uid, exists: filepath ? await isFileExists(filepath) : null };
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* @method DELETE
|
|
16
|
+
* @summary CRM дані для обраного віджета.
|
|
17
|
+
* @priority 2
|
|
18
|
+
* @tag table
|
|
19
|
+
* @type api
|
|
20
|
+
* @param {String} id Ідентифікатор child рядка (файлу, зображення, коментаря тощо)
|
|
21
|
+
* @param {Any} objectid ID parent рядка в БД, primary key
|
|
22
|
+
* @param {Any} entityType Сутність / таблиця, для збереження унікальності при використанні objectid default serial
|
|
23
|
+
* @param {String} type Тип віджету
|
|
24
|
+
* @errors 400, 500
|
|
25
|
+
* @returns {Number} status Номер помилки
|
|
26
|
+
* @returns {String|Object} error Опис помилки
|
|
27
|
+
* @returns {String|Object} message Повідомлення про успішне виконання або об'єкт з параметрами
|
|
28
|
+
*/
|
|
29
|
+
export default async function widgetEntityDel({ type, entityType, objectid, id, uid, userName }, pg = pgClients.client) {
|
|
30
|
+
if (!uid) {
|
|
31
|
+
throw UnauthorizedError("unauthorized");
|
|
32
|
+
}
|
|
33
|
+
if (!entityType) {
|
|
34
|
+
throw BadRequestError("not enough params: entity type");
|
|
35
|
+
}
|
|
36
|
+
if (!objectid) {
|
|
37
|
+
throw BadRequestError("not enough params: object id");
|
|
38
|
+
}
|
|
39
|
+
if (!id && type !== "reaction") {
|
|
40
|
+
throw BadRequestError("not enough params: id");
|
|
41
|
+
}
|
|
42
|
+
// force delete db entry if file not exists
|
|
43
|
+
const { exists, uid: author } = ["file", "gallery"].includes(type)
|
|
44
|
+
? await checkAccess({ pg, objectid, entityType, id })
|
|
45
|
+
: {};
|
|
46
|
+
if (exists && !isAdmin() && uid && author !== uid) {
|
|
47
|
+
throw ForbiddenError("access restricted: file exists, not an author");
|
|
48
|
+
}
|
|
49
|
+
const sqls = {
|
|
50
|
+
comment: `delete from crm.communications where entity_id=$1 and entity_type=$2 and ${isAdmin() ? "$3=$3" : "uid=$3"} and communication_id=$4`,
|
|
51
|
+
checklist: `delete from crm.checklists where entity_id=$1 and entity_type=$2 and ${isAdmin() ? "$3=$3" : "uid=$3"} and checklist_id=$4`,
|
|
52
|
+
file: `update crm.files set file_status=3 where entity_id=$1 and entity_type=$2 and ${!exists || isAdmin() ? "$3=$3" : "uid=$3"} and file_id=$4 returning uploaded_name`,
|
|
53
|
+
gallery: `update crm.files set file_status=3 where entity_id=$1 and entity_type=$2 and ${!exists || isAdmin() ? "$3=$3" : "uid=$3"} and file_id=$4 returning uploaded_name`,
|
|
54
|
+
reaction: `delete from crm.reactions where entity_id=$1 and entity_type=$2 and ${isAdmin() ? "$3=$3" : "created_by=$3"} and $3=$3 and reaction_id=$4 returning reaction_type`,
|
|
55
|
+
};
|
|
56
|
+
const q = sqls[type];
|
|
57
|
+
const table = {
|
|
58
|
+
comment: "crm.communications",
|
|
59
|
+
checklist: "crm.checklists",
|
|
60
|
+
file: "crm.files",
|
|
61
|
+
gallery: "crm.files",
|
|
62
|
+
reaction: "crm.reactions",
|
|
63
|
+
}[type];
|
|
64
|
+
if (!q || !table) {
|
|
65
|
+
throw BadRequestError("invalid widget type");
|
|
66
|
+
}
|
|
67
|
+
const rows = await pg
|
|
68
|
+
.query(q, [objectid, entityType, uid, id || ""])
|
|
69
|
+
.then((r) => r.rows || []);
|
|
70
|
+
await logChanges({
|
|
71
|
+
pg,
|
|
72
|
+
table,
|
|
73
|
+
id: type === "reaction" ? objectid : id,
|
|
74
|
+
data: rows[0],
|
|
75
|
+
uid,
|
|
76
|
+
type: "DELETE",
|
|
77
|
+
});
|
|
78
|
+
return { data: { id }, user: { uid, name: userName } };
|
|
79
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @method GET
|
|
3
|
+
* @type api
|
|
4
|
+
* @param {Any} objectid ID parent рядка в БД, primary key
|
|
5
|
+
* @param {Any} entityType Сутність / таблиця, для збереження унікальності при використанні objectid default serial
|
|
6
|
+
* @param {String} type Тип віджету
|
|
7
|
+
*/
|
|
8
|
+
export default function widgetEntityGet({ type, entityType, objectid, uid, userName, debug }: any, pg?: any): Promise<{
|
|
9
|
+
q: string;
|
|
10
|
+
type: any;
|
|
11
|
+
q1: string;
|
|
12
|
+
id: any;
|
|
13
|
+
data: any;
|
|
14
|
+
time?: undefined;
|
|
15
|
+
rows?: undefined;
|
|
16
|
+
user?: undefined;
|
|
17
|
+
objectid?: undefined;
|
|
18
|
+
} | {
|
|
19
|
+
time: {
|
|
20
|
+
rows: number;
|
|
21
|
+
data: number;
|
|
22
|
+
};
|
|
23
|
+
rows: any;
|
|
24
|
+
user: {
|
|
25
|
+
uid: any;
|
|
26
|
+
name: any;
|
|
27
|
+
};
|
|
28
|
+
data: {
|
|
29
|
+
author: any;
|
|
30
|
+
cdate: any;
|
|
31
|
+
edate: any;
|
|
32
|
+
};
|
|
33
|
+
objectid: any;
|
|
34
|
+
q?: undefined;
|
|
35
|
+
type?: undefined;
|
|
36
|
+
q1?: undefined;
|
|
37
|
+
id?: undefined;
|
|
38
|
+
}>;
|
|
39
|
+
//# sourceMappingURL=widget.entity.get.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget.entity.get.d.ts","sourceRoot":"","sources":["../../../../../server/routes/widget/services/widget.entity.get.ts"],"names":[],"mappings":"AAyBA;;;;;;GAMG;AAEH,wBAA8B,eAAe,CAC3C,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,GAAG,EACzD,EAAE,MAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyKtB"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import getMeta from "../../../plugins/pg/funcs/getMeta.js";
|
|
2
|
+
import pgClients from "../../../plugins/pg/pgClients.js";
|
|
3
|
+
import { BadRequestError, NotFoundError } from "../../../../errors.js";
|
|
4
|
+
const galleryExtList = [
|
|
5
|
+
"png",
|
|
6
|
+
"svg",
|
|
7
|
+
"jpg",
|
|
8
|
+
"jpeg",
|
|
9
|
+
"gif",
|
|
10
|
+
"mp4",
|
|
11
|
+
"mov",
|
|
12
|
+
"avi",
|
|
13
|
+
];
|
|
14
|
+
const username = "coalesce(u.sur_name,'')||coalesce(' '||u.user_name,'') ||coalesce(' '||u.father_name,'')";
|
|
15
|
+
const pkList = {
|
|
16
|
+
comment: "communication_id",
|
|
17
|
+
checklist: "checklist_id",
|
|
18
|
+
file: "file_id",
|
|
19
|
+
gallery: "file_id",
|
|
20
|
+
reaction: "reaction_id",
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* @method GET
|
|
24
|
+
* @type api
|
|
25
|
+
* @param {Any} objectid ID parent рядка в БД, primary key
|
|
26
|
+
* @param {Any} entityType Сутність / таблиця, для збереження унікальності при використанні objectid default serial
|
|
27
|
+
* @param {String} type Тип віджету
|
|
28
|
+
*/
|
|
29
|
+
export default async function widgetEntityGet({ type, entityType, objectid, uid, userName, debug }, pg = pgClients.client) {
|
|
30
|
+
const time = [Date.now()];
|
|
31
|
+
if (!entityType) {
|
|
32
|
+
throw BadRequestError("not enough params: entity type");
|
|
33
|
+
}
|
|
34
|
+
if (!objectid) {
|
|
35
|
+
throw BadRequestError("not enough params: id");
|
|
36
|
+
}
|
|
37
|
+
const sqls = {
|
|
38
|
+
comment: pg.pk["admin.users"]
|
|
39
|
+
? `select communication_id, entity_id, body, subject, c.cdate, c.uid,
|
|
40
|
+
${username} as username, u.login, u.avatar
|
|
41
|
+
from crm.communications c
|
|
42
|
+
left join admin.users u on u.uid=c.uid
|
|
43
|
+
where entity_id=$1 and entity_type=$2 order by cdate desc`
|
|
44
|
+
: "select communication_id, entity_id, body, subject, cdate, uid from crm.communications where entity_id=$1 and entity_type=$2 order by cdate desc",
|
|
45
|
+
history: `SELECT change_id, entity_id, entity_type, change_type, change_date, a.change_user_id, a.uid, a.cdate, b.json_agg as changes,
|
|
46
|
+
${username} as username, u.login, u.avatar
|
|
47
|
+
FROM log.table_changes a
|
|
48
|
+
left join admin.users u on a.change_user_id = u.uid
|
|
49
|
+
left join lateral(
|
|
50
|
+
select json_agg(row_to_json(q)) from (
|
|
51
|
+
select change_data_id, entity_key, value_new, value_old from log.table_changes_data
|
|
52
|
+
where change_id=a.change_id
|
|
53
|
+
)q
|
|
54
|
+
)b on 1=1
|
|
55
|
+
where a.change_type != 'INSERT' and b.json_agg is not null and ((entity_id=$1 and entity_type=$2) or entity_id in (
|
|
56
|
+
select communication_id from crm.communications where entity_id=$1 and entity_type=$2
|
|
57
|
+
union all select checklist_id from crm.checklists where entity_id=$1 and entity_type=$2
|
|
58
|
+
union all select file_id from crm.files where entity_id=$1 and entity_type=$2)
|
|
59
|
+
)
|
|
60
|
+
order by cdate desc limit 100`,
|
|
61
|
+
checklist: pg.pk["admin.users"]
|
|
62
|
+
? `SELECT checklist_id, entity_id, subject, is_done, done_date, c.uid, c.cdate, ${username} as username, u.login, u.avatar
|
|
63
|
+
FROM crm.checklists c
|
|
64
|
+
left join admin.users u on u.uid=c.uid
|
|
65
|
+
where entity_id=$1 and entity_type=$2 order by cdate desc`
|
|
66
|
+
: "SELECT checklist_id, entity_id, subject, is_done, done_date, uid, cdate FROM crm.checklists where entity_id=$1 and entity_type=$2 order by cdate desc",
|
|
67
|
+
file: pg.pk["admin.users"]
|
|
68
|
+
? `SELECT file_id, entity_id, entity_type, file_path, uploaded_name, ext, size, c.uid, c.cdate, file_type, c.ismain,
|
|
69
|
+
${username} as username, u.login, isverified, u.avatar, u.uid as author, file_status
|
|
70
|
+
FROM crm.files c
|
|
71
|
+
left join admin.users u on u.uid=c.uid
|
|
72
|
+
where entity_id=$1 and entity_type=$2 and file_status<>3 order by cdate desc`
|
|
73
|
+
: `SELECT file_id, entity_id, entity_type, file_path, uploaded_name, ext, size, uid, cdate, file_type, ismain,
|
|
74
|
+
isverified, uid as author, file_status FROM crm.files c where entity_id=$1 and entity_type=$2 and file_status<>3 order by cdate desc`,
|
|
75
|
+
gallery: pg.pk["admin.users"]
|
|
76
|
+
? `SELECT file_id, entity_id, entity_type, file_path, uploaded_name, ext, size, c.uid, c.cdate, file_type, c.ismain,
|
|
77
|
+
${username} as username, u.login, u.avatar, isverified, u.avatar, c.uid as author, file_status
|
|
78
|
+
FROM crm.files c
|
|
79
|
+
left join admin.users u on u.uid=c.uid
|
|
80
|
+
where entity_id=$1 and entity_type=$2 and file_status<>3 and ext = any($3) order by cdate desc`
|
|
81
|
+
: `SELECT file_id, entity_id, entity_type, file_path, uploaded_name, ext, size, c.uid, c.cdate, file_type, ismain,
|
|
82
|
+
isverified, uid as author, file_status FROM crm.files c where entity_id=$1 and entity_type=$2 and file_status<>3 and ext = any($3) order by cdate desc`,
|
|
83
|
+
reaction: "SELECT count(*), reaction_type FROM crm.reactions c where entity_id=$1 and entity_type=$2 group by reaction_type",
|
|
84
|
+
};
|
|
85
|
+
const q = sqls[type];
|
|
86
|
+
if (!q) {
|
|
87
|
+
throw BadRequestError("invalid widget type");
|
|
88
|
+
}
|
|
89
|
+
/* rows */
|
|
90
|
+
const rows = await pg
|
|
91
|
+
.query(q, [objectid, entityType, type === "gallery" ? galleryExtList : null].filter(Boolean))
|
|
92
|
+
.then((el) => el.rows || []);
|
|
93
|
+
time.push(Date.now());
|
|
94
|
+
rows.forEach((row) => Object.assign(row, { username: row.username?.trim?.() || row.login }));
|
|
95
|
+
/* reactions */
|
|
96
|
+
const widgetPkey = pkList[type];
|
|
97
|
+
if (type === "comment" && widgetPkey) {
|
|
98
|
+
const reactions = rows.length && uid
|
|
99
|
+
? await pg
|
|
100
|
+
.query("select json_object_agg(entity_id, reaction_type) from crm.reactions where entity_id=any($1) and entity_type=$2 and created_by=$3", [rows.map((row) => row[widgetPkey]), entityType, uid])
|
|
101
|
+
.then((el) => el.rows?.[0]?.json_object_agg || {})
|
|
102
|
+
: {};
|
|
103
|
+
rows
|
|
104
|
+
.filter((row) => reactions[row[widgetPkey]])
|
|
105
|
+
.forEach((row) => {
|
|
106
|
+
Object.assign(row, { reaction: reactions[row[widgetPkey]] });
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/* Object info */
|
|
110
|
+
const { pk, columns = [] } = await getMeta({ pg, table: entityType });
|
|
111
|
+
const authorIdColumn = columns.find((col) => ["uid", "created_by"].includes(col.name))?.name;
|
|
112
|
+
if (!pk &&
|
|
113
|
+
type === "history" &&
|
|
114
|
+
process.env.NODE_ENV !== "test" &&
|
|
115
|
+
!process.env.VITEST) {
|
|
116
|
+
throw NotFoundError("log table not found");
|
|
117
|
+
}
|
|
118
|
+
const cdateColumn = columns.find((col) => ["cdate", "created_at"].includes(col.name))?.name;
|
|
119
|
+
const editorDateColumn = columns.find((col) => ["editor_date", "updated_at"].includes(col.name))?.name;
|
|
120
|
+
const q1 = `select ${username} as author, u.login ${cdateColumn ? `,a.${cdateColumn} as cdate` : ""} ${editorDateColumn ? `,a.${editorDateColumn} as editor_date` : ""} from ${entityType} a
|
|
121
|
+
left join admin.users u on a.${authorIdColumn}=u.uid where a.${pk}=$1 limit 1`;
|
|
122
|
+
const data = pg.pk["admin.users"] && pk && entityType
|
|
123
|
+
? await pg
|
|
124
|
+
.query(q1, [objectid, type === "gallery" ? galleryExtList : null].filter(Boolean))
|
|
125
|
+
.then((el) => el.rows?.[0] || {})
|
|
126
|
+
: {};
|
|
127
|
+
time.push(Date.now());
|
|
128
|
+
if (debug) {
|
|
129
|
+
return {
|
|
130
|
+
q,
|
|
131
|
+
type,
|
|
132
|
+
q1,
|
|
133
|
+
id: objectid,
|
|
134
|
+
data,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
time: { rows: time[1] - time[0], data: time[2] - time[1] },
|
|
139
|
+
rows,
|
|
140
|
+
user: { uid, name: userName },
|
|
141
|
+
data: {
|
|
142
|
+
author: data?.author,
|
|
143
|
+
cdate: data?.cdate,
|
|
144
|
+
edate: data?.editor_date,
|
|
145
|
+
},
|
|
146
|
+
objectid,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @method POST|PUT
|
|
3
|
+
* @type api
|
|
4
|
+
* @param {String} id Ідентифікатор child рядка (файлу, зображення, коментаря тощо)
|
|
5
|
+
* @param {Any} objectid ID parent рядка в БД, primary key
|
|
6
|
+
* @param {Any} entityType Сутність / таблиця, для збереження унікальності при використанні objectid default serial
|
|
7
|
+
* @param {String} type Тип віджету
|
|
8
|
+
*/
|
|
9
|
+
export default function widgetEntitySet({ type, entityType, objectid, id, uid, reqPath, file, body }: any, pg?: any): Promise<{
|
|
10
|
+
rowCount: number;
|
|
11
|
+
data: string;
|
|
12
|
+
command: string;
|
|
13
|
+
id: any;
|
|
14
|
+
entity_id: any;
|
|
15
|
+
} | {
|
|
16
|
+
rowCount: any;
|
|
17
|
+
data: string;
|
|
18
|
+
command: any;
|
|
19
|
+
id: any;
|
|
20
|
+
entity_id?: undefined;
|
|
21
|
+
}>;
|
|
22
|
+
//# sourceMappingURL=widget.entity.set.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"widget.entity.set.d.ts","sourceRoot":"","sources":["../../../../../server/routes/widget/services/widget.entity.set.ts"],"names":[],"mappings":"AAoCA;;;;;;;GAOG;AAEH,wBAA8B,eAAe,CAC3C,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,GAAG,EACjE,EAAE,MAAmB;;;;;;;;;;;;GAiHtB"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import pgClients from "../../../plugins/pg/pgClients.js";
|
|
3
|
+
import getMeta from "../../../plugins/pg/funcs/getMeta.js";
|
|
4
|
+
import dataInsert from "../../../plugins/crud/funcs/dataInsert.js";
|
|
5
|
+
import dataUpdate from "../../../plugins/crud/funcs/dataUpdate.js";
|
|
6
|
+
import { applyHook } from "../../../../utils.js";
|
|
7
|
+
import { BadRequestError, NotFoundError } from "../../../../errors.js";
|
|
8
|
+
const tableList = {
|
|
9
|
+
comment: "crm.communications",
|
|
10
|
+
gallery: "crm.files",
|
|
11
|
+
file: "crm.files",
|
|
12
|
+
checklist: "crm.checklists",
|
|
13
|
+
reaction: "crm.reactions",
|
|
14
|
+
};
|
|
15
|
+
const pkList = {
|
|
16
|
+
comment: "communication_id",
|
|
17
|
+
checklist: "checklist_id",
|
|
18
|
+
file: "file_id",
|
|
19
|
+
gallery: "file_id",
|
|
20
|
+
reaction: "reaction_id",
|
|
21
|
+
};
|
|
22
|
+
const galleryExtList = [
|
|
23
|
+
"png",
|
|
24
|
+
"svg",
|
|
25
|
+
"jpg",
|
|
26
|
+
"jpeg",
|
|
27
|
+
"gif",
|
|
28
|
+
"mp4",
|
|
29
|
+
"mov",
|
|
30
|
+
"avi",
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* @method POST|PUT
|
|
34
|
+
* @type api
|
|
35
|
+
* @param {String} id Ідентифікатор child рядка (файлу, зображення, коментаря тощо)
|
|
36
|
+
* @param {Any} objectid ID parent рядка в БД, primary key
|
|
37
|
+
* @param {Any} entityType Сутність / таблиця, для збереження унікальності при використанні objectid default serial
|
|
38
|
+
* @param {String} type Тип віджету
|
|
39
|
+
*/
|
|
40
|
+
export default async function widgetEntitySet({ type, entityType, objectid, id, uid, reqPath, file, body }, pg = pgClients.client) {
|
|
41
|
+
if (!pkList[type] || !tableList[type]) {
|
|
42
|
+
throw BadRequestError("param type not valid");
|
|
43
|
+
}
|
|
44
|
+
if (!entityType) {
|
|
45
|
+
throw BadRequestError("not enough params: entity type");
|
|
46
|
+
}
|
|
47
|
+
if (!objectid) {
|
|
48
|
+
throw BadRequestError("not enough params: id");
|
|
49
|
+
}
|
|
50
|
+
const table = tableList[type];
|
|
51
|
+
if (file) {
|
|
52
|
+
const extName = path
|
|
53
|
+
.extname(file.filepath || "")
|
|
54
|
+
.slice(1)
|
|
55
|
+
.toLowerCase();
|
|
56
|
+
const data = {
|
|
57
|
+
uploaded_name: file?.originalFilename
|
|
58
|
+
?.toLocaleLowerCase()
|
|
59
|
+
?.replace(/'/g, "''"),
|
|
60
|
+
file_path: file?.relativeFilepath?.replace(/\\/g, "/"),
|
|
61
|
+
ext: extName,
|
|
62
|
+
size: file?.size,
|
|
63
|
+
file_status: 1,
|
|
64
|
+
uid: uid || 1,
|
|
65
|
+
entity_id: objectid,
|
|
66
|
+
entity_type: entityType,
|
|
67
|
+
};
|
|
68
|
+
if (type === "gallery" && !galleryExtList.includes(extName.toLowerCase())) {
|
|
69
|
+
throw BadRequestError("invalid file extension");
|
|
70
|
+
}
|
|
71
|
+
const row = await dataInsert({
|
|
72
|
+
pg,
|
|
73
|
+
table: "crm.files",
|
|
74
|
+
data,
|
|
75
|
+
uid,
|
|
76
|
+
}).then((r) => r.rows?.[0]);
|
|
77
|
+
if (type === "gallery") {
|
|
78
|
+
await pg.query(`update crm.files set ismain=true
|
|
79
|
+
where entity_id=$1 and entity_type=$2
|
|
80
|
+
and file_id=$3
|
|
81
|
+
and (select count(*) = 0 from crm.files where entity_id=$1 and entity_type=$2 and ismain)`, [objectid, entityType, row?.file_id]);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
rowCount: 1,
|
|
85
|
+
data: "ok",
|
|
86
|
+
command: "UPLOAD",
|
|
87
|
+
id: row?.file_id,
|
|
88
|
+
entity_id: row?.entity_id,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const { pk } = await getMeta({ pg, table });
|
|
92
|
+
if (!pk) {
|
|
93
|
+
throw NotFoundError("table not found");
|
|
94
|
+
}
|
|
95
|
+
const data = { ...body, uid, entity_id: objectid, entity_type: entityType };
|
|
96
|
+
await applyHook("onWidgetSet", {
|
|
97
|
+
pg,
|
|
98
|
+
link: reqPath,
|
|
99
|
+
id,
|
|
100
|
+
objectid,
|
|
101
|
+
entityType,
|
|
102
|
+
user: { uid },
|
|
103
|
+
type,
|
|
104
|
+
payload: data,
|
|
105
|
+
});
|
|
106
|
+
const result = id
|
|
107
|
+
? await dataUpdate({
|
|
108
|
+
pg,
|
|
109
|
+
table,
|
|
110
|
+
data,
|
|
111
|
+
id,
|
|
112
|
+
uid,
|
|
113
|
+
})
|
|
114
|
+
: await dataInsert({
|
|
115
|
+
pg,
|
|
116
|
+
table,
|
|
117
|
+
data,
|
|
118
|
+
uid,
|
|
119
|
+
});
|
|
120
|
+
// put w/ out file
|
|
121
|
+
if (type === "gallery" && body?.ismain && id) {
|
|
122
|
+
await pg.query(`update crm.files set ismain=false
|
|
123
|
+
where entity_id=$1 and entity_type=$2 and file_id<>$3`, [objectid, entityType, id]);
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
rowCount: result.rowCount,
|
|
127
|
+
data: "ok",
|
|
128
|
+
command: result.command,
|
|
129
|
+
id: result.rows?.[0]?.[pkList[type]] || result?.[pkList[type]],
|
|
130
|
+
};
|
|
131
|
+
}
|