@opengis/admin 0.2.122 → 0.2.123

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. package/dist/{add-page-D9BweG1f.js → add-page-CNaov3n_.js} +1 -1
  2. package/dist/{admin-interface-Bq8kf59d.js → admin-interface-Bfl5z6ck.js} +582 -732
  3. package/dist/{admin-view-pmWjnncn.js → admin-view-DYePX_Un.js} +1 -1
  4. package/dist/admin.js +2 -2
  5. package/dist/admin.umd.cjs +50 -50
  6. package/dist/{card-view-DNKItKZ_.js → card-view-OkM8SWMi.js} +1 -1
  7. package/dist/{edit-page-DmanLFQC.js → edit-page-BXf-hnHj.js} +1 -1
  8. package/dist/{import-file-B3hz_TTe.js → import-file-Cds3w-U5.js} +15024 -14822
  9. package/dist/{profile-page-CnXrMOV_.js → profile-page-B7ofAYKr.js} +1 -1
  10. package/dist/style.css +1 -1
  11. package/module/settings/menu.json +21 -13
  12. package/package.json +3 -3
  13. package/server/plugins/hook.js +13 -1
  14. package/server/routes/menu/controllers/getMenu.js +9 -3
  15. package/server/routes/print/controllers/printTemplate.add.js +37 -0
  16. package/server/routes/print/controllers/printTemplate.delete.js +29 -0
  17. package/server/routes/print/controllers/printTemplate.edit.js +42 -0
  18. package/server/routes/print/controllers/printTemplate.js +24 -79
  19. package/server/routes/print/controllers/printTemplateList.js +20 -0
  20. package/server/routes/print/controllers/printTemplatePreview.js +81 -0
  21. package/server/routes/print/index.mjs +14 -2
  22. package/server/routes/properties/controllers/admin.properties.post.js +2 -2
  23. package/server/routes/properties/index.mjs +1 -1
  24. package/server/routes/report/controllers/data.js +76 -0
  25. package/server/routes/report/controllers/list.js +18 -0
  26. package/server/routes/report/index.mjs +7 -0
  27. package/server/routes/report/utils/formatValue.js +179 -0
  28. package/server/routes/report/utils/getFilterQuery.js +67 -0
@@ -39,26 +39,34 @@
39
39
  "icon": "IconSettingsCog",
40
40
  "menu": [
41
41
  {
42
- "path": "admin.user-cls",
43
- "ua": "Довідники ",
44
- "component": "user-cls-page"
45
- },
46
- {
47
- "path": "admin.properties",
48
- "table": "admin.properties.table",
42
+ "path": "admin.settings",
43
+ "component": "admin-properties-page" ,
49
44
  "ua": "Налаштування",
50
45
  "en": "Settings"
51
46
  },
52
47
  {
53
- "path": "admin.table_settings",
54
- "ua": "Налаштування таблиць",
48
+ "path": "admin.templates",
49
+ "ua": "Друк ",
50
+ "component": "template-print-page"
51
+ },
52
+ {
53
+ "path": "admin.interfaces",
54
+ "ua": "Таблиці",
55
55
  "component": "table-settings-page"
56
56
  },
57
57
  {
58
- "path": "admin.admin_properties",
59
- "ua": "Налаштування адміна",
60
- "component": "admin-properties-page"
61
- }
58
+ "path": "admin.user-cls",
59
+ "ua": "Довідники ",
60
+ "component": "user-cls-page"
61
+ },
62
+ {
63
+ "path": "admin.reports",
64
+ "ua": "Звіти  ",
65
+ "component": "reports-page"
66
+ },
67
+ { "path": "admin.admin_properties",
68
+ "ua": "Налаштування адміна",
69
+ "table": "admin.properties.table"}
62
70
  ]
63
71
  }
64
72
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/admin",
3
- "version": "0.2.122",
3
+ "version": "0.2.123",
4
4
  "description": "This project Softpro Admin",
5
5
  "main": "dist/admin.js",
6
6
  "type": "module",
@@ -48,9 +48,9 @@
48
48
  "@fullcalendar/vue3": "^6.1.15",
49
49
  "@opengis/fastify-auth": "^1.0.70",
50
50
  "@opengis/fastify-file": "^1.0.42",
51
- "@opengis/fastify-table": "^1.2.10",
51
+ "@opengis/fastify-table": "^1.2.11",
52
52
  "@opengis/v3-core": "^0.3.45",
53
- "@opengis/v3-filter": "^0.0.68",
53
+ "@opengis/v3-filter": "^0.0.71",
54
54
  "@tabler/icons-vue": "^3.28.1",
55
55
  "@tiptap/core": "^2.8.0",
56
56
  "@tiptap/extension-color": "^2.8.0",
@@ -83,10 +83,22 @@ export default async function plugin(fastify) {
83
83
  const printTemplateList = getTemplatePath('print');
84
84
  printTemplateList.filter(el => el[2] === 'json').map((el) => {
85
85
  const settings = JSON.parse(readFileSync(el[1]) || '{}');
86
+ const htmlPath = printTemplateList.find(item => item[0] === el[0] && ['hbs', 'html'].includes(item[2]))?.[1];
87
+ const html = htmlPath ? readFileSync(htmlPath, 'utf-8') : null;
88
+ Object.assign(settings, { html });
86
89
  printTemplates[el[0]] = settings;
87
90
  });
88
91
  if (client?.pk?.['admin.templates']) {
89
- console.log('insert into admin.templates', printTemplateList); // test!
92
+ const arr = Object.keys(printTemplates || {}).map(el => ({ name: el, ...printTemplates?.[el] || {} }));
93
+
94
+ const { rowsCount = 0 } = await pgClients.client.query(`delete from admin.templates where not (name=any($1::text[])) and type = 'demo'`, [arr.map(el => el.name)]);
95
+ console.log('delete deprecated templates', 'ok', rowsCount);
96
+ const { rowsCount: empty = 0 } = await pgClients.client.query('delete from admin.templates where body is null');
97
+ console.log('delete empty templates', 'ok', empty);
98
+
99
+ const q = arr.map(el => `insert into admin.templates(name, route_id, title, type, body) values ('${el.name.replace(/'/g, "''")}', '${el.route.replace(/'/g, "''")}', '${el.title.replace(/'/g, "''")}', 'demo', '${(el.html || '').replace(/'/g, "''")}') on conflict(name, type) do update set route_id=excluded.route_id, title=excluded.title`).join(';');
100
+ const res = await pgClients.client.query(q);
101
+ console.log('insert print templates', 'ok', (Array.isArray(res) ? res : [res]).length);
90
102
  }
91
103
  });
92
104
 
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { join } from 'path';
3
- import { userTemplateDir, config, getAccess, pgClients } from '@opengis/fastify-table/utils.js';
3
+ import { userTemplateDir, config, pgClients } from '@opengis/fastify-table/utils.js';
4
4
 
5
5
  import { existsSync, readdirSync, readFileSync } from 'fs';
6
6
  const menuCache = [];
@@ -38,8 +38,14 @@ export default async function adminMenu({ user = {}, session, pg = pgClients.cli
38
38
 
39
39
  if (session && user?.uid && !config.local && !config.admin && !user.user_type?.includes?.('admin') && !user.type?.includes?.('admin') && pg.pk['admin.role_access']) {
40
40
  const { type, gl = [], routes = [] } = await pg.query(`select user_type as type, b.gl,routes from admin.users a
41
- left join lateral ( select array_agg(role_id) as gl from admin.user_roles where user_uid=a.uid)b on 1=1
42
- left join lateral ( select array_agg(route_id) as routes from admin.role_access where role_id=any(b.gl))r on 1=1
41
+ left join lateral (
42
+ select array_agg(role_id) as gl from admin.user_roles
43
+ where user_uid=a.uid
44
+ and role_id in ( select role_id from admin.roles where enabled)
45
+ )b on 1=1
46
+ left join lateral (
47
+ select array_agg(route_id) as routes from admin.role_access where role_id=any(b.gl)
48
+ )r on 1=1
43
49
  where uid=$1`, [user.uid]).then(el => el.rows[0] || {});
44
50
  /* const { interfaces = [] } = await pg.query(`select array_agg(route_id) as interfaces from admin.role_access
45
51
  where user_uid=$1 or role_id=any($2::text[])`, [user.uid, gl]).then((res) => res.rows?.[0] || {}); */
@@ -0,0 +1,37 @@
1
+ import { pgClients, dataInsert } from '@opengis/fastify-table/utils.js';
2
+
3
+ export default async function printTemplateAdd({
4
+ pg = pgClients.client, body = {}, user = {},
5
+ }) {
6
+ if (!user?.uid) {
7
+ return { message: 'access restricted', status: 403 };
8
+ }
9
+
10
+ const { name, title, route, html } = body;
11
+
12
+ if (!name) {
13
+ return { message: 'not enough body params: name', status: 400 };
14
+ }
15
+
16
+ if (!html) {
17
+ return { message: 'not enough body params: html', status: 400 };
18
+ }
19
+
20
+ const id = await pg.query(`select template_id as id from admin.templates where $1 in (template_id, name)`, [name])
21
+ .then(el => el.rows?.[0]?.id);
22
+
23
+ if (id) {
24
+ return { message: 'access restricted: template exists', status: 403 };
25
+ }
26
+
27
+ const res = await dataInsert({
28
+ pg,
29
+ table: 'admin.templates',
30
+ data: { name, title, route_id: route, body: html, type: 'user' },
31
+ uid: user?.uid,
32
+ })
33
+
34
+ const data = res.rows?.[0];
35
+
36
+ return { id: data?.template_id, data };
37
+ }
@@ -0,0 +1,29 @@
1
+ import { pgClients, dataDelete } from '@opengis/fastify-table/utils.js';
2
+
3
+ export default async function printTemplateDelete({
4
+ pg = pgClients.client, params = {}, user = {},
5
+ }) {
6
+ if (!user?.uid) {
7
+ return { message: 'access restricted', status: 403 };
8
+ }
9
+
10
+ const { id, type } = await pg.query(`select template_id as id, type from admin.templates where enabled and $1 in (template_id, name)`, [params.id])
11
+ .then(el => el.rows?.[0] || {});
12
+
13
+ if (!id) {
14
+ return { message: 'template not found', status: 404 };
15
+ }
16
+
17
+ if (type === 'demo') {
18
+ return { message: 'access restricted: demo', status: 403 };
19
+ }
20
+
21
+ const res = await dataDelete({
22
+ pg,
23
+ table: 'admin.templates',
24
+ id,
25
+ uid: user?.uid,
26
+ });
27
+
28
+ return { id: res?.template_id, data: res };
29
+ }
@@ -0,0 +1,42 @@
1
+ import { pgClients, dataInsert } from '@opengis/fastify-table/utils.js';
2
+
3
+ export default async function printTemplateEdit({
4
+ pg = pgClients.client, params = {}, body = {}, user = {},
5
+ }) {
6
+ if (!user?.uid) {
7
+ return { message: 'access restricted', status: 403 };
8
+ }
9
+
10
+ if (!params?.id) {
11
+ return { message: 'not enough params: id', status: 400 };
12
+ }
13
+
14
+ const { html, route, title } = body;
15
+
16
+ if (!html) {
17
+ return { message: 'not enough body params: html', status: 400 };
18
+ }
19
+
20
+ const { id, name } = await pg.query(`select template_id as id, name from admin.templates where $1 in (template_id, name)`, [params.id])
21
+ .then(el => el.rows?.[0] || {});
22
+
23
+ if (!id) {
24
+ return { message: 'template not found', status: 404 };
25
+ }
26
+
27
+ await pg.query(`delete from admin.templates where $1 in (template_id, name)`, [id]);
28
+
29
+ const data = { name, body: html, type: 'user' };
30
+ if (title) Object.assign(data, { title });
31
+ if (route) Object.assign(data, { route_id: route });
32
+
33
+ const res = await dataInsert({
34
+ pg,
35
+ table: 'admin.templates',
36
+ id,
37
+ data,
38
+ uid: user?.uid,
39
+ });
40
+
41
+ return { id: res.rows?.[0]?.template_id, data: res.rows?.[0] };
42
+ }
@@ -3,8 +3,6 @@ import { createHash } from 'node:crypto';
3
3
  import { config, getFilterSQL, getTemplate, handlebars, pgClients } from '@opengis/fastify-table/utils.js';
4
4
  import { grpc } from '@opengis/fastify-file/utils.js';
5
5
 
6
- import printTemplates from './printTemplates.js';
7
-
8
6
  const { htmlToPdf } = grpc();
9
7
 
10
8
  export default async function printTemplate(req, reply) {
@@ -12,110 +10,57 @@ export default async function printTemplate(req, reply) {
12
10
  pg = pgClients.client,
13
11
  params = {},
14
12
  query = {},
15
- user = {},
16
13
  } = req;
17
14
 
18
15
  if (!params?.name) {
19
16
  return { message: 'not enough params: name', status: 400 };
20
17
  }
21
18
 
22
- const printBody = await getTemplate('print', params.name);
23
-
24
- /* -- params.name === interface -- */
25
- if (!printBody?.route) {
26
- const where = user?.user_type?.includes('admin')
27
- ? '1=1'
28
- : `coalesce(b.user_uid, d.user_uid)='${user?.uid?.replace(/'/g, "''")}'::text`;
29
-
30
- const { route_id: routeId, alias } = await pg.query(`select a.route_id, alias
31
- from admin.routes a
32
- left join admin.role_access b on
33
- a.route_id=b.route_id
34
- left join admin.roles c on
35
- b.role_id=c.role_id
36
- and c.enabled
37
- left join admin.user_roles d on
38
- c.role_id=d.role_id
39
- and ( case when
40
- d.expiration is not null
41
- then d.expiration > CURRENT_DATE
42
- else 1=1
43
- end )
44
- where $1 in (a.route_id, a.alias, a.table_name) and ${where}`, [params.name])
45
- .then(el => el.rows?.[0] || {});
46
-
47
- if (!routeId || !alias) {
48
- return { message: `access restricted: route (${params.name}/${routeId})`, status: 403 };
49
- }
50
-
51
- const rows = Object.keys(printTemplates)
52
- .filter(key => printTemplates[key]?.route === routeId)
53
- .map(key => ({ key, title: printTemplates[key]?.title }));
54
-
55
- return { rows };
19
+ if (!params?.id) {
20
+ return { message: 'not enough params: id', status: 400 };
56
21
  }
57
22
 
58
- if (!query.preview && !query?.demo && !params?.id) {
59
- return { message: 'not enough params: id', status: 400 };
23
+ const { id, route, body = '' } = await pg.query(`select template_id as id, body, route_id as route from admin.templates where $1 in (template_id,name)`, [params.name])
24
+ .then(el => el.rows?.[0] || {});
25
+
26
+ if (!id) {
27
+ return reply.status(404).send('template not found');
60
28
  }
61
29
 
62
30
  /* -- params.name === document template -- */
63
31
  const format = query.format || (config.debug ? 'html' : 'pdf');
64
32
 
65
33
  const hash = createHash('md5')
66
- .update([query?.preview, query?.demo, params?.name, params?.id].join())
34
+ .update([params?.name, params?.id].join())
67
35
  .digest('hex');
68
36
 
69
37
  const headers = format === 'pdf'
70
38
  ? { 'Content-Disposition': `inline; filename=${hash}.pdf`, 'Content-Type': 'application/pdf' }
71
39
  : { 'Content-Type': 'text/html; charset=utf-8' };
72
40
 
73
- const pt = printBody?.hbs || printBody?.html;
74
-
75
- if (query?.preview) {
76
- const matches = pt.match(/{{(?!\!)([^}]*)}}/g);
77
- const preview = `<style> #toggle { background: yellow; } </style>`
78
- + matches.reduce((acc, curr) => acc.replace(curr, curr.replace(/{{(?!\!)([^}]*)}}/g, `<div id="toggle">${curr.replace(/{/g, '%7B').replace(/}/g, '%7D')}</div>`)), pt);
79
-
80
- const html = await handlebars.compile(preview)({});
41
+ const table = await pg.query(`select alias from admin.routes where route_id=$1`, [route])
42
+ .then(el => el.rows?.[0]?.alias);
81
43
 
82
- if (format == 'html') {
83
- return reply.headers(headers).send(html.replace(/%7B/g, '{').replace(/%7D/g, '}'));
84
- }
85
-
86
- const result = await htmlToPdf({ html: html.replace(/%7B/g, '{').replace(/%7D/g, '}') });
87
- const buffer = Buffer.from(result.result, 'base64');
88
- return reply.headers(headers).send(buffer);
44
+ if (!table) {
45
+ return { message: 'route table not found', status: 404 };
89
46
  }
90
47
 
91
- if (query?.demo || params?.id) {
92
- const table = await pg.query(`select alias from admin.routes where route_id=$1`, [printBody.route])
93
- .then(el => el.rows?.[0]?.alias);
94
-
95
- if (!table) {
96
- return { message: 'route table not found', status: 404 };
97
- }
98
-
99
- const loadTable = await getTemplate('table', table);
100
- const { optimizedSQL } = await getFilterSQL({ table, pg });
48
+ const loadTable = await getTemplate('table', table);
49
+ const { optimizedSQL } = await getFilterSQL({ table, pg });
101
50
 
102
- const where = query.demo
103
- ? ' 1=1 limit 1'
104
- : `${loadTable?.key || pg.pk?.[loadTable?.table || table]}=$1`;
51
+ const where = `${loadTable?.key || pg.pk?.[loadTable?.table || table]}=$1`;
105
52
 
106
- const q = `select * from (${optimizedSQL})q where ${loadTable?.query || '1=1'} and ${where}`;
53
+ const q = `select * from (${optimizedSQL})q where ${loadTable?.query || '1=1'} and ${where}`;
107
54
 
108
- const obj = await pg.query(q, [params.id].filter(el => el && !query.demo))
109
- .then(el => el.rows?.[0] || {});
55
+ const obj = await pg.query(q, [params.id]).then(el => el.rows?.[0] || {});
110
56
 
111
- const html = await handlebars.compile(pt)(obj);
57
+ const html = await handlebars.compile(body)(obj);
112
58
 
113
- if (format == 'html') {
114
- return reply.headers(headers).send(html);
115
- }
116
-
117
- const result = await htmlToPdf({ html });
118
- const buffer = Buffer.from(result.result, 'base64');
119
- return reply.headers(headers).send(buffer);
59
+ if (format == 'html') {
60
+ return reply.headers(headers).send(html);
120
61
  }
62
+
63
+ const result = await htmlToPdf({ html });
64
+ const buffer = Buffer.from(result.result, 'base64');
65
+ return reply.headers(headers).send(buffer);
121
66
  }
@@ -0,0 +1,20 @@
1
+ import { pgClients } from "@opengis/fastify-table/utils.js";
2
+
3
+ const maxLimit = 100;
4
+
5
+ export default async function printTemplateList({
6
+ pg = pgClients.client, query = {},
7
+ }) {
8
+ const { page = 1 } = query;
9
+
10
+ const limit = Math.min(maxLimit, +(query.limit || 20));
11
+
12
+ const offset = page && page > 0 ? (page - 1) * limit : '0';
13
+
14
+ const q = `select template_id as id, name, title, route_id as route, type from admin.templates
15
+ where enabled limit ${limit} offset ${offset}`;
16
+
17
+ const { rows = [] } = pg.pk?.['admin.templates'] ? await pg.query(q) : {};
18
+
19
+ return { page, limit, rows };
20
+ }
@@ -0,0 +1,81 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ import { config, getFilterSQL, getTemplate, handlebars, pgClients } from '@opengis/fastify-table/utils.js';
4
+ import { grpc } from '@opengis/fastify-file/utils.js';
5
+
6
+ const { htmlToPdf } = grpc();
7
+
8
+ export default async function printTemplate(req, reply) {
9
+ const {
10
+ pg = pgClients.client,
11
+ params = {},
12
+ query = {},
13
+ } = req;
14
+
15
+ if (!params?.name) {
16
+ return reply.status(404).send('not enough params: name');
17
+ }
18
+
19
+ const { id, name, title, body = '', route } = await pg.query(`select template_id as id, name, title, body, route_id as route from admin.templates where $1 in (template_id,name)`, [params.name])
20
+ .then(el => el.rows?.[0] || {});
21
+
22
+ if (!id) {
23
+ return reply.status(404).send('template not found');
24
+ }
25
+
26
+ /* -- params.name === document template -- */
27
+ const format = query.format || (config.debug ? 'html' : 'pdf');
28
+
29
+ const hash = createHash('md5')
30
+ .update([query?.preview, query?.demo, params?.name].join())
31
+ .digest('hex');
32
+
33
+ const headers = format === 'pdf'
34
+ ? { 'Content-Disposition': `inline; filename=${hash}.pdf`, 'Content-Type': 'application/pdf' }
35
+ : { 'Content-Type': 'text/html; charset=utf-8' };
36
+
37
+ if (query?.preview) {
38
+ const matches = body?.match?.(/{{(?!\!)([^}]*)}}/g) || [];
39
+ const preview = `<style> #toggle { background: yellow; } </style>`
40
+ + matches.reduce((acc, curr) => acc.replace(curr, curr.replace(/{{(?!\!)([^}]*)}}/g, `<div id="toggle">${curr.replace(/{/g, '%7B').replace(/}/g, '%7D')}</div>`)), body);
41
+
42
+ const html = await handlebars.compile(preview)({});
43
+
44
+ if (format == 'html') {
45
+ return reply.headers(headers).send(html.replace(/%7B/g, '{').replace(/%7D/g, '}'));
46
+ }
47
+
48
+ const result = await htmlToPdf({ html: html.replace(/%7B/g, '{').replace(/%7D/g, '}') });
49
+ const buffer = Buffer.from(result.result, 'base64');
50
+ return reply.headers(headers).send(buffer);
51
+ }
52
+
53
+ if (query?.demo) {
54
+ const table = await pg.query(`select alias from admin.routes where route_id=$1`, [route])
55
+ .then(el => el.rows?.[0]?.alias);
56
+
57
+ if (!table) {
58
+ return reply.status(404).send('route table not found');
59
+ }
60
+
61
+ const loadTable = await getTemplate('table', table);
62
+ const { optimizedSQL } = await getFilterSQL({ table, pg });
63
+
64
+ const q = `select * from (${optimizedSQL})q where ${loadTable?.query || '1=1'} limit 1`;
65
+
66
+ const obj = await pg.query(q).then(el => el.rows?.[0] || {});
67
+
68
+ const html = await handlebars.compile(body)(obj);
69
+
70
+ if (format == 'html') {
71
+ return reply.headers(headers).send(html);
72
+ }
73
+
74
+ const result = await htmlToPdf({ html });
75
+ const buffer = Buffer.from(result.result, 'base64');
76
+ return reply.headers(headers).send(buffer);
77
+ }
78
+
79
+ // for body edit
80
+ return { name, title, html: body, route };
81
+ }
@@ -1,7 +1,19 @@
1
1
  import cardPrint from './controllers/cardPrint.js';
2
+ import printTemplateAdd from './controllers/printTemplate.add.js';
3
+ import printTemplateEdit from './controllers/printTemplate.edit.js';
4
+ import printTemplateDelete from './controllers/printTemplate.delete.js';
2
5
  import printTemplate from './controllers/printTemplate.js';
6
+ import printTemplatePreview from './controllers/printTemplatePreview.js';
7
+ import printTemplateList from './controllers/printTemplateList.js';
8
+
9
+ const policy = ['user'];
3
10
 
4
11
  export default async function route(app) {
5
- app.get(`/card-print/:table/:id`, cardPrint);
6
- app.get('/print-template/:name/:id?', printTemplate);
12
+ app.get(`/card-print/:table/:id`, { config: { policy } }, cardPrint);
13
+ app.get('/print-template/:name/:id', { config: { policy } }, printTemplate);
14
+ app.get('/print-template/:name', { config: { policy } }, printTemplatePreview);
15
+ app.get('/print-template', { config: { policy } }, printTemplateList);
16
+ app.post('/print-template', { config: { policy } }, printTemplateAdd);
17
+ app.put('/print-template/:id', { config: { policy } }, printTemplateEdit);
18
+ app.delete('/print-template/:id', { config: { policy } }, printTemplateDelete);
7
19
  }
@@ -3,9 +3,9 @@ const table = 'admin.properties';
3
3
  import { setSettings } from '../../../../utils.js';
4
4
 
5
5
  export default async function postSettingsApp({
6
- pg, session = {}, body = {},
6
+ pg, user = {}, body = {},
7
7
  }) {
8
- if (session.passport?.user?.user_type !== 'superadmin') {
8
+ if (!user?.user_type?.includes?.('admin')) {
9
9
  return { message: 'access restricted', status: 403 };
10
10
  }
11
11
  const { key, val } = body;
@@ -11,7 +11,7 @@ import { propertiesSchema } from './schema.js';
11
11
 
12
12
  export default async function route(fastify) {
13
13
  fastify.get('/settings-app/:key?', { scheme: propertiesSchema }, getSettingsApp);
14
- fastify.post('/settings-app', { config: { policy: ['superadmin'] } }, postSettingsApp);
14
+ fastify.post('/settings-app', { config: { policy: ['admin'] } }, postSettingsApp);
15
15
 
16
16
  fastify.get('/settings-user/:key?', { scheme: propertiesSchema }, getSettingsUser);
17
17
  fastify.post('/settings-user', {}, postSettingsUser);
@@ -0,0 +1,76 @@
1
+ import { handlebars, pgClients, getTemplate, metaFormat, handlebarsSync } from '@opengis/fastify-table/utils.js';
2
+
3
+ import getFilterQuery from '../utils/getFilterQuery.js';
4
+
5
+ const maxLimit = 100;
6
+
7
+ export default async function reportData({
8
+ pg = pgClients.client, params = {}, query = {}, user = {},
9
+ }) {
10
+ if (!params?.name) {
11
+ return { message: 'not enough params: name', status: 400 };
12
+ }
13
+
14
+ const loadTemplate = await getTemplate('report', params.name);
15
+
16
+ if (!loadTemplate?.sql) {
17
+ return { message: `report not found: ${params.name}`, status: 404 };
18
+ }
19
+
20
+ const { uid } = user;
21
+
22
+ const { kpi, sql, meta, filters } = loadTemplate;
23
+ const { date, columns: metaColumns } = meta || {};
24
+
25
+ const granularity = query.granularity && date && false ? `date_trunc('${query.granularity}',${date})::date::text` : null;
26
+ const groupby = [meta?.groupby, granularity].filter(el => el).join(',');
27
+
28
+ const period = query.period && date ? `${date}=${query.period}` : null;
29
+ const filter = [query.filter, period].filter(el => el).join(';');
30
+
31
+ const limit = Math.min(maxLimit, +(query.limit || 20));
32
+ const offset = query.page && query.page > 0 ? (query.page - 1) * limit : 0;
33
+
34
+ const kpiColumns = kpi ? kpi?.map(el => `${el.sql || 'count(*)'} as "${el.name}"`)?.join(',') : '';
35
+ const columnsList = metaColumns || loadTemplate.columns?.map(el => el.name)?.join(',');
36
+
37
+ const qMeta = await handlebars.compile(sql)({ user, uid });
38
+ const { fields = [] } = await pg.query(`select * from (${qMeta})q limit 0`);
39
+
40
+ const where = filter ? getFilterQuery({ pg, filter, fields, filterList: filters })?.map?.(el => el.query)?.join(' and ') : null;
41
+
42
+ const qAgg = `select ${kpiColumns || ''} from (${qMeta})q where ${where || '1=1'}`;
43
+ const q = `select ${columnsList || '*'} from (${qMeta})q where ${where || '1=1'} ${groupby ? `group by ${groupby}` : ''} limit ${limit} offset ${offset}`;
44
+
45
+ if (query.sql && user?.user_type?.includes('admin')) return `${qAgg};${q}`;
46
+
47
+ const kpiData = await pg.query(qAgg).then(el => el.rows?.[0] || {});
48
+ kpi?.forEach(el => Object.assign(el, { count: kpiData[el.name] || '0' }));
49
+
50
+ const { rows = [] } = await pg.query(q);
51
+
52
+ const cls = meta?.cls || loadTemplate.columns
53
+ ?.filter(el => (el.option || el.data))
54
+ ?.reduce((acc, curr) => Object.assign(acc, { [curr.name]: (curr.option || curr.data) }), {});
55
+
56
+ const titles = meta?.titles
57
+ || loadTemplate.columns?.reduce((acc, curr) => Object.assign(acc, { [curr.name]: curr.title }), {});
58
+
59
+ await metaFormat({ rows, cls, sufix: true });
60
+
61
+ const columns = loadTemplate.columns
62
+ || fields.map(el => ({
63
+ name: el.name,
64
+ title: titles?.[el.name] || el.name,
65
+ type: cls?.[el.name] ? 'Autocomplete' : 'Text',
66
+ format: pg.pgType?.[el.dataTypeID],
67
+ data: cls?.[el.name],
68
+ }));
69
+
70
+ return {
71
+ q: user?.user_type?.includes('admin') ? q : undefined,
72
+ kpi,
73
+ rows,
74
+ columns,
75
+ };
76
+ }
@@ -0,0 +1,18 @@
1
+ import { getTemplatePath, getTemplate } from '@opengis/fastify-table/utils.js';
2
+
3
+ export default async function reportList({
4
+ user = {},
5
+ }) {
6
+ const arr = getTemplatePath('report').filter(el => el[2] === 'json').map(el => el[0]);
7
+
8
+ const rows = await Promise.all(arr.map(async (el) => {
9
+ const loadTemplate = await getTemplate('report', el);
10
+ return {
11
+ name: el,
12
+ filters: loadTemplate?.filters || [],
13
+ title: loadTemplate?.title || el,
14
+ sql: user.user_type?.includes('admin') ? loadTemplate?.sql : undefined,
15
+ };
16
+ }));
17
+ return { rows };
18
+ }
@@ -0,0 +1,7 @@
1
+ import reportData from './controllers/data.js';
2
+ import reportList from './controllers/list.js';
3
+
4
+ export default async function route(app) {
5
+ app.get('/reports', {}, reportList);
6
+ app.get('/reports/:name', {}, reportData);
7
+ }