Skip to content

Commit 77ee8f3

Browse files
committed
Updated services
1 parent f4f0bc4 commit 77ee8f3

9 files changed

Lines changed: 2214 additions & 4739 deletions

File tree

‎packages/api-gateway/src/models/email.dto.ts‎

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,102 @@ export class SendGridDNSResponse {
536536
}
537537
}
538538

539+
export class VerifiedSenderResponse {
540+
@ApiProperty({ type: 'number' })
541+
id: number;
542+
543+
@ApiProperty({ type: 'string' })
544+
nickname: string;
545+
546+
@ApiProperty({ type: 'string' })
547+
fromEmail: string;
548+
549+
@ApiProperty({ type: 'string' })
550+
fromName: string;
551+
552+
@ApiProperty({ type: 'string' })
553+
replyTo: string;
554+
555+
@ApiProperty({ type: 'string' })
556+
address: string;
557+
558+
@ApiProperty({ type: 'string' })
559+
city: string;
560+
561+
@ApiProperty({ type: 'string' })
562+
state: string;
563+
564+
@ApiProperty({ type: 'string' })
565+
country: string;
566+
567+
@ApiProperty({ type: 'string' })
568+
zip: string;
569+
570+
@ApiProperty({ type: 'boolean' })
571+
verified: boolean;
572+
573+
@ApiProperty({ type: 'boolean' })
574+
locked: boolean;
575+
576+
constructor(sender: EmailProto.VerifiedSender) {
577+
this.id = Number(sender.id);
578+
this.nickname = sender.nickname;
579+
this.fromEmail = sender.fromEmail;
580+
this.fromName = sender.fromName;
581+
this.replyTo = sender.replyTo;
582+
this.address = sender.address;
583+
this.city = sender.city;
584+
this.state = sender.state;
585+
this.country = sender.country;
586+
this.zip = sender.zip;
587+
this.verified = sender.verified;
588+
this.locked = sender.locked;
589+
}
590+
}
591+
592+
export class GetSendersResponse {
593+
@ApiProperty({ type: [VerifiedSenderResponse] })
594+
senders: VerifiedSenderResponse[];
595+
596+
constructor(res: EmailProto.GetSendersResponse) {
597+
this.senders = (res.senders ?? []).map(
598+
(s) => new VerifiedSenderResponse(s),
599+
);
600+
}
601+
}
602+
603+
export class AuthenticatedDomainResponse {
604+
@ApiProperty({ type: 'number' })
605+
id: number;
606+
607+
@ApiProperty({ type: 'string' })
608+
domain: string;
609+
610+
@ApiPropertyOptional({ type: 'string' })
611+
subdomain?: string;
612+
613+
@ApiProperty({ type: 'boolean' })
614+
valid: boolean;
615+
616+
constructor(domain: EmailProto.AuthenticatedDomain) {
617+
this.id = Number(domain.id);
618+
this.domain = domain.domain;
619+
this.subdomain = domain.subdomain;
620+
this.valid = domain.valid;
621+
}
622+
}
623+
624+
export class GetDomainsResponse {
625+
@ApiProperty({ type: [AuthenticatedDomainResponse] })
626+
domains: AuthenticatedDomainResponse[];
627+
628+
constructor(res: EmailProto.GetDomainsResponse) {
629+
this.domains = (res.domains ?? []).map(
630+
(d) => new AuthenticatedDomainResponse(d),
631+
);
632+
}
633+
}
634+
539635
export class RegisterDomainResponse {
540636
@ApiProperty({ type: 'number' })
541637
id: number;

‎packages/api-gateway/src/modules/email/email.controller.ts‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import {
2828
VerifyDomainModel,
2929
AggregationInterval,
3030
SendEmailStatisticsResponses,
31+
GetSendersResponse,
32+
GetDomainsResponse,
3133
} from 'src/models/email.dto';
3234

3335
import {
@@ -352,4 +354,50 @@ export class EmailController implements OnModuleInit {
352354
),
353355
);
354356
}
357+
358+
@ApiOperation({ summary: 'Gets all verified senders from SendGrid' })
359+
@ApiOkResponse({
360+
description: 'Returned verified senders',
361+
type: GetSendersResponse,
362+
})
363+
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
364+
@ApiBadRequestResponse({ description: 'Bad Request' })
365+
@Get('/senders')
366+
async getSenders(
367+
@ApiKey() apiKey: AuthCommonProto.ApiKey,
368+
@ProjectId() projectId: number,
369+
): Promise<GetSendersResponse> {
370+
return new GetSendersResponse(
371+
await lastValueFrom(
372+
this.emailService.getSenders({
373+
configId: projectId,
374+
configEnvironment: apiKey.environment,
375+
}),
376+
),
377+
);
378+
}
379+
380+
@ApiOperation({
381+
summary: 'Gets all authenticated domains from SendGrid',
382+
})
383+
@ApiOkResponse({
384+
description: 'Returned authenticated domains',
385+
type: GetDomainsResponse,
386+
})
387+
@ApiUnauthorizedResponse({ description: 'Unauthorized' })
388+
@ApiBadRequestResponse({ description: 'Bad Request' })
389+
@Get('/domains')
390+
async getDomains(
391+
@ApiKey() apiKey: AuthCommonProto.ApiKey,
392+
@ProjectId() projectId: number,
393+
): Promise<GetDomainsResponse> {
394+
return new GetDomainsResponse(
395+
await lastValueFrom(
396+
this.emailService.getDomains({
397+
configId: projectId,
398+
configEnvironment: apiKey.environment,
399+
}),
400+
),
401+
);
402+
}
355403
}

‎packages/api-gateway/test/email.e2e-spec.ts‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,58 @@ describe('Domain Registration Routes', () => {
617617
});
618618
});
619619

620+
describe('Email Senders Routes', () => {
621+
it('Successfully gets senders', async () => {
622+
return request(app.getHttpServer())
623+
.get('/email/senders')
624+
.set('Authorization', 'Bearer ' + apiKey)
625+
.expect(200)
626+
.then((response) => {
627+
expect(response.body).toHaveProperty('senders');
628+
expect(Array.isArray(response.body.senders)).toBe(true);
629+
});
630+
});
631+
632+
it('Fails without an Authorization header', async () => {
633+
return request(app.getHttpServer())
634+
.get('/email/senders')
635+
.expect(401);
636+
});
637+
638+
it('Fails with an invalid API Key', async () => {
639+
return request(app.getHttpServer())
640+
.get('/email/senders')
641+
.set('Authorization', 'Bearer invalid.api.key')
642+
.expect(401);
643+
});
644+
});
645+
646+
describe('Email Domains Routes', () => {
647+
it('Successfully gets domains', async () => {
648+
return request(app.getHttpServer())
649+
.get('/email/domains')
650+
.set('Authorization', 'Bearer ' + apiKey)
651+
.expect(200)
652+
.then((response) => {
653+
expect(response.body).toHaveProperty('domains');
654+
expect(Array.isArray(response.body.domains)).toBe(true);
655+
});
656+
});
657+
658+
it('Fails without an Authorization header', async () => {
659+
return request(app.getHttpServer())
660+
.get('/email/domains')
661+
.expect(401);
662+
});
663+
664+
it('Fails with an invalid API Key', async () => {
665+
return request(app.getHttpServer())
666+
.get('/email/domains')
667+
.set('Authorization', 'Bearer invalid.api.key')
668+
.expect(401);
669+
});
670+
});
671+
620672
describe('Domain Verification Routes', () => {
621673
it('Verifies a domain without a domain parameter', () => {
622674
return request(app.getHttpServer())

‎packages/email-service/src/modules/email/email.controller.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,16 @@ export class EmailController implements EmailProto.EmailServiceController {
108108
): Promise<EmailProto.StatisticResponses> {
109109
return await this.emailService.getStatistics(request);
110110
}
111+
112+
async getSenders(
113+
request: EmailProto.GetSendersRequest,
114+
): Promise<EmailProto.GetSendersResponse> {
115+
return await this.emailService.getSenders(request);
116+
}
117+
118+
async getDomains(
119+
request: EmailProto.GetDomainsRequest,
120+
): Promise<EmailProto.GetDomainsResponse> {
121+
return await this.emailService.getDomains(request);
122+
}
111123
}

‎packages/email-service/src/modules/email/email.service.ts‎

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,114 @@ export class EmailService implements OnModuleInit {
312312
});
313313
}
314314
}
315+
async getSenders(
316+
req: EmailProto.GetSendersRequest,
317+
): Promise<EmailProto.GetSendersResponse> {
318+
const config = await lastValueFrom(
319+
this.emailService.getEmailServiceConfig({
320+
id: Number(req.configId),
321+
environment: req.configEnvironment,
322+
}),
323+
);
324+
325+
const sendgridApiKey = config.sendgridKey;
326+
327+
if (!sendgridApiKey) {
328+
throw new RpcException({
329+
code: status.FAILED_PRECONDITION,
330+
message: 'Cannot get senders (SendGrid API key is missing)',
331+
});
332+
}
333+
334+
if (process.env['NODE_ENV'] == 'test') {
335+
return { senders: [] };
336+
}
337+
338+
try {
339+
const response = await axios.get(
340+
'https://api.sendgrid.com/v3/verified_senders',
341+
{
342+
headers: {
343+
Authorization: `Bearer ${sendgridApiKey}`,
344+
'Content-Type': 'application/json',
345+
},
346+
},
347+
);
348+
349+
const senders = (response.data.results ?? []).map((sender: any) => ({
350+
id: sender.id,
351+
nickname: sender.nickname ?? '',
352+
fromEmail: sender.from_email ?? '',
353+
fromName: sender.from_name ?? '',
354+
replyTo: sender.reply_to ?? '',
355+
address: sender.address ?? '',
356+
city: sender.city ?? '',
357+
state: sender.state ?? '',
358+
country: sender.country ?? '',
359+
zip: sender.zip ?? '',
360+
verified: sender.verified ?? false,
361+
locked: sender.locked ?? false,
362+
}));
363+
364+
return { senders };
365+
} catch (error) {
366+
throw new RpcException({
367+
code: status.INTERNAL,
368+
message: `Failed to fetch senders from SendGrid: ${JSON.stringify(error)}`,
369+
});
370+
}
371+
}
372+
373+
async getDomains(
374+
req: EmailProto.GetDomainsRequest,
375+
): Promise<EmailProto.GetDomainsResponse> {
376+
const config = await lastValueFrom(
377+
this.emailService.getEmailServiceConfig({
378+
id: Number(req.configId),
379+
environment: req.configEnvironment,
380+
}),
381+
);
382+
383+
const sendgridApiKey = config.sendgridKey;
384+
385+
if (!sendgridApiKey) {
386+
throw new RpcException({
387+
code: status.FAILED_PRECONDITION,
388+
message: 'Cannot get domains (SendGrid API key is missing)',
389+
});
390+
}
391+
392+
if (process.env['NODE_ENV'] == 'test') {
393+
return { domains: [] };
394+
}
395+
396+
try {
397+
const response = await axios.get(
398+
'https://api.sendgrid.com/v3/whitelabel/domains',
399+
{
400+
headers: {
401+
Authorization: `Bearer ${sendgridApiKey}`,
402+
'Content-Type': 'application/json',
403+
},
404+
},
405+
);
406+
407+
const domains = (response.data ?? []).map((domain: any) => ({
408+
id: domain.id,
409+
domain: domain.domain ?? '',
410+
subdomain: domain.subdomain ?? undefined,
411+
valid: domain.valid ?? false,
412+
}));
413+
414+
return { domains };
415+
} catch (error) {
416+
throw new RpcException({
417+
code: status.INTERNAL,
418+
message: `Failed to fetch domains from SendGrid: ${JSON.stringify(error)}`,
419+
});
420+
}
421+
}
422+
315423
async getStatistics(
316424
req: EmailProto.GetStatisticsRequest,
317425
): Promise<EmailProto.StatisticResponses> {

0 commit comments

Comments
 (0)