@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.
Files changed (61) hide show
  1. package/config.js +7 -0
  2. package/module/pay/pt/marketplace-service-payment-client-template.html +7 -20
  3. package/package.json +15 -31
  4. package/plugin.js +14 -7
  5. package/server/plugins/crud/dataDelete.js +82 -0
  6. package/server/plugins/crud/dataInsert.js +76 -0
  7. package/server/plugins/crud/dataUpdate.js +112 -0
  8. package/server/plugins/{cron.js → hook.js} +24 -22
  9. package/server/plugins/pg/funcs/autoIndex.js +102 -0
  10. package/server/plugins/pg/funcs/getDBParams.js +15 -0
  11. package/server/plugins/pg/funcs/getMeta.js +48 -0
  12. package/server/plugins/pg/funcs/getPG.js +39 -0
  13. package/server/plugins/pg/funcs/getPGAsync.js +40 -0
  14. package/server/plugins/pg/funcs/init.js +113 -0
  15. package/server/plugins/pg/index.js +24 -0
  16. package/server/plugins/pg/pgClients.js +22 -0
  17. package/server/plugins/redis/client.js +8 -0
  18. package/server/plugins/redis/funcs/getRedis.js +24 -0
  19. package/server/plugins/redis/funcs/redisClients.js +3 -0
  20. package/server/plugins/redis/index.js +11 -0
  21. package/server/routes/liqpay/controllers/liqpay.redirect.js +7 -11
  22. package/server/routes/liqpay/controllers/liqpay.status.js +26 -15
  23. package/server/routes/liqpay/utils/check.status.js +1 -1
  24. package/server/routes/monopay/controllers/monopay.redirect.js +141 -0
  25. package/server/routes/monopay/controllers/monopay.status.js +70 -0
  26. package/server/routes/monopay/index.mjs +32 -0
  27. package/server/routes/portmone/controllers/payment.info.js +9 -9
  28. package/server/routes/portmone/controllers/portmone.redirect.js +23 -17
  29. package/server/routes/portmone/controllers/portmone.status.js +49 -38
  30. package/server/routes/portmone/utils/portmone.check.status.js +34 -33
  31. package/server/utils/cron/addCron.js +48 -0
  32. package/server/utils/cron/cronList.js +1 -0
  33. package/server/utils/cron/interval2ms.js +40 -0
  34. package/server/utils/cron/runCron.js +24 -0
  35. package/server/utils/cron/verifyUnique.js +23 -0
  36. package/server/utils/getFolder.js +11 -0
  37. package/server/utils/getInsertQuery.js +44 -0
  38. package/server/utils/migration/exec.migrations.js +38 -0
  39. package/server/utils/migration/exec.sql.js +48 -0
  40. package/server/utils/sendNotification.js +69 -0
  41. package/module/pay1/card/billing.accounts.table/general_info.hbs +0 -11
  42. package/module/pay1/card/billing.accounts.table/index.yml +0 -15
  43. package/module/pay1/card/billing.accounts.table/users.hbs +0 -12
  44. package/module/pay1/card/billing.payments.table/general_info.hbs +0 -13
  45. package/module/pay1/card/billing.payments.table/index.yml +0 -10
  46. package/module/pay1/card/billing.users.table/general_info.hbs +0 -11
  47. package/module/pay1/card/billing.users.table/index.yml +0 -10
  48. package/module/pay1/cls/billing.payment_status.json +0 -22
  49. package/module/pay1/form/billing.account_user.form.json +0 -15
  50. package/module/pay1/form/billing.accounts.form.json +0 -31
  51. package/module/pay1/form/billing.payment.form.json +0 -23
  52. package/module/pay1/form/billing.users.form.json +0 -39
  53. package/module/pay1/menu.json +0 -24
  54. package/module/pay1/pt/marketplace-service-payment-client-template.html +0 -147
  55. package/module/pay1/select/billing.account_id.sql +0 -1
  56. package/module/pay1/select/billing.service_id.sql +0 -1
  57. package/module/pay1/select/billing.subscription_id.sql +0 -1
  58. package/module/pay1/select/billing.user_id.sql +0 -1
  59. package/module/pay1/table/billing.accounts.table.json +0 -61
  60. package/module/pay1/table/billing.payments.table.json +0 -68
  61. 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 { dataUpdate, metaFormat, pgClients } from '@opengis/fastify-table/utils.js';
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 { message: 'unauthorized', status: 401 };
29
+ return reply.status(401).send('unauthorized');
29
30
  }
30
31
 
31
32
  if (!params?.id) {
32
- return { message: 'not enough query params: id', status: 400 };
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 { message: 'payment not found', status: 404 };
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 res1;
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
- config, pgClients, logger, dataInsert, dataUpdate, handlebarsSync,
5
- } from '@opengis/fastify-table/utils.js';
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 { message: 'empty integration host', status: 400 };
32
+ return reply.status(400).send('empty integration host');
32
33
  }
33
34
 
34
35
  if (!payeeId) {
35
- return { message: 'empty integration payeeId', status: 400 };
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
- logger.file('payments/warn', {
62
- message: 'invalid referer', service: 'portmone', domain, referer: headers?.referer, uid: user?.uid,
63
- });
64
- if (!config.auth?.disableRestricted) return { message: 'access restricted: referer', status: 403 };
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 { message: 'payment not found', status: 404 };
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 ? handlebarsSync.compile(paymentRedirect)({ id: paymentId }) : headers?.referer,
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 ? handlebarsSync.compile(paymentRedirect)({ id: paymentId }) : headers?.referer,
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
- Object.assign(logObj, { paymentId, redirect: response.headers?.location, status: 302 });
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
- Object.assign(logObj, { error: err.toString() });
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
- config, logger, pgClients, dataUpdate, getTemplate,
3
- } from '@opengis/fastify-table/utils.js';
1
+ import path from 'node:path';
2
+ import { readFileSync } from 'node:fs';
3
+ import { fileURLToPath } from 'url';
4
4
 
5
- import { sendNotification } from '@opengis/admin/utils.js';
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
- logger.file('payments/skip', {
60
- type: 'status',
61
- service: 'portmone',
67
+ console.log(
68
+ 'payments/skip',
69
+ 'status',
70
+ 'portmone',
62
71
  paymentId,
63
72
  origin,
64
- uid: user?.uid || '0',
65
- message: 'Transaction was already processed',
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
- logger.file('payments/warn', {
79
- type: 'status',
80
- service: 'portmone',
87
+ console.warn(
88
+ 'payments/warn',
89
+ 'status',
90
+ 'portmone',
81
91
  paymentId,
82
92
  origin,
83
- uid: user?.uid || '0',
93
+ user?.uid || '0',
84
94
  refill,
85
- message: 'invalid referer',
86
- });
95
+ 'invalid referer',
96
+ );
87
97
  if (!config.auth?.disableRestricted) return { message: 'access restricted: host', status: 403 };
88
98
  }
89
99
 
90
- logger.file('payments', {
91
- type: 'status',
92
- service: 'portmone',
100
+ console.log(
101
+ 'payments/info',
102
+ 'status',
103
+ 'portmone',
93
104
  paymentId,
94
105
  origin,
95
- uid: user?.uid || '0',
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
- logger.file('payments', {
126
- type: 'status',
127
- service: 'portmone',
136
+ console.log(
137
+ 'payments/info2',
138
+ 'status',
139
+ 'portmone',
128
140
  paymentId,
129
141
  responseStatus,
130
142
  to,
131
143
  origin,
132
- redirect: redirectUrl,
133
- uid: user.uid || '0',
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 = await getTemplate('pt', 'marketplace-service-payment-client-template');
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
- template: pt?.html || pt,
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
- logger.file('payments/error', {
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
- error: err.toString(),
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 { config, pgClients } from '@opengis/fastify-table/utils.js';
4
-
5
- const { host, payeeId } = config.integrations?.pay || {};
6
-
7
- export default async function updateTrxStatus({
8
- pg = pgClients.client, paymentId,
9
- }) {
10
- 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;
11
- if (!paymentId || !host || !payeeId || !data) throw new Error('not enough params');
12
-
13
- const formData = {
14
- method: 'result',
15
- login: 'WDISHOP',
16
- password: 'wdi451',
17
- payeeId,
18
- shopOrderNumber: paymentId,
19
- // shopbillId,
20
- id: paymentId,
21
- };
22
- const response = await request({
23
- url: `${host}/gateway/`,
24
- method: 'POST',
25
- formData,
26
- followAllRedirects: false,
27
- jar: true,
28
- timeout: 6000,
29
- simple: false, // prevent not 2xx code trigger catch
30
- resolveWithFullResponse: true,
31
- });
32
- return response;
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
+ }