Skip to content
This repository was archived by the owner on Aug 13, 2024. It is now read-only.

Commit 2c94638

Browse files
feat: improve performance when generating highlight card image (#60)
1 parent 0a0e968 commit 2c94638

File tree

5 files changed

+38
-35
lines changed

5 files changed

+38
-35
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ DISK_PERCENTAGE=0.7
88
DISK_SIZE=100
99
NODE_ENV=development
1010
API_CODENAME=opengraph-local
11+
API_BASE_URL=https://beta.api.opensauced.pizza
1112

1213
# Github
1314
GITHUB_PAT_USER=

src/social-card/highlight-card/highlight-card.controller.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export class HighlightCardController {
2222
@Get("/:id")
2323
@ApiOperation({
2424
operationId: "generateHighlightSocialCard",
25-
summary: "Gets latest cache aware social card link for :id or generates a new one",
25+
summary: "Generates the social card image for the provided highlight ID",
2626
})
2727
@Header("Content-Type", "image/png")
2828
@ApiOkResponse({ type: StreamableFile, description: "Social card image" })
@@ -34,15 +34,9 @@ export class HighlightCardController {
3434
@Param("id", ParseIntPipe) id: number,
3535
@Res({ passthrough: true }) res: FastifyReply,
3636
): Promise<void> {
37-
const { fileUrl, hasFile, needsUpdate } = await this.highlightCardService.checkRequiresUpdate(id);
37+
const png = await this.highlightCardService.getHighlightCard(id);
3838

39-
if (hasFile && !needsUpdate) {
40-
return res.status(HttpStatus.FOUND).redirect(fileUrl);
41-
}
42-
43-
const url = await this.highlightCardService.getHighlightCard(id);
44-
45-
return res.status(HttpStatus.FOUND).redirect(url);
39+
return res.status(HttpStatus.OK).send(png);
4640
}
4741

4842
@Get("/:id/metadata")

src/social-card/highlight-card/highlight-card.service.ts

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { HttpService } from "@nestjs/axios";
33
import { Resvg } from "@resvg/resvg-js";
44
import { Repository, Language } from "@octokit/graphql-schema";
55
import fs from "node:fs/promises";
6+
import { firstValueFrom } from "rxjs";
67

78
import { GithubService } from "../../github/github.service";
89
import { S3FileStorageService } from "../../s3-file-storage/s3-file-storage.service";
910
import userLangs from "../templates/shared/user-langs";
1011
import userProfileRepos from "../templates/shared/user-repos";
1112
import tailwindConfig from "../templates/tailwind.config";
12-
import { firstValueFrom } from "rxjs";
1313
import highlightCardTemplate from "../templates/highlight-card.template";
1414
import { DbUserHighlight } from "../../github/entities/db-user-highlight.entity";
1515
import { DbReaction } from "../../github/entities/db-reaction.entity";
@@ -32,6 +32,7 @@ interface HighlightCardData {
3232
@Injectable()
3333
export class HighlightCardService {
3434
private readonly logger = new Logger(this.constructor.name);
35+
private fonts: Buffer[] = [];
3536

3637
constructor (
3738
private readonly httpService: HttpService,
@@ -40,20 +41,20 @@ export class HighlightCardService {
4041
) {}
4142

4243
private async getHighlightData (highlightId: number): Promise<HighlightCardData> {
43-
const highlightReq = await firstValueFrom(
44-
this.httpService.get<DbUserHighlight>(`https://api.opensauced.pizza/v1/user/highlights/${highlightId}`),
44+
const highlightReq = firstValueFrom(
45+
this.httpService.get<DbUserHighlight>(`${process.env.API_BASE_URL!}/v1/user/highlights/${highlightId}`),
4546
);
46-
const { login, updated_at, url, highlight: body } = highlightReq.data;
4747

48-
const reactionsReq = await firstValueFrom(
49-
this.httpService.get<DbReaction[]>(`https://api.opensauced.pizza/v1/highlights/${highlightId}/reactions`),
48+
const reactionsReq = firstValueFrom(
49+
this.httpService.get<DbReaction[]>(`${process.env.API_BASE_URL!}/v1/highlights/${highlightId}/reactions`),
5050
);
51-
const reactions = reactionsReq.data.reduce<number>((acc, curr) => acc + Number(curr.reaction_count), 0);
5251

52+
const [highlight, highlightReactions] = await Promise.all([highlightReq, reactionsReq]);
53+
const { login, updated_at, url, highlight: body } = highlight.data;
5354
const [owner, repoName] = url.replace("https://github.com/", "").split("/");
5455

55-
const user = await this.githubService.getUser(login);
5656
const repo = await this.githubService.getRepo(owner, repoName);
57+
const reactions = highlightReactions.data.reduce<number>((acc, curr) => acc + Number(curr.reaction_count), 0);
5758

5859
const langList = repo.languages?.edges?.flatMap(edge => {
5960
if (edge) {
@@ -68,7 +69,7 @@ export class HighlightCardService {
6869
body,
6970
login,
7071
reactions,
71-
avatarUrl: `${String(user.avatarUrl)}&size=150`,
72+
avatarUrl: `https://github.com/${login}.png?size=150`,
7273
langs: langList,
7374
langTotal: repo.languages?.totalSize ?? 0,
7475
repo,
@@ -77,6 +78,17 @@ export class HighlightCardService {
7778
};
7879
}
7980

81+
private async getFonts () {
82+
if (this.fonts.length === 0) {
83+
const interArrayBufferReq = fs.readFile("node_modules/@fontsource/inter/files/inter-all-400-normal.woff");
84+
const interArrayBufferMediumReq = fs.readFile("node_modules/@fontsource/inter/files/inter-all-500-normal.woff");
85+
86+
this.fonts = await Promise.all([interArrayBufferReq, interArrayBufferMediumReq]);
87+
}
88+
89+
return this.fonts;
90+
}
91+
8092
// public only to be used in local scripts. Not for controller direct use.
8193
async generateCardBuffer (highlightId: number, highlightData?: HighlightCardData) {
8294
const { html } = await import("satori-html");
@@ -90,8 +102,7 @@ export class HighlightCardService {
90102
highlightCardTemplate(avatarUrl, body, userLangs(langs, langTotal), userProfileRepos([repo], 2), reactions),
91103
);
92104

93-
const interArrayBuffer = await fs.readFile("node_modules/@fontsource/inter/files/inter-all-400-normal.woff");
94-
const interArrayBufferMedium = await fs.readFile("node_modules/@fontsource/inter/files/inter-all-500-normal.woff");
105+
const [interArrayBuffer, interArrayBufferMedium] = await this.getFonts();
95106

96107
const svg = await satori(template, {
97108
width: 1200,
@@ -133,12 +144,16 @@ export class HighlightCardService {
133144
};
134145

135146
if (hasFile) {
136-
const lastModified = await this.s3FileStorageService.getFileLastModified(hash);
147+
const lastModifiedReq = this.s3FileStorageService.getFileLastModified(hash);
148+
const highlightReq = this.getHighlightData(id);
149+
const metadataReq = this.s3FileStorageService.getFileMeta(hash);
150+
151+
const [lastModified, highlight, metadata] = await Promise.all([lastModifiedReq, highlightReq, metadataReq]);
137152

138153
returnVal.lastModified = lastModified;
139154

140-
const { updated_at, reactions } = await this.getHighlightData(id);
141-
const metadata = await this.s3FileStorageService.getFileMeta(hash);
155+
const { updated_at, reactions } = highlight;
156+
142157
const savedReactions = metadata?.["reactions-count"] ?? "0";
143158

144159
if (lastModified && lastModified > updated_at && savedReactions === String(reactions)) {
@@ -152,7 +167,7 @@ export class HighlightCardService {
152167
return returnVal;
153168
}
154169

155-
async getHighlightCard (id: number): Promise<string> {
170+
async getHighlightCard (id: number): Promise<Buffer> {
156171
const { remaining } = await this.githubService.rateLimit();
157172

158173
if (remaining < 1000) {
@@ -162,16 +177,9 @@ export class HighlightCardService {
162177
const highlightData = await this.getHighlightData(id);
163178

164179
try {
165-
const hash = `highlights/${String(id)}.png`;
166-
const fileUrl = `${this.s3FileStorageService.getCdnEndpoint()}${hash}`;
167-
168180
const { png } = await this.generateCardBuffer(id, highlightData);
169181

170-
await this.s3FileStorageService.uploadFile(png, hash, "image/png", { "reactions-count": String(highlightData.reactions) });
171-
172-
this.logger.debug(`Highlight ${id} did not exist in S3, generated image and uploaded to S3, redirecting`);
173-
174-
return fileUrl;
182+
return png;
175183
} catch (e) {
176184
this.logger.error(`Error generating highlight card for ${id}`, e);
177185

src/social-card/insight-card/insight-card.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class InsightCardService {
3535
const maxRepoQueryIdsLenght = 10;
3636

3737
const insightPageReq = await firstValueFrom(
38-
this.httpService.get<DbInsight>(`https://api.opensauced.pizza/v1/insights/${insightId}`),
38+
this.httpService.get<DbInsight>(`${process.env.API_BASE_URL!}/v1/insights/${insightId}`),
3939
);
4040

4141
const { repos, name, updated_at } = insightPageReq.data;
@@ -52,7 +52,7 @@ export class InsightCardService {
5252

5353
const contributorsReq = await firstValueFrom(
5454
this.httpService.get<{ data: { author_login: string }[] }>(
55-
`https://api.opensauced.pizza/v1/contributors/search?${String(query)}`,
55+
`${process.env.API_BASE_URL!}/v1/contributors/search?${String(query)}`,
5656
),
5757
);
5858

test/local-dev/HighlightCards.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { existsSync } from "node:fs";
44
import { mkdir, writeFile } from "fs/promises";
55
import { HighlightCardService } from "../../src/social-card/highlight-card/highlight-card.service";
66

7-
const testHighlights = [102, 101, 103, 171];
7+
const testHighlights = [124, 120, 116, 115];
88

99
const folderPath = "dist/local-dev";
1010

0 commit comments

Comments
 (0)