@opengis/cms 0.0.58 → 0.0.60
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 +131 -131
- package/dist/{ArticlesPage-CFjE_cw_.js → ArticlesPage-BjYzvTWM.js} +3 -3
- package/dist/{CollectionsBreadcrumb-BCxeRikP.js → CollectionsBreadcrumb-HePNJb-d.js} +1 -1
- package/dist/CollectionsBreadcrumb.vue_vue_type_script_setup_true_lang-BJh-tjam.js +53 -0
- package/dist/{Dashboard-C1eGscNd.js → Dashboard-CXkg_pk8.js} +132 -132
- package/dist/EditCollectionPage-BycuD920.js +188 -0
- package/dist/{MenuAddPage-D-p3gFgm.js → MenuAddPage-QTnwCoGh.js} +1 -1
- package/dist/{MenuBody-rN5j4YBu.js → MenuBody-Bi0ONVZf.js} +2 -2
- package/dist/{MenuItemPage-BoJw885D.js → MenuItemPage-B7Y9KFyb.js} +3 -3
- package/dist/{MenuList-DFEBS0NB.js → MenuList-BLIpeqSd.js} +53 -53
- package/dist/{MenuPage-BCZB_S8j.js → MenuPage-3W6jZ15H.js} +1 -1
- package/dist/{MenuWrapper-AZ_8s-zd.js → MenuWrapper-OrOv6sOb.js} +1 -1
- package/dist/{MonacoEditor-Db-3Jc3E.js → MonacoEditor-ByPT8pnv.js} +1 -1
- package/dist/MonacoEditor.vue_vue_type_script_setup_true_lang-C8cip9Ci.js +84 -0
- package/dist/{UniversalTable-CzqPG-tY.js → UniversalTable-GBd_pStq.js} +80 -80
- package/dist/{UniversalTablePagination-4gL47A7I.js → UniversalTablePagination-Dw2hc0nc.js} +46 -46
- package/dist/{contentForm-CtMhQTG0.js → contentForm-yMn63kza.js} +48 -48
- package/dist/index.js +5 -5
- package/dist/{vs-builder-monaco-B3Jj0V31.js → vs-builder-monaco-Cw-f19gc.js} +1 -1
- package/input-types.json +9 -9
- package/locales/en.json +815 -815
- package/locales/uk.json +813 -813
- package/module/cms/cls/content.status.json +17 -17
- package/module/cms/cls/user_type.json +9 -9
- package/module/cms/form/admin.users.form.json +77 -77
- package/module/cms/select/cms.page_type.sql +1 -1
- package/module/cms/select/news_tag_id.sql +11 -11
- package/module/cms/table/admin.users.table.json +53 -53
- package/module/cms/table/collection.default.table.json +96 -96
- package/module/cms/table/single.default.table.json +116 -116
- package/package.json +2 -1
- package/plugin.js +43 -43
- package/server/app.js +35 -35
- package/server/config.js +4 -4
- package/server/functions/getContent.js +45 -45
- package/server/functions/getDraftKey.js +22 -22
- package/server/functions/getSearchData.js +31 -31
- package/server/functions/getTags.js +30 -30
- package/server/functions/getUser.js +27 -27
- package/server/functions/utils/mock.reply.js +55 -55
- package/server/index.js +22 -22
- package/server/migrations/fixes.sql +129 -129
- package/server/migrations/site.sql +595 -595
- package/server/plugins/adminHook.js +78 -78
- package/server/plugins/hook.js +59 -59
- package/server/plugins/vite.js +75 -75
- 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 +113 -113
- package/server/routes/cms/controllers/deleteMedia.js +76 -76
- package/server/routes/cms/controllers/downloadMedia.js +84 -84
- package/server/routes/cms/controllers/getContent.js +113 -113
- package/server/routes/cms/controllers/getContentBySlug.js +93 -93
- package/server/routes/cms/controllers/getPermissions.js +15 -15
- package/server/routes/cms/controllers/insertContent.js +217 -217
- package/server/routes/cms/controllers/listMedia.js +155 -155
- package/server/routes/cms/controllers/metadataMedia.js +39 -39
- package/server/routes/cms/controllers/properties.get.js +18 -18
- package/server/routes/cms/controllers/properties.post.js +99 -99
- package/server/routes/cms/controllers/searchContent.js +214 -214
- 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 +266 -266
- package/server/routes/cms/controllers/uploadMedia.js +79 -79
- package/server/routes/cms/functions/getSettings.js +48 -48
- package/server/routes/cms/index.mjs +112 -112
- package/server/routes/cms/utils/additionalData.js +35 -35
- package/server/routes/cms/utils/getCollection.js +89 -89
- package/server/routes/cms/utils/getSingle.js +188 -188
- package/server/routes/cms/utils/inputTypes.js +5 -5
- package/server/routes/cms/utils/insertContentLocalization.js +104 -104
- package/server/routes/cms/utils/requestTranslation.js +113 -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 +160 -160
- 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 +88 -88
- package/server/routes/contentType/controllers/getContentType.js +65 -65
- package/server/routes/contentType/index.mjs +35 -35
- package/server/routes/contentType/utils/updateContents.js +44 -44
- package/server/routes/contentType/utils/updateCustomContentTable.js +53 -53
- 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 +24 -24
- package/server/routes/menu/functions/getMenu.js +50 -50
- 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/utils.d.ts +52 -52
- package/utils.js +8 -8
- package/dist/CollectionsBreadcrumb.vue_vue_type_script_setup_true_lang-umRzB5mY.js +0 -53
- package/dist/EditCollectionPage-DIr1tdtn.js +0 -187
- package/dist/MonacoEditor.vue_vue_type_script_setup_true_lang-B1DrxmQX.js +0 -84
- package/dist/images/logo.png +0 -0
- package/dist/index.html +0 -29
- package/dist/vite.svg +0 -1
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
{
|
|
2
|
-
"table": "site.contents",
|
|
3
|
-
"columns": [
|
|
4
|
-
{
|
|
5
|
-
"label": "Заголовок",
|
|
6
|
-
"name": "title",
|
|
7
|
-
"required": true,
|
|
8
|
-
"type": "text",
|
|
9
|
-
"localization": true
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"label": "Посилання",
|
|
13
|
-
"name": "slug",
|
|
14
|
-
"parent": "title",
|
|
15
|
-
"required": true,
|
|
16
|
-
"type": "slug"
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
"label": "Статус",
|
|
20
|
-
"name": "status",
|
|
21
|
-
"type": "select",
|
|
22
|
-
"data": "content.status",
|
|
23
|
-
"options": [
|
|
24
|
-
{
|
|
25
|
-
"id": "draft",
|
|
26
|
-
"text": "Чернетка"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"id": "published",
|
|
30
|
-
"text": "Опубліковано"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"id": "archived",
|
|
34
|
-
"text": "Архівовано"
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
"id": "delayPublished",
|
|
38
|
-
"text": "Відкладена публікація"
|
|
39
|
-
}
|
|
40
|
-
],
|
|
41
|
-
"required": true
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
"label": "Дата публікації",
|
|
45
|
-
"name": "published_at",
|
|
46
|
-
"required": true,
|
|
47
|
-
"type": "datetime"
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"label": "Контент",
|
|
51
|
-
"name": "single_body",
|
|
52
|
-
"type": "richtext",
|
|
53
|
-
"localization": true
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"name": "single_sections",
|
|
57
|
-
"label": "Секції",
|
|
58
|
-
"type": "reference",
|
|
59
|
-
"localization": true
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
"label": "Зображення",
|
|
63
|
-
"name": "main_image",
|
|
64
|
-
"type": "mediaselect"
|
|
65
|
-
}
|
|
66
|
-
],
|
|
67
|
-
"filters": [
|
|
68
|
-
{
|
|
69
|
-
"extra": false,
|
|
70
|
-
"id": "status",
|
|
71
|
-
"name": "status",
|
|
72
|
-
"title": "Статус",
|
|
73
|
-
"type": "Check",
|
|
74
|
-
"data": "content.status",
|
|
75
|
-
"ua": "Статус"
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
"extra": false,
|
|
79
|
-
"label": "Publish at",
|
|
80
|
-
"name": "published_at",
|
|
81
|
-
"type": "Date"
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
"extra": false,
|
|
85
|
-
"placeholder": "Пошук по тексту",
|
|
86
|
-
"label": "Пошук по тексту",
|
|
87
|
-
"columns": "slug,title",
|
|
88
|
-
"name": "search",
|
|
89
|
-
"type": "Text"
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
"extra": false,
|
|
93
|
-
"name": "tag_list",
|
|
94
|
-
"type": "Tags",
|
|
95
|
-
"sql": "id in (select b.data_id from site.tags a left join site.tag_data b on a.tag_id=b.tag_id where a.tag_id=any($1))",
|
|
96
|
-
"label": "Теги"
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
"extra": false,
|
|
100
|
-
"columns": "slug,title",
|
|
101
|
-
"id": "search",
|
|
102
|
-
"name": "search",
|
|
103
|
-
"placeholder": "Пошук по тексту",
|
|
104
|
-
"title": "Пошук по тексту",
|
|
105
|
-
"type": "Text"
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
"extra": false,
|
|
109
|
-
"id": "created_at",
|
|
110
|
-
"name": "created_at",
|
|
111
|
-
"title": "Дата створення",
|
|
112
|
-
"type": "Date",
|
|
113
|
-
"ua": "Дата створення"
|
|
114
|
-
}
|
|
115
|
-
]
|
|
116
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"table": "site.contents",
|
|
3
|
+
"columns": [
|
|
4
|
+
{
|
|
5
|
+
"label": "Заголовок",
|
|
6
|
+
"name": "title",
|
|
7
|
+
"required": true,
|
|
8
|
+
"type": "text",
|
|
9
|
+
"localization": true
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"label": "Посилання",
|
|
13
|
+
"name": "slug",
|
|
14
|
+
"parent": "title",
|
|
15
|
+
"required": true,
|
|
16
|
+
"type": "slug"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"label": "Статус",
|
|
20
|
+
"name": "status",
|
|
21
|
+
"type": "select",
|
|
22
|
+
"data": "content.status",
|
|
23
|
+
"options": [
|
|
24
|
+
{
|
|
25
|
+
"id": "draft",
|
|
26
|
+
"text": "Чернетка"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "published",
|
|
30
|
+
"text": "Опубліковано"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "archived",
|
|
34
|
+
"text": "Архівовано"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "delayPublished",
|
|
38
|
+
"text": "Відкладена публікація"
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"required": true
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"label": "Дата публікації",
|
|
45
|
+
"name": "published_at",
|
|
46
|
+
"required": true,
|
|
47
|
+
"type": "datetime"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"label": "Контент",
|
|
51
|
+
"name": "single_body",
|
|
52
|
+
"type": "richtext",
|
|
53
|
+
"localization": true
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "single_sections",
|
|
57
|
+
"label": "Секції",
|
|
58
|
+
"type": "reference",
|
|
59
|
+
"localization": true
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"label": "Зображення",
|
|
63
|
+
"name": "main_image",
|
|
64
|
+
"type": "mediaselect"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"filters": [
|
|
68
|
+
{
|
|
69
|
+
"extra": false,
|
|
70
|
+
"id": "status",
|
|
71
|
+
"name": "status",
|
|
72
|
+
"title": "Статус",
|
|
73
|
+
"type": "Check",
|
|
74
|
+
"data": "content.status",
|
|
75
|
+
"ua": "Статус"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"extra": false,
|
|
79
|
+
"label": "Publish at",
|
|
80
|
+
"name": "published_at",
|
|
81
|
+
"type": "Date"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"extra": false,
|
|
85
|
+
"placeholder": "Пошук по тексту",
|
|
86
|
+
"label": "Пошук по тексту",
|
|
87
|
+
"columns": "slug,title",
|
|
88
|
+
"name": "search",
|
|
89
|
+
"type": "Text"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"extra": false,
|
|
93
|
+
"name": "tag_list",
|
|
94
|
+
"type": "Tags",
|
|
95
|
+
"sql": "id in (select b.data_id from site.tags a left join site.tag_data b on a.tag_id=b.tag_id where a.tag_id=any($1))",
|
|
96
|
+
"label": "Теги"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"extra": false,
|
|
100
|
+
"columns": "slug,title",
|
|
101
|
+
"id": "search",
|
|
102
|
+
"name": "search",
|
|
103
|
+
"placeholder": "Пошук по тексту",
|
|
104
|
+
"title": "Пошук по тексту",
|
|
105
|
+
"type": "Text"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"extra": false,
|
|
109
|
+
"id": "created_at",
|
|
110
|
+
"name": "created_at",
|
|
111
|
+
"title": "Дата створення",
|
|
112
|
+
"type": "Date",
|
|
113
|
+
"ua": "Дата створення"
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengis/cms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.60",
|
|
4
4
|
"description": "cms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Softpro",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"start": "bun --env-file=.env.ip server",
|
|
31
31
|
"prod": "NODE_ENV=production bun --env-file=.env.ip server ",
|
|
32
32
|
"ip": "bun --env-file=.env.ip server",
|
|
33
|
+
"lemken": "bun --env-file=.env.local-lemken server",
|
|
33
34
|
"demo": "node --env-file=.env.demo --env-file=.env server",
|
|
34
35
|
"i18n:sync": "node i18n-sync.cjs",
|
|
35
36
|
"prepublishOnly": "bun build:lib",
|
package/plugin.js
CHANGED
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { fileURLToPath } from 'url';
|
|
3
|
-
|
|
4
|
-
import { config, execMigrations, getPGAsync, addTemplateDir } from '@opengis/fastify-table/utils.js';
|
|
5
|
-
// for cms as plugin only
|
|
6
|
-
addTemplateDir(path.join(process.cwd(), 'node_modules/@opengis/cms/module/cms'));
|
|
7
|
-
|
|
8
|
-
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
|
|
10
|
-
const { prefix = '/api' } = config;
|
|
11
|
-
|
|
12
|
-
export default async function (app) {
|
|
13
|
-
app.register(import('./server/routes/menu/index.mjs'), { prefix });
|
|
14
|
-
app.register(import('./server/routes/migration/index.mjs'), { prefix });
|
|
15
|
-
app.register(import('./server/routes/cms/index.mjs'), { prefix });
|
|
16
|
-
app.register(import('./server/routes/cmsSpace/index.mjs'), { prefix });
|
|
17
|
-
app.register(import('./server/routes/contentType/index.mjs'), { prefix });
|
|
18
|
-
app.register(import('./server/routes/category/index.mjs'), { prefix });
|
|
19
|
-
app.register(import('./server/routes/tags/index.mjs'), { prefix });
|
|
20
|
-
app.register(import('./server/routes/logs/index.mjs'), { prefix });
|
|
21
|
-
app.register(import('./server/routes/feedback/index.mjs'), { prefix });
|
|
22
|
-
const pg = await getPGAsync(config.pg);
|
|
23
|
-
execMigrations(path.join(dirname, 'server/migrations'), pg).catch(err => console.log(err));
|
|
24
|
-
if (pg?.pk?.['site.content_types']) {
|
|
25
|
-
const customTables = await pg.query('select array_agg(table_name) from site.content_types where table_name is not null').catch(err => console.log(err)).then(el => el?.rows?.[0]?.array_agg || []);
|
|
26
|
-
|
|
27
|
-
await Promise.all(customTables.filter(table => pg.pk?.[`data.${table}`])?.map(async (table) => {
|
|
28
|
-
await pg.query(`alter table data.${table} add column if not exists meta json`).catch(err => console.log(err));
|
|
29
|
-
await pg.query(`alter table data.${table} add column if not exists main_image text`).catch(err => console.log(err));
|
|
30
|
-
await pg.query(`update site.content_types
|
|
31
|
-
SET columns = columns::jsonb || jsonb_build_object('name', 'main_image', 'type', 'image', 'label', 'Зображення')
|
|
32
|
-
WHERE not EXISTS (
|
|
33
|
-
SELECT 1
|
|
34
|
-
FROM jsonb_array_elements(columns::jsonb) AS obj(elem)
|
|
35
|
-
JOIN LATERAL jsonb_each_text(elem) AS kv(key, value) ON true
|
|
36
|
-
WHERE key = 'name' and value='main_image'
|
|
37
|
-
)`);
|
|
38
|
-
await pg.query(`alter table data.${table} add column if not exists published_at timestamp without time zone not null default now()`).catch(err => console.log(err));
|
|
39
|
-
await pg.query(`alter table data.${table} add column if not exists is_pin boolean DEFAULT false`).catch(err => console.log(err));
|
|
40
|
-
await pg.query(`alter table data.${table} drop column if exists publish_at`).catch(err => console.log(err));
|
|
41
|
-
}));
|
|
42
|
-
}
|
|
43
|
-
}
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
|
|
4
|
+
import { config, execMigrations, getPGAsync, addTemplateDir } from '@opengis/fastify-table/utils.js';
|
|
5
|
+
// for cms as plugin only
|
|
6
|
+
addTemplateDir(path.join(process.cwd(), 'node_modules/@opengis/cms/module/cms'));
|
|
7
|
+
|
|
8
|
+
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
const { prefix = '/api' } = config;
|
|
11
|
+
|
|
12
|
+
export default async function (app) {
|
|
13
|
+
app.register(import('./server/routes/menu/index.mjs'), { prefix });
|
|
14
|
+
app.register(import('./server/routes/migration/index.mjs'), { prefix });
|
|
15
|
+
app.register(import('./server/routes/cms/index.mjs'), { prefix });
|
|
16
|
+
app.register(import('./server/routes/cmsSpace/index.mjs'), { prefix });
|
|
17
|
+
app.register(import('./server/routes/contentType/index.mjs'), { prefix });
|
|
18
|
+
app.register(import('./server/routes/category/index.mjs'), { prefix });
|
|
19
|
+
app.register(import('./server/routes/tags/index.mjs'), { prefix });
|
|
20
|
+
app.register(import('./server/routes/logs/index.mjs'), { prefix });
|
|
21
|
+
app.register(import('./server/routes/feedback/index.mjs'), { prefix });
|
|
22
|
+
const pg = await getPGAsync(config.pg);
|
|
23
|
+
execMigrations(path.join(dirname, 'server/migrations'), pg).catch(err => console.log(err));
|
|
24
|
+
if (pg?.pk?.['site.content_types']) {
|
|
25
|
+
const customTables = await pg.query('select array_agg(table_name) from site.content_types where table_name is not null').catch(err => console.log(err)).then(el => el?.rows?.[0]?.array_agg || []);
|
|
26
|
+
|
|
27
|
+
await Promise.all(customTables.filter(table => pg.pk?.[`data.${table}`])?.map(async (table) => {
|
|
28
|
+
await pg.query(`alter table data.${table} add column if not exists meta json`).catch(err => console.log(err));
|
|
29
|
+
await pg.query(`alter table data.${table} add column if not exists main_image text`).catch(err => console.log(err));
|
|
30
|
+
await pg.query(`update site.content_types
|
|
31
|
+
SET columns = columns::jsonb || jsonb_build_object('name', 'main_image', 'type', 'image', 'label', 'Зображення')
|
|
32
|
+
WHERE not EXISTS (
|
|
33
|
+
SELECT 1
|
|
34
|
+
FROM jsonb_array_elements(columns::jsonb) AS obj(elem)
|
|
35
|
+
JOIN LATERAL jsonb_each_text(elem) AS kv(key, value) ON true
|
|
36
|
+
WHERE key = 'name' and value='main_image'
|
|
37
|
+
)`);
|
|
38
|
+
await pg.query(`alter table data.${table} add column if not exists published_at timestamp without time zone not null default now()`).catch(err => console.log(err));
|
|
39
|
+
await pg.query(`alter table data.${table} add column if not exists is_pin boolean DEFAULT false`).catch(err => console.log(err));
|
|
40
|
+
await pg.query(`alter table data.${table} drop column if exists publish_at`).catch(err => console.log(err));
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
}
|
package/server/app.js
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
import { config, addHook } from '@opengis/fastify-table/utils.js';
|
|
2
|
-
|
|
3
|
-
config.rateLimit = false;
|
|
4
|
-
config.prefix = config.prefix || '/api';
|
|
5
|
-
config.mode = config.mode || 'cms';
|
|
6
|
-
// config.auth.oneUser = true;
|
|
7
|
-
config.auth = config.auth || {};
|
|
8
|
-
config.auth['2fa'] = config.auth['2fa'] || {};
|
|
9
|
-
config.auth['2fa'].prefix = config.auth['2fa'].prefix || 'cms';
|
|
10
|
-
config.auth['2fa'].sufix = config.auth['2fa'].sufix || 'login';
|
|
11
|
-
|
|
12
|
-
// when frontend is implemented/used
|
|
13
|
-
// config.auth.loginPage = true; // disable core loginPage api, use vue component instead
|
|
14
|
-
// config.auth.link = config.auth.link || { '2fa': { login: '/2fa' } };
|
|
15
|
-
|
|
16
|
-
export default function (app) {
|
|
17
|
-
// core
|
|
18
|
-
app.register(import('@fastify/compress'), { encodings: ['br', 'gzip'], });
|
|
19
|
-
app.register(import('./plugins/adminHook.js'));
|
|
20
|
-
app.register(import('./plugins/hook.js'));
|
|
21
|
-
|
|
22
|
-
app.register(import('@opengis/fastify-table'), config);
|
|
23
|
-
|
|
24
|
-
app.register(import('./plugins/vite.js'));
|
|
25
|
-
// API
|
|
26
|
-
app.register(import('./routes/root.mjs'));
|
|
27
|
-
app.register(import('../plugin.js'));
|
|
28
|
-
|
|
29
|
-
addHook('errorMessage', (params) => {
|
|
30
|
-
if (params?.constraint && params.constraint === 'content_data_content_id_fkey') {
|
|
31
|
-
return 'Перед видаленням даного типу контенту, необхідно видалити всі дані, що до нього відносяться.';
|
|
32
|
-
}
|
|
33
|
-
return params?.message || 'Помилка API';
|
|
34
|
-
});
|
|
35
|
-
}
|
|
1
|
+
import { config, addHook } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
config.rateLimit = false;
|
|
4
|
+
config.prefix = config.prefix || '/api';
|
|
5
|
+
config.mode = config.mode || 'cms';
|
|
6
|
+
// config.auth.oneUser = true;
|
|
7
|
+
config.auth = config.auth || {};
|
|
8
|
+
config.auth['2fa'] = config.auth['2fa'] || {};
|
|
9
|
+
config.auth['2fa'].prefix = config.auth['2fa'].prefix || 'cms';
|
|
10
|
+
config.auth['2fa'].sufix = config.auth['2fa'].sufix || 'login';
|
|
11
|
+
|
|
12
|
+
// when frontend is implemented/used
|
|
13
|
+
// config.auth.loginPage = true; // disable core loginPage api, use vue component instead
|
|
14
|
+
// config.auth.link = config.auth.link || { '2fa': { login: '/2fa' } };
|
|
15
|
+
|
|
16
|
+
export default function (app) {
|
|
17
|
+
// core
|
|
18
|
+
app.register(import('@fastify/compress'), { encodings: ['br', 'gzip'], });
|
|
19
|
+
app.register(import('./plugins/adminHook.js'));
|
|
20
|
+
app.register(import('./plugins/hook.js'));
|
|
21
|
+
|
|
22
|
+
app.register(import('@opengis/fastify-table'), config);
|
|
23
|
+
|
|
24
|
+
app.register(import('./plugins/vite.js'));
|
|
25
|
+
// API
|
|
26
|
+
app.register(import('./routes/root.mjs'));
|
|
27
|
+
app.register(import('../plugin.js'));
|
|
28
|
+
|
|
29
|
+
addHook('errorMessage', (params) => {
|
|
30
|
+
if (params?.constraint && params.constraint === 'content_data_content_id_fkey') {
|
|
31
|
+
return 'Перед видаленням даного типу контенту, необхідно видалити всі дані, що до нього відносяться.';
|
|
32
|
+
}
|
|
33
|
+
return params?.message || 'Помилка API';
|
|
34
|
+
});
|
|
35
|
+
}
|
package/server/config.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readFile } from 'fs/promises';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
const config = fs.existsSync('config.json') ? JSON.parse(await readFile('config.json')) : {};
|
|
4
|
-
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
const config = fs.existsSync('config.json') ? JSON.parse(await readFile('config.json')) : {};
|
|
4
|
+
|
|
5
5
|
export default config;
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
|
|
3
|
-
import { config, getRedis, pgClients } from '@opengis/fastify-table/utils.js';
|
|
4
|
-
import { createMockReply } from './utils/mock.reply.js';
|
|
5
|
-
import getContent from '../routes/cms/controllers/getContent.js';
|
|
6
|
-
|
|
7
|
-
const pg = pgClients.client;
|
|
8
|
-
const rclient = getRedis();
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export default async function getContentBySlug({ slug, filter, tags, state, locale, contextQuery, collection = 'pages', ttl = 3600, fields, limit = 12, page = 1, order, desc } = {}) {
|
|
12
|
-
if (!slug) {
|
|
13
|
-
// return { error: 'not enough params: slug', code: 400 };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const tables = await pg.queryCache('select json_object_agg(name, \'data.\'||table_name) from site.content_types', { table: 'site.content_types' }).then(el => el.rows?.[0]?.json_object_agg || {});
|
|
17
|
-
|
|
18
|
-
const table = collection === 'pages' ? 'site.contents' : tables[collection];
|
|
19
|
-
|
|
20
|
-
if (!table) {
|
|
21
|
-
return { error: 'invalid params: collection', code: 400 };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// check if any crud operations performed, if not - return cached response
|
|
25
|
-
const crudInc = await rclient.get(`pg:${config.pg?.database}:${table}:crud`) || 0;
|
|
26
|
-
|
|
27
|
-
const cacheKey = createHash('md5').update([config.pg?.database, 'cms:content', slug, crudInc, filter, tags, state, locale, contextQuery, collection, fields, limit, page, order, desc, fields && typeof fields !== 'string' ? JSON.stringify(fields) : fields].join(':')).digest('hex');
|
|
28
|
-
const cacheData = ttl === 0 ? null : JSON.parse(await rclient.get(cacheKey));
|
|
29
|
-
|
|
30
|
-
// return from cache
|
|
31
|
-
if (cacheData) {
|
|
32
|
-
return { cache: true, ...cacheData };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const req = { pg: pgClients.client, params: { type: collection, id: slug }, query: { filter, tags, state, contextQuery, locale, fields, limit, page, order, desc } };
|
|
36
|
-
|
|
37
|
-
const mockReply = createMockReply();
|
|
38
|
-
const result = await getContent(req, mockReply, true);
|
|
39
|
-
|
|
40
|
-
if (ttl) {
|
|
41
|
-
await rclient.set(cacheKey, JSON.stringify(result), 'EX', ttl);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return result;
|
|
45
|
-
}
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { config, getRedis, pgClients } from '@opengis/fastify-table/utils.js';
|
|
4
|
+
import { createMockReply } from './utils/mock.reply.js';
|
|
5
|
+
import getContent from '../routes/cms/controllers/getContent.js';
|
|
6
|
+
|
|
7
|
+
const pg = pgClients.client;
|
|
8
|
+
const rclient = getRedis();
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export default async function getContentBySlug({ slug, filter, tags, state, locale, contextQuery, collection = 'pages', ttl = 3600, fields, limit = 12, page = 1, order, desc } = {}) {
|
|
12
|
+
if (!slug) {
|
|
13
|
+
// return { error: 'not enough params: slug', code: 400 };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const tables = await pg.queryCache('select json_object_agg(name, \'data.\'||table_name) from site.content_types', { table: 'site.content_types' }).then(el => el.rows?.[0]?.json_object_agg || {});
|
|
17
|
+
|
|
18
|
+
const table = collection === 'pages' ? 'site.contents' : tables[collection];
|
|
19
|
+
|
|
20
|
+
if (!table) {
|
|
21
|
+
return { error: 'invalid params: collection', code: 400 };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// check if any crud operations performed, if not - return cached response
|
|
25
|
+
const crudInc = await rclient.get(`pg:${config.pg?.database}:${table}:crud`) || 0;
|
|
26
|
+
|
|
27
|
+
const cacheKey = createHash('md5').update([config.pg?.database, 'cms:content', slug, crudInc, filter, tags, state, locale, contextQuery, collection, fields, limit, page, order, desc, fields && typeof fields !== 'string' ? JSON.stringify(fields) : fields].join(':')).digest('hex');
|
|
28
|
+
const cacheData = ttl === 0 ? null : JSON.parse(await rclient.get(cacheKey));
|
|
29
|
+
|
|
30
|
+
// return from cache
|
|
31
|
+
if (cacheData) {
|
|
32
|
+
return { cache: true, ...cacheData };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const req = { pg: pgClients.client, params: { type: collection, id: slug }, query: { filter, tags, state, contextQuery, locale, fields, limit, page, order, desc } };
|
|
36
|
+
|
|
37
|
+
const mockReply = createMockReply();
|
|
38
|
+
const result = await getContent(req, mockReply, true);
|
|
39
|
+
|
|
40
|
+
if (ttl) {
|
|
41
|
+
await rclient.set(cacheKey, JSON.stringify(result), 'EX', ttl);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
|
|
3
|
-
import { config, getRedis } from "@opengis/fastify-table/utils.js";
|
|
4
|
-
|
|
5
|
-
const rclient = getRedis();
|
|
6
|
-
const redisKey = [config.pg?.database, "draftKey"].filter(Boolean).join(":");
|
|
7
|
-
|
|
8
|
-
// allow users to view content in draft status if passed as query param
|
|
9
|
-
export default async function getDraftKey(nocache = false) {
|
|
10
|
-
const cacheKey = await rclient.get(redisKey);
|
|
11
|
-
const ttl = await rclient.ttl(redisKey) || 0;
|
|
12
|
-
|
|
13
|
-
if (cacheKey && !nocache) {
|
|
14
|
-
return { draftKey: cacheKey, ttl: (ttl / 60).toFixed(0) + " minutes" };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const draftKey = randomUUID().replace(/-/g, "").substring(0, 10);
|
|
18
|
-
|
|
19
|
-
// refresh every hour
|
|
20
|
-
await rclient.set(redisKey, draftKey, "EX", 60 * 60);
|
|
21
|
-
return { draftKey };
|
|
22
|
-
}
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { config, getRedis } from "@opengis/fastify-table/utils.js";
|
|
4
|
+
|
|
5
|
+
const rclient = getRedis();
|
|
6
|
+
const redisKey = [config.pg?.database, "draftKey"].filter(Boolean).join(":");
|
|
7
|
+
|
|
8
|
+
// allow users to view content in draft status if passed as query param
|
|
9
|
+
export default async function getDraftKey(nocache = false) {
|
|
10
|
+
const cacheKey = await rclient.get(redisKey);
|
|
11
|
+
const ttl = await rclient.ttl(redisKey) || 0;
|
|
12
|
+
|
|
13
|
+
if (cacheKey && !nocache) {
|
|
14
|
+
return { draftKey: cacheKey, ttl: (ttl / 60).toFixed(0) + " minutes" };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const draftKey = randomUUID().replace(/-/g, "").substring(0, 10);
|
|
18
|
+
|
|
19
|
+
// refresh every hour
|
|
20
|
+
await rclient.set(redisKey, draftKey, "EX", 60 * 60);
|
|
21
|
+
return { draftKey };
|
|
22
|
+
}
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
|
|
3
|
-
import { config, getRedis, pgClients } from '@opengis/fastify-table/utils.js';
|
|
4
|
-
import { createMockReply } from './utils/mock.reply.js';
|
|
5
|
-
|
|
6
|
-
import searchContent from '../routes/cms/controllers/searchContent.js';
|
|
7
|
-
|
|
8
|
-
const pg = pgClients.client;
|
|
9
|
-
const rclient = getRedis();
|
|
10
|
-
|
|
11
|
-
export default async function getSearchData({ page = 1, limit = 12, ttl = 3600, search, locale, tags, filter, contentType, asc } = {}) {
|
|
12
|
-
// check if any crud operations performed, if not - return cached response
|
|
13
|
-
const crudInc = await rclient.get(`pg:${config.pg?.database}:site.contents:crud`) || 0;
|
|
14
|
-
|
|
15
|
-
const cacheKey = createHash('md5').update([config.pg?.database, 'cms:search', crudInc, page, limit, search, locale, tags, filter, contentType, asc].join(':')).digest('hex');
|
|
16
|
-
const cacheData = ttl === 0 ? null : JSON.parse(await rclient.get(cacheKey));
|
|
17
|
-
|
|
18
|
-
// return from cache
|
|
19
|
-
if (cacheData) {
|
|
20
|
-
return { cache: true, ...cacheData };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const mockReply = createMockReply();
|
|
24
|
-
const result = await searchContent({ pg, query: { search, locale, page, limit, tags, filter, contentType } }, mockReply);
|
|
25
|
-
|
|
26
|
-
if (ttl) {
|
|
27
|
-
await rclient.set(cacheKey, JSON.stringify(result), 'EX', ttl);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return result;
|
|
31
|
-
}
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { config, getRedis, pgClients } from '@opengis/fastify-table/utils.js';
|
|
4
|
+
import { createMockReply } from './utils/mock.reply.js';
|
|
5
|
+
|
|
6
|
+
import searchContent from '../routes/cms/controllers/searchContent.js';
|
|
7
|
+
|
|
8
|
+
const pg = pgClients.client;
|
|
9
|
+
const rclient = getRedis();
|
|
10
|
+
|
|
11
|
+
export default async function getSearchData({ page = 1, limit = 12, ttl = 3600, search, locale, tags, filter, contentType, asc } = {}) {
|
|
12
|
+
// check if any crud operations performed, if not - return cached response
|
|
13
|
+
const crudInc = await rclient.get(`pg:${config.pg?.database}:site.contents:crud`) || 0;
|
|
14
|
+
|
|
15
|
+
const cacheKey = createHash('md5').update([config.pg?.database, 'cms:search', crudInc, page, limit, search, locale, tags, filter, contentType, asc].join(':')).digest('hex');
|
|
16
|
+
const cacheData = ttl === 0 ? null : JSON.parse(await rclient.get(cacheKey));
|
|
17
|
+
|
|
18
|
+
// return from cache
|
|
19
|
+
if (cacheData) {
|
|
20
|
+
return { cache: true, ...cacheData };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const mockReply = createMockReply();
|
|
24
|
+
const result = await searchContent({ pg, query: { search, locale, page, limit, tags, filter, contentType } }, mockReply);
|
|
25
|
+
|
|
26
|
+
if (ttl) {
|
|
27
|
+
await rclient.set(cacheKey, JSON.stringify(result), 'EX', ttl);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return result;
|
|
31
|
+
}
|