@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
@@ -1,69 +1,219 @@
1
- import { config, pgClients, dataInsert } from '@opengis/fastify-table/utils.js';
2
-
3
- import builderCache from '../../contentType/utils/builderCache.js';
4
-
5
- export default async function insertContent(req, reply) {
6
- const {
7
- pg = pgClients.client,
8
- params = {},
9
- user = {},
10
- body = {},
11
- headers = {},
12
- } = req;
13
-
14
- const { type, id = body?.id } = params;
15
-
16
- if (!type) {
17
- return reply.status(400).send('not enough params: type');
18
- }
19
-
20
- if (builderCache[type]?.table && builderCache[type]?.provider === 'pg' && config.pg) {
21
- const { rows = [] } = await dataInsert({
22
- pg,
23
- id,
24
- table: 'site.' + builderCache[type]?.table,
25
- data: body,
26
- referer: headers?.referer,
27
- uid: user?.uid || 0,
28
- }).catch(err => {
29
- if (err.message?.includes?.('unique constraint')) {
30
- return reply.status(400).send('Порушенні унікальності: ' + err.message?.match?.(/([^"]+)/g)?.[1]);
31
- }
32
- return reply.status(500).send(err.toString());
33
- });
34
-
35
- const pk = pg.pk?.['site.' + builderCache[type]?.table];
36
- return reply.status(200).send({ id: rows[0]?.[pk], rows });
37
- }
38
-
39
- const arr = config.pg ? await pg.query(`select array_agg(relname)::text[] from pg_class a
40
- left join pg_namespace b on a.relnamespace=b.oid
41
- where a.relam=2 and b.nspname='site'`).then(el => el.rows?.[0]?.array_agg || []) : [];
42
-
43
- if (!arr.length) {
44
- return reply.status(400).send('empty schema: site');
45
- }
46
-
47
- const table = arr.find(el => el === params.type);
48
-
49
- if (!table) {
50
- return reply.status(400).send('invalid params: type');
51
- }
52
-
53
- const { rows = [] } = await dataInsert({
54
- pg,
55
- id,
56
- table: 'site.' + table,
57
- data: body,
58
- referer: headers?.referer,
59
- uid: user?.uid || 0,
60
- }).catch(err => {
61
- if (err.message?.includes?.('unique constraint')) {
62
- return reply.status(400).send('Порушенні унікальності: ' + err.message?.match?.(/([^"]+)/g)?.[1]);
63
- }
64
- return reply.status(500).send(err.toString());
65
- });
66
-
67
- const pk = pg.pk?.['site.' + table];
68
- return reply.status(200).send({ id: rows[0]?.[pk], rows });
1
+ import { config, checkSQL, getTemplate, pgClients, dataInsert, logger } from '@opengis/fastify-table/utils.js';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+
4
+ const inputTypes = existsSync('input-types.json') ? JSON.parse(readFileSync('input-types.json') || '{}') : {};
5
+
6
+ import updateLocalization from '../utils/updateLocalization.js';
7
+
8
+ const defaultColumns = [
9
+ 'content_id',
10
+ 'space_id',
11
+ 'content_type_id',
12
+ 'created_at',
13
+ 'updated_at',
14
+ 'published_at',
15
+ 'revision',
16
+ 'locale',
17
+ 'status',
18
+ 'slug',
19
+ 'title',
20
+ 'created_by',
21
+ 'published_by',
22
+ 'updated_by',
23
+ 'meta',
24
+ ];
25
+
26
+ export default async function insertContent(req, reply) {
27
+ const {
28
+ pg = pgClients.client,
29
+ params = {},
30
+ user = {},
31
+ body = {},
32
+ headers = {},
33
+ } = req;
34
+
35
+ const { type, id = body?.id } = params;
36
+
37
+ if (!type) {
38
+ return reply.status(400).send('not enough params: type');
39
+ }
40
+
41
+ const arr = config.pg ? await pg.query(`select array_agg(relname)::text[] from pg_class a
42
+ left join pg_namespace b on a.relnamespace=b.oid
43
+ where a.relam=2 and b.nspname='data'`).then(el => el.rows?.[0]?.array_agg || []) : [];
44
+
45
+ if (!arr.length) {
46
+ return reply.status(400).send('empty schema: data');
47
+ }
48
+
49
+ const table = arr.find(el => el === params.type);
50
+
51
+ const { content_id: contentId, type: contentType } = await pg.query(
52
+ 'select content_id, (select type from site.content_types where content_type_id=t.content_type_id) from site.contents t where slug=$1',
53
+ [['single', 'pages'].includes(type) ? id : type],
54
+ ).then(el => el.rows?.[0] || {});
55
+
56
+ const ctypeId = contentId && contentId !== 'pages' && contentType === 'single' ? await pg.query(
57
+ 'select content_type_id from site.contents where content_id=$1',
58
+ [contentId],
59
+ ).then(el => el.rows?.[0]?.content_type_id) : null;
60
+
61
+ const { ctid, dbtable, columns: contentColumns } = await pg.query(
62
+ 'select content_type_id as ctid, table_name as dbtable, columns from site.content_types where $1 in (content_type_id, name)',
63
+ [type === 'single' && id ? id : (ctypeId || type)],
64
+ ).then(el => el.rows?.[0] || {});
65
+
66
+ const loadTable = (ctypeId || type) === 'pages' ? await getTemplate('table', 'single.default.table') : {};
67
+
68
+ const columns = (ctypeId || type) === 'pages'
69
+ ? (loadTable?.columns || []).concat(contentColumns.filter(col => loadTable?.columns.findIndex(el => el.name === col.name) === -1))
70
+ : contentColumns;
71
+
72
+ // site.content_data, includes singletone
73
+ if (((!table && !dbtable) || type === 'pages')) {
74
+ const cid = await pg.query(
75
+ 'select content_id from site.contents where content_type_id=$1 limit 1',
76
+ [ctid || 'pages'],
77
+ ).then(el => el.rows?.[0]?.content_id);
78
+
79
+ const ctid1 = body.content_type_id || ctid || 'pages';
80
+
81
+ if (!cid) {
82
+ return reply.status(404).send('contents not found');
83
+ }
84
+
85
+ const columnList = columns?.map?.(el => el.name) || [];
86
+ const types = columns?.reduce?.((acc, curr) => ({ ...acc, [curr.name]: curr.type || 'text' }), {}) || {};
87
+ const keys = Object.keys(body || {}).filter(key => columnList.includes(key) && !defaultColumns.includes(key));
88
+
89
+ if (!Object.keys(body || {}).length) {
90
+ return reply.status(400).send('invalid payload');
91
+ }
92
+
93
+ // const xss = checkXSS({ body });
94
+ const sql = checkSQL({ body });
95
+
96
+ if (/*xss.error ||*/ sql.error && false) {
97
+ logger.file(`injection/${/*xss.error ? 'xss' : */'sql'}/cms`, {
98
+ table,
99
+ ...params,
100
+ uid: user?.uid,
101
+ ...(/*xss.error ? xss : */sql),
102
+ });
103
+ return reply
104
+ .status(409)
105
+ .send(
106
+ `Дані містять заборонені ${/*xss.error ? 'xss' : */'sql'} символи. Приберіть їх та спробуйте ще раз`
107
+ );
108
+ }
109
+
110
+ const client = await pg.connect();
111
+
112
+ try {
113
+ await client.query('begin');
114
+ const res = await dataInsert({
115
+ pg: client,
116
+ table: 'site.contents',
117
+ id,
118
+ data: { ...body, content_type_id: ctid1 },
119
+ uid: user?.uid,
120
+ }).then(el => el.rows?.[0] || {});
121
+
122
+ if (!res?.content_id) throw new Error('insert contents error');
123
+
124
+ await Promise.all(keys.map(async key => dataInsert({
125
+ pg: client,
126
+ table: 'site.content_data',
127
+ data: {
128
+ field_key: key,
129
+ content_id: res.content_id,
130
+ object_id: res.content_id,
131
+ field_type: types[key] || 'text',
132
+ field_value: inputTypes[types[key] || ''] === 'json' ? undefined : body[key],
133
+ field_value_object: inputTypes[types[key] || ''] === 'json' ? body[key] : undefined,
134
+ },
135
+ uid: user?.uid,
136
+ })));
137
+
138
+ await updateLocalization(client, res.content_id, body, ctid1, user?.uid);
139
+
140
+ if (body?.tag_list?.length) {
141
+ await Promise.all(body.tag_list.map(async tag => dataInsert({
142
+ pg: client,
143
+ table: 'site.tag_data',
144
+ data: {
145
+ tag_id: tag,
146
+ data_id: id,
147
+ },
148
+ uid: user?.uid,
149
+ })));
150
+ }
151
+
152
+ await client.query('commit');
153
+
154
+ return {
155
+ id: res.content_id, rows: [res].filter(Boolean)
156
+ };
157
+ } catch (err) {
158
+ await client.query('rollback');
159
+ return reply.status(500).send(err.toString());
160
+ } finally {
161
+ client.release();
162
+ }
163
+ }
164
+
165
+ // custom table
166
+ if (!table && !dbtable) {
167
+ return reply.status(400).send('invalid params: type');
168
+ }
169
+
170
+ const client = await pg.connect();
171
+
172
+ try {
173
+ await client.query('begin');
174
+
175
+ // const types = columns?.reduce?.((acc, curr) => ({ ...acc, [curr.name]: inputTypes[curr.type] || 'text' }), {}) || {};
176
+ const { rows = [] } = await dataInsert({
177
+ pg: client,
178
+ id,
179
+ table: 'data.' + `"${(table || dbtable)}"`,
180
+ data: body,
181
+ referer: headers?.referer,
182
+ uid: user?.uid,
183
+ }).catch(err => {
184
+ if (err.message?.includes?.('unique constraint')) {
185
+ throw new Error('Порушенні унікальності: ' + err.message?.match?.(/([^']+)/g)?.[1]);
186
+ }
187
+ throw err;
188
+ }) || {};
189
+
190
+ const idRes = rows?.[0]?.id;
191
+
192
+ if (!idRes) {
193
+ throw new Error('content insert error');
194
+ }
195
+
196
+ await updateLocalization(client, idRes, body, ctid, user?.uid);
197
+
198
+ if (body?.tag_list?.length) {
199
+ await Promise.all(body.tag_list.map(async tag => dataInsert({
200
+ pg: client,
201
+ table: 'site.tag_data',
202
+ data: {
203
+ tag_id: tag,
204
+ data_id: id || idRes,
205
+ },
206
+ uid: user?.uid,
207
+ })));
208
+ }
209
+
210
+ await client.query('commit');
211
+
212
+ return reply.status(200).send({ id: idRes, rows });
213
+ } catch (err) {
214
+ await client.query('rollback');
215
+ return reply.status(500).send(err.toString());
216
+ } finally {
217
+ client.release();
218
+ }
69
219
  }
@@ -1,73 +1,94 @@
1
- import path from 'node:path';
2
- import { existsSync, mkdirSync } from 'node:fs';
3
- import { readdir, stat } from 'node:fs/promises';
4
-
5
- import { config, getFolder, pgClients } from '@opengis/fastify-table/utils.js';
6
-
7
- const rootDir = getFolder(config, 'local');
8
- mkdirSync(path.join(rootDir, 'media'), { recursive: true });
9
-
10
- export default async function listMedia(req, reply) {
11
- const { pg = pgClients.client, query = {} } = req;
12
- const { subdir = '' } = query;
13
-
14
- if (!pg.pk?.['site.media']) {
15
- return reply.status(404).send('table not found');
16
- }
17
-
18
- if (typeof subdir !== 'string' || subdir.includes('..')) {
19
- return reply.status(403).send('invalid params: subdir');
20
- }
21
-
22
- const relpath = path.join('/media', subdir).replace(/\\/g, '/');
23
- const dirpath = path.join(rootDir, relpath).replace(/\\/g, '/');
24
-
25
- if (!existsSync(dirpath)) {
26
- return { data: [], relpath, msg: 'directory not exists' };
27
- }
28
-
29
- const { rows = [] } = await pg.query(
30
- `select
31
- media_id as id, filename, filetype, filesize, url, description, alt,
32
- mime, preview_url, created_at, updated_at, created_by, updated_by
33
- from site.media
34
- where url ~ $1`,
35
- [relpath],
36
- );
37
-
38
- const isDirectory = (await stat(dirpath)).isDirectory();
39
-
40
- const items = isDirectory ? await readdir(dirpath, { withFileTypes: true }) : [];
41
-
42
- const subdirs = items
43
- .filter(el => el.isDirectory())
44
- .map(el => ({ type: 'dir', name: el.name }));
45
-
46
- if (subdirs.length) {
47
- await Promise.all(subdirs.map(async (item) => {
48
- const items = isDirectory ? await readdir(path.join(dirpath, item.name)) : [];
49
- Object.assign(item, { count: items.length });
50
- }));
51
- }
52
-
53
- const result = { relpath, data: rows.concat(subdirs) };
54
-
55
- if (config.debug) {
56
- Object.assign(result, { rootDir });
57
- }
58
-
59
- if (!rows.length) {
60
- return result;
61
- }
62
-
63
- rows.forEach(row => Object.assign(row, {
64
- type: 'file',
65
- url: `${req.routeOptions.url}/${row.id}/file`,
66
- filepath: row.url,
67
- name: row.filename,
68
- metadata: `${req.routeOptions.url}/${row.id}`,
69
- exists: !!items.find(el => el.isFile() && el.name === path.basename(row.url))
70
- }));
71
-
72
- return result;
1
+ import path from 'node:path';
2
+ import { existsSync, mkdirSync } from 'node:fs';
3
+ import { readdir, stat } from 'node:fs/promises';
4
+
5
+ import { config, getFolder, pgClients, getMimeType } from '@opengis/fastify-table/utils.js';
6
+ import { createHash } from 'node:crypto';
7
+
8
+ const rootDir = getFolder(config, 'local');
9
+ const dir = '/files';
10
+
11
+ mkdirSync(path.join(rootDir, dir), { recursive: true });
12
+
13
+ export default async function listMedia(req, reply) {
14
+ const { pg = pgClients.client, query = {} } = req;
15
+ const { subdir: subdir1 = '', search } = query;
16
+
17
+ if (!pg.pk?.['site.media']) {
18
+ return reply.status(404).send('table not found');
19
+ }
20
+
21
+ if (typeof subdir1 !== 'string' || subdir1.includes('..')) {
22
+ return reply.status(403).send('invalid params: subdir');
23
+ }
24
+
25
+ const subdir = subdir1.replace(/\/{2,}/g, '/');
26
+ const relpath = path.join(dir, subdir1).replace(/\\/g, '/');
27
+ const dirpath = path.join(rootDir, relpath).replace(/\\/g, '/');
28
+
29
+ if (!existsSync(dirpath)) {
30
+ return { data: [], relpath, msg: 'directory not exists' };
31
+ }
32
+
33
+ const isDirectory = (await stat(dirpath)).isDirectory();
34
+
35
+ const allItems = isDirectory ? await readdir(dirpath, { withFileTypes: true, recursive: true }) : [];
36
+ const items = isDirectory ? await readdir(dirpath, { withFileTypes: true }) : [];
37
+
38
+ const rows = await pg.query(
39
+ `select
40
+ media_id as id, filename, filetype, filesize, url, description, alt,
41
+ mime, preview_url, created_at, updated_at, created_by, updated_by
42
+ from site.media
43
+ where ${subdir ? 'subdir = $1' : (search ? '1=1' : 'subdir is null')} ${search ? `and filename ilike '%${search.replace(/'/g, "''")}%'` : ''}`,
44
+ [subdir].filter(Boolean),
45
+ ).then(el => el.rows || []); // ?.filter(row => items.map(el => el.name).includes(row.filename))
46
+
47
+ const subdirs = items
48
+ .filter(el => el.isDirectory())
49
+ .map(el => ({ type: 'dir', name: el.name }))
50
+ .filter(el => search ? el.name.includes(search) : true);
51
+
52
+ if (subdirs.length) {
53
+ await Promise.all(subdirs.map(async (item) => {
54
+ const items = isDirectory ? await readdir(path.join(dirpath, item.name)) : [];
55
+ Object.assign(item, { count: items.length });
56
+ }));
57
+ }
58
+
59
+ const result = { relpath };
60
+
61
+ if (config.debug) {
62
+ Object.assign(result, { rootDir });
63
+ }
64
+
65
+ const files = (search ? allItems.filter(el => el.name.includes(search)) : items)
66
+ .filter(el => el.isFile())
67
+ .map(el => {
68
+ const media = rows.find(row => row.filename === el.name);
69
+ const filepath = media ? media.url : ('/files/' + (el.path.split('/files/')[1] ? el.path.split('/files/')[1] + '/' : '') + el.name);
70
+ return media ? {
71
+ type: 'file',
72
+ ...media,
73
+ url: `${req.routeOptions.url}/${media.id}/file`,
74
+ preview: `${req.routeOptions.url}/${media.id}/preview`,
75
+ filepath,
76
+ metadata: `${req.routeOptions.url}/${media.id}`,
77
+ } : {
78
+ id: createHash('md5').update(filepath).digest('hex'),
79
+ hash: true,
80
+ type: 'file',
81
+ filename: el.name,
82
+ filepath,
83
+ filetype: getMimeType(el.name)?.startsWith('image/') ? "image" : "other",
84
+ filesize: 0,
85
+ url: filepath,
86
+ mime: getMimeType(el.name),
87
+ preview: filepath,
88
+ }
89
+ });
90
+
91
+ Object.assign(result, { data: subdirs.concat(files) });
92
+ return result;
93
+
73
94
  }
@@ -1,37 +1,38 @@
1
- import path from 'node:path';
2
- import { existsSync } from 'node:fs';
3
-
4
- import { config, getFolder, pgClients } from "@opengis/fastify-table/utils.js";
5
-
6
- const rootDir = getFolder(config, 'local');
7
-
8
- export default async function metadataMedia({
9
- routeOptions = {}, pg = pgClients.client, params = {},
10
- }, reply) {
11
- if (!params?.id) {
12
- return reply.status(400).send('not enough params: id');
13
- }
14
-
15
- if (!pg.pk?.['site.media']) {
16
- return reply.status(404).send('table not found');
17
- }
18
-
19
- const data = await pg.query(
20
- 'select * from site.media where media_id = $1 and url is not null',
21
- [params.id],
22
- ).then(el => el.rows?.[0]);
23
-
24
- if (!data) {
25
- return reply.status(404).send('media not found: ' + params.id);
26
- }
27
-
28
- const filepath = path.join(rootDir, data.url);
29
-
30
- Object.assign(data, {
31
- url: `${routeOptions.url.replace(':id', params.id)}/file`,
32
- filepath: data.url,
33
- exists: existsSync(filepath),
34
- });
35
-
36
- return reply.status(200).send(data);
37
- }
1
+ import path from 'node:path';
2
+ import { existsSync } from 'node:fs';
3
+
4
+ import { config, getFolder, pgClients } from "@opengis/fastify-table/utils.js";
5
+
6
+ const rootDir = getFolder(config, 'local');
7
+
8
+ export default async function metadataMedia({
9
+ routeOptions = {}, pg = pgClients.client, params = {},
10
+ }, reply) {
11
+ if (!params?.id) {
12
+ return reply.status(400).send('not enough params: id');
13
+ }
14
+
15
+ if (!pg.pk?.['site.media']) {
16
+ return reply.status(404).send('table not found');
17
+ }
18
+
19
+ const data = await pg.query(
20
+ 'select * from site.media where media_id = $1 and url is not null',
21
+ [params.id],
22
+ ).then(el => el.rows?.[0]);
23
+
24
+ if (!data) {
25
+ return reply.status(404).send('media not found: ' + params.id);
26
+ }
27
+
28
+ const filepath = path.join(rootDir, data.url);
29
+
30
+ Object.assign(data, {
31
+ url: `${routeOptions.url.replace(':id', params.id)}/file`,
32
+ preview: `${routeOptions.url.replace(':id', params.id)}/preview`,
33
+ filepath: data.url,
34
+ exists: existsSync(filepath),
35
+ });
36
+
37
+ return reply.status(200).send(data);
38
+ }
@@ -0,0 +1,53 @@
1
+ /* eslint-disable camelcase */
2
+ import { pgClients } from '@opengis/fastify-table/utils.js';
3
+
4
+ export default async function getAppSettings(
5
+ {
6
+ pg = pgClients.client,
7
+ query,
8
+ params,
9
+ user = {},
10
+ },
11
+ reply
12
+ ) {
13
+ const t1 = Date.now();
14
+
15
+ if (!pg) {
16
+ return reply.status(500).send("empty pg");
17
+ }
18
+
19
+ if (!pg.pk?.["admin.properties"]) {
20
+ return reply.status(404).send("properties table not found");
21
+ }
22
+
23
+ if (params?.entity === "user" && !user.uid) {
24
+ return reply.status(401).send("unauthorized");
25
+ }
26
+
27
+ const { uid } = user || {};
28
+
29
+ const args =
30
+ params?.entity === "user" ? [user.uid] : [params?.entity || "app"];
31
+
32
+ const { rows = [] } = await pg.query(
33
+ "select property_key as key, property_text, property_json from admin.properties where property_entity=$1",
34
+ args
35
+ );
36
+
37
+ const settings = rows
38
+ .filter((row) =>
39
+ typeof query?.keys === "string" ? query.keys.includes(row.key) : true
40
+ )
41
+ .reduce(
42
+ (
43
+ acc,
44
+ { key, property_text, property_json }
45
+ ) => ({
46
+ ...acc,
47
+ [key]: property_text || property_json,
48
+ }),
49
+ {}
50
+ );
51
+
52
+ return reply.status(200).send({ time: Date.now() - t1, uid, settings });
53
+ }