Skip to content

Commit

Permalink
feat: add validation for email attachments (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
LakshayaT authored Nov 29, 2023
1 parent fee3bd8 commit 92c9679
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ export class MailgunNotificationConsumer {

try {
this.logger.log(`Sending notification with id: ${id}`);
const result = await this.mailgunService.sendEmail(notification.data as MailgunMessageData);
const formattedNotificationData = await this.mailgunService.formatNotificationData(
notification.data,
);
const result = await this.mailgunService.sendEmail(
formattedNotificationData as MailgunMessageData,
);
notification.deliveryStatus = DeliveryStatus.SUCCESS;
notification.result = { result };
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {
IsNotEmpty,
ValidateIf,
registerDecorator,
ValidationOptions,
ValidationArguments,
} from 'class-validator';
import { Stream } from 'stream';

export class CreateNotificationAttachmentDto {
@IsNotEmpty({ message: 'Filename cannot be empty' })
filename: string;

@IsNotEmpty({ message: 'Content or path must be provided' })
@ValidateIf((obj) => !obj.path, { message: 'Content or path must be provided' })
content: string | Buffer | Stream;

@IsNotEmpty({ message: 'Content or path must be provided' })
@ValidateIf((obj) => !obj.content, { message: 'Content or path must be provided' })
path: string;
}

// TODO: Custom Validator added for the bug
// https://github.com/OsmosysSoftware/osmo-notify/pull/62
export function AttachmentValidation(validationOptions?: ValidationOptions) {
return function (object: unknown, propertyName: string) {
registerDecorator({
name: 'attachmentValidation',
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: unknown[]) {
if (!value || value.length === 0) {
return false;
}

for (const attachment of value) {
const path = attachment['path'];
const content = attachment['content'];
const filename = attachment['filename'];

if ((!path && !content) || !filename?.length) {
return false;
}
}

return true;
},
defaultMessage(args: ValidationArguments) {
if (!args.value || args.value.length === 0) {
return 'Attachments must be provided';
}

for (const attachment of args.value) {
if (!attachment['filename']?.length) {
return 'Filename cannot be empty';
}

if (!attachment['content'] && !attachment['path']) {
return 'Content or path must be provided for each attachment';
}
}

return 'Invalid attachments';
},
},
});
};
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
import {
AttachmentValidation,
CreateNotificationAttachmentDto,
} from '../create-notification-attachment.dto';
import { Type } from 'class-transformer';

export class MailgunDataDto {
@IsNotEmpty()
Expand All @@ -24,4 +29,9 @@ export class MailgunDataDto {
@IsNotEmpty()
@IsString()
html: string;

@IsOptional()
@Type(() => CreateNotificationAttachmentDto)
@AttachmentValidation()
attachments: CreateNotificationAttachmentDto[];
}
10 changes: 10 additions & 0 deletions apps/api/src/modules/notifications/dtos/providers/smtp-data.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
import {
AttachmentValidation,
CreateNotificationAttachmentDto,
} from '../create-notification-attachment.dto';
import { Type } from 'class-transformer';

export class SMTPDataDto {
@IsNotEmpty()
Expand All @@ -24,4 +29,9 @@ export class SMTPDataDto {
@IsNotEmpty()
@IsString()
html: string;

@IsOptional()
@Type(() => CreateNotificationAttachmentDto)
@AttachmentValidation()
attachments: CreateNotificationAttachmentDto[];
}
32 changes: 32 additions & 0 deletions apps/api/src/modules/providers/mailgun/mailgun.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as FormData from 'form-data';
import Mailgun, { MailgunClientOptions, MailgunMessageData, MessagesSendResult } from 'mailgun.js';
import * as path from 'path';
import * as fs from 'node:fs/promises';
import { CreateNotificationAttachmentDto } from 'src/modules/notifications/dtos/create-notification-attachment.dto';

@Injectable()
export class MailgunService {
Expand All @@ -22,4 +25,33 @@ export class MailgunService {
sendEmail(mailgunNotificationData: MailgunMessageData): Promise<MessagesSendResult> {
return this.mailgunClient.messages.create(this.mailgunDomain, mailgunNotificationData);
}

async formatNotificationData(notificationData: Record<string, unknown>): Promise<object> {
const formattedNotificationData = notificationData;
formattedNotificationData.attachment = await this.formatAttachments(
notificationData.attachments as CreateNotificationAttachmentDto[],
);
delete formattedNotificationData.attachments;
return formattedNotificationData;
}

async formatAttachments(attachments: CreateNotificationAttachmentDto[]): Promise<object[]> {
const formattedAttachments = [];

for (const attachment of attachments) {
let data = attachment.content;

if (attachment.path) {
const filepath = path.resolve(attachment.path);
data = await fs.readFile(filepath);
}

formattedAttachments.push({
filename: attachment.filename,
data,
});
}

return formattedAttachments;
}
}

0 comments on commit 92c9679

Please sign in to comment.