@opengis/cms 0.0.16 → 0.0.18
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 +96 -3
- package/dist/assets/AddUser-CX-McfRW.js +1 -0
- package/dist/assets/ApiKeys-DSv1exYv.js +16 -0
- package/dist/assets/Appearance-DDtOUvCV.js +6 -0
- package/dist/assets/ArticlesPage-D6B3cZsl.js +6 -0
- package/dist/assets/BuilderPage-CCeSMVWe.js +1 -0
- package/dist/assets/CollectionsBreadcrumb.vue_vue_type_script_setup_true_lang-DVYVfYF4.js +1 -0
- package/dist/assets/CollectionsPage-CHk8Cn5k.js +1 -0
- package/dist/assets/Dashboard-Bs7sXO6h.js +11 -0
- package/dist/assets/EditCollectionPage-gPuLJrN8.js +41 -0
- package/dist/assets/EmailPage-DhlWsPxk.js +1 -0
- package/dist/assets/EmptyData-Ct-xQv_N.js +1 -0
- package/dist/assets/FeedbackPage-DtaOncVv.js +1 -0
- package/dist/assets/Logs-CZ5klHNK.js +1 -0
- package/dist/assets/MediaBreadcrumb-BpOxt5PK.js +11 -0
- package/dist/assets/MediaPage-DqRcZFlO.js +16 -0
- package/dist/assets/MenuAddPage-BLcoVgrS.js +1 -0
- package/dist/assets/MenuItemPage-B2otXqkz.js +20 -0
- package/dist/assets/MenuPage-C00m4Fc_.js +1 -0
- package/dist/assets/MonacoEditor.vue_vue_type_script_setup_true_lang-CjoEsC67.js +3 -0
- package/dist/assets/PermissionsPage-LtqcCJ14.js +1 -0
- package/dist/assets/Settings-BHH6RoBP.js +1 -0
- package/dist/assets/SettingsTable-CY5pZx1z.js +1 -0
- package/dist/assets/SettingsTitle-t4WJBFxZ.js +1 -0
- package/dist/assets/SingletonsPage-Bn2Ypjhs.js +6 -0
- package/dist/assets/TagsPage-DAiakEth.js +1 -0
- package/dist/assets/UniversalTable.vue_vue_type_script_setup_true_lang-BQ5m4aZd.js +11 -0
- package/dist/assets/UniversalTablePagination.vue_vue_type_script_setup_true_lang-DppVBws0.js +1 -0
- package/dist/assets/Users-CMH5j0db.js +1 -0
- package/dist/assets/UsersPage-CDGreEib.js +1 -0
- package/dist/assets/arrow-up-DCe0WsrM.js +16 -0
- package/dist/assets/{calendar-hsWc4yH-.js → calendar-o9t4MkD2.js} +1 -1
- package/dist/assets/chevron-left-WFftVS9c.js +6 -0
- package/dist/assets/chevron-right-BiiSb3Be.js +6 -0
- package/dist/assets/contentForm-unZQhjCu.js +6 -0
- package/dist/assets/en-BDx3Svx8.js +1 -0
- package/dist/assets/eye-Dijywc6g.js +6 -0
- package/dist/assets/file-B_duymIT.js +6 -0
- package/dist/assets/general-CkN_0qIV.js +1 -0
- package/dist/assets/index-BIp7eSXk.js +1 -0
- package/dist/assets/index-DCW2e4Az.js +9 -0
- package/dist/assets/index-DGweaj24.js +1 -0
- package/dist/assets/index-W-qQIppj-BDlsxaGB.js +1 -0
- package/dist/assets/index-W-qQIppj-BsopI3Hz-BIZR-dhy.js +1 -0
- package/dist/assets/index-oQz9FOqL.css +1 -0
- package/dist/assets/index-yMJAVBXk.js +290 -0
- package/dist/assets/list-CXRbSNky.js +6 -0
- package/dist/assets/pencil-CwnPP4IJ.js +6 -0
- package/dist/assets/{plus-D9etvrM2.js → plus-DLR44m6p.js} +1 -1
- package/dist/assets/save-FeDrOUOd.js +6 -0
- package/dist/assets/{search-BI-hqhq6.js → search-C4-fHihx.js} +1 -1
- package/dist/assets/{square-pen-61CkyXzK.js → square-pen-xVs4e8Yb.js} +1 -1
- package/dist/assets/{trash-2-CJSl_r88.js → trash-2-BGXMNU3d.js} +1 -1
- package/dist/assets/uk-BA7DIKEL.js +1 -0
- package/dist/assets/useDebounce-DFq3rxAW.js +1 -0
- package/dist/assets/vs-form-reletion-link-C-xrdHDl.js +20 -0
- package/dist/assets/vs-form-reletion-link-bk-9ZkDH.css +1 -0
- package/dist/assets/vue.-sixQ7xP-CUPNuJcq.js +1 -0
- package/dist/assets/vuedraggable.umd-W_2WTF6i.js +14 -0
- package/dist/assets/{x-BNquQe5y.js → x-D2t-wfBe.js} +1 -1
- package/dist/index.html +14 -9
- package/module/cms/card/cms.content.table/index.yml +17 -0
- package/module/cms/card/cms.content.table/main_info.hbs +26 -0
- package/module/cms/card/cms.menu.table/content_info.hbs +16 -0
- package/module/cms/card/cms.menu.table/index.yml +18 -0
- package/module/cms/card/cms.menu.table/main_info.hbs +22 -0
- package/module/cms/card/cms.settings.table/index.yml +13 -0
- package/module/cms/card/cms.settings.table/main_info.hbs +20 -0
- package/module/cms/cls/content.status.json +18 -0
- package/module/cms/cls/user_type.json +10 -0
- package/module/cms/form/admin.users.form.json +78 -0
- package/module/cms/form/cms.content.form.json +79 -0
- package/module/cms/form/cms.menu.form.json +69 -0
- package/module/cms/form/cms.settings.form.json +32 -0
- package/module/cms/menu.json +24 -0
- package/module/cms/router.js +154 -0
- package/module/cms/select/cms.page_type.sql +2 -0
- package/module/cms/select/collection.sql +1 -0
- package/module/cms/select/locale.sql +17 -0
- package/module/cms/select/news_tag_id.sql +12 -0
- package/module/cms/select/tag_id.sql +1 -0
- package/module/cms/table/admin.users.table.json +54 -0
- package/module/cms/table/cms.content.table.json +106 -0
- package/module/cms/table/cms.menu.table.json +73 -0
- package/module/cms/table/cms.settings.table.json +57 -0
- package/module/cms/table/collection.default.table.json +102 -0
- package/module/cms/table/single.default.table.json +115 -0
- package/package.json +36 -31
- package/plugin.js +63 -23
- package/server/app.js +20 -3
- package/server/functions/getDraftKey.js +22 -0
- package/server/index.js +2 -3
- package/server/migrations/fixes.sql +124 -0
- package/server/migrations/site.sql +338 -249
- package/server/plugins/adminHook.js +2 -2
- package/server/plugins/hook.js +53 -61
- package/server/plugins/vite.js +5 -5
- package/server/routes/cms/controllers/cmsStat.js +56 -0
- package/server/routes/cms/controllers/cmsSuggest.js +58 -0
- package/server/routes/cms/controllers/deleteContent.js +114 -59
- package/server/routes/cms/controllers/deleteMedia.js +75 -46
- package/server/routes/cms/controllers/downloadMedia.js +48 -48
- package/server/routes/cms/controllers/getContent.js +110 -95
- package/server/routes/cms/controllers/getContentBySlug.js +95 -0
- package/server/routes/cms/controllers/getPermissions.js +15 -15
- package/server/routes/cms/controllers/insertContent.js +218 -68
- package/server/routes/cms/controllers/listMedia.js +93 -72
- package/server/routes/cms/controllers/metadataMedia.js +38 -37
- package/server/routes/cms/controllers/properties.get.js +53 -0
- package/server/routes/cms/controllers/properties.post.js +99 -0
- package/server/routes/cms/controllers/searchContent.js +205 -0
- package/server/routes/cms/controllers/setPermissions.js +49 -49
- package/server/routes/cms/controllers/translate.js +90 -0
- package/server/routes/cms/controllers/updateContent.js +238 -111
- package/server/routes/cms/controllers/uploadMedia.js +78 -65
- package/server/routes/cms/index.mjs +81 -12
- package/server/routes/cms/utils/additionalData.js +36 -0
- package/server/routes/cms/utils/getCollection.js +82 -0
- package/server/routes/cms/utils/getSingle.js +188 -0
- package/server/routes/cms/utils/insertContentLocalization.js +87 -0
- package/server/routes/cms/utils/requestTranslation.js +85 -0
- package/server/routes/cms/utils/updateLocalization.js +48 -0
- package/server/routes/cmsSpace/controllers/deleteSpace.js +26 -0
- package/server/routes/cmsSpace/controllers/getSpaces.js +28 -0
- package/server/routes/cmsSpace/controllers/insertSpace.js +22 -0
- package/server/routes/cmsSpace/controllers/updateSpace.js +24 -0
- package/server/routes/cmsSpace/index.mjs +20 -0
- package/server/routes/contentType/controllers/addContentType.js +162 -0
- package/server/routes/contentType/controllers/contentTypeList.js +54 -0
- package/server/routes/contentType/controllers/delContentType.js +75 -0
- package/server/routes/contentType/controllers/editContentType.js +61 -0
- package/server/routes/contentType/controllers/getContentType.js +37 -0
- package/server/routes/contentType/index.mjs +29 -19
- package/server/routes/contentType/utils/updateContents.js +29 -0
- package/server/routes/contentType/utils/updateCustomContentTable.js +56 -0
- package/server/routes/feedback/controllers/email.list.js +25 -0
- package/server/routes/feedback/controllers/feedback.js +49 -0
- package/server/routes/feedback/controllers/feedback.list.js +38 -0
- package/server/routes/feedback/controllers/news.subscriptions.js +44 -0
- package/server/routes/feedback/index.mjs +72 -0
- package/server/routes/logs/controllers/export.user.logs.js +78 -0
- package/server/routes/logs/controllers/user.logs.js +45 -0
- package/server/routes/logs/index.mjs +9 -0
- package/server/routes/menu/controllers/addMenu.js +38 -0
- package/server/routes/menu/controllers/delMenu.js +32 -0
- package/server/routes/menu/controllers/editMenu.js +42 -0
- package/server/routes/menu/controllers/getMenu.js +43 -0
- package/server/routes/menu/index.mjs +13 -0
- package/server/routes/migration/controllers/collectionToCustom.js +137 -0
- package/server/routes/migration/index.mjs +8 -0
- package/server/routes/tags/controllers/add.tags.js +25 -0
- package/server/routes/tags/controllers/del.tags.js +20 -0
- package/server/routes/tags/controllers/edit.tags.js +26 -0
- package/server/routes/tags/controllers/get.tags.js +16 -0
- package/server/routes/tags/index.mjs +14 -0
- package/server/templates/page/login.html +73 -5
- package/server/templates/select/core.user_mentioned.sql +2 -0
- package/src/index.ts +122 -0
- package/dist/assets/ArticlesPage-BveM4q3g.js +0 -11
- package/dist/assets/CollectionsPage-D5td-UBm.js +0 -1
- package/dist/assets/ContentBlock.vue_vue_type_script_setup_true_lang-BwF6D-yB.js +0 -30
- package/dist/assets/CreateCollectionPage-Cu0RW5ui.js +0 -76
- package/dist/assets/Dashboard-faSjwmB8.js +0 -11
- package/dist/assets/EditCollectionPage-K5oPPzCd.js +0 -1
- package/dist/assets/MediaPage-BoW3aWgN.js +0 -1
- package/dist/assets/PermissionsPage-DGy5fha2.js +0 -1
- package/dist/assets/SingletonsPage-C1X2xkQE.js +0 -1
- package/dist/assets/UniversalTable.vue_vue_type_script_setup_true_lang-DUqfWJcy.js +0 -6
- package/dist/assets/contentForm-DMVC4vho.js +0 -1
- package/dist/assets/database-BTxZQzYy.js +0 -6
- package/dist/assets/index-9GY17iSP.css +0 -1
- package/dist/assets/index-DYyZmLWO.js +0 -2138
- package/dist/assets/index-xsH4HHeE.js +0 -6
- package/dist/assets/save-C2B6th9J.js +0 -11
- package/dist/assets/settings-DbyDiH2g.js +0 -6
- package/dist/assets/vue.-sixQ7xP-DwXf3zRn.js +0 -1
- package/dist/assets/x-circle-C3q70RMH.js +0 -16
- package/server/routes/contentType/controllers/cms.type.delete.js +0 -22
- package/server/routes/contentType/controllers/cms.type.get.js +0 -22
- package/server/routes/contentType/controllers/cms.type.list.js +0 -25
- package/server/routes/contentType/controllers/cms.type.post.js +0 -22
- package/server/routes/contentType/controllers/cms.type.put.js +0 -24
- package/server/routes/contentType/utils/builderCache.js +0 -58
- package/server/routes/fileContent/data/deleteContent.js +0 -34
- package/server/routes/fileContent/data/deleteMedia.js +0 -28
- package/server/routes/fileContent/data/downloadMedia.js +0 -41
- package/server/routes/fileContent/data/getContent.js +0 -32
- package/server/routes/fileContent/data/insertContent.js +0 -37
- package/server/routes/fileContent/data/listMedia.js +0 -47
- package/server/routes/fileContent/data/metadataMedia.js +0 -38
- package/server/routes/fileContent/data/updateContent.js +0 -40
- package/server/routes/fileContent/data/uploadMedia.js +0 -49
- package/server/routes/fileContent/index.mjs +0 -54
- package/server/routes/fileContent/type/contentTypeList.js +0 -7
- package/server/routes/fileContent/type/createContentType.js +0 -31
- package/server/routes/fileContent/type/deleteContentType.js +0 -29
- package/server/routes/fileContent/type/getContentType.js +0 -15
- package/server/routes/fileContent/type/updateContentType.js +0 -40
- package/server/routes/fileContent/utils/astroBuilderCache.js +0 -47
- package/server/routes/fileContent/utils/contentDir.js +0 -12
- package/server/routes/fileContent/utils/contentTypeExists.js +0 -15
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { pgClients, config } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
const refererAllow = config?.refererAllow || ['127.0.0.1', 'localhost', 'softpro.ua', 'opengis.info'];
|
|
4
|
+
|
|
5
|
+
function checkReferer(headers) {
|
|
6
|
+
if (config?.local) return true;
|
|
7
|
+
if (!headers?.referer) return false;
|
|
8
|
+
return refererAllow.some(allowed => headers.referer.includes(allowed));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default async function subscribeToNews({
|
|
12
|
+
body = {},
|
|
13
|
+
pg = pgClients.client,
|
|
14
|
+
headers
|
|
15
|
+
}, reply) {
|
|
16
|
+
reply
|
|
17
|
+
.header('Access-Control-Allow-Origin', '*')
|
|
18
|
+
.header('Access-Control-Allow-Methods', 'POST, OPTIONS')
|
|
19
|
+
.header('Access-Control-Allow-Headers', 'Content-Type');
|
|
20
|
+
|
|
21
|
+
if (!checkReferer(headers)) {
|
|
22
|
+
return reply.status(403).send('access restricted');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { email } = body;
|
|
26
|
+
if (!email) return reply.status(400).send('email is required');
|
|
27
|
+
|
|
28
|
+
const { rows: subscriptions } = await pg.query(`
|
|
29
|
+
SELECT subscription_id
|
|
30
|
+
FROM site.subscription
|
|
31
|
+
WHERE email = $1
|
|
32
|
+
LIMIT 1
|
|
33
|
+
`, [email]);
|
|
34
|
+
|
|
35
|
+
if (subscriptions?.length === 0) {
|
|
36
|
+
await pg.query(`
|
|
37
|
+
INSERT INTO site.subscription (email)
|
|
38
|
+
VALUES ($1)
|
|
39
|
+
`, [email]);
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return reply.status(200).send('Успішно');
|
|
44
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import feedback from './controllers/feedback.js';
|
|
2
|
+
import newsSubs from './controllers/news.subscriptions.js';
|
|
3
|
+
import feedbackList from './controllers/feedback.list.js';
|
|
4
|
+
import emailList from './controllers/email.list.js';
|
|
5
|
+
|
|
6
|
+
export default async function route(fastify) {
|
|
7
|
+
fastify.route({
|
|
8
|
+
method: 'POST',
|
|
9
|
+
url: '/cms-feedback/:type',
|
|
10
|
+
handler: feedback,
|
|
11
|
+
config: {
|
|
12
|
+
policy: ['public'],
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
fastify.route({
|
|
17
|
+
method: 'GET',
|
|
18
|
+
url: '/feedback',
|
|
19
|
+
handler: feedbackList,
|
|
20
|
+
config: {
|
|
21
|
+
policy: ['public'],
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
fastify.route({
|
|
26
|
+
method: 'GET',
|
|
27
|
+
url: '/email-list',
|
|
28
|
+
handler: emailList,
|
|
29
|
+
config: {
|
|
30
|
+
policy: ['public'],
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
fastify.route({
|
|
35
|
+
method: 'OPTIONS',
|
|
36
|
+
url: '/cms-feedback/:type',
|
|
37
|
+
handler: (req, reply) => {
|
|
38
|
+
reply
|
|
39
|
+
.header('Access-Control-Allow-Origin', '*')
|
|
40
|
+
.header('Access-Control-Allow-Methods', 'POST, OPTIONS')
|
|
41
|
+
.header('Access-Control-Allow-Headers', 'Content-Type')
|
|
42
|
+
.send();
|
|
43
|
+
},
|
|
44
|
+
config: {
|
|
45
|
+
policy: ['public'],
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
fastify.route({
|
|
50
|
+
method: 'POST',
|
|
51
|
+
url: '/cms-subscribtion',
|
|
52
|
+
handler: newsSubs,
|
|
53
|
+
config: {
|
|
54
|
+
policy: ['public'],
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
fastify.route({
|
|
59
|
+
method: 'OPTIONS',
|
|
60
|
+
url: '/cms-subscribtion',
|
|
61
|
+
handler: (req, reply) => {
|
|
62
|
+
reply
|
|
63
|
+
.header('Access-Control-Allow-Origin', '*')
|
|
64
|
+
.header('Access-Control-Allow-Methods', 'POST, OPTIONS')
|
|
65
|
+
.header('Access-Control-Allow-Headers', 'Content-Type')
|
|
66
|
+
.send();
|
|
67
|
+
},
|
|
68
|
+
config: {
|
|
69
|
+
policy: ['public'],
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { createReadStream } from 'node:fs';
|
|
4
|
+
import { config, getFolder, jsonToCsv, getFilterSQL } from '@opengis/fastify-table/utils.js';
|
|
5
|
+
|
|
6
|
+
const rootDir = getFolder(config, 'local');
|
|
7
|
+
|
|
8
|
+
export default async function exportUserLogs(req, reply) {
|
|
9
|
+
const { pg, query, user = {} } = req;
|
|
10
|
+
const { uid } = user;
|
|
11
|
+
if (!uid) return reply.code(401).send({ message: 'Not authorized', status: 401 });
|
|
12
|
+
|
|
13
|
+
const { filter = null } = query;
|
|
14
|
+
|
|
15
|
+
const { q: filterQuery } = await getFilterSQL({
|
|
16
|
+
filter, table: 'log.table_changes'
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const { rows = [] } = await pg.query(`
|
|
20
|
+
select
|
|
21
|
+
row_number() over(order by cdate desc) as num,
|
|
22
|
+
change_id,
|
|
23
|
+
change_type,
|
|
24
|
+
cdate,
|
|
25
|
+
entity_type,
|
|
26
|
+
entity_id,
|
|
27
|
+
b.data_log
|
|
28
|
+
from log.table_changes a
|
|
29
|
+
left join lateral (
|
|
30
|
+
select json_agg(
|
|
31
|
+
row_to_json(q)
|
|
32
|
+
) as data_log
|
|
33
|
+
from log.table_changes_data q
|
|
34
|
+
where change_id=a.change_id
|
|
35
|
+
|
|
36
|
+
)b on 1=1
|
|
37
|
+
|
|
38
|
+
where change_user_id='${uid}'
|
|
39
|
+
${filterQuery ? `and ${filterQuery}` : ''}
|
|
40
|
+
order by cdate desc
|
|
41
|
+
`);
|
|
42
|
+
|
|
43
|
+
const filePath = path.join(rootDir, `/files/tmp/logs_${uid}.csv`);
|
|
44
|
+
const ext = path.extname(filePath);
|
|
45
|
+
const filePathJSON = filePath.replace(ext, '.json');
|
|
46
|
+
|
|
47
|
+
await mkdir(path.dirname(filePathJSON), { recursive: true });
|
|
48
|
+
await writeFile(filePathJSON, JSON.stringify(rows));
|
|
49
|
+
|
|
50
|
+
await jsonToCsv({
|
|
51
|
+
filePath: filePathJSON,
|
|
52
|
+
colmodel: [
|
|
53
|
+
{ name: 'num', title: '№', ua: '№' },
|
|
54
|
+
{ name: 'change_id', title: 'Ідентифікатор зміни', ua: 'Ідентифікатор зміни' },
|
|
55
|
+
{ name: 'change_type', title: 'Тип зміни', ua: 'Тип зміни' },
|
|
56
|
+
{ name: 'cdate', title: 'Дата та час зміни', ua: 'Дата та час зміни' },
|
|
57
|
+
{ name: 'entity_type', title: 'Тип об\'єкта', ua: 'Тип об\'єкта' },
|
|
58
|
+
{ name: 'entity_id', title: 'Ідентифікатор об\'єкта', ua: 'Ідентифікатор об\'єкта' },
|
|
59
|
+
{ name: 'data_log', title: 'Дані зміни', ua: 'Дані зміни' }
|
|
60
|
+
],
|
|
61
|
+
columnList: [
|
|
62
|
+
'num',
|
|
63
|
+
'change_id',
|
|
64
|
+
'change_type',
|
|
65
|
+
'cdate',
|
|
66
|
+
'entity_type',
|
|
67
|
+
'entity_id',
|
|
68
|
+
'data_log'
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
reply.header('Content-Disposition', `attachment; filename=${encodeURIComponent(path.basename(filePath))}`);
|
|
73
|
+
reply.header('Content-Type', 'text/csv');
|
|
74
|
+
|
|
75
|
+
const fileStream = createReadStream(filePath);
|
|
76
|
+
return reply.send(fileStream);
|
|
77
|
+
|
|
78
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { getFilterSQL } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
export default async function userLogs(req, reply) {
|
|
4
|
+
const { pg, user = {}, query } = req;
|
|
5
|
+
const { uid } = user;
|
|
6
|
+
if (!uid) return reply.code(401).send({ message: 'Not authorized', status: 401 });
|
|
7
|
+
|
|
8
|
+
const { page = 1, limit = 16, filter = null } = query;
|
|
9
|
+
const offset = limit * (page - 1) || 0;
|
|
10
|
+
|
|
11
|
+
const { q: filterQuery } = await getFilterSQL({
|
|
12
|
+
filter, table: 'log.table_changes', filterList: [{ name: 'cdate', type: 'date' }, { name: 'change_type', type: 'check' }]
|
|
13
|
+
});
|
|
14
|
+
//return filterData
|
|
15
|
+
|
|
16
|
+
const { total, filtered } = await pg.one(`select count(*)::int as total, (count(*) filter(where ${filterQuery ? `${filterQuery}` : '1=1'}))::int as filtered from log.table_changes where change_user_id=$1`, [uid])
|
|
17
|
+
|
|
18
|
+
const { rows = [] } = await pg.query(`
|
|
19
|
+
select
|
|
20
|
+
row_number() over(order by cdate desc) as num,
|
|
21
|
+
change_id,
|
|
22
|
+
change_type,
|
|
23
|
+
cdate,
|
|
24
|
+
entity_type,
|
|
25
|
+
entity_id,
|
|
26
|
+
b.data_log
|
|
27
|
+
from log.table_changes a
|
|
28
|
+
left join lateral (
|
|
29
|
+
select json_agg(
|
|
30
|
+
row_to_json(q)
|
|
31
|
+
) as data_log
|
|
32
|
+
from log.table_changes_data q
|
|
33
|
+
where change_id=a.change_id
|
|
34
|
+
|
|
35
|
+
)b on 1=1
|
|
36
|
+
|
|
37
|
+
where change_user_id='${uid}'
|
|
38
|
+
${filterQuery ? `and ${filterQuery}` : ''}
|
|
39
|
+
order by cdate desc
|
|
40
|
+
limit ${limit} offset ${offset}
|
|
41
|
+
`);
|
|
42
|
+
|
|
43
|
+
const status = rows?.length ? 200 : 204;
|
|
44
|
+
return reply.code(status).send({ message: { total, filtered, rows }, status })
|
|
45
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import userLogs from './controllers/user.logs.js';
|
|
2
|
+
import exportUserLogs from './controllers/export.user.logs.js';
|
|
3
|
+
|
|
4
|
+
const params = { config: { policy: ['public'] } };
|
|
5
|
+
|
|
6
|
+
export default async function route(app) {
|
|
7
|
+
app.get('/user-logs', params, userLogs);
|
|
8
|
+
app.get('/export-user-logs', params, exportUserLogs);
|
|
9
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { dataInsert, yml2json, json2yml } from "@opengis/fastify-table/utils.js";
|
|
2
|
+
|
|
3
|
+
export default async function addMenu(req, reply) {
|
|
4
|
+
const { pg = pgClients.client, params = {}, user = {}, headers = {}, body = {} } = req;
|
|
5
|
+
|
|
6
|
+
if (!pg?.pk) {
|
|
7
|
+
return reply.status(400).send('empty pg');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (!pg?.pk?.['site.menus']) {
|
|
11
|
+
return reply.status(404).send('table not found');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!body.name) {
|
|
15
|
+
return reply.status(404).send('not enough body params: name');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (typeof body.content === 'string') {
|
|
19
|
+
body.items = yml2json(body.content);
|
|
20
|
+
} else if (body.items && typeof body.items === 'object') {
|
|
21
|
+
body.content = json2yml(body.items);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const res = await dataInsert({
|
|
25
|
+
pg,
|
|
26
|
+
table: 'site.menus',
|
|
27
|
+
id: params.id,
|
|
28
|
+
data: body,
|
|
29
|
+
referer: headers.referer,
|
|
30
|
+
uid: user.uid,
|
|
31
|
+
}).then(el => el.rows[0]);
|
|
32
|
+
|
|
33
|
+
if (!res) {
|
|
34
|
+
return reply.status(500).send('insert menu error');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return reply.status(200).send(res);
|
|
38
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { dataDelete } from "@opengis/fastify-table/utils.js";
|
|
2
|
+
|
|
3
|
+
export default async function delMenu(req, reply) {
|
|
4
|
+
const { pg = pgClients.client, params = {}, user = {}, headers = {}, body = {} } = req;
|
|
5
|
+
|
|
6
|
+
if (!pg?.pk) {
|
|
7
|
+
return reply.status(400).send('empty pg');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (!pg?.pk?.['site.menus']) {
|
|
11
|
+
return reply.status(404).send('table not found');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!params.id) {
|
|
15
|
+
return reply.status(400).send('not enough params: id');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const res = await dataDelete({
|
|
19
|
+
pg,
|
|
20
|
+
table: 'site.menus',
|
|
21
|
+
id: params.id,
|
|
22
|
+
data: body,
|
|
23
|
+
referer: headers.referer,
|
|
24
|
+
uid: user.uid,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (!res) {
|
|
28
|
+
return reply.status(500).send('delete menu error');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return reply.status(200).send(res);
|
|
32
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { dataUpdate, yml2json, json2yml } from "@opengis/fastify-table/utils.js";
|
|
2
|
+
|
|
3
|
+
export default async function editMenu(req, reply) {
|
|
4
|
+
const { pg = pgClients.client, params = {}, user = {}, headers = {}, body = {} } = req;
|
|
5
|
+
|
|
6
|
+
if (!pg?.pk) {
|
|
7
|
+
return reply.status(400).send('empty pg');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (!pg?.pk?.['site.menus']) {
|
|
11
|
+
return reply.status(404).send('table not found');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!params.id) {
|
|
15
|
+
return reply.status(400).send('not enough params: id');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (typeof body.content === 'string') {
|
|
19
|
+
body.items = yml2json(body.content || '');
|
|
20
|
+
} else if (body.items && typeof body.items === 'object') {
|
|
21
|
+
body.content = json2yml(body.items || {});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!Object.keys(body.items || {}).length) {
|
|
25
|
+
return reply.status(400).send('access restricted: empty json items / invalid yml content');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const res = await dataUpdate({
|
|
29
|
+
pg,
|
|
30
|
+
table: 'site.menus',
|
|
31
|
+
id: params.id,
|
|
32
|
+
data: body,
|
|
33
|
+
referer: headers.referer,
|
|
34
|
+
uid: user.uid,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (!res) {
|
|
38
|
+
return reply.status(500).send('update menu error');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return reply.status(200).send(res);
|
|
42
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { pgClients } from "@opengis/fastify-table/utils.js";
|
|
2
|
+
|
|
3
|
+
const limit = 16;
|
|
4
|
+
|
|
5
|
+
const err = { error: 'menu not found', code: 404 };
|
|
6
|
+
|
|
7
|
+
export default async function getMenu(req, reply) {
|
|
8
|
+
const { pg = pgClients.client, query = {}, params = {} } = req;
|
|
9
|
+
|
|
10
|
+
if (!pg?.pk) {
|
|
11
|
+
return reply.status(400).send('empty pg');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!pg?.pk?.['site.menus']) {
|
|
15
|
+
return reply.status(404).send('table not found');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const offset = query.page && query.page > 0 ? (query.page - 1) * limit : 0;
|
|
19
|
+
|
|
20
|
+
const q = `select menu_id, name, locale, description ${params.id ? ',items::json, content' : ''} from site.menus
|
|
21
|
+
where ${params.id ? '$1 in (menu_id, name)' : '1=1'}
|
|
22
|
+
and ${query.locale ? `locale='${query.locale.replace(/'/g, "''")}'` : 'true'}
|
|
23
|
+
order by (case when locale='uk' then false else true end)
|
|
24
|
+
limit ${limit} offset ${offset}`;
|
|
25
|
+
|
|
26
|
+
if (query.sql) return q;
|
|
27
|
+
|
|
28
|
+
const rows = await pg.query(q, [params.id].filter(Boolean))
|
|
29
|
+
.then(el => el.rows || []);
|
|
30
|
+
|
|
31
|
+
if (params.id) {
|
|
32
|
+
if (!rows.length && params.id) {
|
|
33
|
+
return reply.status(404).send(err);
|
|
34
|
+
}
|
|
35
|
+
return reply.status(200).send(rows[0]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!rows.length && params.id) {
|
|
39
|
+
return reply.status(404).send(err);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return reply.status(200).send({ rows });
|
|
43
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import getMenu from './controllers/getMenu.js';
|
|
2
|
+
import addMenu from './controllers/addMenu.js';
|
|
3
|
+
import editMenu from './controllers/editMenu.js';
|
|
4
|
+
import delMenu from './controllers/delMenu.js';
|
|
5
|
+
|
|
6
|
+
const params = { config: { policy: ['public'] } };
|
|
7
|
+
|
|
8
|
+
export default async function route(app) {
|
|
9
|
+
app.post('/cms-menu/:id?', params, addMenu);
|
|
10
|
+
app.put('/cms-menu/:id', params, editMenu);
|
|
11
|
+
app.delete('/cms-menu/:id', params, delMenu);
|
|
12
|
+
app.get('/cms-menu/:id?', params, getMenu);
|
|
13
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export default async function collectionToCustom(req, reply) {
|
|
2
|
+
const { params, pg, query } = req;
|
|
3
|
+
const { id } = params;
|
|
4
|
+
|
|
5
|
+
if (!id) return reply.code(400).send({ status: 400, message: 'Невказаний id або slug колекції!' });
|
|
6
|
+
|
|
7
|
+
const selectTableStructure = `
|
|
8
|
+
select
|
|
9
|
+
name,
|
|
10
|
+
title,
|
|
11
|
+
columns,
|
|
12
|
+
type
|
|
13
|
+
from site.content_types
|
|
14
|
+
where '${id}' in (content_type_id,name)
|
|
15
|
+
limit 1
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const { name, title, columns, type } = await pg.one(selectTableStructure);
|
|
19
|
+
|
|
20
|
+
if (!name || !title) return reply.code(404).send({ status: 404, message: 'За вказаним параметром колекції не існує!' });
|
|
21
|
+
if (!columns || columns?.length === 0) return reply.code(400).send({ status: 400, message: 'У вказаної колекції відсутня структура колонок!' });
|
|
22
|
+
if (type !== 'collection') return reply.code(400).send({ status: 400, message: 'У вказаного об\'єкта тип не відповідає колекції' });
|
|
23
|
+
|
|
24
|
+
let createTableSql = `
|
|
25
|
+
CREATE TABLE IF NOT EXISTS data."${name}"(
|
|
26
|
+
id text not null DEFAULT next_id(),
|
|
27
|
+
slug text NOT NULL,
|
|
28
|
+
title text NOT NULL,
|
|
29
|
+
status text NOT NULL DEFAULT 'draft'::text,
|
|
30
|
+
published_at timestamp without time zone NOT NULL,
|
|
31
|
+
created_at timestamp without time zone NOT NULL DEFAULT now(),
|
|
32
|
+
updated_at timestamp without time zone NOT NULL DEFAULT now(),
|
|
33
|
+
CONSTRAINT "${name}_pkey" PRIMARY KEY (id)
|
|
34
|
+
);
|
|
35
|
+
COMMENT ON TABLE data."${name}" IS '${title}';
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
let recordColumns = [];
|
|
39
|
+
let columnsArrFormat = [];
|
|
40
|
+
let columnsArr = [];
|
|
41
|
+
|
|
42
|
+
columns?.forEach(column => {
|
|
43
|
+
if (column?.name && column?.type) {
|
|
44
|
+
const columnType = (() => {
|
|
45
|
+
switch (column?.type) {
|
|
46
|
+
case 'text': return 'text';
|
|
47
|
+
case 'number': return 'numeric';
|
|
48
|
+
case 'integer': return 'integer';
|
|
49
|
+
case 'date': return 'date';
|
|
50
|
+
case 'datetime': return 'timestamp without time zone';
|
|
51
|
+
case 'select': return 'text';
|
|
52
|
+
case 'multiselect': return 'text[]';
|
|
53
|
+
case 'radio': return 'text[]';
|
|
54
|
+
case 'boolean': return 'boolean';
|
|
55
|
+
case 'array':
|
|
56
|
+
switch (column?.subtype) {
|
|
57
|
+
case 'text': return 'text[]';
|
|
58
|
+
case 'number': return 'numeric[]';
|
|
59
|
+
case 'integer': return 'integer[]';
|
|
60
|
+
case 'date': return 'date[]';
|
|
61
|
+
case 'datetime': return 'timestamp without time zone[]';
|
|
62
|
+
case 'boolean': return 'boolean[]';
|
|
63
|
+
default: return 'text[]';
|
|
64
|
+
}
|
|
65
|
+
case 'datalist': return 'json';
|
|
66
|
+
case 'image': return 'text';
|
|
67
|
+
case 'slug': return 'text';
|
|
68
|
+
case 'richtext': return 'text';
|
|
69
|
+
case 'FileList': return 'text[]';
|
|
70
|
+
default: return 'text';
|
|
71
|
+
}
|
|
72
|
+
})();
|
|
73
|
+
|
|
74
|
+
const columnSql = `\nALTER TABLE data."${name}" ADD COLUMN IF NOT EXISTS "${column.name}" ${columnType};\nCOMMENT ON COLUMN data."${name}"."${column.name}" IS '${column?.label}';
|
|
75
|
+
`;
|
|
76
|
+
createTableSql += columnSql;
|
|
77
|
+
|
|
78
|
+
const recordColumn = `"${column.name}" ${columnType}`;
|
|
79
|
+
recordColumns.push(recordColumn);
|
|
80
|
+
columnsArr.push(`"${column.name}"`);
|
|
81
|
+
if (columnType === 'json') {
|
|
82
|
+
columnsArrFormat.push(`replace(replace(replace("${column.name}"::text,'\"','"'),'"[','['), ']"',']')::json as "${column.name}"`);
|
|
83
|
+
} else {
|
|
84
|
+
columnsArrFormat.push(`"${column.name}"`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
if (query?.sql == 1) return createTableSql;
|
|
91
|
+
await pg.query(createTableSql);
|
|
92
|
+
|
|
93
|
+
const insertSql = `
|
|
94
|
+
insert into data."${name}" (
|
|
95
|
+
id,${columnsArr.join(',')}
|
|
96
|
+
)
|
|
97
|
+
SELECT
|
|
98
|
+
id,${columnsArrFormat.join(',')}
|
|
99
|
+
FROM
|
|
100
|
+
|
|
101
|
+
(
|
|
102
|
+
select
|
|
103
|
+
jsonb_build_object('id',object_id)||jsonb_object_agg(field_key,field_value) as row
|
|
104
|
+
from site.content_data
|
|
105
|
+
where content_id = (
|
|
106
|
+
select content_id
|
|
107
|
+
from site.contents
|
|
108
|
+
where content_type_id = (
|
|
109
|
+
select content_type_id
|
|
110
|
+
from site.content_types
|
|
111
|
+
where '${id}' in (name,content_type_id)
|
|
112
|
+
limit 1
|
|
113
|
+
)
|
|
114
|
+
limit 1
|
|
115
|
+
)
|
|
116
|
+
group by object_id
|
|
117
|
+
)q,
|
|
118
|
+
jsonb_to_record(row) AS x(
|
|
119
|
+
id text,${recordColumns.join(',')}
|
|
120
|
+
)
|
|
121
|
+
on conflict (id) do nothing;`;
|
|
122
|
+
//return insertSql;
|
|
123
|
+
if (query?.sql == 2) return insertSql;
|
|
124
|
+
await pg.query(insertSql);
|
|
125
|
+
|
|
126
|
+
await pg.one(`
|
|
127
|
+
update site.content_types
|
|
128
|
+
set table_name='${name}'
|
|
129
|
+
where name='${name}'
|
|
130
|
+
`);
|
|
131
|
+
|
|
132
|
+
return reply.code(200).send({
|
|
133
|
+
status: 200,
|
|
134
|
+
message: `Табличку data.\"${name}\" успішно створено, дані колекції перенесено.`
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { dataInsert } from "@opengis/fastify-table/utils.js";
|
|
2
|
+
|
|
3
|
+
export default async function addTags(req, reply) {
|
|
4
|
+
const { pg, body, user } = req;
|
|
5
|
+
|
|
6
|
+
const { uid } = user || {};
|
|
7
|
+
|
|
8
|
+
if (!uid) {
|
|
9
|
+
return reply.code(401).send({ message: 'access restricted', status: 401 })
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (!body) {
|
|
13
|
+
return reply.code(400).send({ message: 'body is empty!', status: 400 });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
await dataInsert({
|
|
17
|
+
pg,
|
|
18
|
+
table: 'site.tags',
|
|
19
|
+
data: body,
|
|
20
|
+
uid,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return reply.code(201).send({ message: 'Tag added successfully', status: 201 });
|
|
24
|
+
|
|
25
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { pgClients } from "@opengis/fastify-table/utils.js";
|
|
2
|
+
|
|
3
|
+
export default async function delTags(req, reply) {
|
|
4
|
+
const { pg = pgClients.client, user = {}, params = {} } = req;
|
|
5
|
+
const { id } = params;
|
|
6
|
+
|
|
7
|
+
const { uid } = user;
|
|
8
|
+
|
|
9
|
+
if (!uid) {
|
|
10
|
+
return reply.code(401).send('unauthorized');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!id) {
|
|
14
|
+
return reply.code(400).send('id is required!');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
await pg.query('delete from site.tags where tag_id=$1', [id]);
|
|
18
|
+
|
|
19
|
+
return reply.code(200).send({ message: 'Tag deleted', status: 200 });
|
|
20
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { dataUpdate } from "@opengis/fastify-table/utils.js";
|
|
2
|
+
|
|
3
|
+
export default async function editTags({ pg, body, user, params }, reply) {
|
|
4
|
+
const { uid } = user || {};
|
|
5
|
+
|
|
6
|
+
if (!uid) {
|
|
7
|
+
return reply.code(401).send({ message: 'access restricted', status: 401 });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (!params?.id) {
|
|
11
|
+
return reply.code(400).send({ message: 'id is required!', status: 400 });
|
|
12
|
+
}
|
|
13
|
+
if (!body) {
|
|
14
|
+
return reply.code(400).send({ message: 'body is empty!', status: 400 });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
await dataUpdate({
|
|
18
|
+
pg,
|
|
19
|
+
id: params.id,
|
|
20
|
+
table: 'site.tags',
|
|
21
|
+
data: body,
|
|
22
|
+
uid,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return reply.code(200).send({ message: 'Tag updated successfully', status: 200 });
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default async function getTags(req, reply) {
|
|
2
|
+
const { pg, query } = req;
|
|
3
|
+
const { page = 1, limit = 16, search } = query;
|
|
4
|
+
const offset = limit * (page - 1) || 0;
|
|
5
|
+
|
|
6
|
+
const { total, filtered } = await pg.one(`select count(*)::int as total, (count(*) filter (where 1=1 ${search ? `and value ilike'%${search}%'` : ''}))::int as filtered from site.tags`);
|
|
7
|
+
|
|
8
|
+
const selectSql = `select *,(select count(*)::int as count_data from site.tag_data where tag_id=a.tag_id) from site.tags a
|
|
9
|
+
where 1=1 ${search ? `and value ilike'%${search}%'` : ''}
|
|
10
|
+
order by created_at desc limit ${limit} offset ${offset}`;
|
|
11
|
+
|
|
12
|
+
const { rows } = await pg.query(selectSql);
|
|
13
|
+
|
|
14
|
+
return reply.code(200).send({ message: { total, filtered, rows }, status: 200 })
|
|
15
|
+
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import getTags from './controllers/get.tags.js';
|
|
2
|
+
import addTags from './controllers/add.tags.js';
|
|
3
|
+
import editTags from './controllers/edit.tags.js';
|
|
4
|
+
import delTags from './controllers/del.tags.js';
|
|
5
|
+
|
|
6
|
+
const policyPublic = { config: { policy: ['public'] } };
|
|
7
|
+
|
|
8
|
+
export default async function route(app) {
|
|
9
|
+
app.get('/tags', policyPublic, getTags);
|
|
10
|
+
app.post('/tags', policyPublic, addTags);
|
|
11
|
+
app.put('/tags/:id', policyPublic, editTags);
|
|
12
|
+
app.delete('/tags/:id', policyPublic, delTags);
|
|
13
|
+
|
|
14
|
+
}
|