fix: convert SVG avatar into PNG on save and serve (#20654)

This commit is contained in:
Omar López authored and GitHub committed 2025-04-11 08:14:01 -07:00
1 parent 2ab34004a2
commit 821bd37b37
6 files changed
+61 -9

No files matched your search

+14 -1
View File
@@ -5,6 +5,7 @@ import { NextResponse } from "next/server";
import { z } from "zod";
import { AVATAR_FALLBACK, WEBAPP_URL } from "@calcom/lib/constants";
import { convertSvgToPng } from "@calcom/lib/server/imageUtils";
import prisma from "@calcom/prisma";
const querySchema = z.object({
@@ -44,7 +45,19 @@ async function handler(req: NextRequest, { params }: { params: Promise<Params> }
data: true,
},
});
img = data;
// Convert SVG to PNG if needed and update the database
if (data.startsWith("data:image/svg+xml;base64,")) {
const pngData = await convertSvgToPng(data);
await prisma.avatar.update({
where: { objectKey },
data: { data: pngData },
});
img = pngData;
} else {
img = data;
}
} catch (e) {
// If anything goes wrong or avatar is not found, use default avatar
const url = new URL(AVATAR_FALLBACK, WEBAPP_URL).toString();
+1
View File
@@ -139,6 +139,7 @@
"rrule": "^2.7.1",
"sanitize-html": "^2.10.0",
"schema-dts": "^1.1.0",
"sharp": "0.33.5",
"short-uuid": "^4.2.0",
"sonner": "^1.7.4",
"stripe": "^9.16.0",
+1
View File
@@ -27,6 +27,7 @@
"next-collect": "^0.2.1",
"next-i18next": "^15.4.2",
"rrule": "^2.7.1",
"sharp": "0.33.5",
"sonner": "^1.7.4",
"tsdav": "2.0.3",
"tslog": "^4.9.2",
+8 -4
View File
@@ -2,8 +2,11 @@ import { v4 as uuidv4 } from "uuid";
import { prisma } from "@calcom/prisma";
import { convertSvgToPng } from "./imageUtils";
export const uploadAvatar = async ({ userId, avatar: data }: { userId: number; avatar: string }) => {
const objectKey = uuidv4();
const processedData = await convertSvgToPng(data);
await prisma.avatar.upsert({
where: {
@@ -15,12 +18,12 @@ export const uploadAvatar = async ({ userId, avatar: data }: { userId: number; a
},
create: {
userId: userId,
data,
data: processedData,
objectKey,
isBanner: false,
},
update: {
data,
data: processedData,
objectKey,
},
});
@@ -38,6 +41,7 @@ export const uploadLogo = async ({
isBanner?: boolean;
}): Promise<string> => {
const objectKey = uuidv4();
const processedData = await convertSvgToPng(data);
await prisma.avatar.upsert({
where: {
@@ -49,12 +53,12 @@ export const uploadLogo = async ({
},
create: {
teamId,
data,
data: processedData,
objectKey,
isBanner,
},
update: {
data,
data: processedData,
objectKey,
},
});
+31
View File
@@ -0,0 +1,31 @@
import sharp from "sharp";
// Maximum allowed size for SVG data (5MB)
const MAX_SVG_SIZE = 5 * 1024 * 1024;
/**
* Converts an SVG image to PNG format
* @param data Base64 encoded image data
* @returns Base64 encoded PNG data or a placeholder image if conversion fails
*/
export const convertSvgToPng = async (data: string) => {
if (data.startsWith("data:image/svg+xml;base64,")) {
try {
const base64Data = data.replace(/^data:image\/svg\+xml;base64,/, "");
const buffer = Buffer.from(base64Data, "base64");
// Check if the SVG data exceeds the size limit
if (buffer.length > MAX_SVG_SIZE) {
throw new Error("SVG data exceeds maximum allowed size");
}
const pngBuffer = await sharp(buffer).png().toBuffer();
return `data:image/png;base64,${pngBuffer.toString("base64")}`;
} catch (error) {
console.error("Error converting SVG to PNG", error);
// Return a 1x1 transparent PNG as placeholder
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
}
}
return data;
};
+6 -4
View File
@@ -3165,6 +3165,7 @@ __metadata:
next-collect: ^0.2.1
next-i18next: ^15.4.2
rrule: ^2.7.1
sharp: 0.33.5
sonner: ^1.7.4
tsdav: 2.0.3
tslog: ^4.9.2
@@ -3951,6 +3952,7 @@ __metadata:
rrule: ^2.7.1
sanitize-html: ^2.10.0
schema-dts: ^1.1.0
sharp: 0.33.5
short-uuid: ^4.2.0
sonner: ^1.7.4
stripe: ^9.16.0
@@ -4480,11 +4482,11 @@ __metadata:
linkType: hard
"@emnapi/runtime@npm:^1.2.0":
version: 1.3.1
resolution: "@emnapi/runtime@npm:1.3.1"
version: 1.4.0
resolution: "@emnapi/runtime@npm:1.4.0"
dependencies:
tslib: ^2.4.0
checksum: 9a16ae7905a9c0e8956cf1854ef74e5087fbf36739abdba7aa6b308485aafdc993da07c19d7af104cd5f8e425121120852851bb3a0f78e2160e420a36d47f42f
checksum: 90cca909ceb842e95cb5e16ab64e4c4d8925c6f4ed5527848e53faad8c4e23885e4a001a6099e26a76d099a67758032a9f208b13252176e2b68c860d19f5c1f9
languageName: node
linkType: hard
@@ -41502,7 +41504,7 @@ __metadata:
languageName: node
linkType: hard
"sharp@npm:^0.33.5":
"sharp@npm:0.33.5, sharp@npm:^0.33.5":
version: 0.33.5
resolution: "sharp@npm:0.33.5"
dependencies: