@opengis/admin 0.1.80 → 0.1.82

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. package/README.md +29 -29
  2. package/config.js +4 -4
  3. package/dist/{IconChevronDown-DWLdkAUG.js → IconChevronDown-TOHGDzb9.js} +1 -1
  4. package/dist/{add-page-GDPnGgQK.js → add-page-DVsruEe8.js} +1 -1
  5. package/dist/{admin-interface-DOqhM3SD.js → admin-interface-CqPS5k9y.js} +363 -363
  6. package/dist/{admin-view-xnJ3u3oI.js → admin-view-DtZwCxNO.js} +4 -4
  7. package/dist/admin.js +1 -1
  8. package/dist/admin.umd.cjs +85 -85
  9. package/dist/assets/logo.svg +41 -41
  10. package/dist/card-page-DiSMqrtf.js +358 -0
  11. package/dist/{card-view-CuKF08UR.js → card-view-RC_RStl7.js} +1 -1
  12. package/dist/{edit-page-J0eDzIuJ.js → edit-page-B0syX8OD.js} +1 -1
  13. package/dist/{import-file-REG5YZVe.js → import-file-BihjtZo9.js} +8208 -8008
  14. package/dist/style.css +1 -1
  15. package/module/settings/card/admin.roles.table/access.hbs +27 -27
  16. package/module/settings/card/admin.roles.table/general_info.hbs +16 -16
  17. package/module/settings/card/admin.roles.table/index.yml +14 -14
  18. package/module/settings/card/admin.roles.table/users.hbs +27 -27
  19. package/module/settings/card/admin.routes.table/general_info.hbs +40 -40
  20. package/module/settings/card/admin.routes.table/index.yml +8 -8
  21. package/module/settings/card/admin.routes.table/users.hbs +33 -33
  22. package/module/settings/card/admin.users.table/general_info.hbs +25 -25
  23. package/module/settings/card/admin.users.table/index.yml +12 -12
  24. package/module/settings/card/admin.users.table/logs.hbs +30 -30
  25. package/module/settings/card/admin.users.table/user_roles.hbs +24 -24
  26. package/module/settings/cls/core.actions.json +17 -13
  27. package/module/settings/cls/core.scope.json +13 -13
  28. package/module/settings/cls/properties.site_status.json +13 -13
  29. package/module/settings/cls/properties.widget_status.json +13 -13
  30. package/module/settings/cls/users.user_type.json +13 -13
  31. package/module/settings/form/admin.access.form.json +36 -36
  32. package/module/settings/form/admin.custom_column.form.json +71 -71
  33. package/module/settings/form/admin.properties.form.json +15 -15
  34. package/module/settings/form/admin.roles.form.json +19 -19
  35. package/module/settings/form/admin.routes.form.json +25 -25
  36. package/module/settings/form/admin.user_properties.form.json +15 -15
  37. package/module/settings/form/admin.user_roles.form.json +21 -21
  38. package/module/settings/form/admin.users.form.json +153 -153
  39. package/module/settings/form/user.user_roles.form.json +13 -13
  40. package/module/settings/interface/admin.properties.json +4 -4
  41. package/module/settings/interface/admin.roles.json +4 -4
  42. package/module/settings/interface/admin.routes.json +4 -4
  43. package/module/settings/interface/admin.users.json +4 -4
  44. package/module/settings/menu.json +50 -50
  45. package/module/settings/select/core.roles.json +2 -2
  46. package/module/settings/select/core.routes.sql +1 -1
  47. package/module/settings/select/core.user_mentioned.sql +1 -1
  48. package/module/settings/select/core.user_uid.sql +1 -1
  49. package/module/settings/table/admin.access.table.json +83 -83
  50. package/module/settings/table/admin.custom_column.table.json +99 -99
  51. package/module/settings/table/admin.properties.table.json +39 -39
  52. package/module/settings/table/admin.roles.table.json +64 -64
  53. package/module/settings/table/admin.routes.table.json +68 -68
  54. package/module/settings/table/admin.user_properties.table.json +34 -34
  55. package/module/settings/table/admin.user_roles.table.json +72 -72
  56. package/module/settings/table/admin.users.table.json +121 -121
  57. package/package.json +82 -82
  58. package/plugin.js +29 -27
  59. package/server/helpers/controllers/badge.js +11 -11
  60. package/server/helpers/controllers/hb.js +2 -2
  61. package/server/helpers/controllers/map.js +2 -2
  62. package/server/helpers/controllers/mls.js +2 -2
  63. package/server/helpers/controllers/token.js +15 -15
  64. package/server/helpers/controllers/vue.js +2 -2
  65. package/server/helpers/index.mjs +17 -17
  66. package/server/plugins/access/funcs/getAdminAccess.js +15 -0
  67. package/server/plugins/access/index.mjs +6 -0
  68. package/server/plugins/adminHook.js +93 -93
  69. package/server/plugins/cron.js +10 -10
  70. package/server/plugins/docs.js +28 -28
  71. package/server/plugins/hook.js +223 -223
  72. package/server/plugins/vite.js +71 -71
  73. package/server/routes/access/controllers/access.group.js +33 -0
  74. package/server/routes/access/controllers/access.group.post.js +44 -0
  75. package/server/routes/access/index.mjs +63 -0
  76. package/server/routes/calendar/controllers/calendar.data.js +87 -87
  77. package/server/routes/calendar/index.mjs +7 -7
  78. package/server/routes/calendar/schema.js +21 -21
  79. package/server/routes/data/controllers/cardData.js +81 -94
  80. package/server/routes/data/controllers/cardTabData.js +49 -49
  81. package/server/routes/data/controllers/funcs/getFilterSQL/index.js +92 -92
  82. package/server/routes/data/controllers/funcs/getFilterSQL/util/formatValue.js +170 -170
  83. package/server/routes/data/controllers/funcs/getFilterSQL/util/getCustomQuery.js +13 -13
  84. package/server/routes/data/controllers/funcs/getFilterSQL/util/getFilterQuery.js +64 -64
  85. package/server/routes/data/controllers/funcs/getFilterSQL/util/getOptimizedQuery.js +12 -12
  86. package/server/routes/data/controllers/funcs/getFilterSQL/util/getTableSql.js +34 -34
  87. package/server/routes/data/controllers/tableData.js +20 -14
  88. package/server/routes/data/controllers/tableDataId.js +27 -27
  89. package/server/routes/data/controllers/tableFilter.js +63 -63
  90. package/server/routes/data/controllers/tokenInfo.js +10 -0
  91. package/server/routes/data/controllers/utils/assignTokens.js +30 -30
  92. package/server/routes/data/controllers/utils/getColumns.js +8 -8
  93. package/server/routes/data/index.mjs +17 -15
  94. package/server/routes/data/schema.js +54 -54
  95. package/server/routes/menu/controllers/getMenu.js +67 -67
  96. package/server/routes/menu/index.mjs +5 -5
  97. package/server/routes/notifications/controllers/readNotifications.js +27 -27
  98. package/server/routes/notifications/controllers/testEmail.js +35 -35
  99. package/server/routes/notifications/controllers/userNotifications.js +53 -53
  100. package/server/routes/notifications/funcs/addNotification.js +21 -21
  101. package/server/routes/notifications/funcs/sendNotification.js +105 -105
  102. package/server/routes/notifications/hook/onWidgetSet.js +57 -57
  103. package/server/routes/notifications/index.mjs +27 -27
  104. package/server/routes/notifications/schema.js +16 -16
  105. package/server/routes/properties/controllers/admin.properties.get.js +29 -29
  106. package/server/routes/properties/controllers/user.properties.get.js +30 -30
  107. package/server/routes/properties/controllers/user.properties.post.js +30 -30
  108. package/server/routes/properties/funcs/getSettings.js +56 -56
  109. package/server/routes/properties/funcs/setSettings.js +44 -44
  110. package/server/routes/properties/funcs/utils/dataInsert.js +26 -26
  111. package/server/routes/properties/index.mjs +14 -14
  112. package/server/routes/properties/schema.js +10 -10
  113. package/server/routes/root.mjs +3 -3
  114. package/server/routes/templates/controllers/getTemplate.js +26 -22
  115. package/server/routes/templates/index.mjs +16 -16
  116. package/server/routes/templates/schema.js +8 -8
  117. package/server/routes/user/controllers/user.cls.id.js +14 -14
  118. package/server/routes/user/controllers/user.cls.js +71 -71
  119. package/server/routes/user/controllers/user.cls.post.js +52 -52
  120. package/server/routes/user/controllers/user.info.js +17 -17
  121. package/server/routes/user/schema.js +14 -14
  122. package/server/routes/widget/controllers/utils/historyFormat.js +75 -75
  123. package/server/routes/widget/controllers/utils/obj2db.js +13 -13
  124. package/server/routes/widget/controllers/widget.del.js +41 -41
  125. package/server/routes/widget/controllers/widget.get.js +96 -96
  126. package/server/routes/widget/controllers/widget.set.js +76 -76
  127. package/server/routes/widget/index.mjs +11 -11
  128. package/server/routes/widget/schema.js +12 -12
  129. package/server/templates/cls/itree.recrzone_category.json +73 -73
  130. package/server/templates/cls/test.json +9 -9
  131. package/server/templates/form/admin.user_cls.data.form.json +49 -49
  132. package/server/templates/form/admin.user_group_rel.form.json +21 -21
  133. package/server/templates/form/cp_building.form.json +32 -32
  134. package/server/templates/form/form-user-pass.json +10 -10
  135. package/server/templates/form/form-user_group.json +39 -39
  136. package/server/templates/form/form-users.json +156 -156
  137. package/server/templates/form/user_group_access.form.json +22 -22
  138. package/server/templates/select/account_id.json +2 -2
  139. package/server/templates/table/gis.dataset.table.json +43 -43
  140. package/server/templates/table/management.user_group.table.json +112 -112
  141. package/server/templates/table/management.users.table.json +126 -126
  142. package/utils.js +29 -26
  143. package/dist/card-page-DhYFr0FI.js +0 -280
@@ -1,224 +1,224 @@
1
- import path from 'node:path';
2
-
3
- import {
4
- getTemplatePath, addHook, getToken, getTemplate, config, pgClients,
5
- initPG,
6
- } from '@opengis/fastify-table/utils.js';
7
-
8
- import getMenu from '../routes/menu/controllers/getMenu.js';
9
-
10
- const { client } = pgClients;
11
-
12
- export default async function plugin(fastify) {
13
- const user1 = config?.auth?.disable || process.env.NODE_ENV !== 'admin' ? { uid: '1' } : null;
14
- await initPG(client);
15
-
16
- fastify.addHook('onListen', async () => {
17
- const json = await getMenu({ user: { uid: 1 } });
18
- // insert interface list to db (user access management)
19
- if (client?.pk?.['admin.routes'] && json?.length) {
20
- const menuList = json.filter((el) => (el?.table || el?.menu?.length) && el?.ua || el?.en || el?.name);
21
- const interfaces = menuList.reduce((acc, curr) => {
22
- if (curr.table) { acc.push(curr.path); }
23
- curr.menu?.forEach((el) => acc.push(el.path));
24
- return acc;
25
- }, []);
26
- await client.query('update admin.routes set enabled=false where not array[route_id] <@ $1::text[]', [interfaces]);
27
-
28
- const q = `insert into admin.menu(name, ord) values${menuList.map((el, i) => `('${(el?.ua || el?.en || el?.name).replace(/'/g, '’')}', ${i}) `).join(',')
29
- } on conflict (name) do update set ord=excluded.ord, enabled=true returning name, menu_id`;
30
- const { rows = [] } = menuList?.length ? await client.query(q) : {};
31
- await client.query('update admin.menu set enabled=false where not array[menu_id] <@ $1::text[]', [rows.map((el) => el.menu_id)]);
32
-
33
- const menus = rows.reduce((acc, curr) => Object.assign(acc, { [curr.menu_id]: menuList.find((item) => (item?.ua || item?.en || item?.name) === curr.name) }), {});
34
- const values = Object.entries(menus).reduce((acc, curr) => {
35
- if (curr[1]?.table) { acc.push({ ...curr[1], menuId: curr[0] }); }
36
- curr[1]?.menu?.forEach((el) => acc.push({ ...el, menuId: curr[0] }));
37
- return acc;
38
- }, []);
39
-
40
- await Promise.all(values.filter((el) => el?.table).map(async (el) => {
41
- const loadTable = await getTemplate('table', el.table);
42
- Object.assign(el, {
43
- table1: loadTable?.table || el.table,
44
- actions: loadTable?.actions,
45
- access: loadTable?.access,
46
- });
47
- }));
48
-
49
- const q1 = `insert into admin.routes(route_id, alias, title, menu_id, table_name, actions, access, query)
50
- values ${values.filter((el) => el?.table).map((el) => `('${el.path}', '${el.table}', ${(el.title || el.ua) ? `'${el.title || el.ua}'` : 'null'}, '${el.menuId || null}', '${el.table1}',
51
- ${el.actions?.length ? `'{ ${el.actions} }'::text[]` : 'null'}, ${el.access ? `'${el.access}'` : 'null'}, ${el.query ? `'${el.query.replace(/'/g,"''")}'` : '\'1=1\''})`).join(',')}
52
- on conflict (route_id) do update set menu_id=excluded.menu_id, alias=excluded.alias, title=excluded.title, enabled=true, query=excluded.query,
53
- table_name=excluded.table_name, actions=excluded.actions, access=excluded.access returning route_id, table_name`;
54
-
55
- try {
56
- const { rowCount } = values?.length ? await client.query(q1) : {};
57
- console.log('interface insert ok', /* values, */ rowCount);
58
- } catch (err) {
59
- console.log('interface insert error', values, q1, err);
60
- }
61
- }
62
- });
63
-
64
- fastify.addHook('onListen', async () => {
65
- const clsQuery = [];
66
- if (!client?.pk?.['admin.cls']) return;
67
-
68
- const selectList = await getTemplatePath('select');
69
- const clsList = (await getTemplatePath('cls'))?.filter((el) => !(selectList?.map((el) => el?.[0]) || []).includes(el[0]));
70
- const cls = (selectList || []).concat(clsList || [])
71
- ?.map((el) => ({ name: el[0], module: path.basename(path.dirname(path.dirname(el[1]))), type: { 'json': 'cls', 'sql': 'select' }[el[2]] }))
72
- if (!cls?.length) return;
73
-
74
- const dupes = cls.filter((el, idx, arr) => arr.map((item) => item.name).indexOf(el.name) !== idx);
75
- //console.log('cls insert skip dupes', dupes.map((el) => el.name));
76
-
77
- try {
78
- await Promise.all(cls.filter((el, idx, arr) => arr.map((item) => item.name).indexOf(el.name) === idx).map(async (el) => {
79
- const { name, module, type } = el;
80
- const loadTemplate = await getTemplate(type, name);
81
- // console.log(name, type);
82
- if (type === 'select') {
83
- clsQuery.push(`insert into admin.cls(name,type,data,module) values('${name}','sql','${(loadTemplate?.sql || loadTemplate)?.replace(/'/g, "''")}', '${module?.replace(/'/g, "''")}')`);
84
- } else if (type === 'cls' && loadTemplate?.length) {
85
- clsQuery.push(`insert into admin.cls(name,type, module) values('${name}','json', '${module?.replace(/'/g, "''")}');
86
- insert into admin.cls(code,name,parent,icon)
87
- select value->>'id',value->>'text','${name}',value->>'icon'
88
- from json_array_elements('${JSON.stringify(loadTemplate).replace(/'/g, "''")}'::json)`);
89
- } else {
90
- console.log(name, type, 'empty');
91
- }
92
- }));
93
-
94
- await client.query('truncate admin.cls');
95
- if (clsQuery.filter((el) => el).length) {
96
- await client.query(clsQuery.filter((el) => el).join(';'));
97
- console.log('cls insert ok', clsQuery?.length);
98
- }
99
- } catch (err) {
100
- console.error('cls insert error', err.toString());
101
- }
102
- });
103
-
104
- addHook('afterTable', async ({ table, res = {}, payload: rows = [], user = {} }) => {
105
- const loadTable = await getTemplate('table', table);
106
- const { uid } = user1 || user;
107
- if (!uid || !table || !client?.pk?.[table] || !rows.length || !loadTable?.table) return;
108
-
109
- // admin.custom_column - user column data
110
- const { rows: properties = [] } = await client.query(`select column_id, name, title, format, data from admin.custom_column
111
- where _table and entity=$1 and uid=$2`, [table, uid]);
112
- const extraColumnList = properties.map((row) => ({ id: row.column_id, name: row.name, title: row.title, format: row.format, data: row.data }));
113
- if (!extraColumnList?.length) return;
114
-
115
- if (res?.columns?.length) {
116
- extraColumnList.forEach((col) => res.columns.push(col));
117
- }
118
-
119
- const { rows: extraData = [] } = await client.query(`select object_id, json_object_agg( property_id, coalesce(value_date::text,value_text) ) as extra from crm.extra_data
120
- where property_entity=$1 and property_id=any($2) and object_id=any($3) group by object_id`, [table, extraColumnList?.map((el) => el.id), rows.map((el) => el.id)]);
121
-
122
- if (!extraData?.length) {
123
- // Object.assign(rows?.[0] || {}, { ...extraColumnList.reduce((acc, curr) => Object.assign(acc, { [curr.name]: null }), {}) });
124
- return;
125
- }
126
-
127
- rows.filter((row) => extraData.map((el) => el?.object_id).includes(row.id)).forEach((row) => {
128
- const { extra = {} } = extraData.find((el) => el.object_id === row.id);
129
- Object.assign(row, { ...Object.fromEntries(Object.entries(extra).map((el) => [extraColumnList.find((col) => col.id === el[0]).name, el[1]])) });
130
- });
131
-
132
- // admin.custom_column - metaFormat
133
- await Promise.all(extraColumnList.filter((el) => el?.data).map(async (attr) => {
134
- const values = [...new Set(rows?.map((el) => el[attr.name]).flat())].filter((el) => el);
135
- if (!values.length) return;
136
- const cls = await getSelectVal({ name: attr.data, values });
137
- if (!cls) return;
138
- rows.forEach(el => {
139
- const val = el[attr.name]?.map?.(c => cls[c] || c) || cls[el[attr.name]] || el[attr.name];
140
- if (!val) return;
141
- Object.assign(el, { [val?.color ? `${attr.name}_data` : `${attr.name}_text`]: (val.color ? val : val.text || val) });
142
- });
143
- }));
144
- });
145
-
146
- // extract table from form token for user columns - p.2 - read (refactor to global token)
147
- addHook('preTemplate', async ({ name, type, user = {} }) => {
148
- if (!name || !type) return;
149
- const { uid } = user1 || user;
150
- const tokenData = await getToken({
151
- uid, token: name, mode: 'w', json: 1,
152
- }) // edit?
153
- || await getToken({
154
- uid, token: name, mode: 'a', json: 1,
155
- }) || {}; // add?
156
- return { name: tokenData?.[type] };
157
- });
158
-
159
- addHook('afterTemplate', async ({ name, type, payload: data = {}, user = {} }) => {
160
- const { uid } = user1 || user;
161
- // extract table from form token for user columns - p.1 - assign (refactor to global token)
162
- if (!uid || !data || type !== 'form' || !name) return null;
163
-
164
- const { form, id, table } = await getToken({
165
- uid, token: name, mode: 'w', json: 1,
166
- }) // edit?
167
- || await getToken({
168
- uid, token: name, mode: 'a', json: 1,
169
- }) || {}; // add?
170
-
171
- const { rows: properties = [] } = await client.query(`select name, title, format, data from admin.custom_column
172
- where entity=$1 and uid=$2`, [table || name, uid]);
173
-
174
- await Promise.all(properties.map(async (el) => {
175
- const clsData = el.data ? await getTemplate(['cls', 'select'], el.data) : undefined;
176
- const type = clsData ? 'Select' : ({ date: 'DatePicker' }[el.format] || 'Text');
177
- Object.assign(data?.schema || data || {}, { [el.name]: { type, ua: el.title, data: el.data, options: type === 'Select' && Array.isArray(clsData) && clsData?.length ? clsData : undefined, extra: 1 } });
178
- }));
179
- });
180
-
181
- addHook('afterUpdate', async ({ table, body = {}, payload: res = {}, user = {} }) => {
182
- const { uid } = user1 || user;
183
- if (!uid || !table || !Object.keys(body)?.length) return null;
184
-
185
- const loadTable = await getTemplate('table', table);
186
- if (!client?.pk?.[loadTable?.table || table]) return null;
187
- const pk = client?.pk?.[loadTable?.table || table];
188
- const id = res[pk];
189
-
190
- const { rows: properties = [] } = await client.query(`select column_id, name, title, format, data from admin.custom_column
191
- where entity=$1 and uid=$2`, [table, uid]);
192
-
193
- if (!id || !properties?.length) return null;
194
-
195
- const q = `delete from crm.extra_data where property_entity='${table}' and object_id='${id}';${properties
196
- .filter((el) => Object.keys(body).includes(el.name))
197
- .map((el) => `insert into crm.extra_data(property_id,property_key,property_entity,object_id,${el.format?.toLowerCase() === 'date' ? 'value_date' : 'value_text'})
198
- select '${el.column_id}', '${el.name}', '${table}', '${id}', ${el.format?.toLowerCase() === 'date' ? `'${body[el.name]}'::timestamp without time zone` : `'${body[el.name]}'::text`}`)
199
- .join(';\n') || ''}`;
200
- return client.query(q);
201
- });
202
-
203
- addHook('afterInsert', async ({ table, body = {}, payload: res = {}, user = {} }) => {
204
- const { uid } = user1 || user;
205
- if (!uid || !table || !Object.keys(body)?.length) return null;
206
-
207
- const loadTable = await getTemplate('table', table);
208
- if (!client?.pk?.[loadTable?.table || table]) return null;
209
- const pk = client?.pk?.[loadTable?.table || table];
210
- const id = res.rows?.[0]?.[pk];
211
-
212
- const { rows: properties = [] } = await client.query(`select column_id, name, title, format, data from admin.custom_column
213
- where entity=$1 and uid=$2`, [table, uid]);
214
-
215
- if (!id || !properties?.length) return null;
216
-
217
- const q = properties
218
- .filter((el) => Object.keys(body).includes(el.name))
219
- .map((el) => `insert into crm.extra_data(property_id,property_key,property_entity,object_id,${el.format?.toLowerCase() === 'date' ? 'value_date' : 'value_text'})
220
- select '${el.column_id}', '${el.name}', '${table}', '${id}', ${el.format?.toLowerCase() === 'date' ? `'${body[el.name]}'::timestamp without time zone` : `'${body[el.name]}'::text`}`)
221
- .join(';\n');
222
- return client.query(q);
223
- });
1
+ import path from 'node:path';
2
+
3
+ import {
4
+ getTemplatePath, addHook, getToken, getTemplate, config, pgClients,
5
+ initPG,
6
+ } from '@opengis/fastify-table/utils.js';
7
+
8
+ import getMenu from '../routes/menu/controllers/getMenu.js';
9
+
10
+ const { client } = pgClients;
11
+
12
+ export default async function plugin(fastify) {
13
+ const user1 = config?.auth?.disable || process.env.NODE_ENV !== 'admin' ? { uid: '1' } : null;
14
+ await initPG(client);
15
+
16
+ fastify.addHook('onListen', async () => {
17
+ const json = await getMenu({ user: { uid: 1 } });
18
+ // insert interface list to db (user access management)
19
+ if (client?.pk?.['admin.routes'] && json?.length) {
20
+ const menuList = json.filter((el) => (el?.table || el?.menu?.length) && el?.ua || el?.en || el?.name);
21
+ const interfaces = menuList.reduce((acc, curr) => {
22
+ if (curr.table) { acc.push(curr.path); }
23
+ curr.menu?.forEach((el) => acc.push(el.path));
24
+ return acc;
25
+ }, []);
26
+ await client.query('update admin.routes set enabled=false where not array[route_id] <@ $1::text[]', [interfaces]);
27
+
28
+ const q = `insert into admin.menu(name, ord) values${menuList.map((el, i) => `('${(el?.ua || el?.en || el?.name).replace(/'/g, '’')}', ${i}) `).join(',')
29
+ } on conflict (name) do update set ord=excluded.ord, enabled=true returning name, menu_id`;
30
+ const { rows = [] } = menuList?.length ? await client.query(q) : {};
31
+ await client.query('update admin.menu set enabled=false where not array[menu_id] <@ $1::text[]', [rows.map((el) => el.menu_id)]);
32
+
33
+ const menus = rows.reduce((acc, curr) => Object.assign(acc, { [curr.menu_id]: menuList.find((item) => (item?.ua || item?.en || item?.name) === curr.name) }), {});
34
+ const values = Object.entries(menus).reduce((acc, curr) => {
35
+ if (curr[1]?.table) { acc.push({ ...curr[1], menuId: curr[0] }); }
36
+ curr[1]?.menu?.forEach((el) => acc.push({ ...el, menuId: curr[0] }));
37
+ return acc;
38
+ }, []);
39
+
40
+ await Promise.all(values.filter((el) => el?.table).map(async (el) => {
41
+ const loadTable = await getTemplate('table', el.table);
42
+ Object.assign(el, {
43
+ table1: loadTable?.table || el.table,
44
+ actions: loadTable?.actions,
45
+ access: loadTable?.access,
46
+ });
47
+ }));
48
+
49
+ const q1 = `insert into admin.routes(route_id, alias, title, menu_id, table_name, actions, access, query)
50
+ values ${values.filter((el) => el?.table).map((el) => `('${el.path}', '${el.table}', ${(el.title || el.ua) ? `'${el.title || el.ua}'` : 'null'}, '${el.menuId || null}', '${el.table1}',
51
+ ${el.actions?.length ? `'{ ${el.actions} }'::text[]` : 'null'}, ${el.access ? `'${el.access}'` : 'null'}, ${el.query ? `'${el.query.replace(/'/g,"''")}'` : '\'1=1\''})`).join(',')}
52
+ on conflict (route_id) do update set menu_id=excluded.menu_id, alias=excluded.alias, title=excluded.title, enabled=true, query=excluded.query,
53
+ table_name=excluded.table_name, actions=excluded.actions, access=excluded.access returning route_id, table_name`;
54
+
55
+ try {
56
+ const { rowCount } = values?.length ? await client.query(q1) : {};
57
+ console.log('interface insert ok', /* values, */ rowCount);
58
+ } catch (err) {
59
+ console.log('interface insert error', values, q1, err);
60
+ }
61
+ }
62
+ });
63
+
64
+ fastify.addHook('onListen', async () => {
65
+ const clsQuery = [];
66
+ if (!client?.pk?.['admin.cls']) return;
67
+
68
+ const selectList = await getTemplatePath('select');
69
+ const clsList = (await getTemplatePath('cls'))?.filter((el) => !(selectList?.map((el) => el?.[0]) || []).includes(el[0]));
70
+ const cls = (selectList || []).concat(clsList || [])
71
+ ?.map((el) => ({ name: el[0], module: path.basename(path.dirname(path.dirname(el[1]))), type: { 'json': 'cls', 'sql': 'select' }[el[2]] }))
72
+ if (!cls?.length) return;
73
+
74
+ const dupes = cls.filter((el, idx, arr) => arr.map((item) => item.name).indexOf(el.name) !== idx);
75
+ //console.log('cls insert skip dupes', dupes.map((el) => el.name));
76
+
77
+ try {
78
+ await Promise.all(cls.filter((el, idx, arr) => arr.map((item) => item.name).indexOf(el.name) === idx).map(async (el) => {
79
+ const { name, module, type } = el;
80
+ const loadTemplate = await getTemplate(type, name);
81
+ // console.log(name, type);
82
+ if (type === 'select') {
83
+ clsQuery.push(`insert into admin.cls(name,type,data,module) values('${name}','sql','${(loadTemplate?.sql || loadTemplate)?.replace(/'/g, "''")}', '${module?.replace(/'/g, "''")}')`);
84
+ } else if (type === 'cls' && loadTemplate?.length) {
85
+ clsQuery.push(`insert into admin.cls(name,type, module) values('${name}','json', '${module?.replace(/'/g, "''")}');
86
+ insert into admin.cls(code,name,parent,icon)
87
+ select value->>'id',value->>'text','${name}',value->>'icon'
88
+ from json_array_elements('${JSON.stringify(loadTemplate).replace(/'/g, "''")}'::json)`);
89
+ } else {
90
+ console.log(name, type, 'empty');
91
+ }
92
+ }));
93
+
94
+ await client.query('truncate admin.cls');
95
+ if (clsQuery.filter((el) => el).length) {
96
+ await client.query(clsQuery.filter((el) => el).join(';'));
97
+ console.log('cls insert ok', clsQuery?.length);
98
+ }
99
+ } catch (err) {
100
+ console.error('cls insert error', err.toString());
101
+ }
102
+ });
103
+
104
+ addHook('afterTable', async ({ table, res = {}, payload: rows = [], user = {} }) => {
105
+ const loadTable = await getTemplate('table', table);
106
+ const { uid } = user1 || user;
107
+ if (!uid || !table || !client?.pk?.[table] || !rows.length || !loadTable?.table) return;
108
+
109
+ // admin.custom_column - user column data
110
+ const { rows: properties = [] } = await client.query(`select column_id, name, title, format, data from admin.custom_column
111
+ where _table and entity=$1 and uid=$2`, [table, uid]);
112
+ const extraColumnList = properties.map((row) => ({ id: row.column_id, name: row.name, title: row.title, format: row.format, data: row.data }));
113
+ if (!extraColumnList?.length) return;
114
+
115
+ if (res?.columns?.length) {
116
+ extraColumnList.forEach((col) => res.columns.push(col));
117
+ }
118
+
119
+ const { rows: extraData = [] } = await client.query(`select object_id, json_object_agg( property_id, coalesce(value_date::text,value_text) ) as extra from crm.extra_data
120
+ where property_entity=$1 and property_id=any($2) and object_id=any($3) group by object_id`, [table, extraColumnList?.map((el) => el.id), rows.map((el) => el.id)]);
121
+
122
+ if (!extraData?.length) {
123
+ // Object.assign(rows?.[0] || {}, { ...extraColumnList.reduce((acc, curr) => Object.assign(acc, { [curr.name]: null }), {}) });
124
+ return;
125
+ }
126
+
127
+ rows.filter((row) => extraData.map((el) => el?.object_id).includes(row.id)).forEach((row) => {
128
+ const { extra = {} } = extraData.find((el) => el.object_id === row.id);
129
+ Object.assign(row, { ...Object.fromEntries(Object.entries(extra).map((el) => [extraColumnList.find((col) => col.id === el[0]).name, el[1]])) });
130
+ });
131
+
132
+ // admin.custom_column - metaFormat
133
+ await Promise.all(extraColumnList.filter((el) => el?.data).map(async (attr) => {
134
+ const values = [...new Set(rows?.map((el) => el[attr.name]).flat())].filter((el) => el);
135
+ if (!values.length) return;
136
+ const cls = await getSelectVal({ name: attr.data, values });
137
+ if (!cls) return;
138
+ rows.forEach(el => {
139
+ const val = el[attr.name]?.map?.(c => cls[c] || c) || cls[el[attr.name]] || el[attr.name];
140
+ if (!val) return;
141
+ Object.assign(el, { [val?.color ? `${attr.name}_data` : `${attr.name}_text`]: (val.color ? val : val.text || val) });
142
+ });
143
+ }));
144
+ });
145
+
146
+ // extract table from form token for user columns - p.2 - read (refactor to global token)
147
+ addHook('preTemplate', async ({ name, type, user = {} }) => {
148
+ if (!name || !type) return;
149
+ const { uid } = user1 || user;
150
+ const tokenData = await getToken({
151
+ uid, token: name, mode: 'w', json: 1,
152
+ }) // edit?
153
+ || await getToken({
154
+ uid, token: name, mode: 'a', json: 1,
155
+ }) || {}; // add?
156
+ return { name: tokenData?.[type] };
157
+ });
158
+
159
+ addHook('afterTemplate', async ({ name, type, payload: data = {}, user = {} }) => {
160
+ const { uid } = user1 || user;
161
+ // extract table from form token for user columns - p.1 - assign (refactor to global token)
162
+ if (!uid || !data || type !== 'form' || !name) return null;
163
+
164
+ const { form, id, table } = await getToken({
165
+ uid, token: name, mode: 'w', json: 1,
166
+ }) // edit?
167
+ || await getToken({
168
+ uid, token: name, mode: 'a', json: 1,
169
+ }) || {}; // add?
170
+
171
+ const { rows: properties = [] } = await client.query(`select name, title, format, data from admin.custom_column
172
+ where entity=$1 and uid=$2`, [table || name, uid]);
173
+
174
+ await Promise.all(properties.map(async (el) => {
175
+ const clsData = el.data ? await getTemplate(['cls', 'select'], el.data) : undefined;
176
+ const type = clsData ? 'Select' : ({ date: 'DatePicker' }[el.format] || 'Text');
177
+ Object.assign(data?.schema || data || {}, { [el.name]: { type, ua: el.title, data: el.data, options: type === 'Select' && Array.isArray(clsData) && clsData?.length ? clsData : undefined, extra: 1 } });
178
+ }));
179
+ });
180
+
181
+ addHook('afterUpdate', async ({ table, body = {}, payload: res = {}, user = {} }) => {
182
+ const { uid } = user1 || user;
183
+ if (!uid || !table || !Object.keys(body)?.length) return null;
184
+
185
+ const loadTable = await getTemplate('table', table);
186
+ if (!client?.pk?.[loadTable?.table || table]) return null;
187
+ const pk = client?.pk?.[loadTable?.table || table];
188
+ const id = res[pk];
189
+
190
+ const { rows: properties = [] } = await client.query(`select column_id, name, title, format, data from admin.custom_column
191
+ where entity=$1 and uid=$2`, [table, uid]);
192
+
193
+ if (!id || !properties?.length) return null;
194
+
195
+ const q = `delete from crm.extra_data where property_entity='${table}' and object_id='${id}';${properties
196
+ .filter((el) => Object.keys(body).includes(el.name))
197
+ .map((el) => `insert into crm.extra_data(property_id,property_key,property_entity,object_id,${el.format?.toLowerCase() === 'date' ? 'value_date' : 'value_text'})
198
+ select '${el.column_id}', '${el.name}', '${table}', '${id}', ${el.format?.toLowerCase() === 'date' ? `'${body[el.name]}'::timestamp without time zone` : `'${body[el.name]}'::text`}`)
199
+ .join(';\n') || ''}`;
200
+ return client.query(q);
201
+ });
202
+
203
+ addHook('afterInsert', async ({ table, body = {}, payload: res = {}, user = {} }) => {
204
+ const { uid } = user1 || user;
205
+ if (!uid || !table || !Object.keys(body)?.length) return null;
206
+
207
+ const loadTable = await getTemplate('table', table);
208
+ if (!client?.pk?.[loadTable?.table || table]) return null;
209
+ const pk = client?.pk?.[loadTable?.table || table];
210
+ const id = res.rows?.[0]?.[pk];
211
+
212
+ const { rows: properties = [] } = await client.query(`select column_id, name, title, format, data from admin.custom_column
213
+ where entity=$1 and uid=$2`, [table, uid]);
214
+
215
+ if (!id || !properties?.length) return null;
216
+
217
+ const q = properties
218
+ .filter((el) => Object.keys(body).includes(el.name))
219
+ .map((el) => `insert into crm.extra_data(property_id,property_key,property_entity,object_id,${el.format?.toLowerCase() === 'date' ? 'value_date' : 'value_text'})
220
+ select '${el.column_id}', '${el.name}', '${table}', '${id}', ${el.format?.toLowerCase() === 'date' ? `'${body[el.name]}'::timestamp without time zone` : `'${body[el.name]}'::text`}`)
221
+ .join(';\n');
222
+ return client.query(q);
223
+ });
224
224
  }
@@ -1,71 +1,71 @@
1
- import fs from 'fs';
2
- import path, { dirname } from 'path';
3
- import { fileURLToPath } from 'url';
4
-
5
- const dir = dirname(fileURLToPath(import.meta.url));
6
- const root = `${dir}/../..`;
7
-
8
- const isProduction = process.env.NODE_ENV === 'production';
9
-
10
- async function plugin(fastify) {
11
- // vite server
12
- if (!isProduction) {
13
- const vite = await import('vite');
14
-
15
- const viteServer = await vite.createServer({
16
- server: {
17
- middlewareMode: true,
18
- },
19
- });
20
- // hot reload
21
- viteServer.watcher.on('all', function (d, t) {
22
- if (!t.includes('module')) return;
23
- console.log(d, t);
24
- viteServer.ws.send({ type: 'full-reload' });
25
- });
26
-
27
- // this is middleware for vite's dev servert
28
- fastify.addHook('onRequest', async (req, reply) => {
29
- const { user } = req.session?.passport || {};
30
- if (!user) return reply.redirect('/login');
31
-
32
- const next = () => new Promise((resolve) => {
33
- viteServer.middlewares(req.raw, reply.raw, () => resolve());
34
- });
35
- await next();
36
- });
37
- fastify.get('*', async () => { });
38
- return;
39
- }
40
-
41
- // From Build
42
- fastify.get('*', async (req, reply) => {
43
- const { user } = req.session?.passport || {};
44
-
45
- if (!user) return reply.redirect('/login');
46
- const stream = fs.createReadStream('dist/index.html');
47
- return reply.type('text/html').send(stream);
48
- });
49
-
50
- const fileSize = {}
51
- async function staticFile(req, reply) {
52
- const assetsDir = 'dist';
53
- const filePath = path.join(root, assetsDir, req.url);
54
- const ext = path.extname(filePath);
55
-
56
- if (!fs.existsSync(filePath)) return { status: 404, message: 'not found' }
57
- fileSize[filePath] = fileSize[filePath] || fs.statSync(filePath).size;
58
- const mime = {
59
- '.js': 'text/javascript', '.css': 'text/css', '.woff2': 'application/font-woff', '.png': 'image/png', '.svg': 'image/svg+xml', '.jpg': 'image/jpg',
60
- }[ext];
61
- reply.headers({ 'Cache-control': 'max-age=3600, public', 'Content-length': fileSize[filePath], 'Content-Encoding': 'identity' });
62
-
63
- const stream = fs.createReadStream(filePath);
64
- return mime ? reply.type(mime).send(stream) : stream;
65
- }
66
-
67
- fastify.get('/assets/*', staticFile);
68
- fastify.get('/public/*', staticFile);
69
- }
70
-
71
- export default plugin;
1
+ import fs from 'fs';
2
+ import path, { dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const dir = dirname(fileURLToPath(import.meta.url));
6
+ const root = `${dir}/../..`;
7
+
8
+ const isProduction = process.env.NODE_ENV === 'production';
9
+
10
+ async function plugin(fastify) {
11
+ // vite server
12
+ if (!isProduction) {
13
+ const vite = await import('vite');
14
+
15
+ const viteServer = await vite.createServer({
16
+ server: {
17
+ middlewareMode: true,
18
+ },
19
+ });
20
+ // hot reload
21
+ viteServer.watcher.on('all', function (d, t) {
22
+ if (!t.includes('module')) return;
23
+ console.log(d, t);
24
+ viteServer.ws.send({ type: 'full-reload' });
25
+ });
26
+
27
+ // this is middleware for vite's dev servert
28
+ fastify.addHook('onRequest', async (req, reply) => {
29
+ const { user } = req.session?.passport || {};
30
+ if (!user) return reply.redirect('/login');
31
+
32
+ const next = () => new Promise((resolve) => {
33
+ viteServer.middlewares(req.raw, reply.raw, () => resolve());
34
+ });
35
+ await next();
36
+ });
37
+ fastify.get('*', async () => { });
38
+ return;
39
+ }
40
+
41
+ // From Build
42
+ fastify.get('*', async (req, reply) => {
43
+ const { user } = req.session?.passport || {};
44
+
45
+ if (!user) return reply.redirect('/login');
46
+ const stream = fs.createReadStream('dist/index.html');
47
+ return reply.type('text/html').send(stream);
48
+ });
49
+
50
+ const fileSize = {}
51
+ async function staticFile(req, reply) {
52
+ const assetsDir = 'dist';
53
+ const filePath = path.join(root, assetsDir, req.url);
54
+ const ext = path.extname(filePath);
55
+
56
+ if (!fs.existsSync(filePath)) return { status: 404, message: 'not found' }
57
+ fileSize[filePath] = fileSize[filePath] || fs.statSync(filePath).size;
58
+ const mime = {
59
+ '.js': 'text/javascript', '.css': 'text/css', '.woff2': 'application/font-woff', '.png': 'image/png', '.svg': 'image/svg+xml', '.jpg': 'image/jpg',
60
+ }[ext];
61
+ reply.headers({ 'Cache-control': 'max-age=3600, public', 'Content-length': fileSize[filePath], 'Content-Encoding': 'identity' });
62
+
63
+ const stream = fs.createReadStream(filePath);
64
+ return mime ? reply.type(mime).send(stream) : stream;
65
+ }
66
+
67
+ fastify.get('/assets/*', staticFile);
68
+ fastify.get('/public/*', staticFile);
69
+ }
70
+
71
+ export default plugin;
@@ -0,0 +1,33 @@
1
+ import { pgClients } from '@opengis/fastify-table/utils.js';
2
+
3
+ import { getAdminAccess } from '../../../../utils.js';
4
+
5
+ export default async function accessGroup({
6
+ pg = pgClients.client, params = {}, session = {},
7
+ }) {
8
+ const { user = {} } = session?.passport || {};
9
+
10
+ if (!params?.id) {
11
+ return { message: 'not enough params: id', status: 400 };
12
+ }
13
+
14
+ // restrict access - admin only
15
+ const check = await getAdminAccess({
16
+ id: params.id, user,
17
+ });
18
+ if (check) return check;
19
+
20
+ const { rows: routes = [] } = await pg.query(`select a.route_id as path, coalesce(a.actions, b.actions) as actions from admin.routes a
21
+ left join admin.access b on a.route_id=b.route_id
22
+ where b.role_id=$1`, [params.id]);
23
+
24
+ const { rows: users = [] } = await pg.query(`select user_uid as id, user_name as name, access_granted,
25
+ b.cdate as user_created, c.last_activity from admin.user_roles a
26
+ left join admin.users b on a.user_uid=b.uid
27
+ left join lateral(
28
+ select max(auth_date) as last_activity from log.user_auth where user_id=a.user_uid
29
+ )c on 1=1
30
+ where a.role_id=$1`, [params.id]);
31
+
32
+ return { routes, users };
33
+ }