From c441cd684406e0ef31dbcb3145e902bbfbcb09d0 Mon Sep 17 00:00:00 2001 From: swh00tw Date: Sat, 16 Nov 2024 21:17:43 -0500 Subject: [PATCH 1/8] test: add back slack test api --- .../subscription/subscription.controller.ts | 75 +++++++++++++++++++ .../subscription/subscription.module.ts | 2 + .../subscription/weekly-digest.worker.ts | 2 +- 3 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 apps/recnet-api/src/modules/subscription/subscription.controller.ts diff --git a/apps/recnet-api/src/modules/subscription/subscription.controller.ts b/apps/recnet-api/src/modules/subscription/subscription.controller.ts new file mode 100644 index 00000000..71bbc3cc --- /dev/null +++ b/apps/recnet-api/src/modules/subscription/subscription.controller.ts @@ -0,0 +1,75 @@ +import { generateMock } from "@anatine/zod-mock"; +import { Body, Controller, HttpStatus, Inject, Post } from "@nestjs/common"; +import { ConfigType } from "@nestjs/config"; +import { + ApiBody, + ApiCreatedResponse, + ApiOperation, + ApiTags, +} from "@nestjs/swagger"; + +import { AppConfig } from "@recnet-api/config/common.config"; +import UserRepository from "@recnet-api/database/repository/user.repository"; +import { RecnetError } from "@recnet-api/utils/error/recnet.error"; +import { ErrorCode } from "@recnet-api/utils/error/recnet.error.const"; + +import { getLatestCutOff } from "@recnet/recnet-date-fns"; + +import { announcementSchema } from "@recnet/recnet-api-model"; + +import { WeeklyDigestWorker } from "./weekly-digest.worker"; + +import { SlackService } from "../slack/slack.service"; + +@ApiTags("subscriptions") +@Controller("subscriptions") +export class SubscriptionController { + constructor( + @Inject(AppConfig.KEY) + private readonly appConfig: ConfigType, + private readonly slackService: SlackService, + private readonly userRepository: UserRepository, + private readonly weeklyDigestWorker: WeeklyDigestWorker + ) {} + + /* Development only */ + @ApiOperation({ + summary: "Send weekly digest slack to the designated user.", + description: "This endpoint is for development only.", + }) + @ApiCreatedResponse() + @ApiBody({ + schema: { + properties: { + userId: { type: "string" }, + }, + required: ["userId"], + }, + }) + @Post("slack/test") + public async testSendingWeeklyDigest( + @Body("userId") userId: string + ): Promise { + if (this.appConfig.nodeEnv === "production") { + throw new RecnetError( + ErrorCode.INTERNAL_SERVER_ERROR, + HttpStatus.INTERNAL_SERVER_ERROR, + "This endpoint is only for development" + ); + } + const cutoff = getLatestCutOff(); + const user = await this.userRepository.findUserById(userId); + const announcement = generateMock(announcementSchema); + const content = await this.weeklyDigestWorker.getWeeklyDigestContent( + user, + cutoff, + { + ...announcement, + startAt: new Date(announcement.startAt), + endAt: new Date(announcement.endAt), + } + ); + + this.slackService.sendWeeklyDigest(user, content, cutoff); + } +} diff --git a/apps/recnet-api/src/modules/subscription/subscription.module.ts b/apps/recnet-api/src/modules/subscription/subscription.module.ts index b94a1323..27901146 100644 --- a/apps/recnet-api/src/modules/subscription/subscription.module.ts +++ b/apps/recnet-api/src/modules/subscription/subscription.module.ts @@ -3,11 +3,13 @@ import { Module } from "@nestjs/common"; import { DbRepositoryModule } from "@recnet-api/database/repository/db.repository.module"; import { EmailModule } from "@recnet-api/modules/email/email.module"; +import { SubscriptionController } from "./subscription.controller"; import { WeeklyDigestWorker } from "./weekly-digest.worker"; import { SlackModule } from "../slack/slack.module"; @Module({ + controllers: [SubscriptionController], providers: [WeeklyDigestWorker], imports: [DbRepositoryModule, EmailModule, SlackModule], }) diff --git a/apps/recnet-api/src/modules/subscription/weekly-digest.worker.ts b/apps/recnet-api/src/modules/subscription/weekly-digest.worker.ts index b3da36bd..bae1a46f 100644 --- a/apps/recnet-api/src/modules/subscription/weekly-digest.worker.ts +++ b/apps/recnet-api/src/modules/subscription/weekly-digest.worker.ts @@ -128,7 +128,7 @@ export class WeeklyDigestWorker { } } - private async getWeeklyDigestContent( + public async getWeeklyDigestContent( user: DbUser, cutoff: Date, latestAnnouncement: Announcement | undefined From 4d771a61da05415a78078cb4abd0528b73c7f0b7 Mon Sep 17 00:00:00 2001 From: swh00tw Date: Sat, 16 Nov 2024 21:43:58 -0500 Subject: [PATCH 2/8] feat: install slack block builder --- package.json | 1 + pnpm-lock.yaml | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/package.json b/package.json index 1bd91729..bd127b45 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "reflect-metadata": "^0.1.13", "rxjs": "^7.8.0", "server-only": "^0.0.1", + "slack-block-builder": "^2.8.0", "sonner": "^1.4.32", "tailwind-merge": "^2.2.1", "tailwindcss-radix": "^3.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cb488afd..ab08fde3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -193,6 +193,9 @@ dependencies: server-only: specifier: ^0.0.1 version: 0.0.1 + slack-block-builder: + specifier: ^2.8.0 + version: 2.8.0 sonner: specifier: ^1.4.32 version: 1.4.32(react-dom@18.2.0)(react@18.2.0) @@ -18416,6 +18419,10 @@ packages: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} dev: true + /slack-block-builder@2.8.0: + resolution: {integrity: sha512-iisM+j99iKRuQFVfdWo0FiszDAl3r8Snq704oZH6C0RbDqvoVQStiptt6Y7kc6RX/5hSAqTqjhgvZ/di8cvaIA==} + dev: false + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} From 780785445461f437b93ec1e9f325470120f38d98 Mon Sep 17 00:00:00 2001 From: swh00tw Date: Sat, 16 Nov 2024 23:18:16 -0500 Subject: [PATCH 3/8] feat: add new slack msg template --- .../src/modules/slack/slack.type.ts | 4 ++ .../slack/templates/weekly-digest.template.ts | 71 ++++++++++++++++--- .../slack/transporters/slack.transporter.ts | 10 +-- .../subscription/subscription.controller.ts | 43 +++++++---- .../subscription/weekly-digest.worker.ts | 2 +- 5 files changed, 102 insertions(+), 28 deletions(-) diff --git a/apps/recnet-api/src/modules/slack/slack.type.ts b/apps/recnet-api/src/modules/slack/slack.type.ts index e2224a0e..9db7a02e 100644 --- a/apps/recnet-api/src/modules/slack/slack.type.ts +++ b/apps/recnet-api/src/modules/slack/slack.type.ts @@ -1,5 +1,9 @@ +import { SlackBlockDto } from "slack-block-builder"; + export type SendSlackResult = { success: boolean; skip?: boolean; userId?: string; }; + +export type SlackMessageBlocks = Readonly[]; diff --git a/apps/recnet-api/src/modules/slack/templates/weekly-digest.template.ts b/apps/recnet-api/src/modules/slack/templates/weekly-digest.template.ts index 67d0240f..3ffd7e8a 100644 --- a/apps/recnet-api/src/modules/slack/templates/weekly-digest.template.ts +++ b/apps/recnet-api/src/modules/slack/templates/weekly-digest.template.ts @@ -1,19 +1,72 @@ +import { BlockCollection, Md, Blocks, BlockBuilder } from "slack-block-builder"; + import { WeeklyDigestContent } from "@recnet-api/modules/subscription/subscription.type"; import { formatDate } from "@recnet/recnet-date-fns"; +import type { SlackMessageBlocks } from "../slack.type"; + +// export const weeklyDigestSlackTemplate = ( +// cutoff: Date, +// content: WeeklyDigestContent, +// nodeEnv: string +// ): string => { +// const subject = `${nodeEnv !== "production" && "[DEV] "}📬 Your Weekly Digest for ${formatDate(cutoff)}`; +// const unusedInviteCodes = `You have ${content.numUnusedInviteCodes} unused invite codes! Share the love ❤️`; +// const latestAnnouncement = content.latestAnnouncement +// ? `📢 ${content.latestAnnouncement.title} \n ${content.latestAnnouncement.content}` +// : ""; +// const recsUrls = content.recs.map( +// (rec) => `[${rec.article.title}](https://recnet.io/rec/${rec.id})` +// ); +// return `${subject}\nYou have ${content.recs.length} recommendations this week!\nCheck out these rec'd paper for you from your network!\n${unusedInviteCodes}\n${latestAnnouncement}\n${recsUrls.join("\n")} \n\nAny interesting read this week? 👀\nShare with your network: https://recnet.io/`; +// }; + export const weeklyDigestSlackTemplate = ( cutoff: Date, content: WeeklyDigestContent, nodeEnv: string -): string => { - const subject = `${nodeEnv !== "production" && "[DEV] "}📬 Your Weekly Digest for ${formatDate(cutoff)}`; - const unusedInviteCodes = `You have ${content.numUnusedInviteCodes} unused invite codes! Share the love ❤️`; - const latestAnnouncement = content.latestAnnouncement - ? `📢 ${content.latestAnnouncement.title} \n ${content.latestAnnouncement.content}` - : ""; - const recsUrls = content.recs.map( - (rec) => `[${rec.article.title}](https://recnet.io/rec/${rec.id})` +): SlackMessageBlocks => { + const { recs, numUnusedInviteCodes, latestAnnouncement } = content; + + const footer: BlockBuilder[] = []; + if (numUnusedInviteCodes > 0) { + footer.push( + Blocks.Section({ + text: `❤️ You have ${Md.bold(`${numUnusedInviteCodes}`)} unused invite codes. Share the love!`, + }) + ); + } + if (latestAnnouncement) { + footer.push( + Blocks.Section({ + text: `📢 Announcement - ${latestAnnouncement.title}: ${latestAnnouncement.content}`, + }) + ); + } + + return BlockCollection( + // headers: number of rec + Blocks.Header({ + text: `${nodeEnv !== "production" && "[DEV] "}📬 Your Weekly Digest for ${formatDate(cutoff)}`, + }), + Blocks.Section({ + text: `You have ${Md.bold(`${recs.length}`)} recommendations this week!`, + }), + Blocks.Section({ + text: "Check out these rec'd paper for you from your network!", + }), + Blocks.Divider(), + // recs + Blocks.Section({ + text: "recs placeholder", + }), + Blocks.Divider(), + // others: num of unused invite codes, latest announcement + ...footer, + // footer: share with your network + Blocks.Section({ + text: `👀 Any interesting read this week? ${Md.link("https://recnet.io", "Share with your network!")}`, + }) ); - return `${subject}\nYou have ${content.recs.length} recommendations this week!\nCheck out these rec'd paper for you from your network!\n${unusedInviteCodes}\n${latestAnnouncement}\n${recsUrls.join("\n")} \n\nAny interesting read this week? 👀\nShare with your network: https://recnet.io/`; }; diff --git a/apps/recnet-api/src/modules/slack/transporters/slack.transporter.ts b/apps/recnet-api/src/modules/slack/transporters/slack.transporter.ts index 95af04f5..e139dfc5 100644 --- a/apps/recnet-api/src/modules/slack/transporters/slack.transporter.ts +++ b/apps/recnet-api/src/modules/slack/transporters/slack.transporter.ts @@ -13,7 +13,7 @@ import { SLACK_RETRY_DURATION_MS, SLACK_RETRY_LIMIT, } from "../slack.const"; -import { SendSlackResult } from "../slack.type"; +import { SendSlackResult, SlackMessageBlocks } from "../slack.type"; @Injectable() export class SlackTransporter { @@ -31,7 +31,7 @@ export class SlackTransporter { public async sendDirectMessage( user: DbUser, - message: string + message: SlackMessageBlocks ): Promise { if ( this.appConfig.nodeEnv !== "production" && @@ -82,7 +82,7 @@ export class SlackTransporter { private async postDirectMessage( userSlackId: string, - message: string + message: SlackMessageBlocks ): Promise { // Open a direct message conversation const conversationResp = await this.client.conversations.open({ @@ -97,10 +97,12 @@ export class SlackTransporter { ); } + const notificationText = `📬 Your RecNet weekly digest has arrived!`; // Send the message await this.client.chat.postMessage({ channel: conversationId, - text: message, + text: notificationText, + blocks: message, }); } } diff --git a/apps/recnet-api/src/modules/subscription/subscription.controller.ts b/apps/recnet-api/src/modules/subscription/subscription.controller.ts index 71bbc3cc..5e4dc68c 100644 --- a/apps/recnet-api/src/modules/subscription/subscription.controller.ts +++ b/apps/recnet-api/src/modules/subscription/subscription.controller.ts @@ -15,9 +15,9 @@ import { ErrorCode } from "@recnet-api/utils/error/recnet.error.const"; import { getLatestCutOff } from "@recnet/recnet-date-fns"; -import { announcementSchema } from "@recnet/recnet-api-model"; +import { announcementSchema, recSchema } from "@recnet/recnet-api-model"; -import { WeeklyDigestWorker } from "./weekly-digest.worker"; +import { WeeklyDigestContent } from "./subscription.type"; import { SlackService } from "../slack/slack.service"; @@ -28,8 +28,7 @@ export class SubscriptionController { @Inject(AppConfig.KEY) private readonly appConfig: ConfigType, private readonly slackService: SlackService, - private readonly userRepository: UserRepository, - private readonly weeklyDigestWorker: WeeklyDigestWorker + private readonly userRepository: UserRepository ) {} /* Development only */ @@ -57,18 +56,34 @@ export class SubscriptionController { "This endpoint is only for development" ); } + + function getMockWeeklyDigestData(): WeeklyDigestContent { + const getMockRec = (title = 1) => + generateMock(recSchema, { + stringMap: { + photoUrl: () => "https://avatar.iran.liara.run/public", + title: () => `Paper Title ${title}`, + }, + }); + const announcement = generateMock(announcementSchema, { + stringMap: { + content: () => "This is a test announcement!", + }, + }); + return { + recs: [getMockRec(), getMockRec(2), getMockRec(3), getMockRec()], + numUnusedInviteCodes: 3, + latestAnnouncement: { + ...announcement, + startAt: new Date(announcement.startAt), + endAt: new Date(announcement.endAt), + }, + }; + } + const cutoff = getLatestCutOff(); const user = await this.userRepository.findUserById(userId); - const announcement = generateMock(announcementSchema); - const content = await this.weeklyDigestWorker.getWeeklyDigestContent( - user, - cutoff, - { - ...announcement, - startAt: new Date(announcement.startAt), - endAt: new Date(announcement.endAt), - } - ); + const content = getMockWeeklyDigestData(); this.slackService.sendWeeklyDigest(user, content, cutoff); } diff --git a/apps/recnet-api/src/modules/subscription/weekly-digest.worker.ts b/apps/recnet-api/src/modules/subscription/weekly-digest.worker.ts index bae1a46f..b3da36bd 100644 --- a/apps/recnet-api/src/modules/subscription/weekly-digest.worker.ts +++ b/apps/recnet-api/src/modules/subscription/weekly-digest.worker.ts @@ -128,7 +128,7 @@ export class WeeklyDigestWorker { } } - public async getWeeklyDigestContent( + private async getWeeklyDigestContent( user: DbUser, cutoff: Date, latestAnnouncement: Announcement | undefined From 3860d5e2ac7b92cb162aa5fc3b10073a85fbc518 Mon Sep 17 00:00:00 2001 From: swh00tw Date: Sat, 16 Nov 2024 23:40:16 -0500 Subject: [PATCH 4/8] feat: finish update slack msg template --- .../slack/templates/weekly-digest.template.ts | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/apps/recnet-api/src/modules/slack/templates/weekly-digest.template.ts b/apps/recnet-api/src/modules/slack/templates/weekly-digest.template.ts index 3ffd7e8a..3fc8e854 100644 --- a/apps/recnet-api/src/modules/slack/templates/weekly-digest.template.ts +++ b/apps/recnet-api/src/modules/slack/templates/weekly-digest.template.ts @@ -1,3 +1,4 @@ +import groupBy from "lodash.groupby"; import { BlockCollection, Md, Blocks, BlockBuilder } from "slack-block-builder"; import { WeeklyDigestContent } from "@recnet-api/modules/subscription/subscription.type"; @@ -6,22 +7,6 @@ import { formatDate } from "@recnet/recnet-date-fns"; import type { SlackMessageBlocks } from "../slack.type"; -// export const weeklyDigestSlackTemplate = ( -// cutoff: Date, -// content: WeeklyDigestContent, -// nodeEnv: string -// ): string => { -// const subject = `${nodeEnv !== "production" && "[DEV] "}📬 Your Weekly Digest for ${formatDate(cutoff)}`; -// const unusedInviteCodes = `You have ${content.numUnusedInviteCodes} unused invite codes! Share the love ❤️`; -// const latestAnnouncement = content.latestAnnouncement -// ? `📢 ${content.latestAnnouncement.title} \n ${content.latestAnnouncement.content}` -// : ""; -// const recsUrls = content.recs.map( -// (rec) => `[${rec.article.title}](https://recnet.io/rec/${rec.id})` -// ); -// return `${subject}\nYou have ${content.recs.length} recommendations this week!\nCheck out these rec'd paper for you from your network!\n${unusedInviteCodes}\n${latestAnnouncement}\n${recsUrls.join("\n")} \n\nAny interesting read this week? 👀\nShare with your network: https://recnet.io/`; -// }; - export const weeklyDigestSlackTemplate = ( cutoff: Date, content: WeeklyDigestContent, @@ -29,6 +14,26 @@ export const weeklyDigestSlackTemplate = ( ): SlackMessageBlocks => { const { recs, numUnusedInviteCodes, latestAnnouncement } = content; + const recsGroupByTitle = groupBy(recs, (rec) => { + const titleLowercase = rec.article.title.toLowerCase(); + const words = titleLowercase.split(" ").filter((w) => w.length > 0); + return words.join(""); + }); + const recSection = Object.values(recsGroupByTitle).map((recs) => { + const article = recs[0].article; + return [ + Blocks.Section({ + text: `${Md.bold(Md.link(article.link, article.title))}\n${Md.italic(article.author)} - ${article.year}`, + }), + ...recs.map((rec) => + Blocks.Section({ + text: `${Md.link(`https://recnet.io/${rec.user.handle}`, rec.user.displayName)}${rec.isSelfRec ? Md.italic("(Self-Rec)") : ""}: ${rec.description} (${Md.link(`https://recnet.io/rec/${rec.id}`, "view")})`, + }) + ), + Blocks.Divider(), + ]; + }); + const footer: BlockBuilder[] = []; if (numUnusedInviteCodes > 0) { footer.push( @@ -57,14 +62,8 @@ export const weeklyDigestSlackTemplate = ( text: "Check out these rec'd paper for you from your network!", }), Blocks.Divider(), - // recs - Blocks.Section({ - text: "recs placeholder", - }), - Blocks.Divider(), - // others: num of unused invite codes, latest announcement + ...recSection.flat(), ...footer, - // footer: share with your network Blocks.Section({ text: `👀 Any interesting read this week? ${Md.link("https://recnet.io", "Share with your network!")}`, }) From 490300c90a26c77324619884b62acac765441fec Mon Sep 17 00:00:00 2001 From: swh00tw Date: Sat, 16 Nov 2024 23:45:58 -0500 Subject: [PATCH 5/8] revert: remove testing api endpoint --- .../subscription/subscription.controller.ts | 90 ------------------- .../subscription/subscription.module.ts | 2 - 2 files changed, 92 deletions(-) delete mode 100644 apps/recnet-api/src/modules/subscription/subscription.controller.ts diff --git a/apps/recnet-api/src/modules/subscription/subscription.controller.ts b/apps/recnet-api/src/modules/subscription/subscription.controller.ts deleted file mode 100644 index 5e4dc68c..00000000 --- a/apps/recnet-api/src/modules/subscription/subscription.controller.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { generateMock } from "@anatine/zod-mock"; -import { Body, Controller, HttpStatus, Inject, Post } from "@nestjs/common"; -import { ConfigType } from "@nestjs/config"; -import { - ApiBody, - ApiCreatedResponse, - ApiOperation, - ApiTags, -} from "@nestjs/swagger"; - -import { AppConfig } from "@recnet-api/config/common.config"; -import UserRepository from "@recnet-api/database/repository/user.repository"; -import { RecnetError } from "@recnet-api/utils/error/recnet.error"; -import { ErrorCode } from "@recnet-api/utils/error/recnet.error.const"; - -import { getLatestCutOff } from "@recnet/recnet-date-fns"; - -import { announcementSchema, recSchema } from "@recnet/recnet-api-model"; - -import { WeeklyDigestContent } from "./subscription.type"; - -import { SlackService } from "../slack/slack.service"; - -@ApiTags("subscriptions") -@Controller("subscriptions") -export class SubscriptionController { - constructor( - @Inject(AppConfig.KEY) - private readonly appConfig: ConfigType, - private readonly slackService: SlackService, - private readonly userRepository: UserRepository - ) {} - - /* Development only */ - @ApiOperation({ - summary: "Send weekly digest slack to the designated user.", - description: "This endpoint is for development only.", - }) - @ApiCreatedResponse() - @ApiBody({ - schema: { - properties: { - userId: { type: "string" }, - }, - required: ["userId"], - }, - }) - @Post("slack/test") - public async testSendingWeeklyDigest( - @Body("userId") userId: string - ): Promise { - if (this.appConfig.nodeEnv === "production") { - throw new RecnetError( - ErrorCode.INTERNAL_SERVER_ERROR, - HttpStatus.INTERNAL_SERVER_ERROR, - "This endpoint is only for development" - ); - } - - function getMockWeeklyDigestData(): WeeklyDigestContent { - const getMockRec = (title = 1) => - generateMock(recSchema, { - stringMap: { - photoUrl: () => "https://avatar.iran.liara.run/public", - title: () => `Paper Title ${title}`, - }, - }); - const announcement = generateMock(announcementSchema, { - stringMap: { - content: () => "This is a test announcement!", - }, - }); - return { - recs: [getMockRec(), getMockRec(2), getMockRec(3), getMockRec()], - numUnusedInviteCodes: 3, - latestAnnouncement: { - ...announcement, - startAt: new Date(announcement.startAt), - endAt: new Date(announcement.endAt), - }, - }; - } - - const cutoff = getLatestCutOff(); - const user = await this.userRepository.findUserById(userId); - const content = getMockWeeklyDigestData(); - - this.slackService.sendWeeklyDigest(user, content, cutoff); - } -} diff --git a/apps/recnet-api/src/modules/subscription/subscription.module.ts b/apps/recnet-api/src/modules/subscription/subscription.module.ts index 27901146..b94a1323 100644 --- a/apps/recnet-api/src/modules/subscription/subscription.module.ts +++ b/apps/recnet-api/src/modules/subscription/subscription.module.ts @@ -3,13 +3,11 @@ import { Module } from "@nestjs/common"; import { DbRepositoryModule } from "@recnet-api/database/repository/db.repository.module"; import { EmailModule } from "@recnet-api/modules/email/email.module"; -import { SubscriptionController } from "./subscription.controller"; import { WeeklyDigestWorker } from "./weekly-digest.worker"; import { SlackModule } from "../slack/slack.module"; @Module({ - controllers: [SubscriptionController], providers: [WeeklyDigestWorker], imports: [DbRepositoryModule, EmailModule, SlackModule], }) From c60f341b5ac82675b628c3f743934137f4349c28 Mon Sep 17 00:00:00 2001 From: swh00tw Date: Sun, 17 Nov 2024 20:41:02 -0500 Subject: [PATCH 6/8] refactor: extract weekly digest notification text --- apps/recnet-api/src/modules/slack/slack.service.ts | 7 ++++++- .../src/modules/slack/transporters/slack.transporter.ts | 9 +++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/recnet-api/src/modules/slack/slack.service.ts b/apps/recnet-api/src/modules/slack/slack.service.ts index 3a5913d6..3e2d236e 100644 --- a/apps/recnet-api/src/modules/slack/slack.service.ts +++ b/apps/recnet-api/src/modules/slack/slack.service.ts @@ -29,7 +29,12 @@ export class SlackService { content, this.appConfig.nodeEnv ); - result = await this.transporter.sendDirectMessage(user, slackMessage); + const notificationText = `📬 Your RecNet weekly digest has arrived!`; + result = await this.transporter.sendDirectMessage( + user, + slackMessage, + notificationText + ); } catch (e) { return { success: false, userId: user.id }; } diff --git a/apps/recnet-api/src/modules/slack/transporters/slack.transporter.ts b/apps/recnet-api/src/modules/slack/transporters/slack.transporter.ts index e139dfc5..647ccb3e 100644 --- a/apps/recnet-api/src/modules/slack/transporters/slack.transporter.ts +++ b/apps/recnet-api/src/modules/slack/transporters/slack.transporter.ts @@ -31,7 +31,8 @@ export class SlackTransporter { public async sendDirectMessage( user: DbUser, - message: SlackMessageBlocks + message: SlackMessageBlocks, + notificationText?: string ): Promise { if ( this.appConfig.nodeEnv !== "production" && @@ -45,7 +46,7 @@ export class SlackTransporter { while (retryCount < SLACK_RETRY_LIMIT) { try { const slackId = await this.getUserSlackId(user); - await this.postDirectMessage(slackId, message); + await this.postDirectMessage(slackId, message, notificationText); return { success: true }; } catch (error) { retryCount++; @@ -82,7 +83,8 @@ export class SlackTransporter { private async postDirectMessage( userSlackId: string, - message: SlackMessageBlocks + message: SlackMessageBlocks, + notificationText?: string ): Promise { // Open a direct message conversation const conversationResp = await this.client.conversations.open({ @@ -97,7 +99,6 @@ export class SlackTransporter { ); } - const notificationText = `📬 Your RecNet weekly digest has arrived!`; // Send the message await this.client.chat.postMessage({ channel: conversationId, From f7b3c430c7d8efa06b64f23bd60088554e3cc82f Mon Sep 17 00:00:00 2001 From: swh00tw Date: Sun, 17 Nov 2024 20:44:30 -0500 Subject: [PATCH 7/8] refactor: extract string const to weekly digest template --- apps/recnet-api/src/modules/slack/slack.service.ts | 7 +++---- .../slack/templates/weekly-digest.template.ts | 13 +++++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/apps/recnet-api/src/modules/slack/slack.service.ts b/apps/recnet-api/src/modules/slack/slack.service.ts index 3e2d236e..886359c8 100644 --- a/apps/recnet-api/src/modules/slack/slack.service.ts +++ b/apps/recnet-api/src/modules/slack/slack.service.ts @@ -24,16 +24,15 @@ export class SlackService { ): Promise { let result; try { - const slackMessage = weeklyDigestSlackTemplate( + const weeklyDigest = weeklyDigestSlackTemplate( cutoff, content, this.appConfig.nodeEnv ); - const notificationText = `📬 Your RecNet weekly digest has arrived!`; result = await this.transporter.sendDirectMessage( user, - slackMessage, - notificationText + weeklyDigest.messageBlocks, + weeklyDigest.notificationText ); } catch (e) { return { success: false, userId: user.id }; diff --git a/apps/recnet-api/src/modules/slack/templates/weekly-digest.template.ts b/apps/recnet-api/src/modules/slack/templates/weekly-digest.template.ts index 3fc8e854..0d0df702 100644 --- a/apps/recnet-api/src/modules/slack/templates/weekly-digest.template.ts +++ b/apps/recnet-api/src/modules/slack/templates/weekly-digest.template.ts @@ -7,11 +7,16 @@ import { formatDate } from "@recnet/recnet-date-fns"; import type { SlackMessageBlocks } from "../slack.type"; +export type WeeklyDigestDto = { + notificationText?: string; + messageBlocks: SlackMessageBlocks; +}; + export const weeklyDigestSlackTemplate = ( cutoff: Date, content: WeeklyDigestContent, nodeEnv: string -): SlackMessageBlocks => { +): WeeklyDigestDto => { const { recs, numUnusedInviteCodes, latestAnnouncement } = content; const recsGroupByTitle = groupBy(recs, (rec) => { @@ -50,7 +55,7 @@ export const weeklyDigestSlackTemplate = ( ); } - return BlockCollection( + const messageBlocks = BlockCollection( // headers: number of rec Blocks.Header({ text: `${nodeEnv !== "production" && "[DEV] "}📬 Your Weekly Digest for ${formatDate(cutoff)}`, @@ -68,4 +73,8 @@ export const weeklyDigestSlackTemplate = ( text: `👀 Any interesting read this week? ${Md.link("https://recnet.io", "Share with your network!")}`, }) ); + return { + notificationText: `📬 Your RecNet weekly digest has arrived!`, + messageBlocks, + }; }; From 7fa7314a2b30b78f188c42adc185f847e7e20e80 Mon Sep 17 00:00:00 2001 From: swh00tw Date: Sun, 17 Nov 2024 20:44:45 -0500 Subject: [PATCH 8/8] chore: remove comment --- .../src/modules/slack/templates/weekly-digest.template.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/recnet-api/src/modules/slack/templates/weekly-digest.template.ts b/apps/recnet-api/src/modules/slack/templates/weekly-digest.template.ts index 0d0df702..3b539eda 100644 --- a/apps/recnet-api/src/modules/slack/templates/weekly-digest.template.ts +++ b/apps/recnet-api/src/modules/slack/templates/weekly-digest.template.ts @@ -56,7 +56,6 @@ export const weeklyDigestSlackTemplate = ( } const messageBlocks = BlockCollection( - // headers: number of rec Blocks.Header({ text: `${nodeEnv !== "production" && "[DEV] "}📬 Your Weekly Digest for ${formatDate(cutoff)}`, }),