@opengis/fastify-table 1.1.30 → 1.1.32

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.
Files changed (48) hide show
  1. package/Changelog.md +1 -1
  2. package/README.md +26 -26
  3. package/config.js +10 -10
  4. package/cron/controllers/cronApi.js +22 -22
  5. package/cron/controllers/utils/cronList.js +1 -1
  6. package/cron/funcs/addCron.js +131 -131
  7. package/cron/index.js +10 -10
  8. package/crud/controllers/insert.js +1 -1
  9. package/crud/controllers/update.js +1 -1
  10. package/crud/controllers/utils/checkXSS.js +45 -45
  11. package/crud/controllers/utils/xssInjection.js +72 -72
  12. package/crud/funcs/getToken.js +27 -27
  13. package/crud/funcs/isFileExists.js +13 -13
  14. package/crud/funcs/setToken.js +53 -53
  15. package/index.js +18 -2
  16. package/logger/createFileStream.js +58 -0
  17. package/logger/getHooks.js +15 -0
  18. package/logger/getLogger.js +29 -0
  19. package/logger/labels.js +11 -0
  20. package/logger/logger.test.api.js +41 -0
  21. package/logger/serializers.js +24 -0
  22. package/notification/controllers/testEmail.js +49 -49
  23. package/notification/funcs/utils/sendEmail.js +39 -39
  24. package/package.json +3 -1
  25. package/pg/funcs/getPG.js +30 -30
  26. package/redis/funcs/getRedis.js +23 -23
  27. package/server/migrations/log.sql +80 -80
  28. package/server/migrations/properties.sql +90 -72
  29. package/server/migrations/roles.sql +2 -0
  30. package/server.js +18 -14
  31. package/table/controllers/card.js +44 -44
  32. package/table/controllers/form.js +28 -28
  33. package/test/api/crud.xss.test.js +72 -72
  34. package/test/config.example +18 -18
  35. package/test/funcs/pg.test.js +34 -34
  36. package/test/funcs/redis.test.js +19 -19
  37. package/test/templates/cls/test.json +9 -9
  38. package/test/templates/form/cp_building.form.json +32 -32
  39. package/test/templates/select/account_id.json +3 -3
  40. package/test/templates/select/storage.data.json +2 -2
  41. package/test/templates/table/gis.dataset.table.json +20 -20
  42. package/util/controllers/logger.file.js +91 -90
  43. package/util/controllers/next.id.js +4 -4
  44. package/util/controllers/properties.get.js +19 -19
  45. package/util/controllers/utils/checkUserAccess.js +17 -19
  46. package/util/controllers/utils/getRootDir.js +22 -21
  47. package/util/index.js +23 -23
  48. package/utils.js +2 -0
@@ -1,72 +1,72 @@
1
- const xssInjection = [
2
- 'onkeypress=',
3
- 'onkeyup=',
4
- 'ondblclick=',
5
- 'onerror=',
6
- 'onmouseover=',
7
- '<meta',
8
- '<script',
9
- 'vascript:',
10
- 'onkeydown=',
11
- 'onmousedown=',
12
- 'onmouseenter=',
13
- 'onmouseleave=',
14
- 'onmousemove=',
15
- 'onmouseout=',
16
- 'onmouseup=',
17
- 'onmousewheel=',
18
- 'onpaste=',
19
- 'onscroll=',
20
- 'onwheel=',
21
- 'javascript:',
22
- '\\x',
23
- 'eval(',
24
- 'onmouseover=',
25
- 'action=',
26
- 'xlink:',
27
- 'allowscriptaccess',
28
- 'href=',
29
- 'behavior:',
30
- 'onreadystatechange=',
31
- 'onstart=',
32
- 'offline=',
33
- 'onabort=',
34
- 'onafterprint=',
35
- 'onbeforeonload=',
36
- 'onbeforeprint=',
37
- 'onblur=',
38
- 'oncanplay=',
39
- 'oncanplaythrough=',
40
- 'onchange=',
41
- 'onclick=',
42
- 'oncontextmenu=',
43
- 'ondblclick=',
44
- 'ondrag=',
45
- 'ondragend=',
46
- 'ondragenter=',
47
- 'ondragleave=',
48
- 'ondragover=',
49
- 'ondragstart=',
50
- 'ondrop=',
51
- 'ondurationchange=',
52
- 'onemptied=',
53
- 'onended=',
54
- 'onerror=',
55
- 'onfocus=',
56
- 'onformchange=',
57
- 'onforminput=',
58
- 'onhaschange=',
59
- 'oninput=',
60
- 'oninvalid=',
61
- 'onkeydown=',
62
- 'onkeypress=',
63
- 'onkeyup=',
64
- 'onload=',
65
- 'onloadeddata=',
66
- 'onloadedmetadata=',
67
- 'onloadstart=',
68
- 'alert(',
69
- 'script:',
70
- ];
71
-
72
- export default xssInjection;
1
+ const xssInjection = [
2
+ 'onkeypress=',
3
+ 'onkeyup=',
4
+ 'ondblclick=',
5
+ 'onerror=',
6
+ 'onmouseover=',
7
+ '<meta',
8
+ '<script',
9
+ 'vascript:',
10
+ 'onkeydown=',
11
+ 'onmousedown=',
12
+ 'onmouseenter=',
13
+ 'onmouseleave=',
14
+ 'onmousemove=',
15
+ 'onmouseout=',
16
+ 'onmouseup=',
17
+ 'onmousewheel=',
18
+ 'onpaste=',
19
+ 'onscroll=',
20
+ 'onwheel=',
21
+ 'javascript:',
22
+ '\\x',
23
+ 'eval(',
24
+ 'onmouseover=',
25
+ 'action=',
26
+ 'xlink:',
27
+ 'allowscriptaccess',
28
+ 'href=',
29
+ 'behavior:',
30
+ 'onreadystatechange=',
31
+ 'onstart=',
32
+ 'offline=',
33
+ 'onabort=',
34
+ 'onafterprint=',
35
+ 'onbeforeonload=',
36
+ 'onbeforeprint=',
37
+ 'onblur=',
38
+ 'oncanplay=',
39
+ 'oncanplaythrough=',
40
+ 'onchange=',
41
+ 'onclick=',
42
+ 'oncontextmenu=',
43
+ 'ondblclick=',
44
+ 'ondrag=',
45
+ 'ondragend=',
46
+ 'ondragenter=',
47
+ 'ondragleave=',
48
+ 'ondragover=',
49
+ 'ondragstart=',
50
+ 'ondrop=',
51
+ 'ondurationchange=',
52
+ 'onemptied=',
53
+ 'onended=',
54
+ 'onerror=',
55
+ 'onfocus=',
56
+ 'onformchange=',
57
+ 'onforminput=',
58
+ 'onhaschange=',
59
+ 'oninput=',
60
+ 'oninvalid=',
61
+ 'onkeydown=',
62
+ 'onkeypress=',
63
+ 'onkeyup=',
64
+ 'onload=',
65
+ 'onloadeddata=',
66
+ 'onloadedmetadata=',
67
+ 'onloadstart=',
68
+ 'alert(',
69
+ 'script:',
70
+ ];
71
+
72
+ export default xssInjection;
@@ -1,27 +1,27 @@
1
- import getRedis from '../../redis/funcs/getRedis.js';
2
- import config from '../../config.js';
3
-
4
- function sprintf(str, ...args) {
5
- return str.replace(/%s/g, () => args.shift());
6
- }
7
-
8
- const keys = {
9
- r: '%s:token:view:%s',
10
- a: '%s:token:add:%s',
11
- w: '%s:token:edit:%s',
12
- e: '%s:token:exec:%s',
13
- };
14
-
15
- async function getToken({
16
- uid, token, mode = 'r', json,
17
- }) {
18
- if (mode === 'r') return token;
19
-
20
- const rclient = getRedis({ db: 0 });
21
-
22
- const key = sprintf(keys[mode], config?.pg?.database, uid?.toString());
23
- const id = await rclient.hget(key, token);
24
- return json && id?.[0] === '{' ? JSON.parse(id) : id;
25
- }
26
-
27
- export default getToken;
1
+ import getRedis from '../../redis/funcs/getRedis.js';
2
+ import config from '../../config.js';
3
+
4
+ function sprintf(str, ...args) {
5
+ return str.replace(/%s/g, () => args.shift());
6
+ }
7
+
8
+ const keys = {
9
+ r: '%s:token:view:%s',
10
+ a: '%s:token:add:%s',
11
+ w: '%s:token:edit:%s',
12
+ e: '%s:token:exec:%s',
13
+ };
14
+
15
+ async function getToken({
16
+ uid, token, mode = 'r', json,
17
+ }) {
18
+ if (mode === 'r') return token;
19
+
20
+ const rclient = getRedis({ db: 0 });
21
+
22
+ const key = sprintf(keys[mode], config?.pg?.database, uid?.toString());
23
+ const id = await rclient.hget(key, token);
24
+ return json && id?.[0] === '{' ? JSON.parse(id) : id;
25
+ }
26
+
27
+ export default getToken;
@@ -1,13 +1,13 @@
1
- import { access } from 'fs/promises';
2
-
3
- const isFileExists = async (filepath) => {
4
- try {
5
- await access(filepath);
6
- return true;
7
- }
8
- catch (err) {
9
- return false;
10
- }
11
- };
12
-
13
- export default isFileExists;
1
+ import { access } from 'fs/promises';
2
+
3
+ const isFileExists = async (filepath) => {
4
+ try {
5
+ await access(filepath);
6
+ return true;
7
+ }
8
+ catch (err) {
9
+ return false;
10
+ }
11
+ };
12
+
13
+ export default isFileExists;
@@ -1,53 +1,53 @@
1
- import { createHash, randomUUID } from 'crypto';
2
-
3
- import config from '../../config.js';
4
- import getRedis from '../../redis/funcs/getRedis.js';
5
-
6
- const generateCodes = (ids, userToken) => {
7
- const token = userToken || randomUUID();
8
- const notNullIds = ids.filter((el) => el);
9
- const obj = {};
10
- const codes = notNullIds.reduce((acc, id) => {
11
- const newToken = createHash('sha1').update(token + id).digest('base64url').replace(/-/g, '');
12
- acc[newToken] = id; obj[id] = newToken;
13
- return acc;
14
- }, {});
15
- return { codes, obj };
16
- };
17
-
18
- function setToken({
19
- ids: idsOrigin, mode = 'r', uid, referer, array,
20
- }) {
21
- const rclient2 = getRedis({ db: 0 });
22
- // const rclient5 = getRedis({ db: 0, funcs });
23
-
24
- if (!uid) return { user: 'empty' };
25
- if (!Object.keys(idsOrigin).length) return { ids: 'empty' };
26
-
27
- const ids = idsOrigin.map((el) => (typeof el === 'object' ? JSON.stringify(el) : el));
28
- // update/delete
29
-
30
- if (mode === 'r') return null;
31
-
32
- // TODO generate salt
33
- const { codes, obj } = generateCodes(ids, uid);
34
-
35
- if (!Object.keys(codes).length) return { ids: 'empty' };
36
-
37
- rclient2.hmset(`${config.pg.database}:token:${{
38
- e: 'exec', r: 'view', w: 'edit', a: 'add',
39
- }[mode]}:${uid}`, codes);
40
-
41
- // log token for debug. add extra data - uid, mode, date
42
- /* const dt = new Date().toISOString();
43
- const codesLog = Object.keys(codes).reduce((acc, key) => {
44
- acc[key] = `{"referer": "${referer}" ,"uid":"${uid}","mode":"${mode}","date":"${dt}",${codes[key].substr(1)}`;
45
- return acc;
46
- }, {});
47
- rclient5.hmset(`${config.pg.database}:token:edit`, codesLog); // 'EX', 64800 */
48
-
49
- // TODO дополнительно писать в hset token -> uid
50
- return array ? Object.values(obj) : obj;
51
- }
52
-
53
- export default setToken;
1
+ import { createHash, randomUUID } from 'crypto';
2
+
3
+ import config from '../../config.js';
4
+ import getRedis from '../../redis/funcs/getRedis.js';
5
+
6
+ const generateCodes = (ids, userToken) => {
7
+ const token = userToken || randomUUID();
8
+ const notNullIds = ids.filter((el) => el);
9
+ const obj = {};
10
+ const codes = notNullIds.reduce((acc, id) => {
11
+ const newToken = createHash('sha1').update(token + id).digest('base64url').replace(/-/g, '');
12
+ acc[newToken] = id; obj[id] = newToken;
13
+ return acc;
14
+ }, {});
15
+ return { codes, obj };
16
+ };
17
+
18
+ function setToken({
19
+ ids: idsOrigin, mode = 'r', uid, referer, array,
20
+ }) {
21
+ const rclient2 = getRedis({ db: 0 });
22
+ // const rclient5 = getRedis({ db: 0, funcs });
23
+
24
+ if (!uid) return { user: 'empty' };
25
+ if (!Object.keys(idsOrigin).length) return { ids: 'empty' };
26
+
27
+ const ids = idsOrigin.map((el) => (typeof el === 'object' ? JSON.stringify(el) : el));
28
+ // update/delete
29
+
30
+ if (mode === 'r') return null;
31
+
32
+ // TODO generate salt
33
+ const { codes, obj } = generateCodes(ids, uid);
34
+
35
+ if (!Object.keys(codes).length) return { ids: 'empty' };
36
+
37
+ rclient2.hmset(`${config.pg.database}:token:${{
38
+ e: 'exec', r: 'view', w: 'edit', a: 'add',
39
+ }[mode]}:${uid}`, codes);
40
+
41
+ // log token for debug. add extra data - uid, mode, date
42
+ /* const dt = new Date().toISOString();
43
+ const codesLog = Object.keys(codes).reduce((acc, key) => {
44
+ acc[key] = `{"referer": "${referer}" ,"uid":"${uid}","mode":"${mode}","date":"${dt}",${codes[key].substr(1)}`;
45
+ return acc;
46
+ }, {});
47
+ rclient5.hmset(`${config.pg.database}:token:edit`, codesLog); // 'EX', 64800 */
48
+
49
+ // TODO дополнительно писать в hset token -> uid
50
+ return array ? Object.values(obj) : obj;
51
+ }
52
+
53
+ export default setToken;
package/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import path from 'node:path';
2
- import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ // import { existsSync, readdirSync, readFileSync } from 'node:fs';
3
3
  import { fileURLToPath } from 'node:url';
4
4
 
5
5
  import fp from 'fastify-plugin';
6
+
6
7
  import config from './config.js';
7
8
  // import rclient from './redis/client.js';
8
9
 
@@ -17,7 +18,7 @@ import utilPlugin from './util/index.js';
17
18
  import cronPlugin from './cron/index.js';
18
19
  import userPlugin from './user/index.js';
19
20
 
20
- import pgClients from './pg/pgClients.js';
21
+ // import pgClients from './pg/pgClients.js';
21
22
 
22
23
  import { addTemplateDir, execMigrations } from './utils.js';
23
24
 
@@ -49,6 +50,21 @@ async function plugin(fastify, opt) {
49
50
  return filepath;
50
51
  });
51
52
 
53
+ fastify.setErrorHandler((error, request, reply) => {
54
+ // console.log(error)
55
+ debugger;
56
+ if (error.validation) {
57
+ // request.log.warn(request, { code: error?.code, status: 422, error: error.toString() });
58
+ const { params, instancePath } = error.validation?.[0] || {};
59
+ return reply.status(422).send(`invalid params: ${instancePath?.substring(1)} (${params?.pattern})`);
60
+ }
61
+ if (error.stack?.startsWith('Error: unhandled exception')) {
62
+ return reply.status(500).send('ServerError');
63
+ }
64
+
65
+ return reply.status(error.statusCode || 500).send(error);
66
+ });
67
+
52
68
  // core migrations
53
69
  await execMigrations();
54
70
 
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+ const streams = {}
3
+
4
+ import { createStream } from 'rotating-file-stream';
5
+ import labels from './labels.js';
6
+
7
+ import config from '../config.js';
8
+
9
+
10
+ const generator = (opt) => () => {
11
+
12
+ const date = new Date();
13
+ const tzOffset = date.getTimezoneOffset();
14
+ const currentTimeWithTimezome = date - tzOffset * 60 * 1000;
15
+
16
+ if (opt.interval === '1h') {
17
+ return `${new Date(currentTimeWithTimezome).toISOString().substring(0, 13).replace(/:/g, '_')}.log`;
18
+ }
19
+ if (opt.interval === '1m') {
20
+ return `${new Date(currentTimeWithTimezome).toISOString().substring(0, 16).replace(/:/g, '_')}.log`;
21
+ }
22
+ if (!opt.interval || opt.interval === '1d') {
23
+ return `${new Date(currentTimeWithTimezome).toISOString().split('T')[0].replace(/:/g, '_')}.log`;
24
+ }
25
+ };
26
+
27
+ const { dir = 'log', interval = '1d', compress = 'gzip', maxFiles = 90, local: teeToStdout } = config?.log || {};
28
+ function createFileStream({ level }) {
29
+ console.log(dir, level, generator({ interval })(), interval, compress);
30
+ const params = {
31
+ maxFiles, // logs to save limit
32
+ history: 'history', // history file name
33
+ interval, // rotate daily
34
+ compress, // compress rotated files
35
+ teeToStdout, // debug / logs to stdout
36
+ path: `${dir}/${level}`, // absolute path (root directory)
37
+ intervalBoundary: true, // true - log name with lower boundary of rotation interval
38
+ initialRotation: true, // true - log rotation check on init
39
+ // intervalUTC: true, // local tz -> utc
40
+ };
41
+ return createStream(generator({ interval }), params);
42
+ }
43
+
44
+
45
+ import build from 'pino-abstract-transport'
46
+
47
+ export default function () {
48
+ return build(function (source) {
49
+ source.on('data', function (obj) {
50
+ // console.log(obj)
51
+ const lvl = obj.msg?.file || obj.file || labels[obj.level];
52
+ streams[lvl] = streams[lvl] || createFileStream({ level: lvl })
53
+ streams[lvl].write(JSON.stringify(obj) + '\n');
54
+ })
55
+ }, {
56
+ parseLine: (line) => JSON.parse(line)
57
+ })
58
+ }
@@ -0,0 +1,15 @@
1
+ function getHooks() {
2
+ return {
3
+ logMethod(inputArgs, method) {
4
+
5
+ // string name param to object
6
+
7
+ if (inputArgs?.length === 2 && inputArgs[1].originalUrl) {
8
+ return method.apply(this, [inputArgs[1], inputArgs[0]]);
9
+ }
10
+ return method.apply(this, inputArgs);
11
+ }
12
+ };
13
+ }
14
+
15
+ export default getHooks;
@@ -0,0 +1,29 @@
1
+ import pino from 'pino';
2
+ // import path from 'node:path';
3
+
4
+ import config from '../config.js';
5
+
6
+ if (!config.log) config.log = {};
7
+
8
+ // utils
9
+ import getHooks from './getHooks.js';
10
+ import serializers from './serializers.js';
11
+
12
+ const level = config.log?.level || process.env.PINO_LOG_LEVEL || 'info';
13
+
14
+ const options = {
15
+ level, // minimal log level to write
16
+ timestamp: pino.stdTimeFunctions.isoTime, // timestamp as isostring
17
+ hooks: getHooks(),
18
+ serializers, // custom log params
19
+ transport: {
20
+ targets: [{
21
+ target: './createFileStream.js', // path.resolve('utils/createFileStream.js')
22
+ }],
23
+ },
24
+ };
25
+ const logger = pino(options);
26
+ logger.file = function userFile(file, msg, req) {
27
+ logger.info({ file, ...(typeof msg === 'string' ? { msg } : msg) }, req);
28
+ };
29
+ export default logger;
@@ -0,0 +1,11 @@
1
+ const labels = {
2
+ 10: 'trace',
3
+ 20: 'debug',
4
+ 30: 'info',
5
+ 40: 'warn',
6
+ 50: "error",
7
+ 60: "fatal",
8
+ 'infinity': "silent",
9
+ };
10
+
11
+ export default labels;
@@ -0,0 +1,41 @@
1
+ import { rename } from 'fs/promises';
2
+
3
+ export default async function testLog(request) {
4
+ // error handler
5
+ if (request.query?.rejection) {
6
+ rename('/data/local/test', '/data/local/test.txt');
7
+ }
8
+ if (request.query?.awaitRejection) {
9
+ await rename('/data/local/test', '/data/local/test.txt');
10
+ }
11
+
12
+ // default pino log
13
+ // request.log.info({ name: 'custom', test: 'param name and object log - info', status: 200 });
14
+ /* request.log.info('string info log');
15
+ //request.log.warn({ name: 'custom', test: 'param name and object log - warn', status: 400 });
16
+ request.log.warn('string warn log');
17
+ //request.log.debug({ name: 'custom', test: 'param name and object log - debug', status: 200 });
18
+ request.log.debug('string debug log');
19
+ //request.log.error({ name: 'custom', test: 'param name and object log - error', status: 200 });
20
+ //request.log.error('string error log');
21
+ request.log.fatal({ name: 'custom', test: 'param name and object log - fatal', status: 200 });
22
+ //request.log.fatal('string fatal log'); */
23
+ // custom params
24
+
25
+ request.log.info(request, { file: 'test', msg: 'string debug log' });
26
+ request.log.info({ file: 'test', msg: 'string debug log1' }, request);
27
+
28
+ request.log.file('test1', 'my message', request);
29
+ /* request.log.error('policy and string log1');
30
+ request.log.error({ test: 'policy and string log1' });
31
+ request.log.error(request, { test: 'string name and object log', status: 502 });
32
+ request.log.error(request, 'policy and string log1'); */
33
+
34
+ // request.log.error(request, { test: 'policy and object error log', status: 500 });
35
+ // request.log.error(request, 'policy and string log2');
36
+
37
+ /// throw new Error('222');
38
+ d / 0;
39
+ // throw new Error('unhandled exception');
40
+ return { root: true };
41
+ }
@@ -0,0 +1,24 @@
1
+ const serializers = {
2
+ req(request) {
3
+
4
+ const { method, url, referer, params, query, body, session } = request;
5
+ const { uid = undefined, user_name = undefined } = session?.passport?.user || {};
6
+ return {
7
+ method,
8
+ url,
9
+ referer,
10
+ ip: request.headers?.['x-real-ip']
11
+ || request.headers?.['x-forwarded-for']
12
+ || request.ip
13
+ || request.connection?.remoteAddress,
14
+ uid,
15
+ dbName: request.pg?.options?.database,
16
+ user_name,
17
+ params,
18
+ query,
19
+ body,
20
+ };
21
+ }
22
+ };
23
+
24
+ export default serializers;
@@ -1,49 +1,49 @@
1
- import path from 'path';
2
- import { existsSync } from 'fs';
3
- import { fileURLToPath } from 'url';
4
-
5
- const fileName = fileURLToPath(import.meta.url);
6
- const dirName = path.dirname(fileName);
7
-
8
- import notification from '../funcs/sendNotification.js';
9
-
10
- export default async function testNotification({
11
- pg, funcs = {}, log, query = {}, session = {},
12
- }) {
13
- const { local } = funcs.config || {};
14
- if (!session?.passport?.user?.user_type?.includes('admin') && !local) {
15
- return { message: 'Forbidden', status: 403 };
16
- }
17
-
18
- const date = new Date().toISOString().split('T')[0];
19
- if (!query.to) {
20
- return { message: 'param to is required', status: 400 };
21
- }
22
-
23
- try {
24
- const {
25
- to, template, table, id, nocache,
26
- } = query;
27
- const file = [path.join(dirName, '../../', 'changelog.md'), path.join(dirName, 'utils', 'pin-m-ty-media-record-outline+303070.png')].filter((el) => existsSync(el));
28
- const data = await notification({
29
- pg,
30
- funcs,
31
- log,
32
- to,
33
- template,
34
- title: `Test Softpro ${date}`,
35
- table,
36
- nocache,
37
- file,
38
- id,
39
- message: `Test mail Softpro ${date} Lorem Ipsum Lorem Ipsum`,
40
- });
41
-
42
- return {
43
- message: data || 'ok',
44
- };
45
- }
46
- catch (err) {
47
- return { error: err.toString(), status: 500 };
48
- }
49
- }
1
+ import path from 'path';
2
+ import { existsSync } from 'fs';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const fileName = fileURLToPath(import.meta.url);
6
+ const dirName = path.dirname(fileName);
7
+
8
+ import notification from '../funcs/sendNotification.js';
9
+
10
+ export default async function testNotification({
11
+ pg, funcs = {}, log, query = {}, session = {},
12
+ }) {
13
+ const { local } = funcs.config || {};
14
+ if (!session?.passport?.user?.user_type?.includes('admin') && !local) {
15
+ return { message: 'Forbidden', status: 403 };
16
+ }
17
+
18
+ const date = new Date().toISOString().split('T')[0];
19
+ if (!query.to) {
20
+ return { message: 'param to is required', status: 400 };
21
+ }
22
+
23
+ try {
24
+ const {
25
+ to, template, table, id, nocache,
26
+ } = query;
27
+ const file = [path.join(dirName, '../../', 'changelog.md'), path.join(dirName, 'utils', 'pin-m-ty-media-record-outline+303070.png')].filter((el) => existsSync(el));
28
+ const data = await notification({
29
+ pg,
30
+ funcs,
31
+ log,
32
+ to,
33
+ template,
34
+ title: `Test Softpro ${date}`,
35
+ table,
36
+ nocache,
37
+ file,
38
+ id,
39
+ message: `Test mail Softpro ${date} Lorem Ipsum Lorem Ipsum`,
40
+ });
41
+
42
+ return {
43
+ message: data || 'ok',
44
+ };
45
+ }
46
+ catch (err) {
47
+ return { error: err.toString(), status: 500 };
48
+ }
49
+ }