miolo 2.0.0-beta.1 → 2.0.0-beta.3

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 (115) hide show
  1. package/dist/cli/miolo.cli.umd.js +1 -1
  2. package/dist/cli-react/miolo.cli-react.umd.js +1 -1
  3. package/dist/server/miolo.server.node.mjs +1 -1
  4. package/package.json +18 -18
  5. package/src/cli/catcher/index.mjs +75 -0
  6. package/src/cli/fetcher/fetcher.mjs +214 -0
  7. package/src/cli/fetcher/index.mjs +7 -0
  8. package/src/cli/fetcher/utils.mjs +51 -0
  9. package/src/cli/fetcher/v1.tar.gz +0 -0
  10. package/src/cli/index.mjs +30 -0
  11. package/src/cli/socket/index.mjs +6 -0
  12. package/src/cli-react/AppBrowser.jsx +14 -0
  13. package/src/cli-react/AppBrowser.mjs +14 -0
  14. package/src/cli-react/AppServer.jsx +17 -0
  15. package/src/cli-react/AppServer.mjs +16 -0
  16. package/src/cli-react/_jsx.tar.gz +0 -0
  17. package/src/cli-react/context/MioloContext.mjs +5 -0
  18. package/src/cli-react/context/MioloContextProvider.jsx +87 -0
  19. package/src/cli-react/context/MioloContextProvider.mjs +80 -0
  20. package/src/cli-react/context/useMioloContext.jsx +6 -0
  21. package/src/cli-react/context/useMioloContext.mjs +6 -0
  22. package/src/cli-react/context/withMioloContext.jsx +15 -0
  23. package/src/cli-react/context/withMioloContext.mjs +17 -0
  24. package/src/cli-react/index.mjs +7 -0
  25. package/src/cli-react/ssr/getSsrDataFromContext.mjs +33 -0
  26. package/src/cli-react/ssr/hooks.tar.gz +0 -0
  27. package/src/cli-react/ssr/useSsrDataOrReload.mjs +43 -0
  28. package/src/server/config/defaults.mjs +418 -0
  29. package/src/server/config/index.mjs +32 -0
  30. package/src/server/engines/cron/emails.mjs +10 -0
  31. package/src/server/engines/cron/index.mjs +153 -0
  32. package/src/server/engines/cron/init.mjs +53 -0
  33. package/src/server/engines/cron/ipsum.mjs +151 -0
  34. package/src/server/engines/cron/syscheck.mjs +56 -0
  35. package/src/server/engines/emailer/index.mjs +2 -0
  36. package/src/server/engines/emailer/queue.mjs +54 -0
  37. package/src/server/engines/emailer/transporter.mjs +149 -0
  38. package/src/server/engines/geoip/index.mjs +66 -0
  39. package/src/server/engines/http/index.mjs +79 -0
  40. package/src/server/engines/logger/index.mjs +313 -0
  41. package/src/server/engines/logger/logger_mail.mjs +89 -0
  42. package/src/server/engines/logger/reopenTransportOnHupSignal.mjs +57 -0
  43. package/src/server/engines/logger/verify.mjs +22 -0
  44. package/src/server/engines/parser/Parser.mjs +126 -0
  45. package/src/server/engines/parser/index.mjs +6 -0
  46. package/src/server/engines/socket/index.mjs +67 -0
  47. package/src/server/index.mjs +16 -0
  48. package/src/server/middleware/auth/basic.mjs +90 -0
  49. package/src/server/middleware/auth/credentials/index.mjs +151 -0
  50. package/src/server/middleware/auth/credentials/session/index.mjs +24 -0
  51. package/src/server/middleware/auth/credentials/session/store.mjs +59 -0
  52. package/src/server/middleware/auth/credentials/session/store_koa_redis.mjs +3 -0
  53. package/src/server/middleware/auth/custom.mjs +29 -0
  54. package/src/server/middleware/auth/guest.mjs +75 -0
  55. package/src/server/middleware/context/cache/index.mjs +61 -0
  56. package/src/server/middleware/context/cache/options.mjs +66 -0
  57. package/src/server/middleware/context/db.mjs +58 -0
  58. package/src/server/middleware/context/index.mjs +35 -0
  59. package/src/server/middleware/extra.mjs +12 -0
  60. package/src/server/middleware/http/body.mjs +31 -0
  61. package/src/server/middleware/http/catcher.mjs +81 -0
  62. package/src/server/middleware/http/custom_blacklist.mjs +16 -0
  63. package/src/server/middleware/http/headers.mjs +73 -0
  64. package/src/server/middleware/http/ratelimit.mjs +66 -0
  65. package/src/server/middleware/http/request.mjs +146 -0
  66. package/src/server/middleware/routes/catch_js_error.mjs +41 -0
  67. package/src/server/middleware/routes/robots.mjs +21 -0
  68. package/src/server/middleware/routes/router/crud/attachCrudRoutes.mjs +214 -0
  69. package/src/server/middleware/routes/router/crud/getCrudConfig.mjs +129 -0
  70. package/src/server/middleware/routes/router/defaults.mjs +29 -0
  71. package/src/server/middleware/routes/router/index.mjs +49 -0
  72. package/src/server/middleware/routes/router/queries/attachQueriesRoutes.mjs +102 -0
  73. package/src/server/middleware/routes/router/queries/getQueriesConfig.mjs +113 -0
  74. package/src/server/middleware/routes/router/utils.mjs +38 -0
  75. package/src/server/middleware/ssr/_old.tar.gz +0 -0
  76. package/src/server/middleware/ssr/context.mjs +21 -0
  77. package/src/server/middleware/ssr/fallbackIndex.mjs +29 -0
  78. package/src/server/middleware/ssr/html.mjs +64 -0
  79. package/src/server/middleware/ssr/loader.mjs +24 -0
  80. package/src/server/middleware/ssr/ssr_render.mjs +49 -0
  81. package/src/server/middleware/static/afialapis.ico +0 -0
  82. package/src/server/middleware/static/index.mjs +27 -0
  83. package/src/server/middleware/static/miolo.ico +0 -0
  84. package/src/server/middleware/vite/devserver.mjs +34 -0
  85. package/src/server/server-dev.mjs +41 -0
  86. package/src/server/server.mjs +135 -0
  87. package/src/server/static/img/afialapis.ico +0 -0
  88. package/src/server/static/img/miolo.ico +0 -0
  89. package/src/server/static/robots.txt +2 -0
  90. package/dist/cli/miolo.cli.iife.bundle.js +0 -968
  91. package/dist/cli/miolo.cli.iife.bundle.js.map +0 -1
  92. package/dist/cli/miolo.cli.iife.bundle.min.js +0 -13
  93. package/dist/cli/miolo.cli.iife.js +0 -968
  94. package/dist/cli/miolo.cli.iife.js.map +0 -1
  95. package/dist/cli/miolo.cli.iife.min.js +0 -13
  96. package/dist/cli/miolo.cli.min.mjs +0 -13
  97. package/dist/cli/miolo.cli.mjs +0 -485
  98. package/dist/cli/miolo.cli.mjs.map +0 -1
  99. package/dist/cli/miolo.cli.umd.bundle.js +0 -969
  100. package/dist/cli/miolo.cli.umd.bundle.js.map +0 -1
  101. package/dist/cli/miolo.cli.umd.bundle.min.js +0 -13
  102. package/dist/cli/miolo.cli.umd.min.js +0 -13
  103. package/dist/cli-react/miolo.cli-react.iife.bundle.js +0 -1232
  104. package/dist/cli-react/miolo.cli-react.iife.bundle.js.map +0 -1
  105. package/dist/cli-react/miolo.cli-react.iife.bundle.min.js +0 -13
  106. package/dist/cli-react/miolo.cli-react.iife.js +0 -1174
  107. package/dist/cli-react/miolo.cli-react.iife.js.map +0 -1
  108. package/dist/cli-react/miolo.cli-react.iife.min.js +0 -13
  109. package/dist/cli-react/miolo.cli-react.min.mjs +0 -13
  110. package/dist/cli-react/miolo.cli-react.mjs +0 -655
  111. package/dist/cli-react/miolo.cli-react.mjs.map +0 -1
  112. package/dist/cli-react/miolo.cli-react.umd.bundle.js +0 -1233
  113. package/dist/cli-react/miolo.cli-react.umd.bundle.js.map +0 -1
  114. package/dist/cli-react/miolo.cli-react.umd.bundle.min.js +0 -13
  115. package/dist/cli-react/miolo.cli-react.umd.min.js +0 -13
@@ -0,0 +1,153 @@
1
+ import {
2
+ init_cron_job
3
+ } from './init.mjs'
4
+ import {
5
+ sys_check_config
6
+ } from './syscheck.mjs'
7
+ import {
8
+ ipsum_config
9
+ } from './ipsum.mjs'
10
+ import { cyan, green_bold, yellow_bold } from 'tinguir'
11
+ import { sys_email_queue_config } from './emails.mjs'
12
+
13
+
14
+ export function init_cron(app, custom) {
15
+ const miolo = app.context.miolo
16
+ const logger= miolo.logger
17
+
18
+ const jobConfigs= [
19
+ sys_check_config(),
20
+ ipsum_config(),
21
+ sys_email_queue_config(),
22
+ ...custom || []
23
+ ]
24
+
25
+ // Keep trace of conr jobs to be accessible later
26
+ const jobInfos= [
27
+ // {name: 'name', job: <job>, isActive: true/false}
28
+ ]
29
+
30
+ jobConfigs.map(config => {
31
+ const name = config.name
32
+ const job= init_cron_job(miolo, config)
33
+ jobInfos.push({
34
+ name,
35
+ job,
36
+ isActive: false
37
+ })
38
+ })
39
+
40
+ const _find_job_by_idx_or_name = (idxOrName) => {
41
+ let jobInfo
42
+ if (typeof idxOrName == 'number') {
43
+ jobInfo= jobInfos[idxOrName]
44
+ } else {
45
+ jobInfo= jobInfos.filter(j => j.name == idxOrName)[0]
46
+ }
47
+
48
+ if (!jobInfo) {
49
+ logger.error(`[cron] Job ${cyan(idxOrName)} Not Found`)
50
+ }
51
+
52
+ return jobInfo
53
+ }
54
+
55
+ const _start_job = (jobInfo) => {
56
+ try {
57
+ jobInfo.job.start()
58
+ jobInfo.isActive= true
59
+ logger.debug(`[cron][Job ${cyan(jobInfo.name)}] ${green_bold('started!')}`)
60
+ return 1
61
+ } catch(e) {
62
+ logger.error(`[cron][Job ${cyan(jobInfo.name)}] Error starting it`)
63
+ logger.error(e)
64
+ return 0
65
+ }
66
+ }
67
+
68
+ const _start_job_by_idx_or_name = (idxOrName) => {
69
+ const jobInfo = _find_job_by_idx_or_name(idxOrName)
70
+ if (jobInfo) {
71
+ const done = _start_job(jobInfo)
72
+ return [done, jobInfo.name]
73
+ }
74
+ return [0, '']
75
+ }
76
+
77
+ const _start_all_jobs = () => {
78
+ try {
79
+ let started= [], errors= []
80
+ jobInfos.map(jobInfo => {
81
+ const done= _start_job(jobInfo)
82
+ if (done == 1) {
83
+ started.push(jobInfo.name)
84
+ } else {
85
+ errors.push(jobInfo.name)
86
+ }
87
+ })
88
+ if (started.length > 0) {
89
+ logger.info(`[cron] Started ${started.length} jobs: ${started}`)
90
+ }
91
+ if (errors.length > 0) {
92
+ logger.warn(`[cron] Could not start ${errors.length} jobs: ${errors}`)
93
+ }
94
+ } catch(error) {
95
+ logger.error(`[cron] start() error: ${error}`)
96
+ }
97
+
98
+ }
99
+
100
+ const _stop_job = (jobInfo) => {
101
+ try {
102
+ jobInfo.job.stop()
103
+ jobInfo.isActive= false
104
+ logger.debug(`[cron][Job ${cyan(jobInfo.name)}] ${yellow_bold('stopped!')}`)
105
+ return 1
106
+ } catch(e) {
107
+ logger.error(`[cron][Job ${cyan(jobInfo.name)}] Error stopping it`)
108
+ logger.error(e)
109
+ return 0
110
+ }
111
+ }
112
+
113
+ const _stop_job_by_idx_or_name = (idxOrName) => {
114
+ const jobInfo = _find_job_by_idx_or_name(idxOrName)
115
+ if (jobInfo) {
116
+ const done = _stop_job(jobInfo)
117
+ return [done, jobInfo.name]
118
+ }
119
+ return [0, '']
120
+ }
121
+
122
+ const _stop_all_jobs = () => {
123
+ try {
124
+ let stopped= [], errors= []
125
+ jobInfos.map(jobInfo => {
126
+ const done= _stop_job(jobInfo)
127
+ if (done == 1) {
128
+ stopped.push(jobInfo.name)
129
+ } else {
130
+ errors.push(jobInfo.name)
131
+ }
132
+ })
133
+ if (stopped.length > 0) {
134
+ logger.info(`[cron] Stopped ${stopped.length} jobs: ${stopped}`)
135
+ }
136
+ if (errors.length > 0) {
137
+ logger.warn(`[cron] Could not stop ${errors.length} jobs: ${errors}`)
138
+ }
139
+ } catch(error) {
140
+ logger.error(`[cron] stop() error: ${error}`)
141
+ }
142
+ }
143
+
144
+ app.cron= {
145
+ jobs: jobInfos,
146
+ start_one: _start_job_by_idx_or_name,
147
+ start: _start_all_jobs,
148
+ stop_one : _stop_job_by_idx_or_name,
149
+ stop: _stop_all_jobs
150
+ }
151
+
152
+ return app
153
+ }
@@ -0,0 +1,53 @@
1
+
2
+ import {CronJob} from 'cron'
3
+ import { cyan, green_bold } from 'tinguir'
4
+
5
+ export function init_cron_job(miolo, config) {
6
+ const logger= miolo.logger
7
+ const onTickName = config?.onTick?.name
8
+ ? config.onTick.name != 'onTick'
9
+ ? config.onTick.name
10
+ : 'custom'
11
+ : 'custom'
12
+
13
+ const name = config?.name || onTickName
14
+
15
+ const job= new CronJob(
16
+ // cronTime
17
+ config?.cronTime || '*/5 * * * *',
18
+
19
+ // onTick(miolo, onComplete)
20
+ (onComplete) => {
21
+ try {
22
+ logger.silly(`[cron][Custom Job ${cyan(name)}] ${green_bold('ticks!')}`)
23
+ config.onTick(miolo, onComplete)
24
+ } catch(e) {
25
+ logger.error(`[cron][Custom Job ${cyan(name)}] Error at onTick()`)
26
+ logger.error(e)
27
+ }
28
+ },
29
+
30
+ // onComplete(miolo)
31
+ () => {
32
+ logger.silly(`[cron][Custom Job ${cyan(name)}] ${green_bold('completed!')}`)
33
+ if (config?.onComplete) {
34
+ try {
35
+ config.onComplete(miolo)
36
+ } catch(e) {
37
+ logger.error(`[cron][Custom Job ${cyan(name)}] Error at onComplete()`)
38
+ logger.error(e)
39
+ }
40
+ }
41
+ },
42
+
43
+ // Do not start yet
44
+ false,
45
+
46
+ config?.timezone || 'Europe/Madrid',
47
+ // config?.context || null,
48
+ // config?.runOnInit || null,
49
+ // config?.utcOffset || null,
50
+ // config?.unrefTimeout || null,
51
+ )
52
+ return job
53
+ }
@@ -0,0 +1,151 @@
1
+ import path from 'path'
2
+ import fs from 'fs'
3
+ import https from 'https'
4
+ //import fetch from 'node-fetch'
5
+ import { cyan, green, yellow } from 'tinguir'
6
+
7
+ const _IPSUM_DEF_FOLDER = '/var/ipsum'
8
+ const _IPSUM_FILE_NAME = 'ipsum.txt'
9
+ const _IPSUM_REMOTE_FILE = 'https://raw.githubusercontent.com/stamparm/ipsum/master/ipsum.txt'
10
+ const _IPSUM_NLISTS = 1
11
+
12
+ /*function ipsum_download_file(callback, remote_file = _IPSUM_REMOTE_FILE) {
13
+ fetch(remote_file).then(response => {
14
+ response.text().then((content) => {
15
+ callback(content)
16
+ })
17
+ })
18
+ }*/
19
+
20
+
21
+ export function ipsum_download_file(callback, remote_file = _IPSUM_REMOTE_FILE, logger) {
22
+ const lerr = logger ? logger.error : console.error
23
+
24
+ https.get(remote_file, (res) => {
25
+ const data = []
26
+ res.on('data', (chunk) => {
27
+ data.push(chunk)
28
+ }).on('end', () => {
29
+ let content = Buffer.concat(data).toString()
30
+ callback(content)
31
+ })
32
+ }).on('error', (err) => {
33
+ lerr(`[cron][${cyan('IPsum')}] Error downloading remote file (${err.toString()})`)
34
+ callback('')
35
+ })
36
+
37
+ }
38
+
39
+ function _ipsum_ips_from_content(content, logger) {
40
+ const lerr = logger ? logger.error : console.error
41
+
42
+ if (! content) {
43
+ return []
44
+ }
45
+
46
+ try {
47
+ let ips = []
48
+ content
49
+ .split('\n')
50
+ .filter(line => line.indexOf('#') < 0)
51
+ .map(line => {
52
+ const [ip, nlists] = line.split('\t')
53
+ if (parseInt(nlists) >= _IPSUM_NLISTS) {
54
+ ips.push(ip)
55
+ }
56
+ })
57
+ return ips
58
+ } catch(error) {
59
+ lerr(`[cron][${cyan('IPsum')}] Error getting IPs from content`)
60
+ return []
61
+ }
62
+ }
63
+
64
+ function _ipsum_ips_from_file(folder= _IPSUM_DEF_FOLDER, logger) {
65
+ const lerr = logger ? logger.error : console.error
66
+
67
+ if (! fs.existsSync(folder)) {
68
+ if (logger) {
69
+ lerr(`[cron][${cyan('IPsum')}] Folder ${folder} does not exist`)
70
+ }
71
+ return []
72
+ }
73
+
74
+ const local_path = path.join(folder, _IPSUM_FILE_NAME)
75
+
76
+ if (! fs.existsSync(local_path)) {
77
+ if (logger) {
78
+ lerr(`[cron][${cyan('IPsum')}] File ${local_path} does not exist`)
79
+ }
80
+ return []
81
+ }
82
+
83
+ const content = fs.readFileSync(local_path, {encoding:'utf8'})
84
+ return _ipsum_ips_from_content(content, logger)
85
+ }
86
+
87
+
88
+ function ipsum_update(folder= _IPSUM_DEF_FOLDER, callback, logger) {
89
+ const lerr = logger ? logger.error : console.error
90
+ const ldbg = logger ? logger.debug : console.log
91
+
92
+ if (! fs.existsSync(folder)) {
93
+ lerr(`[cron][${cyan('IPsum')}] Folder ${folder} does not exist`)
94
+ return
95
+ }
96
+
97
+ try {
98
+ ldbg(`[cron][${cyan('IPsum')}] Updating file...`)
99
+
100
+ ipsum_download_file((content) => {
101
+
102
+ const local_path = path.join(folder, _IPSUM_FILE_NAME)
103
+ fs.writeFileSync(local_path, content, {encoding:'utf8', flag:'w'})
104
+
105
+ const ntot = content.split('\n').length
106
+ const ips = _ipsum_ips_from_content(content, logger)
107
+ const nfilt = ips.length
108
+ ldbg(`[cron][${cyan('IPsum')}] File downloaded. ${ntot} ips on the list (${nfilt} appearing in ${_IPSUM_NLISTS} or more lists)`)
109
+
110
+ if (callback) {
111
+ callback(ips)
112
+ }
113
+ })
114
+
115
+ } catch(error) {
116
+ lerr(`[cron][${cyan('IPsum')}] Error ${error} updating the file`)
117
+ }
118
+ }
119
+
120
+
121
+ export function ipsum_config() {
122
+ return {
123
+ name: 'IPsum',
124
+ cronTime: '0 0 * * *',
125
+ onTick: (miolo, _onCompleted) => {
126
+ const folder = miolo.config.http.ratelimit.ipsum_folder || _IPSUM_DEF_FOLDER
127
+ ipsum_update(folder, (ips) => {
128
+ miolo.logger.debug(`[cron][${cyan('IPsum')}] File downloaded. ${green(ips.length)} ips will be ${yellow('blacklisted')}!`)
129
+ }, miolo.logger)
130
+ },
131
+ start: true
132
+ }
133
+ }
134
+
135
+ export function ipsum_read_ips(folder = _IPSUM_DEF_FOLDER, callback, logger) {
136
+ const ldbg = logger ? logger.debug : console.log
137
+ const lwarn = logger ? logger.warn : console.log
138
+
139
+ const ips = _ipsum_ips_from_file(folder, logger)
140
+ if (ips.length > 0) {
141
+ if (callback) {
142
+ callback(ips)
143
+ }
144
+ ldbg(`[cron][${cyan('IPsum')}] File contains ${ips.length} ips`)
145
+ return ips
146
+ } else {
147
+ lwarn(`[cron][${cyan('IPsum')}] File is empty. Launching update...`)
148
+ ipsum_update(folder, callback, logger)
149
+ return []
150
+ }
151
+ }
@@ -0,0 +1,56 @@
1
+ import os from 'os'
2
+ import diskspace from 'diskspace'
3
+ import { cyan, green, yellow } from 'tinguir'
4
+
5
+ function _toMB(bytes) {
6
+ if (!bytes) return 0
7
+ return bytes/1000000
8
+ }
9
+
10
+
11
+ function _toGB(kbytes) {
12
+ if (!kbytes) return 0
13
+ return kbytes/1000000
14
+ }
15
+
16
+
17
+
18
+ function _sys_check_and_log(logger) {
19
+
20
+
21
+ const used= Math.round(_toMB(os.freemem()), 2)
22
+ const total= Math.round(_toMB(os.totalmem()), 2)
23
+ const perc= Math.round( (used*100)/total, 2)
24
+
25
+ if (perc>80) {
26
+ logger.error(`[cron][${cyan('SysCheck')}] RAM ${yellow(used)} MB used of ${green(total)} MB (${yellow(perc)} %)`)
27
+ } else {
28
+ logger.info(`[cron][${cyan('SysCheck')}] RAM ${yellow(used)} MB used of ${green(total)} MB (${yellow(perc)} %)`)
29
+ }
30
+
31
+
32
+ diskspace.check('/', function (err, result)
33
+ {
34
+ const used = Math.round(_toGB(result.used), 2)
35
+ const total= Math.round(_toGB(result.total), 2)
36
+ const free = Math.round(_toGB(result.free), 2)
37
+
38
+ if (free<1) {
39
+ logger.error(`[cron][${cyan('SysCheck')}] DISK ${yellow(used)} GB used of ${green(total)} GB (${yellow(free)} GB free)`)
40
+ } else {
41
+ logger.info(`[cron][${cyan('SysCheck')}] DISK ${yellow(used)} GB used of ${green(total)} GB (${yellow(free)} GB free)`)
42
+ }
43
+ });
44
+ }
45
+
46
+
47
+ export function sys_check_config() {
48
+ return {
49
+ name: 'SysCheck',
50
+ cronTime: '0,15,30,45 * * * *',
51
+ onTick: (miolo, _onCompleted) => {
52
+ _sys_check_and_log(miolo.logger)
53
+ },
54
+ start: true
55
+ }
56
+ }
@@ -0,0 +1,2 @@
1
+
2
+ export {init_emailer_transporter} from './transporter.mjs'
@@ -0,0 +1,54 @@
1
+ import { v4 as uuidv4 } from 'uuid'
2
+
3
+ export let EMAIL_QUEUE = {}
4
+
5
+ export async function email_queue_an_email(email, logger= undefined) {
6
+ const _loge = logger?.info || console.error
7
+
8
+ try {
9
+ const id = uuidv4()
10
+ const emailData = { id, ...email, sent: false }
11
+ //await client.rPush('email_queue', JSON.stringify(emailData))
12
+ EMAIL_QUEUE[id] = emailData
13
+ return { ok: true, id }
14
+
15
+ } catch (error) {
16
+ _loge(`[emailer] Error al guardar en la cola: ${error}`)
17
+ return { ok: false, id: null, error }
18
+ }
19
+ }
20
+
21
+ export function email_queue_pop_pendings(logger= undefined) {
22
+
23
+ let grouped = {}
24
+
25
+ Object.values(EMAIL_QUEUE)
26
+ .filter(e => !e.sent)
27
+ .forEach(({ id: eid, from, to, subject }) => {
28
+ const key = `${from}_${to}_${subject}`
29
+
30
+ if (!grouped[key]) {
31
+ grouped[key] = { to_subject_key: key, from, to, subject, count: 0, ids: [] }
32
+ }
33
+
34
+ grouped[key].count++
35
+ grouped[key].ids.push(eid)
36
+
37
+ // Mark as sent
38
+ try {
39
+ EMAIL_QUEUE[eid].sent= true
40
+ } catch(_) {}
41
+ })
42
+
43
+
44
+ return Object.values(grouped)
45
+ }
46
+
47
+ export function email_queue_remove_ids(eids, logger= undefined) {
48
+ eids.forEach((eid) => {
49
+
50
+ delete EMAIL_QUEUE[eid]
51
+ })
52
+ }
53
+
54
+
@@ -0,0 +1,149 @@
1
+ import nodemailer from 'nodemailer'
2
+ import { email_queue_an_email, email_queue_pop_pendings, email_queue_remove_ids } from './queue.mjs'
3
+
4
+
5
+ const _logi = (logger, msg) => logger?.info ? logger.info(msg) : console.info(msg)
6
+ const _loge = (logger, msg) => logger?.error ? logger.info(msg) : console.error(msg)
7
+
8
+
9
+ export function _init_emailer_transporter({options, defaults, silent}) {
10
+
11
+ const nmailer = nodemailer.createTransport(options, defaults)
12
+
13
+ function verify_emailer(logger= undefined) {
14
+ _logi(logger, '[emailer] Verifying...')
15
+ nmailer.verify(function(error, _success) {
16
+ if (error) {
17
+ _loge(logger, '[emailer] Verifying ERROR')
18
+ _loge(error)
19
+ } else {
20
+ _logi(logger, '[emailer] Verifyed OK: Server is ready to take our messages')
21
+ }
22
+ })
23
+ }
24
+
25
+ async function send_email(mail, logger= undefined) {
26
+ if (silent === true) {
27
+ _logi(logger, '[emailer] *********************************')
28
+ _logi(logger, '[emailer] This mail will not be send (emailing is disabled):')
29
+ _logi(mail)
30
+ _logi(logger, '[emailer] *********************************')
31
+
32
+ return {
33
+ ok: true,
34
+ silent: true,
35
+ error: undefined,
36
+ messageId: undefined
37
+ }
38
+ } else {
39
+ try {
40
+ let info = await nmailer.sendMail(mail)
41
+ info.ok = info?.messageId ? true : false
42
+
43
+ return info
44
+ } catch(error) {
45
+ _loge(logger, `[emailer] Error sending email: ${mail?.from || ''} => ${mail?.to || ''} (${mail?.subject || ''})`)
46
+
47
+ return {
48
+ ok: false,
49
+ silent: false,
50
+ error,
51
+ messageId: undefined
52
+ }
53
+ }
54
+ }
55
+ }
56
+
57
+ function queue_email(mail, logger= undefined) {
58
+ if (silent === true) {
59
+ _logi(logger, '[emailer] *********************************')
60
+ _logi(logger, '[emailer] This mail will not be send (emailing is disabled):')
61
+ _logi(mail)
62
+ _logi(logger, '[emailer] *********************************')
63
+
64
+ return {
65
+ ok: true,
66
+ silent: true,
67
+ error: undefined,
68
+ messageId: undefined
69
+ }
70
+ } else {
71
+ try {
72
+ const q = email_queue_an_email(mail, logger)
73
+ _logi(logger, `[emailer] Queued email: ${mail?.from || ''} => ${mail?.to || ''} (${mail?.subject || ''})`)
74
+
75
+ return {
76
+ ok: q.ok,
77
+ silent: false,
78
+ error: q.error,
79
+ messageId: q.id
80
+ }
81
+ } catch(error) {
82
+ _loge(logger, `[emailer] Error queueing email: ${mail?.from || ''} => ${mail?.to || ''} (${mail?.subject || ''})`)
83
+
84
+ return {
85
+ ok: false,
86
+ silent: false,
87
+ error,
88
+ messageId: undefined
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ async function queue_send_emails(logger= undefined) {
95
+ const pending = email_queue_pop_pendings(logger)
96
+ if (pending.length <= 0) {
97
+ return
98
+ }
99
+ _logi(logger, `[emailer] Sending emails queue (${pending.length} emails)`)
100
+
101
+ // const emailString = await client.lPop('email_queue')
102
+ // if (!emailString) break
103
+ // const email = JSON.parse(emailString)
104
+
105
+ for (const email of pending) {
106
+ if (email.count > 1) {
107
+ _logi(logger, `[emailer] Sending queued and stacked email [${email.subject}](x${email.count})...`)
108
+ email.subject = `${email.subject} (x${email.count})`
109
+ } else {
110
+ _logi(logger, `[emailer] Sending queued email [${email.subject}]...`)
111
+ }
112
+
113
+ send_email(email).then((res) => {
114
+ _logi(logger, `[emailer] Queued email [${email.subject}]sent ${res.ok ? 'OK' : 'NOT OK'}`)
115
+ if (res.ok) {
116
+ delete email_queue_remove_ids(email.ids, logger)
117
+ }
118
+ })
119
+ }
120
+ _logi(logger, `[emailer] Sent emails ${pending.length} from queue`)
121
+ }
122
+
123
+ const emailer= {
124
+ send: send_email,
125
+ verify: verify_emailer,
126
+ queue_email,
127
+ queue_send_emails,
128
+ options,
129
+ defaults,
130
+ silent
131
+ }
132
+ return emailer
133
+ }
134
+
135
+
136
+
137
+ let _cache = {}
138
+
139
+ export function init_emailer_transporter({options, defaults, silent}, logger= undefined) {
140
+
141
+ const ckey = JSON.stringify({options, defaults, silent})
142
+ if (ckey in _cache) {
143
+ return _cache[ckey]
144
+ }
145
+
146
+ _cache[ckey] = _init_emailer_transporter({options, defaults, silent}, logger)
147
+ return _cache[ckey]
148
+
149
+ }
@@ -0,0 +1,66 @@
1
+ import fs from 'fs'
2
+ import {Reader} from '@maxmind/geoip2-node'
3
+
4
+ let _geoip_reader = undefined
5
+
6
+ const _geoip_def_local_ips= [
7
+ '127.0.0.1',
8
+ '::1:',
9
+ '172.19.0.1' // Docker inner - probably healthcheck
10
+ ]
11
+
12
+ function _geoip_is_local(ip, local_ips = []) {
13
+ // if (process.env.NODE_ENV == 'development') {
14
+ // return true
15
+ // }
16
+
17
+ const all_local_ips = [
18
+ ..._geoip_def_local_ips,
19
+ ...local_ips || []
20
+ ]
21
+ return all_local_ips.indexOf(ip) >= 0
22
+ }
23
+
24
+
25
+
26
+ function _geoip_init(db = '/var/lib/GeoIP/GeoLite2-City.mmdb', logger= console) {
27
+ try {
28
+ if (_geoip_reader != undefined) {
29
+ return _geoip_reader
30
+ }
31
+ const dbBuffer = fs.readFileSync(db)
32
+ _geoip_reader = Reader.openBuffer(dbBuffer)
33
+ return _geoip_reader
34
+ } catch(error) {
35
+ logger.error(`[geoip] Error initing:`)
36
+ logger.error(error)
37
+ return undefined
38
+ }
39
+ }
40
+
41
+ export const geoip_localize_ip= (ip, config, logger= console) => {
42
+ if (_geoip_is_local(ip, config?.local_ips)) {
43
+ return {
44
+ local: true,
45
+ country: '',
46
+ city: '',
47
+ }
48
+ }
49
+
50
+ try {
51
+ const reader = _geoip_init(config?.db, logger)
52
+ const resp= reader.city(ip)
53
+ return {
54
+ country: resp.country.isoCode,
55
+ city : resp.city?.names?.en
56
+ }
57
+ } catch(error) {
58
+ logger.error(`[geoip] Error localizing IP ${ip} (not in the database)`)
59
+ //logger.error(error)
60
+ }
61
+
62
+ return {
63
+ country: '',
64
+ city: '',
65
+ }
66
+ }