diff --git a/.env.example b/.env.example index 3b7cf159e6..503dee3414 100644 --- a/.env.example +++ b/.env.example @@ -138,6 +138,11 @@ FORMBRICKS_HOST_URL=https://app.formbricks.com FORMBRICKS_ENVIRONMENT_ID= FORMBRICKS_FEEDBACK_SURVEY_ID= +# AvatarAPI +# Used to pre-fill avatar during signup +AVATARAPI_USERNAME= +AVATARAPI_PASSWORD= + # Twilio # Used to send SMS reminders in workflows TWILIO_SID= diff --git a/packages/features/auth/signup/handlers/selfHostedHandler.ts b/packages/features/auth/signup/handlers/selfHostedHandler.ts index 60e1111432..62c5e25448 100644 --- a/packages/features/auth/signup/handlers/selfHostedHandler.ts +++ b/packages/features/auth/signup/handlers/selfHostedHandler.ts @@ -15,6 +15,7 @@ import { signupSchema } from "@calcom/prisma/zod-utils"; import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; import { joinAnyChildTeamOnOrgInvite } from "../utils/organization"; +import { prefillAvatar } from "../utils/prefillAvatar"; import { findTokenByToken, throwIfTokenExpired, @@ -135,6 +136,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) identityProvider: IdentityProvider.CAL, }, }); + + if (process.env.AVATARAPI_USERNAME && process.env.AVATARAPI_PASSWORD) { + await prefillAvatar({ email: userEmail }); + } + await sendEmailVerification({ email: userEmail, username: correctedUsername, diff --git a/packages/features/auth/signup/utils/prefillAvatar.ts b/packages/features/auth/signup/utils/prefillAvatar.ts new file mode 100644 index 0000000000..89ec7681bb --- /dev/null +++ b/packages/features/auth/signup/utils/prefillAvatar.ts @@ -0,0 +1,84 @@ +import type { Prisma } from "@prisma/client"; +import fetch from "node-fetch"; + +import { resizeBase64Image } from "@calcom/lib/server/resizeBase64Image"; +import prisma from "@calcom/prisma"; +import { uploadAvatar } from "@calcom/trpc/server/routers/loggedInViewer/updateProfile.handler"; + +interface IPrefillAvatar { + email: string; +} + +async function downloadImageDataFromUrl(url: string) { + try { + const response = await fetch(url); + + if (!response.ok) { + console.log("Error fetching image from: ", url); + return null; + } + + const imageBuffer = await response.buffer(); + const base64Image = `data:image/png;base64,${imageBuffer.toString("base64")}`; + + return base64Image; + } catch (error) { + console.log(error); + return null; + } +} + +export const prefillAvatar = async ({ email }: IPrefillAvatar) => { + const imageUrl = await getImageUrlAvatarAPI(email); + if (!imageUrl) return; + + const base64Image = await downloadImageDataFromUrl(imageUrl); + if (!base64Image) return; + + const avatar = await resizeBase64Image(base64Image); + const user = await prisma.user.findFirst({ + where: { email: email }, + }); + + if (!user) { + return; + } + const avatarUrl = await uploadAvatar({ userId: user.id, avatar }); + + const data: Prisma.UserUpdateInput = {}; + data.avatarUrl = avatarUrl; + data.avatar = avatar; + + await prisma.user.update({ + where: { email: email }, + data, + }); +}; + +const getImageUrlAvatarAPI = async (email: string) => { + if (!process.env.AVATARAPI_USERNAME || !process.env.AVATARAPI_PASSWORD) { + console.info("No avatar api credentials found"); + return null; + } + + const response = await fetch("https://avatarapi.com/v2/api.aspx", { + method: "POST", + headers: { + "Content-Type": "text/plain", + }, + body: JSON.stringify({ + username: process.env.AVATARAPI_USERNAME, + password: process.env.AVATARAPI_PASSWORD, + email: email, + }), + }); + + const info = await response.json(); + + if (!info.Success) { + console.log("Error from avatar api: ", info.Error); + return null; + } + + return info.Image as string; +}; diff --git a/packages/trpc/server/routers/loggedInViewer/updateProfile.handler.ts b/packages/trpc/server/routers/loggedInViewer/updateProfile.handler.ts index 8158dd0c69..16f46360ea 100644 --- a/packages/trpc/server/routers/loggedInViewer/updateProfile.handler.ts +++ b/packages/trpc/server/routers/loggedInViewer/updateProfile.handler.ts @@ -33,7 +33,7 @@ type UpdateProfileOptions = { input: TUpdateProfileInputSchema; }; -const uploadAvatar = async ({ userId, avatar: data }: { userId: number; avatar: string }) => { +export const uploadAvatar = async ({ userId, avatar: data }: { userId: number; avatar: string }) => { const objectKey = uuidv4(); await prisma.avatar.upsert({ diff --git a/turbo.json b/turbo.json index a4fc9423e1..01a0388131 100644 --- a/turbo.json +++ b/turbo.json @@ -261,6 +261,8 @@ "FORMBRICKS_HOST_URL", "FORMBRICKS_ENVIRONMENT_ID", "FORMBRICKS_FEEDBACK_SURVEY_ID", + "AVATARAPI_USERNAME", + "AVATARAPI_PASSWORD", "GIPHY_API_KEY", "GITHUB_API_REPO_TOKEN", "GOOGLE_API_CREDENTIALS",