@opengis/fastify-table 1.3.17 → 1.3.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/package.json +1 -1
- package/server/plugins/cron/funcs/addCron.js +9 -87
- package/server/plugins/cron/funcs/interval2ms.js +40 -0
- package/server/plugins/cron/funcs/runCron.js +23 -0
- package/server/plugins/cron/funcs/verifyUnique.js +22 -0
- package/server/plugins/cron/index.js +70 -2
- package/server/routes/table/controllers/data.js +10 -4
package/package.json
CHANGED
|
@@ -1,89 +1,11 @@
|
|
|
1
|
-
import { createHash } from 'crypto';
|
|
2
|
-
|
|
3
1
|
import config from '../../../../config.js';
|
|
4
|
-
import getRedis from '../../redis/funcs/getRedis.js';
|
|
5
|
-
import getPG from '../../pg/funcs/getPG.js';
|
|
6
|
-
import logger from '../../logger/getLogger.js';
|
|
7
|
-
import cronList from '../cronList.js';
|
|
8
|
-
|
|
9
|
-
const md5 = (string) => createHash('md5').update(string).digest('hex');
|
|
10
|
-
|
|
11
|
-
const rclient = getRedis();
|
|
12
|
-
async function verifyUnique(name) {
|
|
13
|
-
const cronId = config.port || 3000 + md5(name);
|
|
14
|
-
// one per node check
|
|
15
|
-
const key = `cron:unique:${cronId}`;
|
|
16
|
-
const unique = await rclient.setnx(key, 1);
|
|
17
|
-
const ttl = await rclient.ttl(key);
|
|
18
|
-
if (!unique && ttl !== -1) {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
await rclient.expire(key, 20);
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const intervalStringMs = {
|
|
26
|
-
everyMin: 1000 * 60,
|
|
27
|
-
tenMin: 1000 * 60 * 10,
|
|
28
|
-
everyHour: 1000 * 60 * 60,
|
|
29
|
-
isHalfday: 1000 * 60 * 60 * 12,
|
|
30
|
-
dailyHour: 1000 * 60 * 60 * 24,
|
|
31
|
-
};
|
|
32
2
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const date = new Date();
|
|
36
|
-
const intervarSplit = interval.match(/^(\*{2}|(\*)?(\d{1,2})):(\*(\d)|(\d{2}))/);
|
|
37
|
-
if (!intervarSplit) {
|
|
38
|
-
throw new Error(`interval ${interval} not suported`);
|
|
39
|
-
}
|
|
40
|
-
const [, , isHalfday, dailyHour, , tenMin, HourlyMin] = intervarSplit;
|
|
41
|
-
const intervalMs = (isHalfday && intervalStringMs.isHalfday)
|
|
42
|
-
|| (dailyHour && intervalStringMs.dailyHour)
|
|
43
|
-
|| (tenMin && intervalStringMs.tenMin)
|
|
44
|
-
|| intervalStringMs.everyHour;
|
|
45
|
-
const offsetDay = ((+dailyHour || 0) * 60 + (+tenMin || +HourlyMin)) * 60 * 1000;
|
|
46
|
-
const offsetCur = (date - date.getTimezoneOffset() * 1000 * 60) % intervalMs;
|
|
47
|
-
const waitMs = (offsetDay - offsetCur + intervalMs) % intervalMs;
|
|
48
|
-
return [waitMs, intervalMs];
|
|
49
|
-
},
|
|
50
|
-
number: (interval) => {
|
|
51
|
-
const date = new Date();
|
|
52
|
-
const intervalMs = interval * 1000;
|
|
53
|
-
const dateWithTZ = date - date.getTimezoneOffset() * 1000 * 60;
|
|
54
|
-
const offsetCur = dateWithTZ % intervalMs;
|
|
55
|
-
// start every cron within 1 hour
|
|
56
|
-
const sixtyMinutesStartMs = 3600000;
|
|
57
|
-
const waitMs = (intervalMs - offsetCur) % sixtyMinutesStartMs;
|
|
58
|
-
return [waitMs, intervalMs];
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
async function runCron({
|
|
63
|
-
func, name,
|
|
64
|
-
}) {
|
|
65
|
-
const pg = getPG();
|
|
66
|
-
|
|
67
|
-
const unique = await verifyUnique(name);
|
|
68
|
-
|
|
69
|
-
if (!unique) return;
|
|
70
|
-
const db = pg.options.database;
|
|
3
|
+
import logger from '../../logger/getLogger.js';
|
|
4
|
+
import pgClients from '../../pg/pgClients.js';
|
|
71
5
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const subdir = !!data ? 'cron' : 'cron/null';
|
|
76
|
-
logger.file(subdir, {
|
|
77
|
-
db, name, result: data,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
catch (err) {
|
|
81
|
-
logger.file('cron', {
|
|
82
|
-
db, name, error: err.toString(),
|
|
83
|
-
});
|
|
84
|
-
logger.error(err);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
6
|
+
import cronList from '../cronList.js';
|
|
7
|
+
import runCron from './runCron.js';
|
|
8
|
+
import interval2ms from './interval2ms.js';
|
|
87
9
|
|
|
88
10
|
/**
|
|
89
11
|
* interval:
|
|
@@ -97,7 +19,7 @@ async function runCron({
|
|
|
97
19
|
* - 10 * 60 - every 10 minutes
|
|
98
20
|
*/
|
|
99
21
|
|
|
100
|
-
export default async function addCron(func, interval) {
|
|
22
|
+
export default async function addCron(func, interval, pg = pgClients.client) {
|
|
101
23
|
const { time = {}, disabled = [] } = config.cron || {};
|
|
102
24
|
|
|
103
25
|
const name = func.name || func.toString().split('/').at(-1).split('\'')[0];
|
|
@@ -115,16 +37,16 @@ export default async function addCron(func, interval) {
|
|
|
115
37
|
const [waitMs, intervalMs] = interval2ms[typeof interval](userInterval);
|
|
116
38
|
|
|
117
39
|
if (intervalMs < 1000) {
|
|
118
|
-
logger.file('cron', { name, error: `interval ${interval}
|
|
40
|
+
logger.file('cron', { name, error: `interval ${interval} too small` });
|
|
119
41
|
return;
|
|
120
42
|
}
|
|
121
43
|
|
|
122
44
|
// setTimeout to w8 for the time to start
|
|
123
45
|
setTimeout(() => {
|
|
124
|
-
runCron({ func, name });
|
|
46
|
+
runCron({ pg, func, name });
|
|
125
47
|
// interval
|
|
126
48
|
setInterval(() => {
|
|
127
|
-
runCron({ func, name });
|
|
49
|
+
runCron({ pg, func, name });
|
|
128
50
|
}, intervalMs);
|
|
129
51
|
}, waitMs);
|
|
130
52
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const intervalStringMs = {
|
|
2
|
+
everyMin: 1000 * 60,
|
|
3
|
+
tenMin: 1000 * 60 * 10,
|
|
4
|
+
everyHour: 1000 * 60 * 60,
|
|
5
|
+
isHalfday: 1000 * 60 * 60 * 12,
|
|
6
|
+
dailyHour: 1000 * 60 * 60 * 24,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const interval2ms = {
|
|
10
|
+
string: (interval) => {
|
|
11
|
+
const date = new Date();
|
|
12
|
+
const intervarSplit = interval.match(/^(\*{2}|(\*)?(\d{1,2})):(\*(\d)|(\d{2}))/);
|
|
13
|
+
|
|
14
|
+
if (!intervarSplit) {
|
|
15
|
+
throw new Error(`interval ${interval} not suported`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const [, , isHalfday, dailyHour, , tenMin, HourlyMin] = intervarSplit;
|
|
19
|
+
const intervalMs = (isHalfday && intervalStringMs.isHalfday)
|
|
20
|
+
|| (dailyHour && intervalStringMs.dailyHour)
|
|
21
|
+
|| (tenMin && intervalStringMs.tenMin)
|
|
22
|
+
|| intervalStringMs.everyHour;
|
|
23
|
+
const offsetDay = ((+dailyHour || 0) * 60 + (+tenMin || +HourlyMin)) * 60 * 1000;
|
|
24
|
+
const offsetCur = (date - date.getTimezoneOffset() * 1000 * 60) % intervalMs;
|
|
25
|
+
const waitMs = (offsetDay - offsetCur + intervalMs) % intervalMs;
|
|
26
|
+
return [waitMs, intervalMs];
|
|
27
|
+
},
|
|
28
|
+
number: (interval) => {
|
|
29
|
+
const date = new Date();
|
|
30
|
+
const intervalMs = interval * 1000;
|
|
31
|
+
const dateWithTZ = date - date.getTimezoneOffset() * 1000 * 60;
|
|
32
|
+
const offsetCur = dateWithTZ % intervalMs;
|
|
33
|
+
// start every cron within 1 hour
|
|
34
|
+
const sixtyMinutesStartMs = 3600000;
|
|
35
|
+
const waitMs = (intervalMs - offsetCur) % sixtyMinutesStartMs;
|
|
36
|
+
return [waitMs, intervalMs];
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default interval2ms;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import logger from '../../logger/getLogger.js';
|
|
2
|
+
import pgClients from '../../pg/pgClients.js';
|
|
3
|
+
|
|
4
|
+
import verifyUnique from './verifyUnique.js';
|
|
5
|
+
|
|
6
|
+
export default async function runCron({
|
|
7
|
+
pg = pgClients.client, func, name,
|
|
8
|
+
}) {
|
|
9
|
+
const unique = await verifyUnique(name);
|
|
10
|
+
if (!unique) return;
|
|
11
|
+
|
|
12
|
+
const db = pg.options.database;
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const data = await func({ pg });
|
|
16
|
+
const subdir = !!data ? 'cron' : 'cron/null';
|
|
17
|
+
logger.file(subdir, { db, name, result: data });
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
logger.file('cron', { db, name, error: err.toString() });
|
|
21
|
+
logger.error(err);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import config from '../../../../config.js';
|
|
4
|
+
|
|
5
|
+
import getRedis from '../../redis/funcs/getRedis.js';
|
|
6
|
+
|
|
7
|
+
const md5 = (string) => createHash('md5').update(string).digest('hex');
|
|
8
|
+
|
|
9
|
+
const rclient = getRedis();
|
|
10
|
+
|
|
11
|
+
export default async function verifyUnique(name) {
|
|
12
|
+
const cronId = config.port || 3000 + md5(name);
|
|
13
|
+
// one per node check
|
|
14
|
+
const key = `cron:unique:${cronId}`;
|
|
15
|
+
const unique = await rclient.setnx(key, 1);
|
|
16
|
+
const ttl = await rclient.ttl(key);
|
|
17
|
+
if (!unique && ttl !== -1) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
await rclient.expire(key, 20);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
@@ -1,6 +1,74 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import config from "../../../config.js";
|
|
4
|
+
|
|
5
|
+
import getPG from "../pg/funcs/getPG.js";
|
|
6
|
+
import pgClients from "../pg/pgClients.js";
|
|
7
|
+
import getRedis from '../redis/funcs/getRedis.js';
|
|
8
|
+
import logger from "../logger/getLogger.js";
|
|
9
|
+
import interval2ms from "./funcs/interval2ms.js";
|
|
10
|
+
|
|
11
|
+
const rclient = getRedis();
|
|
12
|
+
|
|
13
|
+
async function runCron({
|
|
14
|
+
pg = pgClients.client, query, name,
|
|
15
|
+
}) {
|
|
16
|
+
const db = pg.options.database;
|
|
17
|
+
|
|
18
|
+
// verifyUnique
|
|
19
|
+
const key = `cron:unique:${name}`;
|
|
20
|
+
const unique = await rclient.setnx(key, 1);
|
|
21
|
+
const ttl = await rclient.ttl(key);
|
|
22
|
+
|
|
23
|
+
if (!unique && ttl !== -1) {
|
|
24
|
+
// if (config.trace) console.log(name, db, query, 'skip unique');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
await rclient.expire(key, 20);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
if (!pg.pk) await pg.init();
|
|
32
|
+
|
|
33
|
+
if (config.trace) console.time(`${db}:${query}`);
|
|
34
|
+
const { command, rows = [], rowCount } = await pg.query(query);
|
|
35
|
+
if (config.trace) console.timeEnd(`${db}:${query}`);
|
|
36
|
+
|
|
37
|
+
logger.file('cron', { db, name, result: { command, rows, rowCount } });
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (config.trace) console.error(name, err.toString());
|
|
41
|
+
logger.file('cron/error', { db, name, error: err.toString() });
|
|
42
|
+
logger.error(err);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
2
45
|
|
|
3
46
|
async function plugin(fastify) {
|
|
4
|
-
|
|
47
|
+
if (config.cronList?.length) {
|
|
48
|
+
config.cronList?.filter(el => el.query && !el.disabled)?.forEach?.((el, idx) => {
|
|
49
|
+
const { interval, db, query } = el;
|
|
50
|
+
const name = createHash('md5').update(`${config.port || 3000}:${db}:${query}`).digest('hex');
|
|
51
|
+
const pg = getPG(db);
|
|
52
|
+
|
|
53
|
+
if (config.trace) console.log('cron-list: init', db, idx);
|
|
54
|
+
|
|
55
|
+
const [waitMs, intervalMs] = interval2ms[typeof interval](interval);
|
|
56
|
+
|
|
57
|
+
if (intervalMs < 1000) {
|
|
58
|
+
if (config.trace) console.error('cron-list: skip too small interval', db, idx);
|
|
59
|
+
logger.file('cron', { name, error: `interval ${interval} too small` });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// setTimeout to w8 for the time to start
|
|
64
|
+
setTimeout(() => {
|
|
65
|
+
runCron({ pg, query, name });
|
|
66
|
+
// interval
|
|
67
|
+
setInterval(() => {
|
|
68
|
+
runCron({ pg, query, name });
|
|
69
|
+
}, intervalMs);
|
|
70
|
+
}, waitMs);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
5
73
|
}
|
|
6
74
|
export default plugin;
|
|
@@ -25,6 +25,7 @@ export default async function dataAPI(req, reply, called) {
|
|
|
25
25
|
const hookData = await applyHook('preData', {
|
|
26
26
|
pg, table: params?.table, id: params?.id, user,
|
|
27
27
|
});
|
|
28
|
+
|
|
28
29
|
if (hookData?.message && hookData?.status) {
|
|
29
30
|
return { message: hookData?.message, status: hookData?.status };
|
|
30
31
|
}
|
|
@@ -49,7 +50,9 @@ export default async function dataAPI(req, reply, called) {
|
|
|
49
50
|
|
|
50
51
|
if (query.sql === '0') return loadTable;
|
|
51
52
|
|
|
52
|
-
if (!loadTable && !(tokenData?.table && pg.pk?.[tokenData?.table]) && !(called && pg.pk?.[params?.table])) {
|
|
53
|
+
if (!loadTable && !(tokenData?.table && pg.pk?.[tokenData?.table]) && !(called && pg.pk?.[params?.table])) {
|
|
54
|
+
return { message: 'template not found', status: 404 };
|
|
55
|
+
}
|
|
53
56
|
|
|
54
57
|
const id = tokenData?.id || hookData?.id || params?.id;
|
|
55
58
|
const { actions = [], query: accessQuery } = await getAccess({ table: tokenData?.table || hookData?.table || params.table, id, user }, pg) || {};
|
|
@@ -100,6 +103,7 @@ export default async function dataAPI(req, reply, called) {
|
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
const checkFilter = [query.filter, query.search, query.state, query.custom].filter((el) => el).length;
|
|
106
|
+
|
|
103
107
|
const fData = checkFilter ? await getFilterSQL({
|
|
104
108
|
pg,
|
|
105
109
|
table: loadTable ? params.table : table,
|
|
@@ -110,12 +114,14 @@ export default async function dataAPI(req, reply, called) {
|
|
|
110
114
|
uid,
|
|
111
115
|
json: 1,
|
|
112
116
|
}) : {};
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
|
|
118
|
+
timeArr.push(Date.now());
|
|
119
|
+
|
|
120
|
+
const keyQuery = query.key && (loadTable?.key || tokenData?.key) && !(tokenData?.id || hookData?.id || params.id) ? `${loadTable?.key || tokenData?.key}=$1` : null;
|
|
115
121
|
|
|
116
122
|
const limit = called ? (query.limit || 20) : Math.min(maxLimit, +(query.limit || 20));
|
|
117
123
|
|
|
118
|
-
const offset = query.page && query.page > 0 ? ` offset ${(query.page - 1) * limit}` : '';
|
|
124
|
+
const offset = query.page && query.page > 0 && !(tokenData?.id || hookData?.id || params.id) ? ` offset ${(query.page - 1) * limit}` : '';
|
|
119
125
|
// id, query, filter
|
|
120
126
|
const [orderColumn, orderDir] = (query.order || loadTable?.order || '').split(/[- ]/);
|
|
121
127
|
|