@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.
Files changed (201) hide show
  1. package/README.md +96 -3
  2. package/dist/assets/AddUser-CX-McfRW.js +1 -0
  3. package/dist/assets/ApiKeys-DSv1exYv.js +16 -0
  4. package/dist/assets/Appearance-DDtOUvCV.js +6 -0
  5. package/dist/assets/ArticlesPage-D6B3cZsl.js +6 -0
  6. package/dist/assets/BuilderPage-CCeSMVWe.js +1 -0
  7. package/dist/assets/CollectionsBreadcrumb.vue_vue_type_script_setup_true_lang-DVYVfYF4.js +1 -0
  8. package/dist/assets/CollectionsPage-CHk8Cn5k.js +1 -0
  9. package/dist/assets/Dashboard-Bs7sXO6h.js +11 -0
  10. package/dist/assets/EditCollectionPage-gPuLJrN8.js +41 -0
  11. package/dist/assets/EmailPage-DhlWsPxk.js +1 -0
  12. package/dist/assets/EmptyData-Ct-xQv_N.js +1 -0
  13. package/dist/assets/FeedbackPage-DtaOncVv.js +1 -0
  14. package/dist/assets/Logs-CZ5klHNK.js +1 -0
  15. package/dist/assets/MediaBreadcrumb-BpOxt5PK.js +11 -0
  16. package/dist/assets/MediaPage-DqRcZFlO.js +16 -0
  17. package/dist/assets/MenuAddPage-BLcoVgrS.js +1 -0
  18. package/dist/assets/MenuItemPage-B2otXqkz.js +20 -0
  19. package/dist/assets/MenuPage-C00m4Fc_.js +1 -0
  20. package/dist/assets/MonacoEditor.vue_vue_type_script_setup_true_lang-CjoEsC67.js +3 -0
  21. package/dist/assets/PermissionsPage-LtqcCJ14.js +1 -0
  22. package/dist/assets/Settings-BHH6RoBP.js +1 -0
  23. package/dist/assets/SettingsTable-CY5pZx1z.js +1 -0
  24. package/dist/assets/SettingsTitle-t4WJBFxZ.js +1 -0
  25. package/dist/assets/SingletonsPage-Bn2Ypjhs.js +6 -0
  26. package/dist/assets/TagsPage-DAiakEth.js +1 -0
  27. package/dist/assets/UniversalTable.vue_vue_type_script_setup_true_lang-BQ5m4aZd.js +11 -0
  28. package/dist/assets/UniversalTablePagination.vue_vue_type_script_setup_true_lang-DppVBws0.js +1 -0
  29. package/dist/assets/Users-CMH5j0db.js +1 -0
  30. package/dist/assets/UsersPage-CDGreEib.js +1 -0
  31. package/dist/assets/arrow-up-DCe0WsrM.js +16 -0
  32. package/dist/assets/{calendar-hsWc4yH-.js → calendar-o9t4MkD2.js} +1 -1
  33. package/dist/assets/chevron-left-WFftVS9c.js +6 -0
  34. package/dist/assets/chevron-right-BiiSb3Be.js +6 -0
  35. package/dist/assets/contentForm-unZQhjCu.js +6 -0
  36. package/dist/assets/en-BDx3Svx8.js +1 -0
  37. package/dist/assets/eye-Dijywc6g.js +6 -0
  38. package/dist/assets/file-B_duymIT.js +6 -0
  39. package/dist/assets/general-CkN_0qIV.js +1 -0
  40. package/dist/assets/index-BIp7eSXk.js +1 -0
  41. package/dist/assets/index-DCW2e4Az.js +9 -0
  42. package/dist/assets/index-DGweaj24.js +1 -0
  43. package/dist/assets/index-W-qQIppj-BDlsxaGB.js +1 -0
  44. package/dist/assets/index-W-qQIppj-BsopI3Hz-BIZR-dhy.js +1 -0
  45. package/dist/assets/index-oQz9FOqL.css +1 -0
  46. package/dist/assets/index-yMJAVBXk.js +290 -0
  47. package/dist/assets/list-CXRbSNky.js +6 -0
  48. package/dist/assets/pencil-CwnPP4IJ.js +6 -0
  49. package/dist/assets/{plus-D9etvrM2.js → plus-DLR44m6p.js} +1 -1
  50. package/dist/assets/save-FeDrOUOd.js +6 -0
  51. package/dist/assets/{search-BI-hqhq6.js → search-C4-fHihx.js} +1 -1
  52. package/dist/assets/{square-pen-61CkyXzK.js → square-pen-xVs4e8Yb.js} +1 -1
  53. package/dist/assets/{trash-2-CJSl_r88.js → trash-2-BGXMNU3d.js} +1 -1
  54. package/dist/assets/uk-BA7DIKEL.js +1 -0
  55. package/dist/assets/useDebounce-DFq3rxAW.js +1 -0
  56. package/dist/assets/vs-form-reletion-link-C-xrdHDl.js +20 -0
  57. package/dist/assets/vs-form-reletion-link-bk-9ZkDH.css +1 -0
  58. package/dist/assets/vue.-sixQ7xP-CUPNuJcq.js +1 -0
  59. package/dist/assets/vuedraggable.umd-W_2WTF6i.js +14 -0
  60. package/dist/assets/{x-BNquQe5y.js → x-D2t-wfBe.js} +1 -1
  61. package/dist/index.html +14 -9
  62. package/module/cms/card/cms.content.table/index.yml +17 -0
  63. package/module/cms/card/cms.content.table/main_info.hbs +26 -0
  64. package/module/cms/card/cms.menu.table/content_info.hbs +16 -0
  65. package/module/cms/card/cms.menu.table/index.yml +18 -0
  66. package/module/cms/card/cms.menu.table/main_info.hbs +22 -0
  67. package/module/cms/card/cms.settings.table/index.yml +13 -0
  68. package/module/cms/card/cms.settings.table/main_info.hbs +20 -0
  69. package/module/cms/cls/content.status.json +18 -0
  70. package/module/cms/cls/user_type.json +10 -0
  71. package/module/cms/form/admin.users.form.json +78 -0
  72. package/module/cms/form/cms.content.form.json +79 -0
  73. package/module/cms/form/cms.menu.form.json +69 -0
  74. package/module/cms/form/cms.settings.form.json +32 -0
  75. package/module/cms/menu.json +24 -0
  76. package/module/cms/router.js +154 -0
  77. package/module/cms/select/cms.page_type.sql +2 -0
  78. package/module/cms/select/collection.sql +1 -0
  79. package/module/cms/select/locale.sql +17 -0
  80. package/module/cms/select/news_tag_id.sql +12 -0
  81. package/module/cms/select/tag_id.sql +1 -0
  82. package/module/cms/table/admin.users.table.json +54 -0
  83. package/module/cms/table/cms.content.table.json +106 -0
  84. package/module/cms/table/cms.menu.table.json +73 -0
  85. package/module/cms/table/cms.settings.table.json +57 -0
  86. package/module/cms/table/collection.default.table.json +102 -0
  87. package/module/cms/table/single.default.table.json +115 -0
  88. package/package.json +36 -31
  89. package/plugin.js +63 -23
  90. package/server/app.js +20 -3
  91. package/server/functions/getDraftKey.js +22 -0
  92. package/server/index.js +2 -3
  93. package/server/migrations/fixes.sql +124 -0
  94. package/server/migrations/site.sql +338 -249
  95. package/server/plugins/adminHook.js +2 -2
  96. package/server/plugins/hook.js +53 -61
  97. package/server/plugins/vite.js +5 -5
  98. package/server/routes/cms/controllers/cmsStat.js +56 -0
  99. package/server/routes/cms/controllers/cmsSuggest.js +58 -0
  100. package/server/routes/cms/controllers/deleteContent.js +114 -59
  101. package/server/routes/cms/controllers/deleteMedia.js +75 -46
  102. package/server/routes/cms/controllers/downloadMedia.js +48 -48
  103. package/server/routes/cms/controllers/getContent.js +110 -95
  104. package/server/routes/cms/controllers/getContentBySlug.js +95 -0
  105. package/server/routes/cms/controllers/getPermissions.js +15 -15
  106. package/server/routes/cms/controllers/insertContent.js +218 -68
  107. package/server/routes/cms/controllers/listMedia.js +93 -72
  108. package/server/routes/cms/controllers/metadataMedia.js +38 -37
  109. package/server/routes/cms/controllers/properties.get.js +53 -0
  110. package/server/routes/cms/controllers/properties.post.js +99 -0
  111. package/server/routes/cms/controllers/searchContent.js +205 -0
  112. package/server/routes/cms/controllers/setPermissions.js +49 -49
  113. package/server/routes/cms/controllers/translate.js +90 -0
  114. package/server/routes/cms/controllers/updateContent.js +238 -111
  115. package/server/routes/cms/controllers/uploadMedia.js +78 -65
  116. package/server/routes/cms/index.mjs +81 -12
  117. package/server/routes/cms/utils/additionalData.js +36 -0
  118. package/server/routes/cms/utils/getCollection.js +82 -0
  119. package/server/routes/cms/utils/getSingle.js +188 -0
  120. package/server/routes/cms/utils/insertContentLocalization.js +87 -0
  121. package/server/routes/cms/utils/requestTranslation.js +85 -0
  122. package/server/routes/cms/utils/updateLocalization.js +48 -0
  123. package/server/routes/cmsSpace/controllers/deleteSpace.js +26 -0
  124. package/server/routes/cmsSpace/controllers/getSpaces.js +28 -0
  125. package/server/routes/cmsSpace/controllers/insertSpace.js +22 -0
  126. package/server/routes/cmsSpace/controllers/updateSpace.js +24 -0
  127. package/server/routes/cmsSpace/index.mjs +20 -0
  128. package/server/routes/contentType/controllers/addContentType.js +162 -0
  129. package/server/routes/contentType/controllers/contentTypeList.js +54 -0
  130. package/server/routes/contentType/controllers/delContentType.js +75 -0
  131. package/server/routes/contentType/controllers/editContentType.js +61 -0
  132. package/server/routes/contentType/controllers/getContentType.js +37 -0
  133. package/server/routes/contentType/index.mjs +29 -19
  134. package/server/routes/contentType/utils/updateContents.js +29 -0
  135. package/server/routes/contentType/utils/updateCustomContentTable.js +56 -0
  136. package/server/routes/feedback/controllers/email.list.js +25 -0
  137. package/server/routes/feedback/controllers/feedback.js +49 -0
  138. package/server/routes/feedback/controllers/feedback.list.js +38 -0
  139. package/server/routes/feedback/controllers/news.subscriptions.js +44 -0
  140. package/server/routes/feedback/index.mjs +72 -0
  141. package/server/routes/logs/controllers/export.user.logs.js +78 -0
  142. package/server/routes/logs/controllers/user.logs.js +45 -0
  143. package/server/routes/logs/index.mjs +9 -0
  144. package/server/routes/menu/controllers/addMenu.js +38 -0
  145. package/server/routes/menu/controllers/delMenu.js +32 -0
  146. package/server/routes/menu/controllers/editMenu.js +42 -0
  147. package/server/routes/menu/controllers/getMenu.js +43 -0
  148. package/server/routes/menu/index.mjs +13 -0
  149. package/server/routes/migration/controllers/collectionToCustom.js +137 -0
  150. package/server/routes/migration/index.mjs +8 -0
  151. package/server/routes/tags/controllers/add.tags.js +25 -0
  152. package/server/routes/tags/controllers/del.tags.js +20 -0
  153. package/server/routes/tags/controllers/edit.tags.js +26 -0
  154. package/server/routes/tags/controllers/get.tags.js +16 -0
  155. package/server/routes/tags/index.mjs +14 -0
  156. package/server/templates/page/login.html +73 -5
  157. package/server/templates/select/core.user_mentioned.sql +2 -0
  158. package/src/index.ts +122 -0
  159. package/dist/assets/ArticlesPage-BveM4q3g.js +0 -11
  160. package/dist/assets/CollectionsPage-D5td-UBm.js +0 -1
  161. package/dist/assets/ContentBlock.vue_vue_type_script_setup_true_lang-BwF6D-yB.js +0 -30
  162. package/dist/assets/CreateCollectionPage-Cu0RW5ui.js +0 -76
  163. package/dist/assets/Dashboard-faSjwmB8.js +0 -11
  164. package/dist/assets/EditCollectionPage-K5oPPzCd.js +0 -1
  165. package/dist/assets/MediaPage-BoW3aWgN.js +0 -1
  166. package/dist/assets/PermissionsPage-DGy5fha2.js +0 -1
  167. package/dist/assets/SingletonsPage-C1X2xkQE.js +0 -1
  168. package/dist/assets/UniversalTable.vue_vue_type_script_setup_true_lang-DUqfWJcy.js +0 -6
  169. package/dist/assets/contentForm-DMVC4vho.js +0 -1
  170. package/dist/assets/database-BTxZQzYy.js +0 -6
  171. package/dist/assets/index-9GY17iSP.css +0 -1
  172. package/dist/assets/index-DYyZmLWO.js +0 -2138
  173. package/dist/assets/index-xsH4HHeE.js +0 -6
  174. package/dist/assets/save-C2B6th9J.js +0 -11
  175. package/dist/assets/settings-DbyDiH2g.js +0 -6
  176. package/dist/assets/vue.-sixQ7xP-DwXf3zRn.js +0 -1
  177. package/dist/assets/x-circle-C3q70RMH.js +0 -16
  178. package/server/routes/contentType/controllers/cms.type.delete.js +0 -22
  179. package/server/routes/contentType/controllers/cms.type.get.js +0 -22
  180. package/server/routes/contentType/controllers/cms.type.list.js +0 -25
  181. package/server/routes/contentType/controllers/cms.type.post.js +0 -22
  182. package/server/routes/contentType/controllers/cms.type.put.js +0 -24
  183. package/server/routes/contentType/utils/builderCache.js +0 -58
  184. package/server/routes/fileContent/data/deleteContent.js +0 -34
  185. package/server/routes/fileContent/data/deleteMedia.js +0 -28
  186. package/server/routes/fileContent/data/downloadMedia.js +0 -41
  187. package/server/routes/fileContent/data/getContent.js +0 -32
  188. package/server/routes/fileContent/data/insertContent.js +0 -37
  189. package/server/routes/fileContent/data/listMedia.js +0 -47
  190. package/server/routes/fileContent/data/metadataMedia.js +0 -38
  191. package/server/routes/fileContent/data/updateContent.js +0 -40
  192. package/server/routes/fileContent/data/uploadMedia.js +0 -49
  193. package/server/routes/fileContent/index.mjs +0 -54
  194. package/server/routes/fileContent/type/contentTypeList.js +0 -7
  195. package/server/routes/fileContent/type/createContentType.js +0 -31
  196. package/server/routes/fileContent/type/deleteContentType.js +0 -29
  197. package/server/routes/fileContent/type/getContentType.js +0 -15
  198. package/server/routes/fileContent/type/updateContentType.js +0 -40
  199. package/server/routes/fileContent/utils/astroBuilderCache.js +0 -47
  200. package/server/routes/fileContent/utils/contentDir.js +0 -12
  201. package/server/routes/fileContent/utils/contentTypeExists.js +0 -15
@@ -0,0 +1,102 @@
1
+ {
2
+ "table": "site.contents",
3
+ "columns": [
4
+ {
5
+ "label": "Slug",
6
+ "name": "slug",
7
+ "parent": "title",
8
+ "required": true,
9
+ "type": "slug"
10
+ },
11
+ {
12
+ "label": "Title",
13
+ "name": "title",
14
+ "required": true,
15
+ "type": "text",
16
+ "localization": true
17
+ },
18
+ {
19
+ "label": "Status",
20
+ "name": "status",
21
+ "type": "select",
22
+ "data": "content.status",
23
+ "options": [
24
+ {
25
+ "id": "draft",
26
+ "text": "Draft"
27
+ },
28
+ {
29
+ "id": "published",
30
+ "text": "Published"
31
+ },
32
+ {
33
+ "id": "archived",
34
+ "text": "Archived"
35
+ },
36
+ {
37
+ "id": "delayPublished",
38
+ "text": "Delay Published"
39
+ }
40
+ ],
41
+ "required": true
42
+ },
43
+ {
44
+ "label": "Publish at",
45
+ "name": "published_at",
46
+ "required": true,
47
+ "type": "datetime"
48
+ },
49
+ {
50
+ "label": "Зображення",
51
+ "name": "main_image",
52
+ "type": "mediaselect"
53
+ }
54
+ ],
55
+ "filters": [
56
+ {
57
+ "extra": false,
58
+ "id": "status",
59
+ "name": "status",
60
+ "title": "Статус",
61
+ "type": "Check",
62
+ "data": "content.status",
63
+ "ua": "Статус"
64
+ },
65
+ {
66
+ "extra": false,
67
+ "label": "Publish at",
68
+ "name": "published_at",
69
+ "type": "Date"
70
+ },
71
+ {
72
+ "extra": false,
73
+ "label": "Закріплені",
74
+ "name": "is_pin",
75
+ "type": "Check"
76
+ },
77
+ {
78
+ "extra": false,
79
+ "name": "tag_list",
80
+ "type": "Tags",
81
+ "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))",
82
+ "label": "Теги"
83
+ },
84
+ {
85
+ "extra": false,
86
+ "columns": "slug,title",
87
+ "id": "search",
88
+ "name": "search",
89
+ "placeholder": "Пошук по тексту",
90
+ "title": "Пошук по тексту",
91
+ "type": "Text"
92
+ },
93
+ {
94
+ "extra": false,
95
+ "id": "created_at",
96
+ "name": "created_at",
97
+ "title": "Дата створення",
98
+ "type": "Date",
99
+ "ua": "Дата створення"
100
+ }
101
+ ]
102
+ }
@@ -0,0 +1,115 @@
1
+ {
2
+ "table": "site.contents",
3
+ "columns": [
4
+ {
5
+ "label": "Slug",
6
+ "name": "slug",
7
+ "parent": "title",
8
+ "required": true,
9
+ "type": "slug"
10
+ },
11
+ {
12
+ "label": "Title",
13
+ "name": "title",
14
+ "required": true,
15
+ "type": "text",
16
+ "localization": true
17
+ },
18
+ {
19
+ "label": "Status",
20
+ "name": "status",
21
+ "type": "select",
22
+ "data": "content.status",
23
+ "options": [
24
+ {
25
+ "id": "draft",
26
+ "text": "Draft"
27
+ },
28
+ {
29
+ "id": "published",
30
+ "text": "Published"
31
+ },
32
+ {
33
+ "id": "archived",
34
+ "text": "Archived"
35
+ },
36
+ {
37
+ "id": "delayPublished",
38
+ "text": "Delay Published"
39
+ }
40
+ ],
41
+ "required": true
42
+ },
43
+ {
44
+ "label": "Publish at",
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
+ "label": "Контент",
86
+ "name": "single_body",
87
+ "sql": "content_id in (select object_id from site.content_data where field_key='single_body' and field_value ~ $1)",
88
+ "type": "Text"
89
+ },
90
+ {
91
+ "extra": false,
92
+ "name": "tag_list",
93
+ "type": "Tags",
94
+ "sql": "content_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))",
95
+ "label": "Теги"
96
+ },
97
+ {
98
+ "extra": false,
99
+ "columns": "slug,title",
100
+ "id": "search",
101
+ "name": "search",
102
+ "placeholder": "Пошук по тексту",
103
+ "title": "Пошук по тексту",
104
+ "type": "Text"
105
+ },
106
+ {
107
+ "extra": false,
108
+ "id": "created_at",
109
+ "name": "created_at",
110
+ "title": "Дата створення",
111
+ "type": "Date",
112
+ "ua": "Дата створення"
113
+ }
114
+ ]
115
+ }
package/package.json CHANGED
@@ -1,64 +1,69 @@
1
1
  {
2
2
  "name": "@opengis/cms",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "cms",
5
5
  "type": "module",
6
6
  "author": "Softpro",
7
7
  "main": "dist/cms.js",
8
+
8
9
  "license": "EULA",
9
10
  "files": [
10
11
  "dist/*",
12
+ "module/*",
13
+ "src/index.ts",
11
14
  "server",
12
15
  "plugin.js"
13
16
  ],
14
17
  "scripts": {
15
18
  "test": "node --test test/**/*.test.js",
16
19
  "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
17
- "build": "cross-env APP=true vite build",
20
+ "build": "vite build",
21
+ "proxy": "vite dev",
18
22
  "build-npm": "vite build",
19
- "start": "node server",
20
- "prod": "cross-env NODE_ENV=production node server --cms=cms/softpro",
21
- "prod-ip": "cross-env NODE_ENV=production node server --cms=cms/ip",
22
- "dev": "node server --cms=cms/softpro"
23
+ "start": "bun --env-file=.env.ip server",
24
+ "prod-ip1": "node server --config=ip ",
25
+ "prod-softpro1": "node server --config=softpro",
26
+ "prod": "NODE_ENV=production bun --env-file=.env.ip server ",
27
+ "prod-ip": "PORT=3019 node server --config=ip",
28
+ "prod-ip-test": "PORT=3025 node server --config=ip",
29
+ "dev": "node server --config=softpro",
30
+ "debug": "bun --watch server --config=softpro",
31
+ "ip-test": "node server --config=ip-test",
32
+ "ip": "node --env-file=.env.ip server",
33
+ "demo": "node --env-file=.env.demo --env-file=.env server",
34
+ "i18n:sync": "node i18n-sync.cjs",
35
+ "softpro": "bun --env-file=.env.softpro server"
23
36
  },
24
37
  "dependencies": {
25
- "@opengis/admin": "0.4.4",
26
- "@opengis/fastify-auth": "1.1.0",
27
- "@opengis/fastify-file": "1.1.2",
28
- "@opengis/fastify-table": "1.4.5",
29
- "@opengis/v3-core": "0.3.190",
30
- "@opengis/v3-filter": "0.0.74",
31
- "@vitejs/plugin-vue": "5.0.4",
32
- "@vueuse/core": "10.9.0",
38
+ "@fastify/compress": "^8.1.0",
39
+ "@opengis/core": "^0.0.30",
40
+ "@opengis/fastify-table": "^2.0.118",
41
+ "@opengis/filter": "^0.1.10",
42
+ "@opengis/form": "^0.0.70",
43
+ "@opengis/richtext": "0.0.38",
33
44
  "@vueuse/head": "2.0.0",
34
- "cross-env": "7.0.3",
35
- "fastify": "5.3.3",
36
- "fastify-plugin": "5.0.1",
45
+ "js-yaml": "^4.1.0",
37
46
  "lucide-vue-next": "0.344.0",
38
- "moment": "2.30.1",
39
- "pinia": "3.0.2",
40
47
  "vite": "5.1.4",
41
- "vue": "3.5.13",
48
+ "vue": "^3.5.17",
42
49
  "vue-i18n": "11.1.5",
43
- "vue-router": "4.4.3"
50
+ "vue-router": "4.4.3",
51
+ "vuedraggable": "4.1.0"
44
52
  },
45
- "overrides": {
46
- "@opengis/fastify-table": "1.4.5"
53
+ "resolutions": {
54
+ "rollup": "4.30.0"
47
55
  },
48
56
  "devDependencies": {
49
- "@parcel/watcher": "2.4.1",
50
57
  "@tailwindcss/typography": "0.5.10",
51
- "@types/node": "20.11.25",
58
+ "@tsconfig/node22": "^22.0.2",
52
59
  "@vitejs/plugin-vue": "5.0.4",
53
60
  "autoprefixer": "10.4.18",
54
61
  "eslint": "8.49.0",
55
- "eslint-config-airbnb": "19.0.4",
56
- "eslint-plugin-import": "2.25.3",
57
- "eslint-plugin-vue": "9.17.0",
58
62
  "postcss": "8.4.35",
63
+ "sass": "^1.92.1",
59
64
  "tailwindcss": "3.4.1",
60
- "typescript": "5.2.2",
61
- "vite": "5.1.4",
62
- "vue-tsc": "1.8.27"
65
+ "typescript": "~5.8.0",
66
+ "vitest": "3.2.4",
67
+ "vue-tsc": "^2.2.10"
63
68
  }
64
69
  }
package/plugin.js CHANGED
@@ -2,35 +2,75 @@ import path from 'node:path';
2
2
  import { fileURLToPath } from 'url';
3
3
  import { existsSync, readFileSync } from 'node:fs';
4
4
 
5
- import { config, execMigrations } from '@opengis/fastify-table/utils.js';
5
+ import { config, execMigrations, getPGAsync } from '@opengis/fastify-table/utils.js';
6
+ import { table } from 'node:console';
6
7
 
7
8
  const dirname = path.dirname(fileURLToPath(import.meta.url));
8
9
 
9
- // cross-env cms=cms/softpro npm start
10
- if (process.argv.find(el => el.includes('--cms'))) {
11
- const subdir = process.argv.find(el => el.includes('--cms')).substring(6);
12
- const cmsDir = path.join(dirname, '..', subdir).replace(/\\/g, '/'); // from app.js
13
-
14
- const cmsConfig = existsSync(path.join(cmsDir, 'setting.json'))
15
- ? JSON.parse(readFileSync(path.join(cmsDir, 'setting.json'), 'utf8'))
10
+ // node server --config=softpro
11
+ console.log(process.argv);
12
+ if (process.argv.find(el => el.includes('--config'))) {
13
+ const filename = process.argv.find(el => el.includes('--config')).split('=').pop();
14
+ const cmsConfig = existsSync(`config/${filename}.json`)
15
+ ? JSON.parse(readFileSync(`config/${filename}.json`, 'utf8'))
16
16
  : {};
17
17
 
18
- config.auth = config.auth || cmsConfig.auth;
19
- config.pg = cmsConfig.pg;
20
- config.cms = cmsConfig.cms || {};
21
- config.cms.root = cmsDir;
22
- config.folder = path.posix.join(config.folder || '', config.cms.folder || subdir);
18
+ console.log('cms init', cmsConfig.pg?.database);
19
+ Object.keys(cmsConfig).forEach(key => {
20
+ config[key] = cmsConfig[key];
21
+ });
22
+ config.spacename = 'default' || cmsConfig.name || filename;
23
+ } else {
24
+ config.spacename = 'default' || config.spacename || process.env.NODE_ENV; // --env.file=.env.ip
23
25
  }
24
26
 
25
- export default async function (app, opts = config) {
26
- const { prefix = '/api' } = opts;
27
- if (!opts.pg || opts.cms?.provider === 'file') {
28
- app.register(import('./server/routes/fileContent/index.mjs'), { prefix });
29
- } else {
30
- app.register(import('./server/routes/cms/index.mjs'), { prefix });
31
- app.register(import('./server/routes/contentType/index.mjs'), { prefix });
32
- app.register(import('./server/routes/category/index.mjs'), { prefix });
33
- const pg = await getPGAsync(opts.pg);
34
- execMigrations(path.join(dirname, 'migrations'), pg).catch(err => console.log(err));
27
+ const { prefix = '/api' } = config;
28
+
29
+ export default async function (app) {
30
+ app.register(import('./server/routes/menu/index.mjs'), { prefix });
31
+ app.register(import('./server/routes/migration/index.mjs'), { prefix });
32
+ app.register(import('./server/routes/cms/index.mjs'), { prefix });
33
+ app.register(import('./server/routes/cmsSpace/index.mjs'), { prefix });
34
+ app.register(import('./server/routes/contentType/index.mjs'), { prefix });
35
+ app.register(import('./server/routes/category/index.mjs'), { prefix });
36
+ app.register(import('./server/routes/tags/index.mjs'), { prefix });
37
+ app.register(import('./server/routes/logs/index.mjs'), { prefix });
38
+ app.register(import('./server/routes/feedback/index.mjs'), { prefix });
39
+ const pg = await getPGAsync(config.pg);
40
+ execMigrations(path.join(dirname, 'server/migrations'), pg).catch(err => console.log(err));
41
+ if (pg?.pk?.['site.content_types']) {
42
+ const customTables = await pg.query('select array_agg(table_name) from site.content_types where table_name is not null').then(el => el.rows?.[0]?.array_agg || []).catch(err => console.log(err));
43
+
44
+ await Promise.all(customTables.filter(table => pg.pk?.[`data.${table}`])?.map(async (table) => {
45
+ await pg.query(`alter table data.${table} add column if not exists meta json`).catch(err => console.log(err));
46
+ await pg.query(`alter table data.${table} add column if not exists main_image text`).catch(err => console.log(err));
47
+ await pg.query(`update site.content_types
48
+ SET columns = columns::jsonb || jsonb_build_object('name', 'main_image', 'type', 'image', 'label', 'Зображення')
49
+ WHERE not EXISTS (
50
+ SELECT 1
51
+ FROM jsonb_array_elements(columns::jsonb) AS obj(elem)
52
+ JOIN LATERAL jsonb_each_text(elem) AS kv(key, value) ON true
53
+ WHERE key = 'name' and value='main_image'
54
+ )`);
55
+ 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));
56
+ await pg.query(`alter table data.${table} add column if not exists is_pin boolean DEFAULT false`).catch(err => console.log(err));
57
+ await pg.query(`alter table data.${table} drop column if exists publish_at`).catch(err => console.log(err));
58
+ }));
59
+
60
+
61
+
62
+ /*customTables.filter(table => pg.pk?.[`data.${table}`])?.forEach(table => pg.query(`alter table data.${table} add column if not exists meta json`).catch(err => console.log(err)));
63
+ customTables.filter(table => pg.pk?.[`data.${table}`])?.forEach(table => 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)));
64
+ customTables.filter(table => pg.pk?.[`data.${table}`])?.forEach(table => pg.query(`alter table data.${table} add column if not exists is_pin boolean DEFAULT false`).catch(err => console.log(err)));*/
65
+
66
+
67
+ }
68
+ /* if (pg?.pk?.['site.content_types'] && pg?.pk?.['site.contents']) {
69
+ await pg.query('insert into site.contents(content_type_id, space_id, locale) select content_type_id, $1, $2 from site.content_types where content_type_id not in (select content_type_id from site.contents)', [config.spacename || 'default', config.locale || 'ua']).catch(err => {
70
+ console.log(err.toString());
71
+ });
35
72
  }
73
+ if (pg?.pk?.['site.spaces']) {
74
+ pg.query('insert into site.spaces(space_id, name) values($1, $1) on conflict(space_id) do nothing', [config.spacename || 'default']).catch(err => console.error('spaces insert error', err.toString())); // for content site.spaces
75
+ } */
36
76
  }
package/server/app.js CHANGED
@@ -1,18 +1,35 @@
1
- import { config } from '@opengis/fastify-table/utils.js';
1
+ import { config, addHook } from '@opengis/fastify-table/utils.js';
2
2
 
3
+ config.rateLimit = false;
3
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' } };
4
15
 
5
16
  export default async function (app) {
6
17
  // core
18
+ app.register(import('@fastify/compress'), { encodings: ['br', 'gzip'], });
7
19
  app.register(import('./plugins/adminHook.js'));
8
20
  app.register(import('./plugins/hook.js'));
9
21
 
10
22
  app.register(import('@opengis/fastify-table'), config);
11
- // fastify.register(import('@opengis/fastify-auth'), config);
12
- app.register(import('@opengis/fastify-file'), config);
13
23
 
14
24
  app.register(import('./plugins/vite.js'));
15
25
  // API
16
26
  app.register(import('./routes/root.mjs'));
17
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
+ });
18
35
  }
@@ -0,0 +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
+ }
package/server/index.js CHANGED
@@ -1,12 +1,11 @@
1
1
  import Fastify from 'fastify';
2
2
 
3
-
4
3
  import appService from './app.js';
5
4
 
6
5
  import { config, logger } from '@opengis/fastify-table/utils.js';
7
6
 
8
7
  // Instantiate Fastify with some config
9
- const app = Fastify({ loggerInstance: logger });
8
+ const app = Fastify({ loggerInstance: logger, maxParamLength: config.maxParamLength || 500 });
10
9
 
11
10
  // Register your application as a normal plugin.
12
11
 
@@ -17,7 +16,7 @@ process.env.PORT = process.env.PORT || config.port || 3000;
17
16
  app.listen({ host: '0.0.0.0', port: process.env.PORT }, (err) => {
18
17
  if (err) {
19
18
  console.error(err.toString());
20
- app.log.error(err);
19
+ logger.error(err);
21
20
  process.exit(1);
22
21
  }
23
22
  });
@@ -0,0 +1,124 @@
1
+ create schema if not exists site;
2
+
3
+ ALTER TABLE if exists site.spaces add column if not exists locales text[];
4
+ ALTER TABLE IF exists site.spaces add column if not exists default_locale text;
5
+ ALTER TABLE if exists site.spaces alter column default_locale set default 'ua';
6
+ update site.spaces set default_locale = 'ua' where default_locale is null;
7
+ ALTER TABLE if exists site.spaces alter column default_locale set not null;
8
+
9
+ ALTER TABLE if exists site.media ADD COLUMN IF NOT EXISTS subdir TEXT;
10
+ COMMENT ON COLUMN site.media.subdir is 'Піддиректорія';
11
+
12
+ -- deprecated tables
13
+ -- drop table if exists site.articles cascade;
14
+ drop table if exists site.article_media cascade;
15
+ drop table if exists site.content_attributes cascade;
16
+ drop table if exists site.article_translations cascade;
17
+ drop table if exists site.menu_items cascade;
18
+
19
+ -- redundant columns / constraints
20
+ ALTER TABLE if exists site.content_types DROP CONSTRAINT if exists content_types_slug_key;
21
+ ALTER TABLE if exists site.content_types drop column if exists slug;
22
+ ALTER TABLE if exists site.content_types drop column if exists display_name;
23
+ ALTER TABLE if exists site.contents drop column if exists fields;
24
+
25
+ DROP FUNCTION if exists site.getmenu(text);
26
+
27
+ -- seamless migration start
28
+ /*
29
+ insert into site.contents(content_id, space_id, content_type_id, slug)
30
+ select content_type_id, 'default', content_type_id, min(type_name) from site.single_type_values a
31
+ left join lateral (select content_type_id from site.content_types b where a.type_name=b.name limit 1)b on 1=1
32
+ where content_type_id not in (select content_id from site.contents) and content_type_id is not null
33
+ group by content_type_id;
34
+ insert into site.content_data(field_id, content_id, object_id, field_key, field_type, field_value)
35
+ select distinct single_type_value_id, content_type_id, type_name, key,
36
+ coalesce(value_type,case when substring(value from 1 for 1) = '[' then 'datalist' else 'text' end), value from site.single_type_values a
37
+ left join lateral (select content_type_id from site.content_types b where a.type_name=b.name limit 1)b on 1=1
38
+ where single_type_value_id not in (select field_id from site.content_data) and content_type_id is not null
39
+ and case when key = 'slug' then value not in ( select field_value from site.content_data where field_key='slug') else true end;
40
+ -- drop table if exists site.single_type_values cascade;
41
+ */
42
+ -- seamless migration finish
43
+
44
+ --- insert default content types start
45
+ insert into site.content_types(content_type_id,name,title,type,columns)
46
+ values('pages','pages','Сторінки','collection',
47
+ '[{"name":"slug","label":"Slug","type":"slug","parent":"title","required":true},{"name":"title","label":"Title","type":"text","required":true},{"name":"status","label":"Status","type":"select","options":[{"id":"draft","text":"Draft"},{"id":"published","text":"Published"},{"id":"archived","text":"Archived"},{"id":"delayPublished","text":"Delay Published"}],"required":true},{"name":"published_at","label":"Publish at","type":"datetime","required":true}]'::json
48
+ ) on conflict(content_type_id) do update set columns=excluded.columns, type = excluded.type;
49
+ insert into site.contents(content_id,content_type_id,space_id)
50
+ values('pages','pages','default') on conflict(content_id) do nothing;
51
+ --- insert default content types finish
52
+
53
+ alter table site.contents add column if not exists meta json;
54
+ alter table site.contents add column if not exists main_image text;
55
+ /*
56
+ ALTER TABLE if exists site.contents drop column if exists publish_at;
57
+ ALTER TABLE if exists site.contents add column if not exists published_at timestamp without time zone;
58
+ update site.contents set published_at=now() where published_at is null;
59
+ ALTER TABLE if exists site.contents alter column published_at set not null;
60
+ ALTER TABLE if exists site.contents alter column published_at set default now();
61
+ */
62
+
63
+ alter table site.content_data add column if not exists created_by text;
64
+ alter table site.content_data add column if not exists updated_by text;
65
+ alter table site.content_data add column if not exists field_value_object json;
66
+
67
+ --- seamless migration for content_data start
68
+ update site.content_data
69
+ set field_value_object=field_value::json
70
+ where field_value_object is null
71
+ and field_value is not null
72
+ and field_type in ('reference-list', 'reference', 'datalist', 'filelist', 'array');
73
+ --- seamless migration for content_data finish
74
+
75
+
76
+ -- delete accidental dupes
77
+ delete from site.content_data where object_id in ( select min(object_id) from site.content_data where field_key= 'slug' group by field_value having count(*) > 1 );
78
+
79
+ alter table site.content_types ADD COLUMN if not exists preview_path text;
80
+
81
+ -- get rid of naming inconsistency
82
+ update site.content_types
83
+ set columns=replace(columns::text,'publish_at', 'published_at')::json
84
+ where columns::text like '%publish_at%';
85
+
86
+ ALTER TABLE site.contents DROP CONSTRAINT contents_status_check;
87
+ ALTER TABLE site.contents ADD CONSTRAINT contents_status_check CHECK (status = ANY (ARRAY['draft'::text, 'published'::text, 'archived'::text, 'delayPublished'::text]));
88
+
89
+ UPDATE site.content_types
90
+ SET columns = (
91
+ SELECT jsonb_agg(elem)
92
+ FROM jsonb_array_elements(columns::jsonb) elem
93
+ WHERE elem->>'name' <> 'status'
94
+ )
95
+ WHERE columns IS NOT NULL;
96
+
97
+
98
+ UPDATE site.content_types
99
+ SET columns = columns::jsonb || '{"name": "status", "type": "select", "label": "Status", "options": [{"id": "draft", "text": "Draft"}, {"id": "published", "text": "Published"}, {"id": "archived", "text": "Archived"}, {"id": "delayPublished", "text": "Delay Published"}], "required": true}
100
+ '::jsonb
101
+ WHERE columns is not null;
102
+
103
+ -- remove dupes, make slug unique
104
+ update site.content_data set content_id='pages' where content_id<>'pages' and content_id in (
105
+ select content_id from site.contents where content_id<>'pages' and slug='pages'
106
+ );
107
+
108
+ delete from site.contents where content_id<>'pages' and slug='pages';
109
+
110
+ delete from site.contents a
111
+ where slug in (select slug from site.contents group by slug having count(*) > 1)
112
+ and (select count(*) from site.content_data where content_id=a.content_id) = 0;
113
+
114
+ alter table site.contents drop constraint if exists site_contents_slug_unique;
115
+ alter table site.contents add constraint site_contents_slug_unique UNIQUE (slug);
116
+
117
+ alter table site.contents add column if not exists space_id text;
118
+ alter table site.contents alter column space_id drop not null;
119
+ update site.spaces set locales='{ua,en,de}'::text[] where space_id='default';
120
+
121
+ alter table site.content_data drop constraint if exists content_data_content_id_fkey;
122
+ alter table site.content_data add constraint content_data_content_id_fkey foreign key (content_id) references site.contents(content_id) on delete cascade;
123
+
124
+ alter table site.tags add column if not exists locale jsonb;