@opengis/cms 0.0.61 → 0.0.62

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 (109) hide show
  1. package/README.md +131 -131
  2. package/dist/{EditCollectionPage-3Q57ptN3.js → EditCollectionPage-C4uNmBJf.js} +1 -1
  3. package/dist/{contentForm-CLStrfSg.js → contentForm-CcFbUeal.js} +144 -146
  4. package/dist/index.js +1 -1
  5. package/dist/{vs-builder-preview-BH4VAM3a.js → vs-builder-preview-DL3RYMp7.js} +11 -12
  6. package/dist/vs-form-custom-datatable-D880w8gx.js +493 -0
  7. package/input-types.json +9 -9
  8. package/locales/en.json +815 -815
  9. package/locales/uk.json +813 -813
  10. package/module/cms/cls/content.status.json +17 -17
  11. package/module/cms/cls/user_type.json +9 -9
  12. package/module/cms/form/admin.users.form.json +77 -77
  13. package/module/cms/select/cms.page_type.sql +1 -1
  14. package/module/cms/select/news_tag_id.sql +11 -11
  15. package/module/cms/table/admin.users.table.json +53 -53
  16. package/module/cms/table/collection.default.table.json +96 -96
  17. package/module/cms/table/single.default.table.json +116 -116
  18. package/package.json +69 -69
  19. package/plugin.js +43 -43
  20. package/server/app.js +35 -35
  21. package/server/config.js +4 -4
  22. package/server/functions/getContent.js +45 -45
  23. package/server/functions/getDraftKey.js +22 -22
  24. package/server/functions/getSearchData.js +31 -31
  25. package/server/functions/getTags.js +30 -30
  26. package/server/functions/getUser.js +27 -27
  27. package/server/functions/utils/mock.reply.js +55 -55
  28. package/server/index.js +22 -22
  29. package/server/migrations/fixes.sql +132 -132
  30. package/server/migrations/site.sql +596 -596
  31. package/server/plugins/adminHook.js +78 -78
  32. package/server/plugins/hook.js +59 -59
  33. package/server/plugins/vite.js +75 -75
  34. package/server/routes/category/controllers/cms.category.delete.js +21 -21
  35. package/server/routes/category/controllers/cms.category.get.js +17 -17
  36. package/server/routes/category/controllers/cms.category.list.js +16 -16
  37. package/server/routes/category/controllers/cms.category.post.js +21 -21
  38. package/server/routes/category/controllers/cms.category.put.js +23 -23
  39. package/server/routes/category/index.mjs +22 -22
  40. package/server/routes/cms/controllers/cmsStat.js +55 -55
  41. package/server/routes/cms/controllers/cmsSuggest.js +57 -57
  42. package/server/routes/cms/controllers/deleteContent.js +113 -113
  43. package/server/routes/cms/controllers/deleteMedia.js +76 -76
  44. package/server/routes/cms/controllers/downloadMedia.js +84 -84
  45. package/server/routes/cms/controllers/getContent.js +113 -113
  46. package/server/routes/cms/controllers/getContentBySlug.js +93 -93
  47. package/server/routes/cms/controllers/insertContent.js +226 -226
  48. package/server/routes/cms/controllers/listMedia.js +155 -155
  49. package/server/routes/cms/controllers/metadataMedia.js +39 -39
  50. package/server/routes/cms/controllers/properties.get.js +18 -18
  51. package/server/routes/cms/controllers/properties.post.js +99 -99
  52. package/server/routes/cms/controllers/searchContent.js +214 -214
  53. package/server/routes/cms/controllers/translate.js +89 -89
  54. package/server/routes/cms/controllers/updateContent.js +231 -231
  55. package/server/routes/cms/controllers/uploadMedia.js +79 -79
  56. package/server/routes/cms/functions/getSettings.js +48 -48
  57. package/server/routes/cms/index.mjs +112 -112
  58. package/server/routes/cms/utils/additionalData.js +35 -35
  59. package/server/routes/cms/utils/getCollection.js +89 -89
  60. package/server/routes/cms/utils/getSingle.js +188 -188
  61. package/server/routes/cms/utils/inputTypes.js +5 -5
  62. package/server/routes/cms/utils/insertContentLocalization.js +104 -104
  63. package/server/routes/cms/utils/requestTranslation.js +135 -135
  64. package/server/routes/cms/utils/updateLocalization.js +42 -42
  65. package/server/routes/cmsSpace/controllers/deleteSpace.js +26 -26
  66. package/server/routes/cmsSpace/controllers/getSpaces.js +28 -28
  67. package/server/routes/cmsSpace/controllers/insertSpace.js +22 -22
  68. package/server/routes/cmsSpace/controllers/updateSpace.js +24 -24
  69. package/server/routes/cmsSpace/index.mjs +20 -20
  70. package/server/routes/contentType/controllers/addContentType.js +160 -160
  71. package/server/routes/contentType/controllers/contentTypeList.js +47 -47
  72. package/server/routes/contentType/controllers/delContentType.js +75 -75
  73. package/server/routes/contentType/controllers/editContentType.js +70 -70
  74. package/server/routes/contentType/controllers/getContentType.js +57 -57
  75. package/server/routes/contentType/index.mjs +35 -35
  76. package/server/routes/contentType/utils/updateContents.js +44 -44
  77. package/server/routes/contentType/utils/updateCustomContentTable.js +53 -53
  78. package/server/routes/feedback/controllers/email.list.js +24 -24
  79. package/server/routes/feedback/controllers/feedback.js +48 -48
  80. package/server/routes/feedback/controllers/feedback.list.js +37 -37
  81. package/server/routes/feedback/controllers/news.subscriptions.js +44 -44
  82. package/server/routes/feedback/index.mjs +71 -71
  83. package/server/routes/logs/controllers/export.user.logs.js +77 -77
  84. package/server/routes/logs/controllers/user.logs.js +44 -44
  85. package/server/routes/logs/index.mjs +9 -9
  86. package/server/routes/menu/controllers/addMenu.js +37 -37
  87. package/server/routes/menu/controllers/delMenu.js +31 -31
  88. package/server/routes/menu/controllers/editMenu.js +41 -41
  89. package/server/routes/menu/controllers/getMenu.js +24 -24
  90. package/server/routes/menu/functions/getMenu.js +50 -50
  91. package/server/routes/menu/index.mjs +13 -13
  92. package/server/routes/migration/controllers/collectionToCustom.js +137 -137
  93. package/server/routes/migration/index.mjs +8 -8
  94. package/server/routes/root.mjs +8 -8
  95. package/server/routes/tags/controllers/add.tags.js +24 -24
  96. package/server/routes/tags/controllers/del.tags.js +19 -19
  97. package/server/routes/tags/controllers/edit.tags.js +25 -25
  98. package/server/routes/tags/controllers/get.tags.js +15 -15
  99. package/server/routes/tags/index.mjs +14 -14
  100. package/server/templates/cls/cms.category_type.json +9 -9
  101. package/server/templates/cls/cms.content_review_status.json +9 -9
  102. package/server/templates/cls/cms.content_status.json +9 -9
  103. package/server/templates/cls/cms.content_type.json +9 -9
  104. package/server/templates/cls/cms.lang.json +9 -9
  105. package/server/templates/page/login.html +126 -126
  106. package/utils.d.ts +52 -52
  107. package/utils.js +8 -8
  108. package/dist/getField-CpwVE28P.js +0 -179
  109. package/dist/vs-form-custom-datatable-BDZo48w3.js +0 -317
@@ -1,232 +1,232 @@
1
- import { config, pgClients, dataUpdate, dataInsert, getTemplate, checkSQL, logger } from '@opengis/fastify-table/utils.js';
2
-
3
- import inputTypes from '../utils/inputTypes.js';
4
-
5
- import updateLocalization from '../utils/updateLocalization.js';
6
-
7
- const defaultColumns = [
8
- 'content_id',
9
- 'space_id',
10
- 'content_type_id',
11
- 'created_at',
12
- 'updated_at',
13
- 'published_at',
14
- 'revision',
15
- 'locale',
16
- 'status',
17
- 'slug',
18
- 'title',
19
- 'created_by',
20
- 'published_by',
21
- 'updated_by',
22
- 'meta',
23
- ];
24
-
25
- export default async function updateContent(req, reply) {
26
- const {
27
- pg = pgClients.client,
28
- params = {},
29
- user = {},
30
- body = {},
31
- headers = {},
32
- } = req;
33
-
34
- const { type, id } = params;
35
-
36
- if (!type) {
37
- return reply.status(400).send({ error: 'not enough params: type', code: 400 });
38
- }
39
-
40
- if (!id) {
41
- return reply.status(400).send({ error: 'not enough params: id', code: 400 });
42
- }
43
-
44
- if (!Object.keys(body || {}).length) {
45
- return reply.status(400).send({ error: 'empty body', code: 400 });
46
- }
47
-
48
- // order priority - custom columns -> default for pages
49
- const { ctid, ctname, dbtable, columns: contentColumns = [] } = await pg.query('select content_type_id as ctid, name as ctname, table_name as dbtable, columns from site.content_types where content_type_id in (select content_type_id from site.contents where content_id=$1) or content_type_id=$2 order by content_type_id = \'pages\'', [id, type]).then(el => el.rows?.[0] || {});
50
-
51
- const arr = pg ? await pg.query(`select array_agg(relname)::text[] from pg_class a
52
- left join pg_namespace b on a.relnamespace=b.oid
53
- where a.relam=2 and b.nspname='data'`).then(el => el.rows?.[0]?.array_agg || []) : [];
54
-
55
- if (!arr.length && type !== 'pages') {
56
- return reply.status(400).send({ error: 'empty schema: data', code: 400 });
57
- }
58
-
59
- const table = arr.find(el => el === params.type);
60
-
61
- const loadTable = type === 'pages' ? await getTemplate('table', 'single.default.table') : {};
62
- const columns = type === 'pages'
63
- ? (loadTable?.columns || []).concat((contentColumns || []).filter(col => loadTable?.columns.findIndex(el => el.name === col.name) === -1))
64
- : contentColumns;
65
-
66
- // const xss = checkXSS({ body });
67
- const sql = checkSQL({ body });
68
-
69
- if (/*xss.error ||*/ sql.error && false) {
70
- logger.file(`injection/${/*xss.error ? 'xss' : */'sql'}/cms`, {
71
- table,
72
- ...params,
73
- uid: user?.uid,
74
- ...(/*xss.error ? xss : */sql),
75
- });
76
- return reply
77
- .status(409)
78
- .send(
79
- `Дані містять заборонені ${/*xss.error ? 'xss' : */'sql'} символи. Приберіть їх та спробуйте ще раз`
80
- );
81
- }
82
-
83
- // site.content_data, includes singletone
84
- if (((!table && !dbtable) || ['single', 'pages'].includes(type)) && ctid) {
85
- const cid = await pg.query(
86
- 'select content_id from site.contents where content_type_id=$1 limit 1',
87
- [ctid],
88
- ).then(el => el.rows?.[0]?.content_id);
89
-
90
- const ctid1 = body.content_type_id || ctid;
91
-
92
- if (!cid) {
93
- return reply.status(404).send({ error: 'contents not found', code: 404 });
94
- }
95
-
96
- const columnList = columns?.map?.(el => el.name) || [];
97
- const types = columns?.reduce?.((acc, curr) => ({ ...acc, [curr.name]: curr.type || 'text' }), {}) || {};
98
- const keys = Object.keys(body || {}).filter(key => columnList.includes(key) && !defaultColumns.includes(key));
99
-
100
-
101
- if (!Object.keys(body || {}).length) {
102
- return reply.status(400).send({ error: 'invalid payload', code: 400 });
103
- }
104
-
105
- const blocks = await pg.query(`select json_object_agg(field_key,field_value) from site.content_data where content_id=$1 and field_type='reference'`, [id])
106
- .then(el => el.rows?.[0]?.json_object_agg || {});
107
-
108
- const emptyBlock = Object.keys(body).find(key => blocks[key] && (!body[key] || typeof body[key] !== 'object' || Object.keys(body[key] || {}) === 0));
109
-
110
- if (emptyBlock) {
111
- return reply.status(400).send({ error: 'access restricted: empty/invalid block ' + emptyBlock, code: 400 });
112
- }
113
-
114
- const client = await pg.connect();
115
-
116
- try {
117
- await client.query('begin');
118
-
119
- const res = {};
120
-
121
- const res1 = await dataUpdate({
122
- pg: client,
123
- table: 'site.contents',
124
- id,
125
- data: { ...body, content_type_id: ctid1 },
126
- uid: user?.uid,
127
- });
128
- Object.assign(res, res1);
129
-
130
- const objectId = (ctname === 'pages' || ['single', 'pages'].includes(type)) && id ? id : cid;
131
- await client.query(`delete from site.content_data where object_id=$1`, [objectId]);
132
- await Promise.all(keys.map(async key => dataInsert({
133
- pg: client,
134
- table: 'site.content_data',
135
- data: {
136
- field_key: key,
137
- content_id: objectId,
138
- object_id: objectId,
139
- field_type: types[key] || 'text',
140
- field_value: inputTypes[types[key] || ''] === 'json' || key === 'meta' ? undefined : body[key],
141
- field_value_object: inputTypes[types[key] || ''] === 'json' || key === 'meta' ? body[key] : undefined,
142
- },
143
- uid: user?.uid,
144
- })));
145
-
146
- // if pages allow localization, update localization
147
- const localeRes = await updateLocalization(client, id, body, type === 'pages' ? type : ctid1, user?.uid);
148
-
149
- if (Object.hasOwn(body, 'tag_list')) {
150
- await client.query('delete from site.tag_data where data_id=$1', [id]);
151
- if (body.tag_list?.length) {
152
- await Promise.all(body.tag_list.map(async tag => dataInsert({
153
- pg: client,
154
- table: 'site.tag_data',
155
- data: {
156
- tag_id: tag,
157
- data_id: id,
158
- },
159
- uid: user?.uid,
160
- })));
161
- Object.assign(res, { tag_list: body.tag_list });
162
- }
163
- }
164
-
165
- await client.query('commit');
166
-
167
- return {
168
- id, ...res || {}, ...(keys || []).reduce((acc, curr) => ({ ...acc, [curr]: body[curr] }), {}), ...(localeRes || {})
169
- };
170
- } catch (err) {
171
- await client.query('rollback');
172
- return reply.status(500).send({ error: err.toString(), code: 500 });
173
- } finally {
174
- client.release();
175
- }
176
- }
177
-
178
- if (!table && !dbtable) {
179
- return reply.status(400).send({ error: 'invalid params: type', code: 400 });
180
- }
181
-
182
- const client = await pg.connect();
183
-
184
- try {
185
- await client.query('begin');
186
-
187
- const result = await dataUpdate({
188
- pg: client,
189
- id,
190
- table: 'data.' + `"${(table || dbtable)}"`,
191
- data: body,
192
- referer: headers?.referer,
193
- uid: user?.uid,
194
- }).catch(err => {
195
- if (err.message?.includes?.('unique constraint')) {
196
- throw new Error('Порушенні унікальності: ' + err.message?.match?.(/([^"]+)/g)?.[1]);
197
- }
198
- throw err;
199
- });
200
-
201
- await updateLocalization(client, result?.id, body, ctid, user?.uid);
202
-
203
- if (Object.hasOwn(body, 'tag_list')) {
204
- await client.query('delete from site.tag_data where data_id=$1', [id]);
205
- if (body.tag_list?.length) {
206
- await Promise.all(body.tag_list.map(async tag => dataInsert({
207
- pg: client,
208
- table: 'site.tag_data',
209
- data: {
210
- tag_id: tag?.id,
211
- data_id: id,
212
- },
213
- uid: user?.uid,
214
- })));
215
- Object.assign(result, { tag_list: body.tag_list });
216
- }
217
- }
218
-
219
- await client.query('commit');
220
-
221
- if (!result?.id) {
222
- return reply.status(404).send({ error: 'content not found', code: 404 });
223
- }
224
-
225
- return reply.status(200).send(result);
226
- } catch (err) {
227
- await client.query('rollback');
228
- return reply.status(500).send({ error: err.toString(), code: 500 });
229
- } finally {
230
- client.release();
231
- }
1
+ import { config, pgClients, dataUpdate, dataInsert, getTemplate, checkSQL, logger } from '@opengis/fastify-table/utils.js';
2
+
3
+ import inputTypes from '../utils/inputTypes.js';
4
+
5
+ import updateLocalization from '../utils/updateLocalization.js';
6
+
7
+ const defaultColumns = [
8
+ 'content_id',
9
+ 'space_id',
10
+ 'content_type_id',
11
+ 'created_at',
12
+ 'updated_at',
13
+ 'published_at',
14
+ 'revision',
15
+ 'locale',
16
+ 'status',
17
+ 'slug',
18
+ 'title',
19
+ 'created_by',
20
+ 'published_by',
21
+ 'updated_by',
22
+ 'meta',
23
+ ];
24
+
25
+ export default async function updateContent(req, reply) {
26
+ const {
27
+ pg = pgClients.client,
28
+ params = {},
29
+ user = {},
30
+ body = {},
31
+ headers = {},
32
+ } = req;
33
+
34
+ const { type, id } = params;
35
+
36
+ if (!type) {
37
+ return reply.status(400).send({ error: 'not enough params: type', code: 400 });
38
+ }
39
+
40
+ if (!id) {
41
+ return reply.status(400).send({ error: 'not enough params: id', code: 400 });
42
+ }
43
+
44
+ if (!Object.keys(body || {}).length) {
45
+ return reply.status(400).send({ error: 'empty body', code: 400 });
46
+ }
47
+
48
+ // order priority - custom columns -> default for pages
49
+ const { ctid, ctname, dbtable, columns: contentColumns = [] } = await pg.query('select content_type_id as ctid, name as ctname, table_name as dbtable, columns from site.content_types where content_type_id in (select content_type_id from site.contents where content_id=$1) or content_type_id=$2 order by content_type_id = \'pages\'', [id, type]).then(el => el.rows?.[0] || {});
50
+
51
+ const arr = pg ? await pg.query(`select array_agg(relname)::text[] from pg_class a
52
+ left join pg_namespace b on a.relnamespace=b.oid
53
+ where a.relam=2 and b.nspname='data'`).then(el => el.rows?.[0]?.array_agg || []) : [];
54
+
55
+ if (!arr.length && type !== 'pages') {
56
+ return reply.status(400).send({ error: 'empty schema: data', code: 400 });
57
+ }
58
+
59
+ const table = arr.find(el => el === params.type);
60
+
61
+ const loadTable = type === 'pages' ? await getTemplate('table', 'single.default.table') : {};
62
+ const columns = type === 'pages'
63
+ ? (loadTable?.columns || []).concat((contentColumns || []).filter(col => loadTable?.columns.findIndex(el => el.name === col.name) === -1))
64
+ : contentColumns;
65
+
66
+ // const xss = checkXSS({ body });
67
+ const sql = checkSQL({ body });
68
+
69
+ if (/*xss.error ||*/ sql.error && false) {
70
+ logger.file(`injection/${/*xss.error ? 'xss' : */'sql'}/cms`, {
71
+ table,
72
+ ...params,
73
+ uid: user?.uid,
74
+ ...(/*xss.error ? xss : */sql),
75
+ });
76
+ return reply
77
+ .status(409)
78
+ .send(
79
+ `Дані містять заборонені ${/*xss.error ? 'xss' : */'sql'} символи. Приберіть їх та спробуйте ще раз`
80
+ );
81
+ }
82
+
83
+ // site.content_data, includes singletone
84
+ if (((!table && !dbtable) || ['single', 'pages'].includes(type)) && ctid) {
85
+ const cid = await pg.query(
86
+ 'select content_id from site.contents where content_type_id=$1 limit 1',
87
+ [ctid],
88
+ ).then(el => el.rows?.[0]?.content_id);
89
+
90
+ const ctid1 = body.content_type_id || ctid;
91
+
92
+ if (!cid) {
93
+ return reply.status(404).send({ error: 'contents not found', code: 404 });
94
+ }
95
+
96
+ const columnList = columns?.map?.(el => el.name) || [];
97
+ const types = columns?.reduce?.((acc, curr) => ({ ...acc, [curr.name]: curr.type || 'text' }), {}) || {};
98
+ const keys = Object.keys(body || {}).filter(key => columnList.includes(key) && !defaultColumns.includes(key));
99
+
100
+
101
+ if (!Object.keys(body || {}).length) {
102
+ return reply.status(400).send({ error: 'invalid payload', code: 400 });
103
+ }
104
+
105
+ const blocks = await pg.query(`select json_object_agg(field_key,field_value) from site.content_data where content_id=$1 and field_type='reference'`, [id])
106
+ .then(el => el.rows?.[0]?.json_object_agg || {});
107
+
108
+ const emptyBlock = Object.keys(body).find(key => blocks[key] && (!body[key] || typeof body[key] !== 'object' || Object.keys(body[key] || {}) === 0));
109
+
110
+ if (emptyBlock) {
111
+ return reply.status(400).send({ error: 'access restricted: empty/invalid block ' + emptyBlock, code: 400 });
112
+ }
113
+
114
+ const client = await pg.connect();
115
+
116
+ try {
117
+ await client.query('begin');
118
+
119
+ const res = {};
120
+
121
+ const res1 = await dataUpdate({
122
+ pg: client,
123
+ table: 'site.contents',
124
+ id,
125
+ data: { ...body, content_type_id: ctid1 },
126
+ uid: user?.uid,
127
+ });
128
+ Object.assign(res, res1);
129
+
130
+ const objectId = (ctname === 'pages' || ['single', 'pages'].includes(type)) && id ? id : cid;
131
+ await client.query(`delete from site.content_data where object_id=$1`, [objectId]);
132
+ await Promise.all(keys.map(async key => dataInsert({
133
+ pg: client,
134
+ table: 'site.content_data',
135
+ data: {
136
+ field_key: key,
137
+ content_id: objectId,
138
+ object_id: objectId,
139
+ field_type: types[key] || 'text',
140
+ field_value: inputTypes[types[key] || ''] === 'json' || key === 'meta' ? undefined : body[key],
141
+ field_value_object: inputTypes[types[key] || ''] === 'json' || key === 'meta' ? body[key] : undefined,
142
+ },
143
+ uid: user?.uid,
144
+ })));
145
+
146
+ // if pages allow localization, update localization
147
+ const localeRes = await updateLocalization(client, id, body, type === 'pages' ? type : ctid1, user?.uid);
148
+
149
+ if (Object.hasOwn(body, 'tag_list')) {
150
+ await client.query('delete from site.tag_data where data_id=$1', [id]);
151
+ if (body.tag_list?.length) {
152
+ await Promise.all(body.tag_list.map(async tag => dataInsert({
153
+ pg: client,
154
+ table: 'site.tag_data',
155
+ data: {
156
+ tag_id: tag,
157
+ data_id: id,
158
+ },
159
+ uid: user?.uid,
160
+ })));
161
+ Object.assign(res, { tag_list: body.tag_list });
162
+ }
163
+ }
164
+
165
+ await client.query('commit');
166
+
167
+ return {
168
+ id, ...res || {}, ...(keys || []).reduce((acc, curr) => ({ ...acc, [curr]: body[curr] }), {}), ...(localeRes || {})
169
+ };
170
+ } catch (err) {
171
+ await client.query('rollback');
172
+ return reply.status(500).send({ error: err.toString(), code: 500 });
173
+ } finally {
174
+ client.release();
175
+ }
176
+ }
177
+
178
+ if (!table && !dbtable) {
179
+ return reply.status(400).send({ error: 'invalid params: type', code: 400 });
180
+ }
181
+
182
+ const client = await pg.connect();
183
+
184
+ try {
185
+ await client.query('begin');
186
+
187
+ const result = await dataUpdate({
188
+ pg: client,
189
+ id,
190
+ table: 'data.' + `"${(table || dbtable)}"`,
191
+ data: body,
192
+ referer: headers?.referer,
193
+ uid: user?.uid,
194
+ }).catch(err => {
195
+ if (err.message?.includes?.('unique constraint')) {
196
+ throw new Error('Порушенні унікальності: ' + err.message?.match?.(/([^"]+)/g)?.[1]);
197
+ }
198
+ throw err;
199
+ });
200
+
201
+ await updateLocalization(client, result?.id, body, ctid, user?.uid);
202
+
203
+ if (Object.hasOwn(body, 'tag_list')) {
204
+ await client.query('delete from site.tag_data where data_id=$1', [id]);
205
+ if (body.tag_list?.length) {
206
+ await Promise.all(body.tag_list.map(async tag => dataInsert({
207
+ pg: client,
208
+ table: 'site.tag_data',
209
+ data: {
210
+ tag_id: tag?.id,
211
+ data_id: id,
212
+ },
213
+ uid: user?.uid,
214
+ })));
215
+ Object.assign(result, { tag_list: body.tag_list });
216
+ }
217
+ }
218
+
219
+ await client.query('commit');
220
+
221
+ if (!result?.id) {
222
+ return reply.status(404).send({ error: 'content not found', code: 404 });
223
+ }
224
+
225
+ return reply.status(200).send(result);
226
+ } catch (err) {
227
+ await client.query('rollback');
228
+ return reply.status(500).send({ error: err.toString(), code: 500 });
229
+ } finally {
230
+ client.release();
231
+ }
232
232
  }
@@ -1,80 +1,80 @@
1
- import path from 'node:path';
2
- import { mkdir } from 'node:fs/promises';
3
-
4
- import { uploadMultiPart, config, getFolder, dataInsert, pgClients } from "@opengis/fastify-table/utils.js";
5
-
6
- // path.resolve() converts POSIX paths from getFolder to valid Windows paths (Bun/Node fs require this on Windows)
7
- const rootDir = path.resolve(getFolder(config, 'local'));
8
- const dir = '/files';
9
-
10
- export default async function uploadMedia(req, reply) {
11
- const { pg = pgClients.client, user = {}, query = {} } = req;
12
-
13
- if (!pg?.pk?.['site.media']) {
14
- return reply.status(404).send('table not found');
15
- }
16
-
17
- if (query.subdir && (typeof query.subdir !== 'string' || query.subdir.includes('..'))) {
18
- return reply.status(403).send('invalid query params: subdir');
19
- }
20
-
21
- // upload assets
22
- if (req.headers['content-type']?.split?.(';')?.shift?.() === 'multipart/form-data') {
23
- const file = await uploadMultiPart(req, { subdir: query.subdir || '', originalFilename: true }).catch(err => {
24
- if (err.message === 'file with specified name already exists in directory') {
25
- err.message = 'Файл з вказаною назвою вже існує';
26
- err.statusCode = 400;
27
- }
28
- throw err;
29
- });
30
-
31
- const { originalFilename: filename, filetype, mimetype } = file;
32
- const relpath = path.join(dir, query.subdir || '', file.originalFilename).replace(/\\/g, '/');
33
-
34
- const id = await dataInsert({
35
- pg,
36
- table: 'site.media',
37
- data: {
38
- filename,
39
- filetype,
40
- subdir: query.subdir,
41
- url: relpath,
42
- mime: mimetype,
43
- filesize: file.size,
44
- },
45
- uid: user?.uid,
46
- }).then(el => el?.rows?.[0]?.media_id);
47
-
48
- return reply.status(200).send({
49
- res: 'ok',
50
- name: filename,
51
- type: 'file',
52
- mimetype,
53
- result: {
54
- file_id: id,
55
- format: file.extension,
56
- size: file.size,
57
- // entity_id: resultInsert?.entity_id,
58
- file_path: relpath,
59
- file_name: filename,
60
- dir: path.dirname(relpath).replace(/\\/g, '/'),
61
- native_file_name: filename,
62
- },
63
- });
64
- }
65
-
66
- if (!query.subdir) {
67
- return reply.status(400).send('not enough query params: subdir');
68
- }
69
-
70
- // create directory
71
- const relpath = path.join(dir, query.subdir).replace(/\\/g, '/');
72
- const dirpath = path.join(rootDir, relpath);
73
- await mkdir(dirpath, { recursive: true });
74
-
75
- return reply.status(200).send({
76
- relpath,
77
- dirname: path.basename(query.subdir),
78
- type: 'dir',
79
- });
1
+ import path from 'node:path';
2
+ import { mkdir } from 'node:fs/promises';
3
+
4
+ import { uploadMultiPart, config, getFolder, dataInsert, pgClients } from "@opengis/fastify-table/utils.js";
5
+
6
+ // path.resolve() converts POSIX paths from getFolder to valid Windows paths (Bun/Node fs require this on Windows)
7
+ const rootDir = path.resolve(getFolder(config, 'local'));
8
+ const dir = '/files';
9
+
10
+ export default async function uploadMedia(req, reply) {
11
+ const { pg = pgClients.client, user = {}, query = {} } = req;
12
+
13
+ if (!pg?.pk?.['site.media']) {
14
+ return reply.status(404).send('table not found');
15
+ }
16
+
17
+ if (query.subdir && (typeof query.subdir !== 'string' || query.subdir.includes('..'))) {
18
+ return reply.status(403).send('invalid query params: subdir');
19
+ }
20
+
21
+ // upload assets
22
+ if (req.headers['content-type']?.split?.(';')?.shift?.() === 'multipart/form-data') {
23
+ const file = await uploadMultiPart(req, { subdir: query.subdir || '', originalFilename: true }).catch(err => {
24
+ if (err.message === 'file with specified name already exists in directory') {
25
+ err.message = 'Файл з вказаною назвою вже існує';
26
+ err.statusCode = 400;
27
+ }
28
+ throw err;
29
+ });
30
+
31
+ const { originalFilename: filename, filetype, mimetype } = file;
32
+ const relpath = path.join(dir, query.subdir || '', file.originalFilename).replace(/\\/g, '/');
33
+
34
+ const id = await dataInsert({
35
+ pg,
36
+ table: 'site.media',
37
+ data: {
38
+ filename,
39
+ filetype,
40
+ subdir: query.subdir,
41
+ url: relpath,
42
+ mime: mimetype,
43
+ filesize: file.size,
44
+ },
45
+ uid: user?.uid,
46
+ }).then(el => el?.rows?.[0]?.media_id);
47
+
48
+ return reply.status(200).send({
49
+ res: 'ok',
50
+ name: filename,
51
+ type: 'file',
52
+ mimetype,
53
+ result: {
54
+ file_id: id,
55
+ format: file.extension,
56
+ size: file.size,
57
+ // entity_id: resultInsert?.entity_id,
58
+ file_path: relpath,
59
+ file_name: filename,
60
+ dir: path.dirname(relpath).replace(/\\/g, '/'),
61
+ native_file_name: filename,
62
+ },
63
+ });
64
+ }
65
+
66
+ if (!query.subdir) {
67
+ return reply.status(400).send('not enough query params: subdir');
68
+ }
69
+
70
+ // create directory
71
+ const relpath = path.join(dir, query.subdir).replace(/\\/g, '/');
72
+ const dirpath = path.join(rootDir, relpath);
73
+ await mkdir(dirpath, { recursive: true });
74
+
75
+ return reply.status(200).send({
76
+ relpath,
77
+ dirname: path.basename(query.subdir),
78
+ type: 'dir',
79
+ });
80
80
  }