@technomoron/mail-magic 1.0.4
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/.do-realease.sh +10 -0
- package/.editorconfig +9 -0
- package/.env-dist +62 -0
- package/.prettierrc +14 -0
- package/.vscode/extensions.json +15 -0
- package/.vscode/settings.json +123 -0
- package/CHANGES +25 -0
- package/README.md +63 -0
- package/config-example/form-template/default.njk +102 -0
- package/config-example/forms.config.json +8 -0
- package/config-example/init-data.json +33 -0
- package/config-example/tx-template/default.njk +107 -0
- package/dist/api/forms.js +175 -0
- package/dist/api/mailer.js +213 -0
- package/dist/index.js +50 -0
- package/dist/models/db.js +99 -0
- package/dist/models/domain.js +58 -0
- package/dist/models/form.js +168 -0
- package/dist/models/init.js +176 -0
- package/dist/models/txmail.js +167 -0
- package/dist/models/user.js +65 -0
- package/dist/server.js +22 -0
- package/dist/store/envloader.js +116 -0
- package/dist/store/store.js +85 -0
- package/dist/types.js +1 -0
- package/dist/util.js +94 -0
- package/ecosystem.config.cjs +42 -0
- package/eslint.config.mjs +104 -0
- package/package.json +67 -0
- package/src/api/forms.ts +209 -0
- package/src/api/mailer.ts +242 -0
- package/src/index.ts +67 -0
- package/src/models/db.ts +112 -0
- package/src/models/domain.ts +72 -0
- package/src/models/form.ts +198 -0
- package/src/models/init.ts +237 -0
- package/src/models/txmail.ts +199 -0
- package/src/models/user.ts +79 -0
- package/src/server.ts +27 -0
- package/src/store/envloader.ts +117 -0
- package/src/store/store.ts +116 -0
- package/src/types.ts +39 -0
- package/src/util.ts +111 -0
- package/test1.sh +13 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { defineEnvOptions } from '@technomoron/env-loader';
|
|
2
|
+
export const envOptions = defineEnvOptions({
|
|
3
|
+
NODE_ENV: {
|
|
4
|
+
description: 'Specifies the environment in which the app is running',
|
|
5
|
+
options: ['development', 'production', 'staging'],
|
|
6
|
+
default: 'development'
|
|
7
|
+
},
|
|
8
|
+
API_PORT: {
|
|
9
|
+
description: 'Defines the port on which the app listens. Default 3780',
|
|
10
|
+
default: '3776',
|
|
11
|
+
type: 'number'
|
|
12
|
+
},
|
|
13
|
+
API_HOST: {
|
|
14
|
+
description: 'Sets the local IP address for the API to listen at',
|
|
15
|
+
default: '0.0.0.0'
|
|
16
|
+
},
|
|
17
|
+
DB_AUTO_RELOAD: {
|
|
18
|
+
description: 'Reload init-data.db automatically on change',
|
|
19
|
+
type: 'boolean',
|
|
20
|
+
default: true
|
|
21
|
+
},
|
|
22
|
+
DB_FORCE_SYNC: {
|
|
23
|
+
description: 'Whether to force sync on table definitions (ALTER TABLE)',
|
|
24
|
+
type: 'boolean',
|
|
25
|
+
default: false
|
|
26
|
+
},
|
|
27
|
+
API_URL: {
|
|
28
|
+
description: 'Sets the public URL for the API (i.e. https://ml.example.com:3790)',
|
|
29
|
+
default: 'http://localhost:3776'
|
|
30
|
+
},
|
|
31
|
+
CONFIG_PATH: {
|
|
32
|
+
description: 'Path to directory where config files are located',
|
|
33
|
+
default: './config/'
|
|
34
|
+
},
|
|
35
|
+
/*
|
|
36
|
+
SWAGGER_ENABLE: {
|
|
37
|
+
description: 'Enable Swagger API docs',
|
|
38
|
+
default: 'false',
|
|
39
|
+
type: 'boolean'
|
|
40
|
+
},
|
|
41
|
+
SWAGGER_PATH: {
|
|
42
|
+
description: 'Path for swagger api docs',
|
|
43
|
+
default: '/api-docs'
|
|
44
|
+
},
|
|
45
|
+
*/
|
|
46
|
+
/*
|
|
47
|
+
JWT_SECRET: {
|
|
48
|
+
description: 'Secret key for generating JWT access tokens',
|
|
49
|
+
required: true
|
|
50
|
+
},
|
|
51
|
+
JWT_REFRESH: {
|
|
52
|
+
description: 'Secret key for generating JWT refresh tokens',
|
|
53
|
+
required: true
|
|
54
|
+
},
|
|
55
|
+
*/
|
|
56
|
+
DB_USER: {
|
|
57
|
+
description: 'Database username for API database'
|
|
58
|
+
},
|
|
59
|
+
DB_PASS: {
|
|
60
|
+
description: 'Password for API database'
|
|
61
|
+
},
|
|
62
|
+
DB_NAME: {
|
|
63
|
+
description: 'Name of API database. Filename for sqlite3, database name for others',
|
|
64
|
+
default: 'maildata'
|
|
65
|
+
},
|
|
66
|
+
DB_HOST: {
|
|
67
|
+
description: 'Host of API database',
|
|
68
|
+
default: 'localhost'
|
|
69
|
+
},
|
|
70
|
+
DB_TYPE: {
|
|
71
|
+
description: 'Database type of WP database',
|
|
72
|
+
options: ['sqlite'],
|
|
73
|
+
default: 'sqlite'
|
|
74
|
+
},
|
|
75
|
+
DB_LOG: {
|
|
76
|
+
description: 'Log SQL statements',
|
|
77
|
+
default: 'false',
|
|
78
|
+
type: 'boolean'
|
|
79
|
+
},
|
|
80
|
+
DEBUG: {
|
|
81
|
+
description: 'Enable debug output, including nodemailer and API',
|
|
82
|
+
default: false,
|
|
83
|
+
type: 'boolean'
|
|
84
|
+
},
|
|
85
|
+
SMTP_HOST: {
|
|
86
|
+
description: 'Hostname of SMTP sending host',
|
|
87
|
+
default: 'localhost'
|
|
88
|
+
},
|
|
89
|
+
SMTP_PORT: {
|
|
90
|
+
description: 'SMTP host server port',
|
|
91
|
+
default: 587,
|
|
92
|
+
type: 'number'
|
|
93
|
+
},
|
|
94
|
+
SMTP_SECURE: {
|
|
95
|
+
description: 'Use secure connection to SMTP host (SSL/TSL)',
|
|
96
|
+
default: false,
|
|
97
|
+
type: 'boolean'
|
|
98
|
+
},
|
|
99
|
+
SMTP_TLS_REJECT: {
|
|
100
|
+
description: 'Reject bad cert/TLS connection to SMTP host',
|
|
101
|
+
default: false,
|
|
102
|
+
type: 'boolean'
|
|
103
|
+
},
|
|
104
|
+
SMTP_USER: {
|
|
105
|
+
description: 'Username for SMTP host',
|
|
106
|
+
default: ''
|
|
107
|
+
},
|
|
108
|
+
SMTP_PASSWORD: {
|
|
109
|
+
description: 'Password for SMTP host',
|
|
110
|
+
default: ''
|
|
111
|
+
},
|
|
112
|
+
UPLOAD_PATH: {
|
|
113
|
+
description: 'Path for attached files',
|
|
114
|
+
default: './uploads/'
|
|
115
|
+
}
|
|
116
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { EnvLoader } from '@technomoron/env-loader';
|
|
4
|
+
import { createTransport } from 'nodemailer';
|
|
5
|
+
import { connect_api_db } from '../models/db.js';
|
|
6
|
+
import { importData } from '../models/init.js';
|
|
7
|
+
import { envOptions } from './envloader.js';
|
|
8
|
+
function create_mail_transport(env) {
|
|
9
|
+
const args = {
|
|
10
|
+
host: env.SMTP_HOST,
|
|
11
|
+
port: env.SMTP_PORT,
|
|
12
|
+
secure: env.SMTP_SECURE,
|
|
13
|
+
tls: {
|
|
14
|
+
rejectUnauthorized: env.SMTP_TLS_REJECT
|
|
15
|
+
},
|
|
16
|
+
requireTLS: true,
|
|
17
|
+
logger: env.DEBUG,
|
|
18
|
+
debug: env.DEBUG
|
|
19
|
+
};
|
|
20
|
+
const user = env.SMTP_USER;
|
|
21
|
+
const pass = env.SMTP_PASSWORD;
|
|
22
|
+
if (user && pass) {
|
|
23
|
+
args.auth = { user, pass };
|
|
24
|
+
}
|
|
25
|
+
// console.log(JSON.stringify(args, undefined, 2));
|
|
26
|
+
const mailer = createTransport({
|
|
27
|
+
...args
|
|
28
|
+
});
|
|
29
|
+
if (!mailer) {
|
|
30
|
+
throw new Error('Unable to create mailer');
|
|
31
|
+
}
|
|
32
|
+
return mailer;
|
|
33
|
+
}
|
|
34
|
+
export class mailStore {
|
|
35
|
+
env;
|
|
36
|
+
transport;
|
|
37
|
+
api_db = null;
|
|
38
|
+
keys = {};
|
|
39
|
+
configpath = '';
|
|
40
|
+
print_debug(msg) {
|
|
41
|
+
if (this.env.DEBUG) {
|
|
42
|
+
console.log(msg);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
config_filename(name) {
|
|
46
|
+
return path.resolve(path.join(this.configpath, name));
|
|
47
|
+
}
|
|
48
|
+
async load_api_keys(cfgpath) {
|
|
49
|
+
const keyfile = path.resolve(cfgpath, 'api-keys.json');
|
|
50
|
+
if (fs.existsSync(keyfile)) {
|
|
51
|
+
const raw = fs.readFileSync(keyfile, 'utf-8');
|
|
52
|
+
const jsonData = JSON.parse(raw);
|
|
53
|
+
this.print_debug(`API Key Database loaded from ${keyfile}`);
|
|
54
|
+
return jsonData;
|
|
55
|
+
}
|
|
56
|
+
this.print_debug(`No api-keys.json file found: tried ${keyfile}`);
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
async init() {
|
|
60
|
+
const env = (this.env = await EnvLoader.createConfigProxy(envOptions, { debug: true }));
|
|
61
|
+
EnvLoader.genTemplate(envOptions, '.env-dist');
|
|
62
|
+
const p = env.CONFIG_PATH;
|
|
63
|
+
this.configpath = path.isAbsolute(p) ? p : path.join(process.cwd(), p);
|
|
64
|
+
console.log(`Config path is ${this.configpath}`);
|
|
65
|
+
// this.keys = await this.load_api_keys(this.configpath);
|
|
66
|
+
this.transport = await create_mail_transport(env);
|
|
67
|
+
this.api_db = await connect_api_db(this);
|
|
68
|
+
if (this.env.DB_AUTO_RELOAD) {
|
|
69
|
+
this.print_debug('Enabling auto reload of init-data.json');
|
|
70
|
+
fs.watchFile(this.config_filename('init-data.json'), { interval: 2000 }, () => {
|
|
71
|
+
this.print_debug('Config file changed, reloading...');
|
|
72
|
+
try {
|
|
73
|
+
importData(this);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
this.print_debug(`Failed to reload config: ${err}`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
get_api_key(key) {
|
|
83
|
+
return this.keys[key] || null;
|
|
84
|
+
}
|
|
85
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { api_domain } from './models/domain.js';
|
|
2
|
+
import { api_user } from './models/user.js';
|
|
3
|
+
/**
|
|
4
|
+
* Normalize a string into a safe identifier for slugs, filenames, etc.
|
|
5
|
+
*
|
|
6
|
+
* - Lowercases all characters
|
|
7
|
+
* - Replaces any character that is not `a-z`, `0-9`, `-`, '.' or `_` with `-`
|
|
8
|
+
* - Collapses multiple consecutive dashes into one
|
|
9
|
+
* - Trims leading and trailing dashes
|
|
10
|
+
*
|
|
11
|
+
* Examples:
|
|
12
|
+
* normalizeSlug("Hello World!") -> "hello-world"
|
|
13
|
+
* normalizeSlug(" Áccêntš ") -> "ccnt"
|
|
14
|
+
* normalizeSlug("My--Slug__Test") -> "my-slug__test"
|
|
15
|
+
*/
|
|
16
|
+
export function normalizeSlug(input) {
|
|
17
|
+
if (!input) {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
return input
|
|
21
|
+
.trim()
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.replace(/[^a-z0-9-_\.]/g, '-')
|
|
24
|
+
.replace(/--+/g, '-') // collapse multiple dashes
|
|
25
|
+
.replace(/^-+|-+$/g, ''); // trim leading/trailing dashes
|
|
26
|
+
}
|
|
27
|
+
export async function user_and_domain(domain_id) {
|
|
28
|
+
const domain = await api_domain.findByPk(domain_id);
|
|
29
|
+
if (!domain) {
|
|
30
|
+
throw new Error(`Unable to look up domain ${domain_id}`);
|
|
31
|
+
}
|
|
32
|
+
const user = await api_user.findByPk(domain.user_id);
|
|
33
|
+
if (!user) {
|
|
34
|
+
throw new Error(`Unable to look up user ${domain.user_id}`);
|
|
35
|
+
}
|
|
36
|
+
return { user, domain };
|
|
37
|
+
}
|
|
38
|
+
function collectHeaderIps(header) {
|
|
39
|
+
if (!header) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(header)) {
|
|
43
|
+
return header
|
|
44
|
+
.join(',')
|
|
45
|
+
.split(',')
|
|
46
|
+
.map((ip) => ip.trim())
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
}
|
|
49
|
+
return header
|
|
50
|
+
.split(',')
|
|
51
|
+
.map((ip) => ip.trim())
|
|
52
|
+
.filter(Boolean);
|
|
53
|
+
}
|
|
54
|
+
function resolveHeader(headers, key) {
|
|
55
|
+
const direct = headers[key];
|
|
56
|
+
const alt = headers[key.toLowerCase()];
|
|
57
|
+
const value = direct ?? alt;
|
|
58
|
+
if (typeof value === 'string' || Array.isArray(value)) {
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
export function buildRequestMeta(rawReq) {
|
|
64
|
+
const req = (rawReq ?? {});
|
|
65
|
+
const headers = req.headers ?? {};
|
|
66
|
+
const ips = [];
|
|
67
|
+
ips.push(...collectHeaderIps(resolveHeader(headers, 'x-forwarded-for')));
|
|
68
|
+
const realIp = resolveHeader(headers, 'x-real-ip');
|
|
69
|
+
if (typeof realIp === 'string' && realIp.trim()) {
|
|
70
|
+
ips.push(realIp.trim());
|
|
71
|
+
}
|
|
72
|
+
const cfIp = resolveHeader(headers, 'cf-connecting-ip');
|
|
73
|
+
if (typeof cfIp === 'string' && cfIp.trim()) {
|
|
74
|
+
ips.push(cfIp.trim());
|
|
75
|
+
}
|
|
76
|
+
const fastlyIp = resolveHeader(headers, 'fastly-client-ip');
|
|
77
|
+
if (typeof fastlyIp === 'string' && fastlyIp.trim()) {
|
|
78
|
+
ips.push(fastlyIp.trim());
|
|
79
|
+
}
|
|
80
|
+
if (req.ip && req.ip.trim()) {
|
|
81
|
+
ips.push(req.ip.trim());
|
|
82
|
+
}
|
|
83
|
+
const remoteAddress = req.socket?.remoteAddress;
|
|
84
|
+
if (remoteAddress) {
|
|
85
|
+
ips.push(remoteAddress);
|
|
86
|
+
}
|
|
87
|
+
const uniqueIps = ips.filter((ip, index) => ips.indexOf(ip) === index);
|
|
88
|
+
const clientIp = uniqueIps[0] || '';
|
|
89
|
+
return {
|
|
90
|
+
client_ip: clientIp,
|
|
91
|
+
received_at: new Date().toISOString(),
|
|
92
|
+
ip_chain: uniqueIps
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import tsParser from '@typescript-eslint/parser';
|
|
2
|
+
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript';
|
|
3
|
+
import pluginImport from 'eslint-plugin-import';
|
|
4
|
+
import pluginPrettier from 'eslint-plugin-prettier';
|
|
5
|
+
import pluginVue from 'eslint-plugin-vue';
|
|
6
|
+
import jsoncParser from 'jsonc-eslint-parser';
|
|
7
|
+
|
|
8
|
+
export default [
|
|
9
|
+
{
|
|
10
|
+
ignores: [
|
|
11
|
+
'node_modules',
|
|
12
|
+
'dist',
|
|
13
|
+
'.output',
|
|
14
|
+
'.nuxt',
|
|
15
|
+
'coverage',
|
|
16
|
+
'**/*.d.ts',
|
|
17
|
+
'configure-eslint.js',
|
|
18
|
+
'*.config.js',
|
|
19
|
+
'*.config.ts',
|
|
20
|
+
'public'
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
...defineConfigWithVueTs(vueTsConfigs.recommended),
|
|
24
|
+
{
|
|
25
|
+
files: ['**/*.vue'],
|
|
26
|
+
plugins: {
|
|
27
|
+
vue: pluginVue,
|
|
28
|
+
prettier: pluginPrettier
|
|
29
|
+
},
|
|
30
|
+
rules: {
|
|
31
|
+
'prettier/prettier': 'error', // Enforce Prettier rules
|
|
32
|
+
'vue/html-indent': 'off', // Let Prettier handle indentation
|
|
33
|
+
'vue/max-attributes-per-line': 'off', // Let Prettier handle line breaks
|
|
34
|
+
'vue/first-attribute-linebreak': 'off', // Let Prettier handle attribute positioning
|
|
35
|
+
'vue/singleline-html-element-content-newline': 'off',
|
|
36
|
+
'vue/html-self-closing': [
|
|
37
|
+
'error',
|
|
38
|
+
{
|
|
39
|
+
html: {
|
|
40
|
+
void: 'always',
|
|
41
|
+
normal: 'always',
|
|
42
|
+
component: 'always'
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
'vue/multi-word-component-names': 'off', // Disable multi-word name restriction
|
|
47
|
+
'vue/attribute-hyphenation': ['error', 'always']
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
files: ['*.json'],
|
|
52
|
+
languageOptions: {
|
|
53
|
+
parser: jsoncParser
|
|
54
|
+
},
|
|
55
|
+
plugins: {
|
|
56
|
+
prettier: pluginPrettier
|
|
57
|
+
},
|
|
58
|
+
rules: {
|
|
59
|
+
quotes: ['error', 'double'], // Enforce double quotes in JSON
|
|
60
|
+
'prettier/prettier': 'error',
|
|
61
|
+
'@typescript-eslint/no-unused-expressions': 'off',
|
|
62
|
+
'@typescript-eslint/no-unused-vars': 'off'
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
files: ['**/*.{ts,mts,tsx,js,mjs,cjs}'],
|
|
67
|
+
languageOptions: {
|
|
68
|
+
parser: tsParser,
|
|
69
|
+
parserOptions: {
|
|
70
|
+
ecmaVersion: 2023,
|
|
71
|
+
sourceType: 'module',
|
|
72
|
+
extraFileExtensions: ['.vue']
|
|
73
|
+
},
|
|
74
|
+
globals: {
|
|
75
|
+
RequestInit: 'readonly',
|
|
76
|
+
process: 'readonly',
|
|
77
|
+
Capacitor: 'readonly',
|
|
78
|
+
chrome: 'readonly'
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
plugins: {
|
|
82
|
+
prettier: pluginPrettier,
|
|
83
|
+
import: pluginImport
|
|
84
|
+
},
|
|
85
|
+
rules: {
|
|
86
|
+
indent: ['error', 'tab', { SwitchCase: 1 }], // Use tabs for JS/TS
|
|
87
|
+
quotes: ['warn', 'single', { avoidEscape: true }], // Prefer single quotes
|
|
88
|
+
semi: ['error', 'always'], // Enforce semicolons
|
|
89
|
+
'comma-dangle': 'off', // Disable trailing commas
|
|
90
|
+
'prettier/prettier': 'error', // Enforce Prettier rules
|
|
91
|
+
'import/order': [
|
|
92
|
+
'error',
|
|
93
|
+
{
|
|
94
|
+
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
|
|
95
|
+
'newlines-between': 'always',
|
|
96
|
+
alphabetize: { order: 'asc', caseInsensitive: true }
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
'@typescript-eslint/no-explicit-any': ['warn'],
|
|
100
|
+
'@typescript-eslint/no-unused-vars': ['warn'],
|
|
101
|
+
'@typescript-eslint/no-require-imports': 'off'
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@technomoron/mail-magic",
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/technomoron/mail-magic.git"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node dist/index.js",
|
|
12
|
+
"dev": "NODE_ENV=development nodemon --watch 'src/**/*.ts' --watch 'config/**/*.*' --watch '.env' --exec 'tsx' src/index.ts",
|
|
13
|
+
"run": "NODE_ENV=production npm run start",
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"scrub": "rm -rf ./node_modules/ ./dist/ pnpm-lock.yaml",
|
|
16
|
+
"lint": "eslint --ext .js,.cjs,.mjs,.ts,.mts,.tsx,.vue ./",
|
|
17
|
+
"lintfix": "eslint --fix --ext .js,.cjs,.mjs,.ts,.mts,.tsx,.vue ./",
|
|
18
|
+
"pretty": "prettier --write \"**/*.{js,jsx,cjs,mjs,ts,tsx,mts,vue,json,css,scss,md}\"",
|
|
19
|
+
"format": "npm run lintfix && npm run pretty",
|
|
20
|
+
"cleanbuild": "rm -rf ./dist/ && npm run lintfix && npm run format && npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [],
|
|
23
|
+
"author": "Bjørn Erik Jacobsen",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"copyright": "Copyright (c) 2025 Bjørn Erik Jacobsen",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/technomoron/mail-magic/issues"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@technomoron/api-server-base": "^1.0.40",
|
|
31
|
+
"@technomoron/env-loader": "^1.0.8",
|
|
32
|
+
"@technomoron/unyuck": "^1.0.4",
|
|
33
|
+
"bcryptjs": "^3.0.2",
|
|
34
|
+
"email-addresses": "^5.0.0",
|
|
35
|
+
"html-to-text": "^9.0.5",
|
|
36
|
+
"nodemailer": "^6.10.1",
|
|
37
|
+
"nunjucks": "^3.2.4",
|
|
38
|
+
"sequelize": "^6.37.7",
|
|
39
|
+
"sqlite3": "^5.1.7",
|
|
40
|
+
"swagger-jsdoc": "^6.2.8",
|
|
41
|
+
"swagger-ui-express": "^5.0.1",
|
|
42
|
+
"zod": "^4.1.5"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/html-to-text": "^9.0.4",
|
|
46
|
+
"@types/nodemailer": "^6.4.19",
|
|
47
|
+
"@types/nunjucks": "^3.2.6",
|
|
48
|
+
"@typescript-eslint/eslint-plugin": "8.44.1",
|
|
49
|
+
"@typescript-eslint/parser": "8.44.1",
|
|
50
|
+
"@vue/eslint-config-prettier": "10.2.0",
|
|
51
|
+
"@vue/eslint-config-typescript": "^14.6.0",
|
|
52
|
+
"eslint": "9.36.0",
|
|
53
|
+
"eslint-config-prettier": "10.1.8",
|
|
54
|
+
"eslint-import-resolver-alias": "1.1.2",
|
|
55
|
+
"eslint-plugin-import": "2.32.0",
|
|
56
|
+
"eslint-plugin-nuxt": "4.0.0",
|
|
57
|
+
"eslint-plugin-prettier": "5.5.4",
|
|
58
|
+
"eslint-plugin-vue": "^10.5.0",
|
|
59
|
+
"jsonc-eslint-parser": "^2.4.1",
|
|
60
|
+
"nodemon": "^3.1.10",
|
|
61
|
+
"prettier": "3.6.2",
|
|
62
|
+
"tsx": "^4.20.5",
|
|
63
|
+
"typescript": "^5.9.2",
|
|
64
|
+
"vue-eslint-parser": "^10.2.0"
|
|
65
|
+
},
|
|
66
|
+
"homepage": "https://github.com/technomoron/mail-magic#readme"
|
|
67
|
+
}
|