@opengis/fastify-table 1.0.69 → 1.0.71

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/Changelog.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # fastify-table
2
2
 
3
+ ## 1.0.71 - 01.08.2024
4
+
5
+ - add compile settings to form
6
+
7
+ ## 1.0.70 - 29.07.2024
8
+
9
+ - add cron funcs and API
10
+
3
11
  ## 1.0.69 - 26.07.2024
4
12
 
5
13
  - add column classifier mode for data API
@@ -0,0 +1,22 @@
1
+ import cronList from './utils/cronList.js';
2
+
3
+ export default async function cronApi(req) {
4
+ const {
5
+ params = {}, user = {}, hostname,
6
+ } = req;
7
+
8
+ if ((!user.uid || !user.user_type?.includes('admin')) && !hostname?.includes('localhost')) {
9
+ return { message: 'access restricted', status: 403 };
10
+ }
11
+
12
+ if (params.name === 'list') {
13
+ return { data: Object.keys(cronList || {}) };
14
+ }
15
+
16
+ if (!cronList[params.name]) {
17
+ return { message: `cron not found: ${params.name}`, status: 404 };
18
+ }
19
+
20
+ const result = await cronList[params.name](req);
21
+ return result;
22
+ }
@@ -0,0 +1 @@
1
+ export default {};
@@ -0,0 +1,131 @@
1
+ import { createHash } from 'crypto';
2
+
3
+ import cronList from '../controllers/utils/cronList.js';
4
+ import getRedis from '../../redis/funcs/getRedis.js';
5
+ import getPG from '../../pg/funcs/getPG.js';
6
+
7
+ const md5 = (string) => createHash('md5').update(string).digest('hex');
8
+
9
+ async function verifyUnique(name, config, rclient) {
10
+ const cronId = config.port || 3000 + md5(name);
11
+ // one per node check
12
+ const key = `cron:unique:${cronId}`;
13
+ const unique = await rclient.setnx(key, 1);
14
+ const ttl = await rclient.ttl(key);
15
+ if (!unique && ttl !== -1) {
16
+ return false;
17
+ }
18
+ await rclient.expire(key, 20);
19
+ return true;
20
+ }
21
+
22
+ const intervalStringMs = {
23
+ everyMin: 1000 * 60,
24
+ tenMin: 1000 * 60 * 10,
25
+ everyHour: 1000 * 60 * 60,
26
+ isHalfday: 1000 * 60 * 60 * 12,
27
+ dailyHour: 1000 * 60 * 60 * 24,
28
+ };
29
+
30
+ const interval2ms = {
31
+ string: (interval) => {
32
+ const date = new Date();
33
+ const intervarSplit = interval.match(/^(\*{2}|(\*)?(\d{1,2})):(\*(\d)|(\d{2}))/);
34
+ if (!intervarSplit) {
35
+ throw new Error(`interval ${interval} not suported`);
36
+ }
37
+ const [, , isHalfday, dailyHour, , tenMin, HourlyMin] = intervarSplit;
38
+ const intervalMs = (isHalfday && intervalStringMs.isHalfday)
39
+ || (dailyHour && intervalStringMs.dailyHour)
40
+ || (tenMin && intervalStringMs.tenMin)
41
+ || intervalStringMs.everyHour;
42
+ const offsetDay = ((+dailyHour || 0) * 60 + (+tenMin || +HourlyMin)) * 60 * 1000;
43
+ const offsetCur = (date - date.getTimezoneOffset() * 1000 * 60) % intervalMs;
44
+ const waitMs = (offsetDay - offsetCur + intervalMs) % intervalMs;
45
+ return [waitMs, intervalMs];
46
+ },
47
+ number: (interval) => {
48
+ const date = new Date();
49
+ const intervalMs = interval * 1000;
50
+ const dateWithTZ = date - date.getTimezoneOffset() * 1000 * 60;
51
+ const offsetCur = dateWithTZ % intervalMs;
52
+ // start every cron within 1 hour
53
+ const sixtyMinutesStartMs = 3600000;
54
+ const waitMs = (intervalMs - offsetCur) % sixtyMinutesStartMs;
55
+ return [waitMs, intervalMs];
56
+ },
57
+ };
58
+
59
+ async function runCron({
60
+ pg, funcs, func, name, rclient, log,
61
+ }) {
62
+ const unique = await verifyUnique(name, funcs.config, rclient);
63
+
64
+ if (!unique) return;
65
+ const db = pg.options.database;
66
+ log.debug(`cron.${name}`, 1, db);
67
+ try {
68
+ const data = await func({ pg, funcs, log });
69
+ log.debug('cron', { db, name, result: data });
70
+ log.info('cron', { db, name, result: data });
71
+ }
72
+ catch (err) {
73
+ log.debug('cron', { db, name, error: err.toString() });
74
+ log.error('cron', { db, name, error: err.toString() });
75
+ }
76
+ }
77
+
78
+ /**
79
+ * interval:
80
+ * - 02:54 - every day
81
+ * - 2:03 - every day
82
+ * - *1:43 - 2 times a day
83
+ * - *12:03 - 2 times a day
84
+ * - **:54 - every hour
85
+ * - **:*3 - every 10 minutes
86
+ * - 60 - every minute
87
+ * - 10 * 60 - every 10 minutes
88
+ */
89
+
90
+ export default async function addCron(func, interval, fastify) {
91
+ if (!fastify) {
92
+ throw new Error('not enough params: fastify');
93
+ }
94
+
95
+ const { config = {}, log } = fastify;
96
+ const { time = {}, disabled = [] } = config.cron || {};
97
+ const pg = getPG();
98
+ const rclient = getRedis();
99
+
100
+ const name = func.name || func.toString().split('/').at(-1).split('\'')[0];
101
+
102
+ // if (!config.isServer) return;
103
+
104
+ if (disabled.includes(name)) {
105
+ log.debug('cron', { name, message: 'cron disabled' });
106
+ return;
107
+ }
108
+
109
+ cronList[name] = func;
110
+
111
+ const userInterval = time[name] || interval;
112
+ const [waitMs, intervalMs] = interval2ms[typeof interval](userInterval);
113
+
114
+ if (intervalMs < 1000) {
115
+ log.warn('cron', { name, error: `interval ${interval} to small` });
116
+ return;
117
+ }
118
+
119
+ // setTimeout to w8 for the time to start
120
+ setTimeout(() => {
121
+ runCron({
122
+ pg, funcs: fastify, func, name, rclient, log,
123
+ });
124
+ // interval
125
+ setInterval(() => {
126
+ runCron({
127
+ pg, funcs: fastify, func, name, rclient, log,
128
+ });
129
+ }, intervalMs);
130
+ }, waitMs);
131
+ }
package/cron/index.js ADDED
@@ -0,0 +1,10 @@
1
+ import cronApi from './controllers/cronApi.js';
2
+ import addCron from './funcs/addCron.js';
3
+
4
+ async function plugin(fastify, config = {}) {
5
+ const prefix = config.prefix || '/api';
6
+ fastify.decorate('addCron', addCron);
7
+ fastify.get(`${prefix}/cron/:name`, {}, cronApi);
8
+ }
9
+
10
+ export default plugin;
package/index.js CHANGED
@@ -1,87 +1,89 @@
1
- import path from 'path';
2
- import { existsSync, readdirSync, readFileSync } from 'fs';
3
-
4
- import fp from 'fastify-plugin';
5
- import config from './config.js';
6
- // import rclient from './redis/client.js';
7
-
8
- import redisPlugin from './redis/index.js';
9
- import pgPlugin from './pg/index.js';
10
- import tablePlugin from './table/index.js';
11
- import notificationPlugin from './notification/index.js';
12
- import widgetPlugin from './widget/index.js';
13
- import crudPlugin from './crud/index.js';
14
- import policyPlugin from './policy/index.js';
15
- import utilPlugin from './util/index.js';
16
-
17
- import pgClients from './pg/pgClients.js';
18
-
19
- import execMigrations from './migration/exec.migrations.js';
20
-
21
- async function plugin(fastify, opt) {
22
- // console.log(opt);
23
- config.pg = opt.pg;
24
- config.redis = opt.redis;
25
- config.root = opt.root;
26
- config.mapServerRoot = opt.mapServerRoot;
27
-
28
- // independent npm start / unit test
29
- if (!fastify.config) {
30
- fastify.decorate('config', config);
31
- }
32
-
33
- fastify.register(import('@opengis/fastify-hb'));
34
- fastify.decorate('getFolder', (req, type = 'server') => {
35
- if (!['server', 'local'].includes(type)) throw new Error('params type is invalid');
36
- const types = { local: req.root, server: req.mapServerRoot };
37
- const filepath = path.posix.join(types[type] || '/data/local', req.folder || config.folder || '');
38
- return filepath;
39
- });
40
-
41
- fastify.addHook('onListen', async () => {
42
- const { client } = pgClients;
43
- if (client?.pk?.['crm.cls']) {
44
- const clsDir = path.join(process.cwd(), 'server/templates/cls');
45
- const files = existsSync(clsDir) ? readdirSync(clsDir) : [];
46
- if (files.length) {
47
- const res = await Promise.all(files.map(async (filename) => {
48
- const filepath = path.join(clsDir, filename);
49
- const data = JSON.parse(readFileSync(filepath));
50
- return { name: path.parse(filename).name, data };
51
- }));
52
- await client.query('truncate table crm.cls');
53
- const { rows } = await client.query(`insert into crm.cls(name, type)
54
- select value->>'name', 'json' from json_array_elements($1) returning cls_id as id, name`, [JSON.stringify(res).replace(/'/g, "''")]);
55
- rows.forEach((row) => Object.assign(row, { data: res.find((cls) => row.name === cls.name)?.data }));
56
- const sql = `insert into crm.cls(code, name, parent)
57
- select json_array_elements(value->'data')->>'id', json_array_elements(value->'data')->>'text', value->>'name' from json_array_elements($1)`;
58
- await client.query(sql, [JSON.stringify(rows).replace(/'/g, "''")]);
59
- }
60
- }
61
- // call from another repo / project
62
- fastify.execMigrations = execMigrations;
63
- // execute core migrations
64
- await fastify.execMigrations();
65
- });
66
- if (!fastify.funcs) {
67
- fastify.addHook('onRequest', async (req) => {
68
- req.funcs = fastify;
69
- if (!req.user && req.session?.passport?.user) {
70
- const { user } = req.session?.passport || {};
71
- req.user = user;
72
- }
73
- });
74
- // fastify.decorateRequest('funcs', fastify);
75
- }
76
-
77
- policyPlugin(fastify);
78
- redisPlugin(fastify);
79
- await pgPlugin(fastify, opt);
80
- tablePlugin(fastify, opt);
81
- crudPlugin(fastify, opt);
82
- notificationPlugin(fastify, opt);
83
- widgetPlugin(fastify, opt);
84
- utilPlugin(fastify, opt);
85
- }
86
- export default fp(plugin);
87
- // export { rclient };
1
+ import path from 'path';
2
+ import { existsSync, readdirSync, readFileSync } from 'fs';
3
+
4
+ import fp from 'fastify-plugin';
5
+ import config from './config.js';
6
+ // import rclient from './redis/client.js';
7
+
8
+ import redisPlugin from './redis/index.js';
9
+ import pgPlugin from './pg/index.js';
10
+ import tablePlugin from './table/index.js';
11
+ import notificationPlugin from './notification/index.js';
12
+ import widgetPlugin from './widget/index.js';
13
+ import crudPlugin from './crud/index.js';
14
+ import policyPlugin from './policy/index.js';
15
+ import utilPlugin from './util/index.js';
16
+ import cronPlugin from './cron/index.js';
17
+
18
+ import pgClients from './pg/pgClients.js';
19
+
20
+ import execMigrations from './migration/exec.migrations.js';
21
+
22
+ async function plugin(fastify, opt) {
23
+ // console.log(opt);
24
+ config.pg = opt.pg;
25
+ config.redis = opt.redis;
26
+ config.root = opt.root;
27
+ config.mapServerRoot = opt.mapServerRoot;
28
+
29
+ // independent npm start / unit test
30
+ if (!fastify.config) {
31
+ fastify.decorate('config', config);
32
+ }
33
+
34
+ fastify.register(import('@opengis/fastify-hb'));
35
+ fastify.decorate('getFolder', (req, type = 'server') => {
36
+ if (!['server', 'local'].includes(type)) throw new Error('params type is invalid');
37
+ const types = { local: req.root, server: req.mapServerRoot };
38
+ const filepath = path.posix.join(types[type] || '/data/local', req.folder || config.folder || '');
39
+ return filepath;
40
+ });
41
+
42
+ fastify.addHook('onListen', async () => {
43
+ const { client } = pgClients;
44
+ if (client?.pk?.['crm.cls']) {
45
+ const clsDir = path.join(process.cwd(), 'server/templates/cls');
46
+ const files = existsSync(clsDir) ? readdirSync(clsDir) : [];
47
+ if (files.length) {
48
+ const res = await Promise.all(files.map(async (filename) => {
49
+ const filepath = path.join(clsDir, filename);
50
+ const data = JSON.parse(readFileSync(filepath));
51
+ return { name: path.parse(filename).name, data };
52
+ }));
53
+ await client.query('truncate table crm.cls');
54
+ const { rows } = await client.query(`insert into crm.cls(name, type)
55
+ select value->>'name', 'json' from json_array_elements($1) returning cls_id as id, name`, [JSON.stringify(res).replace(/'/g, "''")]);
56
+ rows.forEach((row) => Object.assign(row, { data: res.find((cls) => row.name === cls.name)?.data }));
57
+ const sql = `insert into crm.cls(code, name, parent)
58
+ select json_array_elements(value->'data')->>'id', json_array_elements(value->'data')->>'text', value->>'name' from json_array_elements($1)`;
59
+ await client.query(sql, [JSON.stringify(rows).replace(/'/g, "''")]);
60
+ }
61
+ }
62
+ // call from another repo / project
63
+ fastify.execMigrations = execMigrations;
64
+ // execute core migrations
65
+ await fastify.execMigrations();
66
+ });
67
+ if (!fastify.funcs) {
68
+ fastify.addHook('onRequest', async (req) => {
69
+ req.funcs = fastify;
70
+ if (!req.user && req.session?.passport?.user) {
71
+ const { user } = req.session?.passport || {};
72
+ req.user = user;
73
+ }
74
+ });
75
+ // fastify.decorateRequest('funcs', fastify);
76
+ }
77
+
78
+ policyPlugin(fastify);
79
+ redisPlugin(fastify);
80
+ await pgPlugin(fastify, opt);
81
+ tablePlugin(fastify, opt);
82
+ crudPlugin(fastify, opt);
83
+ notificationPlugin(fastify, opt);
84
+ widgetPlugin(fastify, opt);
85
+ utilPlugin(fastify, opt);
86
+ cronPlugin(fastify, opt);
87
+ }
88
+ export default fp(plugin);
89
+ // export { rclient };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/fastify-table",
3
- "version": "1.0.69",
3
+ "version": "1.0.71",
4
4
  "type": "module",
5
5
  "description": "core-plugins",
6
6
  "main": "index.js",
@@ -1,10 +1,28 @@
1
1
  import getTemplate from './utils/getTemplate.js';
2
2
 
3
+ const sql = `select property_key as key, property_json as json, property_int as int,
4
+ property_text as text from admin.properties where 1=1`;
5
+
6
+ async function getSettings({ pg }) {
7
+ const { rows = [] } = await pg.query(sql);
8
+ const data = rows.reduce((acc, curr) => Object.assign(acc, { [curr.key]: curr.json || curr.int || curr.text }), {});
9
+ return data;
10
+ }
11
+
3
12
  export default async function formFunction(req) {
4
13
  const time = Date.now();
5
- const { params } = req;
14
+ const { pg, params } = req;
6
15
  const form = await getTemplate('form', params.form);
7
16
  if (!form) { return { status: 404, message: 'not found' }; }
8
17
 
18
+ // replace settings
19
+ const arr = JSON.stringify(form).match(/{{settings.([^}]*)}}/g);
20
+ if (arr?.length) {
21
+ const string = JSON.stringify(form);
22
+ const settings = await getSettings({ pg });
23
+ const match = arr.reduce((acc, curr) => Object.assign(acc, { [curr]: settings[curr.replace(/^{{settings./g, '').replace(/}}$/, '')] }), {});
24
+ const res = Object.keys(match).reduce((s, m) => s.replace(m, match[m]), string);
25
+ return { time: Date.now() - time, form: JSON.parse(res) };
26
+ }
9
27
  return { time: Date.now() - time, form };
10
28
  }
@@ -15,7 +15,7 @@ export default async function suggest(req) {
15
15
  if (!selectName) return { headers, status: 400, message: 'name is required' };
16
16
 
17
17
  const meta = await getSelectMeta({ name: selectName });
18
- const pg = meta.db ? getPG(meta.db) : pg1;
18
+ const pg = meta?.db ? getPG(meta.db) : pg1;
19
19
  if (!meta) return { headers, status: 404, message: 'Not found query select ' };
20
20
 
21
21
  const { arr, searchQuery } = meta;