Skip to content

Commit

Permalink
UBERF-9605: Test MTA hook integration (#8189)
Browse files Browse the repository at this point in the history
* UBERF-9605: Test MTA hook service

Signed-off-by: Artem Savchenko <[email protected]>

* UBERF-9605: pod-hook config

Signed-off-by: Artem Savchenko <[email protected]>

* UBERF-9605: Minor fixes

Signed-off-by: Artem Savchenko <[email protected]>

* UBERF-9605: Add TODO

Signed-off-by: Artem Savchenko <[email protected]>

---------

Signed-off-by: Artem Savchenko <[email protected]>
  • Loading branch information
ArtyomSavchenko authored Mar 11, 2025
1 parent 03584ae commit f94d8bf
Show file tree
Hide file tree
Showing 18 changed files with 427 additions and 2 deletions.
2 changes: 1 addition & 1 deletion common/config/rush/command-line.json
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@
"summary": "Build docker with platform",
"description": "use to build all docker containers required for platform",
"safeForSimultaneousRushProcesses": true,
"shellCommand": "rush docker:build -p 20 --to @hcengineering/pod-server --to @hcengineering/pod-front --to @hcengineering/prod --to @hcengineering/pod-account --to @hcengineering/pod-workspace --to @hcengineering/pod-collaborator --to @hcengineering/tool --to @hcengineering/pod-print --to @hcengineering/pod-sign --to @hcengineering/pod-analytics-collector --to @hcengineering/rekoni-service --to @hcengineering/pod-ai-bot --to @hcengineering/import-tool --to @hcengineering/pod-stats --to @hcengineering/pod-fulltext --to @hcengineering/pod-love --to @hcengineering/green --to @hcengineering/pod-mail --to @hcengineering/pod-datalake"
"shellCommand": "rush docker:build -p 20 --to @hcengineering/pod-server --to @hcengineering/pod-front --to @hcengineering/prod --to @hcengineering/pod-account --to @hcengineering/pod-workspace --to @hcengineering/pod-collaborator --to @hcengineering/tool --to @hcengineering/pod-print --to @hcengineering/pod-sign --to @hcengineering/pod-analytics-collector --to @hcengineering/rekoni-service --to @hcengineering/pod-ai-bot --to @hcengineering/import-tool --to @hcengineering/pod-stats --to @hcengineering/pod-fulltext --to @hcengineering/pod-love --to @hcengineering/green --to @hcengineering/pod-mail --to @hcengineering/pod-datalake --to @hcengineering/pod-hook"
},
{
"commandKind": "global",
Expand Down
44 changes: 43 additions & 1 deletion common/config/rush/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions rush.json
Original file line number Diff line number Diff line change
Expand Up @@ -2362,6 +2362,11 @@
"packageName": "@hcengineering/pod-mail",
"projectFolder": "services/mail/pod-mail",
"shouldPublish": false
},
{
"packageName": "@hcengineering/pod-hook",
"projectFolder": "services/hook/pod-hook",
"shouldPublish": false
}
]
}
7 changes: 7 additions & 0 deletions services/hook/pod-hook/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
1 change: 1 addition & 0 deletions services/hook/pod-hook/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.env
4 changes: 4 additions & 0 deletions services/hook/pod-hook/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
7 changes: 7 additions & 0 deletions services/hook/pod-hook/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM hardcoreeng/base:v20250310
WORKDIR /usr/src/app

COPY bundle/bundle.js ./

EXPOSE 8098
CMD [ "node", "bundle.js" ]
19 changes: 19 additions & 0 deletions services/hook/pod-hook/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
#
# Copyright © 2025 Hardcore Engineering Inc.
#
# Licensed under the Eclipse Public License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. You may
# obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
# See the License for the specific language governing permissions and
# limitations under the License.
#

rushx bundle
rushx docker:build
rushx docker:push
4 changes: 4 additions & 0 deletions services/hook/pod-hook/config/rig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}
7 changes: 7 additions & 0 deletions services/hook/pod-hook/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}
60 changes: 60 additions & 0 deletions services/hook/pod-hook/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"name": "@hcengineering/pod-hook",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"files": [
"lib/**/*",
"types/**/*",
"tsconfig.json"
],
"author": "Hardcore Engineering Inc.",
"scripts": {
"build": "compile",
"build:watch": "compile",
"test": "jest --passWithNoTests --silent",
"_phase:bundle": "rushx bundle",
"_phase:docker-build": "rushx docker:build",
"_phase:docker-staging": "rushx docker:staging",
"bundle": "node ../../../common/scripts/esbuild.js",
"docker:build": "../../../common/scripts/docker_build.sh hardcoreeng/hook",
"docker:staging": "../../../common/scripts/docker_tag.sh hardcoreeng/hook staging",
"docker:abuild": "docker build -t hardcoreeng/hook . --platform=linux/arm64 && ../../../common/scripts/docker_tag_push.sh hardcoreeng/hook",
"docker:push": "../../../common/scripts/docker_tag.sh hardcoreeng/hook",
"run-local": "ts-node src/index.ts",
"format": "format src",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"cross-env": "~7.0.3",
"@hcengineering/platform-rig": "^0.6.0",
"@types/node": "~20.11.16",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"esbuild": "^0.24.2",
"prettier": "^3.1.0",
"ts-node": "^10.8.0",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5",
"@tsconfig/node16": "^1.0.4",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"eslint-plugin-node": "^11.1.0"
},
"dependencies": {
"cors": "^2.8.5",
"express": "^4.21.2",
"dotenv": "~16.0.0"
}
}
41 changes: 41 additions & 0 deletions services/hook/pod-hook/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { config as dotenvConfig } from 'dotenv'

dotenvConfig()

export interface Config {
port: number
}

const envMap = {
Port: 'PORT'
}

const parseNumber = (str: string | undefined): number | undefined => (str !== undefined ? Number(str) : undefined)

const config: Config = (() => {
const port = parseNumber(process.env[envMap.Port])
if (port === undefined) {
throw Error('Missing env variable: Port')
}
const params: Config = {
port
}

return params
})()

export default config
23 changes: 23 additions & 0 deletions services/hook/pod-hook/src/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//

export class ApiError extends Error {
constructor (
readonly code: string,
readonly message: string
) {
super(message)
}
}
22 changes: 22 additions & 0 deletions services/hook/pod-hook/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//

import { main } from './main'

void main().catch((err) => {
if (err != null) {
console.error(err)
}
})
78 changes: 78 additions & 0 deletions services/hook/pod-hook/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//

import { createServer, listen } from './server'
import { Endpoint } from './types'
import config from './config'

export const main = async (): Promise<void> => {
const endpoints: Endpoint[] = [
{
endpoint: '/mta',
type: 'post',
handler: async (req, res) => {
console.log('mta-hook retrieved')
const message = getMessageInfo(req.body)
console.log('Email from:', message?.from)
// TODO: Send request to add message or put event to the queue

res.json({
action: 'accept'
})
}
}
]

const server = listen(createServer(endpoints), config.port)

const shutdown = (): void => {
server.close(() => {
process.exit()
})
}

process.on('SIGINT', shutdown)
process.on('SIGTERM', shutdown)
process.on('uncaughtException', (e) => {
console.error(e)
})
process.on('unhandledRejection', (e) => {
console.error(e)
})
}

const getMessageInfo = (
body: any
): { from: string, to: string[], subject: string, contents: any, size: number } | undefined => {
try {
const from = body.envelope.from.address
const to = body.envelope.to.map((recipient: any) => recipient.address)
const subjectHeader = body.message.headers.find((header: any) => header[0] === 'Subject')
const subject = subjectHeader !== undefined ? subjectHeader[1] : 'No Subject'
const contents = body.message.contents
const size = body.message.size

return {
from,
to,
subject,
contents,
size
}
} catch (e) {
console.error('Failed to parse message:', e)
return undefined
}
}
Loading

0 comments on commit f94d8bf

Please sign in to comment.