anote-server-libs 0.11.0 → 0.11.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anote-server-libs",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Helpers for express-TS servers",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
package/services/utils.js CHANGED
@@ -11,6 +11,12 @@ exports.idempotent = idempotent;
11
11
  exports.sendSelfPostableMessage = sendSelfPostableMessage;
12
12
  exports.fpEuros = fpEuros;
13
13
  exports.digitize = digitize;
14
+ exports.readEmailTemplates = readEmailTemplates;
15
+ exports.replacePlaceholders = replacePlaceholders;
16
+ exports.getHtmlReplaced = getHtmlReplaced;
17
+ exports.getEmailTitle = getEmailTitle;
18
+ exports.getEmailTranslation = getEmailTranslation;
19
+ const fs = require("fs");
14
20
  function atob(str) {
15
21
  return Buffer.from(str, 'base64').toString('binary');
16
22
  }
@@ -195,3 +201,89 @@ function digitize(value, opts) {
195
201
  }
196
202
  return (opts && opts.currency) ? (parts[0] + '.00') : parts[0];
197
203
  }
204
+ function loadEmailTemplates(path) {
205
+ if (!path || !fs.existsSync(path))
206
+ return undefined;
207
+ const loadedEmailTemplates = {};
208
+ const fileNames = fs.readdirSync(path);
209
+ fileNames.forEach(fileName => {
210
+ if (!fileName.startsWith('template-') || !fileName.endsWith('.html'))
211
+ return;
212
+ const templateIdentifier = fileName.slice('template-'.length, -'.html'.length);
213
+ loadedEmailTemplates[templateIdentifier] = fs.readFileSync(path + '/' + fileName).toString('utf8');
214
+ });
215
+ return loadedEmailTemplates;
216
+ }
217
+ function readEmailTemplates(path, config) {
218
+ const { product, name } = config.app;
219
+ if (!product || !name)
220
+ return loadEmailTemplates(path);
221
+ const mainPath = path + '/' + product;
222
+ const overridePath = product !== name ? __dirname + `/${product}/${name}` : undefined;
223
+ const defaultTemplates = loadEmailTemplates(mainPath);
224
+ const overrideTemplates = overridePath ? loadEmailTemplates(overridePath) : {};
225
+ return { ...defaultTemplates, ...overrideTemplates };
226
+ }
227
+ function replacePlaceholders(data, keys, depth) {
228
+ if (!keys || depth >= keys.length)
229
+ return undefined;
230
+ if (depth === keys.length - 1)
231
+ return (data || {})[keys[depth]];
232
+ return replacePlaceholders((data || {})[keys[depth]], keys, depth + 1);
233
+ }
234
+ function getHtml(template, translationKeyValue) {
235
+ if (!template)
236
+ return undefined;
237
+ const pattern = new RegExp('{{\\s*([a-zA-Z._0-9]+)\\s*}}', 'g');
238
+ const replacer = (_, key) => replacePlaceholders(translationKeyValue, key.split('.'), 0) ?? '';
239
+ template = template.replace(pattern, replacer);
240
+ template = template.replace(pattern, replacer);
241
+ template = template.replace(new RegExp('\\[\\[\\s*([a-zA-Z._0-9:]+)\\s*\\]\\]', 'g'), (_, key) => {
242
+ const keyParts = key.split(':');
243
+ return (replacePlaceholders(translationKeyValue, keyParts[1].split('.'), 0) || [])
244
+ .map((subContext) => getHtml(keyParts[0], subContext)).join('');
245
+ });
246
+ template = template.replace(new RegExp('<<\\s*([a-zA-Z._0-9:]+)\\s*>>', 'g'), (_, key) => {
247
+ const keyParts = key.split(':');
248
+ if (replacePlaceholders(translationKeyValue, keyParts[1].split('.'), 0))
249
+ return getHtml(keyParts[0], translationKeyValue);
250
+ return '';
251
+ });
252
+ return template;
253
+ }
254
+ function getHtmlReplaced(config, templates, key, lang, defaultTemplateTranslations = {}, envSpecificTranslations, extraData, newPage) {
255
+ const name = config.app.name;
256
+ const defaultTranslations = defaultTemplateTranslations?.[lang] || defaultTemplateTranslations?.[0] || {};
257
+ const envSpecificTrans = envSpecificTranslations?.[name]?.[key]?.[lang]
258
+ || envSpecificTranslations?.[name]?.[key]?.[0]
259
+ || {};
260
+ const translationKeyValue = {
261
+ ...defaultTranslations,
262
+ ...envSpecificTrans,
263
+ ...extraData,
264
+ domain: config.frontend,
265
+ ...config.app.emailConfig
266
+ };
267
+ let html = getHtml(templates[key], translationKeyValue);
268
+ if (newPage) {
269
+ html = html.replace('</head>', `<style>
270
+ @media print {
271
+ .new-page {
272
+ page-break-before: always;
273
+ }
274
+ }
275
+ </style>
276
+ </head>`);
277
+ html = html.replace('</body>', `<p class="new-page">Provided by Satoris</p>
278
+ </body>`);
279
+ }
280
+ return html;
281
+ }
282
+ function getEmailTitle(key, lang, titles, overrideTitles, data) {
283
+ const pattern = new RegExp('{{\\s*([a-zA-Z._0-9]+)\\s*}}', 'g');
284
+ const title = overrideTitles?.[lang]?.[key] ?? titles[lang]?.[key];
285
+ return title?.replace(pattern, (_, placeholderKey) => data?.[placeholderKey]);
286
+ }
287
+ function getEmailTranslation(key, lang, translationKeyValue, defaultLang = 0) {
288
+ return translationKeyValue?.[lang]?.[key] ?? translationKeyValue?.[defaultLang]?.[key];
289
+ }
package/services/utils.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import * as fs from 'fs';
1
2
  import {NextFunction, Request, Response} from 'express';
2
3
  import {Logger} from 'winston';
3
4
  import {BaseModelRepository} from '../models/repository/BaseModelRepository';
@@ -178,4 +179,91 @@ export function digitize(value: number | string, opts?: {[key: string]: any}): s
178
179
  return parts[0] + '.' + decimals;
179
180
  }
180
181
  return (opts && opts.currency)? (parts[0] + '.00') : parts[0];
181
- }
182
+ }
183
+
184
+ function loadEmailTemplates(path: string): Record<string, string> | undefined {
185
+ if(!path || !fs.existsSync(path)) return undefined;
186
+ const loadedEmailTemplates: {[id: string]: string} = {};
187
+ const fileNames = fs.readdirSync(path);
188
+ fileNames.forEach(fileName => {
189
+ if(!fileName.startsWith('template-') || !fileName.endsWith('.html')) return;
190
+ const templateIdentifier = fileName.slice('template-'.length, -'.html'.length);
191
+ loadedEmailTemplates[templateIdentifier] = fs.readFileSync(path + '/' + fileName).toString('utf8');
192
+ });
193
+ return loadedEmailTemplates;
194
+ }
195
+
196
+ export function readEmailTemplates(path: string, config: Record<string, any>): {[id: string]: string} {
197
+ const { product, name } = config.app;
198
+ if (!product || !name) return loadEmailTemplates(path);
199
+ const mainPath = path + '/' + product;
200
+ const overridePath = product !== name ? __dirname + `/${product}/${name}` : undefined;
201
+ const defaultTemplates = loadEmailTemplates(mainPath);
202
+ const overrideTemplates = overridePath ? loadEmailTemplates(overridePath) : {};
203
+ return {...defaultTemplates, ...overrideTemplates};
204
+ }
205
+
206
+ export function replacePlaceholders(data: Record<string, any>, keys: string[], depth: number): any {
207
+ if(!keys || depth >= keys.length) return undefined;
208
+ if(depth === keys.length - 1) return (data || {})[keys[depth]];
209
+ return replacePlaceholders((data || {})[keys[depth]], keys, depth + 1);
210
+ }
211
+
212
+ function getHtml(template: string, translationKeyValue: Record<string, any>): string | undefined {
213
+ if(!template) return undefined;
214
+ const pattern = new RegExp('{{\\s*([a-zA-Z._0-9]+)\\s*}}', 'g');
215
+ const replacer = (_: string, key: string) => replacePlaceholders(translationKeyValue, key.split('.'), 0) ?? '';
216
+ template = template.replace(pattern, replacer);
217
+ template = template.replace(pattern, replacer);
218
+ template = template.replace(new RegExp('\\[\\[\\s*([a-zA-Z._0-9:]+)\\s*\\]\\]', 'g'), (_, key) => {
219
+ const keyParts = key.split(':');
220
+ return (replacePlaceholders(translationKeyValue, keyParts[1].split('.'), 0) || [])
221
+ .map((subContext: any) => getHtml(keyParts[0], subContext)).join('');
222
+ });
223
+ template = template.replace(new RegExp('<<\\s*([a-zA-Z._0-9:]+)\\s*>>', 'g'), (_, key) => {
224
+ const keyParts = key.split(':');
225
+ if(replacePlaceholders(translationKeyValue, keyParts[1].split('.'), 0))
226
+ return getHtml(keyParts[0], translationKeyValue);
227
+ return '';
228
+ });
229
+ return template;
230
+ }
231
+
232
+ export function getHtmlReplaced(config: Record<string, any>, templates: Record<string, string>, key: string, lang: number, defaultTemplateTranslations: Record<number, any> = {}, envSpecificTranslations?: Record<string, any>, extraData?: Record<string, any>, newPage?: boolean): string {
233
+ const name = config.app.name;
234
+ const defaultTranslations = defaultTemplateTranslations?.[lang] || defaultTemplateTranslations?.[0] || {};
235
+ const envSpecificTrans = envSpecificTranslations?.[name]?.[key]?.[lang]
236
+ || envSpecificTranslations?.[name]?.[key]?.[0]
237
+ || {};
238
+ const translationKeyValue = {
239
+ ...defaultTranslations,
240
+ ...envSpecificTrans,
241
+ ...extraData,
242
+ domain: config.frontend,
243
+ ...config.app.emailConfig
244
+ };
245
+ let html = getHtml(templates[key], translationKeyValue);
246
+ if(newPage) {
247
+ html = html.replace('</head>', `<style>
248
+ @media print {
249
+ .new-page {
250
+ page-break-before: always;
251
+ }
252
+ }
253
+ </style>
254
+ </head>`);
255
+ html = html.replace('</body>', `<p class="new-page">Provided by Satoris</p>
256
+ </body>`);
257
+ }
258
+ return html;
259
+ }
260
+
261
+ export function getEmailTitle(key: string, lang: number, titles: Record<number, any>, overrideTitles?: Record<number, any>, data?: Record<string, string>): string | undefined {
262
+ const pattern = new RegExp('{{\\s*([a-zA-Z._0-9]+)\\s*}}', 'g');
263
+ const title = overrideTitles?.[lang]?.[key] ?? titles[lang]?.[key];
264
+ return title?.replace(pattern, (_: string, placeholderKey: string) => data?.[placeholderKey]);
265
+ }
266
+
267
+ export function getEmailTranslation(key: string, lang: number, translationKeyValue: Record<number, any>, defaultLang: number = 0): string | undefined {
268
+ return translationKeyValue?.[lang]?.[key] ?? translationKeyValue?.[defaultLang]?.[key];
269
+ }