Skip to content

Commit

Permalink
Merge pull request #65 from amosproj/48-mailing
Browse files Browse the repository at this point in the history
48 mailing
  • Loading branch information
flo0852 authored Nov 19, 2024
2 parents 440f059 + fa22434 commit ab2c021
Show file tree
Hide file tree
Showing 18 changed files with 1,836 additions and 25 deletions.
6 changes: 6 additions & 0 deletions Documentation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ cd ./apps/analyzer/metadata_analyzer ; poetry install
- frontend: `nx run metadata-analyzer-frontend:test`
- python: `nx run metadata-analyzer:test`

### Mailing

Fill the `.env` file with the mailing information
Hint: For gmail you have to generate an app password, which you have to use as password in the `.env` file
For changes in templates being used, you have to restart the backend

## Installing new dependencies

### in python app
Expand Down
9 changes: 8 additions & 1 deletion apps/backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,11 @@ DATABASE_PORT=5433
DATABASE_USER="postgres"
DATABASE_PASSWORD="postgres"
DATABASE_DATABASE="postgres"
ANALYZER_URL="http://localhost:8000"
ANALYZER_URL="http://localhost:8000"
#Mailing
MAIL_HOST=smtp.example.com
MAIL_PORT=465
MAIL_USER=[email protected]
MAIL_PASSWORD=topsecret
MAIL_FROM=[email protected]
MAILING_LIST=[email protected],[email protected]
4 changes: 3 additions & 1 deletion apps/backend/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@
"main": "apps/backend/src/main.ts",
"outputPath": "dist/apps/backend",
"tsConfig": "apps/backend/tsconfig.app.json",
"assets": [],
"assets": [
"**/*.hbs"
],
"platform": "node",
"additionalEntryPoints": [
"{projectRoot}/src/migrations.main.ts"
Expand Down
22 changes: 22 additions & 0 deletions apps/backend/src/app/alerting/alerting.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Body, Controller, Logger, Post } from '@nestjs/common';
import { ApiOperation } from '@nestjs/swagger';
import { AlertingService } from './alerting.service';
import { AlertingInformationDto } from './dto/alertingInformationDto';

@Controller('alerting')
export class AlertingController {
readonly logger = new Logger(AlertingController.name);

constructor(private readonly alertingService: AlertingService) {}

@Post()
@ApiOperation({ summary: 'Send an alert mail with the given informations.' })
async sendAlertMail(
@Body() alertingInformationDto: AlertingInformationDto
): Promise<void> {
await this.alertingService.triggerAlertMail(
alertingInformationDto.reason,
alertingInformationDto.description
);
}
}
11 changes: 11 additions & 0 deletions apps/backend/src/app/alerting/alerting.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { AlertingService } from './alerting.service';
import { MailModule } from '../utils/mail/mail.module';
import { AlertingController } from './alerting.controller';

@Module({
imports: [MailModule],
providers: [AlertingService],
controllers: [AlertingController],
})
export class AlertingModule {}
27 changes: 27 additions & 0 deletions apps/backend/src/app/alerting/alerting.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AlertingService } from './alerting.service';
import { MailService } from '../utils/mail/mail.service';

describe('AlertingService', () => {
let service: AlertingService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AlertingService,
{
provide: MailService,
useValue: {
sendAlertMail: jest.fn(),
},
},
],
}).compile();

service = module.get(AlertingService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
11 changes: 11 additions & 0 deletions apps/backend/src/app/alerting/alerting.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Injectable } from '@nestjs/common';
import { MailService } from '../utils/mail/mail.service';

@Injectable()
export class AlertingService {
constructor(private mailService: MailService) {}

async triggerAlertMail(reason: string, description: string) {
await this.mailService.sendAlertMail(reason, description);
}
}
18 changes: 18 additions & 0 deletions apps/backend/src/app/alerting/dto/alertingInformationDto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class AlertingInformationDto {
@ApiProperty({
description: 'Reason for the alert',
required: true,
})
@IsString()
reason!: string;

@ApiProperty({
description: 'Description of the alert',
required: true,
})
@IsString()
description!: string;
}
4 changes: 3 additions & 1 deletion apps/backend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {TypeOrmModule} from '@nestjs/typeorm';
import {DbConfigService} from "./db-config.service";
import {DemoModule} from "./demo/demo.module";
import {BackupDataModule} from "./backupData/backupData.module";
import { AlertingModule } from './alerting/alerting.module';

@Module({
imports: [
Expand All @@ -18,7 +19,8 @@ import {BackupDataModule} from "./backupData/backupData.module";
useClass: DbConfigService,
}),
DemoModule,
BackupDataModule
BackupDataModule,
AlertingModule
],
controllers: [AppController],
providers: [AppService],
Expand Down
1 change: 0 additions & 1 deletion apps/backend/src/app/demo/demo.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,4 @@ export class DemoController {
async createEntry(@Body() createEntryDto: CreateEntryDto): Promise<DemoDto> {
return await this.demoService.createEntry(createEntryDto.text);
}

}
2 changes: 0 additions & 2 deletions apps/backend/src/app/demo/demo.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { InjectRepository } from '@nestjs/typeorm';
import { DemoEntity } from './entity/demo.entity';
import { Repository } from 'typeorm';
import { AnalyzerServiceService } from '../analyzerService/analyzer-service.service';
import { firstValueFrom } from 'rxjs';

@Injectable()
export class DemoService {
Expand All @@ -30,5 +29,4 @@ export class DemoService {
entry.text = text;
return this.demoRepository.save(entry);
}

}
42 changes: 42 additions & 0 deletions apps/backend/src/app/utils/mail/mail.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { MailerModule } from '@nestjs-modules/mailer';
import { Global, Module } from '@nestjs/common';
import { MailService } from './mail.service';
import { join } from 'path';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter.js';

@Global()
@Module({
imports: [
MailerModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (config: ConfigService) => ({
transport: {
host: config.getOrThrow('MAIL_HOST'),
port: config.getOrThrow('MAIL_PORT'),
secure: true,
auth: {
user: config.getOrThrow('MAIL_USER'),
pass: config.getOrThrow('MAIL_PASSWORD'),
},
},
defaults: {
from: `"Metadata Mavericks Alerting Service" <${config.getOrThrow(
'MAIL_FROM'
)}>`,
},
template: {
dir: join('apps/backend/src/app/utils/mail/templates'),
adapter: new HandlebarsAdapter(),
options: {
strict: true,
},
},
}),
inject: [ConfigService],
}),
],
providers: [MailService],
exports: [MailService],
})
export class MailModule {}
86 changes: 86 additions & 0 deletions apps/backend/src/app/utils/mail/mail.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { Test, TestingModule } from '@nestjs/testing';
import { MailService } from './mail.service';
import { MailerService } from '@nestjs-modules/mailer';
import { ConfigService } from '@nestjs/config';

jest.mock('path', () => ({
resolve: jest.fn().mockReturnValue('mocked/path/to/logo.png'),
}));

describe('MailService', () => {
let service: MailService;
let mailerService: MailerService;
let configService: ConfigService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
MailService,
{
provide: MailerService,
useValue: {
sendMail: jest.fn(),
},
},
{
provide: ConfigService,
useValue: {
getOrThrow: jest.fn().mockReturnValue('[email protected]'),
},
},
],
}).compile();

service = module.get(MailService);
mailerService = module.get(MailerService);
configService = module.get(ConfigService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});

it('should send alert mail', async () => {
const reason = 'Test Reason';
const description = 'Test Description';
await service.sendAlertMail(reason, description);

expect(mailerService.sendMail).toHaveBeenCalledWith({
to: ['[email protected]'],
subject: 'Alert has been triggered',
template: './alertMail',
context: {
reason,
description,
},
attachments: [
{
filename: 'logo.png',
path: 'mocked/path/to/logo.png',
cid: 'logo',
contentType: 'image/png',
},
],
});
});

it('should send mail with correct parameters', async () => {
const to = ['[email protected]'];
const subject = 'Test Subject';
const template = 'testTemplate';
const context = { key: 'value' };
const attachments = [
{ filename: 'test.png', path: 'test/path', cid: 'test' },
];

await service.sendMail(to, subject, template, context, attachments);

expect(mailerService.sendMail).toHaveBeenCalledWith({
to,
subject,
template: `./${template}`,
context,
attachments,
});
});
});
60 changes: 60 additions & 0 deletions apps/backend/src/app/utils/mail/mail.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { MailerService } from '@nestjs-modules/mailer';
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as path from 'path';

@Injectable()
export class MailService {
readonly logger = new Logger(MailService.name);

constructor(
private mailerService: MailerService,
private configService: ConfigService
) {}

async sendAlertMail(reason: string, description: string) {
const receivers =
this.configService.getOrThrow<string>('MAILING_LIST').split(',') || [];
const context = {
reason,
description,
};

const logoPath = path.resolve('apps/backend/src/assets/team_logo.png');

const attachments = [
{
filename: 'logo.png',
path: logoPath,
cid: 'logo',
contentType: 'image/png', // Ensure the MIME type is set correctly
},
];
await this.sendMail(
receivers,
'Alert has been triggered',
'alertMail',
context,
attachments
);
}

async sendMail(
receivers: string[],
subject: string,
template: string,
context: Record<string, string>,
attachments?: any[]
) {
this.logger.log(
`Sending mail to: ${receivers.join(',')} with subject "${subject}"`
);
await this.mailerService.sendMail({
to: receivers,
subject,
template: `./${template}`, // `.hbs` extension is appended automatically
context,
attachments: attachments ?? [],
});
}
}
Loading

0 comments on commit ab2c021

Please sign in to comment.