@opengis/cms 0.0.26 → 0.0.28
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/README.md +98 -98
- package/dist/index-W-qQIppj-DunG40EG.js +2452 -0
- package/dist/index.html +28 -28
- package/dist/index.js +12683 -6702
- package/dist/index.umd.cjs +87 -41
- package/dist/style.css +1 -0
- package/package.json +68 -68
- package/plugin.js +76 -76
- package/server/app.js +35 -35
- package/server/config.js +4 -4
- package/server/functions/getDraftKey.js +22 -22
- package/server/index.js +22 -22
- package/server/migrations/fixes.sql +123 -123
- package/server/migrations/site.sql +545 -545
- package/server/plugins/adminHook.js +78 -78
- package/server/plugins/hook.js +59 -59
- package/server/plugins/vite.js +84 -84
- package/server/routes/category/controllers/cms.category.delete.js +21 -21
- package/server/routes/category/controllers/cms.category.get.js +17 -17
- package/server/routes/category/controllers/cms.category.list.js +16 -16
- package/server/routes/category/controllers/cms.category.post.js +21 -21
- package/server/routes/category/controllers/cms.category.put.js +23 -23
- package/server/routes/category/index.mjs +22 -22
- package/server/routes/cms/controllers/cmsStat.js +55 -55
- package/server/routes/cms/controllers/cmsSuggest.js +57 -57
- package/server/routes/cms/controllers/deleteContent.js +114 -114
- package/server/routes/cms/controllers/deleteMedia.js +75 -75
- package/server/routes/cms/controllers/downloadMedia.js +48 -48
- package/server/routes/cms/controllers/getContent.js +110 -110
- package/server/routes/cms/controllers/getContentBySlug.js +95 -95
- package/server/routes/cms/controllers/getPermissions.js +15 -15
- package/server/routes/cms/controllers/insertContent.js +218 -218
- package/server/routes/cms/controllers/listMedia.js +93 -93
- package/server/routes/cms/controllers/metadataMedia.js +38 -38
- package/server/routes/cms/controllers/properties.get.js +53 -53
- package/server/routes/cms/controllers/properties.post.js +99 -99
- package/server/routes/cms/controllers/searchContent.js +205 -205
- package/server/routes/cms/controllers/setPermissions.js +49 -49
- package/server/routes/cms/controllers/translate.js +89 -89
- package/server/routes/cms/controllers/updateContent.js +238 -238
- package/server/routes/cms/controllers/uploadMedia.js +78 -78
- package/server/routes/cms/index.mjs +114 -114
- package/server/routes/cms/utils/additionalData.js +35 -35
- package/server/routes/cms/utils/getCollection.js +81 -81
- package/server/routes/cms/utils/getSingle.js +187 -187
- package/server/routes/cms/utils/insertContentLocalization.js +86 -86
- package/server/routes/cms/utils/requestTranslation.js +85 -85
- package/server/routes/cms/utils/updateLocalization.js +47 -47
- package/server/routes/cmsSpace/controllers/deleteSpace.js +25 -25
- package/server/routes/cmsSpace/controllers/getSpaces.js +27 -27
- package/server/routes/cmsSpace/controllers/insertSpace.js +21 -21
- package/server/routes/cmsSpace/controllers/updateSpace.js +23 -23
- package/server/routes/cmsSpace/index.mjs +20 -20
- package/server/routes/contentType/controllers/addContentType.js +162 -162
- package/server/routes/contentType/controllers/contentTypeList.js +54 -54
- package/server/routes/contentType/controllers/delContentType.js +75 -75
- package/server/routes/contentType/controllers/editContentType.js +61 -61
- package/server/routes/contentType/controllers/getContentType.js +37 -37
- package/server/routes/contentType/index.mjs +35 -35
- package/server/routes/contentType/utils/updateContents.js +28 -28
- package/server/routes/contentType/utils/updateCustomContentTable.js +55 -55
- package/server/routes/feedback/controllers/email.list.js +24 -24
- package/server/routes/feedback/controllers/feedback.js +48 -48
- package/server/routes/feedback/controllers/feedback.list.js +37 -37
- package/server/routes/feedback/controllers/news.subscriptions.js +44 -44
- package/server/routes/feedback/index.mjs +71 -71
- package/server/routes/logs/controllers/export.user.logs.js +77 -77
- package/server/routes/logs/controllers/user.logs.js +44 -44
- package/server/routes/logs/index.mjs +9 -9
- package/server/routes/menu/controllers/addMenu.js +37 -37
- package/server/routes/menu/controllers/delMenu.js +31 -31
- package/server/routes/menu/controllers/editMenu.js +41 -41
- package/server/routes/menu/controllers/getMenu.js +42 -42
- package/server/routes/menu/index.mjs +13 -13
- package/server/routes/migration/controllers/collectionToCustom.js +137 -137
- package/server/routes/migration/index.mjs +8 -8
- package/server/routes/root.mjs +8 -8
- package/server/routes/tags/controllers/add.tags.js +24 -24
- package/server/routes/tags/controllers/del.tags.js +19 -19
- package/server/routes/tags/controllers/edit.tags.js +25 -25
- package/server/routes/tags/controllers/get.tags.js +15 -15
- package/server/routes/tags/index.mjs +14 -14
- package/server/templates/cls/cms.category_type.json +9 -9
- package/server/templates/cls/cms.content_review_status.json +9 -9
- package/server/templates/cls/cms.content_status.json +9 -9
- package/server/templates/cls/cms.content_type.json +9 -9
- package/server/templates/cls/cms.lang.json +9 -9
- package/server/templates/page/login.html +126 -126
- package/server/templates/select/core.user_mentioned.sql +1 -1
- package/dist/index-W-qQIppj-DRzFSjU1.js +0 -2452
|
@@ -1,90 +1,90 @@
|
|
|
1
|
-
import { pgClients, eventStream } from "@opengis/fastify-table/utils.js";
|
|
2
|
-
|
|
3
|
-
import insertContentLocalization from "../utils/insertContentLocalization.js";
|
|
4
|
-
|
|
5
|
-
const getCollectionId = async (id, pg) => pg.query(`select content_type_id as id, table_name as table, columns FROM site.content_types
|
|
6
|
-
where $1 in (content_type_id, name)`, [id]).then(el => el.rows?.[0] || {});
|
|
7
|
-
|
|
8
|
-
function sequence(arr, data, fn) {
|
|
9
|
-
return arr.reduce((promise, id) => promise.then(() => fn({
|
|
10
|
-
...data, id,
|
|
11
|
-
})), Promise.resolve());
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export async function translateContent({
|
|
15
|
-
pg = pgClients.client, query, user,
|
|
16
|
-
}, reply) {
|
|
17
|
-
if (!query.id) {
|
|
18
|
-
return reply.status(400).send({ error: 'not enough query params: id', code: 400 });
|
|
19
|
-
}
|
|
20
|
-
if (!query.to) {
|
|
21
|
-
return reply.status(400).send({ error: 'not enough query params: to', code: 400 });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const { id, collection = 'pages', from = 'uk', to, nocache } = query;
|
|
25
|
-
const { id: collectionId, table, columns = [] } = await getCollectionId(collection, pg);
|
|
26
|
-
|
|
27
|
-
if (!collectionId) {
|
|
28
|
-
return reply.status(404).send({ error: 'collection not found', code: 404 });
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const schemaKeys = columns.filter(el => el.name && el.localization).map(el => el.name);
|
|
32
|
-
|
|
33
|
-
if (!schemaKeys.length) {
|
|
34
|
-
return reply.status(400).send({ error: 'collection does not contain any fields with localization enabled', code: 400 });
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const result = await insertContentLocalization({ table, id, from, to, nocache, schemaKeys, user }, pg);
|
|
38
|
-
|
|
39
|
-
if (result?.error) {
|
|
40
|
-
return reply.status(result.code).send(result);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return result;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export async function translateCollection({
|
|
47
|
-
pg = pgClients.client, params, query, user,
|
|
48
|
-
}, reply) {
|
|
49
|
-
if (!query.to) {
|
|
50
|
-
return reply.status(400).send({ error: 'not enough query params: to', code: 400 });
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const { id: collectionId, table, columns = [] } = await getCollectionId(params.id, pg);
|
|
54
|
-
|
|
55
|
-
if (!collectionId) {
|
|
56
|
-
return reply.status(404).send({ error: 'collection not found', code: 404 });
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const schemaKeys = columns.filter(el => el.name && el.localization).map(el => el.name);
|
|
60
|
-
|
|
61
|
-
if (!schemaKeys.length) {
|
|
62
|
-
return reply.status(400).send({ error: 'collection does not contain any fields with localization enabled', code: 400 });
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const { from = 'uk', to, nocache, debug } = query;
|
|
66
|
-
|
|
67
|
-
const send = eventStream(reply);
|
|
68
|
-
|
|
69
|
-
send(`target localization: ${to}`);
|
|
70
|
-
send(`skip existing: ${!nocache}`);
|
|
71
|
-
send('localization enabled for schema keys: ' + schemaKeys.join(','));
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
const ids = !table
|
|
75
|
-
? await pg.query(`select array_agg(content_id) from site.contents where content_type_id=$1`, [collectionId]).then(el => el.rows?.[0]?.array_agg || [])
|
|
76
|
-
: await pg.query(`select array_agg(id) from data."${table}"`).then(el => el.rows?.[0]?.array_agg || []);
|
|
77
|
-
|
|
78
|
-
const skip = await pg.query('select array_agg(object_id) from site.localization where object_id=any($1::text[]) and split_part(field_key,\':\',2)=$2', [ids, to]).then(el => el.rows?.[0]?.array_agg || []);
|
|
79
|
-
const filteredIds = nocache ? ids : ids.filter(id => !skip.includes(id));
|
|
80
|
-
|
|
81
|
-
send(`target localization already exists for ${ids.length - filteredIds.length}/${ids.length} rows`);
|
|
82
|
-
|
|
83
|
-
await sequence(debug ? filteredIds.slice(0, 1) : filteredIds, { send, pg, from, to, table, nocache: true, schemaKeys, user }, insertContentLocalization);
|
|
84
|
-
return send('translation complete', true);
|
|
85
|
-
} catch (err) {
|
|
86
|
-
return send(err.toString(), true);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
1
|
+
import { pgClients, eventStream } from "@opengis/fastify-table/utils.js";
|
|
2
|
+
|
|
3
|
+
import insertContentLocalization from "../utils/insertContentLocalization.js";
|
|
4
|
+
|
|
5
|
+
const getCollectionId = async (id, pg) => pg.query(`select content_type_id as id, table_name as table, columns FROM site.content_types
|
|
6
|
+
where $1 in (content_type_id, name)`, [id]).then(el => el.rows?.[0] || {});
|
|
7
|
+
|
|
8
|
+
function sequence(arr, data, fn) {
|
|
9
|
+
return arr.reduce((promise, id) => promise.then(() => fn({
|
|
10
|
+
...data, id,
|
|
11
|
+
})), Promise.resolve());
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function translateContent({
|
|
15
|
+
pg = pgClients.client, query, user,
|
|
16
|
+
}, reply) {
|
|
17
|
+
if (!query.id) {
|
|
18
|
+
return reply.status(400).send({ error: 'not enough query params: id', code: 400 });
|
|
19
|
+
}
|
|
20
|
+
if (!query.to) {
|
|
21
|
+
return reply.status(400).send({ error: 'not enough query params: to', code: 400 });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { id, collection = 'pages', from = 'uk', to, nocache } = query;
|
|
25
|
+
const { id: collectionId, table, columns = [] } = await getCollectionId(collection, pg);
|
|
26
|
+
|
|
27
|
+
if (!collectionId) {
|
|
28
|
+
return reply.status(404).send({ error: 'collection not found', code: 404 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const schemaKeys = columns.filter(el => el.name && el.localization).map(el => el.name);
|
|
32
|
+
|
|
33
|
+
if (!schemaKeys.length) {
|
|
34
|
+
return reply.status(400).send({ error: 'collection does not contain any fields with localization enabled', code: 400 });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const result = await insertContentLocalization({ table, id, from, to, nocache, schemaKeys, user }, pg);
|
|
38
|
+
|
|
39
|
+
if (result?.error) {
|
|
40
|
+
return reply.status(result.code).send(result);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function translateCollection({
|
|
47
|
+
pg = pgClients.client, params, query, user,
|
|
48
|
+
}, reply) {
|
|
49
|
+
if (!query.to) {
|
|
50
|
+
return reply.status(400).send({ error: 'not enough query params: to', code: 400 });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { id: collectionId, table, columns = [] } = await getCollectionId(params.id, pg);
|
|
54
|
+
|
|
55
|
+
if (!collectionId) {
|
|
56
|
+
return reply.status(404).send({ error: 'collection not found', code: 404 });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const schemaKeys = columns.filter(el => el.name && el.localization).map(el => el.name);
|
|
60
|
+
|
|
61
|
+
if (!schemaKeys.length) {
|
|
62
|
+
return reply.status(400).send({ error: 'collection does not contain any fields with localization enabled', code: 400 });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { from = 'uk', to, nocache, debug } = query;
|
|
66
|
+
|
|
67
|
+
const send = eventStream(reply);
|
|
68
|
+
|
|
69
|
+
send(`target localization: ${to}`);
|
|
70
|
+
send(`skip existing: ${!nocache}`);
|
|
71
|
+
send('localization enabled for schema keys: ' + schemaKeys.join(','));
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const ids = !table
|
|
75
|
+
? await pg.query(`select array_agg(content_id) from site.contents where content_type_id=$1`, [collectionId]).then(el => el.rows?.[0]?.array_agg || [])
|
|
76
|
+
: await pg.query(`select array_agg(id) from data."${table}"`).then(el => el.rows?.[0]?.array_agg || []);
|
|
77
|
+
|
|
78
|
+
const skip = await pg.query('select array_agg(object_id) from site.localization where object_id=any($1::text[]) and split_part(field_key,\':\',2)=$2', [ids, to]).then(el => el.rows?.[0]?.array_agg || []);
|
|
79
|
+
const filteredIds = nocache ? ids : ids.filter(id => !skip.includes(id));
|
|
80
|
+
|
|
81
|
+
send(`target localization already exists for ${ids.length - filteredIds.length}/${ids.length} rows`);
|
|
82
|
+
|
|
83
|
+
await sequence(debug ? filteredIds.slice(0, 1) : filteredIds, { send, pg, from, to, table, nocache: true, schemaKeys, user }, insertContentLocalization);
|
|
84
|
+
return send('translation complete', true);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return send(err.toString(), true);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
90
|
export default null;
|
|
@@ -1,239 +1,239 @@
|
|
|
1
|
-
import { config, pgClients, dataUpdate, dataInsert, getTemplate, checkSQL, logger } from '@opengis/fastify-table/utils.js';
|
|
2
|
-
|
|
3
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
-
|
|
5
|
-
const inputTypes = existsSync('input-types.json') ? JSON.parse(readFileSync('input-types.json') || '{}') : {};
|
|
6
|
-
|
|
7
|
-
import updateLocalization from '../utils/updateLocalization.js';
|
|
8
|
-
|
|
9
|
-
const defaultColumns = [
|
|
10
|
-
'content_id',
|
|
11
|
-
'space_id',
|
|
12
|
-
'content_type_id',
|
|
13
|
-
'created_at',
|
|
14
|
-
'updated_at',
|
|
15
|
-
'published_at',
|
|
16
|
-
'revision',
|
|
17
|
-
'locale',
|
|
18
|
-
'status',
|
|
19
|
-
'slug',
|
|
20
|
-
'title',
|
|
21
|
-
'created_by',
|
|
22
|
-
'published_by',
|
|
23
|
-
'updated_by',
|
|
24
|
-
'meta',
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
export default async function updateContent(req, reply) {
|
|
28
|
-
const {
|
|
29
|
-
pg = pgClients.client,
|
|
30
|
-
params = {},
|
|
31
|
-
user = {},
|
|
32
|
-
body = {},
|
|
33
|
-
headers = {},
|
|
34
|
-
} = req;
|
|
35
|
-
|
|
36
|
-
const { type, id } = params;
|
|
37
|
-
|
|
38
|
-
if (!type) {
|
|
39
|
-
return reply.status(400).send('not enough params: type');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (!id) {
|
|
43
|
-
return reply.status(400).send('not enough params: id');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!Object.keys(body || {}).length) {
|
|
47
|
-
return reply.status(400).send('empty body');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const arr = config.pg ? await pg.query(`select array_agg(relname)::text[] from pg_class a
|
|
51
|
-
left join pg_namespace b on a.relnamespace=b.oid
|
|
52
|
-
where a.relam=2 and b.nspname='data'`).then(el => el.rows?.[0]?.array_agg || []) : [];
|
|
53
|
-
|
|
54
|
-
if (!arr.length) {
|
|
55
|
-
return reply.status(400).send('empty schema: data');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const table = arr.find(el => el === params.type);
|
|
59
|
-
|
|
60
|
-
// order priority - custom columns -> default for pages
|
|
61
|
-
const { ctid, ctname, dbtable, columns: contentColumns = [] } = await pg.query('select content_type_id as ctid, name as ctname, table_name as dbtable, columns from site.content_types where content_type_id in (select content_type_id from site.contents where content_id=$1) or content_type_id=$2 order by content_type_id = \'pages\'', [id, type]).then(el => el.rows?.[0] || {});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const loadTable = type === 'pages' ? await getTemplate('table', 'single.default.table') : {};
|
|
65
|
-
const columns = type === 'pages'
|
|
66
|
-
? (loadTable?.columns || []).concat(contentColumns.filter(col => loadTable?.columns.findIndex(el => el.name === col.name) === -1))
|
|
67
|
-
: contentColumns;
|
|
68
|
-
|
|
69
|
-
// const xss = checkXSS({ body });
|
|
70
|
-
const sql = checkSQL({ body });
|
|
71
|
-
|
|
72
|
-
if (/*xss.error ||*/ sql.error && false) {
|
|
73
|
-
logger.file(`injection/${/*xss.error ? 'xss' : */'sql'}/cms`, {
|
|
74
|
-
table,
|
|
75
|
-
...params,
|
|
76
|
-
uid: user?.uid,
|
|
77
|
-
...(/*xss.error ? xss : */sql),
|
|
78
|
-
});
|
|
79
|
-
return reply
|
|
80
|
-
.status(409)
|
|
81
|
-
.send(
|
|
82
|
-
`Дані містять заборонені ${/*xss.error ? 'xss' : */'sql'} символи. Приберіть їх та спробуйте ще раз`
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// site.content_data, includes singletone
|
|
87
|
-
if (((!table && !dbtable) || ['single', 'pages'].includes(type)) && ctid) {
|
|
88
|
-
const cid = await pg.query(
|
|
89
|
-
'select content_id from site.contents where content_type_id=$1 limit 1',
|
|
90
|
-
[ctid],
|
|
91
|
-
).then(el => el.rows?.[0]?.content_id);
|
|
92
|
-
|
|
93
|
-
const ctid1 = body.content_type_id || ctid;
|
|
94
|
-
|
|
95
|
-
if (!cid) {
|
|
96
|
-
return reply.status(404).send('contents not found');
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const contentId = cid === 'pages'
|
|
100
|
-
? await pg.query('select content_id from site.content_data where object_id=$1', [id]).then(el => el.rows?.[0]?.content_id)
|
|
101
|
-
: cid;
|
|
102
|
-
|
|
103
|
-
if (!contentId) {
|
|
104
|
-
return reply.status(404).send('contents not found: 2');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const columnList = columns?.map?.(el => el.name) || [];
|
|
108
|
-
const types = columns?.reduce?.((acc, curr) => ({ ...acc, [curr.name]: curr.type || 'text' }), {}) || {};
|
|
109
|
-
const keys = Object.keys(body || {}).filter(key => columnList.includes(key) && !defaultColumns.includes(key));
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (!Object.keys(body || {}).length) {
|
|
113
|
-
return reply.status(400).send('invalid payload');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const blocks = await pg.query(`select json_object_agg(field_key,field_value) from site.content_data where content_id=$1 and field_type='reference'`, [id])
|
|
117
|
-
.then(el => el.rows?.[0]?.json_object_agg || {});
|
|
118
|
-
|
|
119
|
-
const emptyBlock = Object.keys(body).find(key => blocks[key] && (!body[key] || typeof body[key] !== 'object' || Object.keys(body[key] || {}) === 0));
|
|
120
|
-
|
|
121
|
-
if (emptyBlock) {
|
|
122
|
-
return reply.status(400).send('access restricted: empty/invalid block ' + emptyBlock);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const client = await pg.connect();
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
await client.query('begin');
|
|
129
|
-
const res = await dataUpdate({
|
|
130
|
-
pg: client,
|
|
131
|
-
table: 'site.contents',
|
|
132
|
-
id,
|
|
133
|
-
data: { ...body, content_type_id: ctid1 },
|
|
134
|
-
uid: user?.uid,
|
|
135
|
-
});
|
|
136
|
-
await client.query(`delete from site.content_data where content_id=$1`, [contentId]);
|
|
137
|
-
const objectId = (ctname === 'pages' || ['single', 'pages'].includes(type)) && id ? id : cid;
|
|
138
|
-
await client.query(`delete from site.content_data where object_id=$1`, [objectId]);
|
|
139
|
-
await Promise.all(keys.map(async key => dataInsert({
|
|
140
|
-
pg: client,
|
|
141
|
-
table: 'site.content_data',
|
|
142
|
-
data: {
|
|
143
|
-
field_key: key,
|
|
144
|
-
content_id: objectId,
|
|
145
|
-
object_id: objectId,
|
|
146
|
-
field_type: types[key] || 'text',
|
|
147
|
-
field_value: inputTypes[types[key] || ''] === 'json' || key === 'meta' ? undefined : body[key],
|
|
148
|
-
field_value_object: inputTypes[types[key] || ''] === 'json' || key === 'meta' ? body[key] : undefined,
|
|
149
|
-
},
|
|
150
|
-
uid: user?.uid,
|
|
151
|
-
})));
|
|
152
|
-
|
|
153
|
-
// if pages allow localization, update localization
|
|
154
|
-
const localeRes = await updateLocalization(client, id, body, type === 'pages' ? type : ctid1, user?.uid);
|
|
155
|
-
|
|
156
|
-
if (Object.hasOwn(body, 'tag_list')) {
|
|
157
|
-
await client.query('delete from site.tag_data where data_id=$1', [id]);
|
|
158
|
-
if (body.tag_list?.length) {
|
|
159
|
-
await Promise.all(body.tag_list.map(async tag => dataInsert({
|
|
160
|
-
pg: client,
|
|
161
|
-
table: 'site.tag_data',
|
|
162
|
-
data: {
|
|
163
|
-
tag_id: tag,
|
|
164
|
-
data_id: id,
|
|
165
|
-
},
|
|
166
|
-
uid: user?.uid,
|
|
167
|
-
})));
|
|
168
|
-
Object.assign(res, { tag_list: body.tag_list });
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
await client.query('commit');
|
|
173
|
-
|
|
174
|
-
return {
|
|
175
|
-
id, ...res || {}, ...(keys || []).reduce((acc, curr) => ({ ...acc, [curr]: body[curr] }), {}), ...(localeRes || {})
|
|
176
|
-
};
|
|
177
|
-
} catch (err) {
|
|
178
|
-
await client.query('rollback');
|
|
179
|
-
return reply.status(500).send(err.toString());
|
|
180
|
-
} finally {
|
|
181
|
-
client.release();
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (!table && !dbtable) {
|
|
186
|
-
return reply.status(400).send('invalid params: type');
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const client = await pg.connect();
|
|
190
|
-
|
|
191
|
-
try {
|
|
192
|
-
await client.query('begin');
|
|
193
|
-
|
|
194
|
-
const result = await dataUpdate({
|
|
195
|
-
pg: client,
|
|
196
|
-
id,
|
|
197
|
-
table: 'data.' + `"${(table || dbtable)}"`,
|
|
198
|
-
data: body,
|
|
199
|
-
referer: headers?.referer,
|
|
200
|
-
uid: user?.uid,
|
|
201
|
-
}).catch(err => {
|
|
202
|
-
if (err.message?.includes?.('unique constraint')) {
|
|
203
|
-
throw new Error('Порушенні унікальності: ' + err.message?.match?.(/([^"]+)/g)?.[1]);
|
|
204
|
-
}
|
|
205
|
-
throw err;
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
await updateLocalization(client, result?.id, body, ctid, user?.uid);
|
|
209
|
-
|
|
210
|
-
if (Object.hasOwn(body, 'tag_list')) {
|
|
211
|
-
await client.query('delete from site.tag_data where data_id=$1', [id]);
|
|
212
|
-
if (body.tag_list?.length) {
|
|
213
|
-
await Promise.all(body.tag_list.map(async tag => dataInsert({
|
|
214
|
-
pg: client,
|
|
215
|
-
table: 'site.tag_data',
|
|
216
|
-
data: {
|
|
217
|
-
tag_id: tag?.id,
|
|
218
|
-
data_id: id,
|
|
219
|
-
},
|
|
220
|
-
uid: user?.uid,
|
|
221
|
-
})));
|
|
222
|
-
Object.assign(result, { tag_list: body.tag_list });
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
await client.query('commit');
|
|
227
|
-
|
|
228
|
-
if (!result?.id) {
|
|
229
|
-
return reply.status(404).send('content not found');
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return reply.status(200).send(result);
|
|
233
|
-
} catch (err) {
|
|
234
|
-
await client.query('rollback');
|
|
235
|
-
return reply.status(500).send(err.toString());
|
|
236
|
-
} finally {
|
|
237
|
-
client.release();
|
|
238
|
-
}
|
|
1
|
+
import { config, pgClients, dataUpdate, dataInsert, getTemplate, checkSQL, logger } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
const inputTypes = existsSync('input-types.json') ? JSON.parse(readFileSync('input-types.json') || '{}') : {};
|
|
6
|
+
|
|
7
|
+
import updateLocalization from '../utils/updateLocalization.js';
|
|
8
|
+
|
|
9
|
+
const defaultColumns = [
|
|
10
|
+
'content_id',
|
|
11
|
+
'space_id',
|
|
12
|
+
'content_type_id',
|
|
13
|
+
'created_at',
|
|
14
|
+
'updated_at',
|
|
15
|
+
'published_at',
|
|
16
|
+
'revision',
|
|
17
|
+
'locale',
|
|
18
|
+
'status',
|
|
19
|
+
'slug',
|
|
20
|
+
'title',
|
|
21
|
+
'created_by',
|
|
22
|
+
'published_by',
|
|
23
|
+
'updated_by',
|
|
24
|
+
'meta',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export default async function updateContent(req, reply) {
|
|
28
|
+
const {
|
|
29
|
+
pg = pgClients.client,
|
|
30
|
+
params = {},
|
|
31
|
+
user = {},
|
|
32
|
+
body = {},
|
|
33
|
+
headers = {},
|
|
34
|
+
} = req;
|
|
35
|
+
|
|
36
|
+
const { type, id } = params;
|
|
37
|
+
|
|
38
|
+
if (!type) {
|
|
39
|
+
return reply.status(400).send('not enough params: type');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!id) {
|
|
43
|
+
return reply.status(400).send('not enough params: id');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!Object.keys(body || {}).length) {
|
|
47
|
+
return reply.status(400).send('empty body');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const arr = config.pg ? await pg.query(`select array_agg(relname)::text[] from pg_class a
|
|
51
|
+
left join pg_namespace b on a.relnamespace=b.oid
|
|
52
|
+
where a.relam=2 and b.nspname='data'`).then(el => el.rows?.[0]?.array_agg || []) : [];
|
|
53
|
+
|
|
54
|
+
if (!arr.length) {
|
|
55
|
+
return reply.status(400).send('empty schema: data');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const table = arr.find(el => el === params.type);
|
|
59
|
+
|
|
60
|
+
// order priority - custom columns -> default for pages
|
|
61
|
+
const { ctid, ctname, dbtable, columns: contentColumns = [] } = await pg.query('select content_type_id as ctid, name as ctname, table_name as dbtable, columns from site.content_types where content_type_id in (select content_type_id from site.contents where content_id=$1) or content_type_id=$2 order by content_type_id = \'pages\'', [id, type]).then(el => el.rows?.[0] || {});
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
const loadTable = type === 'pages' ? await getTemplate('table', 'single.default.table') : {};
|
|
65
|
+
const columns = type === 'pages'
|
|
66
|
+
? (loadTable?.columns || []).concat(contentColumns.filter(col => loadTable?.columns.findIndex(el => el.name === col.name) === -1))
|
|
67
|
+
: contentColumns;
|
|
68
|
+
|
|
69
|
+
// const xss = checkXSS({ body });
|
|
70
|
+
const sql = checkSQL({ body });
|
|
71
|
+
|
|
72
|
+
if (/*xss.error ||*/ sql.error && false) {
|
|
73
|
+
logger.file(`injection/${/*xss.error ? 'xss' : */'sql'}/cms`, {
|
|
74
|
+
table,
|
|
75
|
+
...params,
|
|
76
|
+
uid: user?.uid,
|
|
77
|
+
...(/*xss.error ? xss : */sql),
|
|
78
|
+
});
|
|
79
|
+
return reply
|
|
80
|
+
.status(409)
|
|
81
|
+
.send(
|
|
82
|
+
`Дані містять заборонені ${/*xss.error ? 'xss' : */'sql'} символи. Приберіть їх та спробуйте ще раз`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// site.content_data, includes singletone
|
|
87
|
+
if (((!table && !dbtable) || ['single', 'pages'].includes(type)) && ctid) {
|
|
88
|
+
const cid = await pg.query(
|
|
89
|
+
'select content_id from site.contents where content_type_id=$1 limit 1',
|
|
90
|
+
[ctid],
|
|
91
|
+
).then(el => el.rows?.[0]?.content_id);
|
|
92
|
+
|
|
93
|
+
const ctid1 = body.content_type_id || ctid;
|
|
94
|
+
|
|
95
|
+
if (!cid) {
|
|
96
|
+
return reply.status(404).send('contents not found');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const contentId = cid === 'pages'
|
|
100
|
+
? await pg.query('select content_id from site.content_data where object_id=$1', [id]).then(el => el.rows?.[0]?.content_id)
|
|
101
|
+
: cid;
|
|
102
|
+
|
|
103
|
+
if (!contentId) {
|
|
104
|
+
return reply.status(404).send('contents not found: 2');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const columnList = columns?.map?.(el => el.name) || [];
|
|
108
|
+
const types = columns?.reduce?.((acc, curr) => ({ ...acc, [curr.name]: curr.type || 'text' }), {}) || {};
|
|
109
|
+
const keys = Object.keys(body || {}).filter(key => columnList.includes(key) && !defaultColumns.includes(key));
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if (!Object.keys(body || {}).length) {
|
|
113
|
+
return reply.status(400).send('invalid payload');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const blocks = await pg.query(`select json_object_agg(field_key,field_value) from site.content_data where content_id=$1 and field_type='reference'`, [id])
|
|
117
|
+
.then(el => el.rows?.[0]?.json_object_agg || {});
|
|
118
|
+
|
|
119
|
+
const emptyBlock = Object.keys(body).find(key => blocks[key] && (!body[key] || typeof body[key] !== 'object' || Object.keys(body[key] || {}) === 0));
|
|
120
|
+
|
|
121
|
+
if (emptyBlock) {
|
|
122
|
+
return reply.status(400).send('access restricted: empty/invalid block ' + emptyBlock);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const client = await pg.connect();
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
await client.query('begin');
|
|
129
|
+
const res = await dataUpdate({
|
|
130
|
+
pg: client,
|
|
131
|
+
table: 'site.contents',
|
|
132
|
+
id,
|
|
133
|
+
data: { ...body, content_type_id: ctid1 },
|
|
134
|
+
uid: user?.uid,
|
|
135
|
+
});
|
|
136
|
+
await client.query(`delete from site.content_data where content_id=$1`, [contentId]);
|
|
137
|
+
const objectId = (ctname === 'pages' || ['single', 'pages'].includes(type)) && id ? id : cid;
|
|
138
|
+
await client.query(`delete from site.content_data where object_id=$1`, [objectId]);
|
|
139
|
+
await Promise.all(keys.map(async key => dataInsert({
|
|
140
|
+
pg: client,
|
|
141
|
+
table: 'site.content_data',
|
|
142
|
+
data: {
|
|
143
|
+
field_key: key,
|
|
144
|
+
content_id: objectId,
|
|
145
|
+
object_id: objectId,
|
|
146
|
+
field_type: types[key] || 'text',
|
|
147
|
+
field_value: inputTypes[types[key] || ''] === 'json' || key === 'meta' ? undefined : body[key],
|
|
148
|
+
field_value_object: inputTypes[types[key] || ''] === 'json' || key === 'meta' ? body[key] : undefined,
|
|
149
|
+
},
|
|
150
|
+
uid: user?.uid,
|
|
151
|
+
})));
|
|
152
|
+
|
|
153
|
+
// if pages allow localization, update localization
|
|
154
|
+
const localeRes = await updateLocalization(client, id, body, type === 'pages' ? type : ctid1, user?.uid);
|
|
155
|
+
|
|
156
|
+
if (Object.hasOwn(body, 'tag_list')) {
|
|
157
|
+
await client.query('delete from site.tag_data where data_id=$1', [id]);
|
|
158
|
+
if (body.tag_list?.length) {
|
|
159
|
+
await Promise.all(body.tag_list.map(async tag => dataInsert({
|
|
160
|
+
pg: client,
|
|
161
|
+
table: 'site.tag_data',
|
|
162
|
+
data: {
|
|
163
|
+
tag_id: tag,
|
|
164
|
+
data_id: id,
|
|
165
|
+
},
|
|
166
|
+
uid: user?.uid,
|
|
167
|
+
})));
|
|
168
|
+
Object.assign(res, { tag_list: body.tag_list });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
await client.query('commit');
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
id, ...res || {}, ...(keys || []).reduce((acc, curr) => ({ ...acc, [curr]: body[curr] }), {}), ...(localeRes || {})
|
|
176
|
+
};
|
|
177
|
+
} catch (err) {
|
|
178
|
+
await client.query('rollback');
|
|
179
|
+
return reply.status(500).send(err.toString());
|
|
180
|
+
} finally {
|
|
181
|
+
client.release();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!table && !dbtable) {
|
|
186
|
+
return reply.status(400).send('invalid params: type');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const client = await pg.connect();
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
await client.query('begin');
|
|
193
|
+
|
|
194
|
+
const result = await dataUpdate({
|
|
195
|
+
pg: client,
|
|
196
|
+
id,
|
|
197
|
+
table: 'data.' + `"${(table || dbtable)}"`,
|
|
198
|
+
data: body,
|
|
199
|
+
referer: headers?.referer,
|
|
200
|
+
uid: user?.uid,
|
|
201
|
+
}).catch(err => {
|
|
202
|
+
if (err.message?.includes?.('unique constraint')) {
|
|
203
|
+
throw new Error('Порушенні унікальності: ' + err.message?.match?.(/([^"]+)/g)?.[1]);
|
|
204
|
+
}
|
|
205
|
+
throw err;
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
await updateLocalization(client, result?.id, body, ctid, user?.uid);
|
|
209
|
+
|
|
210
|
+
if (Object.hasOwn(body, 'tag_list')) {
|
|
211
|
+
await client.query('delete from site.tag_data where data_id=$1', [id]);
|
|
212
|
+
if (body.tag_list?.length) {
|
|
213
|
+
await Promise.all(body.tag_list.map(async tag => dataInsert({
|
|
214
|
+
pg: client,
|
|
215
|
+
table: 'site.tag_data',
|
|
216
|
+
data: {
|
|
217
|
+
tag_id: tag?.id,
|
|
218
|
+
data_id: id,
|
|
219
|
+
},
|
|
220
|
+
uid: user?.uid,
|
|
221
|
+
})));
|
|
222
|
+
Object.assign(result, { tag_list: body.tag_list });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
await client.query('commit');
|
|
227
|
+
|
|
228
|
+
if (!result?.id) {
|
|
229
|
+
return reply.status(404).send('content not found');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return reply.status(200).send(result);
|
|
233
|
+
} catch (err) {
|
|
234
|
+
await client.query('rollback');
|
|
235
|
+
return reply.status(500).send(err.toString());
|
|
236
|
+
} finally {
|
|
237
|
+
client.release();
|
|
238
|
+
}
|
|
239
239
|
}
|