@opengis/pay 1.2.2 → 1.3.0
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/config.js +7 -0
- package/module/pay/pt/marketplace-service-payment-client-template.html +7 -20
- package/package.json +15 -31
- package/plugin.js +14 -7
- package/server/plugins/crud/dataDelete.js +82 -0
- package/server/plugins/crud/dataInsert.js +76 -0
- package/server/plugins/crud/dataUpdate.js +112 -0
- package/server/plugins/{cron.js → hook.js} +24 -22
- package/server/plugins/pg/funcs/autoIndex.js +102 -0
- package/server/plugins/pg/funcs/getDBParams.js +15 -0
- package/server/plugins/pg/funcs/getMeta.js +48 -0
- package/server/plugins/pg/funcs/getPG.js +39 -0
- package/server/plugins/pg/funcs/getPGAsync.js +40 -0
- package/server/plugins/pg/funcs/init.js +113 -0
- package/server/plugins/pg/index.js +24 -0
- package/server/plugins/pg/pgClients.js +22 -0
- package/server/plugins/redis/client.js +8 -0
- package/server/plugins/redis/funcs/getRedis.js +24 -0
- package/server/plugins/redis/funcs/redisClients.js +3 -0
- package/server/plugins/redis/index.js +11 -0
- package/server/routes/liqpay/controllers/liqpay.redirect.js +7 -11
- package/server/routes/liqpay/controllers/liqpay.status.js +26 -15
- package/server/routes/liqpay/utils/check.status.js +1 -1
- package/server/routes/monopay/controllers/monopay.redirect.js +141 -0
- package/server/routes/monopay/controllers/monopay.status.js +70 -0
- package/server/routes/monopay/index.mjs +32 -0
- package/server/routes/portmone/controllers/payment.info.js +9 -9
- package/server/routes/portmone/controllers/portmone.redirect.js +23 -17
- package/server/routes/portmone/controllers/portmone.status.js +49 -38
- package/server/routes/portmone/utils/portmone.check.status.js +34 -33
- package/server/utils/cron/addCron.js +48 -0
- package/server/utils/cron/cronList.js +1 -0
- package/server/utils/cron/interval2ms.js +40 -0
- package/server/utils/cron/runCron.js +24 -0
- package/server/utils/cron/verifyUnique.js +23 -0
- package/server/utils/getFolder.js +11 -0
- package/server/utils/getInsertQuery.js +44 -0
- package/server/utils/migration/exec.migrations.js +38 -0
- package/server/utils/migration/exec.sql.js +48 -0
- package/server/utils/sendNotification.js +69 -0
- package/module/pay1/card/billing.accounts.table/general_info.hbs +0 -11
- package/module/pay1/card/billing.accounts.table/index.yml +0 -15
- package/module/pay1/card/billing.accounts.table/users.hbs +0 -12
- package/module/pay1/card/billing.payments.table/general_info.hbs +0 -13
- package/module/pay1/card/billing.payments.table/index.yml +0 -10
- package/module/pay1/card/billing.users.table/general_info.hbs +0 -11
- package/module/pay1/card/billing.users.table/index.yml +0 -10
- package/module/pay1/cls/billing.payment_status.json +0 -22
- package/module/pay1/form/billing.account_user.form.json +0 -15
- package/module/pay1/form/billing.accounts.form.json +0 -31
- package/module/pay1/form/billing.payment.form.json +0 -23
- package/module/pay1/form/billing.users.form.json +0 -39
- package/module/pay1/menu.json +0 -24
- package/module/pay1/pt/marketplace-service-payment-client-template.html +0 -147
- package/module/pay1/select/billing.account_id.sql +0 -1
- package/module/pay1/select/billing.service_id.sql +0 -1
- package/module/pay1/select/billing.subscription_id.sql +0 -1
- package/module/pay1/select/billing.user_id.sql +0 -1
- package/module/pay1/table/billing.accounts.table.json +0 -61
- package/module/pay1/table/billing.payments.table.json +0 -68
- package/module/pay1/table/billing.users.table.json +0 -71
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import monopayRedirect from './controllers/monopay.redirect.js';
|
|
2
|
+
import monopayStatus from './controllers/monopay.status.js';
|
|
3
|
+
|
|
4
|
+
const schema = {
|
|
5
|
+
querystring: {
|
|
6
|
+
type: 'object',
|
|
7
|
+
properties: {
|
|
8
|
+
price: {
|
|
9
|
+
type: 'number',
|
|
10
|
+
exclusiveMinimum: 0,
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
// additionalProperties: false,
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default async function plugin(app, opts) {
|
|
18
|
+
app.route({
|
|
19
|
+
method: 'GET',
|
|
20
|
+
url: '/monopay-redirect',
|
|
21
|
+
config: { policy: ['site'] },
|
|
22
|
+
schema,
|
|
23
|
+
handler: monopayRedirect,
|
|
24
|
+
});
|
|
25
|
+
app.route({
|
|
26
|
+
method: 'GET',
|
|
27
|
+
url: '/monopay/:id/:status',
|
|
28
|
+
config: { policy: ['public'] },
|
|
29
|
+
schema,
|
|
30
|
+
handler: monopayStatus,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import pgClients from '../../../plugins/pg/pgClients.js';
|
|
2
|
+
import dataUpdate from '../../../plugins/crud/dataUpdate.js';
|
|
2
3
|
|
|
3
4
|
import liqpayCheckStatus from '../../liqpay/utils/check.status.js';
|
|
4
5
|
|
|
@@ -23,13 +24,13 @@ const newDate = (dateNum) => {
|
|
|
23
24
|
|
|
24
25
|
export default async function paymentInfo({
|
|
25
26
|
pg = pgClients.client, user = {}, params = {},
|
|
26
|
-
}) {
|
|
27
|
+
}, reply) {
|
|
27
28
|
if (!user?.uid) {
|
|
28
|
-
return
|
|
29
|
+
return reply.status(401).send('unauthorized');
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
if (!params?.id) {
|
|
32
|
-
return
|
|
33
|
+
return reply.status(400).send('not enough query params: id');
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
const { rows = [] } = await pg.query(`select
|
|
@@ -47,14 +48,13 @@ export default async function paymentInfo({
|
|
|
47
48
|
and account_id in (select account_id from billing.account_user where user_id = $2)`, [params.id, user.uid]);
|
|
48
49
|
|
|
49
50
|
if (!rows.length) {
|
|
50
|
-
return
|
|
51
|
+
return reply.status(404).send('payment not found');
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
const {
|
|
54
55
|
account_id: accountId,
|
|
55
56
|
payment_service: service,
|
|
56
57
|
payment_status: status,
|
|
57
|
-
|
|
58
58
|
} = rows[0];
|
|
59
59
|
|
|
60
60
|
if (status !== 'success' && service === 'liqpay') {
|
|
@@ -80,11 +80,11 @@ export default async function paymentInfo({
|
|
|
80
80
|
uid: user?.uid,
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
const res1 = await metaFormat({ rows: [res].filter(el => el), cls: { account_id: 'billing.account_id', payment_status: 'billing.payment_status', subscription_id: 'billing.subscription_id' } });
|
|
84
|
-
return
|
|
83
|
+
// const res1 = await metaFormat({ rows: [res].filter(el => el), cls: { account_id: 'billing.account_id', payment_status: 'billing.payment_status', subscription_id: 'billing.subscription_id' } });
|
|
84
|
+
return res;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
await metaFormat({ rows, cls: { account_id: 'billing.account_id', payment_status: 'billing.payment_status', subscription_id: 'billing.subscription_id' } });
|
|
87
|
+
// await metaFormat({ rows, cls: { account_id: 'billing.account_id', payment_status: 'billing.payment_status', subscription_id: 'billing.subscription_id' } });
|
|
88
88
|
|
|
89
89
|
return rows[0];
|
|
90
90
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import request from 'request-promise';
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import config from '../../../../config.js';
|
|
4
|
+
import pgClients from '../../../plugins/pg/pgClients.js';
|
|
5
|
+
import dataInsert from '../../../plugins/crud/dataInsert.js';
|
|
6
|
+
import dataUpdate from '../../../plugins/crud/dataUpdate.js';
|
|
6
7
|
|
|
7
8
|
const { host, payeeId, paymentRedirect = config.paymentRedirect } = config.integrations?.pay || {};
|
|
8
9
|
|
|
@@ -24,15 +25,15 @@ const { host, payeeId, paymentRedirect = config.paymentRedirect } = config.integ
|
|
|
24
25
|
|
|
25
26
|
export default async function PortmoneRedirect(req, reply) {
|
|
26
27
|
const {
|
|
27
|
-
pg = pgClients.client, user = {}, query = {}, headers = {},
|
|
28
|
+
pg = pgClients.client, user = {}, query = {}, headers = {}, unittest,
|
|
28
29
|
} = req;
|
|
29
30
|
|
|
30
31
|
if (!host) {
|
|
31
|
-
return
|
|
32
|
+
return reply.status(400).send('empty integration host');
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
if (!payeeId) {
|
|
35
|
-
return
|
|
36
|
+
return reply.status(400).send('empty integration payeeId');
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
const domain = req.hostname.split(':').shift();
|
|
@@ -58,10 +59,17 @@ export default async function PortmoneRedirect(req, reply) {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
if ((!headers?.referer || !headers.referer.includes(domain))) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
console.warn(
|
|
63
|
+
'payments/warn',
|
|
64
|
+
'invalid referer',
|
|
65
|
+
'portmone',
|
|
66
|
+
domain,
|
|
67
|
+
headers?.referer,
|
|
68
|
+
user?.uid,
|
|
69
|
+
);
|
|
70
|
+
if (!config.auth?.disableRestricted && !unittest) {
|
|
71
|
+
return reply.status(403).send('access restricted: referer');
|
|
72
|
+
}
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
const { payment_id: paymentId } = !data.payment_id
|
|
@@ -78,14 +86,14 @@ export default async function PortmoneRedirect(req, reply) {
|
|
|
78
86
|
: { payment_id: data.payment_id };
|
|
79
87
|
|
|
80
88
|
if (!paymentId) {
|
|
81
|
-
return
|
|
89
|
+
return reply.status(404).send('payment not found');
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
await dataUpdate({
|
|
85
93
|
table: 'billing.payments',
|
|
86
94
|
id: paymentId,
|
|
87
95
|
data: {
|
|
88
|
-
referer_url: paymentRedirect ?
|
|
96
|
+
referer_url: paymentRedirect ? paymentRedirect.replace(/{{id}}/g, paymentId) : headers?.referer,
|
|
89
97
|
payment_status: 'inprogress',
|
|
90
98
|
},
|
|
91
99
|
uid: user?.uid || '0',
|
|
@@ -111,7 +119,7 @@ export default async function PortmoneRedirect(req, reply) {
|
|
|
111
119
|
accountId,
|
|
112
120
|
price: paymentAmount,
|
|
113
121
|
domain,
|
|
114
|
-
paymentRedirectUrl: paymentRedirect ?
|
|
122
|
+
paymentRedirectUrl: paymentRedirect ? paymentRedirect.replace(/{{id}}/g, paymentId) : headers?.referer,
|
|
115
123
|
uid: user?.uid || '0',
|
|
116
124
|
};
|
|
117
125
|
|
|
@@ -133,13 +141,11 @@ export default async function PortmoneRedirect(req, reply) {
|
|
|
133
141
|
throw new Error(response);
|
|
134
142
|
}
|
|
135
143
|
|
|
136
|
-
|
|
137
|
-
logger.file('payments', logObj);
|
|
144
|
+
console.log('payments/redirect', paymentId, response.headers?.location);
|
|
138
145
|
return reply.redirect(response.headers.location);
|
|
139
146
|
}
|
|
140
147
|
catch (err) {
|
|
141
|
-
|
|
142
|
-
logger.file('payments/error', logObj);
|
|
148
|
+
console.error('payments/error', JSON.stringify(logObj), err.toString(), err.stack);
|
|
143
149
|
return { error: err.toString(), status: 500 };
|
|
144
150
|
}
|
|
145
151
|
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
} from '
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
import config from '../../../../config.js';
|
|
8
|
+
import pgClients from '../../../plugins/pg/pgClients.js';
|
|
9
|
+
import dataUpdate from '../../../plugins/crud/dataUpdate.js';
|
|
10
|
+
|
|
11
|
+
import sendNotification from '../../../utils/sendNotification.js';
|
|
12
|
+
|
|
13
|
+
const ptBody = readFileSync(path.join(dirname, '../../../../module/pay/pt/marketplace-service-payment-client-template.html'));
|
|
6
14
|
|
|
7
15
|
const newDate = () => {
|
|
8
16
|
const date = new Date();
|
|
@@ -56,14 +64,15 @@ export default async function portmoneStatus(req, reply) {
|
|
|
56
64
|
} = data;
|
|
57
65
|
|
|
58
66
|
if (trxStatus === 'success') {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
console.log(
|
|
68
|
+
'payments/skip',
|
|
69
|
+
'status',
|
|
70
|
+
'portmone',
|
|
62
71
|
paymentId,
|
|
63
72
|
origin,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
user?.uid || '0',
|
|
74
|
+
'Transaction was already processed',
|
|
75
|
+
);
|
|
67
76
|
return { message: 'Transaction was already processed', status: 400 };
|
|
68
77
|
}
|
|
69
78
|
|
|
@@ -75,28 +84,30 @@ export default async function portmoneStatus(req, reply) {
|
|
|
75
84
|
|
|
76
85
|
// request portmone trx info to recheck request data is valid?
|
|
77
86
|
if (!origin?.startsWith(host)) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
console.warn(
|
|
88
|
+
'payments/warn',
|
|
89
|
+
'status',
|
|
90
|
+
'portmone',
|
|
81
91
|
paymentId,
|
|
82
92
|
origin,
|
|
83
|
-
|
|
93
|
+
user?.uid || '0',
|
|
84
94
|
refill,
|
|
85
|
-
|
|
86
|
-
|
|
95
|
+
'invalid referer',
|
|
96
|
+
);
|
|
87
97
|
if (!config.auth?.disableRestricted) return { message: 'access restricted: host', status: 403 };
|
|
88
98
|
}
|
|
89
99
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
100
|
+
console.log(
|
|
101
|
+
'payments/info',
|
|
102
|
+
'status',
|
|
103
|
+
'portmone',
|
|
93
104
|
paymentId,
|
|
94
105
|
origin,
|
|
95
|
-
|
|
106
|
+
user?.uid || '0',
|
|
96
107
|
refill,
|
|
97
108
|
body,
|
|
98
109
|
params,
|
|
99
|
-
|
|
110
|
+
);
|
|
100
111
|
|
|
101
112
|
// last trx contains total balance
|
|
102
113
|
const { total_balance: totalBalance } = await pg.query(`select total_balance from billing.payments
|
|
@@ -122,30 +133,31 @@ export default async function portmoneStatus(req, reply) {
|
|
|
122
133
|
const { account_email: to } = await pg.query('select account_email from billing.accounts where account_id=$1', [accountId])
|
|
123
134
|
.then(el => el.rows?.[0] || {});
|
|
124
135
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
136
|
+
console.log(
|
|
137
|
+
'payments/info2',
|
|
138
|
+
'status',
|
|
139
|
+
'portmone',
|
|
128
140
|
paymentId,
|
|
129
141
|
responseStatus,
|
|
130
142
|
to,
|
|
131
143
|
origin,
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
Object.assign(data, {
|
|
137
|
-
domain: req.hostname,
|
|
138
|
-
total_balance: +(refill || 0) + (+totalBalance || 0),
|
|
139
|
-
});
|
|
144
|
+
redirectUrl,
|
|
145
|
+
user.uid || '0',
|
|
146
|
+
);
|
|
140
147
|
|
|
141
148
|
if (to) {
|
|
142
|
-
const pt =
|
|
149
|
+
const pt = ptBody
|
|
150
|
+
.replace(/{{domain}}/g, req.hostname)
|
|
151
|
+
.replace(/{{total_balance}}/g, +(refillSum || 0) + (+totalBalance || 0))
|
|
152
|
+
.replace(/{{username}}/g, to)
|
|
153
|
+
.replace(/{{created_at}}/g, (data?.cdate || data?.created_at || new Date()).toLocaleString('uk-UA'))
|
|
154
|
+
.replace(/{{payment_status}}/g, responseStatus)
|
|
155
|
+
.replace(/{{payment_amount}}/g, refillSum || 0);
|
|
143
156
|
const options = {
|
|
144
157
|
pg,
|
|
145
158
|
to,
|
|
146
159
|
title: `Поповнення рахунку на порталі ${req.hostname}`,
|
|
147
|
-
|
|
148
|
-
data,
|
|
160
|
+
pt,
|
|
149
161
|
nocache: config?.local,
|
|
150
162
|
};
|
|
151
163
|
await sendNotification(options);
|
|
@@ -153,15 +165,14 @@ export default async function portmoneStatus(req, reply) {
|
|
|
153
165
|
return reply.redirect(redirectUrl || '/');
|
|
154
166
|
}
|
|
155
167
|
catch (err) {
|
|
156
|
-
|
|
168
|
+
console.error('payments/error', JSON.stringify({
|
|
157
169
|
type: 'status',
|
|
158
170
|
service: 'portmone',
|
|
159
171
|
paymentId,
|
|
160
172
|
origin,
|
|
161
173
|
responseStatus,
|
|
162
174
|
uid: user?.uid || '0',
|
|
163
|
-
|
|
164
|
-
});
|
|
175
|
+
}), err.toString(), err.stack);
|
|
165
176
|
return reply.status(500).send(err.toString());
|
|
166
177
|
}
|
|
167
178
|
}
|
|
@@ -1,33 +1,34 @@
|
|
|
1
|
-
import request from 'request-promise';
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
1
|
+
import request from 'request-promise';
|
|
2
|
+
|
|
3
|
+
import config from '../../../../config.js';
|
|
4
|
+
import pgClients from '../../../plugins/pg/pgClients.js';
|
|
5
|
+
|
|
6
|
+
const { host, payeeId } = config.integrations?.pay || {};
|
|
7
|
+
|
|
8
|
+
export default async function updateTrxStatus({
|
|
9
|
+
pg = pgClients.client, paymentId,
|
|
10
|
+
}) {
|
|
11
|
+
const data = pg.pk?.['billing.payments'] && paymentId ? await pg.query('select * from billing.payments where payment_id=$1', [paymentId]).then(el => el.rows?.[0] || {}) : undefined;
|
|
12
|
+
if (!paymentId || !host || !payeeId || !data) throw new Error('not enough params');
|
|
13
|
+
|
|
14
|
+
const formData = {
|
|
15
|
+
method: 'result',
|
|
16
|
+
login: 'WDISHOP',
|
|
17
|
+
password: 'wdi451',
|
|
18
|
+
payeeId,
|
|
19
|
+
shopOrderNumber: paymentId,
|
|
20
|
+
// shopbillId,
|
|
21
|
+
id: paymentId,
|
|
22
|
+
};
|
|
23
|
+
const response = await request({
|
|
24
|
+
url: `${host}/gateway/`,
|
|
25
|
+
method: 'POST',
|
|
26
|
+
formData,
|
|
27
|
+
followAllRedirects: false,
|
|
28
|
+
jar: true,
|
|
29
|
+
timeout: 6000,
|
|
30
|
+
simple: false, // prevent not 2xx code trigger catch
|
|
31
|
+
resolveWithFullResponse: true,
|
|
32
|
+
});
|
|
33
|
+
return response;
|
|
34
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import config from '../../../config.js';
|
|
2
|
+
import pgClients from '../pg/pgClients.js';
|
|
3
|
+
|
|
4
|
+
import cronList from './cronList.js';
|
|
5
|
+
import runCron from './runCron.js';
|
|
6
|
+
import interval2ms from './interval2ms.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* interval:
|
|
10
|
+
* - 02:54 - every day
|
|
11
|
+
* - 2:03 - every day
|
|
12
|
+
* - *1:43 - 2 times a day
|
|
13
|
+
* - *12:03 - 2 times a day
|
|
14
|
+
* - **:54 - every hour
|
|
15
|
+
* - **:*3 - every 10 minutes
|
|
16
|
+
* - 60 - every minute
|
|
17
|
+
* - 10 * 60 - every 10 minutes
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export default async function addCron(func, interval, pg = pgClients.client) {
|
|
21
|
+
const { time = {}, disabled = [] } = config.cron || {};
|
|
22
|
+
|
|
23
|
+
const name = func.name || func.toString().split('/').at(-1).split('\'')[0];
|
|
24
|
+
|
|
25
|
+
if (disabled.includes(name)) {
|
|
26
|
+
console.warn('cron', name, 'cron disabled');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
cronList[name] = func;
|
|
31
|
+
|
|
32
|
+
const userInterval = time[name] || interval;
|
|
33
|
+
const [waitMs, intervalMs] = interval2ms[typeof interval](userInterval);
|
|
34
|
+
|
|
35
|
+
if (intervalMs < 1000) {
|
|
36
|
+
console.warn('cron', name, `interval ${interval} too small`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// setTimeout to w8 for the time to start
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
runCron({ pg, func, name });
|
|
43
|
+
// interval
|
|
44
|
+
setInterval(() => {
|
|
45
|
+
runCron({ pg, func, name });
|
|
46
|
+
}, intervalMs);
|
|
47
|
+
}, waitMs);
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default {};
|
|
@@ -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,24 @@
|
|
|
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
|
+
if (!db) return;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const data = await func({ pg });
|
|
17
|
+
const subdir = data ? 'cron' : 'cron/null';
|
|
18
|
+
logger.file(subdir, { db, name, result: data });
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
logger.file('cron', { db, name, error: err.toString() });
|
|
22
|
+
logger.error(err);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
if (!config.redis) return true; // skip if redis is disabled
|
|
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
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
import config from '../../config.js';
|
|
4
|
+
|
|
5
|
+
export default function getFolder(req, type = 'server') {
|
|
6
|
+
if (!['server', 'local'].includes(type)) throw new Error('params type is invalid');
|
|
7
|
+
const types = { local: req.root || config.root, server: req.mapServerRoot || config.mapServerRoot };
|
|
8
|
+
const dbname = req.pg?.options?.database || req.pg?.database || config.pg?.database; // request / config params / default config params
|
|
9
|
+
const filepath = path.posix.join(types[type] || `/data/local/${dbname || ''}`, req.folder || config.folder || '');
|
|
10
|
+
return filepath;
|
|
11
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import getMeta from '../plugins/pg/funcs/getMeta.js';
|
|
2
|
+
|
|
3
|
+
export default async function getInsertQuery({
|
|
4
|
+
pg, table, data, id, uid,
|
|
5
|
+
}) {
|
|
6
|
+
if (!pg?.pk || !data) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { columns } = await getMeta({ pg, table });
|
|
11
|
+
|
|
12
|
+
if (!columns) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const names = columns.map((el) => el.name);
|
|
17
|
+
|
|
18
|
+
Object.assign(data, {
|
|
19
|
+
...(id && pg.pk?.[table] ? { [pg.pk?.[table]]: id } : {}),
|
|
20
|
+
...(table !== 'admin.users' ? { uid } : {}),
|
|
21
|
+
editor_id: uid,
|
|
22
|
+
created_by: uid,
|
|
23
|
+
updated_by: uid,
|
|
24
|
+
// editor_id: uid,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const systemColumns = ['cdate', 'editor_date', 'created_at', 'updated_at'].filter((el) => names.includes(el)).map((el) => [el, 'now()']);
|
|
28
|
+
const systemColumnNames = systemColumns.map(el => el[0]);
|
|
29
|
+
|
|
30
|
+
const filterData = Object.keys(data)
|
|
31
|
+
.filter((el) => !systemColumnNames.includes(el) && (typeof data[el] === 'boolean' ? true : data[el]) && names.includes(el)).map((el) => [el, data[el]]);
|
|
32
|
+
|
|
33
|
+
const insertQuery = `insert into ${table}
|
|
34
|
+
|
|
35
|
+
( ${filterData?.map((key) => `"${key[0]}"`).concat(systemColumnNames).join(',')})
|
|
36
|
+
|
|
37
|
+
values (${filterData?.map((key, i) => (key[0] === 'geom' ? `st_setsrid(st_geomfromgeojson($${i + 1}::json),4326)` : `$${i + 1}`)).concat(systemColumns.map((el) => el[1])).join(',')})
|
|
38
|
+
|
|
39
|
+
returning *`;
|
|
40
|
+
|
|
41
|
+
const args = [...filterData.map((el) => (typeof el[1] === 'object' && (!Array.isArray(el[1]) || typeof el[1]?.[0] === 'object') ? JSON.stringify(el[1]) : el[1]))];
|
|
42
|
+
|
|
43
|
+
return { insertQuery, args };
|
|
44
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readdirSync, existsSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
import config from '../../../config.js';
|
|
5
|
+
import execSql from './exec.sql.js';
|
|
6
|
+
import pgClients from '../../plugins/pg/pgClients.js';
|
|
7
|
+
|
|
8
|
+
const time = Date.now();
|
|
9
|
+
|
|
10
|
+
export default async function execMigrations(dirPath, pg = pgClients.client, iscore = false) {
|
|
11
|
+
if (
|
|
12
|
+
!dirPath
|
|
13
|
+
|| (config.migrationsCore === false && iscore)
|
|
14
|
+
|| config.migrations === false
|
|
15
|
+
|| process.env.NODE_ENV === 'unit test'
|
|
16
|
+
) {
|
|
17
|
+
console.log('migrations skip', 'core: ', !!iscore, 'dir: ', dirPath || 'not specified');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log('migrations start', dirPath, Date.now() - time);
|
|
22
|
+
|
|
23
|
+
const exists = existsSync(dirPath);
|
|
24
|
+
|
|
25
|
+
if (exists) {
|
|
26
|
+
// get directory sql file list
|
|
27
|
+
const content = readdirSync(dirPath, { withFileTypes: true })
|
|
28
|
+
?.filter((el) => el.isFile() && path.extname(el.name) === '.sql')
|
|
29
|
+
?.map((el) => el.name) || [];
|
|
30
|
+
|
|
31
|
+
// execute sql files
|
|
32
|
+
if (content?.length) {
|
|
33
|
+
await content.reduce((promise, filename) => promise.then(() => execSql(path.join(dirPath, filename), pg)), Promise.resolve());
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log('migrations finish', dirPath, exists, Date.now() - time);
|
|
38
|
+
}
|