@technomoron/mail-magic 1.0.8 → 1.0.11

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 (45) hide show
  1. package/CHANGES +27 -0
  2. package/README.md +9 -3
  3. package/TUTORIAL.MD +1 -0
  4. package/dist/api/assets.js +153 -0
  5. package/dist/api/forms.js +2 -0
  6. package/dist/api/mailer.js +1 -0
  7. package/dist/bin/mail-magic.js +63 -0
  8. package/dist/index.js +61 -2
  9. package/dist/store/envloader.js +3 -3
  10. package/dist/store/store.js +66 -1
  11. package/package.json +17 -3
  12. package/.do-realease.sh +0 -49
  13. package/.editorconfig +0 -9
  14. package/.env-dist +0 -71
  15. package/.prettierrc +0 -14
  16. package/.vscode/extensions.json +0 -3
  17. package/.vscode/settings.json +0 -22
  18. package/config-example/form-template/default.njk +0 -102
  19. package/config-example/forms.config.json +0 -8
  20. package/config-example/init-data.json +0 -33
  21. package/config-example/tx-template/default.njk +0 -107
  22. package/ecosystem.config.cjs +0 -42
  23. package/eslint.config.mjs +0 -196
  24. package/lintconfig.cjs +0 -81
  25. package/src/api/assets.ts +0 -92
  26. package/src/api/forms.ts +0 -237
  27. package/src/api/mailer.ts +0 -269
  28. package/src/index.ts +0 -71
  29. package/src/models/db.ts +0 -112
  30. package/src/models/domain.ts +0 -72
  31. package/src/models/form.ts +0 -209
  32. package/src/models/init.ts +0 -240
  33. package/src/models/txmail.ts +0 -206
  34. package/src/models/user.ts +0 -79
  35. package/src/server.ts +0 -27
  36. package/src/store/envloader.ts +0 -109
  37. package/src/store/store.ts +0 -118
  38. package/src/types.ts +0 -39
  39. package/src/util.ts +0 -137
  40. package/tests/fixtures/certs/test.crt +0 -19
  41. package/tests/fixtures/certs/test.key +0 -28
  42. package/tests/helpers/test-setup.ts +0 -316
  43. package/tests/mail-magic.test.ts +0 -154
  44. package/tsconfig.json +0 -14
  45. package/vitest.config.ts +0 -11
package/.env-dist DELETED
@@ -1,71 +0,0 @@
1
- # Specifies the environment in which the app is running Possible values: development, production, staging
2
- NODE_ENV=development
3
-
4
- # Defines the port on which the app listens. Default 3780 [number]
5
- API_PORT=3776
6
-
7
- # Sets the local IP address for the API to listen at
8
- API_HOST=0.0.0.0
9
-
10
- # Reload init-data.db automatically on change [boolean]
11
- DB_AUTO_RELOAD=true
12
-
13
- # Whether to force sync on table definitions (ALTER TABLE) [boolean]
14
- DB_FORCE_SYNC=false
15
-
16
- # Sets the public URL for the API (i.e. https://ml.example.com:3790)
17
- API_URL=http://localhost:3776
18
-
19
- # Enable the Swagger/OpenAPI endpoint [boolean]
20
- SWAGGER_ENABLED=false
21
-
22
- # Path to expose the Swagger/OpenAPI spec (default: /api/swagger when enabled)
23
- SWAGGER_PATH=
24
-
25
- # Route prefix exposed for config assets
26
- ASSET_ROUTE=/asset
27
-
28
- # Path to directory where config files are located
29
- CONFIG_PATH=./config/
30
-
31
- # Database username for API database
32
- DB_USER=
33
-
34
- # Password for API database
35
- DB_PASS=
36
-
37
- # Name of API database. Filename for sqlite3, database name for others
38
- DB_NAME=maildata
39
-
40
- # Host of API database
41
- DB_HOST=localhost
42
-
43
- # Database type of WP database Possible values: sqlite
44
- DB_TYPE=sqlite
45
-
46
- # Log SQL statements [boolean]
47
- DB_LOG=false
48
-
49
- # Enable debug output, including nodemailer and API [boolean]
50
- DEBUG=false
51
-
52
- # Hostname of SMTP sending host
53
- SMTP_HOST=localhost
54
-
55
- # SMTP host server port [number]
56
- SMTP_PORT=587
57
-
58
- # Use secure connection to SMTP host (SSL/TSL) [boolean]
59
- SMTP_SECURE=false
60
-
61
- # Reject bad cert/TLS connection to SMTP host [boolean]
62
- SMTP_TLS_REJECT=false
63
-
64
- # Username for SMTP host
65
- SMTP_USER=
66
-
67
- # Password for SMTP host
68
- SMTP_PASSWORD=
69
-
70
- # Path for attached files
71
- UPLOAD_PATH=./uploads/
package/.prettierrc DELETED
@@ -1,14 +0,0 @@
1
- {
2
- "tabWidth": 4,
3
- "useTabs": true,
4
- "singleQuote": true,
5
- "semi": true,
6
- "trailingComma": "none",
7
- "bracketSpacing": true,
8
- "bracketSameLine": true,
9
- "arrowParens": "always",
10
- "printWidth": 120,
11
- "vueIndentScriptAndStyle": false,
12
- "htmlWhitespaceSensitivity": "ignore",
13
- "singleAttributePerLine": false
14
- }
@@ -1,3 +0,0 @@
1
- {
2
- "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
3
- }
@@ -1,22 +0,0 @@
1
- {
2
- "editor.formatOnSave": true,
3
- "editor.defaultFormatter": "esbenp.prettier-vscode",
4
- "[javascript]": {
5
- "editor.defaultFormatter": "esbenp.prettier-vscode"
6
- },
7
- "[typescript]": {
8
- "editor.defaultFormatter": "esbenp.prettier-vscode"
9
- },
10
- "[vue]": {
11
- "editor.defaultFormatter": "esbenp.prettier-vscode"
12
- },
13
- "[json]": {
14
- "editor.defaultFormatter": "esbenp.prettier-vscode"
15
- },
16
- "editor.codeActionsOnSave": {
17
- "source.fixAll.eslint": "explicit"
18
- },
19
- "eslint.enable": true,
20
- "eslint.workingDirectories": ["./"],
21
- "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact", "vue"]
22
- }
@@ -1,102 +0,0 @@
1
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
- <html xmlns="http://www.w3.org/1999/xhtml">
3
- <html>
4
- <head>
5
- <meta charset="utf-8" />
6
- <title>Form Submission Details</title>
7
- <style>
8
- body {
9
- font-family: Arial, sans-serif;
10
- line-height: 1.6;
11
- color: #333;
12
- }
13
- h3 {
14
- color: #444;
15
- }
16
- table {
17
- width: 100%;
18
- border-collapse: collapse;
19
- margin-bottom: 20px;
20
- }
21
- th,
22
- td {
23
- border: 1px solid #ddd;
24
- padding: 8px;
25
- text-align: left;
26
- }
27
- th {
28
- background-color: #f5f5f5;
29
- }
30
- </style>
31
- </head>
32
- <body>
33
- <h3>Form Fields</h3>
34
- {% if formFields %}
35
- <table>
36
- <thead>
37
- <tr>
38
- <th>Field</th>
39
- <th>Value</th>
40
- </tr>
41
- </thead>
42
- <tbody>
43
- {% for field, value in formFields %}
44
- <tr>
45
- <td>{{ field }}</td>
46
- <td>{{ value }}</td>
47
- </tr>
48
- {% endfor %}
49
- </tbody>
50
- </table>
51
- {% else %}
52
- <p>No form fields submitted.</p>
53
- {% endif %}
54
-
55
- <h3>File Metadata</h3>
56
- {% if files and files.length %}
57
- <table>
58
- <thead>
59
- <tr>
60
- <th>Filename</th>
61
- <th>Path</th>
62
- </tr>
63
- </thead>
64
- <tbody>
65
- {% for file in files %}
66
- <tr>
67
- <td>{{ file.originalname }}</td>
68
- <td>{{ file.path }}</td>
69
- </tr>
70
- {% endfor %}
71
- </tbody>
72
- </table>
73
- {% else %}
74
- <p>No files attached.</p>
75
- {% endif %}
76
-
77
- <h3>Request Metadata</h3>
78
- {% if _meta_ %}
79
- <table>
80
- <tbody>
81
- <tr>
82
- <th>Client IP</th>
83
- <td>{{ _meta_.client_ip | default('Unknown') }}</td>
84
- </tr>
85
- <tr>
86
- <th>Received At</th>
87
- <td>{{ _meta_.received_at | default('Unknown') }}</td>
88
- </tr>
89
- {% if _meta_.ip_chain and _meta_.ip_chain.length %}
90
- <tr>
91
- <th>IP Chain</th>
92
- <td>{{ _meta_.ip_chain | join(', ') }}</td>
93
- </tr>
94
- {% endif %}
95
- </tbody>
96
- </table>
97
- {% else %}
98
- <p>No metadata provided.</p>
99
- {% endif %}
100
- </body>
101
- </html>
102
- </html>
@@ -1,8 +0,0 @@
1
- {
2
- "testform": {
3
- "rcpt": "someone@example.com",
4
- "sender": "Mother of All Forms <noreply@example.com>",
5
- "subject": "A New Form Has Been Gifted You",
6
- "template": "default.njk"
7
- }
8
- }
@@ -1,33 +0,0 @@
1
- {
2
- "user": [
3
- {
4
- "user_id": 1,
5
- "idname": "testuser",
6
- "token": "SomethingWeirdWhoKnows",
7
- "name": "Test Inc.",
8
- "email": "noreply@example.com"
9
- }
10
- ],
11
- "domain": [
12
- {
13
- "domain_id": 1,
14
- "user_id": 1,
15
- "domain": "mail.example.com",
16
- "sender": "Some User <noreply@example.com>"
17
- }
18
- ],
19
- "template": [
20
- {
21
- "template_id": 1,
22
- "user_id": 1,
23
- "domain_id": 1,
24
- "name": "test",
25
- "locale": "",
26
- "template": "",
27
- "template_file": "",
28
- "sender": "someone@mail.example.com",
29
- "subject": "Example Mail",
30
- "slug": "test"
31
- }
32
- ]
33
- }
@@ -1,107 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <title>Email Details</title>
6
- <style>
7
- body {
8
- font-family: Arial, sans-serif;
9
- color: #333;
10
- line-height: 1.6;
11
- }
12
- h2 {
13
- margin-top: 30px;
14
- border-bottom: 1px solid #ccc;
15
- }
16
- table {
17
- width: 100%;
18
- border-collapse: collapse;
19
- margin-top: 10px;
20
- }
21
- th,
22
- td {
23
- padding: 8px;
24
- border: 1px solid #ddd;
25
- }
26
- th {
27
- background: #f0f0f0;
28
- }
29
- </style>
30
- </head>
31
- <body>
32
- <h2>Email Recipient</h2>
33
- <p>
34
- <strong>To:</strong>
35
- {{ _rcpt_email_ }}
36
- </p>
37
-
38
- <h2>Template Variables</h2>
39
-
40
- {% if _vars_ %}
41
- <table>
42
- <thead>
43
- <tr>
44
- <th>Key</th>
45
- <th>Value</th>
46
- </tr>
47
- </thead>
48
- <tbody>
49
- {% for key, value in _vars_ %}
50
- <tr>
51
- <td>{{ key }}</td>
52
- <td>{{ value }}</td>
53
- </tr>
54
- {% endfor %}
55
- </tbody>
56
- </table>
57
- {% else %}
58
- <p>No variables found.</p>
59
- {% endif %}
60
-
61
- <h2>Attachments</h2>
62
- {% if _attachments_ and _attachments_ | length > 0 %}
63
- <table>
64
- <thead>
65
- <tr>
66
- <th>Label</th>
67
- <th>Filename</th>
68
- </tr>
69
- </thead>
70
- <tbody>
71
- {% for label, filename in _attachments_ %}
72
- <tr>
73
- <td>{{ label }}</td>
74
- <td>{{ filename }}</td>
75
- </tr>
76
- {% endfor %}
77
- </tbody>
78
- </table>
79
- {% else %}
80
- <p>No attachments included.</p>
81
- {% endif %}
82
-
83
- <h2>Request Metadata</h2>
84
- {% if _meta_ %}
85
- <table>
86
- <tbody>
87
- <tr>
88
- <th>Client IP</th>
89
- <td>{{ _meta_.client_ip | default('Unknown') }}</td>
90
- </tr>
91
- <tr>
92
- <th>Received At</th>
93
- <td>{{ _meta_.received_at | default('Unknown') }}</td>
94
- </tr>
95
- {% if _meta_.ip_chain and _meta_.ip_chain.length %}
96
- <tr>
97
- <th>IP Chain</th>
98
- <td>{{ _meta_.ip_chain | join(', ') }}</td>
99
- </tr>
100
- {% endif %}
101
- </tbody>
102
- </table>
103
- {% else %}
104
- <p>No request metadata provided.</p>
105
- {% endif %}
106
- </body>
107
- </html>
@@ -1,42 +0,0 @@
1
- const tplsrv = 'mail-magic';
2
-
3
- module.exports = {
4
- apps: [
5
- {
6
- name: tplsrv,
7
- script: 'npm',
8
- args: 'run start',
9
- cwd: `/root/deploy/${tplsrv}/source`,
10
- env: {
11
- NODE_ENV: 'production'
12
- }
13
- },
14
- {
15
- name: 'listmonk',
16
- script: './listmonk',
17
- cwd: '/var/www/ml.yesmedia.no',
18
- exec_mode: 'fork',
19
- instances: 1,
20
- autorestart: true,
21
- watch: false,
22
- max_memory_restart: '1G',
23
- user: 'listmonk',
24
- group: 'listmonk',
25
- env: {
26
- NODE_ENV: 'production'
27
- }
28
- }
29
- ],
30
- deploy: {
31
- 'mail-magic': {
32
- user: 'root',
33
- host: 'localhost',
34
- ref: 'origin/main',
35
- path: `/root/deploy/${tplsrv}`,
36
- repo: `git@github.com:technomoron/${tplsrv}`,
37
- 'pre-deploy-local': '',
38
- 'pre-setup': '',
39
- 'post-deploy': `cd /root/deploy/${tplsrv}/source && pnpm install && pnpm upgrade && pnpm run build && pm2 start /root/deploy/ecosystem.config.cjs`
40
- }
41
- }
42
- };
package/eslint.config.mjs DELETED
@@ -1,196 +0,0 @@
1
- import tsPlugin from '@typescript-eslint/eslint-plugin';
2
- import tsParser from '@typescript-eslint/parser';
3
- import eslintConfigPrettier from 'eslint-config-prettier/flat';
4
- import pluginImport from 'eslint-plugin-import';
5
- import jsoncParser from 'jsonc-eslint-parser';
6
- const TS_FILE_GLOBS = ['**/*.{ts,tsx,mts,cts,vue}'];
7
- const TS_PLUGIN_FILE_GLOBS = ['**/*.{ts,tsx,mts,cts,js,mjs,cjs,vue}'];
8
- const VUE_FILE_GLOBS = ['**/*.vue'];
9
-
10
- const { hasVueSupport, pluginVue, vueTypeScriptConfigs } = await loadVueSupport();
11
- const scopedVueTypeScriptConfigs = hasVueSupport
12
- ? scopeVueConfigs(vueTypeScriptConfigs).map(stripTypeScriptPlugin)
13
- : [];
14
- const vueSpecificBlocks = hasVueSupport
15
- ? [
16
- ...scopedVueTypeScriptConfigs,
17
- {
18
- files: VUE_FILE_GLOBS,
19
- plugins: {
20
- vue: pluginVue
21
- },
22
- rules: {
23
- 'vue/html-indent': 'off', // Let Prettier handle indentation
24
- 'vue/max-attributes-per-line': 'off', // Let Prettier handle line breaks
25
- 'vue/first-attribute-linebreak': 'off', // Let Prettier handle attribute positioning
26
- 'vue/singleline-html-element-content-newline': 'off',
27
- 'vue/html-self-closing': [
28
- 'error',
29
- {
30
- html: {
31
- void: 'always',
32
- normal: 'always',
33
- component: 'always'
34
- }
35
- }
36
- ],
37
- 'vue/multi-word-component-names': 'off', // Disable multi-word name restriction
38
- 'vue/attribute-hyphenation': ['error', 'always']
39
- }
40
- }
41
- ]
42
- : [];
43
-
44
- export default [
45
- {
46
- ignores: [
47
- 'node_modules',
48
- 'dist',
49
- '.output',
50
- '.nuxt',
51
- '.netlify',
52
- 'node_modules/.netlify',
53
- '4000/.nuxt',
54
- 'coverage',
55
- '**/*.d.ts',
56
- 'configure-eslint.cjs',
57
- 'configure-eslint.js',
58
- '*.config.js',
59
- 'public'
60
- ]
61
- },
62
- {
63
- files: TS_PLUGIN_FILE_GLOBS,
64
- plugins: {
65
- '@typescript-eslint': tsPlugin
66
- }
67
- },
68
- ...vueSpecificBlocks,
69
- {
70
- files: ['**/*.json'],
71
- languageOptions: {
72
- parser: jsoncParser
73
- },
74
- rules: {
75
- quotes: ['error', 'double'] // Enforce double quotes in JSON
76
- }
77
- },
78
- {
79
- files: ['**/*.{ts,mts,tsx,js,mjs,cjs}'],
80
- languageOptions: {
81
- parser: tsParser,
82
- parserOptions: {
83
- ecmaVersion: 2023,
84
- sourceType: 'module',
85
- extraFileExtensions: ['.vue']
86
- },
87
- globals: {
88
- RequestInit: 'readonly',
89
- process: 'readonly',
90
- Capacitor: 'readonly',
91
- chrome: 'readonly'
92
- }
93
- },
94
- plugins: {
95
- import: pluginImport
96
- },
97
- rules: {
98
- 'import/order': [
99
- 'error',
100
- {
101
- groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
102
- 'newlines-between': 'always',
103
- alphabetize: { order: 'asc', caseInsensitive: true }
104
- }
105
- ],
106
- '@typescript-eslint/no-explicit-any': ['warn'],
107
- '@typescript-eslint/no-unused-vars': ['warn'],
108
- '@typescript-eslint/no-require-imports': 'off'
109
- }
110
- },
111
- {
112
- ...eslintConfigPrettier
113
- }
114
- ];
115
-
116
- async function loadVueSupport() {
117
- try {
118
- const [vuePluginModule, vueConfigModule] = await Promise.all([
119
- import('eslint-plugin-vue'),
120
- import('@vue/eslint-config-typescript')
121
- ]);
122
-
123
- const pluginVue = unwrapDefault(vuePluginModule);
124
- const { defineConfigWithVueTs, vueTsConfigs } = vueConfigModule;
125
- const configs = defineConfigWithVueTs(vueTsConfigs.recommended);
126
-
127
- return {
128
- hasVueSupport: Boolean(pluginVue && configs.length),
129
- pluginVue,
130
- vueTypeScriptConfigs: configs
131
- };
132
- } catch (error) {
133
- if (isModuleNotFoundError(error)) {
134
- return {
135
- hasVueSupport: false,
136
- pluginVue: null,
137
- vueTypeScriptConfigs: []
138
- };
139
- }
140
-
141
- throw error;
142
- }
143
- }
144
-
145
- function scopeVueConfigs(configs) {
146
- return configs.map((config) => {
147
- const files = config.files ?? [];
148
- const referencesOnlyVueFiles = files.length > 0 && files.every((pattern) => pattern.includes('.vue'));
149
- const hasVuePlugin = Boolean(config.plugins?.vue);
150
-
151
- if (hasVuePlugin || referencesOnlyVueFiles) {
152
- return {
153
- ...config,
154
- files: VUE_FILE_GLOBS
155
- };
156
- }
157
-
158
- return {
159
- ...config,
160
- files: TS_FILE_GLOBS
161
- };
162
- });
163
- }
164
-
165
- function stripTypeScriptPlugin(config) {
166
- const { plugins = {}, ...rest } = config;
167
-
168
- if (!plugins['@typescript-eslint']) {
169
- return config;
170
- }
171
-
172
- const otherPlugins = { ...plugins };
173
- delete otherPlugins['@typescript-eslint'];
174
- const hasOtherPlugins = Object.keys(otherPlugins).length > 0;
175
-
176
- return {
177
- ...rest,
178
- ...(hasOtherPlugins ? { plugins: otherPlugins } : {})
179
- };
180
- }
181
-
182
- function unwrapDefault(module) {
183
- return module?.default ?? module;
184
- }
185
-
186
- function isModuleNotFoundError(error) {
187
- if (!error) {
188
- return false;
189
- }
190
-
191
- if (error.code === 'ERR_MODULE_NOT_FOUND' || error.code === 'MODULE_NOT_FOUND') {
192
- return true;
193
- }
194
-
195
- return typeof error.message === 'string' && error.message.includes('Cannot find module');
196
- }
package/lintconfig.cjs DELETED
@@ -1,81 +0,0 @@
1
- #!/usr/bin/env node
2
- const { execSync, spawnSync } = require('child_process');
3
- const fs = require('fs');
4
- const os = require('os');
5
- const path = require('path');
6
-
7
- const RELEASE_API_URL = 'https://api.github.com/repos/technomoron/vscode-eslint-defaults/releases/latest';
8
- const INSTALLER_ASSET_NAME = 'installer.tgz';
9
-
10
- async function fetch_json(url) {
11
- const response = await fetch(url, {
12
- headers: {
13
- 'User-Agent': 'vscode-eslint-defaults-lintconfig',
14
- Accept: 'application/vnd.github+json'
15
- }
16
- });
17
-
18
- if (!response.ok) {
19
- throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`);
20
- }
21
-
22
- return response.json();
23
- }
24
-
25
- async function download_asset(url, destination) {
26
- const response = await fetch(url, {
27
- headers: {
28
- 'User-Agent': 'vscode-eslint-defaults-lintconfig'
29
- }
30
- });
31
-
32
- if (!response.ok) {
33
- throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
34
- }
35
-
36
- const buffer = Buffer.from(await response.arrayBuffer());
37
- fs.writeFileSync(destination, buffer);
38
- }
39
-
40
- async function run() {
41
- const release = await fetch_json(RELEASE_API_URL);
42
- const assets = Array.isArray(release.assets) ? release.assets : [];
43
- const asset = assets.find((item) => item.name === INSTALLER_ASSET_NAME);
44
-
45
- if (!asset?.browser_download_url) {
46
- throw new Error('Latest release does not include installer.tgz.');
47
- }
48
-
49
- const temp_dir = fs.mkdtempSync(path.join(os.tmpdir(), 'lintconfig-'));
50
- const tgz_path = path.join(temp_dir, INSTALLER_ASSET_NAME);
51
- const args = process.argv.slice(2);
52
- let exit_code = 0;
53
-
54
- try {
55
- await download_asset(asset.browser_download_url, tgz_path);
56
- execSync(`tar -xzf "${tgz_path}" -C "${process.cwd()}"`, { stdio: 'inherit' });
57
-
58
- const configure_path = path.join(process.cwd(), 'configure-eslint.cjs');
59
- if (!fs.existsSync(configure_path)) {
60
- throw new Error('configure-eslint.cjs not found after extraction.');
61
- }
62
-
63
- const result = spawnSync(process.execPath, [configure_path, ...args], { stdio: 'inherit' });
64
- if (result.status !== 0) {
65
- exit_code = result.status ?? 1;
66
- } else {
67
- fs.unlinkSync(configure_path);
68
- }
69
- } finally {
70
- fs.rmSync(temp_dir, { recursive: true, force: true });
71
- }
72
-
73
- if (exit_code !== 0) {
74
- process.exit(exit_code);
75
- }
76
- }
77
-
78
- run().catch((error) => {
79
- console.error(error.message || error);
80
- process.exit(1);
81
- });