@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 +8 -0
- package/cron/controllers/cronApi.js +22 -0
- package/cron/controllers/utils/cronList.js +1 -0
- package/cron/funcs/addCron.js +131 -0
- package/cron/index.js +10 -0
- package/index.js +89 -87
- package/package.json +1 -1
- package/table/controllers/form.js +19 -1
- package/table/controllers/suggest.js +1 -1
package/Changelog.md
CHANGED
|
@@ -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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
config.
|
|
25
|
-
config.
|
|
26
|
-
config.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
fastify.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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,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
|
|
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;
|