Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release dev to master recnet-api v1.8.3 / recnet v1.16.2 #360

Merged
merged 16 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/recnet-api/package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"name": "recnet-api",
"version": "1.8.2"
"version": "1.8.3"
}
10 changes: 9 additions & 1 deletion apps/recnet-api/src/database/repository/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,22 @@ export default class UserRepository {
select: user.select,
});

await prisma.inviteCode.update({
const inviteCode = await prisma.inviteCode.update({
where: { code: createUserInput.inviteCode },
data: {
usedById: userInTransaction.id,
usedAt: new Date(),
},
});

// follow the person who gave the invite code
await prisma.followingRecord.create({
data: {
followedById: userInTransaction.id,
followingId: inviteCode.ownerId,
},
});

return userInTransaction;
});
}
Expand Down
8 changes: 6 additions & 2 deletions apps/recnet-api/src/modules/slack/slack.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,16 @@ export class SlackService {
): Promise<SendSlackResult> {
let result;
try {
const slackMessage = weeklyDigestSlackTemplate(
const weeklyDigest = weeklyDigestSlackTemplate(
cutoff,
content,
this.appConfig.nodeEnv
);
result = await this.transporter.sendDirectMessage(user, slackMessage);
result = await this.transporter.sendDirectMessage(
user,
weeklyDigest.messageBlocks,
weeklyDigest.notificationText
);
} catch (e) {
return { success: false, userId: user.id };
}
Expand Down
4 changes: 4 additions & 0 deletions apps/recnet-api/src/modules/slack/slack.type.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { SlackBlockDto } from "slack-block-builder";

export type SendSlackResult = {
success: boolean;
skip?: boolean;
userId?: string;
};

export type SlackMessageBlocks = Readonly<SlackBlockDto>[];
Original file line number Diff line number Diff line change
@@ -1,19 +1,79 @@
import groupBy from "lodash.groupby";
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 type WeeklyDigestDto = {
notificationText?: string;
messageBlocks: SlackMessageBlocks;
};

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})`
): WeeklyDigestDto => {
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(
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}`,
})
);
}

const messageBlocks = BlockCollection(
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(),
...recSection.flat(),
...footer,
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/`;
return {
notificationText: `📬 Your RecNet weekly digest has arrived!`,
messageBlocks,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -31,7 +31,8 @@ export class SlackTransporter {

public async sendDirectMessage(
user: DbUser,
message: string
message: SlackMessageBlocks,
notificationText?: string
): Promise<SendSlackResult> {
if (
this.appConfig.nodeEnv !== "production" &&
Expand All @@ -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++;
Expand Down Expand Up @@ -82,7 +83,8 @@ export class SlackTransporter {

private async postDirectMessage(
userSlackId: string,
message: string
message: SlackMessageBlocks,
notificationText?: string
): Promise<void> {
// Open a direct message conversation
const conversationResp = await this.client.conversations.open({
Expand All @@ -100,7 +102,8 @@ export class SlackTransporter {
// Send the message
await this.client.chat.postMessage({
channel: conversationId,
text: message,
text: notificationText,
blocks: message,
});
}
}
15 changes: 15 additions & 0 deletions apps/recnet/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.

## [1.16.2](https://github.com/lil-lab/recnet/compare/recnet-web-v1.16.1...recnet-web-v1.16.2) (2024-11-18)


### Features

* add new slack msg template ([7807854](https://github.com/lil-lab/recnet/commit/780785445461f437b93ec1e9f325470120f38d98))
* finish update slack msg template ([3860d5e](https://github.com/lil-lab/recnet/commit/3860d5e2ac7b92cb162aa5fc3b10073a85fbc518))
* follow the person who invites the user ([2f213ae](https://github.com/lil-lab/recnet/commit/2f213ae8c5cf992281ebfc8ccac8f1fc9399fb92))
* install slack block builder ([4d771a6](https://github.com/lil-lab/recnet/commit/4d771a61da05415a78078cb4abd0528b73c7f0b7))


### Bug Fixes

* use prisma instance inside the transaction ([75e5bcd](https://github.com/lil-lab/recnet/commit/75e5bcde05941e9d5dd20dca1d6e5b214e593395))

## [1.16.1](https://github.com/lil-lab/recnet/compare/recnet-web-v1.16.0...recnet-web-v1.16.1) (2024-11-08)


Expand Down
2 changes: 1 addition & 1 deletion apps/recnet/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "recnet",
"version": "1.16.1",
"version": "1.16.2",
"commit-and-tag-version": {
"skip": {
"commit": true
Expand Down
67 changes: 43 additions & 24 deletions apps/recnet/src/app/rec/[id]/opengraph-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export const size = {

export const contentType = "image/png";

const fallbackImage =
"https://recnet.io/_next/image?url=%2Frecnet-logo.webp&w=64&q=100";

/**
Styling: You can write Tailwind CSS via "tw" prop here.
However, our radix-theme preset won't be available here.
Expand All @@ -25,19 +28,22 @@ export default async function Image({ params }: { params: { id: string } }) {
const { rec } = await serverClient.getRecById({
id,
});
const linkPreviewMetadata = await serverClient.getLinkPreviewMetadata({
url: rec.article.link,
});

return new ImageResponse(
(
// ImageResponse JSX element
<div
tw={cn(
"flex flex-col justify-start h-full bg-white",
"p-8",
"flex flex-col justify-start h-full w-full bg-white",
"py-8",
"gap-y-2",
"text-[48px]"
)}
>
<div tw="flex flex-row items-center">
<div tw="flex flex-row items-center px-8">
{/* eslint-disable-next-line */}
<img
src={"https://imgur.com/jVp1pG1.png"}
Expand All @@ -46,28 +52,41 @@ export default async function Image({ params }: { params: { id: string } }) {
/>
<p tw={cn("text-[32px] text-[#0090FF]")}> RecNet </p>
</div>
<div tw="font-medium">{rec.article.title}</div>
<p tw="text-[28px] text-gray-700">{rec.description} </p>
<p tw={cn("text-[24px] text-gray-600")}>{rec.article.author} </p>
<div
tw={cn(
"flex flex-row items-center",
"mt-auto",
"text-gray-600 text-[24px]"
)}
>
{/* eslint-disable-next-line */}
<img
src={rec.user.photoUrl}
tw="w-12 h-12 rounded-full mr-4"
alt={rec.user.displayName}
/>
<span tw="self-center">{`${rec.user.displayName} · ${formatDate(new Date(rec.cutoff))}`}</span>
{rec.isSelfRec ? (
<div tw="ml-4 bg-[#FFEFDD] text-[#D14E00] rounded-md px-2 py-1 text-[16px] font-medium w-fit max-w-fit">
Self Rec
<div tw="flex flex-col justify-start px-8 w-[1600px]">
<div tw="flex flex-row items-center">
{/* eslint-disable-next-line */}
<img
src={rec.user.photoUrl}
tw="w-10 h-10 rounded-full mr-4"
alt={rec.user.displayName}
/>
<p tw="text-[24px] text-gray-600">
<span tw="mr-2 text-[#0090FF] font-bold">{`${rec.user.displayName}`}</span>
{formatDate(new Date(rec.cutoff))}
</p>
</div>
<p tw="text-[24px] text-gray-600 px-2 mb-12 mt-0">
{rec.description}
</p>
<div tw="flex flex-row items-center my-4 rounded-[16px] border-[1px] border-[#D9D9D9] text-gray-11">
<div tw="aspect-square w-auto max-w-[200px] h-full flex items-center justify-center">
{/* eslint-disable-next-line */}
<img
src={
linkPreviewMetadata?.logo?.url ||
linkPreviewMetadata?.image?.url ||
fallbackImage
}
alt={"link-metadata-logo"}
tw={cn("object-contain", "p-4", "min-w-[120px] max-w-[150px]")}
/>
</div>
<div tw="flex flex-col p-3 justify-between h-full w-min gap-y-2 pr-2 text-wrap text-gray-600 text-[16px] border-l-[1px] border-[#D9D9D9]">
<p tw="text-gray-900 text-[24px]">{rec.article.title}</p>
<p>{rec.article.author}</p>
<p>{rec.article.link.replace("https://", "").split("/")[0]}</p>
</div>
) : null}
</div>
</div>
</div>
),
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading