Skip to content

Commit 22e5390

Browse files
committed
chore: apply feedback to job services
1 parent c112bd1 commit 22e5390

15 files changed

Lines changed: 175 additions & 137 deletions

apps/api/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ EMAIL_ADAPTER="mailhog"
88
DATABASE_URL="postgres://postgres:boilerplate@localhost:5432/boilerplate"
99
DATABASE_TEST_URL="postgres://postgres:boilerplate@localhost:5432/boilerplate_test"
1010
REDIS_URL="redis://localhost:6379"
11+
BULLBOARD_PASSWORD=admin123
1112

1213
# MAILS
1314
SMTP_HOST=

apps/api/src/auth/auth-email.consumer.ts

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,49 +2,56 @@ import { Processor, WorkerHost } from "@nestjs/bullmq";
22
import { AuthService } from "./auth.service";
33
import { Job } from "bullmq";
44
import {
5-
QUEUE_EMAIL,
6-
QueueEmailJobData,
7-
QueueEmailJobPayloads,
5+
EMAIL_QUEUE,
6+
EmailQueueJobData,
7+
EmailQueueJobPayloads,
88
} from "./auth.queue";
99

10-
@Processor(QUEUE_EMAIL.name)
10+
type QueueEmailJobType = EmailQueueJobData["type"];
11+
type QueueEmailJob<T extends QueueEmailJobType = QueueEmailJobType> = Job<
12+
Extract<EmailQueueJobData, { type: T }>,
13+
unknown,
14+
string
15+
>;
16+
17+
@Processor(EMAIL_QUEUE.name)
1118
export class AuthEmailConsumer extends WorkerHost {
1219
constructor(private readonly authService: AuthService) {
1320
super();
1421
}
1522

16-
async process(
17-
job: Job<QueueEmailJobData, unknown, string>,
18-
): Promise<unknown> {
19-
switch (job.name) {
20-
case QUEUE_EMAIL.actions.SEND_WELCOME_EMAIL:
21-
return this.sendWelcomeEmail(
22-
job as Job<QueueEmailJobPayloads["SEND_WELCOME_EMAIL"]>,
23-
);
24-
case QUEUE_EMAIL.actions.SEND_RESET_PASSWORD_EMAIL:
25-
return this.sendResetPasswordEmail(
26-
job as Job<QueueEmailJobPayloads["SEND_RESET_PASSWORD_EMAIL"]>,
27-
);
28-
default:
29-
throw new Error(`Unknown job name: ${job.name}`);
23+
async process(job: QueueEmailJob): Promise<unknown> {
24+
if (this.isJobOfType(job, EMAIL_QUEUE.actions.SEND_WELCOME_EMAIL)) {
25+
return this.sendWelcomeEmail(job.data);
26+
}
27+
28+
if (this.isJobOfType(job, EMAIL_QUEUE.actions.SEND_RESET_PASSWORD_EMAIL)) {
29+
return this.sendResetPasswordEmail(job.data);
3030
}
31+
32+
throw new Error(`Unknown job name: ${job.name}`);
3133
}
3234

3335
async sendResetPasswordEmail(
34-
job: Job<QueueEmailJobPayloads["SEND_RESET_PASSWORD_EMAIL"]>,
36+
payload: EmailQueueJobPayloads["SEND_RESET_PASSWORD_EMAIL"],
3537
) {
36-
const { url, name, email } = job.data;
38+
const { url, name, email } = payload;
3739
return this.authService.sendResetPassordEmail(email, { url, name });
3840
}
3941

40-
async sendWelcomeEmail(
41-
job: Job<QueueEmailJobPayloads["SEND_WELCOME_EMAIL"]>,
42-
) {
43-
const { url, name, email } = job.data;
42+
async sendWelcomeEmail(payload: EmailQueueJobPayloads["SEND_WELCOME_EMAIL"]) {
43+
const { url, name, email } = payload;
4444
return this.authService.sendWelcomeVerifyEmail(email, {
4545
url,
4646
name,
4747
email,
4848
});
4949
}
50+
51+
private isJobOfType<T extends QueueEmailJobType>(
52+
job: QueueEmailJob,
53+
type: T,
54+
): job is QueueEmailJob<T> {
55+
return job.data.type === type;
56+
}
5057
}

apps/api/src/auth/auth-email.producer.ts

Lines changed: 0 additions & 31 deletions
This file was deleted.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { InjectQueue } from "@nestjs/bullmq";
2+
import { Injectable } from "@nestjs/common";
3+
import { Queue } from "bullmq";
4+
import { EMAIL_QUEUE, EmailQueueJobPayloads } from "./auth.queue";
5+
6+
@Injectable()
7+
export class AuthEmailService {
8+
private readonly jobSettings = { age: 3600 };
9+
10+
constructor(@InjectQueue(EMAIL_QUEUE.name) private readonly queue: Queue) {}
11+
12+
public async sendWelcomeMessageEmailAsync(
13+
jobId: string,
14+
payload: Omit<EmailQueueJobPayloads["SEND_WELCOME_EMAIL"], "type">,
15+
) {
16+
const opts = { jobId, removeOnComplete: this.jobSettings };
17+
const payloadWithType = {
18+
...payload,
19+
type: EMAIL_QUEUE.actions.SEND_WELCOME_EMAIL,
20+
};
21+
await this.queue.add(
22+
EMAIL_QUEUE.actions.SEND_WELCOME_EMAIL,
23+
payloadWithType,
24+
opts,
25+
);
26+
}
27+
28+
public async sendResetPasswordEmailAsync(
29+
jobId: string,
30+
payload: Omit<EmailQueueJobPayloads["SEND_RESET_PASSWORD_EMAIL"], "type">,
31+
) {
32+
const opts = { jobId, removeOnComplete: this.jobSettings };
33+
const payloadWithType = {
34+
...payload,
35+
type: EMAIL_QUEUE.actions.SEND_RESET_PASSWORD_EMAIL,
36+
};
37+
38+
await this.queue.add(
39+
EMAIL_QUEUE.actions.SEND_RESET_PASSWORD_EMAIL,
40+
payloadWithType,
41+
opts,
42+
);
43+
}
44+
}

apps/api/src/auth/auth.module.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { Module } from "@nestjs/common";
22
import { ConfigModule } from "@nestjs/config";
33
import { AuthService } from "./auth.service";
44
import { BullModule } from "@nestjs/bullmq";
5-
import { QUEUE_EMAIL } from "./auth.queue";
5+
import { EMAIL_QUEUE } from "./auth.queue";
66
import { AuthEmailConsumer } from "./auth-email.consumer";
7-
import { AuthEmailProducer } from "./auth-email.producer";
7+
import { AuthEmailService } from "./auth-email.service";
88
import { EmailModule } from "src/common/emails/emails.module";
99
import { BullBoardModule } from "@bull-board/nestjs";
1010
import { BullMQAdapter } from "@bull-board/api/bullMQAdapter";
@@ -13,15 +13,15 @@ import { BullMQAdapter } from "@bull-board/api/bullMQAdapter";
1313
imports: [
1414
ConfigModule,
1515
BullModule.registerQueue({
16-
name: QUEUE_EMAIL.name,
16+
name: EMAIL_QUEUE.name,
1717
}),
1818
BullBoardModule.forFeature({
19-
name: QUEUE_EMAIL.name,
19+
name: EMAIL_QUEUE.name,
2020
adapter: BullMQAdapter,
2121
}),
2222
EmailModule,
2323
],
24-
providers: [AuthService, AuthEmailConsumer, AuthEmailProducer],
25-
exports: [AuthService, AuthEmailProducer],
24+
providers: [AuthService, AuthEmailConsumer, AuthEmailService],
25+
exports: [AuthService, AuthEmailService],
2626
})
2727
export class AuthModule {}

apps/api/src/auth/auth.queue.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
1-
export const QUEUE_EMAIL = {
1+
export const EMAIL_QUEUE = {
22
name: "email-queue",
33
actions: {
4-
SEND_WELCOME_EMAIL: "send-welcome-email" as const,
5-
SEND_RESET_PASSWORD_EMAIL: "send-reset-password-email" as const,
4+
SEND_WELCOME_EMAIL: "SEND_WELCOME_EMAIL" as const,
5+
SEND_RESET_PASSWORD_EMAIL: "SEND_RESET_PASSWORD_EMAIL" as const,
66
},
77
};
88

9-
export type QueueEmailJobPayloads = {
9+
export type EmailQueueJobPayloads = {
1010
SEND_WELCOME_EMAIL: {
11+
type: "SEND_WELCOME_EMAIL";
1112
url: string;
1213
name: string;
1314
email: string;
1415
};
1516
SEND_RESET_PASSWORD_EMAIL: {
17+
type: "SEND_RESET_PASSWORD_EMAIL";
1618
url: string;
1719
name: string;
1820
email: string;
1921
};
2022
};
2123

22-
export type QueueEmailJobData =
23-
| QueueEmailJobPayloads["SEND_WELCOME_EMAIL"]
24-
| QueueEmailJobPayloads["SEND_RESET_PASSWORD_EMAIL"];
24+
export type EmailQueueJobData =
25+
| EmailQueueJobPayloads["SEND_WELCOME_EMAIL"]
26+
| EmailQueueJobPayloads["SEND_RESET_PASSWORD_EMAIL"];

apps/api/src/auth/auth.service.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,21 @@ import {
66
RESET_PASSWORD_EMAIL,
77
WELCOME_VERIFY_EMAIL,
88
} from "src/common/emails/email.consts";
9-
import { AuthEmailProducer } from "./auth-email.producer";
9+
import { AuthEmailService } from "./auth-email.service";
1010

1111
@Injectable()
1212
export class AuthService {
1313
constructor(
1414
private readonly emailService: EmailService,
15-
private readonly authEmailProducer: AuthEmailProducer,
15+
private readonly authEmailService: AuthEmailService,
1616
) {}
1717

1818
public async onWelcomeEmail(
1919
to: string,
2020
data: { email: string; name: string; url: string },
2121
) {
2222
const jobId = `welcome-email-${data.email}-${Date.now()}`;
23-
await this.authEmailProducer.newUserWelcomeMessageSent(jobId, {
23+
await this.authEmailService.sendWelcomeMessageEmailAsync(jobId, {
2424
email: data.email,
2525
name: data.name,
2626
url: data.url,
@@ -32,7 +32,7 @@ export class AuthService {
3232
data: { email: string; name: string; url: string },
3333
) {
3434
const jobId = `reset-password-${data.email}-${Date.now()}`;
35-
await this.authEmailProducer.resetPasswordMessageSent(jobId, {
35+
await this.authEmailService.sendResetPasswordEmailAsync(jobId, {
3636
email: data.email,
3737
name: data.name,
3838
url: data.url,

apps/api/src/queue/queue.module.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,16 @@ import basicAuth from "express-basic-auth";
1717
},
1818
}),
1919
}),
20-
BullBoardModule.forRoot({
21-
route: "/queues",
22-
adapter: ExpressAdapter,
23-
middleware: basicAuth({
24-
challenge: true,
25-
users: { admin: "passwordhere" },
20+
BullBoardModule.forRootAsync({
21+
imports: [ConfigModule],
22+
inject: [ConfigService],
23+
useFactory: async (configService: ConfigService) => ({
24+
route: "/queues",
25+
adapter: ExpressAdapter,
26+
middleware: basicAuth({
27+
challenge: true,
28+
users: { admin: configService.get<string>("BULLBOARD_PASSWORD")! },
29+
}),
2630
}),
2731
}),
2832
],

apps/api/src/users/__tests__/user.controller.e2e-spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
import { truncateTables } from "test/helpers/test-helpers";
1616
import { EmailService } from "src/common/emails/emails.service";
1717
import { Email } from "src/common/emails/email.interface";
18-
import { QUEUE_USER_ALERT } from "../users.queue";
18+
import { USER_ALERT_QUEUE } from "../users.queue";
1919
import {
2020
createQueueTestHarness,
2121
QueueTestHarness,
@@ -45,7 +45,7 @@ describe("UsersController (e2e)", () => {
4545
app = testApp;
4646
db = testDb;
4747
userFactory = createUserFactory(db);
48-
queueHarness = await createQueueTestHarness(app, QUEUE_USER_ALERT.name);
48+
queueHarness = await createQueueTestHarness(app, USER_ALERT_QUEUE.name);
4949
});
5050

5151
afterAll(async () => {

apps/api/src/users/users-alert.consumer.ts

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,40 @@ import { Processor, WorkerHost } from "@nestjs/bullmq";
22
import { UsersService } from "./users.service";
33
import { Job } from "bullmq";
44
import {
5-
QUEUE_USER_ALERT,
6-
QueueUserAlertJobData,
7-
QueueUserAlertJobPayloads,
5+
USER_ALERT_QUEUE,
6+
UserAlertQueueJobData,
7+
UserAlertQueueJobPayloads,
88
} from "./users.queue";
99

10-
@Processor(QUEUE_USER_ALERT.name)
10+
type QueueUserAlertJobType = UserAlertQueueJobData["type"];
11+
type QueueUserAlertJob<
12+
T extends QueueUserAlertJobType = QueueUserAlertJobType,
13+
> = Job<Extract<UserAlertQueueJobData, { type: T }>, unknown, string>;
14+
15+
@Processor(USER_ALERT_QUEUE.name)
1116
export class UsersAlertConsumer extends WorkerHost {
1217
constructor(private readonly usersService: UsersService) {
1318
super();
1419
}
1520

16-
async process(job: Job<QueueUserAlertJobData, unknown, string>): Promise<unknown> {
17-
switch (job.name) {
18-
case QUEUE_USER_ALERT.actions.SEND_ALERT_EMAIL:
19-
return this.sendAlertEmail(
20-
job as Job<QueueUserAlertJobPayloads["SEND_ALERT_EMAIL"]>,
21-
);
22-
default:
23-
throw new Error(`Unknown job name: ${job.name}`);
21+
async process(job: QueueUserAlertJob): Promise<unknown> {
22+
if (this.isJobOfType(job, USER_ALERT_QUEUE.actions.SEND_ALERT_EMAIL)) {
23+
return this.sendAlertEmail(job.data);
2424
}
25+
26+
// @ts-expect-error should not reach here
27+
throw new Error(`Unknown job name: ${job.name}`);
2528
}
2629

27-
async sendAlertEmail(
28-
job: Job<QueueUserAlertJobPayloads["SEND_ALERT_EMAIL"]>,
29-
) {
30-
console.log('processing job', job.id, job.data);
31-
const { email } = job.data;
30+
async sendAlertEmail(payload: UserAlertQueueJobPayloads["SEND_ALERT_EMAIL"]) {
31+
const { email } = payload;
3232
return this.usersService.sendAlertEmail(email);
3333
}
34+
35+
private isJobOfType<T extends QueueUserAlertJobType>(
36+
job: QueueUserAlertJob,
37+
type: T,
38+
): job is QueueUserAlertJob<T> {
39+
return job.data.type === type;
40+
}
3441
}

0 commit comments

Comments
 (0)