@opengis/fastify-table 1.3.71 → 1.3.73
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/README.md +86 -86
- package/index.js +9 -0
- package/package.json +1 -1
- package/server/helpers/format/formatAuto.js +13 -13
- package/server/helpers/format/formatDate.js +258 -258
- package/server/helpers/format/formatDigit.js +20 -20
- package/server/helpers/format/formatNum.js +361 -361
- package/server/helpers/format/formatNumber.js +54 -54
- package/server/helpers/format/formatRelative.js +106 -106
- package/server/helpers/format/formatUnit.js +38 -38
- package/server/helpers/format/num_format.js +41 -41
- package/server/helpers/funcs/_math.js +49 -49
- package/server/helpers/funcs/empty.js +21 -21
- package/server/helpers/funcs/ifCond.js +106 -106
- package/server/helpers/funcs/ifCondAnd.js +96 -96
- package/server/helpers/funcs/ifCondOr.js +98 -98
- package/server/helpers/funcs/inc.js +20 -20
- package/server/helpers/funcs/json.js +2 -2
- package/server/helpers/funcs/round.js +27 -27
- package/server/helpers/string/coalesce.js +31 -31
- package/server/helpers/string/concat.js +28 -28
- package/server/helpers/string/split.js +19 -19
- package/server/helpers/string/str_replace.js +60 -60
- package/server/helpers/string/substr.js +31 -31
- package/server/helpers/string/translit.js +23 -23
- package/server/helpers/string/utils/alphabet.js +75 -75
- package/server/plugins/cron/funcs/addCron.js +52 -52
- package/server/plugins/cron/index.js +76 -76
- package/server/plugins/crud/funcs/getOpt.js +14 -14
- package/server/plugins/crud/funcs/setOpt.js +21 -21
- package/server/plugins/crud/funcs/setToken.js +43 -43
- package/server/plugins/crud/funcs/utils/getFolder.js +11 -11
- package/server/plugins/crud/index.js +23 -23
- package/server/plugins/hook/index.js +8 -8
- package/server/plugins/logger/errorStatus.js +19 -19
- package/server/plugins/logger/index.js +26 -26
- package/server/plugins/migration/index.js +7 -7
- package/server/plugins/pg/index.js +11 -2
- package/server/plugins/policy/sqlInjection.js +33 -33
- package/server/plugins/redis/client.js +8 -8
- package/server/plugins/redis/funcs/redisClients.js +3 -3
- package/server/plugins/redis/index.js +17 -17
- package/server/plugins/table/funcs/getFilterSQL/util/getCustomQuery.js +13 -13
- package/server/plugins/table/funcs/getFilterSQL/util/getTableSql.js +34 -34
- package/server/plugins/table/funcs/getTemplates.js +19 -19
- package/server/plugins/table/funcs/gisIRColumn.js +82 -82
- package/server/plugins/table/funcs/loadTemplate.js +1 -1
- package/server/plugins/table/funcs/loadTemplatePath.js +1 -1
- package/server/plugins/table/funcs/userTemplateDir.js +1 -1
- package/server/plugins/table/index.js +13 -13
- package/server/plugins/util/index.js +7 -7
- package/server/routes/cron/index.js +16 -14
- package/server/routes/crud/index.js +8 -7
- package/server/routes/data/controllers/cardData.js +144 -0
- package/server/routes/data/controllers/cardTabData.js +58 -0
- package/server/routes/data/controllers/funcs/getFilterSQL/index.js +92 -0
- package/server/routes/data/controllers/funcs/getFilterSQL/util/formatValue.js +170 -0
- package/server/routes/data/controllers/funcs/getFilterSQL/util/getCustomQuery.js +13 -0
- package/server/routes/data/controllers/funcs/getFilterSQL/util/getFilterQuery.js +64 -0
- package/server/routes/data/controllers/funcs/getFilterSQL/util/getOptimizedQuery.js +12 -0
- package/server/routes/data/controllers/funcs/getFilterSQL/util/getTableSql.js +34 -0
- package/server/routes/data/controllers/tableData.js +55 -0
- package/server/routes/data/controllers/tableFilter.js +16 -0
- package/server/routes/data/controllers/tableInfo.js +110 -0
- package/server/routes/data/controllers/tokenInfo.js +12 -0
- package/server/routes/data/controllers/utils/assignTokens.js +31 -0
- package/server/routes/data/controllers/utils/conditions.js +19 -0
- package/server/routes/data/controllers/utils/getColumns.js +9 -0
- package/server/routes/data/index.mjs +22 -0
- package/server/routes/data/schema.js +54 -0
- package/server/routes/dblist/index.mjs +9 -7
- package/server/routes/logger/controllers/logger.file.js +93 -93
- package/server/routes/logger/controllers/utils/checkUserAccess.js +19 -19
- package/server/routes/logger/controllers/utils/getRootDir.js +26 -26
- package/server/routes/logger/index.js +17 -17
- package/server/routes/menu/controllers/getMenu.js +97 -0
- package/server/routes/menu/controllers/interfaces.js +21 -0
- package/server/routes/menu/index.mjs +8 -0
- package/server/routes/menu/schema.js +0 -0
- package/server/routes/properties/controllers/properties.add.js +55 -55
- package/server/routes/properties/controllers/properties.get.js +17 -17
- package/server/routes/properties/index.js +16 -16
- package/server/routes/table/controllers/form.js +42 -42
- package/server/routes/table/controllers/search.js +74 -74
- package/server/routes/table/controllers/suggest.js +28 -14
- package/server/routes/table/index.js +10 -10
- package/server/routes/table/schema.js +65 -64
- package/server/routes/templates/controllers/getTemplate.js +51 -0
- package/server/routes/templates/index.mjs +10 -0
- package/server/routes/templates/schema.js +9 -0
- package/server/routes/util/controllers/status.monitor.js +8 -8
- package/server/routes/util/index.js +2 -2
- package/utils.js +2 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getTemplate, getMeta, metaFormat, applyHook, handlebars, pgClients,
|
|
3
|
+
} from '../../../../utils.js';
|
|
4
|
+
|
|
5
|
+
export default async function tableInfo(req, reply) {
|
|
6
|
+
const {
|
|
7
|
+
pg = pgClients.client, params = {}, query = {}, user = {},
|
|
8
|
+
} = req;
|
|
9
|
+
|
|
10
|
+
const time = Date.now();
|
|
11
|
+
|
|
12
|
+
const { uid } = user;
|
|
13
|
+
|
|
14
|
+
if (!uid) {
|
|
15
|
+
return reply.status(403).send('access restricted: uid');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!params?.id || !params?.table) {
|
|
19
|
+
return reply.status(400).send('not enougn params');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const loadTable = await getTemplate('table', params.table);
|
|
23
|
+
|
|
24
|
+
if (!loadTable) {
|
|
25
|
+
return reply.status(404).send('template not found');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
table, columns = [], sql, cardSql, filters, form, meta, public: ispublic,
|
|
30
|
+
} = loadTable;
|
|
31
|
+
|
|
32
|
+
if (!meta?.info) {
|
|
33
|
+
return reply.status(400).send('empty meta info');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const tableMeta = await getMeta({ pg, table });
|
|
37
|
+
|
|
38
|
+
if (tableMeta?.view) {
|
|
39
|
+
if (!loadTable?.key) {
|
|
40
|
+
return reply.status(404).send(`key not found: ${table}`);
|
|
41
|
+
}
|
|
42
|
+
Object.assign(tableMeta, { pk: loadTable?.key });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { pk } = tableMeta || {};
|
|
46
|
+
|
|
47
|
+
if (!pk) {
|
|
48
|
+
return reply.status(404).send(`table not found: ${table}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const sqlTable = sql
|
|
52
|
+
?.filter?.((el) => !el?.disabled && el?.sql?.replace)
|
|
53
|
+
?.map((el, i) => ` left join lateral (${el.sql.replace('{{uid}}', uid)}) ${el.name || `t${i}`} on 1=1 `)
|
|
54
|
+
?.join('') || '';
|
|
55
|
+
|
|
56
|
+
const cardSqlFiltered = params.id ? (cardSql?.filter?.((el) => !el?.disabled && el?.name && el?.sql?.replace) || []) : [];
|
|
57
|
+
const cardSqlTable = cardSqlFiltered.length ? cardSqlFiltered.map((el, i) => ` left join lateral (${el.sql.replace('{{uid}}', uid)}) ct${i} on 1=1 `).join('\n') || '' : '';
|
|
58
|
+
|
|
59
|
+
const interfaceQuery = params?.query ? await handlebars.compile(params?.query)({ user, uid }) : undefined;
|
|
60
|
+
const where = [params.id ? `"${pk}" = $1` : null, loadTable.query, interfaceQuery].filter((el) => el).filter((el) => (user?.user_type === 'superadmin' ? !el.includes('{{uid}}') : true));
|
|
61
|
+
|
|
62
|
+
const { fields = [] } = await pg.query(`select * from ${table} where ${sqlTable ? 'true' : (where.join(' and ') || 'true')} limit 0`, ([sqlTable ? null : params.id].filter(el => el)) );
|
|
63
|
+
const columnList = fields.map(el => el.name);
|
|
64
|
+
|
|
65
|
+
const metaInfoCols = meta.info.split(',').filter(el => columnList.includes(el)).map((el) => `"${el}"`);
|
|
66
|
+
|
|
67
|
+
if (!metaInfoCols.length) {
|
|
68
|
+
return reply.status(400).send('invalid meta info: columns not found');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const q = `select ${pk ? `"${pk}" as id,` : ''}
|
|
72
|
+
${metaInfoCols.join(',')}
|
|
73
|
+
from (select * from ${table} where ${sqlTable ? 'true' : (where.join(' and ') || 'true')}) t
|
|
74
|
+
${sqlTable}
|
|
75
|
+
${cardSqlTable}
|
|
76
|
+
where ${where.join(' and ') || 'true'}
|
|
77
|
+
limit 1`
|
|
78
|
+
.replace(/{{uid}}/g, uid);
|
|
79
|
+
|
|
80
|
+
if (query.sql === '1') { return q; }
|
|
81
|
+
|
|
82
|
+
const { rows } = await pg.query(q, [params.id]);
|
|
83
|
+
|
|
84
|
+
const qCount = `select
|
|
85
|
+
count(*)::int as total,
|
|
86
|
+
count(*) FILTER(WHERE ${[interfaceQuery].filter(el => el).join(' and ') || 'true'})::int as filtered
|
|
87
|
+
from ${table} t ${sqlTable}
|
|
88
|
+
where ${[loadTable.query].filter(el => el).join(' and ') || 'true'} `
|
|
89
|
+
.replace(/{{uid}}/g, uid);
|
|
90
|
+
|
|
91
|
+
const { total, filtered } = params.id ? rows.length
|
|
92
|
+
: await pg.queryCache(qCount, { table: loadTable.table, time: 5 }).then(el => el?.rows?.[0] || {});
|
|
93
|
+
|
|
94
|
+
const cls = meta.info.split(',').filter(el => columnList.includes(el))
|
|
95
|
+
.map(el => ({ name: el, data: columns.find(col => col.name === el)?.data }))
|
|
96
|
+
.filter(el => el.data)
|
|
97
|
+
.reduce((acc, curr) => Object.assign(acc, { [curr.name]: curr.data }), {});
|
|
98
|
+
|
|
99
|
+
await metaFormat({ rows, cls /* , sufix: false */ }, pg);
|
|
100
|
+
|
|
101
|
+
const res = {
|
|
102
|
+
time: Date.now() - time, public: ispublic, card: loadTable.card, total, filtered, count: rows.length, pk, form, rows, meta, columns, filters,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const result = await applyHook('afterData', {
|
|
106
|
+
table: loadTable.table, payload: res, user,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return result || res;
|
|
110
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getOpt, getToken } from '../../../../utils.js';
|
|
2
|
+
|
|
3
|
+
export default async function tokenInfo(req, reply) {
|
|
4
|
+
const { params = {}, user } = req;
|
|
5
|
+
|
|
6
|
+
if (user.user_type !== 'admin') {
|
|
7
|
+
return reply.status(403).send('access restricted');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const tokenData = await getToken({ uid: user.uid, token: params.token }) || await getOpt(params.token, user.uid);
|
|
11
|
+
return tokenData;
|
|
12
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { config, setToken } from "@opengis/fastify-table/utils.js";
|
|
2
|
+
|
|
3
|
+
export default function assignTokens({
|
|
4
|
+
rows = [], ispublic, uid, loadTable = {},
|
|
5
|
+
}) {
|
|
6
|
+
if (config?.security?.disableToken) return;
|
|
7
|
+
|
|
8
|
+
if (!config?.auth?.disable && !ispublic && !uid) throw new Error('empty user');
|
|
9
|
+
if (!loadTable?.table || !(loadTable?.form || loadTable?.add_form)) return null;
|
|
10
|
+
|
|
11
|
+
const form = loadTable?.form || loadTable?.add_form;
|
|
12
|
+
const addTokens = setToken({
|
|
13
|
+
ids: [JSON.stringify({ add: loadTable.table, form })],
|
|
14
|
+
mode: 'a',
|
|
15
|
+
uid: config?.auth?.disable || ispublic ? '1' : uid,
|
|
16
|
+
array: 1,
|
|
17
|
+
});
|
|
18
|
+
if (!rows.length) return addTokens[0];
|
|
19
|
+
|
|
20
|
+
rows.forEach((row) => {
|
|
21
|
+
const editTokens = setToken({
|
|
22
|
+
ids: [JSON.stringify({ id: row.id, table: loadTable.table, form })],
|
|
23
|
+
mode: 'w',
|
|
24
|
+
uid: config?.auth?.disable || ispublic ? '1' : uid,
|
|
25
|
+
array: 1,
|
|
26
|
+
});
|
|
27
|
+
Object.assign(row, { token: editTokens[0] });
|
|
28
|
+
});
|
|
29
|
+
return addTokens[0];
|
|
30
|
+
|
|
31
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function onCheck(rule, data) {
|
|
2
|
+
const val = data[rule[0]];
|
|
3
|
+
if (rule[1] === '==') return val === rule[2];
|
|
4
|
+
if (rule[1] === '!=') return val !== rule[2];
|
|
5
|
+
if (rule[1] === 'in' && rule[2].split) return rule[2].split(',').includes(val);
|
|
6
|
+
if (rule[1] === 'in' && rule[2].includes) return rule[2].includes(val);
|
|
7
|
+
|
|
8
|
+
if (rule[1] === 'not in' && rule[2].split) return !rule[2].split(',').includes(val);
|
|
9
|
+
if (rule[1] === 'not in' && rule[2].includes) return !rule[2].includes(val);
|
|
10
|
+
|
|
11
|
+
if (rule[1] === '>') return val > rule[2];
|
|
12
|
+
if (rule[1] === '<') return val < rule[2];
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
export default function conditions(rules, data) {
|
|
16
|
+
if (!rules?.length) return true;
|
|
17
|
+
const result = Array.isArray(rules[0]) ? !rules.filter(el => !onCheck(el, data)).length : onCheck(rules, data);
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getTemplate } from "@opengis/fastify-table/utils.js";
|
|
2
|
+
|
|
3
|
+
export default async function getColumns({
|
|
4
|
+
columns = [], params = {}, opt = {}, loadTable = {}, form, table, dbColumns = [], mode = 'table',
|
|
5
|
+
}) {
|
|
6
|
+
const columnList = dbColumns.map((el) => el.name || el).join(',');
|
|
7
|
+
const cols = columns.filter((el) => columnList.includes(el?.name) && el?.name !== 'geom').map((el) => el?.name || el).join(',');
|
|
8
|
+
return { cols, columnList };
|
|
9
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import tableData from './controllers/tableData.js';
|
|
2
|
+
import tokenInfo from './controllers/tokenInfo.js';
|
|
3
|
+
import cardTabData from './controllers/cardTabData.js';
|
|
4
|
+
import cardData from './controllers/cardData.js';
|
|
5
|
+
import tableFilter from './controllers/tableFilter.js';
|
|
6
|
+
import tableInfo from './controllers/tableInfo.js';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
tableDataSchema, tableDataIdSchema, tableFilterSchema, cardTabDataSchema,
|
|
10
|
+
} from './schema.js';
|
|
11
|
+
|
|
12
|
+
const policy = ['user'];
|
|
13
|
+
|
|
14
|
+
export default async function route(app, config = {}) {
|
|
15
|
+
const { prefix = '/api' } = config;
|
|
16
|
+
app.get(`${prefix}/table-data/:table`, { config: { policy: ['user', 'no-sql'] }, schema: tableDataSchema }, tableData);
|
|
17
|
+
app.get(`${prefix}/token-info/:token`, { config: { policy: ['admin'] } }, tokenInfo);
|
|
18
|
+
app.get(`${prefix}/card-data/:token`, { config: { policy }, scheme: cardTabDataSchema }, cardTabData);
|
|
19
|
+
app.get(`${prefix}/table-data/:table/:id`, { config: { policy }, schema: tableDataIdSchema }, cardData);
|
|
20
|
+
app.get(`${prefix}/table-filter/:table`, { config: { policy }, schema: tableFilterSchema }, tableFilter);
|
|
21
|
+
app.get(`${prefix}/table-info/:table/:id?`, { config: { policy }, schema: tableDataSchema }, tableInfo);
|
|
22
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const tableDataSchema = {
|
|
2
|
+
querystring: {
|
|
3
|
+
limit: { type: 'string', pattern: '^(\\d+)$' },
|
|
4
|
+
page: { type: 'string', pattern: '^(\\d+)$' },
|
|
5
|
+
// filter: { type: 'string', pattern: '^([\\w\\d_-]+)=([А-Яа-яҐґЄєІіЇї\\d\\w\\s\\/\\[\\]\\(\\)\\{\\}\\|,.!?;:—_=-@%#$&^*+=`~]+)$' },
|
|
6
|
+
// search: { type: 'string', pattern: '^([А-Яа-яҐґЄєІіЇї\\d\\w\\s\\/\\[\\]\\(\\)\\{\\}\\|,.!?;:—_=-@%#$&^*+=`~]+)$' },
|
|
7
|
+
order: { type: 'string', pattern: '^([\\d\\w_.-]+)$' },
|
|
8
|
+
desc: { type: 'string', pattern: '^(\\d+)$' },
|
|
9
|
+
state: { type: 'string', pattern: '^([\\d\\w._-]+)$' },
|
|
10
|
+
custom: { type: 'string', pattern: '^([\\d\\w._-]+)$' },
|
|
11
|
+
bbox: { type: 'string', pattern: '^([\\d\\s,.-]+)$' },
|
|
12
|
+
polyline: { type: 'string', pattern: '^([\\d\\w|@{}~_`]+)$' },
|
|
13
|
+
// key: { type: 'string', pattern: '^([\\d\\w_]+)$' },
|
|
14
|
+
sql: { type: 'string', pattern: '^(\\d)$' },
|
|
15
|
+
},
|
|
16
|
+
params: {
|
|
17
|
+
id: { type: 'string', pattern: '^([\\d\\w_.-]+)$' },
|
|
18
|
+
table: { type: 'string', pattern: '^([\\d\\w_.-]+)$' },
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const tableDataIdSchema = {
|
|
23
|
+
params: {
|
|
24
|
+
id: { type: 'string', pattern: '^([\\d\\w_.-]+)$' },
|
|
25
|
+
name: { type: 'string', pattern: '^([\\d\\w._-]+)$' },
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const tableFilterSchema = {
|
|
30
|
+
params: {
|
|
31
|
+
name: { type: 'string', pattern: '^([\\d\\w._-]+)$' },
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const cardDataSchema = {
|
|
36
|
+
params: {
|
|
37
|
+
id: { type: 'string', pattern: '^([\\d\\w_.-]+)$' },
|
|
38
|
+
table: { type: 'string', pattern: '^([\\d\\w._-]+)$' },
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const cardTabDataSchema = {
|
|
43
|
+
querystring: {
|
|
44
|
+
sql: { type: 'string', pattern: '^(\\d)$' }
|
|
45
|
+
},
|
|
46
|
+
params: {
|
|
47
|
+
token: { type: 'string', pattern: '^([\\d\\w]+)$' },
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
export {
|
|
51
|
+
tableDataSchema, tableDataIdSchema, tableFilterSchema,
|
|
52
|
+
cardDataSchema, cardTabDataSchema
|
|
53
|
+
}
|
|
54
|
+
export default null;
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import readItemList from './controllers/readItems.js';
|
|
2
2
|
import setItem from './controllers/setItem.js';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const policy = ['site'];
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
export default async function plugin(app, config = {}) {
|
|
7
|
+
const { prefix = '/api' } = config;
|
|
8
|
+
app.route({
|
|
7
9
|
method: 'GET',
|
|
8
|
-
url:
|
|
9
|
-
config: { policy
|
|
10
|
+
url: `${prefix}/db-list`,
|
|
11
|
+
config: { policy },
|
|
10
12
|
handler: readItemList,
|
|
11
13
|
});
|
|
12
|
-
|
|
14
|
+
app.route({
|
|
13
15
|
method: 'GET',
|
|
14
|
-
url:
|
|
15
|
-
config: { policy
|
|
16
|
+
url: `${prefix}/db-list/:id`,
|
|
17
|
+
config: { policy },
|
|
16
18
|
handler: setItem,
|
|
17
19
|
});
|
|
18
20
|
}
|
|
@@ -1,93 +1,93 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { lstat, readdir, readFile } from 'node:fs/promises';
|
|
3
|
-
import { createReadStream, existsSync } from 'node:fs';
|
|
4
|
-
import readline from 'node:readline';
|
|
5
|
-
|
|
6
|
-
import checkUserAccess from './utils/checkUserAccess.js';
|
|
7
|
-
import getRootDir from './utils/getRootDir.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
*
|
|
11
|
-
* @method GET
|
|
12
|
-
* @summary API для перегляду логів
|
|
13
|
-
*
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
export default async function loggerFile({
|
|
17
|
-
params = {}, user = {}, query = {}, originalUrl,
|
|
18
|
-
}, reply) {
|
|
19
|
-
const limit = 200000;
|
|
20
|
-
// console.log(user);
|
|
21
|
-
const access = checkUserAccess({ user });
|
|
22
|
-
// log.info('Сервер запущен по адресу?'); // test!
|
|
23
|
-
|
|
24
|
-
if (access?.status !== 200) return access;
|
|
25
|
-
|
|
26
|
-
// absolute / relative path
|
|
27
|
-
const rootDir = getRootDir();
|
|
28
|
-
|
|
29
|
-
const filepath = path.join(rootDir, params['*'] || '');
|
|
30
|
-
|
|
31
|
-
if (!existsSync(filepath)) {
|
|
32
|
-
return { message: 'file not exists', status: 404 };
|
|
33
|
-
}
|
|
34
|
-
const stat = await lstat(filepath);
|
|
35
|
-
const isFile = stat.isFile();
|
|
36
|
-
|
|
37
|
-
if (query.download && isFile) {
|
|
38
|
-
const buffer = await readFile(filepath, { buffer: true });
|
|
39
|
-
return buffer;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (query.full && isFile) {
|
|
43
|
-
if (stat.size > 20 * 1000 * 1000) {
|
|
44
|
-
return { message: 'file size > 20MB' };
|
|
45
|
-
}
|
|
46
|
-
const buffer = await readFile(filepath, { buffer: true });
|
|
47
|
-
return buffer;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (isFile) {
|
|
51
|
-
const ext = path.extname(filepath);
|
|
52
|
-
|
|
53
|
-
const lines = await new Promise((resolve) => {
|
|
54
|
-
const rl = readline.createInterface({
|
|
55
|
-
input: createReadStream(filepath, { start: stat.size > limit ? stat.size - limit : 0 }),
|
|
56
|
-
});
|
|
57
|
-
const lines1 = [];
|
|
58
|
-
rl.on('close', () => resolve(lines1));
|
|
59
|
-
rl.on('line', (line) => lines1.push(line));
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
if (ext === '.html') {
|
|
63
|
-
const buffer = await readFile(filepath, { buffer: true });
|
|
64
|
-
reply.headers({ 'Content-type': 'text/html; charset=UTF-8' });
|
|
65
|
-
return buffer;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
reply.headers({ 'Content-type': 'text/plain; charset=UTF-8' });
|
|
69
|
-
return stat.size > limit && lines.length > 1
|
|
70
|
-
? lines.reverse().slice(0, -1).join('\n')
|
|
71
|
-
: lines.reverse().join('\n');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// dir
|
|
75
|
-
const files = await readdir(filepath);
|
|
76
|
-
|
|
77
|
-
if (query.dir) {
|
|
78
|
-
return files.filter((el) => !['backup', 'marker_icon', 'error', 'migration'].includes(el));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const lstatsArr = await Promise.all(files.map(async (file) => [file, await lstat(path.join(filepath, file))]));
|
|
82
|
-
const lstats = Object.fromEntries(lstatsArr);
|
|
83
|
-
|
|
84
|
-
const message = (params['*'] ? '<a href="/logger-file/">...</a><br>' : '')
|
|
85
|
-
+ files.map((file) => `<a href="${originalUrl}/${file}">${file}</a> (${lstats[file].size} bytes)`).join('</br>');
|
|
86
|
-
|
|
87
|
-
reply.headers({
|
|
88
|
-
'Content-Type': 'text/html; charset=UTF-8',
|
|
89
|
-
'Content-Security-Policy': "default-src 'none'",
|
|
90
|
-
'X-Content-Type-Options': 'nosniff',
|
|
91
|
-
});
|
|
92
|
-
return message;
|
|
93
|
-
}
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { lstat, readdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import { createReadStream, existsSync } from 'node:fs';
|
|
4
|
+
import readline from 'node:readline';
|
|
5
|
+
|
|
6
|
+
import checkUserAccess from './utils/checkUserAccess.js';
|
|
7
|
+
import getRootDir from './utils/getRootDir.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
* @method GET
|
|
12
|
+
* @summary API для перегляду логів
|
|
13
|
+
*
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export default async function loggerFile({
|
|
17
|
+
params = {}, user = {}, query = {}, originalUrl,
|
|
18
|
+
}, reply) {
|
|
19
|
+
const limit = 200000;
|
|
20
|
+
// console.log(user);
|
|
21
|
+
const access = checkUserAccess({ user });
|
|
22
|
+
// log.info('Сервер запущен по адресу?'); // test!
|
|
23
|
+
|
|
24
|
+
if (access?.status !== 200) return access;
|
|
25
|
+
|
|
26
|
+
// absolute / relative path
|
|
27
|
+
const rootDir = getRootDir();
|
|
28
|
+
|
|
29
|
+
const filepath = path.join(rootDir, params['*'] || '');
|
|
30
|
+
|
|
31
|
+
if (!existsSync(filepath)) {
|
|
32
|
+
return { message: 'file not exists', status: 404 };
|
|
33
|
+
}
|
|
34
|
+
const stat = await lstat(filepath);
|
|
35
|
+
const isFile = stat.isFile();
|
|
36
|
+
|
|
37
|
+
if (query.download && isFile) {
|
|
38
|
+
const buffer = await readFile(filepath, { buffer: true });
|
|
39
|
+
return buffer;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (query.full && isFile) {
|
|
43
|
+
if (stat.size > 20 * 1000 * 1000) {
|
|
44
|
+
return { message: 'file size > 20MB' };
|
|
45
|
+
}
|
|
46
|
+
const buffer = await readFile(filepath, { buffer: true });
|
|
47
|
+
return buffer;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (isFile) {
|
|
51
|
+
const ext = path.extname(filepath);
|
|
52
|
+
|
|
53
|
+
const lines = await new Promise((resolve) => {
|
|
54
|
+
const rl = readline.createInterface({
|
|
55
|
+
input: createReadStream(filepath, { start: stat.size > limit ? stat.size - limit : 0 }),
|
|
56
|
+
});
|
|
57
|
+
const lines1 = [];
|
|
58
|
+
rl.on('close', () => resolve(lines1));
|
|
59
|
+
rl.on('line', (line) => lines1.push(line));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (ext === '.html') {
|
|
63
|
+
const buffer = await readFile(filepath, { buffer: true });
|
|
64
|
+
reply.headers({ 'Content-type': 'text/html; charset=UTF-8' });
|
|
65
|
+
return buffer;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
reply.headers({ 'Content-type': 'text/plain; charset=UTF-8' });
|
|
69
|
+
return stat.size > limit && lines.length > 1
|
|
70
|
+
? lines.reverse().slice(0, -1).join('\n')
|
|
71
|
+
: lines.reverse().join('\n');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// dir
|
|
75
|
+
const files = await readdir(filepath);
|
|
76
|
+
|
|
77
|
+
if (query.dir) {
|
|
78
|
+
return files.filter((el) => !['backup', 'marker_icon', 'error', 'migration'].includes(el));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const lstatsArr = await Promise.all(files.map(async (file) => [file, await lstat(path.join(filepath, file))]));
|
|
82
|
+
const lstats = Object.fromEntries(lstatsArr);
|
|
83
|
+
|
|
84
|
+
const message = (params['*'] ? '<a href="/logger-file/">...</a><br>' : '')
|
|
85
|
+
+ files.map((file) => `<a href="${originalUrl}/${file}">${file}</a> (${lstats[file].size} bytes)`).join('</br>');
|
|
86
|
+
|
|
87
|
+
reply.headers({
|
|
88
|
+
'Content-Type': 'text/html; charset=UTF-8',
|
|
89
|
+
'Content-Security-Policy': "default-src 'none'",
|
|
90
|
+
'X-Content-Type-Options': 'nosniff',
|
|
91
|
+
});
|
|
92
|
+
return message;
|
|
93
|
+
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import config from '../../../../../config.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
* @summary check user access to logger interface - per admin user type or user group
|
|
6
|
-
* @returns {Object} message, status
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export default function checkUserAccess({ user = {} }) {
|
|
10
|
-
// console.log(user);
|
|
11
|
-
if (user.user_type !== 'admin' && !config?.local) {
|
|
12
|
-
return { message: 'access restricted', status: 403 };
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/* if (!['admin', 'superadmin']?.includes(user.user_type) && count === '0') {
|
|
16
|
-
return { message: 'access restricted', status: 403 };
|
|
17
|
-
} */
|
|
18
|
-
return { message: 'access granted', status: 200 };
|
|
19
|
-
}
|
|
1
|
+
import config from '../../../../../config.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @summary check user access to logger interface - per admin user type or user group
|
|
6
|
+
* @returns {Object} message, status
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export default function checkUserAccess({ user = {} }) {
|
|
10
|
+
// console.log(user);
|
|
11
|
+
if (user.user_type !== 'admin' && !config?.local) {
|
|
12
|
+
return { message: 'access restricted', status: 403 };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* if (!['admin', 'superadmin']?.includes(user.user_type) && count === '0') {
|
|
16
|
+
return { message: 'access restricted', status: 403 };
|
|
17
|
+
} */
|
|
18
|
+
return { message: 'access granted', status: 200 };
|
|
19
|
+
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
import config from '../../../../../config.js';
|
|
5
|
-
|
|
6
|
-
// import { existsSync } from 'fs';
|
|
7
|
-
let logDir = null;
|
|
8
|
-
export default function getRootDir() {
|
|
9
|
-
// absolute / relative path
|
|
10
|
-
if (logDir) return logDir;
|
|
11
|
-
const file = ['config.json', '/data/local/config.json'].find(el => (fs.existsSync(el) ? el : null));
|
|
12
|
-
const root = file === 'config.json' ? process.cwd() : '/data/local';
|
|
13
|
-
logDir = config.logDir || path.join(root, config.log?.dir || 'log');
|
|
14
|
-
console.log({ logDir });
|
|
15
|
-
return logDir;
|
|
16
|
-
|
|
17
|
-
// windows debug support
|
|
18
|
-
/* const customLogDir = process.cwd().includes(':') ? 'c:/data/local' : '/data/local';
|
|
19
|
-
// docker default path
|
|
20
|
-
if (existsSync(customLogDir)) {
|
|
21
|
-
return path.join(customLogDir, config.folder || '', 'log');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// non-docker default path
|
|
25
|
-
return path.join(config.root || '/data/local', config.folder || '', 'log'); */
|
|
26
|
-
}
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import config from '../../../../../config.js';
|
|
5
|
+
|
|
6
|
+
// import { existsSync } from 'fs';
|
|
7
|
+
let logDir = null;
|
|
8
|
+
export default function getRootDir() {
|
|
9
|
+
// absolute / relative path
|
|
10
|
+
if (logDir) return logDir;
|
|
11
|
+
const file = ['config.json', '/data/local/config.json'].find(el => (fs.existsSync(el) ? el : null));
|
|
12
|
+
const root = file === 'config.json' ? process.cwd() : '/data/local';
|
|
13
|
+
logDir = config.logDir || path.join(root, config.log?.dir || 'log');
|
|
14
|
+
console.log({ logDir });
|
|
15
|
+
return logDir;
|
|
16
|
+
|
|
17
|
+
// windows debug support
|
|
18
|
+
/* const customLogDir = process.cwd().includes(':') ? 'c:/data/local' : '/data/local';
|
|
19
|
+
// docker default path
|
|
20
|
+
if (existsSync(customLogDir)) {
|
|
21
|
+
return path.join(customLogDir, config.folder || '', 'log');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// non-docker default path
|
|
25
|
+
return path.join(config.root || '/data/local', config.folder || '', 'log'); */
|
|
26
|
+
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import loggerFile from './controllers/logger.file.js';
|
|
2
|
-
|
|
3
|
-
// import loggerTest from './controllers/logger.test.api.js';
|
|
4
|
-
|
|
5
|
-
const loggerSchema = {
|
|
6
|
-
querystring: {
|
|
7
|
-
download: { type: 'string', pattern: '^(\\d+)$' },
|
|
8
|
-
full: { type: 'string', pattern: '^(\\d+)$' },
|
|
9
|
-
dir: { type: 'string', pattern: '^(\\d+)$' },
|
|
10
|
-
},
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
async function plugin(
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export default plugin;
|
|
1
|
+
import loggerFile from './controllers/logger.file.js';
|
|
2
|
+
|
|
3
|
+
// import loggerTest from './controllers/logger.test.api.js';
|
|
4
|
+
|
|
5
|
+
const loggerSchema = {
|
|
6
|
+
querystring: {
|
|
7
|
+
download: { type: 'string', pattern: '^(\\d+)$' },
|
|
8
|
+
full: { type: 'string', pattern: '^(\\d+)$' },
|
|
9
|
+
dir: { type: 'string', pattern: '^(\\d+)$' },
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
async function plugin(app) {
|
|
14
|
+
app.get('/logger-file/*', { config: { policy: ['site'] }, schema: loggerSchema }, loggerFile);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default plugin;
|