@opengis/cms 0.0.51 → 0.0.53

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 (30) hide show
  1. package/dist/{ArticlesPage-BcR1hbds.js → ArticlesPage-dx7Se1Xo.js} +1 -1
  2. package/dist/{BuilderPage-CK_osM89.js → BuilderPage-ChkWE8rE.js} +158 -158
  3. package/dist/{EditCollectionPage-DtgvKGm-.js → EditCollectionPage-H1J9pZNo.js} +64 -58
  4. package/dist/{MenuAddPage-Bf48Z-ah.js → MenuAddPage-B6Qs6qV8.js} +1 -1
  5. package/dist/{MenuItemPage-BSwUW8tv.js → MenuItemPage-XN9lx5Vv.js} +3 -3
  6. package/dist/{MonacoEditor.vue_vue_type_script_setup_true_lang-B1DrxmQX.js → MonacoEditor.vue_vue_type_script_setup_true_lang-DQjDxfF8.js} +7 -7
  7. package/dist/{UniversalTablePagination.vue_vue_type_script_setup_true_lang-GYZd_gkA.js → UniversalTablePagination.vue_vue_type_script_setup_true_lang-Cp1WWIf0.js} +5 -5
  8. package/dist/contentForm-BgEoJcWF.js +586 -0
  9. package/dist/{getField-C7EZs-YQ.js → getField-DoisYj34.js} +960 -933
  10. package/dist/index.js +9 -9
  11. package/dist/style.css +1 -1
  12. package/dist/{vuedraggable-CoAPPFYd.js → vuedraggable-DTHNuRWs.js} +108 -108
  13. package/locales/en.json +6 -1
  14. package/locales/uk.json +4 -1
  15. package/package.json +4 -4
  16. package/server/functions/getContent.js +6 -15
  17. package/server/functions/getSearchData.js +2 -12
  18. package/server/functions/getTags.js +30 -0
  19. package/server/functions/utils/mock.reply.js +56 -0
  20. package/server/routes/cms/controllers/getPermissions.js +15 -15
  21. package/server/routes/cms/controllers/listMedia.js +30 -3
  22. package/server/routes/cms/controllers/searchContent.js +2 -2
  23. package/server/routes/cms/controllers/setPermissions.js +49 -49
  24. package/server/routes/cms/controllers/translate.js +3 -3
  25. package/server/routes/cms/utils/insertContentLocalization.js +22 -4
  26. package/server/routes/contentType/controllers/getContentType.js +9 -2
  27. package/server/routes/tags/controllers/get.tags.js +1 -1
  28. package/server/templates/select/core.user_mentioned.sql +1 -1
  29. package/utils.js +1 -0
  30. package/dist/contentForm-NcG15_5z.js +0 -553
package/locales/en.json CHANGED
@@ -126,7 +126,10 @@
126
126
  "dragToBuild": "Drag and drop content blocks here to build your collection structure",
127
127
  "collectionCreated": "Collection created successfully",
128
128
  "collectionCreationFailed": "Error creating collection",
129
- "pinPublication": "Pin Publication"
129
+ "pinPublication": "Pin Publication",
130
+ "enLocalization": "EN localization",
131
+ "translateContentSuccess": "Content translated successfully",
132
+ "translateContentError": "Error translating content"
130
133
  },
131
134
  "articles": {
132
135
  "title": "Articles",
@@ -478,6 +481,8 @@
478
481
  "configure": "Configure",
479
482
  "successTitle": "Success",
480
483
  "successMessage": "Settings saved successfully",
484
+ "translateContentSuccess": "Content translated successfully",
485
+ "translateContentError": "Error translating content",
481
486
  "errorTitle": "Error",
482
487
  "errorMessage": "Error saving settings",
483
488
  "generateNewKey": "Generate New Key",
package/locales/uk.json CHANGED
@@ -126,7 +126,10 @@
126
126
  "dragToBuild": "Перетягніть блоки контенту сюди, щоб створити структуру колекції",
127
127
  "collectionCreated": "Колекція успішно створена",
128
128
  "collectionCreationFailed": "Помилка при створенні колекції",
129
- "pinPublication": "Закріпити публікацію"
129
+ "pinPublication": "Закріпити публікацію",
130
+ "enLocalization": "EN локалізація",
131
+ "translateContentSuccess": "Контент успішно перекладений",
132
+ "translateContentError": "Помилка при перекладі контенту"
130
133
  },
131
134
  "articles": {
132
135
  "title": "Сторінки",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/cms",
3
- "version": "0.0.51",
3
+ "version": "0.0.53",
4
4
  "description": "cms",
5
5
  "type": "module",
6
6
  "author": "Softpro",
@@ -41,10 +41,10 @@
41
41
  "devDependencies": {
42
42
  "@fastify/compress": "^8.1.0",
43
43
  "@opengis/core": "^0.0.30",
44
- "@opengis/fastify-table": "^2.0.138",
44
+ "@opengis/fastify-table": "^2.0.143",
45
45
  "@opengis/filter": "^0.1.31",
46
46
  "@opengis/form": "^0.0.109",
47
- "@opengis/richtext": "0.0.38",
47
+ "@opengis/richtext": "0.0.45",
48
48
  "@vueuse/head": "2.0.0",
49
49
  "js-yaml": "^4.1.0",
50
50
  "lucide-vue-next": "0.344.0",
@@ -65,4 +65,4 @@
65
65
  "vitest": "3.2.4",
66
66
  "vue-tsc": "^2.2.10"
67
67
  }
68
- }
68
+ }
@@ -1,25 +1,14 @@
1
1
  import { createHash } from 'node:crypto';
2
2
 
3
3
  import { config, getRedis, pgClients } from '@opengis/fastify-table/utils.js';
4
-
4
+ import { createMockReply } from './utils/mock.reply.js';
5
5
  import getContent from '../routes/cms/controllers/getContent.js';
6
6
 
7
7
  const pg = pgClients.client;
8
8
  const rclient = getRedis();
9
9
 
10
- const mockReply = {
11
- response: {},
12
- status: (statusCode) => {
13
- Object.assign(mockReply.response, { status: statusCode });
14
- return mockReply;
15
- },
16
- send: (result) => {
17
- Object.assign(mockReply.response, typeof result === 'object' ? result : { message: result });
18
- return { ...mockReply.response };
19
- },
20
- };
21
-
22
- export default async function getContentBySlug({ slug, filter, tags, state, locale, contextQuery, collection = 'pages', ttl = 3600, fields, limit = 12, page = 1 } = {}) {
10
+
11
+ export default async function getContentBySlug({ slug, filter, tags, state, locale, contextQuery, collection = 'pages', ttl = 3600, fields, limit = 12, page = 1, order, desc } = {}) {
23
12
  if (!slug) {
24
13
  // return { error: 'not enough params: slug', code: 400 };
25
14
  }
@@ -43,7 +32,9 @@ export default async function getContentBySlug({ slug, filter, tags, state, loca
43
32
  return { cache: true, ...cacheData };
44
33
  }
45
34
 
46
- const req = { pg: pgClients.client, params: { type: collection, id: slug }, query: { filter, tags, state, contextQuery, locale, fields, limit, page } };
35
+ const req = { pg: pgClients.client, params: { type: collection, id: slug }, query: { filter, tags, state, contextQuery, locale, fields, limit, page, order, desc } };
36
+
37
+ const mockReply = createMockReply();
47
38
  const result = await getContent(req, mockReply, true);
48
39
 
49
40
  if (ttl) {
@@ -1,24 +1,13 @@
1
1
  import { createHash } from 'node:crypto';
2
2
 
3
3
  import { config, getRedis, pgClients } from '@opengis/fastify-table/utils.js';
4
+ import { createMockReply } from './utils/mock.reply.js';
4
5
 
5
6
  import searchContent from '../routes/cms/controllers/searchContent.js';
6
7
 
7
8
  const pg = pgClients.client;
8
9
  const rclient = getRedis();
9
10
 
10
- const mockReply = {
11
- response: {},
12
- status: (statusCode) => {
13
- Object.assign(mockReply.response, { status: statusCode });
14
- return mockReply;
15
- },
16
- send: (result) => {
17
- Object.assign(mockReply.response, typeof result === 'object' ? result : { message: result });
18
- return { ...mockReply.response };
19
- },
20
- };
21
-
22
11
  export default async function getSearchData({ page = 1, limit = 12, ttl = 3600, search, locale, tags, filter, contentType, asc } = {}) {
23
12
  // check if any crud operations performed, if not - return cached response
24
13
  const crudInc = await rclient.get(`pg:site.contents:crud`) || 0;
@@ -31,6 +20,7 @@ export default async function getSearchData({ page = 1, limit = 12, ttl = 3600,
31
20
  return { cache: true, ...cacheData };
32
21
  }
33
22
 
23
+ const mockReply = createMockReply();
34
24
  const result = await searchContent({ pg, query: { search, locale, page, limit, tags, filter, contentType } }, mockReply);
35
25
 
36
26
  if (ttl) {
@@ -0,0 +1,30 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ import { config, getRedis, pgClients } from '@opengis/fastify-table/utils.js';
4
+ import { createMockReply } from './utils/mock.reply.js';
5
+
6
+ import getTagsApi from '../routes/tags/controllers/get.tags.js';
7
+
8
+ const pg = pgClients.client;
9
+ const rclient = getRedis();
10
+
11
+ export default async function getTags({ page = 1, limit = 16, search, ttl = 3600 } = {}) {
12
+
13
+ const crudInc = await rclient.get(`pg:site.contents:tags`) || 0;
14
+
15
+ const cacheKey = createHash('md5').update([config.pg?.database, 'cms:tags', crudInc, page, limit, search].join(':')).digest('hex');
16
+ const cacheData = ttl === 0 ? null : JSON.parse(await rclient.get(cacheKey));
17
+
18
+ if (cacheData) {
19
+ return { cache: true, ...cacheData };
20
+ }
21
+
22
+ const mockReply = createMockReply();
23
+ const result = await getTagsApi({ pg, query: { page, limit, search } }, mockReply);
24
+
25
+ if (ttl) {
26
+ await rclient.set(cacheKey, JSON.stringify(result), 'EX', ttl);
27
+ }
28
+
29
+ return result;
30
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @template T
3
+ * @typedef {Object} MockReplyResponse
4
+ * @property {number} [status] - HTTP статус-код
5
+ * @property {T} [data] - Дані відповіді (якщо передано об'єкт)
6
+ * @property {string} [message] - Повідомлення (якщо передано рядок)
7
+ */
8
+
9
+ /**
10
+ * Створює ізольований mock-об'єкт reply для тестування Fastify-хендлерів.
11
+ * Імітує методи `status()` та `send()`.
12
+ *
13
+ * @template T
14
+ * @returns {{
15
+ * status: (statusCode: number) => any,
16
+ * send: (result: T | string) => MockReplyResponse<T>
17
+ * }}
18
+ */
19
+ export function createMockReply() {
20
+ /** @type {MockReplyResponse<any>} */
21
+
22
+ let response = {};
23
+
24
+ return {
25
+ /**
26
+ * Встановлює HTTP статус-код.
27
+ * Повертає той самий reply для чейнінгу.
28
+ *
29
+ * @param {number} statusCode
30
+ * @returns {any}
31
+ */
32
+ status(statusCode) {
33
+ response.status = statusCode;
34
+ return this;
35
+ },
36
+
37
+ /**
38
+ * Імітує Fastify reply.send().
39
+ * Якщо передано об'єкт — він додається як data.
40
+ * Якщо передано рядок — додається як message.
41
+ *
42
+ * @param {T | string} result
43
+ * @returns {MockReplyResponse<T>}
44
+ */
45
+ send(result) {
46
+ response = {
47
+ ...response,
48
+ ...(typeof result === 'object'
49
+ ? { data: result }
50
+ : { message: result }),
51
+ };
52
+
53
+ return response;
54
+ },
55
+ };
56
+ }
@@ -1,16 +1,16 @@
1
- import { pgClients } from '@opengis/fastify-table/utils.js';
2
-
3
- export default async function getPermissions(req, reply) {
4
- const { pg = pgClients.client, params = {}, user = {} } = req;
5
-
6
- if (!user?.uid) {
7
- return reply.status(401).send('unauthorized');
8
- }
9
-
10
- const { rows = [] } = await pg.query(
11
- `select * from site.permissions where ${params.id ? 'user_id=$1' : 'true'}`,
12
- [params.id].filter(Boolean),
13
- );
14
-
15
- return { permissions: rows };
1
+ import { pgClients } from '@opengis/fastify-table/utils.js';
2
+
3
+ export default async function getPermissions(req, reply) {
4
+ const { pg = pgClients.client, params = {}, user = {} } = req;
5
+
6
+ if (!user?.uid) {
7
+ return reply.status(401).send('unauthorized');
8
+ }
9
+
10
+ const { rows = [] } = await pg.query(
11
+ `select * from site.permissions where ${params.id ? 'user_id=$1' : 'true'}`,
12
+ [params.id].filter(Boolean),
13
+ );
14
+
15
+ return { permissions: rows };
16
16
  }
@@ -23,7 +23,7 @@ const rootDir = path.resolve(getFolder(config, 'local'));
23
23
  const dir = '/files';
24
24
 
25
25
  const filesizeCache = {};
26
- const previewWidth = 300;
26
+ const fileMtimeCache = {};
27
27
 
28
28
  mkdirSync(path.join(rootDir, dir), { recursive: true });
29
29
 
@@ -89,8 +89,33 @@ export default async function listMedia(req, reply) {
89
89
  const filepath = media ? media.url : ('/files/' + (el.path.split('/files/')[1] ? el.path.split('/files/')[1] + '/' : '') + el.name);
90
90
 
91
91
  if (!filesizeCache[filepath]) {
92
- const { size } = await stat(path.join(rootDir, filepath));
92
+ const { mtime, size } = await stat(path.join(rootDir, filepath));
93
93
  Object.assign(filesizeCache, { [filepath]: size });
94
+ mtime.setHours(0, 0, 0, 0); // strip hours from timestamp, leave only date
95
+ Object.assign(fileMtimeCache, { [filepath]: mtime });
96
+ }
97
+
98
+
99
+ const [, from, to] = q ? (q.match(/between\s+'(\d{4}-\d{2}-\d{2})'::date\s+and\s+'(\d{4}-\d{2}-\d{2})'::date/i) || []) : [];
100
+
101
+ // skip entirely if invalid dates passed to query.filter
102
+ if ((from && new Date(from).toString() === 'Invalid Date') || (to && new Date(to).toString() === 'Invalid Date')) {
103
+ return null;
104
+ }
105
+
106
+ // skip if file modification date does not match filter value
107
+ if (fileMtimeCache[filepath] && from && to && (new Date(from) > fileMtimeCache[filepath] || new Date(to) < fileMtimeCache[filepath])) {
108
+ return null;
109
+ }
110
+
111
+ const dt = q && !q.includes('between') ? query.filter.match(/\d{4}-\d{2}-\d{2}/)?.[0] : null;
112
+ const date = dt ? new Date(dt) : null;
113
+ // skip timezone offset
114
+ if (date) { date.setHours(0, 0, 0, 0); }
115
+
116
+ // filter by exact date
117
+ if (fileMtimeCache[filepath] && date && (new Date(date).getTime() !== fileMtimeCache[filepath].getTime())) {
118
+ return null;
94
119
  }
95
120
 
96
121
  const mime = getMimeType(el.name) || '';
@@ -116,10 +141,12 @@ export default async function listMedia(req, reply) {
116
141
  url: filepath,
117
142
  mime,
118
143
  preview,
144
+ updated_at: fileMtimeCache[filepath].toISOString(),
119
145
  }
120
146
  }));
121
147
 
122
- Object.assign(result, { data: subdirs.concat(files) });
148
+ // filter empty items, such as filtered out by query.filter=updated_at (not from db, but from fs.stat)
149
+ Object.assign(result, { data: subdirs.concat(files.filter(Boolean)) });
123
150
  return result;
124
151
 
125
152
  }
@@ -94,11 +94,11 @@ export default async function searchContent({
94
94
  const collections = contents.filter(el => el.content_type === 'collection' && el.table_name);
95
95
 
96
96
  // const totals = await pg.query(`select json_object_agg(oid::regclass, reltuples) from pg_class`).then(el => el.rows?.[0]?.json_object_agg || {});
97
- const allSingles = await pg.query(`select count(*)::int from site.contents where content_type_id in (select content_type_id from site.content_types where type = 'single' )`).then(el => el.rows?.[0]?.count || 0);
97
+ const allSingles = await pg.query(`select count(*)::int from site.contents where content_type_id in (select content_type_id from site.content_types where type = 'single') and status='published'`).then(el => el.rows?.[0]?.count || 0);
98
98
  const allCollectionTables = await pg.query(`select array_agg(table_name) from site.content_types where type = 'collection' and table_name is not null`).then(el => el.rows?.[0]?.array_agg || []);
99
99
 
100
100
  const allCollections = await Promise.all(allCollectionTables.map(async tablename => {
101
- const total = pg.queryCache ? await pg.queryCache(`select count(*)::int from data."${tablename}"`, { table: `data.${tablename}` }).then(el => el.rows?.[0]?.count || 0) : 0;
101
+ const total = pg.queryCache ? await pg.queryCache(`select count(*)::int from data."${tablename}" where status='published'`, { table: `data.${tablename}` }).then(el => el.rows?.[0]?.count || 0) : 0;
102
102
  return total;
103
103
  })).then(el => el.reduce((acc, curr) => acc + (curr || 0), 0) || 0);
104
104
 
@@ -1,50 +1,50 @@
1
- import { logger, pgClients, dataInsert } from '@opengis/fastify-table/utils.js';
2
-
3
- export default async function setPermissions(req, reply) {
4
- const { pg = pgClients.client, params = {}, user = {}, body = {} } = req;
5
-
6
- if (!user?.uid) {
7
- return reply.status(401).send('unauthorized');
8
- }
9
-
10
- if (!params.id) {
11
- return reply.status(400).send('not enough params: id');
12
- }
13
-
14
- const client = await pg.connect()
15
- const result = {};
16
- try {
17
- await client.query('BEGIN');
18
-
19
- const { rowCount = 0 } = await client.query(
20
- `delete from site.permissions where user_id=$1`,
21
- [params.id].filter(Boolean),
22
- );
23
-
24
- Object.assign(result, { deleted: rowCount });
25
-
26
- if (Array.isArray(body.permissions) && body.permissions?.length) {
27
- body.permissions.forEach((el) => {
28
- Object.assign(el, { user_id: el.user_id || params.id });
29
- });
30
-
31
- await Promise.all(body.permissions.map(async (el) => dataInsert({
32
- pg: client,
33
- table: 'site.permissions',
34
- data: el,
35
- uid: user.uid,
36
- })));
37
-
38
- Object.assign(result, { inserted: body.permissions.length });
39
- }
40
-
41
- await client.query('COMMIT');
42
- return reply.status(200).send(result);
43
- } catch (err) {
44
- await client.query('ROLLBACK');
45
- logger.file('cms/permissions', { error: err.toString(), stack: err.stack });
46
- return reply.status(500).send('set permissions error');
47
- } finally {
48
- client.release();
49
- }
1
+ import { logger, pgClients, dataInsert } from '@opengis/fastify-table/utils.js';
2
+
3
+ export default async function setPermissions(req, reply) {
4
+ const { pg = pgClients.client, params = {}, user = {}, body = {} } = req;
5
+
6
+ if (!user?.uid) {
7
+ return reply.status(401).send('unauthorized');
8
+ }
9
+
10
+ if (!params.id) {
11
+ return reply.status(400).send('not enough params: id');
12
+ }
13
+
14
+ const client = await pg.connect()
15
+ const result = {};
16
+ try {
17
+ await client.query('BEGIN');
18
+
19
+ const { rowCount = 0 } = await client.query(
20
+ `delete from site.permissions where user_id=$1`,
21
+ [params.id].filter(Boolean),
22
+ );
23
+
24
+ Object.assign(result, { deleted: rowCount });
25
+
26
+ if (Array.isArray(body.permissions) && body.permissions?.length) {
27
+ body.permissions.forEach((el) => {
28
+ Object.assign(el, { user_id: el.user_id || params.id });
29
+ });
30
+
31
+ await Promise.all(body.permissions.map(async (el) => dataInsert({
32
+ pg: client,
33
+ table: 'site.permissions',
34
+ data: el,
35
+ uid: user.uid,
36
+ })));
37
+
38
+ Object.assign(result, { inserted: body.permissions.length });
39
+ }
40
+
41
+ await client.query('COMMIT');
42
+ return reply.status(200).send(result);
43
+ } catch (err) {
44
+ await client.query('ROLLBACK');
45
+ logger.file('cms/permissions', { error: err.toString(), stack: err.stack });
46
+ return reply.status(500).send('set permissions error');
47
+ } finally {
48
+ client.release();
49
+ }
50
50
  }
@@ -21,7 +21,7 @@ export async function translateContent({
21
21
  return reply.status(400).send({ error: 'not enough query params: to', code: 400 });
22
22
  }
23
23
 
24
- const { id, collection = 'pages', from = 'uk', to, nocache } = query;
24
+ const { id, collection = 'pages', from = 'uk', to, nocache, skip } = query;
25
25
  const { id: collectionId, table, columns = [] } = await getCollectionId(collection, pg);
26
26
 
27
27
  if (!collectionId) {
@@ -34,7 +34,7 @@ export async function translateContent({
34
34
  return reply.status(400).send({ error: 'collection does not contain any fields with localization enabled', code: 400 });
35
35
  }
36
36
 
37
- const result = await insertContentLocalization({ table, id, from, to, nocache, schemaKeys, user }, pg);
37
+ const result = await insertContentLocalization({ table, id, from, to, nocache, skip, schemaKeys, user }, pg);
38
38
 
39
39
  if (result?.error) {
40
40
  return reply.status(result.code).send(result);
@@ -80,7 +80,7 @@ export async function translateCollection({
80
80
 
81
81
  send(`target localization already exists for ${ids.length - filteredIds.length}/${ids.length} rows`);
82
82
 
83
- await sequence(debug ? filteredIds.slice(0, 1) : filteredIds, { send, pg, from, to, table, nocache: true, schemaKeys, user }, insertContentLocalization);
83
+ await sequence(debug ? filteredIds.slice(0, 1) : filteredIds, { send, pg, from, to, table, nocache: true, skip, schemaKeys, user }, insertContentLocalization);
84
84
  return send('translation complete', true);
85
85
  } catch (err) {
86
86
  return send(err.toString(), true);
@@ -2,7 +2,7 @@ import { dataInsert, pgClients } from "@opengis/fastify-table/utils.js";
2
2
 
3
3
  import requestTranslation from "./requestTranslation.js";
4
4
 
5
- export default async function insertContentLocalization({ send = () => { }, table, id, from, to, nocache, schemaKeys, user }, pg = pgClients.client) {
5
+ export default async function insertContentLocalization({ send = () => { }, table, id, from, to, nocache, skip, schemaKeys, user }, pg = pgClients.client) {
6
6
  if (!to) {
7
7
  const resp = { error: 'not enough query params: to', code: 400 };
8
8
  send(resp.error + ': ' + id);
@@ -17,10 +17,13 @@ export default async function insertContentLocalization({ send = () => { }, tabl
17
17
  return resp;
18
18
  }
19
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
+
20
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])
21
24
  .then(el => el.rowCount);
22
25
 
23
- if (localizationExists && !nocache) {
26
+ if (localizationExists && !nocache && !skip) {
24
27
  const resp = { error: 'target localization already exists', code: 400 };
25
28
  send(resp.error + ': ' + id);
26
29
  return resp;
@@ -38,9 +41,18 @@ export default async function insertContentLocalization({ send = () => { }, tabl
38
41
  const obj = { ...row, ...localization };
39
42
 
40
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)));
41
- const skipped = schemaKeys.filter(key => !entries.map(([el]) => el).includes(key));
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));
42
54
 
43
- const { result, error, code = 200 } = await requestTranslation(entries, from, to);
55
+ const { result, error, code = 200 } = await requestTranslation(filteredEntries, from, to);
44
56
 
45
57
  if (error && code) {
46
58
  send(error + ': ' + id);
@@ -67,6 +79,12 @@ export default async function insertContentLocalization({ send = () => { }, tabl
67
79
  console.log('deleted existing localizations: ', deleted, id);
68
80
  }
69
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
+
70
88
  await Promise.all(arr.map(async row => dataInsert({
71
89
  pg: client,
72
90
  table: 'site.localization',
@@ -23,9 +23,16 @@ export default async function builderGet({ pg = pgClients.client, params = {} },
23
23
  ).then(el => el.rows?.[0] || {});
24
24
  Object.assign(row, { space });
25
25
 
26
- const loadTable = params.id === 'pages' ? await getTemplate('table', 'single.default.table') : {};
26
+ // const loadTable = params.id === 'pages' ? await getTemplate('table', 'single.default.table') : {};
27
+ // const columns = params.id === 'pages' ? loadTable?.columns : row.columns;
28
+
29
+ const loadTable = params.id === 'pages'
30
+ ? await getTemplate('table', 'single.default.table')
31
+ : await getTemplate('table', 'collection.default.table');
32
+ const { columns: defaultColumns = [] } = loadTable || {};
33
+ defaultColumns.forEach(col => Object.assign(col, { default: true }));
34
+ const columns = (defaultColumns || []).concat(row.columns.filter(col => !defaultColumns.map(el => el.name).includes(col.name)));
27
35
 
28
- const columns = params.id === 'pages' ? loadTable?.columns : row.columns;
29
36
  Object.assign(row, { columns });
30
37
 
31
38
  // filter system / hidden
@@ -11,6 +11,6 @@ export default async function getTags(req, reply) {
11
11
 
12
12
  const { rows } = await pg.query(selectSql);
13
13
 
14
- return reply.code(200).send({ message: { total, filtered, rows }, status: 200 })
14
+ return reply.status(200).send({ message: { total, filtered, rows }, status: 200 })
15
15
 
16
16
  }
@@ -1,2 +1,2 @@
1
- select uid, coalesce(sur_name,'')||coalesce(' '||user_name,'') as text, email from admin.users
1
+ select uid, coalesce(sur_name,'')||coalesce(' '||user_name,'') as text, email from admin.users
2
2
  where enabled order by coalesce(sur_name,'')||coalesce(' '||user_name,'')
package/utils.js CHANGED
@@ -3,5 +3,6 @@ export { default as getContent } from "./server/functions/getContent.js";
3
3
  export { default as getSettings } from "./server/routes/cms/functions/getSettings.js";
4
4
  export { default as getUser } from "./server/functions/getUser.js";
5
5
  export { default as getSearchData } from "./server/functions/getSearchData.js";
6
+ export { default as getTags } from "./server/functions/getTags.js";
6
7
 
7
8
  export default null;