@opengis/cms 0.0.26 → 0.0.28
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.
- package/README.md +98 -98
- package/dist/index-W-qQIppj-DunG40EG.js +2452 -0
- package/dist/index.html +28 -28
- package/dist/index.js +12683 -6702
- package/dist/index.umd.cjs +87 -41
- package/dist/style.css +1 -0
- package/package.json +68 -68
- package/plugin.js +76 -76
- package/server/app.js +35 -35
- package/server/config.js +4 -4
- package/server/functions/getDraftKey.js +22 -22
- package/server/index.js +22 -22
- package/server/migrations/fixes.sql +123 -123
- package/server/migrations/site.sql +545 -545
- package/server/plugins/adminHook.js +78 -78
- package/server/plugins/hook.js +59 -59
- package/server/plugins/vite.js +84 -84
- package/server/routes/category/controllers/cms.category.delete.js +21 -21
- package/server/routes/category/controllers/cms.category.get.js +17 -17
- package/server/routes/category/controllers/cms.category.list.js +16 -16
- package/server/routes/category/controllers/cms.category.post.js +21 -21
- package/server/routes/category/controllers/cms.category.put.js +23 -23
- package/server/routes/category/index.mjs +22 -22
- package/server/routes/cms/controllers/cmsStat.js +55 -55
- package/server/routes/cms/controllers/cmsSuggest.js +57 -57
- package/server/routes/cms/controllers/deleteContent.js +114 -114
- package/server/routes/cms/controllers/deleteMedia.js +75 -75
- package/server/routes/cms/controllers/downloadMedia.js +48 -48
- package/server/routes/cms/controllers/getContent.js +110 -110
- package/server/routes/cms/controllers/getContentBySlug.js +95 -95
- package/server/routes/cms/controllers/getPermissions.js +15 -15
- package/server/routes/cms/controllers/insertContent.js +218 -218
- package/server/routes/cms/controllers/listMedia.js +93 -93
- package/server/routes/cms/controllers/metadataMedia.js +38 -38
- package/server/routes/cms/controllers/properties.get.js +53 -53
- package/server/routes/cms/controllers/properties.post.js +99 -99
- package/server/routes/cms/controllers/searchContent.js +205 -205
- package/server/routes/cms/controllers/setPermissions.js +49 -49
- package/server/routes/cms/controllers/translate.js +89 -89
- package/server/routes/cms/controllers/updateContent.js +238 -238
- package/server/routes/cms/controllers/uploadMedia.js +78 -78
- package/server/routes/cms/index.mjs +114 -114
- package/server/routes/cms/utils/additionalData.js +35 -35
- package/server/routes/cms/utils/getCollection.js +81 -81
- package/server/routes/cms/utils/getSingle.js +187 -187
- package/server/routes/cms/utils/insertContentLocalization.js +86 -86
- package/server/routes/cms/utils/requestTranslation.js +85 -85
- package/server/routes/cms/utils/updateLocalization.js +47 -47
- package/server/routes/cmsSpace/controllers/deleteSpace.js +25 -25
- package/server/routes/cmsSpace/controllers/getSpaces.js +27 -27
- package/server/routes/cmsSpace/controllers/insertSpace.js +21 -21
- package/server/routes/cmsSpace/controllers/updateSpace.js +23 -23
- package/server/routes/cmsSpace/index.mjs +20 -20
- package/server/routes/contentType/controllers/addContentType.js +162 -162
- package/server/routes/contentType/controllers/contentTypeList.js +54 -54
- package/server/routes/contentType/controllers/delContentType.js +75 -75
- package/server/routes/contentType/controllers/editContentType.js +61 -61
- package/server/routes/contentType/controllers/getContentType.js +37 -37
- package/server/routes/contentType/index.mjs +35 -35
- package/server/routes/contentType/utils/updateContents.js +28 -28
- package/server/routes/contentType/utils/updateCustomContentTable.js +55 -55
- package/server/routes/feedback/controllers/email.list.js +24 -24
- package/server/routes/feedback/controllers/feedback.js +48 -48
- package/server/routes/feedback/controllers/feedback.list.js +37 -37
- package/server/routes/feedback/controllers/news.subscriptions.js +44 -44
- package/server/routes/feedback/index.mjs +71 -71
- package/server/routes/logs/controllers/export.user.logs.js +77 -77
- package/server/routes/logs/controllers/user.logs.js +44 -44
- package/server/routes/logs/index.mjs +9 -9
- package/server/routes/menu/controllers/addMenu.js +37 -37
- package/server/routes/menu/controllers/delMenu.js +31 -31
- package/server/routes/menu/controllers/editMenu.js +41 -41
- package/server/routes/menu/controllers/getMenu.js +42 -42
- package/server/routes/menu/index.mjs +13 -13
- package/server/routes/migration/controllers/collectionToCustom.js +137 -137
- package/server/routes/migration/index.mjs +8 -8
- package/server/routes/root.mjs +8 -8
- package/server/routes/tags/controllers/add.tags.js +24 -24
- package/server/routes/tags/controllers/del.tags.js +19 -19
- package/server/routes/tags/controllers/edit.tags.js +25 -25
- package/server/routes/tags/controllers/get.tags.js +15 -15
- package/server/routes/tags/index.mjs +14 -14
- package/server/templates/cls/cms.category_type.json +9 -9
- package/server/templates/cls/cms.content_review_status.json +9 -9
- package/server/templates/cls/cms.content_status.json +9 -9
- package/server/templates/cls/cms.content_type.json +9 -9
- package/server/templates/cls/cms.lang.json +9 -9
- package/server/templates/page/login.html +126 -126
- package/server/templates/select/core.user_mentioned.sql +1 -1
- package/dist/index-W-qQIppj-DRzFSjU1.js +0 -2452
|
@@ -1,79 +1,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
|
-
const rootDir = getFolder(config, 'local');
|
|
7
|
-
const dir = '/files';
|
|
8
|
-
|
|
9
|
-
export default async function uploadMedia(req, reply) {
|
|
10
|
-
const { pg = pgClients.client, user = {}, query = {} } = req;
|
|
11
|
-
|
|
12
|
-
if (!pg?.pk?.['site.media']) {
|
|
13
|
-
return reply.status(404).send('table not found');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (query.subdir && (typeof query.subdir !== 'string' || query.subdir.includes('..'))) {
|
|
17
|
-
return reply.status(403).send('invalid query params: subdir');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// upload assets
|
|
21
|
-
if (req.headers['content-type']?.split?.(';')?.shift?.() === 'multipart/form-data') {
|
|
22
|
-
const file = await uploadMultiPart(req, { subdir: query.subdir || '', originalFilename: true }).catch(err => {
|
|
23
|
-
if (err.message === 'file with specified name already exists in directory') {
|
|
24
|
-
err.message = 'Файл з вказаною назвою вже існує';
|
|
25
|
-
err.statusCode = 400;
|
|
26
|
-
}
|
|
27
|
-
throw err;
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const { originalFilename: filename, filetype, mimetype } = file;
|
|
31
|
-
const relpath = path.join(dir, query.subdir || '', file.originalFilename).replace(/\\/g, '/');
|
|
32
|
-
|
|
33
|
-
const id = await dataInsert({
|
|
34
|
-
pg,
|
|
35
|
-
table: 'site.media',
|
|
36
|
-
data: {
|
|
37
|
-
filename,
|
|
38
|
-
filetype,
|
|
39
|
-
subdir: query.subdir,
|
|
40
|
-
url: relpath,
|
|
41
|
-
mime: mimetype,
|
|
42
|
-
filesize: file.size,
|
|
43
|
-
},
|
|
44
|
-
uid: user?.uid,
|
|
45
|
-
}).then(el => el?.rows?.[0]?.media_id);
|
|
46
|
-
|
|
47
|
-
return reply.status(200).send({
|
|
48
|
-
res: 'ok',
|
|
49
|
-
name: filename,
|
|
50
|
-
type: 'file',
|
|
51
|
-
mimetype,
|
|
52
|
-
result: {
|
|
53
|
-
file_id: id,
|
|
54
|
-
format: file.extension,
|
|
55
|
-
size: file.size,
|
|
56
|
-
// entity_id: resultInsert?.entity_id,
|
|
57
|
-
file_path: relpath,
|
|
58
|
-
file_name: filename,
|
|
59
|
-
dir: path.dirname(relpath).replace(/\\/g, '/'),
|
|
60
|
-
native_file_name: filename,
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (!query.subdir) {
|
|
66
|
-
return reply.status(400).send('not enough query params: subdir');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// create directory
|
|
70
|
-
const relpath = path.join(dir, query.subdir).replace(/\\/g, '/');
|
|
71
|
-
const dirpath = path.join(rootDir, relpath);
|
|
72
|
-
await mkdir(dirpath, { recursive: true });
|
|
73
|
-
|
|
74
|
-
return reply.status(200).send({
|
|
75
|
-
relpath,
|
|
76
|
-
dirname: path.basename(query.subdir),
|
|
77
|
-
type: 'dir',
|
|
78
|
-
});
|
|
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
|
+
const rootDir = getFolder(config, 'local');
|
|
7
|
+
const dir = '/files';
|
|
8
|
+
|
|
9
|
+
export default async function uploadMedia(req, reply) {
|
|
10
|
+
const { pg = pgClients.client, user = {}, query = {} } = req;
|
|
11
|
+
|
|
12
|
+
if (!pg?.pk?.['site.media']) {
|
|
13
|
+
return reply.status(404).send('table not found');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (query.subdir && (typeof query.subdir !== 'string' || query.subdir.includes('..'))) {
|
|
17
|
+
return reply.status(403).send('invalid query params: subdir');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// upload assets
|
|
21
|
+
if (req.headers['content-type']?.split?.(';')?.shift?.() === 'multipart/form-data') {
|
|
22
|
+
const file = await uploadMultiPart(req, { subdir: query.subdir || '', originalFilename: true }).catch(err => {
|
|
23
|
+
if (err.message === 'file with specified name already exists in directory') {
|
|
24
|
+
err.message = 'Файл з вказаною назвою вже існує';
|
|
25
|
+
err.statusCode = 400;
|
|
26
|
+
}
|
|
27
|
+
throw err;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const { originalFilename: filename, filetype, mimetype } = file;
|
|
31
|
+
const relpath = path.join(dir, query.subdir || '', file.originalFilename).replace(/\\/g, '/');
|
|
32
|
+
|
|
33
|
+
const id = await dataInsert({
|
|
34
|
+
pg,
|
|
35
|
+
table: 'site.media',
|
|
36
|
+
data: {
|
|
37
|
+
filename,
|
|
38
|
+
filetype,
|
|
39
|
+
subdir: query.subdir,
|
|
40
|
+
url: relpath,
|
|
41
|
+
mime: mimetype,
|
|
42
|
+
filesize: file.size,
|
|
43
|
+
},
|
|
44
|
+
uid: user?.uid,
|
|
45
|
+
}).then(el => el?.rows?.[0]?.media_id);
|
|
46
|
+
|
|
47
|
+
return reply.status(200).send({
|
|
48
|
+
res: 'ok',
|
|
49
|
+
name: filename,
|
|
50
|
+
type: 'file',
|
|
51
|
+
mimetype,
|
|
52
|
+
result: {
|
|
53
|
+
file_id: id,
|
|
54
|
+
format: file.extension,
|
|
55
|
+
size: file.size,
|
|
56
|
+
// entity_id: resultInsert?.entity_id,
|
|
57
|
+
file_path: relpath,
|
|
58
|
+
file_name: filename,
|
|
59
|
+
dir: path.dirname(relpath).replace(/\\/g, '/'),
|
|
60
|
+
native_file_name: filename,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!query.subdir) {
|
|
66
|
+
return reply.status(400).send('not enough query params: subdir');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// create directory
|
|
70
|
+
const relpath = path.join(dir, query.subdir).replace(/\\/g, '/');
|
|
71
|
+
const dirpath = path.join(rootDir, relpath);
|
|
72
|
+
await mkdir(dirpath, { recursive: true });
|
|
73
|
+
|
|
74
|
+
return reply.status(200).send({
|
|
75
|
+
relpath,
|
|
76
|
+
dirname: path.basename(query.subdir),
|
|
77
|
+
type: 'dir',
|
|
78
|
+
});
|
|
79
79
|
}
|
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
import { config } from '@opengis/fastify-table/utils.js';
|
|
2
|
-
|
|
3
|
-
// perimissions
|
|
4
|
-
import getPermissions from './controllers/getPermissions.js';
|
|
5
|
-
import setPermissions from './controllers/setPermissions.js';
|
|
6
|
-
|
|
7
|
-
// media
|
|
8
|
-
import listMedia from './controllers/listMedia.js';
|
|
9
|
-
import metadata from './controllers/metadataMedia.js';
|
|
10
|
-
import uploadMedia from './controllers/uploadMedia.js';
|
|
11
|
-
import del from './controllers/deleteMedia.js';
|
|
12
|
-
import download from './controllers/downloadMedia.js';
|
|
13
|
-
|
|
14
|
-
// content
|
|
15
|
-
import getContent from './controllers/getContent.js';
|
|
16
|
-
import getContentBySlug from './controllers/getContentBySlug.js';
|
|
17
|
-
import insertContent from './controllers/insertContent.js';
|
|
18
|
-
import updateContent from './controllers/updateContent.js';
|
|
19
|
-
import deleteContent from './controllers/deleteContent.js';
|
|
20
|
-
import { translateContent, translateCollection } from './controllers/translate.js';
|
|
21
|
-
|
|
22
|
-
// suggest
|
|
23
|
-
import cmsSuggest from './controllers/cmsSuggest.js';
|
|
24
|
-
|
|
25
|
-
// statistics
|
|
26
|
-
import cmsStat from './controllers/cmsStat.js';
|
|
27
|
-
import searchContent from './controllers/searchContent.js';
|
|
28
|
-
|
|
29
|
-
import getAppSettings from "./controllers/properties.get.js";
|
|
30
|
-
import postAppSettings from "./controllers/properties.post.js";
|
|
31
|
-
|
|
32
|
-
const suggestSchema = {
|
|
33
|
-
querystring: {
|
|
34
|
-
type: 'object',
|
|
35
|
-
properties: {
|
|
36
|
-
search: { type: 'string' },
|
|
37
|
-
filter: { type: 'string' },
|
|
38
|
-
limit: { type: 'number', exclusiveMinimum: 0 },
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const propertiesSchema = {
|
|
44
|
-
type: "object",
|
|
45
|
-
properties: {
|
|
46
|
-
params: {
|
|
47
|
-
id: { type: "string", pattern: "^([\\d\\w]+)$" },
|
|
48
|
-
key: { type: "string", pattern: "^([\\d\\w._]+)$" },
|
|
49
|
-
},
|
|
50
|
-
querystring: {
|
|
51
|
-
json: { type: "string", pattern: "^([\\d\\w]+)$" },
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const translateContentSchema = {
|
|
57
|
-
type: "object",
|
|
58
|
-
properties: {
|
|
59
|
-
params: {
|
|
60
|
-
id: {
|
|
61
|
-
type: "string"
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
querystring: {
|
|
65
|
-
required: ['to'],
|
|
66
|
-
additionalProperties: false,
|
|
67
|
-
from: {
|
|
68
|
-
type: "string"
|
|
69
|
-
},
|
|
70
|
-
to: {
|
|
71
|
-
type: "string"
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const params = { config: { policy: 'L0' } };
|
|
78
|
-
|
|
79
|
-
export default async function route(app) {
|
|
80
|
-
app.post('/cms-permissions/:id', { config: { role: 'admin' } }, setPermissions);
|
|
81
|
-
app.get('/cms-permissions/:id?', params, getPermissions);
|
|
82
|
-
app.get('/cms-translate', { config: { role: "admin" }, schema: translateContentSchema }, translateContent);
|
|
83
|
-
app.get('/cms-translate-collection/:id', { config: { role: "admin" }, schema: translateContentSchema }, translateCollection);
|
|
84
|
-
|
|
85
|
-
app.post('/cms-media/upload', params, uploadMedia);
|
|
86
|
-
app.get('/cms-media', params, listMedia);
|
|
87
|
-
app.get('/cms-media/:id/:type', params, download);
|
|
88
|
-
app.get('/cms-media/:id', params, metadata);
|
|
89
|
-
app.delete('/cms-media/:id?', params, del); // media file with id / subdir without
|
|
90
|
-
app.get('/cms-media/:id/delete', params, del); // debug
|
|
91
|
-
|
|
92
|
-
// Bearer token required
|
|
93
|
-
app.get('/cms-content/:slug/:lang?', params, getContentBySlug);
|
|
94
|
-
app.get('/cms-search', params, searchContent);
|
|
95
|
-
app.get('/cms/:type', params, getContent);
|
|
96
|
-
app.get('/cms/:type/:id/:lang?', params, getContent);
|
|
97
|
-
|
|
98
|
-
app.post('/cms/:type/:id?', params, insertContent);
|
|
99
|
-
app.put('/cms/:type/:id?', params, updateContent);
|
|
100
|
-
app.delete('/cms/:type/:id', params, deleteContent);
|
|
101
|
-
|
|
102
|
-
app.get('/cms-suggest/:id', { ...params, schema: suggestSchema }, cmsSuggest);
|
|
103
|
-
app.get('/cms-stat', params, cmsStat);
|
|
104
|
-
app.get(
|
|
105
|
-
"/settings",
|
|
106
|
-
{ config: { policy: "L0" }, schema: propertiesSchema },
|
|
107
|
-
getAppSettings
|
|
108
|
-
);
|
|
109
|
-
app.post(
|
|
110
|
-
"/settings",
|
|
111
|
-
{ config: { policy: "L0" } },
|
|
112
|
-
postAppSettings
|
|
113
|
-
);
|
|
114
|
-
}
|
|
1
|
+
import { config } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
// perimissions
|
|
4
|
+
import getPermissions from './controllers/getPermissions.js';
|
|
5
|
+
import setPermissions from './controllers/setPermissions.js';
|
|
6
|
+
|
|
7
|
+
// media
|
|
8
|
+
import listMedia from './controllers/listMedia.js';
|
|
9
|
+
import metadata from './controllers/metadataMedia.js';
|
|
10
|
+
import uploadMedia from './controllers/uploadMedia.js';
|
|
11
|
+
import del from './controllers/deleteMedia.js';
|
|
12
|
+
import download from './controllers/downloadMedia.js';
|
|
13
|
+
|
|
14
|
+
// content
|
|
15
|
+
import getContent from './controllers/getContent.js';
|
|
16
|
+
import getContentBySlug from './controllers/getContentBySlug.js';
|
|
17
|
+
import insertContent from './controllers/insertContent.js';
|
|
18
|
+
import updateContent from './controllers/updateContent.js';
|
|
19
|
+
import deleteContent from './controllers/deleteContent.js';
|
|
20
|
+
import { translateContent, translateCollection } from './controllers/translate.js';
|
|
21
|
+
|
|
22
|
+
// suggest
|
|
23
|
+
import cmsSuggest from './controllers/cmsSuggest.js';
|
|
24
|
+
|
|
25
|
+
// statistics
|
|
26
|
+
import cmsStat from './controllers/cmsStat.js';
|
|
27
|
+
import searchContent from './controllers/searchContent.js';
|
|
28
|
+
|
|
29
|
+
import getAppSettings from "./controllers/properties.get.js";
|
|
30
|
+
import postAppSettings from "./controllers/properties.post.js";
|
|
31
|
+
|
|
32
|
+
const suggestSchema = {
|
|
33
|
+
querystring: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
search: { type: 'string' },
|
|
37
|
+
filter: { type: 'string' },
|
|
38
|
+
limit: { type: 'number', exclusiveMinimum: 0 },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const propertiesSchema = {
|
|
44
|
+
type: "object",
|
|
45
|
+
properties: {
|
|
46
|
+
params: {
|
|
47
|
+
id: { type: "string", pattern: "^([\\d\\w]+)$" },
|
|
48
|
+
key: { type: "string", pattern: "^([\\d\\w._]+)$" },
|
|
49
|
+
},
|
|
50
|
+
querystring: {
|
|
51
|
+
json: { type: "string", pattern: "^([\\d\\w]+)$" },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const translateContentSchema = {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
params: {
|
|
60
|
+
id: {
|
|
61
|
+
type: "string"
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
querystring: {
|
|
65
|
+
required: ['to'],
|
|
66
|
+
additionalProperties: false,
|
|
67
|
+
from: {
|
|
68
|
+
type: "string"
|
|
69
|
+
},
|
|
70
|
+
to: {
|
|
71
|
+
type: "string"
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const params = { config: { policy: 'L0' } };
|
|
78
|
+
|
|
79
|
+
export default async function route(app) {
|
|
80
|
+
app.post('/cms-permissions/:id', { config: { role: 'admin' } }, setPermissions);
|
|
81
|
+
app.get('/cms-permissions/:id?', params, getPermissions);
|
|
82
|
+
app.get('/cms-translate', { config: { role: "admin" }, schema: translateContentSchema }, translateContent);
|
|
83
|
+
app.get('/cms-translate-collection/:id', { config: { role: "admin" }, schema: translateContentSchema }, translateCollection);
|
|
84
|
+
|
|
85
|
+
app.post('/cms-media/upload', params, uploadMedia);
|
|
86
|
+
app.get('/cms-media', params, listMedia);
|
|
87
|
+
app.get('/cms-media/:id/:type', params, download);
|
|
88
|
+
app.get('/cms-media/:id', params, metadata);
|
|
89
|
+
app.delete('/cms-media/:id?', params, del); // media file with id / subdir without
|
|
90
|
+
app.get('/cms-media/:id/delete', params, del); // debug
|
|
91
|
+
|
|
92
|
+
// Bearer token required
|
|
93
|
+
app.get('/cms-content/:slug/:lang?', params, getContentBySlug);
|
|
94
|
+
app.get('/cms-search', params, searchContent);
|
|
95
|
+
app.get('/cms/:type', params, getContent);
|
|
96
|
+
app.get('/cms/:type/:id/:lang?', params, getContent);
|
|
97
|
+
|
|
98
|
+
app.post('/cms/:type/:id?', params, insertContent);
|
|
99
|
+
app.put('/cms/:type/:id?', params, updateContent);
|
|
100
|
+
app.delete('/cms/:type/:id', params, deleteContent);
|
|
101
|
+
|
|
102
|
+
app.get('/cms-suggest/:id', { ...params, schema: suggestSchema }, cmsSuggest);
|
|
103
|
+
app.get('/cms-stat', params, cmsStat);
|
|
104
|
+
app.get(
|
|
105
|
+
"/settings",
|
|
106
|
+
{ config: { policy: "L0" }, schema: propertiesSchema },
|
|
107
|
+
getAppSettings
|
|
108
|
+
);
|
|
109
|
+
app.post(
|
|
110
|
+
"/settings",
|
|
111
|
+
{ config: { policy: "L0" } },
|
|
112
|
+
postAppSettings
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
export default async function additionalData(pg, rows, locale, fields = '*') {
|
|
2
|
-
const { rows: localizations = [] } = await pg.query(`select json_object_agg(field_key, field_value), object_id from site.localization
|
|
3
|
-
where object_id=any($1) and ${locale && locale !== 'uk' ? 'REVERSE(split_part(REVERSE(field_key), \':\', 1)) = $2' : '1=1'} group by object_id`, [rows.map(el => el.id), locale && locale !== 'uk' ? locale : null].filter(Boolean));
|
|
4
|
-
|
|
5
|
-
const { rows: tagsList = [] } = await pg.query(`
|
|
6
|
-
SELECT
|
|
7
|
-
td.data_id,
|
|
8
|
-
json_agg(
|
|
9
|
-
json_build_object('id', t.tag_id, 'text', t.value, 'color', t.color, 'slug', t.slug, 'locale', t.locale)
|
|
10
|
-
) AS tag
|
|
11
|
-
FROM site.tag_data td
|
|
12
|
-
JOIN site.tags t ON t.tag_id = td.tag_id
|
|
13
|
-
where data_id=any($1)
|
|
14
|
-
GROUP BY td.data_id;
|
|
15
|
-
`, [rows.map(el => el.id)]);
|
|
16
|
-
|
|
17
|
-
rows.forEach(row => {
|
|
18
|
-
if (locale && locale !== 'uk' && row.meta && Object.keys(row.meta || {}).find(key => key.split(':').pop() === locale)) {
|
|
19
|
-
row.meta = Object.fromEntries(Object.keys(row.meta || {}).filter(key => key.split(':').pop() === locale && row.meta?.[key]).map(key => [key.split(':').shift(), row.meta[key]]));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const localization = localizations.find(el => el.object_id === row.id);
|
|
23
|
-
const localizationKeys = Object.keys(localization?.json_object_agg || {}).filter(key => row[key.split(':').shift()]);
|
|
24
|
-
const localizationObj1 = Object.entries(localization?.json_object_agg || {}).filter(([key]) => rows.length > 1 ? true : localizationKeys.includes(key)).reduce((acc, curr) => ({ ...acc, [curr[0]]: typeof curr[1] === 'string' && curr[1].startsWith('[') && curr[1].endsWith(']') ? JSON.parse(curr[1]) : curr[1] }), {});
|
|
25
|
-
const localizationObj = fields?.length ? Object.fromEntries(Object.entries(localizationObj1).filter(([key]) => fields.split(',').includes(key.split(':').shift()))) : localizationObj1;
|
|
26
|
-
if (locale && locale !== 'uk') {
|
|
27
|
-
Object.assign(row, Object.keys(localizationObj).reduce((acc, curr) => ({ ...acc, [curr.replace(`:${locale}`, '')]: localizationObj[curr] }), {}));
|
|
28
|
-
} else if (!locale) {
|
|
29
|
-
Object.assign(row, localizationObj);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const { tag = [] } = tagsList.find(el => el?.data_id === row?.id) || {};
|
|
33
|
-
const localizedTags = locale ? tag.map(el => ({ ...el, text: el.locale?.[locale] || el.text })) : tag;
|
|
34
|
-
Object.assign(row, { tag_list: localizedTags });
|
|
35
|
-
});
|
|
1
|
+
export default async function additionalData(pg, rows, locale, fields = '*') {
|
|
2
|
+
const { rows: localizations = [] } = await pg.query(`select json_object_agg(field_key, field_value), object_id from site.localization
|
|
3
|
+
where object_id=any($1) and ${locale && locale !== 'uk' ? 'REVERSE(split_part(REVERSE(field_key), \':\', 1)) = $2' : '1=1'} group by object_id`, [rows.map(el => el.id), locale && locale !== 'uk' ? locale : null].filter(Boolean));
|
|
4
|
+
|
|
5
|
+
const { rows: tagsList = [] } = await pg.query(`
|
|
6
|
+
SELECT
|
|
7
|
+
td.data_id,
|
|
8
|
+
json_agg(
|
|
9
|
+
json_build_object('id', t.tag_id, 'text', t.value, 'color', t.color, 'slug', t.slug, 'locale', t.locale)
|
|
10
|
+
) AS tag
|
|
11
|
+
FROM site.tag_data td
|
|
12
|
+
JOIN site.tags t ON t.tag_id = td.tag_id
|
|
13
|
+
where data_id=any($1)
|
|
14
|
+
GROUP BY td.data_id;
|
|
15
|
+
`, [rows.map(el => el.id)]);
|
|
16
|
+
|
|
17
|
+
rows.forEach(row => {
|
|
18
|
+
if (locale && locale !== 'uk' && row.meta && Object.keys(row.meta || {}).find(key => key.split(':').pop() === locale)) {
|
|
19
|
+
row.meta = Object.fromEntries(Object.keys(row.meta || {}).filter(key => key.split(':').pop() === locale && row.meta?.[key]).map(key => [key.split(':').shift(), row.meta[key]]));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const localization = localizations.find(el => el.object_id === row.id);
|
|
23
|
+
const localizationKeys = Object.keys(localization?.json_object_agg || {}).filter(key => row[key.split(':').shift()]);
|
|
24
|
+
const localizationObj1 = Object.entries(localization?.json_object_agg || {}).filter(([key]) => rows.length > 1 ? true : localizationKeys.includes(key)).reduce((acc, curr) => ({ ...acc, [curr[0]]: typeof curr[1] === 'string' && curr[1].startsWith('[') && curr[1].endsWith(']') ? JSON.parse(curr[1]) : curr[1] }), {});
|
|
25
|
+
const localizationObj = fields?.length ? Object.fromEntries(Object.entries(localizationObj1).filter(([key]) => fields.split(',').includes(key.split(':').shift()))) : localizationObj1;
|
|
26
|
+
if (locale && locale !== 'uk') {
|
|
27
|
+
Object.assign(row, Object.keys(localizationObj).reduce((acc, curr) => ({ ...acc, [curr.replace(`:${locale}`, '')]: localizationObj[curr] }), {}));
|
|
28
|
+
} else if (!locale) {
|
|
29
|
+
Object.assign(row, localizationObj);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { tag = [] } = tagsList.find(el => el?.data_id === row?.id) || {};
|
|
33
|
+
const localizedTags = locale ? tag.map(el => ({ ...el, text: el.locale?.[locale] || el.text })) : tag;
|
|
34
|
+
Object.assign(row, { tag_list: localizedTags });
|
|
35
|
+
});
|
|
36
36
|
}
|
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
import { pgClients, getData, getMeta } from "@opengis/fastify-table/utils.js";
|
|
2
|
-
|
|
3
|
-
import additionalData from "./additionalData.js";
|
|
4
|
-
|
|
5
|
-
export default async function getCollection({
|
|
6
|
-
table, id, limit, maxLimit, order, search, tags, filter, state, page, desc, sql, locale, contextQuery, statusQuery, columns, preview, user, defaultColumns, fields, defaultFields = [], defaultFilters = []
|
|
7
|
-
}, reply, pg = pgClients.client) {
|
|
8
|
-
if (!table || !pg.pk?.['data.' + table]) {
|
|
9
|
-
return { message: 'content table not found', status: 404 };
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const isPinExists = await pg.query(`
|
|
13
|
-
SELECT 1
|
|
14
|
-
FROM pg_catalog.pg_attribute a
|
|
15
|
-
JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
|
|
16
|
-
JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
|
|
17
|
-
WHERE n.nspname = 'data'
|
|
18
|
-
AND c.relname = $1
|
|
19
|
-
AND a.attname = 'is_pin'
|
|
20
|
-
AND a.attnum > 0
|
|
21
|
-
AND NOT a.attisdropped;
|
|
22
|
-
`, [table]).then(el => el.rowCount);
|
|
23
|
-
|
|
24
|
-
const defaultOrder = `case when status='archived' then true else false end ${isPinExists ? ', is_pin desc' : ''}, published_at desc nulls last`;
|
|
25
|
-
|
|
26
|
-
const localeQuery = locale && locale !== 'uk' ? `id in ( select object_id from site.localization where REVERSE(split_part(REVERSE(field_key), ':', 1)) = '${locale.replace(/'/g, "''")}' )` : undefined;
|
|
27
|
-
const tagQuery = tags ? `id in (SELECT data_id FROM site.tag_data td left join site.tags ts on td.tag_id=ts.tag_id WHERE ts.slug = any('{ ${tags.replace(/'/g, "''")} }'::text[]) or ts.tag_id = any('{ ${tags.replace(/'/g, "''")} }'::text[]))` : null;
|
|
28
|
-
const cQuery = [id ? `'${id}' in (id,slug)` : contextQuery, localeQuery, ((columns || []).concat(defaultColumns || [])).find(col => col.name === 'status') ? statusQuery : null, tagQuery].filter(Boolean).join(' and ');
|
|
29
|
-
|
|
30
|
-
const columnList = await getMeta({
|
|
31
|
-
pg,
|
|
32
|
-
table: 'data.' + `"${table}"`
|
|
33
|
-
}).then(el => el.columns?.map?.(col => col.name) || []);
|
|
34
|
-
const existingFields = fields && !id ? defaultFields.concat((fields || '').split(',')).filter(colName => columnList.includes(colName)).join(',') : null;
|
|
35
|
-
|
|
36
|
-
const filterList = defaultFilters.concat(columns.map(col => ({ name: col.name, type: col.type && ['date', 'datetime'].includes(col.type) ? 'Date' : 'Text', label: col.label, sql: col.sql })));
|
|
37
|
-
|
|
38
|
-
const res = await getData({
|
|
39
|
-
pg,
|
|
40
|
-
table: 'data.' + `"${table}"`,
|
|
41
|
-
columns: existingFields,
|
|
42
|
-
filterList,
|
|
43
|
-
query: {
|
|
44
|
-
filter,
|
|
45
|
-
state,
|
|
46
|
-
limit: Math.min(limit, maxLimit),
|
|
47
|
-
page,
|
|
48
|
-
search,
|
|
49
|
-
order: order ? order : defaultOrder,
|
|
50
|
-
desc,
|
|
51
|
-
sql
|
|
52
|
-
},
|
|
53
|
-
user,
|
|
54
|
-
contextQuery: cQuery,
|
|
55
|
-
}, reply, true);
|
|
56
|
-
|
|
57
|
-
const locales = await pg.query(`select array_agg(distinct REVERSE(split_part(REVERSE(field_key), ':', 1))) from site.localization where object_id in (select id from ${'data.' + `"${table}"`})`).then(el => el.rows?.[0]?.array_agg || []);
|
|
58
|
-
|
|
59
|
-
// Apply localization and tags etc.
|
|
60
|
-
if (res?.rows?.length) {
|
|
61
|
-
await additionalData(pg, res.rows, locale, id ? null : existingFields);
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const columns1 = !id
|
|
65
|
-
? columns?.map(col => Object.assign(col, { type: col.name === 'published_at' ? 'date' : col.type }))?.filter?.(el => !el.hidden)
|
|
66
|
-
: columns?.filter?.(el => !el.hidden);
|
|
67
|
-
|
|
68
|
-
const finalColumns = (defaultColumns || []).concat(columns1.filter(col => defaultColumns.findIndex(el => el.name === col.name) === -1));
|
|
69
|
-
|
|
70
|
-
finalColumns.filter(el => el.default).forEach(col => {
|
|
71
|
-
const { name, localization } = columns1.find(item => item.name === col.name) || {};
|
|
72
|
-
if (name) {
|
|
73
|
-
Object.assign(col, { localization });
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
if (res?.columns) {
|
|
78
|
-
Object.assign(res, { type: 'collection', locales, preview_path: preview, columns: finalColumns });
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return res;
|
|
1
|
+
import { pgClients, getData, getMeta } from "@opengis/fastify-table/utils.js";
|
|
2
|
+
|
|
3
|
+
import additionalData from "./additionalData.js";
|
|
4
|
+
|
|
5
|
+
export default async function getCollection({
|
|
6
|
+
table, id, limit, maxLimit, order, search, tags, filter, state, page, desc, sql, locale, contextQuery, statusQuery, columns, preview, user, defaultColumns, fields, defaultFields = [], defaultFilters = []
|
|
7
|
+
}, reply, pg = pgClients.client) {
|
|
8
|
+
if (!table || !pg.pk?.['data.' + table]) {
|
|
9
|
+
return { message: 'content table not found', status: 404 };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const isPinExists = await pg.query(`
|
|
13
|
+
SELECT 1
|
|
14
|
+
FROM pg_catalog.pg_attribute a
|
|
15
|
+
JOIN pg_catalog.pg_class c ON a.attrelid = c.oid
|
|
16
|
+
JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
|
|
17
|
+
WHERE n.nspname = 'data'
|
|
18
|
+
AND c.relname = $1
|
|
19
|
+
AND a.attname = 'is_pin'
|
|
20
|
+
AND a.attnum > 0
|
|
21
|
+
AND NOT a.attisdropped;
|
|
22
|
+
`, [table]).then(el => el.rowCount);
|
|
23
|
+
|
|
24
|
+
const defaultOrder = `case when status='archived' then true else false end ${isPinExists ? ', is_pin desc' : ''}, published_at desc nulls last`;
|
|
25
|
+
|
|
26
|
+
const localeQuery = locale && locale !== 'uk' ? `id in ( select object_id from site.localization where REVERSE(split_part(REVERSE(field_key), ':', 1)) = '${locale.replace(/'/g, "''")}' )` : undefined;
|
|
27
|
+
const tagQuery = tags ? `id in (SELECT data_id FROM site.tag_data td left join site.tags ts on td.tag_id=ts.tag_id WHERE ts.slug = any('{ ${tags.replace(/'/g, "''")} }'::text[]) or ts.tag_id = any('{ ${tags.replace(/'/g, "''")} }'::text[]))` : null;
|
|
28
|
+
const cQuery = [id ? `'${id}' in (id,slug)` : contextQuery, localeQuery, ((columns || []).concat(defaultColumns || [])).find(col => col.name === 'status') ? statusQuery : null, tagQuery].filter(Boolean).join(' and ');
|
|
29
|
+
|
|
30
|
+
const columnList = await getMeta({
|
|
31
|
+
pg,
|
|
32
|
+
table: 'data.' + `"${table}"`
|
|
33
|
+
}).then(el => el.columns?.map?.(col => col.name) || []);
|
|
34
|
+
const existingFields = fields && !id ? defaultFields.concat((fields || '').split(',')).filter(colName => columnList.includes(colName)).join(',') : null;
|
|
35
|
+
|
|
36
|
+
const filterList = defaultFilters.concat(columns.map(col => ({ name: col.name, type: col.type && ['date', 'datetime'].includes(col.type) ? 'Date' : 'Text', label: col.label, sql: col.sql })));
|
|
37
|
+
|
|
38
|
+
const res = await getData({
|
|
39
|
+
pg,
|
|
40
|
+
table: 'data.' + `"${table}"`,
|
|
41
|
+
columns: existingFields,
|
|
42
|
+
filterList,
|
|
43
|
+
query: {
|
|
44
|
+
filter,
|
|
45
|
+
state,
|
|
46
|
+
limit: Math.min(limit, maxLimit),
|
|
47
|
+
page,
|
|
48
|
+
search,
|
|
49
|
+
order: order ? order : defaultOrder,
|
|
50
|
+
desc,
|
|
51
|
+
sql
|
|
52
|
+
},
|
|
53
|
+
user,
|
|
54
|
+
contextQuery: cQuery,
|
|
55
|
+
}, reply, true);
|
|
56
|
+
|
|
57
|
+
const locales = await pg.query(`select array_agg(distinct REVERSE(split_part(REVERSE(field_key), ':', 1))) from site.localization where object_id in (select id from ${'data.' + `"${table}"`})`).then(el => el.rows?.[0]?.array_agg || []);
|
|
58
|
+
|
|
59
|
+
// Apply localization and tags etc.
|
|
60
|
+
if (res?.rows?.length) {
|
|
61
|
+
await additionalData(pg, res.rows, locale, id ? null : existingFields);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const columns1 = !id
|
|
65
|
+
? columns?.map(col => Object.assign(col, { type: col.name === 'published_at' ? 'date' : col.type }))?.filter?.(el => !el.hidden)
|
|
66
|
+
: columns?.filter?.(el => !el.hidden);
|
|
67
|
+
|
|
68
|
+
const finalColumns = (defaultColumns || []).concat(columns1.filter(col => defaultColumns.findIndex(el => el.name === col.name) === -1));
|
|
69
|
+
|
|
70
|
+
finalColumns.filter(el => el.default).forEach(col => {
|
|
71
|
+
const { name, localization } = columns1.find(item => item.name === col.name) || {};
|
|
72
|
+
if (name) {
|
|
73
|
+
Object.assign(col, { localization });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (res?.columns) {
|
|
78
|
+
Object.assign(res, { type: 'collection', locales, preview_path: preview, columns: finalColumns });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return res;
|
|
82
82
|
}
|