@opengis/cms 0.0.58 → 0.0.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/README.md +131 -131
  2. package/dist/{ArticlesPage-CFjE_cw_.js → ArticlesPage-BjYzvTWM.js} +3 -3
  3. package/dist/{CollectionsBreadcrumb-BCxeRikP.js → CollectionsBreadcrumb-HePNJb-d.js} +1 -1
  4. package/dist/CollectionsBreadcrumb.vue_vue_type_script_setup_true_lang-BJh-tjam.js +53 -0
  5. package/dist/{Dashboard-C1eGscNd.js → Dashboard-CXkg_pk8.js} +132 -132
  6. package/dist/EditCollectionPage-BycuD920.js +188 -0
  7. package/dist/{MenuAddPage-D-p3gFgm.js → MenuAddPage-QTnwCoGh.js} +1 -1
  8. package/dist/{MenuBody-rN5j4YBu.js → MenuBody-Bi0ONVZf.js} +2 -2
  9. package/dist/{MenuItemPage-BoJw885D.js → MenuItemPage-B7Y9KFyb.js} +3 -3
  10. package/dist/{MenuList-DFEBS0NB.js → MenuList-BLIpeqSd.js} +53 -53
  11. package/dist/{MenuPage-BCZB_S8j.js → MenuPage-3W6jZ15H.js} +1 -1
  12. package/dist/{MenuWrapper-AZ_8s-zd.js → MenuWrapper-OrOv6sOb.js} +1 -1
  13. package/dist/{MonacoEditor-Db-3Jc3E.js → MonacoEditor-ByPT8pnv.js} +1 -1
  14. package/dist/MonacoEditor.vue_vue_type_script_setup_true_lang-C8cip9Ci.js +84 -0
  15. package/dist/{UniversalTable-CzqPG-tY.js → UniversalTable-GBd_pStq.js} +80 -80
  16. package/dist/{UniversalTablePagination-4gL47A7I.js → UniversalTablePagination-Dw2hc0nc.js} +46 -46
  17. package/dist/{contentForm-CtMhQTG0.js → contentForm-yMn63kza.js} +48 -48
  18. package/dist/index.js +5 -5
  19. package/dist/{vs-builder-monaco-B3Jj0V31.js → vs-builder-monaco-Cw-f19gc.js} +1 -1
  20. package/input-types.json +9 -9
  21. package/locales/en.json +815 -815
  22. package/locales/uk.json +813 -813
  23. package/module/cms/cls/content.status.json +17 -17
  24. package/module/cms/cls/user_type.json +9 -9
  25. package/module/cms/form/admin.users.form.json +77 -77
  26. package/module/cms/select/cms.page_type.sql +1 -1
  27. package/module/cms/select/news_tag_id.sql +11 -11
  28. package/module/cms/table/admin.users.table.json +53 -53
  29. package/module/cms/table/collection.default.table.json +96 -96
  30. package/module/cms/table/single.default.table.json +116 -116
  31. package/package.json +2 -1
  32. package/plugin.js +43 -43
  33. package/server/app.js +35 -35
  34. package/server/config.js +4 -4
  35. package/server/functions/getContent.js +45 -45
  36. package/server/functions/getDraftKey.js +22 -22
  37. package/server/functions/getSearchData.js +31 -31
  38. package/server/functions/getTags.js +30 -30
  39. package/server/functions/getUser.js +27 -27
  40. package/server/functions/utils/mock.reply.js +55 -55
  41. package/server/index.js +22 -22
  42. package/server/migrations/fixes.sql +129 -129
  43. package/server/migrations/site.sql +595 -595
  44. package/server/plugins/adminHook.js +78 -78
  45. package/server/plugins/hook.js +59 -59
  46. package/server/plugins/vite.js +75 -75
  47. package/server/routes/category/controllers/cms.category.delete.js +21 -21
  48. package/server/routes/category/controllers/cms.category.get.js +17 -17
  49. package/server/routes/category/controllers/cms.category.list.js +16 -16
  50. package/server/routes/category/controllers/cms.category.post.js +21 -21
  51. package/server/routes/category/controllers/cms.category.put.js +23 -23
  52. package/server/routes/category/index.mjs +22 -22
  53. package/server/routes/cms/controllers/cmsStat.js +55 -55
  54. package/server/routes/cms/controllers/cmsSuggest.js +57 -57
  55. package/server/routes/cms/controllers/deleteContent.js +113 -113
  56. package/server/routes/cms/controllers/deleteMedia.js +76 -76
  57. package/server/routes/cms/controllers/downloadMedia.js +84 -84
  58. package/server/routes/cms/controllers/getContent.js +113 -113
  59. package/server/routes/cms/controllers/getContentBySlug.js +93 -93
  60. package/server/routes/cms/controllers/getPermissions.js +15 -15
  61. package/server/routes/cms/controllers/insertContent.js +217 -217
  62. package/server/routes/cms/controllers/listMedia.js +155 -155
  63. package/server/routes/cms/controllers/metadataMedia.js +39 -39
  64. package/server/routes/cms/controllers/properties.get.js +18 -18
  65. package/server/routes/cms/controllers/properties.post.js +99 -99
  66. package/server/routes/cms/controllers/searchContent.js +214 -214
  67. package/server/routes/cms/controllers/setPermissions.js +49 -49
  68. package/server/routes/cms/controllers/translate.js +89 -89
  69. package/server/routes/cms/controllers/updateContent.js +266 -266
  70. package/server/routes/cms/controllers/uploadMedia.js +79 -79
  71. package/server/routes/cms/functions/getSettings.js +48 -48
  72. package/server/routes/cms/index.mjs +112 -112
  73. package/server/routes/cms/utils/additionalData.js +35 -35
  74. package/server/routes/cms/utils/getCollection.js +89 -89
  75. package/server/routes/cms/utils/getSingle.js +188 -188
  76. package/server/routes/cms/utils/inputTypes.js +5 -5
  77. package/server/routes/cms/utils/insertContentLocalization.js +104 -104
  78. package/server/routes/cms/utils/requestTranslation.js +113 -85
  79. package/server/routes/cms/utils/updateLocalization.js +47 -47
  80. package/server/routes/cmsSpace/controllers/deleteSpace.js +25 -25
  81. package/server/routes/cmsSpace/controllers/getSpaces.js +27 -27
  82. package/server/routes/cmsSpace/controllers/insertSpace.js +21 -21
  83. package/server/routes/cmsSpace/controllers/updateSpace.js +23 -23
  84. package/server/routes/cmsSpace/index.mjs +20 -20
  85. package/server/routes/contentType/controllers/addContentType.js +160 -160
  86. package/server/routes/contentType/controllers/contentTypeList.js +54 -54
  87. package/server/routes/contentType/controllers/delContentType.js +75 -75
  88. package/server/routes/contentType/controllers/editContentType.js +88 -88
  89. package/server/routes/contentType/controllers/getContentType.js +65 -65
  90. package/server/routes/contentType/index.mjs +35 -35
  91. package/server/routes/contentType/utils/updateContents.js +44 -44
  92. package/server/routes/contentType/utils/updateCustomContentTable.js +53 -53
  93. package/server/routes/feedback/controllers/email.list.js +24 -24
  94. package/server/routes/feedback/controllers/feedback.js +48 -48
  95. package/server/routes/feedback/controllers/feedback.list.js +37 -37
  96. package/server/routes/feedback/controllers/news.subscriptions.js +44 -44
  97. package/server/routes/feedback/index.mjs +71 -71
  98. package/server/routes/logs/controllers/export.user.logs.js +77 -77
  99. package/server/routes/logs/controllers/user.logs.js +44 -44
  100. package/server/routes/logs/index.mjs +9 -9
  101. package/server/routes/menu/controllers/addMenu.js +37 -37
  102. package/server/routes/menu/controllers/delMenu.js +31 -31
  103. package/server/routes/menu/controllers/editMenu.js +41 -41
  104. package/server/routes/menu/controllers/getMenu.js +24 -24
  105. package/server/routes/menu/functions/getMenu.js +50 -50
  106. package/server/routes/menu/index.mjs +13 -13
  107. package/server/routes/migration/controllers/collectionToCustom.js +137 -137
  108. package/server/routes/migration/index.mjs +8 -8
  109. package/server/routes/root.mjs +8 -8
  110. package/server/routes/tags/controllers/add.tags.js +24 -24
  111. package/server/routes/tags/controllers/del.tags.js +19 -19
  112. package/server/routes/tags/controllers/edit.tags.js +25 -25
  113. package/server/routes/tags/controllers/get.tags.js +15 -15
  114. package/server/routes/tags/index.mjs +14 -14
  115. package/server/templates/cls/cms.category_type.json +9 -9
  116. package/server/templates/cls/cms.content_review_status.json +9 -9
  117. package/server/templates/cls/cms.content_status.json +9 -9
  118. package/server/templates/cls/cms.content_type.json +9 -9
  119. package/server/templates/cls/cms.lang.json +9 -9
  120. package/server/templates/page/login.html +126 -126
  121. package/server/templates/select/core.user_mentioned.sql +1 -1
  122. package/utils.d.ts +52 -52
  123. package/utils.js +8 -8
  124. package/dist/CollectionsBreadcrumb.vue_vue_type_script_setup_true_lang-umRzB5mY.js +0 -53
  125. package/dist/EditCollectionPage-DIr1tdtn.js +0 -187
  126. package/dist/MonacoEditor.vue_vue_type_script_setup_true_lang-B1DrxmQX.js +0 -84
  127. package/dist/images/logo.png +0 -0
  128. package/dist/index.html +0 -29
  129. package/dist/vite.svg +0 -1
@@ -1,105 +1,105 @@
1
- import { dataInsert, pgClients } from "@opengis/fastify-table/utils.js";
2
-
3
- import requestTranslation from "./requestTranslation.js";
4
-
5
- export default async function insertContentLocalization({ send = () => { }, table, id, from, to, nocache, skip, schemaKeys, user }, pg = pgClients.client) {
6
- if (!to) {
7
- const resp = { error: 'not enough query params: to', code: 400 };
8
- send(resp.error + ': ' + id);
9
- return resp;
10
- }
11
-
12
- const row = await pg.query(`select * ${table ? '' : ',content_id as id'} from ${table ? `data."${table}"` : 'site.contents'} where $1 in (id,slug)`, [id]).then(el => el.rows?.[0]);
13
-
14
- if (!row) {
15
- const resp = { error: 'content not found', code: 404 };
16
- send(resp.error + ': ' + id);
17
- return resp;
18
- }
19
-
20
- const targetLocalization = await pg.query('select json_object_agg(split_part(field_key,\':\',1),field_value) from site.localization where object_id=$1 and field_key is not null and split_part(field_key,\':\',2)=$2', [row.id, to])
21
- .then(el => el.rows?.[0]?.json_object_agg);
22
-
23
- const localizationExists = await pg.query('select 1 from site.localization where object_id=$1 and field_key is not null and split_part(field_key,\':\',2)=$2', [row.id, to])
24
- .then(el => el.rowCount);
25
-
26
- if (localizationExists && !nocache && !skip) {
27
- const resp = { error: 'target localization already exists', code: 400 };
28
- send(resp.error + ': ' + id);
29
- return resp;
30
- }
31
-
32
- const localization = await pg.query('select json_object_agg(split_part(field_key,\':\',2),field_value) from site.localization where object_id=$1 and field_key is not null and split_part(field_key,\':\',2)=$2', [row.id, from])
33
- .then(el => el.rows?.[0]?.json_object_agg || {});
34
-
35
- if (!row) {
36
- const resp = { error: 'content not found', code: 404 };
37
- send(resp.error + ': ' + id);
38
- return resp;
39
- }
40
-
41
- const obj = { ...row, ...localization };
42
-
43
- const entries = Object.entries(obj).filter(([key, value]) => value && schemaKeys.includes(key) && (typeof value === 'string' || (Array.isArray(value) && value?.[0] && typeof value?.[0] === 'object' && Object.keys(value).length)));
44
- // do not old localization overwrite by default
45
- const filteredEntries = skip && targetLocalization ? entries.filter(([key]) => !Object.keys(targetLocalization).includes(key)) : entries;
46
-
47
- if (skip && targetLocalization && !filteredEntries.length) {
48
- const resp = { error: 'nothing to localize after skipping of fields with existing localization', code: 400 };
49
- send(resp.error + ': ' + id);
50
- return resp;
51
- }
52
-
53
- const skipped = schemaKeys.filter(key => !filteredEntries.map(([el]) => el).includes(key));
54
-
55
- const { result, error, code = 200 } = await requestTranslation(filteredEntries, from, to);
56
-
57
- if (error && code) {
58
- send(error + ': ' + id);
59
- return { error, code };
60
- }
61
-
62
- const arr = Object.keys(result).reduce((acc, curr) => {
63
- acc.push({
64
- field_key: curr,
65
- field_value: result[curr],
66
- object_id: id,
67
- });
68
- return acc;
69
- }, []);
70
-
71
- const client = await pg.connect();
72
-
73
- try {
74
- await client.query('begin');
75
-
76
- if (nocache) {
77
- const deleted = await client.query('delete from site.localization where object_id=$1 and split_part(field_key,\':\',2)=$2', [id, to]).then(el => el.rowCount || 0);
78
- send(`deleted existing localizations: ${deleted}, ${id}`);
79
- console.log('deleted existing localizations: ', deleted, id);
80
- }
81
-
82
- if (skip) {
83
- const deleted = await client.query('delete from site.localization where object_id=$1 and not split_part(field_key,\':\',1)<>any($2)', [id, Object.keys(targetLocalization)]).then(el => el.rowCount || 0);
84
- send(`deleted existing localizations1: ${deleted}, ${id}`);
85
- console.log('deleted existing localizations1: ', deleted, id);
86
- }
87
-
88
- await Promise.all(arr.map(async row => dataInsert({
89
- pg: client,
90
- table: 'site.localization',
91
- data: row,
92
- uid: user?.uid,
93
- })));
94
- await client.query('commit');
95
-
96
- send(`translation success: ${table}/${id}`);
97
- return { table, id: row.id, keys: schemaKeys, skipped, to };
98
- } catch (err) {
99
- await client.query('rollback');
100
- send(`translation error: ${err.toString()} (${id})`);
101
- return { error: err.toString(), code: 500 };
102
- } finally {
103
- client.release();
104
- }
1
+ import { dataInsert, pgClients } from "@opengis/fastify-table/utils.js";
2
+
3
+ import requestTranslation from "./requestTranslation.js";
4
+
5
+ export default async function insertContentLocalization({ send = () => { }, table, id, from, to, nocache, skip, schemaKeys, user }, pg = pgClients.client) {
6
+ if (!to) {
7
+ const resp = { error: 'not enough query params: to', code: 400 };
8
+ send(resp.error + ': ' + id);
9
+ return resp;
10
+ }
11
+
12
+ const row = await pg.query(`select * ${table ? '' : ',content_id as id'} from ${table ? `data."${table}"` : 'site.contents'} where $1 in (id,slug)`, [id]).then(el => el.rows?.[0]);
13
+
14
+ if (!row) {
15
+ const resp = { error: 'content not found', code: 404 };
16
+ send(resp.error + ': ' + id);
17
+ return resp;
18
+ }
19
+
20
+ const targetLocalization = await pg.query('select json_object_agg(split_part(field_key,\':\',1),field_value) from site.localization where object_id=$1 and field_key is not null and split_part(field_key,\':\',2)=$2', [row.id, to])
21
+ .then(el => el.rows?.[0]?.json_object_agg);
22
+
23
+ const localizationExists = await pg.query('select 1 from site.localization where object_id=$1 and field_key is not null and split_part(field_key,\':\',2)=$2', [row.id, to])
24
+ .then(el => el.rowCount);
25
+
26
+ if (localizationExists && !nocache && !skip) {
27
+ const resp = { error: 'target localization already exists', code: 400 };
28
+ send(resp.error + ': ' + id);
29
+ return resp;
30
+ }
31
+
32
+ const localization = await pg.query('select json_object_agg(split_part(field_key,\':\',2),field_value) from site.localization where object_id=$1 and field_key is not null and split_part(field_key,\':\',2)=$2', [row.id, from])
33
+ .then(el => el.rows?.[0]?.json_object_agg || {});
34
+
35
+ if (!row) {
36
+ const resp = { error: 'content not found', code: 404 };
37
+ send(resp.error + ': ' + id);
38
+ return resp;
39
+ }
40
+
41
+ const obj = { ...row, ...localization };
42
+
43
+ const entries = Object.entries(obj).filter(([key, value]) => value && schemaKeys.includes(key) && (typeof value === 'string' || (Array.isArray(value) && value?.[0] && typeof value?.[0] === 'object' && Object.keys(value).length)));
44
+ // do not overwrite old localization by default
45
+ const filteredEntries = skip && targetLocalization ? entries.filter(([key]) => !Object.keys(targetLocalization).includes(key)) : entries;
46
+
47
+ if (skip && targetLocalization && !filteredEntries.length) {
48
+ const resp = { error: 'nothing to localize after skipping of fields with existing localization', code: 400 };
49
+ send(resp.error + ': ' + id);
50
+ return resp;
51
+ }
52
+
53
+ const skipped = schemaKeys.filter(key => !filteredEntries.map(([el]) => el).includes(key));
54
+
55
+ const { result, error, code = 200 } = await requestTranslation(filteredEntries, from, to);
56
+
57
+ if (error && code) {
58
+ send(error + ': ' + id);
59
+ return { error, code };
60
+ }
61
+
62
+ const arr = Object.keys(result).reduce((acc, curr) => {
63
+ acc.push({
64
+ field_key: curr,
65
+ field_value: result[curr],
66
+ object_id: id,
67
+ });
68
+ return acc;
69
+ }, []);
70
+
71
+ const client = await pg.connect();
72
+
73
+ try {
74
+ await client.query('begin');
75
+
76
+ if (nocache) {
77
+ const deleted = await client.query('delete from site.localization where object_id=$1 and split_part(field_key,\':\',2)=$2', [id, to]).then(el => el.rowCount || 0);
78
+ send(`deleted existing localizations: ${deleted}, ${id}`);
79
+ console.log('deleted existing localizations: ', deleted, id);
80
+ }
81
+
82
+ if (skip) {
83
+ const deleted = await client.query('delete from site.localization where object_id=$1 and not split_part(field_key,\':\',1)<>any($2)', [id, Object.keys(targetLocalization)]).then(el => el.rowCount || 0);
84
+ send(`deleted existing localizations1: ${deleted}, ${id}`);
85
+ console.log('deleted existing localizations1: ', deleted, id);
86
+ }
87
+
88
+ await Promise.all(arr.map(async row => dataInsert({
89
+ pg: client,
90
+ table: 'site.localization',
91
+ data: row,
92
+ uid: user?.uid,
93
+ })));
94
+ await client.query('commit');
95
+
96
+ send(`translation success: ${table}/${id}`);
97
+ return { table, id: row.id, keys: schemaKeys, skipped, to };
98
+ } catch (err) {
99
+ await client.query('rollback');
100
+ send(`translation error: ${err.toString()} (${id})`);
101
+ return { error: err.toString(), code: 500 };
102
+ } finally {
103
+ client.release();
104
+ }
105
105
  }
@@ -1,85 +1,113 @@
1
- import { config } from "@opengis/fastify-table/utils.js";
2
-
3
- const { host = 'https://translate.softpro.ua', key = '' } = config.integrations?.translation || {};
4
-
5
- const divider = ' <divider> ';
6
-
7
- // Wrap dot notation keys in quotes
8
- export const arrayToPhrases = (arr, prefix = 'item') => {
9
- if (!arr || !arr.length) return [];
10
- return arr.flatMap((obj, idx) =>
11
- Object.entries(obj).map(([key, value]) => ({
12
- prefix,
13
- value,
14
- // phrase: `"${prefix}"."${key}".${idx}: ${value}`,
15
- key,
16
- idx
17
- }))
18
- );
19
- };
20
-
21
- export default async function requestTranslation(entries, from = 'uk', to) {
22
- // Flatten all phrases
23
- const phrasesWithMeta = entries.flatMap(([key, value]) =>
24
- Array.isArray(value) ? arrayToPhrases(value, key) : [{ key, value, prefix: key }]
25
- );
26
-
27
- // Translate each phrase independently
28
- const translatedPhrases = await Promise.all(
29
- phrasesWithMeta.map(async ({ key, idx, prefix, value }) => {
30
- if (!value || typeof value === 'number' || value?.startsWith?.('/files/')) {
31
- return { key, idx, prefix, value, skip: true }; // fallback to original
32
- }
33
-
34
- try {
35
- const parts = value.startsWith('<') && value.endsWith('>')
36
- ? [...value.matchAll(/(<[^>]+>)([^<]*)(<\/[^>]+>)/g)].map(([, openTag, str, closeTag]) => ({ openTag, str, closeTag }))
37
- : [{ str: value }];
38
-
39
- const q = parts.map(({ str }) => str).join(divider);
40
-
41
- const resp = await fetch(`${host}/translate`, {
42
- method: "POST",
43
- headers: { "Content-Type": "application/json" },
44
- body: JSON.stringify({
45
- q,
46
- source: from || "auto",
47
- target: to,
48
- format: "text",
49
- api_key: key
50
- }),
51
- });
52
-
53
- if (resp.status !== 200 || resp.headers.get('content-type') !== 'application/json') {
54
- const err = await resp.text();
55
- console.warn('translation request error', resp.status, err);
56
- return { key, idx, prefix, value, error: err }; // fallback to original
57
- }
58
-
59
- const body = await resp.json();
60
- const resultValue = body.translatedText ? parts.map(({ openTag, str, closeTag }, i) => `${openTag || ''}${body.translatedText.split(divider)[i] || str || ''}${closeTag || ''}`).join(' ') : null;
61
- return { key, idx, prefix, value: resultValue || value };
62
- } catch (err) {
63
- console.warn('translation request failed', err.toString());
64
- return { key, idx, prefix, value, error: err.toString() }; // fallback
65
- }
66
- })
67
- );
68
-
69
- const result = translatedPhrases.reduce((acc, { key, idx, prefix, value }) => {
70
- if (!acc[`${prefix}:${to}`]) acc[`${prefix}:${to}`] = Array.isArray(entries.find(e => e[0] === prefix)[1]) ? [] : null;
71
-
72
- if (Array.isArray(entries.find(e => e[0] === prefix)[1])) {
73
- if (!acc[`${prefix}:${to}`][idx]) acc[`${prefix}:${to}`][idx] = {};
74
- acc[`${prefix}:${to}`][idx][key] = value;
75
- } else {
76
- acc[`${prefix}:${to}`] = value;
77
- }
78
-
79
- return acc;
80
- }, {});
81
-
82
- const errors = translatedPhrases.filter(({ error }) => !!error).length;
83
-
84
- return { result, errors };
85
- }
1
+ import { config } from "@opengis/fastify-table/utils.js";
2
+
3
+ const { host = 'https://translate.softpro.ua', key = '' } = config.integrations?.translation || {};
4
+
5
+ const divider = ' <divider> ';
6
+
7
+ // Wrap dot notation keys in quotes
8
+ export const arrayToPhrases = (arr, prefix = 'item', mainIdx = null) => {
9
+ if (!arr || !arr.length) return [];
10
+
11
+ return arr.flatMap((obj, idx) => {
12
+ const currentMainIdx = mainIdx ?? idx;
13
+
14
+ return Object.entries(obj).flatMap(([key, value]) => {
15
+ if (Array.isArray(value)) {
16
+ return arrayToPhrases(value, `${prefix}.${key}`, currentMainIdx);
17
+ }
18
+
19
+ return [{
20
+ prefix,
21
+ value,
22
+ key,
23
+ mainIdx: currentMainIdx,
24
+ idx,
25
+ }];
26
+ });
27
+ });
28
+ };
29
+
30
+ export default async function requestTranslation(entries, from = 'uk', to) {
31
+ // Flatten all phrases
32
+ const phrasesWithMeta = entries.flatMap(([key, value]) =>
33
+ Array.isArray(value) ? arrayToPhrases(value, key) : [{ key, value, prefix: key }]
34
+ );
35
+
36
+ // Translate each phrase independently
37
+ const translatedPhrases = await Promise.all(
38
+ phrasesWithMeta.map(async ({ key, idx, prefix, value, mainIdx }) => {
39
+ if (!value || typeof value === 'number' || value?.startsWith?.('/files/')) {
40
+ return { key, idx, mainIdx, prefix, value, skip: true }; // fallback to original
41
+ }
42
+
43
+ try {
44
+ const parts = typeof value === 'string' && value.startsWith('<') && value.endsWith('>')
45
+ ? [...value.matchAll(/(<[^>]+>)([^<]*)(<\/[^>]+>)/g)].map(([, openTag, str, closeTag]) => ({ openTag, str, closeTag }))
46
+ : [{ str: value }];
47
+
48
+ const q = parts.map(({ str }) => str).join(divider);
49
+
50
+ const resp = await fetch(`${host}/translate`, {
51
+ method: "POST",
52
+ headers: { "Content-Type": "application/json" },
53
+ body: JSON.stringify({
54
+ q,
55
+ source: from || "auto",
56
+ target: to,
57
+ format: "text",
58
+ api_key: key
59
+ }),
60
+ });
61
+
62
+ if (resp.status !== 200 || resp.headers.get('content-type') !== 'application/json') {
63
+ const err = await resp.text();
64
+ console.warn('translation request error', resp.status, err);
65
+ return { key, idx, prefix, value, error: err }; // fallback to original
66
+ }
67
+
68
+ const body = await resp.json();
69
+ const resultValue = body.translatedText ? parts.map(({ openTag, str, closeTag }, i) => `${openTag || ''}${body.translatedText.split(divider)[i] || str || ''}${closeTag || ''}`).join(' ') : null;
70
+ return { key, idx, mainIdx, prefix, value: resultValue || value };
71
+ } catch (err) {
72
+ console.warn('translation request failed', err.toString());
73
+ return { key, idx, mainIdx, prefix, value, error: err.toString() }; // fallback
74
+ }
75
+ })
76
+ );
77
+
78
+ const result = translatedPhrases.reduce((acc, curr) => {
79
+ const { key, idx, prefix: prefix1, value, mainIdx } = curr;
80
+ const [prefix, subkey] = prefix1.split('.');
81
+
82
+ if (!acc[`${prefix}:${to}`]) {
83
+ acc[`${prefix}:${to}`] = Array.isArray(entries.find(e => e[0] === prefix)[1]) ? [] : null;
84
+ }
85
+
86
+ if (subkey && Array.isArray(entries.find(e => e[0] === prefix)[1])) {
87
+ if (!acc[`${prefix}:${to}`][mainIdx]) {
88
+ acc[`${prefix}:${to}`][mainIdx] = {};
89
+ }
90
+ if (!acc[`${prefix}:${to}`][mainIdx][subkey]) {
91
+ acc[`${prefix}:${to}`][mainIdx][subkey] = [];
92
+ }
93
+ if (!acc[`${prefix}:${to}`][mainIdx][subkey]?.[idx]) {
94
+ acc[`${prefix}:${to}`][mainIdx][subkey][idx] = {};
95
+ }
96
+ acc[`${prefix}:${to}`][mainIdx][subkey][idx][key] = value;
97
+ // console.log(prefix, to, idx, subkey, mainIdx, key);
98
+ } else if (Array.isArray(entries.find(e => e[0] === prefix)[1])) {
99
+ if (!acc[`${prefix}:${to}`][idx]) {
100
+ acc[`${prefix}:${to}`][idx] = {};
101
+ }
102
+ acc[`${prefix}:${to}`][idx][key] = value;
103
+ } else {
104
+ acc[`${prefix}:${to}`] = value;
105
+ }
106
+
107
+ return acc;
108
+ }, {});
109
+
110
+ const errors = translatedPhrases.filter(({ error }) => !!error).length;
111
+
112
+ return { result, errors };
113
+ }
@@ -1,48 +1,48 @@
1
- import { dataInsert, getTemplate } from "@opengis/fastify-table/utils.js";
2
-
3
- export default async function updateLocalization(pg, id, body, contentTypeId, uid) {
4
- if (!pg || !id || !body || !contentTypeId) { return null };
5
-
6
- const 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, contentTypeId]).then(el => el.rows?.[0]?.columns || []);
7
-
8
- // const contentColumns = await pg.query('select columns from site.content_types where content_type_id=$1', [contentTypeId])
9
- // .then(el => el.rows?.[0]?.columns || []);
10
-
11
- const loadTable = contentTypeId === 'pages'
12
- ? await getTemplate('table', 'single.default.table')
13
- : await getTemplate('table', 'collection.default.table');
14
-
15
- const columns = (loadTable?.columns || []).concat((contentColumns || []).filter(col => loadTable?.columns.findIndex(el => el.name === col.name) === -1));
16
-
17
- const locales = await pg.query('select locales from site.spaces where space_id = $1 limit 1', ['default']).then(el => el.rows?.[0]?.locales || []);
18
-
19
- await pg.query('delete from site.localization where object_id=$1', [id]);
20
-
21
- // localization disable for current space
22
- if (!locales?.length) { return null; }
23
-
24
- const schemaKeys = columns.filter(el => el?.name && el.localization).map(el => el.name);
25
-
26
- const bodyKeys = Object.keys(body || {}).filter(key => body[key] && key.includes(':') && key.split(':').pop() && locales.includes(key.split(':').pop()) && schemaKeys.includes(key.split(':').shift()));
27
- const obj = bodyKeys.reduce((acc, curr) => ({ ...acc, [curr]: body[curr] }), {});
28
-
29
- if (bodyKeys.length === 0) { return null; }
30
-
31
- const arr = Object.keys(obj).reduce((acc, curr) => {
32
- acc.push({
33
- field_key: curr,
34
- field_value: body[curr],
35
- object_id: id,
36
- });
37
- return acc;
38
- }, []);
39
-
40
- await Promise.all(arr.map(async row => dataInsert({
41
- pg,
42
- table: 'site.localization',
43
- data: row,
44
- uid,
45
- })));
46
-
47
- return obj;
1
+ import { dataInsert, getTemplate } from "@opengis/fastify-table/utils.js";
2
+
3
+ export default async function updateLocalization(pg, id, body, contentTypeId, uid) {
4
+ if (!pg || !id || !body || !contentTypeId) { return null };
5
+
6
+ const 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, contentTypeId]).then(el => el.rows?.[0]?.columns || []);
7
+
8
+ // const contentColumns = await pg.query('select columns from site.content_types where content_type_id=$1', [contentTypeId])
9
+ // .then(el => el.rows?.[0]?.columns || []);
10
+
11
+ const loadTable = contentTypeId === 'pages'
12
+ ? await getTemplate('table', 'single.default.table')
13
+ : await getTemplate('table', 'collection.default.table');
14
+
15
+ const columns = (loadTable?.columns || []).concat((contentColumns || []).filter(col => loadTable?.columns.findIndex(el => el.name === col.name) === -1));
16
+
17
+ const locales = await pg.query('select locales from site.spaces where space_id = $1 limit 1', ['default']).then(el => el.rows?.[0]?.locales || []);
18
+
19
+ await pg.query('delete from site.localization where object_id=$1', [id]);
20
+
21
+ // localization disable for current space
22
+ if (!locales?.length) { return null; }
23
+
24
+ const schemaKeys = columns.filter(el => el?.name && el.localization).map(el => el.name);
25
+
26
+ const bodyKeys = Object.keys(body || {}).filter(key => body[key] && key.includes(':') && key.split(':').pop() && locales.includes(key.split(':').pop()) && schemaKeys.includes(key.split(':').shift()));
27
+ const obj = bodyKeys.reduce((acc, curr) => ({ ...acc, [curr]: body[curr] }), {});
28
+
29
+ if (bodyKeys.length === 0) { return null; }
30
+
31
+ const arr = Object.keys(obj).reduce((acc, curr) => {
32
+ acc.push({
33
+ field_key: curr,
34
+ field_value: body[curr],
35
+ object_id: id,
36
+ });
37
+ return acc;
38
+ }, []);
39
+
40
+ await Promise.all(arr.map(async row => dataInsert({
41
+ pg,
42
+ table: 'site.localization',
43
+ data: row,
44
+ uid,
45
+ })));
46
+
47
+ return obj;
48
48
  }
@@ -1,26 +1,26 @@
1
- import { dataDelete, pgClients } from '@opengis/fastify-table/utils.js';
2
-
3
- export default async function deleteSpace({ pg = pgClients.client, user, params, headers = {} }, reply) {
4
- if (!params?.id) { return reply.status(400).send('not enough params: id'); }
5
- if (!pg?.pk) { return reply.status(400).send('empty pg'); }
6
- if (!pg?.pk?.['site.spaces']) { return reply.status(400).send('table not found: site.spaces'); }
7
-
8
- if (params?.id && params.id === 'default') {
9
- return reply.status(400).send('default space cannot be deleted');
10
- }
11
-
12
- const exists = await pg.query('select space_id from site.spaces where space_id=$1', [params.id]).then(el => el.rows?.[0]?.space_id);
13
- if (!exists) { return reply.status(404).send('space not found: ' + params.id); }
14
-
15
- const { referer } = headers;
16
-
17
- const res = await dataDelete({
18
- pg,
19
- table: 'site.spaces',
20
- id: params?.id,
21
- uid: user?.uid,
22
- referer,
23
- });
24
-
25
- return { id: params.id, ...res || {} };
1
+ import { dataDelete, pgClients } from '@opengis/fastify-table/utils.js';
2
+
3
+ export default async function deleteSpace({ pg = pgClients.client, user, params, headers = {} }, reply) {
4
+ if (!params?.id) { return reply.status(400).send('not enough params: id'); }
5
+ if (!pg?.pk) { return reply.status(400).send('empty pg'); }
6
+ if (!pg?.pk?.['site.spaces']) { return reply.status(400).send('table not found: site.spaces'); }
7
+
8
+ if (params?.id && params.id === 'default') {
9
+ return reply.status(400).send('default space cannot be deleted');
10
+ }
11
+
12
+ const exists = await pg.query('select space_id from site.spaces where space_id=$1', [params.id]).then(el => el.rows?.[0]?.space_id);
13
+ if (!exists) { return reply.status(404).send('space not found: ' + params.id); }
14
+
15
+ const { referer } = headers;
16
+
17
+ const res = await dataDelete({
18
+ pg,
19
+ table: 'site.spaces',
20
+ id: params?.id,
21
+ uid: user?.uid,
22
+ referer,
23
+ });
24
+
25
+ return { id: params.id, ...res || {} };
26
26
  }
@@ -1,28 +1,28 @@
1
- import { pgClients } from '@opengis/fastify-table/utils.js';
2
-
3
- const maxLimit = 100;
4
- const defaultLimit = 20;
5
-
6
- export default async function getSpaces({ pg = pgClients.client, user, params = {}, query = {} }, reply) {
7
- if (!pg?.pk) { return reply.status(400).send('empty pg'); }
8
- if (!pg?.pk?.['site.spaces']) { return reply.status(400).send('table not found: site.spaces'); }
9
-
10
- const limit = Math.min(maxLimit, +(query.limit || defaultLimit));
11
- const offset = query.page && query.page > 0 && !params.id ? (query.page - 1) * limit : 0;
12
-
13
- const q = `select * from site.spaces where ${params.id ? 'space_id=$1' : '1=1'} limit ${limit} offset ${offset}`;
14
- if (query.sql && user?.uid) return q;
15
-
16
- const { rows = [] } = await pg.query(q, [params.id].filter(Boolean));
17
-
18
- const total = await pg.queryCache('select count(*) from site.spaces', { table: 'site.spaces' }).then(el => el.rows?.[0]?.count || 0);
19
-
20
- rows.forEach(row => Object.assign(row, { id: row.space_id }));
21
-
22
- if (params.id) {
23
- if (!rows.length) { return reply.status(404).send('space not found: ' + params.id); }
24
- return reply.status(200).send(rows[0]);
25
- }
26
-
27
- return { total, filtered: rows.length, rows };
1
+ import { pgClients } from '@opengis/fastify-table/utils.js';
2
+
3
+ const maxLimit = 100;
4
+ const defaultLimit = 20;
5
+
6
+ export default async function getSpaces({ pg = pgClients.client, user, params = {}, query = {} }, reply) {
7
+ if (!pg?.pk) { return reply.status(400).send('empty pg'); }
8
+ if (!pg?.pk?.['site.spaces']) { return reply.status(400).send('table not found: site.spaces'); }
9
+
10
+ const limit = Math.min(maxLimit, +(query.limit || defaultLimit));
11
+ const offset = query.page && query.page > 0 && !params.id ? (query.page - 1) * limit : 0;
12
+
13
+ const q = `select * from site.spaces where ${params.id ? 'space_id=$1' : '1=1'} limit ${limit} offset ${offset}`;
14
+ if (query.sql && user?.uid) return q;
15
+
16
+ const { rows = [] } = await pg.query(q, [params.id].filter(Boolean));
17
+
18
+ const total = await pg.queryCache('select count(*) from site.spaces', { table: 'site.spaces' }).then(el => el.rows?.[0]?.count || 0);
19
+
20
+ rows.forEach(row => Object.assign(row, { id: row.space_id }));
21
+
22
+ if (params.id) {
23
+ if (!rows.length) { return reply.status(404).send('space not found: ' + params.id); }
24
+ return reply.status(200).send(rows[0]);
25
+ }
26
+
27
+ return { total, filtered: rows.length, rows };
28
28
  }