@opengis/cms 0.0.45 → 0.0.46
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/package.json +66 -65
- package/server/functions/getContent.js +44 -0
- package/server/functions/getUser.js +26 -0
- package/server/plugins/hook.js +1 -1
- package/server/routes/cms/controllers/properties.get.js +11 -46
- package/server/routes/cms/functions/getSettings.js +46 -0
- package/server/routes/menu/controllers/getMenu.js +14 -32
- package/server/routes/menu/functions/getMenu.js +48 -0
- package/utils.js +6 -0
package/package.json
CHANGED
|
@@ -1,65 +1,66 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@opengis/cms",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "cms",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"author": "Softpro",
|
|
7
|
-
"main": "./dist/index.js",
|
|
8
|
-
"license": "EULA",
|
|
9
|
-
"files": [
|
|
10
|
-
"module",
|
|
11
|
-
"dist",
|
|
12
|
-
"server",
|
|
13
|
-
"plugin.js",
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"build
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"@
|
|
41
|
-
"@opengis/
|
|
42
|
-
"@opengis/
|
|
43
|
-
"@opengis/
|
|
44
|
-
"@opengis/
|
|
45
|
-
"@
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"vue
|
|
51
|
-
"vue-
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"@
|
|
55
|
-
"@
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@opengis/cms",
|
|
3
|
+
"version": "0.0.46",
|
|
4
|
+
"description": "cms",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": "Softpro",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"license": "EULA",
|
|
9
|
+
"files": [
|
|
10
|
+
"module",
|
|
11
|
+
"dist",
|
|
12
|
+
"server",
|
|
13
|
+
"plugin.js",
|
|
14
|
+
"utils.js",
|
|
15
|
+
"input-types.json"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"patch": "npm version patch && git push && npm publish",
|
|
19
|
+
"test": "node --test test/**/*.test.js",
|
|
20
|
+
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
|
21
|
+
"dev": "NODE_ENV=dev bun --env-file=.env.ip server ",
|
|
22
|
+
"admin": "vite admin",
|
|
23
|
+
"build": "vite build && vite build admin",
|
|
24
|
+
"build:lib": "vite build",
|
|
25
|
+
"proxy": "vite dev admin",
|
|
26
|
+
"start": "bun --env-file=.env.ip server",
|
|
27
|
+
"prod": "NODE_ENV=production bun --env-file=.env.ip server ",
|
|
28
|
+
"ip": "bun --env-file=.env.ip server",
|
|
29
|
+
"demo": "node --env-file=.env.demo --env-file=.env server",
|
|
30
|
+
"i18n:sync": "node i18n-sync.cjs",
|
|
31
|
+
"prepublishOnly": "bun build:lib",
|
|
32
|
+
"softpro": "bun --env-file=.env.softpro server",
|
|
33
|
+
"softpro1": "NODE_ENV=production bun --env-file=.env.prod-softpro.local server"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {},
|
|
36
|
+
"resolutions": {
|
|
37
|
+
"rollup": "4.30.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@fastify/compress": "^8.1.0",
|
|
41
|
+
"@opengis/core": "^0.0.30",
|
|
42
|
+
"@opengis/fastify-table": "^2.0.128",
|
|
43
|
+
"@opengis/filter": "^0.1.31",
|
|
44
|
+
"@opengis/form": "^0.0.108",
|
|
45
|
+
"@opengis/richtext": "0.0.38",
|
|
46
|
+
"@vueuse/head": "2.0.0",
|
|
47
|
+
"js-yaml": "^4.1.0",
|
|
48
|
+
"lucide-vue-next": "0.344.0",
|
|
49
|
+
"vite": "5.1.4",
|
|
50
|
+
"vue": "^3.5.17",
|
|
51
|
+
"vue-i18n": "11.1.5",
|
|
52
|
+
"vue-router": "4.4.3",
|
|
53
|
+
"vuedraggable": "4.1.0",
|
|
54
|
+
"@tailwindcss/typography": "0.5.10",
|
|
55
|
+
"@tsconfig/node22": "^22.0.2",
|
|
56
|
+
"@vitejs/plugin-vue": "5.0.4",
|
|
57
|
+
"autoprefixer": "10.4.18",
|
|
58
|
+
"eslint": "8.49.0",
|
|
59
|
+
"postcss": "8.4.35",
|
|
60
|
+
"sass": "^1.92.1",
|
|
61
|
+
"tailwindcss": "3.4.1",
|
|
62
|
+
"typescript": "~5.8.0",
|
|
63
|
+
"vitest": "3.2.4",
|
|
64
|
+
"vue-tsc": "^2.2.10"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { config, getRedis, pgClients } from '@opengis/fastify-table/utils.js';
|
|
4
|
+
|
|
5
|
+
import getContent from '../routes/cms/controllers/getContent.js';
|
|
6
|
+
|
|
7
|
+
const rclient = getRedis();
|
|
8
|
+
|
|
9
|
+
const mockReply = {
|
|
10
|
+
response: {},
|
|
11
|
+
status: (statusCode) => {
|
|
12
|
+
Object.assign(mockReply.response, { status: statusCode });
|
|
13
|
+
return mockReply;
|
|
14
|
+
},
|
|
15
|
+
send: (result) => {
|
|
16
|
+
Object.assign(mockReply.response, typeof result === 'object' ? result : { message: result });
|
|
17
|
+
return { ...mockReply.response };
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default async function getContentBySlug(slug, { locale, ttl = 3600, fields, limit = 12, page = 1, pg = pgClients.client } = {}) {
|
|
22
|
+
if (!slug) {
|
|
23
|
+
return { error: 'not enough params: slug', code: 400 };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const cacheKey = createHash('md5').update([config.pg?.database, 'cms:content', slug, fields && typeof fields !== 'string' ? JSON.stringify(fields) : fields].filter(Boolean).join(':')).digest('hex');
|
|
27
|
+
|
|
28
|
+
const cache = ttl === 0 ? null : await rclient.get(cacheKey);
|
|
29
|
+
|
|
30
|
+
// return from cache
|
|
31
|
+
if (cache) {
|
|
32
|
+
return { cache: true, ...JSON.parse(cache) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
const token = await pg.query(`select token_value from site.tokens where token_status = 'Active'`).then(el => el.rows?.[0]?.token_value);
|
|
37
|
+
|
|
38
|
+
const req = { pg: pgClients.client, params: { type: slug }, query: { locale, fields, limit, page }, headers: { 'authorization': `Bearer ${token}` } };
|
|
39
|
+
const result = await getContent(req, mockReply);
|
|
40
|
+
|
|
41
|
+
if (ttl) await rclient.set(cacheKey, JSON.stringify(result), 'EX', ttl);
|
|
42
|
+
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { unsign } from "@fastify/cookie";
|
|
2
|
+
|
|
3
|
+
import { config, getRedis } from "@opengis/fastify-table/utils.js";
|
|
4
|
+
|
|
5
|
+
const rclient2 = getRedis({ db: 2 });
|
|
6
|
+
|
|
7
|
+
// const test = await getUser(req.cookies);
|
|
8
|
+
|
|
9
|
+
export default async function getUser(cookies) {
|
|
10
|
+
const rawCookie = cookies?.['session_auth'];
|
|
11
|
+
|
|
12
|
+
// unauthorized
|
|
13
|
+
if (!rawCookie) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const { valid, value: sessionId } = unsign(rawCookie, config.auth?.secret || "61b820e12858570a4b0633020d4394a17903d9a9");
|
|
18
|
+
|
|
19
|
+
// invalid cookie
|
|
20
|
+
if (!valid) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const str = await rclient2.get(`session_auth:${config.pg?.database || 'db'}:${sessionId}`);
|
|
25
|
+
return str ? JSON.parse(str).passport.user : null;
|
|
26
|
+
}
|
package/server/plugins/hook.js
CHANGED
|
@@ -23,7 +23,7 @@ where
|
|
|
23
23
|
end`;
|
|
24
24
|
|
|
25
25
|
async function updateDelayPublished() {
|
|
26
|
-
const rows = await pg.query(q).then(el => el.rows || []);
|
|
26
|
+
const rows = pg ? await pg.query(q).then(el => el.rows || []) : [];
|
|
27
27
|
|
|
28
28
|
const result = await Promise.all(rows.map(async ({ id, status, type, table }) => {
|
|
29
29
|
if (status === 'delayPublished' && type === 'single') {
|
|
@@ -1,53 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
import { pgClients } from '@opengis/fastify-table/utils.js';
|
|
1
|
+
import getSettings from '../functions/getSettings.js';
|
|
3
2
|
|
|
4
|
-
export default async function getAppSettings(
|
|
5
|
-
{
|
|
6
|
-
pg = pgClients.client,
|
|
7
|
-
query,
|
|
8
|
-
params,
|
|
9
|
-
user = {},
|
|
10
|
-
},
|
|
11
|
-
reply
|
|
12
|
-
) {
|
|
3
|
+
export default async function getAppSettings({ query, params, user }, reply) {
|
|
13
4
|
const t1 = Date.now();
|
|
14
5
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
6
|
+
const result = await getSettings({
|
|
7
|
+
entity: params.entity,
|
|
8
|
+
keys: query.keys,
|
|
9
|
+
user,
|
|
10
|
+
ttl: 0
|
|
11
|
+
});
|
|
22
12
|
|
|
23
|
-
if (
|
|
24
|
-
return reply.status(
|
|
13
|
+
if (result.error) {
|
|
14
|
+
return reply.status(result.code).send(result);
|
|
25
15
|
}
|
|
26
16
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const args =
|
|
30
|
-
params?.entity === "user" ? [user.uid] : [params?.entity || "app"];
|
|
31
|
-
|
|
32
|
-
const { rows = [] } = await pg.query(
|
|
33
|
-
"select property_key as key, property_text, property_json from admin.properties where property_entity=$1",
|
|
34
|
-
args
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
const settings = rows
|
|
38
|
-
.filter((row) =>
|
|
39
|
-
typeof query?.keys === "string" ? query.keys.includes(row.key) : true
|
|
40
|
-
)
|
|
41
|
-
.reduce(
|
|
42
|
-
(
|
|
43
|
-
acc,
|
|
44
|
-
{ key, property_text, property_json }
|
|
45
|
-
) => ({
|
|
46
|
-
...acc,
|
|
47
|
-
[key]: property_text || property_json,
|
|
48
|
-
}),
|
|
49
|
-
{}
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
return reply.status(200).send({ time: Date.now() - t1, uid, settings });
|
|
17
|
+
return { time: Date.now() - t1, uid: user?.uid, settings: result };
|
|
53
18
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { config, pgClients, getRedis } from "@opengis/fastify-table/utils.js";
|
|
2
|
+
|
|
3
|
+
const rclient = getRedis();
|
|
4
|
+
|
|
5
|
+
export default async function getSettings({ entity = 'app', keys = ['cms'], uid = 0, ttl = 3600, pg = pgClients.client } = {}) {
|
|
6
|
+
if (!pg) {
|
|
7
|
+
return { error: "empty pg", code: 500 };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (!pg.pk?.["admin.properties"]) {
|
|
11
|
+
return { error: "properties table not found", code: 404 };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const cacheKey = [config.pg?.database, "cms:settings", entity, keys, uid].filter(Boolean).join(":");
|
|
15
|
+
const cache = ttl === 0 ? null : await rclient.get(cacheKey);
|
|
16
|
+
|
|
17
|
+
// return from cache
|
|
18
|
+
if (cache) {
|
|
19
|
+
return { cache: true, ...JSON.parse(cache) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const rows = await pg.query(
|
|
23
|
+
"select property_key as key, property_text, property_json from admin.properties where property_entity=$1",
|
|
24
|
+
entity === "user" ? [uid] : [entity || "app"]
|
|
25
|
+
).then(el => el.rows || []);
|
|
26
|
+
|
|
27
|
+
const settings = rows
|
|
28
|
+
.filter((row) =>
|
|
29
|
+
typeof keys === "string" ? keys.includes(row.key) : true
|
|
30
|
+
)
|
|
31
|
+
.reduce(
|
|
32
|
+
(
|
|
33
|
+
acc,
|
|
34
|
+
{ key, property_text, property_json }
|
|
35
|
+
) => ({
|
|
36
|
+
...acc,
|
|
37
|
+
[key]: property_text || property_json,
|
|
38
|
+
}),
|
|
39
|
+
{}
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// save to cache, default = 1 hour
|
|
43
|
+
if (ttl) await rclient.set(cacheKey, JSON.stringify(settings), 'EX', ttl);
|
|
44
|
+
|
|
45
|
+
return settings;
|
|
46
|
+
}
|
|
@@ -1,43 +1,25 @@
|
|
|
1
|
-
import
|
|
1
|
+
import getMenu from "../functions/getMenu.js";
|
|
2
2
|
|
|
3
3
|
const limit = 16;
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
export default async function getMenuAPI(req, reply) {
|
|
6
|
+
const { query = {}, params = {} } = req;
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
const result = await getMenu(params.id, {
|
|
9
|
+
locale: query.locale,
|
|
10
|
+
page: query.page,
|
|
11
|
+
limit: Math.min(query.limit || limit, limit),
|
|
12
|
+
sql: query.sql,
|
|
13
|
+
ttl: 3600
|
|
14
|
+
});
|
|
9
15
|
|
|
10
|
-
if (
|
|
11
|
-
return reply.status(
|
|
16
|
+
if (result.error) {
|
|
17
|
+
return reply.status(result.code || 500).send(result);
|
|
12
18
|
}
|
|
13
19
|
|
|
14
|
-
if (!pg?.pk?.['site.menus']) {
|
|
15
|
-
return reply.status(404).send('table not found');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const offset = query.page && query.page > 0 ? (query.page - 1) * limit : 0;
|
|
19
|
-
|
|
20
|
-
const q = `select menu_id, name, locale, description ${params.id ? ',items::json, content' : ''} from site.menus
|
|
21
|
-
where ${params.id ? '$1 in (menu_id, name)' : '1=1'}
|
|
22
|
-
and ${query.locale ? `locale='${query.locale.replace(/'/g, "''")}'` : 'true'}
|
|
23
|
-
order by (case when locale='uk' then false else true end)
|
|
24
|
-
limit ${limit} offset ${offset}`;
|
|
25
|
-
|
|
26
|
-
if (query.sql) return q;
|
|
27
|
-
|
|
28
|
-
const rows = await pg.query(q, [params.id].filter(Boolean))
|
|
29
|
-
.then(el => el.rows || []);
|
|
30
|
-
|
|
31
20
|
if (params.id) {
|
|
32
|
-
|
|
33
|
-
return reply.status(404).send(err);
|
|
34
|
-
}
|
|
35
|
-
return reply.status(200).send(rows[0]);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (!rows.length && params.id) {
|
|
39
|
-
return reply.status(404).send(err);
|
|
21
|
+
return reply.status(200).send(result);
|
|
40
22
|
}
|
|
41
23
|
|
|
42
|
-
return reply.status(200).send({ rows });
|
|
24
|
+
return reply.status(200).send({ rows: result.rows });
|
|
43
25
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { config, pgClients, getRedis } from "@opengis/fastify-table/utils.js";
|
|
4
|
+
|
|
5
|
+
const rclient = getRedis();
|
|
6
|
+
|
|
7
|
+
export default async function getMenu(name, { locale, ttl = 3600, page, limit = 16, sql, pg = pgClients.client } = {}) {
|
|
8
|
+
if (!pg?.pk) {
|
|
9
|
+
return { error: 'empty pg', code: 400 };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (!pg?.pk?.['site.menus']) {
|
|
13
|
+
return { error: 'table not found', code: 404 };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const cacheKey = createHash('md5').update([config.pg?.database, 'cms:menu', name, locale].filter(Boolean).join(':')).digest('hex');
|
|
17
|
+
|
|
18
|
+
const cache = ttl === 0 ? null : await rclient.get(cacheKey);
|
|
19
|
+
|
|
20
|
+
// return from cache
|
|
21
|
+
if (cache) {
|
|
22
|
+
return { cache: true, ...JSON.parse(cache) };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const offset = page && page > 0 ? (page - 1) * limit : 0;
|
|
26
|
+
const args = name ? [name, locale].filter(Boolean) : [locale].filter(Boolean);
|
|
27
|
+
const where = name ?
|
|
28
|
+
`$1 in (menu_id, name) and ${locale ? `locale=$2` : 'true'}`
|
|
29
|
+
: (locale ? `locale=$1` : 'true');
|
|
30
|
+
|
|
31
|
+
const q = `select menu_id, name, locale, description ${name ? ', items::json, content' : ''}
|
|
32
|
+
from site.menus
|
|
33
|
+
where ${where}
|
|
34
|
+
order by (case when locale='uk' then false else true end)
|
|
35
|
+
limit ${limit}
|
|
36
|
+
offset ${offset}`;
|
|
37
|
+
|
|
38
|
+
if (sql) return q;
|
|
39
|
+
|
|
40
|
+
const rows = pg ? await pg.query(q, args).then(el => el.rows || []) : [];
|
|
41
|
+
|
|
42
|
+
const payload = name ? (rows[0] || { error: 'menu not found', code: 404 }) : { rows };
|
|
43
|
+
|
|
44
|
+
// save to cache, default = 1 hour
|
|
45
|
+
if (ttl) await rclient.set(cacheKey, JSON.stringify(payload), 'EX', ttl);
|
|
46
|
+
|
|
47
|
+
return payload;
|
|
48
|
+
}
|
package/utils.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as getMenu } from "./server/routes/menu/functions/getMenu.js";
|
|
2
|
+
export { default as getContent } from "./server/functions/getContent.js";
|
|
3
|
+
export { default as getSettings } from "./server/routes/cms/functions/getSettings.js";
|
|
4
|
+
export { default as getUser } from "./server/functions/getUser.js";
|
|
5
|
+
|
|
6
|
+
export default null;
|