node-server-engine 1.2.0 → 1.2.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/.env.example CHANGED
@@ -118,9 +118,6 @@ PUBSUB_UPDATE_SUBSCRIPTION=
118
118
  HASH_SECRET=
119
119
  HASH_SALT=
120
120
 
121
- #Base URL of the email service.
122
- EMAIL_SERVICE_URL=
123
-
124
121
 
125
122
  ##########
126
123
  # Elastic Search #
@@ -154,3 +151,20 @@ DB_MIGRATIONS_DIR=
154
151
 
155
152
  # Default message Audience
156
153
  DEFAULT_MESSAGE_AUDIENCE=
154
+
155
+
156
+ ###########
157
+ # SMTP Configuration #
158
+ ###########
159
+
160
+ # The SMTP server host (e.g., smtp.gmail.com, smtp.mailtrap.io)
161
+ SMTP_HOST=smtp.example.com
162
+
163
+ # The SMTP port (e.g., 587 for TLS, 465 for SSL, 25 for insecure)
164
+ SMTP_PORT=587
165
+
166
+ # SMTP authentication username (your email address or username)
167
+ SMTP_USER=your-email@example.com
168
+
169
+ # SMTP authentication password (your email password or app password)
170
+ SMTP_PASS=your-email-password
package/README.md CHANGED
@@ -191,13 +191,13 @@ The server engine exposes an enumeration for auth types.
191
191
 
192
192
  <!-- markdownlint-disable MD033 -->
193
193
 
194
- | AuthType | Description | AuthParams |
195
- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
196
- | AuthType.NONE | No authentication. All requests are handled | |
197
- | AuthType.JWT | A valid JSON Web Token is required as Bearer token. To be valid it must be properly signed by auth0, and its payload must match with what is set as environment variables.<br>The user's ID is added to `req.user`. | |
198
- | AuthType.TLS | Authenticate through mutual TLS. CA and an optional list of white listed host names should be set in the environment variables. | **whitelist** [Array<String>]: List of certificate Common Name or Alt Name that are permitted to make requests to this endpoint. |
199
- | AuthType.HMAC | Authenticate with a signature in the payload.<br>**This authentication is deprecated and should be avoided** | **secret** [String]: Overrides the secret used for signatures set in environment variables<br>**isGithub** [Boolean]: The request is a Github webhook and therefore uses their signature system and not the default one. |
200
- | AuthType.STATIC | A valid shared Bearer token is required. Shared token is stored in env variable (STATIC_TOKEN) |
194
+ | AuthType | Description | AuthParams |
195
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
196
+ | AuthType.NONE | No authentication. All requests are handled | |
197
+ | AuthType.JWT | A valid JSON Web Token is required as Bearer token. To be valid it must be properly signed by auth0, and its payload must match with what is set as environment variables.<br>The user's ID is added to `req.user`. | |
198
+ | AuthType.TLS | Authenticate through mutual TLS. CA and an optional list of white listed host names should be set in the environment variables. | **whitelist** [Array<String>]: List of certificate Common Name or Alt Name that are permitted to make requests to this endpoint. |
199
+ | AuthType.HMAC | Authenticate with a signature in the payload.<br>**This authentication is deprecated and should be avoided** | **secret** [String]: Overrides the secret used for signatures set in environment variables<br>**isGithub** [Boolean]: The request is a Github webhook and therefore uses their signature system and not the default one. |
200
+ | AuthType.STATIC | A valid shared Bearer token is required. Shared token is stored in env variable (STATIC_TOKEN) |
201
201
 
202
202
  <!-- markdownlint-enable MD033 -->
203
203
 
@@ -519,12 +519,12 @@ All our custom error classes take a `data` parameter. This will be logged on the
519
519
 
520
520
  These options are common to each of the error classes described below.
521
521
 
522
- | option | definition | example | default |
523
- | -------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | ------------------------------ |
524
- | message | A message logged on the backend only | "Could not find user in the DB" | **required** |
525
- | severity | The level at which this error should be logged | [Severity](#severity).WARNING | [Severity](#severity).CRITICAL |
526
- | data | Some data related to the error that will be logged on the backend. This should help to undetrstand the runtime context. | {userId: 'xf563ugh0'} | |
527
- | error | An error object, when this is a wrapper around an existing error object | |
522
+ | option | definition | example | default |
523
+ | -------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------- | ------------------------------ |
524
+ | message | A message logged on the backend only | "Could not find user in the DB" | **required** |
525
+ | severity | The level at which this error should be logged | [Severity](#severity).WARNING | [Severity](#severity).CRITICAL |
526
+ | data | Some data related to the error that will be logged on the backend. This should help to undetrstand the runtime context. | {userId: 'xf563ugh0'} | |
527
+ | error | An error object, when this is a wrapper around an existing error object | |
528
528
 
529
529
  #### Severity
530
530
 
@@ -674,6 +674,7 @@ new Endpoint({
674
674
  }
675
675
  });
676
676
  ```
677
+
677
678
  ---
678
679
 
679
680
  #### Gemini File Upload
@@ -684,8 +685,7 @@ The request must be made as `multipart/form-data`.
684
685
 
685
686
  File should be uploaded under the key file.
686
687
 
687
- The file's data will be available at `req.body.fileUri req.body.mimeType req.body.originalname`
688
-
688
+ The file's data will be available at `req.body.fileUri req.body.mimeType req.body.originalname`
689
689
 
690
690
  #### Check Permission Middleware
691
691
 
@@ -716,14 +716,18 @@ export const checkPermission = (requiredActions: string | string[]) => {
716
716
  const user = req.user;
717
717
 
718
718
  if (!user || !user.permissions) {
719
- return res.status(403).json({ message: 'User does not have permissions' });
719
+ return res
720
+ .status(403)
721
+ .json({ message: 'User does not have permissions' });
720
722
  }
721
723
 
722
724
  const requiredActionsArray = Array.isArray(requiredActions)
723
725
  ? requiredActions
724
726
  : [requiredActions];
725
727
 
726
- const requiredActionsLower = requiredActionsArray.map(action => action.toLowerCase());
728
+ const requiredActionsLower = requiredActionsArray.map((action) =>
729
+ action.toLowerCase()
730
+ );
727
731
 
728
732
  const hasPermission = user.permissions.some((permission: string) =>
729
733
  requiredActionsLower.includes(permission.toLowerCase())
@@ -813,27 +817,56 @@ await sendPush(userId, notification);
813
817
 
814
818
  ---
815
819
 
816
- ---
817
-
818
820
  ### Send Email
819
821
 
820
- Send an email through the email service.
822
+ Send an email using the SMTP configuration.
821
823
 
822
824
  ```javascript
823
825
  import { sendEmail } from 'node-server-engine';
824
- await sendEmail(userId, template, translationKey, parameters, tags, to);
826
+
827
+ const emailOptions = {
828
+ to: 'recipient@example.com',
829
+ subject: 'Welcome to our service!',
830
+ text: 'Hello, welcome to our platform!',
831
+ html: '<h1>Welcome</h1><p>Glad to have you onboard!</p>',
832
+ attachments: [
833
+ {
834
+ filename: 'welcome.txt',
835
+ content: 'Welcome to our service!'
836
+ }
837
+ ]
838
+ };
839
+
840
+ const result = await sendEmail(emailOptions);
841
+ console.log(result.status); // 'sent', 'delivered', 'queued', or 'failed'
825
842
  ```
826
843
 
827
- | parameter | description |
828
- | -------------- | -------------------------------------------------------------------------- |
829
- | userId | ID of the user that should receive the notification |
830
- | template | Email template to use |
831
- | translationKey | Key used to get localized strings |
832
- | parameters | Variables that can be inserted in the email template and localized strings |
833
- | tags | Data injected in the localized strings as HTML tags |
834
- | to | Array of email recipients |
835
- | to.\*.email | Email of an email recipient |
836
- | to.\*.name | Name of an email recipient |
844
+ ### Parameters
845
+
846
+ | Parameter | Description |
847
+ | ------------- | ----------------------------------------------------------------------------------------------------------------- |
848
+ | `from` | (Optional) Sender's email address. Defaults to the authenticated email. |
849
+ | `to` | Email recipient(s) as a string or array of strings. |
850
+ | `cc` | (Optional) Carbon Copy recipients (string or array). |
851
+ | `bcc` | (Optional) Blind Carbon Copy recipients (string or array). |
852
+ | `subject` | Subject of the email. |
853
+ | `text` | (Optional) Plain text version of the email body. |
854
+ | `html` | (Optional) HTML version of the email body. |
855
+ | `attachments` | (Optional) Array of attachments. Each attachment can include `filename`, `content`, `path`, and other properties. |
856
+ | `replyTo` | (Optional) Email address for replies. |
857
+ | `headers` | (Optional) Custom email headers. |
858
+ | `priority` | (Optional) Email priority (`high`, `normal`, or `low`). |
859
+
860
+ ### Return Status
861
+
862
+ The function returns an object with the following status options:
863
+
864
+ | Status | Description |
865
+ | ----------- | ----------------------------------------------------------------------- |
866
+ | `sent` | Email was successfully sent but delivery confirmation is not available. |
867
+ | `delivered` | Email was successfully delivered. |
868
+ | `queued` | Email is queued for delivery but not yet sent. |
869
+ | `failed` | Email could not be sent due to an error. |
837
870
 
838
871
  ---
839
872
 
@@ -101,9 +101,6 @@ let Server = exports.Server = /*#__PURE__*/function () {
101
101
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
102
102
  allowedHeaders: ['Content-Type', 'Authorization']
103
103
  }));
104
- this.app.options('*', (req, res) => {
105
- res.sendStatus(204);
106
- });
107
104
  this.registerMiddleware(this.app);
108
105
  this.httpServer = (0, _utils.createHttpServer)(this.app);
109
106
 
@@ -1 +1 @@
1
- {"version":3,"file":"Server.js","names":["_fs","_interopRequireDefault","require","_path","_promises","_express","_cors","_ws","_SocketClient","_Server2","_Endpoint","_EngineError","_LifecycleController","_PubSub","_utils","_jwt","_report","_tlsConfig","namespace","Server","exports","config","_classCallCheck2","default","_defineProperty2","AbortController","validate","reportDebug","message","endpoints","globalMiddleware","errorMiddleware","initCallbacks","shutdownCallbacks","syncCallbacks","cron","auth","noFetch","checkEnvironment","process","env","RUN_MIGRATION","UNDO_MIGRATION","SQL_HOST","envAssert","isHost","SQL_USER","isString","SQL_PASSWORD","SQL_DB","NODE_ENV","PORT","isPort","SECONDARY_PORT","app","express","use","cors","origin","CORS_ORIGIN","methods","allowedHeaders","options","req","res","sendStatus","registerMiddleware","httpServer","createHttpServer","secondaryApp","secondaryMiddleware","defaultErrorMiddleware","forEach","middleware","secondaryHttpServer","disable","server","headersTimeout","keepAliveTimeout","webSocket","attachWebSocket","_createClass2","key","value","init","initKeySets","callback","runCallback","Promise","all","map","PubSub","job","runCronJob","resolve","listen","reportInfo","data","port","secondaryPort","nodeEnv","handleSignal","watchTlsCaFileChange","shutdown","listening","address","on","error","reportError","exit","close","wss","clients","socket","socketKillTimeout","WEBSOCKET_CLOSE_TIMEOUT","parseInt","setTimeout","readyState","CLOSING","OPEN","terminate","LifecycleController","shutdownRunningInstances","abortController","abort","shutdownKeySets","shutdownSignals","signal","removeAllListeners","getApp","getSecondaryApp","defaultMiddleware","path","endpoint","register","WebSocketServer","request","SocketClient","client","catch","file","fs","watch","event","loadTlsConfig","setSecureContext","tlsConfig","handler","name","interval","setTimeoutPromise","undefined","Error","function","apply","parameters","some","Endpoint","EngineError","Object","values","validator","Array"],"sources":["../../../src/entities/Server/Server.ts"],"sourcesContent":["import fs from 'fs';\nimport http from 'http';\nimport https from 'https';\nimport { AddressInfo } from 'net';\nimport path from 'path';\nimport { setTimeout as setTimeoutPromise } from 'timers/promises';\nimport { ValidatorSpec } from 'envalid';\nimport express, { Application } from 'express';\nimport cors from 'cors';\nimport { Server as WebSocketServer } from 'ws';\nimport { SocketClient } from '../SocketClient';\nimport {\n defaultMiddleware,\n defaultErrorMiddleware,\n secondaryMiddleware,\n shutdownSignals\n} from './Server.const';\nimport {\n ServerOptions,\n ServerCronJon,\n ServerAuthConfig,\n ServerWebSocketConfig,\n ServerGlobalMiddleware,\n ServerErrorMiddleWare,\n ServerCallback\n} from './Server.types';\nimport { Endpoint } from 'entities/Endpoint';\nimport { EngineError } from 'entities/EngineError';\nimport { LifecycleController } from 'entities/LifecycleController';\nimport { PubSub } from 'entities/PubSub';\nimport { checkEnvironment, envAssert, createHttpServer } from 'utils';\nimport { initKeySets, shutdownKeySets } from 'utils/jwt';\nimport { reportInfo, reportError, reportDebug } from 'utils/report';\nimport { tlsConfig, loadTlsConfig, TlsConfig } from 'utils/tlsConfig';\n\nconst namespace = 'engine:Server';\n\n/** Main server class */\nexport class Server {\n /** The express application powering the main endpoints */\n private app: Application;\n /** The node HTTP(S) server, serving the main express application */\n private httpServer: http.Server | https.Server;\n /** The express application powering the support endpoints */\n private secondaryApp: Application;\n /** The node HTTP(S) server, serving the secondary express application */\n private secondaryHttpServer: http.Server | https.Server;\n /** List of endpoint instances that the server should handle */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private endpoints: Array<Endpoint<any>>;\n /** Mapping of env variable name with functions that should run to check their validity */\n private checkEnvironment: Record<string, ValidatorSpec<unknown>>;\n /** Middleware that should run on every request before the endpoint specific logic */\n private globalMiddleware: Array<ServerGlobalMiddleware>;\n /** Error middleware that should run on every request after the endpoint specific logic, but before the generic global middleware */\n private errorMiddleware: Array<\n ServerGlobalMiddleware | ServerErrorMiddleWare\n >;\n /** Functions that should be called on server startup */\n private initCallbacks: Array<ServerCallback>;\n /** Functions that should be called on server shutdown */\n private shutdownCallbacks: Array<ServerCallback>;\n /** When set to true, this indicates that init callbacks should run synchronously in the order they are declared. By default they all run in parallel */\n private syncCallbacks: boolean;\n /** Config to set regularly running jobs on the server */\n private cron?: Array<ServerCronJon>;\n /** Global auth related configuration */\n private auth: ServerAuthConfig;\n /** WebSocket server instance, if configured */\n private wss?: WebSocketServer;\n /** Abort controller that handles signals for the server */\n private abortController = new AbortController();\n\n /** Create a server */\n public constructor(config: ServerOptions) {\n this.validate(config);\n\n reportDebug({\n namespace,\n message: `Creating server`\n });\n\n this.endpoints = config.endpoints;\n this.globalMiddleware = config.globalMiddleware ?? [];\n this.errorMiddleware = config.errorMiddleware ?? [];\n this.initCallbacks = config.initCallbacks ?? [];\n this.shutdownCallbacks = config.shutdownCallbacks ?? [];\n this.syncCallbacks = config.syncCallbacks ?? false;\n this.cron = config.cron;\n this.auth = { noFetch: false, ...config.auth };\n\n // The minimal environment check is different between the migration modes of execution and the regular server execution mode\n // When running in migration mode, we only check that we have SQL related environment variables\n this.checkEnvironment =\n process.env.RUN_MIGRATION === 'true' ||\n process.env.UNDO_MIGRATION === 'true'\n ? {\n SQL_HOST: envAssert.isHost(),\n SQL_USER: envAssert.isString(),\n SQL_PASSWORD: envAssert.isString(),\n SQL_DB: envAssert.isString()\n }\n : {\n NODE_ENV: envAssert.isString(),\n PORT: envAssert.isPort(),\n SECONDARY_PORT: envAssert.isPort(),\n ...config.checkEnvironment\n };\n\n checkEnvironment(this.checkEnvironment);\n\n // Create the primary express core that serves the business logic\n this.app = express();\n\n this.app.use(\n cors({\n origin: process.env.CORS_ORIGIN ?? '*',\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization']\n })\n );\n\n this.app.options('*', (req, res) => {\n res.sendStatus(204);\n });\n\n this.registerMiddleware(this.app);\n this.httpServer = createHttpServer(this.app);\n\n // Create a secondary express core that servers metrics and analytics\n // This will be served on a different port, allowing to specify different firewall rules for these routes\n this.secondaryApp = express();\n [...secondaryMiddleware, ...defaultErrorMiddleware].forEach(\n (middleware) => {\n if (middleware) this.secondaryApp.use(middleware);\n }\n );\n this.secondaryHttpServer = createHttpServer(this.secondaryApp);\n\n // Remove the express headers\n this.app.disable('x-powered-by');\n this.secondaryApp.disable('x-powered-by');\n\n // We remove all node timeout on connections and requests\n // This responsibility is delegated to the front-facing load balancer\n [this.httpServer, this.secondaryHttpServer].forEach((server) => {\n server.headersTimeout = 0;\n server.keepAliveTimeout = 0;\n });\n\n if (config.webSocket) {\n this.attachWebSocket(config.webSocket);\n }\n }\n\n /** Start the server */\n public async init(): Promise<void> {\n reportDebug({ namespace, message: `Starting Server` });\n // Fetch the keys for JWT authentication\n // Ignore in test environment as we do symmetrical token signing\n if (process.env.NODE_ENV !== 'test' && !this.auth.noFetch)\n await initKeySets();\n // Run all the init callbacks\n // We can either run them all 1/1 synchronously or run them all in parallel (default)\n // Callback can either be direct function or an object with function and parameters attributes\n\n if (this.syncCallbacks) {\n for (const callback of this.initCallbacks) {\n await this.runCallback(callback);\n }\n } else {\n await Promise.all(\n this.initCallbacks.map((callback) => this.runCallback(callback))\n );\n }\n\n // Initialize the Pub/Sub module\n await PubSub.init();\n // Run all the cronjob\n if (this.cron) {\n this.cron.forEach((job) => {\n this.runCronJob(job);\n });\n }\n\n // Start listening to requests\n await Promise.all([\n new Promise<void>((resolve) =>\n this.httpServer.listen(process.env.PORT, () => {\n resolve();\n })\n ),\n new Promise<void>((resolve) =>\n this.secondaryHttpServer.listen(process.env.SECONDARY_PORT, () => {\n resolve();\n })\n )\n ]);\n reportInfo({\n message: 'SERVER_RUNNING',\n data: {\n port: process.env.PORT,\n secondaryPort: process.env.SECONDARY_PORT,\n nodeEnv: process.env.NODE_ENV\n }\n });\n\n this.handleSignal();\n\n // Watch TSL config files changing\n this.watchTlsCaFileChange();\n }\n\n /** Graceful shutdown */\n public async shutdown(): Promise<void> {\n reportDebug({ namespace, message: `Shutting down Server` });\n\n // Stop listening to HTTP requests\n await Promise.all(\n [this.httpServer, this.secondaryHttpServer].map((server) => {\n // Ignore if the server was already stopped\n // We have observed cases where SIGTERM was thrown multiple times\n if (!server.listening) return;\n const { port } = server.address() as AddressInfo;\n server.on('close', (error: unknown) => {\n if (error) {\n reportError(error);\n process.exit(1);\n } else {\n reportInfo({\n message: `Stopped listening to requests on port ${port}`\n });\n }\n });\n server.close();\n })\n );\n\n // Close all socket connections\n if (this.wss) {\n // First ask then to close gracefully\n this.wss.clients.forEach((socket) => {\n socket.close();\n });\n // Hard terminate all connections still open after a defined time\n const socketKillTimeout = process.env.WEBSOCKET_CLOSE_TIMEOUT\n ? parseInt(process.env.WEBSOCKET_CLOSE_TIMEOUT)\n : 5000;\n await new Promise<void>((resolve) => {\n setTimeout(() => {\n (this.wss as WebSocketServer).clients.forEach((socket) => {\n if (\n socket.readyState === socket.CLOSING ||\n socket.readyState === socket.OPEN\n ) {\n socket.terminate();\n }\n });\n resolve();\n }, socketKillTimeout);\n });\n }\n\n // Run all the shutdown callback\n if (this.syncCallbacks) {\n for (const callback of this.shutdownCallbacks) {\n await this.runCallback(callback);\n }\n } else {\n await Promise.all(\n this.shutdownCallbacks.map((callback) => this.runCallback(callback))\n );\n }\n\n // Shutdown all the running entities\n await LifecycleController.shutdownRunningInstances();\n\n // Abort all the cronjob\n if (this.abortController) this.abortController.abort();\n\n // Stop the keyset timers\n shutdownKeySets();\n\n shutdownSignals.forEach((signal) => process.removeAllListeners(signal));\n }\n\n /** Get the express app instance of the server */\n public getApp(): Application {\n return this.app;\n }\n\n /** Get the express app instance of the server */\n public getSecondaryApp(): Application {\n return this.secondaryApp;\n }\n\n /** Registers all the middleware with the express app */\n private registerMiddleware(app: Application): void {\n // Global middleware can specify a path to which they apply\n [...defaultMiddleware, ...this.globalMiddleware].forEach((middleware) => {\n if (typeof middleware === 'function') {\n app.use(middleware);\n } else {\n app.use(middleware.path, middleware.middleware);\n }\n });\n // Call the register method of each endpoint\n this.endpoints.forEach((endpoint) => {\n endpoint.register(app);\n });\n // Add error middleware\n [...this.errorMiddleware, ...defaultErrorMiddleware].forEach(\n (middleware) => {\n if (typeof middleware === 'function') {\n app.use(middleware);\n } else {\n app.use(middleware.path, middleware.middleware);\n }\n }\n );\n }\n\n /** Add a handler for WebSocket connections */\n private attachWebSocket(options: ServerWebSocketConfig): void {\n this.wss = new WebSocketServer({\n ...options.server,\n server: this.httpServer\n })\n .on('connection', (webSocket, request) => {\n new SocketClient(webSocket, request, options.client);\n })\n .on('error', (error) => {\n reportError(error);\n });\n }\n\n /** Call shutdown if signal gets sent to the process */\n private handleSignal(): void {\n shutdownSignals.forEach((signal) =>\n process.on(signal, () => {\n (async (): Promise<void> => {\n reportInfo(`Received shutdown signal [${signal}]`);\n await this.shutdown();\n })().catch((error) => {\n reportError(error);\n });\n })\n );\n }\n\n /** Watch the TLS certificate change event */\n private watchTlsCaFileChange(): void {\n [\n 'TLS_REQUEST_KEY',\n 'TLS_REQUEST_KEY_PASSPHRASE',\n 'TLS_REQUEST_CERT',\n 'TLS_REQUEST_CA',\n 'TLS_SERVER_KEY',\n 'TLS_SERVER_KEY_PASSPHRASE',\n 'TLS_SERVER_CERT',\n 'TLS_CA'\n ].forEach((key) => {\n const file = process.env[key];\n if (file)\n fs.watch(path.resolve(file), (event) => {\n if (event !== 'change') return;\n reportInfo(`Reloading TLS config [${file} changed]`);\n loadTlsConfig();\n (this.httpServer as https.Server).setSecureContext(\n tlsConfig as TlsConfig\n );\n });\n });\n }\n\n /** Executes a recurring job */\n private runCronJob(job: ServerCronJon): void {\n (async (): Promise<void> => {\n reportInfo({ message: `Starting scheduled job \"${job.handler.name}\"` });\n try {\n await job.handler();\n reportInfo({\n message: `Job \"${job.handler.name}\" finished, starting again in ${job.interval} seconds`\n });\n await setTimeoutPromise(job.interval * 1000, undefined, {\n signal: this.abortController.signal\n });\n this.runCronJob(job);\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n reportDebug({\n namespace,\n message: `Scheduled job \"${job.handler?.name}\" has been aborted`\n });\n } else {\n throw error;\n }\n }\n })().catch((error) => {\n reportError(error);\n });\n }\n\n /** Run a shutdown/init callback */\n private runCallback(callback: ServerCallback): unknown {\n return typeof callback === 'function'\n ? callback()\n : callback.function.apply(null, callback.parameters ?? []);\n }\n\n /**\n * Validate the Server constructor configuration\n */\n private validate(config: ServerOptions): void {\n if (\n !config.endpoints ||\n config.endpoints.some((endpoint) => !(endpoint instanceof Endpoint))\n )\n throw new EngineError(\n 'Server endpoints should be declared and all be Endpoint instances'\n );\n\n if (\n config.checkEnvironment &&\n Object.values(config.checkEnvironment).some(\n (validator) => typeof validator !== 'function'\n )\n )\n throw new EngineError(\n 'Some of the environment checks are not valid functions'\n );\n\n if (\n config.initCallbacks &&\n (!(config.initCallbacks instanceof Array) ||\n config.initCallbacks.some(\n (callback) =>\n !(\n typeof callback === 'function' ||\n typeof callback?.function === 'function'\n )\n ))\n )\n throw new EngineError('Init callbacks is not a list of functions');\n\n if (\n config.shutdownCallbacks &&\n (!(config.shutdownCallbacks instanceof Array) ||\n config.shutdownCallbacks.some(\n (callback) =>\n !(\n typeof callback === 'function' ||\n typeof callback?.function === 'function'\n )\n ))\n )\n throw new EngineError('Shutdown callbacks is not a list of functions');\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"Server.js","names":["_fs","_interopRequireDefault","require","_path","_promises","_express","_cors","_ws","_SocketClient","_Server2","_Endpoint","_EngineError","_LifecycleController","_PubSub","_utils","_jwt","_report","_tlsConfig","namespace","Server","exports","config","_classCallCheck2","default","_defineProperty2","AbortController","validate","reportDebug","message","endpoints","globalMiddleware","errorMiddleware","initCallbacks","shutdownCallbacks","syncCallbacks","cron","auth","noFetch","checkEnvironment","process","env","RUN_MIGRATION","UNDO_MIGRATION","SQL_HOST","envAssert","isHost","SQL_USER","isString","SQL_PASSWORD","SQL_DB","NODE_ENV","PORT","isPort","SECONDARY_PORT","app","express","use","cors","origin","CORS_ORIGIN","methods","allowedHeaders","registerMiddleware","httpServer","createHttpServer","secondaryApp","secondaryMiddleware","defaultErrorMiddleware","forEach","middleware","secondaryHttpServer","disable","server","headersTimeout","keepAliveTimeout","webSocket","attachWebSocket","_createClass2","key","value","init","initKeySets","callback","runCallback","Promise","all","map","PubSub","job","runCronJob","resolve","listen","reportInfo","data","port","secondaryPort","nodeEnv","handleSignal","watchTlsCaFileChange","shutdown","listening","address","on","error","reportError","exit","close","wss","clients","socket","socketKillTimeout","WEBSOCKET_CLOSE_TIMEOUT","parseInt","setTimeout","readyState","CLOSING","OPEN","terminate","LifecycleController","shutdownRunningInstances","abortController","abort","shutdownKeySets","shutdownSignals","signal","removeAllListeners","getApp","getSecondaryApp","defaultMiddleware","path","endpoint","register","options","WebSocketServer","request","SocketClient","client","catch","file","fs","watch","event","loadTlsConfig","setSecureContext","tlsConfig","handler","name","interval","setTimeoutPromise","undefined","Error","function","apply","parameters","some","Endpoint","EngineError","Object","values","validator","Array"],"sources":["../../../src/entities/Server/Server.ts"],"sourcesContent":["import fs from 'fs';\nimport http from 'http';\nimport https from 'https';\nimport { AddressInfo } from 'net';\nimport path from 'path';\nimport { setTimeout as setTimeoutPromise } from 'timers/promises';\nimport { ValidatorSpec } from 'envalid';\nimport express, { Application } from 'express';\nimport cors from 'cors';\nimport { Server as WebSocketServer } from 'ws';\nimport { SocketClient } from '../SocketClient';\nimport {\n defaultMiddleware,\n defaultErrorMiddleware,\n secondaryMiddleware,\n shutdownSignals\n} from './Server.const';\nimport {\n ServerOptions,\n ServerCronJon,\n ServerAuthConfig,\n ServerWebSocketConfig,\n ServerGlobalMiddleware,\n ServerErrorMiddleWare,\n ServerCallback\n} from './Server.types';\nimport { Endpoint } from 'entities/Endpoint';\nimport { EngineError } from 'entities/EngineError';\nimport { LifecycleController } from 'entities/LifecycleController';\nimport { PubSub } from 'entities/PubSub';\nimport { checkEnvironment, envAssert, createHttpServer } from 'utils';\nimport { initKeySets, shutdownKeySets } from 'utils/jwt';\nimport { reportInfo, reportError, reportDebug } from 'utils/report';\nimport { tlsConfig, loadTlsConfig, TlsConfig } from 'utils/tlsConfig';\n\nconst namespace = 'engine:Server';\n\n/** Main server class */\nexport class Server {\n /** The express application powering the main endpoints */\n private app: Application;\n /** The node HTTP(S) server, serving the main express application */\n private httpServer: http.Server | https.Server;\n /** The express application powering the support endpoints */\n private secondaryApp: Application;\n /** The node HTTP(S) server, serving the secondary express application */\n private secondaryHttpServer: http.Server | https.Server;\n /** List of endpoint instances that the server should handle */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private endpoints: Array<Endpoint<any>>;\n /** Mapping of env variable name with functions that should run to check their validity */\n private checkEnvironment: Record<string, ValidatorSpec<unknown>>;\n /** Middleware that should run on every request before the endpoint specific logic */\n private globalMiddleware: Array<ServerGlobalMiddleware>;\n /** Error middleware that should run on every request after the endpoint specific logic, but before the generic global middleware */\n private errorMiddleware: Array<\n ServerGlobalMiddleware | ServerErrorMiddleWare\n >;\n /** Functions that should be called on server startup */\n private initCallbacks: Array<ServerCallback>;\n /** Functions that should be called on server shutdown */\n private shutdownCallbacks: Array<ServerCallback>;\n /** When set to true, this indicates that init callbacks should run synchronously in the order they are declared. By default they all run in parallel */\n private syncCallbacks: boolean;\n /** Config to set regularly running jobs on the server */\n private cron?: Array<ServerCronJon>;\n /** Global auth related configuration */\n private auth: ServerAuthConfig;\n /** WebSocket server instance, if configured */\n private wss?: WebSocketServer;\n /** Abort controller that handles signals for the server */\n private abortController = new AbortController();\n\n /** Create a server */\n public constructor(config: ServerOptions) {\n this.validate(config);\n\n reportDebug({\n namespace,\n message: `Creating server`\n });\n\n this.endpoints = config.endpoints;\n this.globalMiddleware = config.globalMiddleware ?? [];\n this.errorMiddleware = config.errorMiddleware ?? [];\n this.initCallbacks = config.initCallbacks ?? [];\n this.shutdownCallbacks = config.shutdownCallbacks ?? [];\n this.syncCallbacks = config.syncCallbacks ?? false;\n this.cron = config.cron;\n this.auth = { noFetch: false, ...config.auth };\n\n // The minimal environment check is different between the migration modes of execution and the regular server execution mode\n // When running in migration mode, we only check that we have SQL related environment variables\n this.checkEnvironment =\n process.env.RUN_MIGRATION === 'true' ||\n process.env.UNDO_MIGRATION === 'true'\n ? {\n SQL_HOST: envAssert.isHost(),\n SQL_USER: envAssert.isString(),\n SQL_PASSWORD: envAssert.isString(),\n SQL_DB: envAssert.isString()\n }\n : {\n NODE_ENV: envAssert.isString(),\n PORT: envAssert.isPort(),\n SECONDARY_PORT: envAssert.isPort(),\n ...config.checkEnvironment\n };\n\n checkEnvironment(this.checkEnvironment);\n\n // Create the primary express core that serves the business logic\n this.app = express();\n\n this.app.use(\n cors({\n origin: process.env.CORS_ORIGIN ?? '*',\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization']\n })\n );\n\n this.registerMiddleware(this.app);\n this.httpServer = createHttpServer(this.app);\n\n // Create a secondary express core that servers metrics and analytics\n // This will be served on a different port, allowing to specify different firewall rules for these routes\n this.secondaryApp = express();\n [...secondaryMiddleware, ...defaultErrorMiddleware].forEach(\n (middleware) => {\n if (middleware) this.secondaryApp.use(middleware);\n }\n );\n this.secondaryHttpServer = createHttpServer(this.secondaryApp);\n\n // Remove the express headers\n this.app.disable('x-powered-by');\n this.secondaryApp.disable('x-powered-by');\n\n // We remove all node timeout on connections and requests\n // This responsibility is delegated to the front-facing load balancer\n [this.httpServer, this.secondaryHttpServer].forEach((server) => {\n server.headersTimeout = 0;\n server.keepAliveTimeout = 0;\n });\n\n if (config.webSocket) {\n this.attachWebSocket(config.webSocket);\n }\n }\n\n /** Start the server */\n public async init(): Promise<void> {\n reportDebug({ namespace, message: `Starting Server` });\n // Fetch the keys for JWT authentication\n // Ignore in test environment as we do symmetrical token signing\n if (process.env.NODE_ENV !== 'test' && !this.auth.noFetch)\n await initKeySets();\n // Run all the init callbacks\n // We can either run them all 1/1 synchronously or run them all in parallel (default)\n // Callback can either be direct function or an object with function and parameters attributes\n\n if (this.syncCallbacks) {\n for (const callback of this.initCallbacks) {\n await this.runCallback(callback);\n }\n } else {\n await Promise.all(\n this.initCallbacks.map((callback) => this.runCallback(callback))\n );\n }\n\n // Initialize the Pub/Sub module\n await PubSub.init();\n // Run all the cronjob\n if (this.cron) {\n this.cron.forEach((job) => {\n this.runCronJob(job);\n });\n }\n\n // Start listening to requests\n await Promise.all([\n new Promise<void>((resolve) =>\n this.httpServer.listen(process.env.PORT, () => {\n resolve();\n })\n ),\n new Promise<void>((resolve) =>\n this.secondaryHttpServer.listen(process.env.SECONDARY_PORT, () => {\n resolve();\n })\n )\n ]);\n reportInfo({\n message: 'SERVER_RUNNING',\n data: {\n port: process.env.PORT,\n secondaryPort: process.env.SECONDARY_PORT,\n nodeEnv: process.env.NODE_ENV\n }\n });\n\n this.handleSignal();\n\n // Watch TSL config files changing\n this.watchTlsCaFileChange();\n }\n\n /** Graceful shutdown */\n public async shutdown(): Promise<void> {\n reportDebug({ namespace, message: `Shutting down Server` });\n\n // Stop listening to HTTP requests\n await Promise.all(\n [this.httpServer, this.secondaryHttpServer].map((server) => {\n // Ignore if the server was already stopped\n // We have observed cases where SIGTERM was thrown multiple times\n if (!server.listening) return;\n const { port } = server.address() as AddressInfo;\n server.on('close', (error: unknown) => {\n if (error) {\n reportError(error);\n process.exit(1);\n } else {\n reportInfo({\n message: `Stopped listening to requests on port ${port}`\n });\n }\n });\n server.close();\n })\n );\n\n // Close all socket connections\n if (this.wss) {\n // First ask then to close gracefully\n this.wss.clients.forEach((socket) => {\n socket.close();\n });\n // Hard terminate all connections still open after a defined time\n const socketKillTimeout = process.env.WEBSOCKET_CLOSE_TIMEOUT\n ? parseInt(process.env.WEBSOCKET_CLOSE_TIMEOUT)\n : 5000;\n await new Promise<void>((resolve) => {\n setTimeout(() => {\n (this.wss as WebSocketServer).clients.forEach((socket) => {\n if (\n socket.readyState === socket.CLOSING ||\n socket.readyState === socket.OPEN\n ) {\n socket.terminate();\n }\n });\n resolve();\n }, socketKillTimeout);\n });\n }\n\n // Run all the shutdown callback\n if (this.syncCallbacks) {\n for (const callback of this.shutdownCallbacks) {\n await this.runCallback(callback);\n }\n } else {\n await Promise.all(\n this.shutdownCallbacks.map((callback) => this.runCallback(callback))\n );\n }\n\n // Shutdown all the running entities\n await LifecycleController.shutdownRunningInstances();\n\n // Abort all the cronjob\n if (this.abortController) this.abortController.abort();\n\n // Stop the keyset timers\n shutdownKeySets();\n\n shutdownSignals.forEach((signal) => process.removeAllListeners(signal));\n }\n\n /** Get the express app instance of the server */\n public getApp(): Application {\n return this.app;\n }\n\n /** Get the express app instance of the server */\n public getSecondaryApp(): Application {\n return this.secondaryApp;\n }\n\n /** Registers all the middleware with the express app */\n private registerMiddleware(app: Application): void {\n // Global middleware can specify a path to which they apply\n [...defaultMiddleware, ...this.globalMiddleware].forEach((middleware) => {\n if (typeof middleware === 'function') {\n app.use(middleware);\n } else {\n app.use(middleware.path, middleware.middleware);\n }\n });\n // Call the register method of each endpoint\n this.endpoints.forEach((endpoint) => {\n endpoint.register(app);\n });\n // Add error middleware\n [...this.errorMiddleware, ...defaultErrorMiddleware].forEach(\n (middleware) => {\n if (typeof middleware === 'function') {\n app.use(middleware);\n } else {\n app.use(middleware.path, middleware.middleware);\n }\n }\n );\n }\n\n /** Add a handler for WebSocket connections */\n private attachWebSocket(options: ServerWebSocketConfig): void {\n this.wss = new WebSocketServer({\n ...options.server,\n server: this.httpServer\n })\n .on('connection', (webSocket, request) => {\n new SocketClient(webSocket, request, options.client);\n })\n .on('error', (error) => {\n reportError(error);\n });\n }\n\n /** Call shutdown if signal gets sent to the process */\n private handleSignal(): void {\n shutdownSignals.forEach((signal) =>\n process.on(signal, () => {\n (async (): Promise<void> => {\n reportInfo(`Received shutdown signal [${signal}]`);\n await this.shutdown();\n })().catch((error) => {\n reportError(error);\n });\n })\n );\n }\n\n /** Watch the TLS certificate change event */\n private watchTlsCaFileChange(): void {\n [\n 'TLS_REQUEST_KEY',\n 'TLS_REQUEST_KEY_PASSPHRASE',\n 'TLS_REQUEST_CERT',\n 'TLS_REQUEST_CA',\n 'TLS_SERVER_KEY',\n 'TLS_SERVER_KEY_PASSPHRASE',\n 'TLS_SERVER_CERT',\n 'TLS_CA'\n ].forEach((key) => {\n const file = process.env[key];\n if (file)\n fs.watch(path.resolve(file), (event) => {\n if (event !== 'change') return;\n reportInfo(`Reloading TLS config [${file} changed]`);\n loadTlsConfig();\n (this.httpServer as https.Server).setSecureContext(\n tlsConfig as TlsConfig\n );\n });\n });\n }\n\n /** Executes a recurring job */\n private runCronJob(job: ServerCronJon): void {\n (async (): Promise<void> => {\n reportInfo({ message: `Starting scheduled job \"${job.handler.name}\"` });\n try {\n await job.handler();\n reportInfo({\n message: `Job \"${job.handler.name}\" finished, starting again in ${job.interval} seconds`\n });\n await setTimeoutPromise(job.interval * 1000, undefined, {\n signal: this.abortController.signal\n });\n this.runCronJob(job);\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n reportDebug({\n namespace,\n message: `Scheduled job \"${job.handler?.name}\" has been aborted`\n });\n } else {\n throw error;\n }\n }\n })().catch((error) => {\n reportError(error);\n });\n }\n\n /** Run a shutdown/init callback */\n private runCallback(callback: ServerCallback): unknown {\n return typeof callback === 'function'\n ? callback()\n : callback.function.apply(null, callback.parameters ?? []);\n }\n\n /**\n * Validate the Server constructor configuration\n */\n private validate(config: ServerOptions): void {\n if (\n !config.endpoints ||\n config.endpoints.some((endpoint) => !(endpoint instanceof Endpoint))\n )\n throw new EngineError(\n 'Server endpoints should be declared and all be Endpoint instances'\n );\n\n if (\n config.checkEnvironment &&\n Object.values(config.checkEnvironment).some(\n (validator) => typeof validator !== 'function'\n )\n )\n throw new EngineError(\n 'Some of the environment checks are not valid functions'\n );\n\n if (\n config.initCallbacks &&\n (!(config.initCallbacks instanceof Array) ||\n config.initCallbacks.some(\n (callback) =>\n !(\n typeof callback === 'function' ||\n typeof callback?.function === 'function'\n )\n ))\n )\n throw new EngineError('Init callbacks is not a list of functions');\n\n if (\n config.shutdownCallbacks &&\n (!(config.shutdownCallbacks instanceof Array) ||\n config.shutdownCallbacks.some(\n (callback) =>\n !(\n typeof callback === 'function' ||\n typeof callback?.function === 'function'\n )\n ))\n )\n throw new EngineError('Shutdown callbacks is not a list of functions');\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,11 +1,7 @@
1
- import { EmailRecipient } from './sendEmail.types';
1
+ import { EmailOptions, EmailResult } from './sendEmail.types';
2
2
  /**
3
- * Send an email through the email service
4
- * @param userId - ID of the user that should receive the email
5
- * @param template - Email template to use
6
- * @param translationKey - Key used to get localized strings
7
- * @param parameters - Variables that can be inserted in the email template and localized strings
8
- * @param tags - Data injected in the localized strings as HTML tags
9
- * @param to - Users to which the email should be sent
3
+ * Sends an email using Nodemailer.
4
+ * @param emailOptions - Configuration options for sending the email.
5
+ * @returns A Promise that resolves with an EmailResult indicating success or failure.
10
6
  */
11
- export declare function sendEmail(userId: string, template: string, translationKey: string, parameters?: unknown, tags?: unknown, to?: Array<EmailRecipient>): Promise<unknown>;
7
+ export declare const sendEmail: (emailOptions: EmailOptions) => Promise<EmailResult>;
@@ -1,32 +1,67 @@
1
1
  "use strict";
2
2
 
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
3
4
  Object.defineProperty(exports, "__esModule", {
4
5
  value: true
5
6
  });
6
- exports.sendEmail = sendEmail;
7
- var _tlsRequest = require("../tlsRequest");
7
+ exports.sendEmail = void 0;
8
+ var _nodemailer = _interopRequireDefault(require("nodemailer"));
8
9
  /**
9
- * Send an email through the email service
10
- * @param userId - ID of the user that should receive the email
11
- * @param template - Email template to use
12
- * @param translationKey - Key used to get localized strings
13
- * @param parameters - Variables that can be inserted in the email template and localized strings
14
- * @param tags - Data injected in the localized strings as HTML tags
15
- * @param to - Users to which the email should be sent
10
+ * Sends an email using Nodemailer.
11
+ * @param emailOptions - Configuration options for sending the email.
12
+ * @returns A Promise that resolves with an EmailResult indicating success or failure.
16
13
  */
17
- async function sendEmail(userId, template, translationKey, parameters, tags, to) {
18
- return (0, _tlsRequest.tlsRequest)({
19
- method: 'post',
20
- url: '/send',
21
- baseURL: process.env.EMAIL_SERVICE_URL,
22
- data: {
23
- userId,
24
- template,
25
- translationKey,
26
- parameters,
27
- tags,
28
- to
14
+ const sendEmail = async emailOptions => {
15
+ try {
16
+ // Create a transport with SMTP configuration
17
+ const transporter = _nodemailer.default.createTransport({
18
+ host: process.env.SMTP_HOST,
19
+ port: parseInt(process.env.SMTP_PORT || '587', 10),
20
+ secure: process.env.SMTP_PORT === '465' ? true : false,
21
+ auth: {
22
+ user: process.env.SMTP_USER,
23
+ pass: process.env.SMTP_PASS
24
+ },
25
+ tls: {
26
+ rejectUnauthorized: process.env.SMTP_PORT === '465' ? true : false
27
+ }
28
+ });
29
+
30
+ // Construct email options
31
+ const mailOptions = {
32
+ from: emailOptions.from || 'your-email@example.com',
33
+ to: emailOptions.to,
34
+ cc: emailOptions.cc,
35
+ bcc: emailOptions.bcc,
36
+ subject: emailOptions.subject,
37
+ text: emailOptions.text,
38
+ html: emailOptions.html,
39
+ attachments: emailOptions.attachments,
40
+ replyTo: emailOptions.replyTo,
41
+ headers: emailOptions.headers,
42
+ priority: emailOptions.priority
43
+ };
44
+
45
+ // Send email
46
+ const info = await transporter.sendMail(mailOptions);
47
+
48
+ // Determine email status
49
+ let status = 'sent';
50
+ if (info.response.includes('queued')) {
51
+ status = 'queued';
52
+ } else if (info.response.includes('delivered')) {
53
+ status = 'delivered';
29
54
  }
30
- });
31
- }
55
+ return {
56
+ status,
57
+ messageId: info.messageId
58
+ };
59
+ } catch (error) {
60
+ return {
61
+ status: 'failed',
62
+ error: error.message
63
+ };
64
+ }
65
+ };
66
+ exports.sendEmail = sendEmail;
32
67
  //# sourceMappingURL=sendEmail.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sendEmail.js","names":["_tlsRequest","require","sendEmail","userId","template","translationKey","parameters","tags","to","tlsRequest","method","url","baseURL","process","env","EMAIL_SERVICE_URL","data"],"sources":["../../../src/utils/sendEmail/sendEmail.ts"],"sourcesContent":["import { EmailRecipient } from './sendEmail.types';\nimport { tlsRequest } from 'utils/tlsRequest';\n\n/**\n * Send an email through the email service\n * @param userId - ID of the user that should receive the email\n * @param template - Email template to use\n * @param translationKey - Key used to get localized strings\n * @param parameters - Variables that can be inserted in the email template and localized strings\n * @param tags - Data injected in the localized strings as HTML tags\n * @param to - Users to which the email should be sent\n */\nexport async function sendEmail(\n userId: string,\n template: string,\n translationKey: string,\n parameters?: unknown,\n tags?: unknown,\n to?: Array<EmailRecipient>\n): Promise<unknown> {\n return tlsRequest({\n method: 'post',\n url: '/send',\n baseURL: process.env.EMAIL_SERVICE_URL,\n data: { userId, template, translationKey, parameters, tags, to }\n });\n}\n"],"mappings":";;;;;;AACA,IAAAA,WAAA,GAAAC,OAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,eAAeC,SAASA,CAC7BC,MAAc,EACdC,QAAgB,EAChBC,cAAsB,EACtBC,UAAoB,EACpBC,IAAc,EACdC,EAA0B,EACR;EAClB,OAAO,IAAAC,sBAAU,EAAC;IAChBC,MAAM,EAAE,MAAM;IACdC,GAAG,EAAE,OAAO;IACZC,OAAO,EAAEC,OAAO,CAACC,GAAG,CAACC,iBAAiB;IACtCC,IAAI,EAAE;MAAEb,MAAM;MAAEC,QAAQ;MAAEC,cAAc;MAAEC,UAAU;MAAEC,IAAI;MAAEC;IAAG;EACjE,CAAC,CAAC;AACJ","ignoreList":[]}
1
+ {"version":3,"file":"sendEmail.js","names":["_nodemailer","_interopRequireDefault","require","sendEmail","emailOptions","transporter","nodemailer","createTransport","host","process","env","SMTP_HOST","port","parseInt","SMTP_PORT","secure","auth","user","SMTP_USER","pass","SMTP_PASS","tls","rejectUnauthorized","mailOptions","from","to","cc","bcc","subject","text","html","attachments","replyTo","headers","priority","info","sendMail","status","response","includes","messageId","error","message","exports"],"sources":["../../../src/utils/sendEmail/sendEmail.ts"],"sourcesContent":["import { EmailOptions, EmailResult, EmailStatus } from './sendEmail.types';\nimport nodemailer, {\n Transporter,\n SendMailOptions,\n SentMessageInfo\n} from 'nodemailer';\n\n/**\n * Sends an email using Nodemailer.\n * @param emailOptions - Configuration options for sending the email.\n * @returns A Promise that resolves with an EmailResult indicating success or failure.\n */\nexport const sendEmail = async (\n emailOptions: EmailOptions\n): Promise<EmailResult> => {\n try {\n // Create a transport with SMTP configuration\n const transporter: Transporter = nodemailer.createTransport({\n host: process.env.SMTP_HOST,\n port: parseInt(process.env.SMTP_PORT || '587', 10),\n secure: process.env.SMTP_PORT === '465' ? true : false,\n auth: {\n user: process.env.SMTP_USER,\n pass: process.env.SMTP_PASS\n },\n tls: {\n rejectUnauthorized: process.env.SMTP_PORT === '465' ? true : false\n }\n });\n\n // Construct email options\n const mailOptions: SendMailOptions = {\n from: emailOptions.from || 'your-email@example.com',\n to: emailOptions.to,\n cc: emailOptions.cc,\n bcc: emailOptions.bcc,\n subject: emailOptions.subject,\n text: emailOptions.text,\n html: emailOptions.html,\n attachments: emailOptions.attachments,\n replyTo: emailOptions.replyTo,\n headers: emailOptions.headers,\n priority: emailOptions.priority\n };\n\n // Send email\n const info: SentMessageInfo = await transporter.sendMail(mailOptions);\n\n // Determine email status\n let status: EmailStatus = 'sent';\n if (info.response.includes('queued')) {\n status = 'queued';\n } else if (info.response.includes('delivered')) {\n status = 'delivered';\n }\n\n return { status, messageId: info.messageId };\n } catch (error) {\n return { status: 'failed', error: (error as Error).message };\n }\n};\n"],"mappings":";;;;;;;AACA,IAAAA,WAAA,GAAAC,sBAAA,CAAAC,OAAA;AAMA;AACA;AACA;AACA;AACA;AACO,MAAMC,SAAS,GAAG,MACvBC,YAA0B,IACD;EACzB,IAAI;IACF;IACA,MAAMC,WAAwB,GAAGC,mBAAU,CAACC,eAAe,CAAC;MAC1DC,IAAI,EAAEC,OAAO,CAACC,GAAG,CAACC,SAAS;MAC3BC,IAAI,EAAEC,QAAQ,CAACJ,OAAO,CAACC,GAAG,CAACI,SAAS,IAAI,KAAK,EAAE,EAAE,CAAC;MAClDC,MAAM,EAAEN,OAAO,CAACC,GAAG,CAACI,SAAS,KAAK,KAAK,GAAG,IAAI,GAAG,KAAK;MACtDE,IAAI,EAAE;QACJC,IAAI,EAAER,OAAO,CAACC,GAAG,CAACQ,SAAS;QAC3BC,IAAI,EAAEV,OAAO,CAACC,GAAG,CAACU;MACpB,CAAC;MACDC,GAAG,EAAE;QACHC,kBAAkB,EAAEb,OAAO,CAACC,GAAG,CAACI,SAAS,KAAK,KAAK,GAAG,IAAI,GAAG;MAC/D;IACF,CAAC,CAAC;;IAEF;IACA,MAAMS,WAA4B,GAAG;MACnCC,IAAI,EAAEpB,YAAY,CAACoB,IAAI,IAAI,wBAAwB;MACnDC,EAAE,EAAErB,YAAY,CAACqB,EAAE;MACnBC,EAAE,EAAEtB,YAAY,CAACsB,EAAE;MACnBC,GAAG,EAAEvB,YAAY,CAACuB,GAAG;MACrBC,OAAO,EAAExB,YAAY,CAACwB,OAAO;MAC7BC,IAAI,EAAEzB,YAAY,CAACyB,IAAI;MACvBC,IAAI,EAAE1B,YAAY,CAAC0B,IAAI;MACvBC,WAAW,EAAE3B,YAAY,CAAC2B,WAAW;MACrCC,OAAO,EAAE5B,YAAY,CAAC4B,OAAO;MAC7BC,OAAO,EAAE7B,YAAY,CAAC6B,OAAO;MAC7BC,QAAQ,EAAE9B,YAAY,CAAC8B;IACzB,CAAC;;IAED;IACA,MAAMC,IAAqB,GAAG,MAAM9B,WAAW,CAAC+B,QAAQ,CAACb,WAAW,CAAC;;IAErE;IACA,IAAIc,MAAmB,GAAG,MAAM;IAChC,IAAIF,IAAI,CAACG,QAAQ,CAACC,QAAQ,CAAC,QAAQ,CAAC,EAAE;MACpCF,MAAM,GAAG,QAAQ;IACnB,CAAC,MAAM,IAAIF,IAAI,CAACG,QAAQ,CAACC,QAAQ,CAAC,WAAW,CAAC,EAAE;MAC9CF,MAAM,GAAG,WAAW;IACtB;IAEA,OAAO;MAAEA,MAAM;MAAEG,SAAS,EAAEL,IAAI,CAACK;IAAU,CAAC;EAC9C,CAAC,CAAC,OAAOC,KAAK,EAAE;IACd,OAAO;MAAEJ,MAAM,EAAE,QAAQ;MAAEI,KAAK,EAAGA,KAAK,CAAWC;IAAQ,CAAC;EAC9D;AACF,CAAC;AAACC,OAAA,CAAAxC,SAAA,GAAAA,SAAA","ignoreList":[]}
@@ -1,7 +1,51 @@
1
- /** Structure representing an email recipient */
2
- export interface EmailRecipient {
3
- /** Email of the person to which the email should be sent */
4
- email: string;
5
- /** Name of the person receiving the email */
6
- name?: string;
1
+ /** Structure representing email options for sending an email */
2
+ export interface EmailOptions {
3
+ /** Sender's email address (optional, defaults to the authenticated user) */
4
+ from?: string;
5
+ /** Recipient(s) of the email - can be a string or an array of strings */
6
+ to: string | string[];
7
+ /** Carbon Copy (CC) recipients (optional) */
8
+ cc?: string | string[];
9
+ /** Blind Carbon Copy (BCC) recipients (optional) */
10
+ bcc?: string | string[];
11
+ /** Subject of the email */
12
+ subject: string;
13
+ /** Plain text version of the email body (optional) */
14
+ text?: string;
15
+ /** HTML version of the email body (optional) */
16
+ html?: string;
17
+ /** Attachments to include in the email (optional) */
18
+ attachments?: {
19
+ /** Name of the attached file */
20
+ filename: string;
21
+ /** Content of the attachment (Buffer or string) */
22
+ content?: string | Buffer;
23
+ /** File path of the attachment (alternative to `content`) */
24
+ path?: string;
25
+ /** MIME type of the attachment */
26
+ contentType?: string;
27
+ /** Encoding of the attachment (e.g., base64, utf-8) */
28
+ encoding?: string;
29
+ /** Content ID for inline images (optional) */
30
+ cid?: string;
31
+ }[];
32
+ /** Email address for replies (optional) */
33
+ replyTo?: string;
34
+ /** Custom headers for the email (optional) */
35
+ headers?: {
36
+ [key: string]: string;
37
+ };
38
+ /** Email priority level (optional) */
39
+ priority?: 'high' | 'normal' | 'low';
40
+ }
41
+ /** Possible statuses for email delivery */
42
+ export type EmailStatus = 'sent' | 'delivered' | 'queued' | 'failed';
43
+ /** Structure representing the result of an email sending operation */
44
+ export interface EmailResult {
45
+ /** Status of the email operation */
46
+ status: EmailStatus;
47
+ /** Message ID returned by the mail server (if available) */
48
+ messageId?: string;
49
+ /** Error message in case of failure (if any) */
50
+ error?: string;
7
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sendEmail.types.js","names":[],"sources":["../../../src/utils/sendEmail/sendEmail.types.ts"],"sourcesContent":["/** Structure representing an email recipient */\nexport interface EmailRecipient {\n /** Email of the person to which the email should be sent */\n email: string;\n /** Name of the person receiving the email */\n name?: string;\n}\n"],"mappings":"","ignoreList":[]}
1
+ {"version":3,"file":"sendEmail.types.js","names":[],"sources":["../../../src/utils/sendEmail/sendEmail.types.ts"],"sourcesContent":["/** Structure representing email options for sending an email */\nexport interface EmailOptions {\n /** Sender's email address (optional, defaults to the authenticated user) */\n from?: string;\n\n /** Recipient(s) of the email - can be a string or an array of strings */\n to: string | string[];\n\n /** Carbon Copy (CC) recipients (optional) */\n cc?: string | string[];\n\n /** Blind Carbon Copy (BCC) recipients (optional) */\n bcc?: string | string[];\n\n /** Subject of the email */\n subject: string;\n\n /** Plain text version of the email body (optional) */\n text?: string;\n\n /** HTML version of the email body (optional) */\n html?: string;\n\n /** Attachments to include in the email (optional) */\n attachments?: {\n /** Name of the attached file */\n filename: string;\n /** Content of the attachment (Buffer or string) */\n content?: string | Buffer;\n /** File path of the attachment (alternative to `content`) */\n path?: string;\n /** MIME type of the attachment */\n contentType?: string;\n /** Encoding of the attachment (e.g., base64, utf-8) */\n encoding?: string;\n /** Content ID for inline images (optional) */\n cid?: string;\n }[];\n\n /** Email address for replies (optional) */\n replyTo?: string;\n\n /** Custom headers for the email (optional) */\n headers?: { [key: string]: string };\n\n /** Email priority level (optional) */\n priority?: 'high' | 'normal' | 'low';\n}\n\n/** Possible statuses for email delivery */\nexport type EmailStatus = 'sent' | 'delivered' | 'queued' | 'failed';\n\n/** Structure representing the result of an email sending operation */\nexport interface EmailResult {\n /** Status of the email operation */\n status: EmailStatus;\n /** Message ID returned by the mail server (if available) */\n messageId?: string;\n /** Error message in case of failure (if any) */\n error?: string;\n}\n"],"mappings":"","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-server-engine",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Framework used to develop backend services. This package ships with a lot of features to standardize the creation of services, letting you focus on the business logic.",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -71,6 +71,7 @@
71
71
  "multer": "^1.4.5-lts.1",
72
72
  "mysql2": "^3.11.5",
73
73
  "nock": "^13.5.6",
74
+ "nodemailer": "^6.10.0",
74
75
  "nodemon": "^3.1.9",
75
76
  "pg": "^8.13.1",
76
77
  "prom-client": "^15.1.3",
@@ -119,6 +120,7 @@
119
120
  "@types/morgan": "^1.9.9",
120
121
  "@types/multer": "^1.4.12",
121
122
  "@types/node": "^22.10.2",
123
+ "@types/nodemailer": "^6.4.17",
122
124
  "@types/response-time": "^2.3.8",
123
125
  "@types/sequelize": "^4.28.20",
124
126
  "@types/sinon-chai": "^4.0.0",
@@ -120,10 +120,6 @@ export class Server {
120
120
  })
121
121
  );
122
122
 
123
- this.app.options('*', (req, res) => {
124
- res.sendStatus(204);
125
- });
126
-
127
123
  this.registerMiddleware(this.app);
128
124
  this.httpServer = createHttpServer(this.app);
129
125
 
@@ -1,27 +1,61 @@
1
- import { EmailRecipient } from './sendEmail.types';
2
- import { tlsRequest } from 'utils/tlsRequest';
1
+ import { EmailOptions, EmailResult, EmailStatus } from './sendEmail.types';
2
+ import nodemailer, {
3
+ Transporter,
4
+ SendMailOptions,
5
+ SentMessageInfo
6
+ } from 'nodemailer';
3
7
 
4
8
  /**
5
- * Send an email through the email service
6
- * @param userId - ID of the user that should receive the email
7
- * @param template - Email template to use
8
- * @param translationKey - Key used to get localized strings
9
- * @param parameters - Variables that can be inserted in the email template and localized strings
10
- * @param tags - Data injected in the localized strings as HTML tags
11
- * @param to - Users to which the email should be sent
9
+ * Sends an email using Nodemailer.
10
+ * @param emailOptions - Configuration options for sending the email.
11
+ * @returns A Promise that resolves with an EmailResult indicating success or failure.
12
12
  */
13
- export async function sendEmail(
14
- userId: string,
15
- template: string,
16
- translationKey: string,
17
- parameters?: unknown,
18
- tags?: unknown,
19
- to?: Array<EmailRecipient>
20
- ): Promise<unknown> {
21
- return tlsRequest({
22
- method: 'post',
23
- url: '/send',
24
- baseURL: process.env.EMAIL_SERVICE_URL,
25
- data: { userId, template, translationKey, parameters, tags, to }
26
- });
27
- }
13
+ export const sendEmail = async (
14
+ emailOptions: EmailOptions
15
+ ): Promise<EmailResult> => {
16
+ try {
17
+ // Create a transport with SMTP configuration
18
+ const transporter: Transporter = nodemailer.createTransport({
19
+ host: process.env.SMTP_HOST,
20
+ port: parseInt(process.env.SMTP_PORT || '587', 10),
21
+ secure: process.env.SMTP_PORT === '465' ? true : false,
22
+ auth: {
23
+ user: process.env.SMTP_USER,
24
+ pass: process.env.SMTP_PASS
25
+ },
26
+ tls: {
27
+ rejectUnauthorized: process.env.SMTP_PORT === '465' ? true : false
28
+ }
29
+ });
30
+
31
+ // Construct email options
32
+ const mailOptions: SendMailOptions = {
33
+ from: emailOptions.from || 'your-email@example.com',
34
+ to: emailOptions.to,
35
+ cc: emailOptions.cc,
36
+ bcc: emailOptions.bcc,
37
+ subject: emailOptions.subject,
38
+ text: emailOptions.text,
39
+ html: emailOptions.html,
40
+ attachments: emailOptions.attachments,
41
+ replyTo: emailOptions.replyTo,
42
+ headers: emailOptions.headers,
43
+ priority: emailOptions.priority
44
+ };
45
+
46
+ // Send email
47
+ const info: SentMessageInfo = await transporter.sendMail(mailOptions);
48
+
49
+ // Determine email status
50
+ let status: EmailStatus = 'sent';
51
+ if (info.response.includes('queued')) {
52
+ status = 'queued';
53
+ } else if (info.response.includes('delivered')) {
54
+ status = 'delivered';
55
+ }
56
+
57
+ return { status, messageId: info.messageId };
58
+ } catch (error) {
59
+ return { status: 'failed', error: (error as Error).message };
60
+ }
61
+ };
@@ -1,7 +1,61 @@
1
- /** Structure representing an email recipient */
2
- export interface EmailRecipient {
3
- /** Email of the person to which the email should be sent */
4
- email: string;
5
- /** Name of the person receiving the email */
6
- name?: string;
1
+ /** Structure representing email options for sending an email */
2
+ export interface EmailOptions {
3
+ /** Sender's email address (optional, defaults to the authenticated user) */
4
+ from?: string;
5
+
6
+ /** Recipient(s) of the email - can be a string or an array of strings */
7
+ to: string | string[];
8
+
9
+ /** Carbon Copy (CC) recipients (optional) */
10
+ cc?: string | string[];
11
+
12
+ /** Blind Carbon Copy (BCC) recipients (optional) */
13
+ bcc?: string | string[];
14
+
15
+ /** Subject of the email */
16
+ subject: string;
17
+
18
+ /** Plain text version of the email body (optional) */
19
+ text?: string;
20
+
21
+ /** HTML version of the email body (optional) */
22
+ html?: string;
23
+
24
+ /** Attachments to include in the email (optional) */
25
+ attachments?: {
26
+ /** Name of the attached file */
27
+ filename: string;
28
+ /** Content of the attachment (Buffer or string) */
29
+ content?: string | Buffer;
30
+ /** File path of the attachment (alternative to `content`) */
31
+ path?: string;
32
+ /** MIME type of the attachment */
33
+ contentType?: string;
34
+ /** Encoding of the attachment (e.g., base64, utf-8) */
35
+ encoding?: string;
36
+ /** Content ID for inline images (optional) */
37
+ cid?: string;
38
+ }[];
39
+
40
+ /** Email address for replies (optional) */
41
+ replyTo?: string;
42
+
43
+ /** Custom headers for the email (optional) */
44
+ headers?: { [key: string]: string };
45
+
46
+ /** Email priority level (optional) */
47
+ priority?: 'high' | 'normal' | 'low';
48
+ }
49
+
50
+ /** Possible statuses for email delivery */
51
+ export type EmailStatus = 'sent' | 'delivered' | 'queued' | 'failed';
52
+
53
+ /** Structure representing the result of an email sending operation */
54
+ export interface EmailResult {
55
+ /** Status of the email operation */
56
+ status: EmailStatus;
57
+ /** Message ID returned by the mail server (if available) */
58
+ messageId?: string;
59
+ /** Error message in case of failure (if any) */
60
+ error?: string;
7
61
  }