@opengis/admin 0.1.17 → 0.1.19
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/module/settings/card/admin.roles.table/access.hbs +26 -0
- package/module/settings/card/admin.roles.table/general_info.hbs +17 -0
- package/module/settings/card/admin.roles.table/index.yml +12 -0
- package/module/settings/card/admin.roles.table/users.hbs +31 -0
- package/module/settings/card/admin.routes.table/general_info.hbs +41 -0
- package/module/settings/card/admin.routes.table/index.yml +8 -0
- package/module/settings/card/admin.routes.table/users.hbs +28 -0
- package/module/settings/card/admin.users.table/general_info.hbs +25 -30
- package/module/settings/card/admin.users.table/index.yml +7 -2
- package/module/settings/card/admin.users.table/logs.hbs +31 -0
- package/module/settings/card/admin.users.table/user_roles.hbs +17 -18
- package/module/settings/form/admin.user_properties.form.json +16 -0
- package/module/settings/form/admin.users.form.json +146 -71
- package/module/settings/menu.json +13 -7
- package/module/settings/table/admin.properties.table.json +8 -7
- package/module/settings/table/admin.user_properties.table.json +29 -0
- package/module/settings/table/admin.users.table.json +92 -31
- package/package.json +3 -2
- package/plugin.js +1 -0
- package/server/plugins/hook.js +71 -4
- package/server/routes/menu/controllers/getMenu.js +2 -2
- package/server/routes/menu/index.mjs +2 -2
- package/server/routes/properties/controllers/admin.properties.get.js +29 -0
- package/server/routes/properties/controllers/admin.properties.post.js +26 -0
- package/server/routes/properties/controllers/user.properties.get.js +34 -0
- package/server/routes/properties/controllers/user.properties.post.js +30 -0
- package/server/routes/properties/funcs/getSettings.js +56 -0
- package/server/routes/properties/funcs/setSettings.js +44 -0
- package/server/routes/properties/funcs/utils/dataInsert.js +26 -0
- package/server/routes/properties/index.mjs +26 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{{#contentList table="admin.access" query="role_id='{{role_id}}'" order="cdate desc" sql1=1}}
|
|
2
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
3
|
+
<thead class="bg-gray-50">
|
|
4
|
+
<tr>
|
|
5
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
6
|
+
ID шаблона</th>
|
|
7
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
8
|
+
Назва шаблону</th>
|
|
9
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
10
|
+
Права</th>
|
|
11
|
+
</tr>
|
|
12
|
+
</thead>
|
|
13
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
14
|
+
{{#each rows}}
|
|
15
|
+
<tr>
|
|
16
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-F500">
|
|
17
|
+
{{route_id}}</td>
|
|
18
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-F500">
|
|
19
|
+
{{select route_id data="core.routes"}}</td>
|
|
20
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-F500">
|
|
21
|
+
{{select actions data="core.actions"}}</td>
|
|
22
|
+
</tr>
|
|
23
|
+
{{/each}}
|
|
24
|
+
</tbody>
|
|
25
|
+
</table>
|
|
26
|
+
{{/contentList}}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<dl class="-my-3 divide-y divide-gray-100 text-sm">
|
|
2
|
+
<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4">
|
|
3
|
+
<dt class="font-medium text-gray-900">Назва</dt>
|
|
4
|
+
<dd class="text-gray-700 sm:col-span-2">{{name}}</dd>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4">
|
|
8
|
+
<dt class="font-medium text-gray-900">On/Off</dt>
|
|
9
|
+
<dd class="text-gray-700 sm:col-span-2">{{select enabled data="yes_no"}}</dd>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4">
|
|
13
|
+
<dt class="font-medium text-gray-900">Опис</dt>
|
|
14
|
+
<dd class="text-gray-700 sm:col-span-2">{{info}}</dd>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
</dl>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{{#contentList table="admin.user_roles" query="user_uid='{{id}}'" order="cdate desc" limit="50" sql1=1}}
|
|
2
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
3
|
+
<thead class="bg-gray-50">
|
|
4
|
+
<tr>
|
|
5
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
6
|
+
Користувач</th>
|
|
7
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
8
|
+
Закінчення терміну дії доступу до групи</th>
|
|
9
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
10
|
+
Дата надання доступу</th>
|
|
11
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
12
|
+
Хто надав доступ</th>
|
|
13
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
14
|
+
Дії</th>
|
|
15
|
+
</tr>
|
|
16
|
+
</thead>
|
|
17
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
18
|
+
{{#each rows}}
|
|
19
|
+
<tr>
|
|
20
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-F500">
|
|
21
|
+
{{select user_uid data="core.user_uid"}}</td>
|
|
22
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-F500">
|
|
23
|
+
{{formatDate expiration}}</td>
|
|
24
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-F500">{{formatDate access_granted_time}}</td>
|
|
25
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-F500">{{select name="access_granted"
|
|
26
|
+
data="core.user_uid"}}</td>
|
|
27
|
+
</tr>
|
|
28
|
+
{{/each}}
|
|
29
|
+
</tbody>
|
|
30
|
+
</table>
|
|
31
|
+
{{/contentList}}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<dl class="-my-3 divide-y divide-gray-100 text-sm">
|
|
2
|
+
<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4">
|
|
3
|
+
<dt class="font-medium text-gray-900">Назва файлу інтерфейсу</dt>
|
|
4
|
+
<dd class="text-gray-700 sm:col-span-2">{{alias}}</dd>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4">
|
|
8
|
+
<dt class="font-medium text-gray-900">Назва інтерфейсу українською</dt>
|
|
9
|
+
<dd class="text-gray-700 sm:col-span-2">{{title}}</dd>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4">
|
|
13
|
+
<dt class="font-medium text-gray-900">Кількість користувачів</dt>
|
|
14
|
+
<dd class="text-gray-700 sm:col-span-2">{{user_count}}</dd>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4">
|
|
18
|
+
<dt class="font-medium text-gray-900">Кількість груп</dt>
|
|
19
|
+
<dd class="text-gray-700 sm:col-span-2">{{roles_count}}</dd>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4">
|
|
23
|
+
<dt class="font-medium text-gray-900">Ознака чи для всіх показується</dt>
|
|
24
|
+
<dd class="text-gray-700 sm:col-span-2">{{select public data="yes_no"}}</dd>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4">
|
|
28
|
+
<dt class="font-medium text-gray-900">Пункт меню</dt>
|
|
29
|
+
<dd class="text-gray-700 sm:col-span-2">{{menu_id}}</dd>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4">
|
|
33
|
+
<dt class="font-medium text-gray-900">On / Off</dt>
|
|
34
|
+
<dd class="text-gray-700 sm:col-span-2">{{select enabled data="yes_no"}}</dd>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4">
|
|
38
|
+
<dt class="font-medium text-gray-900">Дата створення</dt>
|
|
39
|
+
<dd class="text-gray-700 sm:col-span-2">{{formatDate cdate}}</dd>
|
|
40
|
+
</div>
|
|
41
|
+
</dl>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{{#contentList table="admin.access" query="user_uid='{{id}}'" order="cdate desc" limit="50" sql1=1}}
|
|
2
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
3
|
+
<thead class="bg-gray-50">
|
|
4
|
+
<tr>
|
|
5
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
6
|
+
Користувач</th>
|
|
7
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
8
|
+
Права</th>
|
|
9
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
10
|
+
Користувач який надав доступ</th>
|
|
11
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
12
|
+
Дії</th>
|
|
13
|
+
</tr>
|
|
14
|
+
</thead>
|
|
15
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
16
|
+
{{#each rows}}
|
|
17
|
+
<tr>
|
|
18
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-F500">
|
|
19
|
+
{{select user_uid data="core.user_uid"}}</td>
|
|
20
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-F500">
|
|
21
|
+
{{select actions data="core.actions"}}</td>
|
|
22
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-F500">
|
|
23
|
+
{{select access_granted data="core.user_uid"}}</td>
|
|
24
|
+
</tr>
|
|
25
|
+
{{/each}}
|
|
26
|
+
</tbody>
|
|
27
|
+
</table>
|
|
28
|
+
{{/contentList}}
|
|
@@ -1,31 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
</
|
|
5
|
-
<div class="panel-body">
|
|
6
|
-
<table class="table table-striped">
|
|
7
|
-
<tbody>
|
|
8
|
-
<tr>
|
|
9
|
-
<th>ПІБ</th>
|
|
10
|
-
<td>{{sur_name}} {{user_name}} {{father_name}}</td>
|
|
11
|
-
</tr>
|
|
12
|
-
<tr>
|
|
13
|
-
<th>Email</th>
|
|
14
|
-
<td> {{{coalesce email '-'}}}</td>
|
|
15
|
-
</tr>
|
|
16
|
-
<tr>
|
|
17
|
-
<th>Телефон</th>
|
|
18
|
-
<td>{{coalesce phone '-'}}</td>
|
|
19
|
-
</tr>
|
|
20
|
-
<tr>
|
|
21
|
-
<th>Логін</th>
|
|
22
|
-
<td>{{coalesce login '-'}}</td>
|
|
23
|
-
</tr>
|
|
24
|
-
<tr>
|
|
25
|
-
<th>Тип користувача</th>
|
|
26
|
-
<td>{{select (coalesce user_type 'regular') data="users.user_type" return="-"}}</td>
|
|
27
|
-
</tr>
|
|
28
|
-
</tbody>
|
|
29
|
-
</table>
|
|
1
|
+
<dl class="-my-3 divide-y divide-gray-100 text-sm">
|
|
2
|
+
<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4">
|
|
3
|
+
<dt class="font-medium text-gray-900">ПІБ</dt>
|
|
4
|
+
<dd class="text-gray-700 sm:col-span-2">{{sur_name}} {{user_name}} {{father_name}}</dd>
|
|
30
5
|
</div>
|
|
31
|
-
|
|
6
|
+
|
|
7
|
+
<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4">
|
|
8
|
+
<dt class="font-medium text-gray-900">Електронна пошта</dt>
|
|
9
|
+
<dd class="text-gray-700 sm:col-span-2">{{email}}</dd>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4">
|
|
13
|
+
<dt class="font-medium text-gray-900">Телефон</dt>
|
|
14
|
+
<dd class="text-gray-700 sm:col-span-2">{{phone}}</dd>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4">
|
|
18
|
+
<dt class="font-medium text-gray-900">РНОКПП</dt>
|
|
19
|
+
<dd class="text-gray-700 sm:col-span-2">{{user_rnokpp}}</dd>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="grid grid-cols-1 gap-1 py-3 sm:grid-cols-3 sm:gap-4">
|
|
23
|
+
<dt class="font-medium text-gray-900">ЄДРПОУ</dt>
|
|
24
|
+
<dd class="text-gray-700 sm:col-span-2">{{organ_edrpou}}</dd>
|
|
25
|
+
</div>
|
|
26
|
+
</dl>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{{#contentList table="log.table_log" query="uid='{{uid}}'" order="cdate desc" sql1=1}}
|
|
2
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
3
|
+
<thead class="bg-gray-50">
|
|
4
|
+
<tr>
|
|
5
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
6
|
+
Дата</th>
|
|
7
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
8
|
+
Тип операції</th>
|
|
9
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
10
|
+
Таблиця</th>
|
|
11
|
+
</tr>
|
|
12
|
+
</thead>
|
|
13
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
14
|
+
{{#each rows}}
|
|
15
|
+
<tr>
|
|
16
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-F500">
|
|
17
|
+
{{formatDate cdate format="dd.MM.yy hh:mm"}}</td>
|
|
18
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-F500">
|
|
19
|
+
{{_type}}</td>
|
|
20
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-F500">
|
|
21
|
+
{{_table}}</td>
|
|
22
|
+
</tr>
|
|
23
|
+
{{else}}
|
|
24
|
+
<tr>
|
|
25
|
+
<td colspan="3" class="px-6 py-4 whitespace-nowrap text-center text-sm font-medium text-gray-500">
|
|
26
|
+
Поки що не зафіксовано активності</td>
|
|
27
|
+
</tr>
|
|
28
|
+
{{/each}}
|
|
29
|
+
</tbody>
|
|
30
|
+
</table>
|
|
31
|
+
{{/contentList}}
|
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
{{#contentList table="admin.user_roles" query="user_uid='{{id}}'" order="cdate desc" limit="50" sql1=1}}
|
|
2
|
+
<table class="min-w-full divide-y divide-gray-200">
|
|
3
|
+
<thead class="bg-gray-50">
|
|
4
|
+
<tr>
|
|
5
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
6
|
+
Група</th>
|
|
7
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
8
|
+
Закінчення терміну дії доступу до групи</th>
|
|
9
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
10
|
+
Дії</th>
|
|
11
|
+
</tr>
|
|
10
12
|
</thead>
|
|
11
|
-
<tbody>
|
|
13
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
|
12
14
|
{{#each rows}}
|
|
13
15
|
<tr>
|
|
14
|
-
<td
|
|
15
|
-
|
|
16
|
-
<td>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<a reload=1 del="admin.user_roles/{{ugr_id}}" class="btn btn-default btn-xs"><i
|
|
20
|
-
class="fa fa-trash"></i></a>
|
|
21
|
-
</td>
|
|
16
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-F500">
|
|
17
|
+
{{select role_id data="core.roles"}}</td>
|
|
18
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-F500">
|
|
19
|
+
{{formatDate expiration}}</td>
|
|
20
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-F500"></td>
|
|
22
21
|
</tr>
|
|
23
22
|
{{/each}}
|
|
24
23
|
</tbody>
|
|
@@ -1,74 +1,146 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
2
|
+
"schema": {
|
|
3
|
+
"d1": {
|
|
4
|
+
"type": "HTML",
|
|
5
|
+
"title": false,
|
|
6
|
+
"text": "<h4 class='text-center'>Акаунт</h4><br>",
|
|
7
|
+
"col": 12
|
|
8
|
+
},
|
|
9
|
+
"user_name": {
|
|
10
|
+
"type": "Text",
|
|
11
|
+
"validators": [
|
|
12
|
+
"required"
|
|
13
|
+
],
|
|
14
|
+
"ua": "Ім'я",
|
|
15
|
+
"i": "Вноситься ім'я користувача, що буде відображатися у системі",
|
|
16
|
+
"ru": "Имя"
|
|
17
|
+
},
|
|
18
|
+
"sur_name": {
|
|
19
|
+
"type": "Text",
|
|
20
|
+
"validators": [
|
|
21
|
+
"required"
|
|
22
|
+
],
|
|
23
|
+
"ua": "Прізвище",
|
|
24
|
+
"i": "Вноситься прізвище користувача, що буде відображатися у системі",
|
|
25
|
+
"ru": "Фамилия"
|
|
26
|
+
},
|
|
27
|
+
"father_name": {
|
|
28
|
+
"ua": "По-батькові",
|
|
29
|
+
"type": "text"
|
|
30
|
+
},
|
|
31
|
+
"phone": {
|
|
32
|
+
"type": "MarkedText",
|
|
33
|
+
"mask": "+389999999999",
|
|
34
|
+
"id": "1",
|
|
35
|
+
"ua": "Телефон",
|
|
36
|
+
"i": "Вноситься телефон користувача",
|
|
37
|
+
"ru": "Телефон"
|
|
38
|
+
},
|
|
39
|
+
"email": {
|
|
40
|
+
"type": "Email",
|
|
41
|
+
"ua": "E-mail",
|
|
42
|
+
"i": "Вноситься електронна адреса користувача",
|
|
43
|
+
"ru": "E-mail"
|
|
44
|
+
},
|
|
45
|
+
"d2": {
|
|
46
|
+
"type": "HTML",
|
|
47
|
+
"title": false,
|
|
48
|
+
"text": "<h4 class='text-center'>Логін / Пароль</h4><br>",
|
|
49
|
+
"col": 12
|
|
50
|
+
},
|
|
51
|
+
"login": {
|
|
52
|
+
"type": "Text",
|
|
53
|
+
"validators": [
|
|
54
|
+
"required"
|
|
55
|
+
],
|
|
56
|
+
"ua": "Логін",
|
|
57
|
+
"i": "Вноситься довільний логін користувача латинськими літерами, що буде використовуватися для входу в систему",
|
|
58
|
+
"ru": "Логин"
|
|
59
|
+
},
|
|
60
|
+
"password": {
|
|
61
|
+
"type": "Password",
|
|
62
|
+
"validators": [
|
|
63
|
+
"required",
|
|
64
|
+
{
|
|
65
|
+
"type": "regexp",
|
|
66
|
+
"regexp": "^.{8,}$",
|
|
67
|
+
"flags": "gm",
|
|
68
|
+
"message": "Пароль повинен бути більше 8 символів"
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"ua": "Пароль",
|
|
72
|
+
"i": "Вноситься пароль, що буде використовуватися для входу в систему (рекомендоване використання складних паролів)",
|
|
73
|
+
"ru": "Пароль"
|
|
74
|
+
},
|
|
75
|
+
"d3": {
|
|
76
|
+
"type": "HTML",
|
|
77
|
+
"title": false,
|
|
78
|
+
"text": "<h4 class='text-center'>Доступ</h4><br>",
|
|
79
|
+
"col": 12
|
|
80
|
+
},
|
|
81
|
+
"user_type": {
|
|
82
|
+
"type": "Autocomplete",
|
|
83
|
+
"data": "users.user_type",
|
|
84
|
+
"default": "regular",
|
|
85
|
+
"ua": "Тип користувача"
|
|
86
|
+
},
|
|
87
|
+
"enabled": {
|
|
88
|
+
"type": "Switcher",
|
|
89
|
+
"ua": "Off/On",
|
|
90
|
+
"help": "Off - користувач вимкнутий; On - Користувач увімкнутий"
|
|
91
|
+
},
|
|
92
|
+
"readonly": {
|
|
93
|
+
"type": "Switcher",
|
|
94
|
+
"ua": "Read Only",
|
|
95
|
+
"help": "On - У користувача усі інтерфейси в режимі перегляду, без редагування"
|
|
96
|
+
},
|
|
97
|
+
"d4": {
|
|
98
|
+
"type": "HTML",
|
|
99
|
+
"title": false,
|
|
100
|
+
"text": "<h4 class='text-center'>Профіль</h4><br>",
|
|
101
|
+
"col": 12
|
|
102
|
+
},
|
|
103
|
+
"user_rnokpp": {
|
|
104
|
+
"type": "MarkedText",
|
|
105
|
+
"mask": "9999999999",
|
|
106
|
+
"ua": "РНОКПП",
|
|
107
|
+
"col": 6,
|
|
108
|
+
"validators": [
|
|
109
|
+
{
|
|
110
|
+
"type": "regexp",
|
|
111
|
+
"regexp": "^([0-9]{10,10})$",
|
|
112
|
+
"flags": "g",
|
|
113
|
+
"message": "Лише цифри, 10 символів"
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
},
|
|
117
|
+
"organ_edrpou": {
|
|
118
|
+
"type": "MarkedText",
|
|
119
|
+
"mask": "99999999",
|
|
120
|
+
"ua": "Код ЄДРПОУ",
|
|
121
|
+
"col": 6,
|
|
122
|
+
"validators": [
|
|
123
|
+
{
|
|
124
|
+
"type": "regexp",
|
|
125
|
+
"regexp": "^([0-9]{8,8})$",
|
|
126
|
+
"flags": "g",
|
|
127
|
+
"message": "Лише цифри, 8 символів"
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
"avatar": {
|
|
132
|
+
"type": "File",
|
|
133
|
+
"ua": "Аватар",
|
|
134
|
+
"i": "Додається зображення, що буде відображено в системі у якості аватара цього користувача",
|
|
135
|
+
"ru": "Аватар",
|
|
136
|
+
"dir": "avatar"
|
|
137
|
+
},
|
|
138
|
+
"groups": {
|
|
139
|
+
"title": false,
|
|
140
|
+
"type": "DataTable",
|
|
141
|
+
"ua": "Групи доступу",
|
|
142
|
+
"parent_id": "user_uid",
|
|
143
|
+
"table": "admin.user_roles",
|
|
72
144
|
"col": 12,
|
|
73
145
|
"colModel": [
|
|
74
146
|
{
|
|
@@ -86,5 +158,8 @@
|
|
|
86
158
|
"type": "DatePicker"
|
|
87
159
|
}
|
|
88
160
|
]
|
|
89
|
-
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
"label_style": "vertical",
|
|
164
|
+
"width": 900
|
|
90
165
|
}
|
|
@@ -5,12 +5,6 @@
|
|
|
5
5
|
"name": "administration",
|
|
6
6
|
"icon": "fa fa-cogs",
|
|
7
7
|
"menu": [
|
|
8
|
-
{
|
|
9
|
-
"path": "admin.properties",
|
|
10
|
-
"table": "admin.properties.table",
|
|
11
|
-
"ua": "Налаштування",
|
|
12
|
-
"en": "Settings"
|
|
13
|
-
},
|
|
14
8
|
{
|
|
15
9
|
"path": "admin.users",
|
|
16
10
|
"table": "admin.users.table",
|
|
@@ -20,7 +14,7 @@
|
|
|
20
14
|
{
|
|
21
15
|
"path": "admin.roles",
|
|
22
16
|
"table": "admin.roles.table",
|
|
23
|
-
"ua": "
|
|
17
|
+
"ua": "Ролі/Групи",
|
|
24
18
|
"en": "Roles"
|
|
25
19
|
},
|
|
26
20
|
{
|
|
@@ -28,6 +22,18 @@
|
|
|
28
22
|
"table": "admin.routes.table",
|
|
29
23
|
"ua": "Інтерфейси",
|
|
30
24
|
"en": "Routes"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"path": "admin.properties",
|
|
28
|
+
"table": "admin.properties.table",
|
|
29
|
+
"ua": "Налаштування",
|
|
30
|
+
"en": "Settings"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"path": "admin.user_properties",
|
|
34
|
+
"table": "admin.user_properties.table",
|
|
35
|
+
"ua": "Налаштування користувача",
|
|
36
|
+
"en": "Settings"
|
|
31
37
|
}
|
|
32
38
|
]
|
|
33
39
|
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
+
"ua": "Налаштування",
|
|
3
|
+
"key": "property_id",
|
|
4
|
+
"form": "admin.properties.form",
|
|
5
|
+
"table": "admin.properties",
|
|
6
|
+
"order": "cdate desc",
|
|
7
|
+
"meta": {
|
|
8
|
+
"search": "property_key,property_text"
|
|
9
|
+
},
|
|
2
10
|
"columns": [
|
|
3
11
|
{
|
|
4
12
|
"name": "property_key",
|
|
@@ -11,13 +19,6 @@
|
|
|
11
19
|
"format": "text"
|
|
12
20
|
}
|
|
13
21
|
],
|
|
14
|
-
"ua": "Налаштування",
|
|
15
|
-
"form": "admin.properties.form",
|
|
16
|
-
"table": "admin.properties",
|
|
17
|
-
"order": "cdate desc",
|
|
18
|
-
"meta": {
|
|
19
|
-
"search": "property_key,property_text"
|
|
20
|
-
},
|
|
21
22
|
"filters": [
|
|
22
23
|
{
|
|
23
24
|
"ua": "Ключ",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ua": "Налаштування користувача",
|
|
3
|
+
"key": "property_id",
|
|
4
|
+
"form": "admin.user_properties.form",
|
|
5
|
+
"table": "admin.user_properties",
|
|
6
|
+
"order": "cdate desc",
|
|
7
|
+
"meta": {
|
|
8
|
+
"search": "property_key"
|
|
9
|
+
},
|
|
10
|
+
"columns": [
|
|
11
|
+
{
|
|
12
|
+
"name": "property_key",
|
|
13
|
+
"title": "Ключ",
|
|
14
|
+
"format": "text"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "property_json",
|
|
18
|
+
"title": "Значення",
|
|
19
|
+
"format": "text"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"filters": [
|
|
23
|
+
{
|
|
24
|
+
"ua": "Ключ",
|
|
25
|
+
"name": "property_key",
|
|
26
|
+
"type": "text"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -1,65 +1,126 @@
|
|
|
1
1
|
{
|
|
2
|
+
"key": "uid",
|
|
2
3
|
"ua": "Користувачі",
|
|
3
|
-
"form": "admin.users.form",
|
|
4
4
|
"table": "admin.users",
|
|
5
|
+
"form": "admin.users.form",
|
|
5
6
|
"order": "cdate desc",
|
|
7
|
+
"action": [
|
|
8
|
+
"edit",
|
|
9
|
+
"del"
|
|
10
|
+
],
|
|
11
|
+
"meta": {
|
|
12
|
+
"title": "user_name",
|
|
13
|
+
"search": "sur_name,user_name,uid,father_name,email,phone,login"
|
|
14
|
+
},
|
|
6
15
|
"sql": [
|
|
7
16
|
{
|
|
8
|
-
"sql": "select
|
|
9
|
-
"name": "
|
|
17
|
+
"sql": "select count(*) as group_count from admin.user_group_rel where user_uid=t.uid",
|
|
18
|
+
"name": "group_count_sql"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"sql": "select concat(user_name, ' ', sur_name, ' ', father_name, ' ') as full_name",
|
|
22
|
+
"name": "full_name_sql"
|
|
10
23
|
},
|
|
11
24
|
{
|
|
12
|
-
"sql": "select
|
|
13
|
-
"name": "
|
|
25
|
+
"sql": "select string_agg(group_name,',') as groups from admin.user_group where user_group_id in (select user_group_id from admin.user_group_rel where user_uid=t.uid)",
|
|
26
|
+
"name": "group_list_sql"
|
|
14
27
|
}
|
|
15
28
|
],
|
|
16
29
|
"columns": [
|
|
17
30
|
{
|
|
31
|
+
"ua": "ПІБ",
|
|
18
32
|
"name": "full_name",
|
|
19
|
-
"format": "text"
|
|
20
|
-
"title": "ПІБ"
|
|
33
|
+
"format": "text"
|
|
21
34
|
},
|
|
22
35
|
{
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
36
|
+
"ua": "Логін",
|
|
37
|
+
"name": "login",
|
|
38
|
+
"format": "text"
|
|
26
39
|
},
|
|
27
40
|
{
|
|
41
|
+
"ua": "Email",
|
|
28
42
|
"name": "email",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
43
|
+
"hidden": true,
|
|
44
|
+
"format": "text"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"ua": "РНОКПП",
|
|
48
|
+
"name": "user_rnokpp",
|
|
49
|
+
"hidden": true,
|
|
50
|
+
"format": "text"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"ua": "Код ЄДРПОУ",
|
|
54
|
+
"name": "organ_edrpou",
|
|
55
|
+
"hidden": true,
|
|
56
|
+
"format": "text"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"name": "group_count",
|
|
60
|
+
"ua": "Кількість груп",
|
|
61
|
+
"format": "text"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "groups",
|
|
65
|
+
"ua": "Групи",
|
|
66
|
+
"hidden": true,
|
|
67
|
+
"format": "text"
|
|
31
68
|
},
|
|
32
69
|
{
|
|
33
70
|
"name": "cdate",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
71
|
+
"ua": "Створено",
|
|
72
|
+
"format": "date"
|
|
36
73
|
},
|
|
37
74
|
{
|
|
38
|
-
"name": "
|
|
39
|
-
"
|
|
40
|
-
"
|
|
75
|
+
"name": "editor_date",
|
|
76
|
+
"ua": "Дата останньої активності",
|
|
77
|
+
"format": "date"
|
|
41
78
|
},
|
|
42
79
|
{
|
|
43
80
|
"name": "enabled",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
81
|
+
"ua": "Вимикач",
|
|
82
|
+
"data": "yes_no",
|
|
83
|
+
"format": "select"
|
|
46
84
|
}
|
|
47
85
|
],
|
|
48
|
-
"
|
|
49
|
-
"title": "cdate",
|
|
50
|
-
"search": "login"
|
|
51
|
-
},
|
|
52
|
-
"filters": [
|
|
86
|
+
"filter_list": [
|
|
53
87
|
{
|
|
54
|
-
"ua": "
|
|
55
|
-
"name": "
|
|
56
|
-
"type": "
|
|
88
|
+
"ua": "РНОКПП",
|
|
89
|
+
"name": "user_rnokpp",
|
|
90
|
+
"type": "Text"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"ua": "Код ЄДРПОУ",
|
|
94
|
+
"name": "organ_edrpou",
|
|
95
|
+
"type": "Text"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"ua": "Email",
|
|
99
|
+
"name": "email",
|
|
100
|
+
"type": "Text"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"en": "Group",
|
|
104
|
+
"ua": "Група",
|
|
105
|
+
"name": "groups",
|
|
106
|
+
"type": "Text"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"en": "On/Off",
|
|
110
|
+
"ua": "On/Off",
|
|
111
|
+
"data": "enabled",
|
|
112
|
+
"name": "enabled",
|
|
113
|
+
"type": "Check"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"ua": "Дата створення",
|
|
117
|
+
"name": "cdate",
|
|
118
|
+
"type": "Date"
|
|
57
119
|
},
|
|
58
120
|
{
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"type": "check"
|
|
121
|
+
"name": "editor_date",
|
|
122
|
+
"type": "Date",
|
|
123
|
+
"ua": "Дата останньої активності"
|
|
63
124
|
}
|
|
64
125
|
]
|
|
65
126
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengis/admin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "This project Softpro Admin",
|
|
5
5
|
"main": "dist/admin.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"module/*",
|
|
13
13
|
"server/*",
|
|
14
14
|
"plugin.js",
|
|
15
|
+
"util.js",
|
|
15
16
|
"config.js"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
"dependencies": {
|
|
34
35
|
"@opengis/fastify-auth": "^1.0.20",
|
|
35
36
|
"@opengis/fastify-file": "^1.0.17",
|
|
36
|
-
"@opengis/fastify-table": "^1.1.
|
|
37
|
+
"@opengis/fastify-table": "^1.1.16",
|
|
37
38
|
"@opengis/v3-core": "^0.1.92",
|
|
38
39
|
"@opengis/v3-filter": "^0.0.31",
|
|
39
40
|
"@turf/turf": "^7.1.0",
|
package/plugin.js
CHANGED
|
@@ -17,6 +17,7 @@ async function plugin(fastify, opts = config) {
|
|
|
17
17
|
|
|
18
18
|
// API
|
|
19
19
|
|
|
20
|
+
fastify.register(import('./server/routes/properties/index.mjs'), opts);
|
|
20
21
|
fastify.register(import('./server/routes/templates/index.mjs'), opts);
|
|
21
22
|
fastify.register(import('./server/routes/menu/index.mjs'), opts);
|
|
22
23
|
fastify.register(import('./server/routes/data/index.mjs'), opts);
|
package/server/plugins/hook.js
CHANGED
|
@@ -1,18 +1,85 @@
|
|
|
1
1
|
import fp from 'fastify-plugin';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
2
4
|
|
|
3
|
-
import
|
|
4
|
-
import path from 'path'
|
|
5
|
-
|
|
6
|
-
import config from '../../config.js';
|
|
5
|
+
import { userTemplateDir, getTemplate, pgClients } from '@opengis/fastify-table/utils.js';
|
|
7
6
|
|
|
7
|
+
import getMenu from '../routes/menu/controllers/getMenu.js';
|
|
8
8
|
|
|
9
|
+
import config from '../../config.js';
|
|
9
10
|
|
|
10
11
|
// to export the decorators to the outer scope
|
|
11
12
|
|
|
12
13
|
async function plugin(fastify) {
|
|
13
14
|
fastify.decorate('config', config);
|
|
14
15
|
|
|
16
|
+
fastify.addHook('onListen', async () => {
|
|
17
|
+
const { client } = pgClients;
|
|
18
|
+
const json = await getMenu();
|
|
19
|
+
// insert interface list to db (user access management)
|
|
20
|
+
if (client?.pk?.['admin.routes'] && json?.length) {
|
|
21
|
+
const menuList = json.filter((el) => el?.menu?.length && el?.ua || el?.en || el?.name);
|
|
22
|
+
const interfaces = menuList.reduce((acc, curr) => { curr.menu.forEach((el) => acc.push(el.path)); return acc; }, []);
|
|
23
|
+
await client.query('update admin.routes set enabled=false where not array[route_id] <@ $1::text[]', [interfaces]);
|
|
24
|
+
|
|
25
|
+
const q = `insert into admin.menu(name, ord) values${
|
|
26
|
+
menuList.map((el, i) => `('${(el?.ua || el?.en || el?.name).replace(/'/g, '’')}', ${i}) `).join(',')
|
|
27
|
+
} on conflict (name) do update set ord=excluded.ord, enabled=true returning name, menu_id`;
|
|
28
|
+
const { rows = [] } = await client.query(q);
|
|
29
|
+
await client.query('update admin.menu set enabled=false where not array[menu_id] <@ $1::text[]', [rows.map((el) => el.menu_id)]);
|
|
30
|
+
|
|
31
|
+
const menus = rows.reduce((acc, curr) => Object.assign(acc, { [curr.menu_id]: menuList.find((item) => (item?.ua || item?.en || item?.name) === curr.name) }), {});
|
|
32
|
+
const values = Object.entries(menus).reduce((acc, curr) => { curr[1]?.menu?.forEach((el) => acc.push({ ...el, menuId: curr[0] })); return acc; }, []);
|
|
33
|
+
|
|
34
|
+
await Promise.all(values.filter((el) => el?.table).map(async (el) => Object.assign(el, { table: (await getTemplate('table', el.table))?.table || el.table })));
|
|
35
|
+
|
|
36
|
+
const q1 = `insert into admin.routes(route_id, alias, title, menu_id, table_name) values ${values.map((el) => `('${el.path}', '${el.path}', '${el.title}', '${el.menuId}', '${el.table}')`).join(',')}
|
|
37
|
+
on conflict (route_id) do update set menu_id=excluded.menu_id, title=excluded.title, enabled=true,
|
|
38
|
+
table_name=excluded.table_name returning route_id, table_name`;
|
|
39
|
+
try {
|
|
40
|
+
const { rowCount } = await client.query(q1);
|
|
41
|
+
console.log('interface insert ok', values, rowCount);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.log('interface insert error', values, q1, err);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
15
47
|
|
|
48
|
+
fastify.addHook('onListen', async () => {
|
|
49
|
+
const { client: pg } = pgClients;
|
|
50
|
+
const clsQuery = [];
|
|
51
|
+
if (!pg.pk?.['admin.cls']) return;
|
|
52
|
+
|
|
53
|
+
const cls = ['cls', 'select']
|
|
54
|
+
.filter((el) => fs.existsSync(`${userTemplateDir[0]}/${el}`))
|
|
55
|
+
.map((el) => fs.readdirSync(`${userTemplateDir[0]}/${el}`).filter((item) => el === 'cls' ? true : path.extname(item) === '.sql'))
|
|
56
|
+
.reduce((acc, curr) => { curr?.forEach((el) => acc.push(el)); return acc; }, []);
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
await Promise.all(cls.map(async (el) => {
|
|
60
|
+
const { ext, name } = path.parse(el);
|
|
61
|
+
const type = { '.json': 'cls', '.sql': 'select' }[ext];
|
|
62
|
+
const loadTemplate = await getTemplate(type, name);
|
|
63
|
+
console.log(name, type);
|
|
64
|
+
if (type === 'select') {
|
|
65
|
+
clsQuery.push(`insert into admin.cls(name,type,data) values('${name}','sql','${(loadTemplate?.sql || loadTemplate)?.replace(/'/g, "''")}')`);
|
|
66
|
+
} else if (type === 'cls') {
|
|
67
|
+
clsQuery.push(`insert into admin.cls(name,type) values('${name}','json');
|
|
68
|
+
insert into admin.cls(code,name,parent,icon)
|
|
69
|
+
select value->>'id',value->>'text','${name}',value->>'icon'
|
|
70
|
+
from json_array_elements('${JSON.stringify(loadTemplate).replace(/'/g, "''")}'::json)`);
|
|
71
|
+
}
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
await pg.query('truncate admin.cls');
|
|
75
|
+
if (clsQuery.filter((el) => el).length) {
|
|
76
|
+
await pg.query(clsQuery.filter((el) => el).join(';'));
|
|
77
|
+
console.log('cls insert ok', clsQuery?.length);
|
|
78
|
+
}
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error('cls insert error', err.toString());
|
|
81
|
+
}
|
|
82
|
+
});
|
|
16
83
|
|
|
17
84
|
// pre Request
|
|
18
85
|
fastify.addHook('onRequest', async (req) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import { join } from 'path';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { userTemplateDir } from '@opengis/fastify-table/utils.js';
|
|
4
|
+
|
|
5
5
|
import { existsSync, readdirSync, readFileSync } from 'fs';
|
|
6
6
|
const menuCache = [];
|
|
7
7
|
// check module dir
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const table = 'admin.properties';
|
|
2
|
+
|
|
3
|
+
import { getRedis } from '@opengis/fastify-table/utils.js';
|
|
4
|
+
|
|
5
|
+
import { getSettings } from '../../../../utils.js';
|
|
6
|
+
|
|
7
|
+
export default async function getSettingsAPI({
|
|
8
|
+
pg = {}, funcs = {}, params = {}, query = {},
|
|
9
|
+
}) {
|
|
10
|
+
if (!pg.pk?.[table]) {
|
|
11
|
+
return { message: 'table not found', status: 404 };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const redis = getRedis();
|
|
15
|
+
const keyCache = `${pg.options?.database}:settings:${params?.key || 'all'}:${query?.json ? 'json' : 'plain'}:${table}`;
|
|
16
|
+
|
|
17
|
+
const cache = await redis.get(keyCache);
|
|
18
|
+
if (cache && !funcs.config?.local) return JSON.parse(cache);
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const res = await getSettings({
|
|
22
|
+
pg, key: params.key, json: query.json, redis, table,
|
|
23
|
+
});
|
|
24
|
+
return { message: res, status: 200 };
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
return { error: err.toString(), status: 500 };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const table = 'admin.properties';
|
|
2
|
+
|
|
3
|
+
import { setSettings } from '../../../../utils.js';
|
|
4
|
+
|
|
5
|
+
export default async function postSettingsAPI({
|
|
6
|
+
pg, session = {}, funcs, body = {},
|
|
7
|
+
}) {
|
|
8
|
+
if (session.passport?.user?.user_type !== 'superadmin') {
|
|
9
|
+
return { message: 'access restricted', status: 403 };
|
|
10
|
+
}
|
|
11
|
+
const { key, val } = body;
|
|
12
|
+
if ((!key || !val) && !Object.keys(body).length) {
|
|
13
|
+
return { message: 'not enough params', status: 400 };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!pg?.pk?.[table]) {
|
|
17
|
+
return { message: 'table not found', status: 404 };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const res = await setSettings({
|
|
21
|
+
pg, funcs, key, val, body,
|
|
22
|
+
});
|
|
23
|
+
if (res?.error) return res;
|
|
24
|
+
|
|
25
|
+
return { message: res, status: 200 };
|
|
26
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const table = 'admin.user_properties';
|
|
2
|
+
|
|
3
|
+
import { getRedis } from '@opengis/fastify-table/utils.js';
|
|
4
|
+
|
|
5
|
+
import { getSettings } from '../../../../utils.js';
|
|
6
|
+
|
|
7
|
+
export default async function getUserProperties({
|
|
8
|
+
pg = {}, params = {}, query = {}, session = {},
|
|
9
|
+
}) {
|
|
10
|
+
const { uid } = session.passport?.user || {};
|
|
11
|
+
if (!uid) {
|
|
12
|
+
return { message: 'access restreicted', status: 403 };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!pg.pk?.[table]) {
|
|
16
|
+
return { message: 'table not found', status: 404 };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const redis = getRedis();
|
|
20
|
+
const keyCache = `${pg.options?.database}:settings:${params?.key || 'all'}:${query?.json ? 'json' : 'plain'}:user`;
|
|
21
|
+
|
|
22
|
+
const cache = await redis.get(keyCache);
|
|
23
|
+
if (cache) return JSON.parse(cache);
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const res = await getSettings({
|
|
27
|
+
pg, key: params.key, json: query.json, redis, table, uid,
|
|
28
|
+
});
|
|
29
|
+
return { message: res, status: 200 };
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
return { error: err.toString(), status: 500 };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const table = 'admin.user_properties';
|
|
2
|
+
|
|
3
|
+
import { setSettings } from '../../../../utils.js';
|
|
4
|
+
|
|
5
|
+
export default async function postUserProperties({
|
|
6
|
+
pg, session = {}, body = {},
|
|
7
|
+
}) {
|
|
8
|
+
const { uid } = session.passport?.user || {};
|
|
9
|
+
|
|
10
|
+
if (!uid) {
|
|
11
|
+
return { message: 'access restricted', status: 403 };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { key, val } = body;
|
|
15
|
+
|
|
16
|
+
if ((!key || !val) && !Object.keys(body).length) {
|
|
17
|
+
return { message: 'not enough params', status: 400 };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!pg?.pk?.[table]) {
|
|
21
|
+
return { message: 'table not found', status: 404 };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const res = await setSettings({
|
|
25
|
+
pg, key, val, body, table, uid,
|
|
26
|
+
});
|
|
27
|
+
if (res?.error) return res;
|
|
28
|
+
|
|
29
|
+
return { message: res, status: 200 };
|
|
30
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getMeta } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
const match = {
|
|
4
|
+
property_key: 'key',
|
|
5
|
+
property_json: 'json',
|
|
6
|
+
property_int: 'int',
|
|
7
|
+
property_text: 'text',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function getQuery({
|
|
11
|
+
table, columns = [], key, uid,
|
|
12
|
+
}) {
|
|
13
|
+
const columnList = columns?.filter((el) => match[el?.name]).map((el) => el?.name).map((el) => `${el} as ${match[el]}`);
|
|
14
|
+
const sql = `select ${columnList.join(',')} from ${table} where 1=1`;
|
|
15
|
+
|
|
16
|
+
if (table === 'admin.user_properties') {
|
|
17
|
+
const args = [uid, key].filter((el) => el);
|
|
18
|
+
const q = sql + (uid ? ' and uid=$1' : '') + (key ? ` and property_key=$${args.indexOf(key) + 1}` : '');
|
|
19
|
+
return { q, args };
|
|
20
|
+
}
|
|
21
|
+
const args = [key].filter((el) => el);
|
|
22
|
+
const q = sql + (key ? ` and property_key=$${args.indexOf(key) + 1}` : '');
|
|
23
|
+
return { q, args };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default async function getSettings({
|
|
27
|
+
pg, redis, json, key, table = 'admin.properties', uid,
|
|
28
|
+
}) {
|
|
29
|
+
const { columns = [] } = await getMeta({ table });
|
|
30
|
+
|
|
31
|
+
const { q, args } = getQuery({
|
|
32
|
+
table, columns, key, uid,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const { rows } = await pg.query(q, args);
|
|
36
|
+
const data = rows?.reduce((acc, curr) => Object.assign(acc, { [curr.key]: curr.json || curr.int || curr.text }), {});
|
|
37
|
+
|
|
38
|
+
const jsonData = Object.keys(data || {}).reduce((acc, curr) => {
|
|
39
|
+
const [ckey, csubkey] = curr.split('.');
|
|
40
|
+
if (!csubkey) return Object.assign(acc, { [curr]: data[curr] });
|
|
41
|
+
if (!acc[ckey]) Object.assign(acc, { [ckey]: {} });
|
|
42
|
+
Object.assign(acc[ckey], { [csubkey]: data[curr] });
|
|
43
|
+
return acc;
|
|
44
|
+
}, {});
|
|
45
|
+
|
|
46
|
+
// save both
|
|
47
|
+
if (redis) {
|
|
48
|
+
const keyCacheJSON = `${pg.options?.database}:settings:${key || 'all'}:json:${table}`;
|
|
49
|
+
await redis.set(keyCacheJSON, JSON.stringify(jsonData));
|
|
50
|
+
|
|
51
|
+
const keyCachePlain = `${pg.options?.database}:settings:${key || 'all'}:plain:${table}`;
|
|
52
|
+
await redis.set(keyCachePlain, JSON.stringify(data));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return json ? jsonData : data;
|
|
56
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { getRedis } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
import { getSettings } from '../../../../utils.js';
|
|
4
|
+
import dataInsert from './utils/dataInsert.js';
|
|
5
|
+
|
|
6
|
+
function checkValueType(val) {
|
|
7
|
+
if (val) {
|
|
8
|
+
if (typeof val === 'object') {
|
|
9
|
+
return 'property_json';
|
|
10
|
+
}
|
|
11
|
+
if (typeof val === 'number' && (!/\D/.test(val.toString()) && val.toString().length < 10)) {
|
|
12
|
+
return 'property_int';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return 'property_text';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default async function setSettings({
|
|
19
|
+
pg, key, val, body = {}, table = 'admin.properties', uid,
|
|
20
|
+
}) {
|
|
21
|
+
const body1 = key && val ? { [key]: val } : body;
|
|
22
|
+
const keys = Object.keys(body1);
|
|
23
|
+
try {
|
|
24
|
+
const redis = getRedis();
|
|
25
|
+
await pg.query(`delete from ${table} where property_key=any($1)`, [keys]);
|
|
26
|
+
|
|
27
|
+
await Promise.all(keys.map(async (el) => {
|
|
28
|
+
const columnType = table === 'admin.user_properties' ? 'property_json' : checkValueType(body1[el]);
|
|
29
|
+
const data = { property_key: el, [columnType]: body1[el], uid };
|
|
30
|
+
const { rows } = await dataInsert({
|
|
31
|
+
pg, table, data, uid,
|
|
32
|
+
});
|
|
33
|
+
return { key: el, rows: { val: body[el], data: rows?.[0] } };
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
const res = await getSettings({
|
|
37
|
+
pg, redis, table, uid,
|
|
38
|
+
});
|
|
39
|
+
return res;
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
return { error: err.toString(), status: 500 };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getMeta, getPG } from '@opengis/fastify-table/utils.js';
|
|
2
|
+
|
|
3
|
+
export default async function dataInsert({
|
|
4
|
+
table, data, pg: pg1,
|
|
5
|
+
}) {
|
|
6
|
+
const pg = pg1 || getPG({ name: 'client' });
|
|
7
|
+
if (!data) return null;
|
|
8
|
+
const { columns } = await getMeta(table);
|
|
9
|
+
if (!columns) return null;
|
|
10
|
+
|
|
11
|
+
const names = columns.map((el) => el.name);
|
|
12
|
+
const filterData = Object.keys(data)
|
|
13
|
+
.filter((el) => data[el] && names.includes(el)).map((el) => [el, data[el]]);
|
|
14
|
+
|
|
15
|
+
const insertQuery = `insert into ${table}
|
|
16
|
+
|
|
17
|
+
( ${filterData?.map((key) => `"${key[0]}"`).join(',')})
|
|
18
|
+
|
|
19
|
+
values (${filterData?.map((key, i) => (key[0] === 'property_json' ? `to_json($${i + 1}::${key[1] && typeof key[1] === 'object' ? 'json' : 'text'})` : `$${i + 1}`)).join(',')})
|
|
20
|
+
|
|
21
|
+
returning *`;
|
|
22
|
+
|
|
23
|
+
const res = await pg.query(insertQuery, [...filterData.map((el) => (typeof el[1] === 'object' && (!Array.isArray(el[1]) || typeof el[1]?.[0] === 'object') ? JSON.stringify(el[1]) : el[1]))]) || {};
|
|
24
|
+
|
|
25
|
+
return res;
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import getAdminProperties from './controllers/admin.properties.get.js';
|
|
2
|
+
import postAdminProperties from './controllers/admin.properties.post.js';
|
|
3
|
+
|
|
4
|
+
import getUserProperties from './controllers/user.properties.get.js';
|
|
5
|
+
import postUserProperties from './controllers/user.properties.post.js';
|
|
6
|
+
|
|
7
|
+
export default async function route(fastify) {
|
|
8
|
+
fastify.get('/admin-properties/:key?', {}, getAdminProperties);
|
|
9
|
+
|
|
10
|
+
fastify.route({
|
|
11
|
+
method: 'POST',
|
|
12
|
+
path: '/admin-properties',
|
|
13
|
+
config: {
|
|
14
|
+
policy: ['superadmin'],
|
|
15
|
+
},
|
|
16
|
+
handler: postAdminProperties,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
fastify.get('/user-properties/:key?', {}, getUserProperties);
|
|
20
|
+
|
|
21
|
+
fastify.route({
|
|
22
|
+
method: 'POST',
|
|
23
|
+
path: '/user-properties',
|
|
24
|
+
handler: postUserProperties,
|
|
25
|
+
});
|
|
26
|
+
}
|