@opengis/admin 0.2.122 → 0.2.123
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/dist/{add-page-D9BweG1f.js → add-page-CNaov3n_.js} +1 -1
- package/dist/{admin-interface-Bq8kf59d.js → admin-interface-Bfl5z6ck.js} +582 -732
- package/dist/{admin-view-pmWjnncn.js → admin-view-DYePX_Un.js} +1 -1
- package/dist/admin.js +2 -2
- package/dist/admin.umd.cjs +50 -50
- package/dist/{card-view-DNKItKZ_.js → card-view-OkM8SWMi.js} +1 -1
- package/dist/{edit-page-DmanLFQC.js → edit-page-BXf-hnHj.js} +1 -1
- package/dist/{import-file-B3hz_TTe.js → import-file-Cds3w-U5.js} +15024 -14822
- package/dist/{profile-page-CnXrMOV_.js → profile-page-B7ofAYKr.js} +1 -1
- package/dist/style.css +1 -1
- package/module/settings/menu.json +21 -13
- package/package.json +3 -3
- package/server/plugins/hook.js +13 -1
- package/server/routes/menu/controllers/getMenu.js +9 -3
- package/server/routes/print/controllers/printTemplate.add.js +37 -0
- package/server/routes/print/controllers/printTemplate.delete.js +29 -0
- package/server/routes/print/controllers/printTemplate.edit.js +42 -0
- package/server/routes/print/controllers/printTemplate.js +24 -79
- package/server/routes/print/controllers/printTemplateList.js +20 -0
- package/server/routes/print/controllers/printTemplatePreview.js +81 -0
- package/server/routes/print/index.mjs +14 -2
- package/server/routes/properties/controllers/admin.properties.post.js +2 -2
- package/server/routes/properties/index.mjs +1 -1
- package/server/routes/report/controllers/data.js +76 -0
- package/server/routes/report/controllers/list.js +18 -0
- package/server/routes/report/index.mjs +7 -0
- package/server/routes/report/utils/formatValue.js +179 -0
- 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. | 
| 43 | 
            -
                    " | 
| 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. | 
| 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. | 
| 59 | 
            -
                    "ua": " | 
| 60 | 
            -
                    "component": " | 
| 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. | 
| 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. | 
| 51 | 
            +
                "@opengis/fastify-table": "^1.2.11",
         | 
| 52 52 | 
             
                "@opengis/v3-core": "^0.3.45",
         | 
| 53 | 
            -
                "@opengis/v3-filter": "^0.0. | 
| 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",
         | 
    
        package/server/plugins/hook.js
    CHANGED
    
    | @@ -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 | 
            -
                         | 
| 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,  | 
| 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 ( | 
| 42 | 
            -
             | 
| 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 | 
            -
                 | 
| 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 | 
            -
                 | 
| 59 | 
            -
                     | 
| 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([ | 
| 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  | 
| 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 | 
            -
             | 
| 83 | 
            -
             | 
| 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 | 
            -
                 | 
| 92 | 
            -
             | 
| 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 | 
            -
             | 
| 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 | 
            -
             | 
| 53 | 
            +
                const q = `select * from (${optimizedSQL})q where ${loadTable?.query || '1=1'} and ${where}`;
         | 
| 107 54 |  | 
| 108 | 
            -
             | 
| 109 | 
            -
                        .then(el => el.rows?.[0] || {});
         | 
| 55 | 
            +
                const obj = await pg.query(q, [params.id]).then(el => el.rows?.[0] || {});
         | 
| 110 56 |  | 
| 111 | 
            -
             | 
| 57 | 
            +
                const html = await handlebars.compile(body)(obj);
         | 
| 112 58 |  | 
| 113 | 
            -
             | 
| 114 | 
            -
             | 
| 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 | 
| 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,  | 
| 6 | 
            +
              pg, user = {}, body = {},
         | 
| 7 7 | 
             
            }) {
         | 
| 8 | 
            -
              if ( | 
| 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: [' | 
| 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 | 
            +
            }
         |