Cal 710 turn dataimagejpegbase64 avatar into (#1429)

* --wip

* added next-config custom header path

* added avatar endpoint

* cleanup --wip

* adding gravatar fallback support --wip

* added endpoint rewrite and avatar access

* gravatar support added

* build err fix

* updated HeadSEO with new avatar logic

* --wip

* adds og compat

* added truncated bio

* cleanup

* changed truncate of body from 24 chars to 32 chars

* removed unused, commented code

* removed trailing whitespace

* requested changes

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
This commit is contained in:
Syed Ali Shahbaz 2022-01-11 14:24:02 +05:30 committed by GitHub
parent f0abf47ecc
commit e24d8889fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 77 additions and 14 deletions

View file

@ -10,8 +10,8 @@ export type HeadSeoProps = {
description: string; description: string;
siteName?: string; siteName?: string;
name?: string; name?: string;
avatar?: string;
url?: string; url?: string;
username?: string;
canonical?: string; canonical?: string;
nextSeoProps?: NextSeoProps; nextSeoProps?: NextSeoProps;
}; };
@ -39,9 +39,6 @@ const buildSeoMeta = (pageProps: {
images: [ images: [
{ {
url: image, url: image,
//width: 1077,
//height: 565,
//alt: "Alt image"
}, },
], ],
}, },
@ -66,11 +63,14 @@ const buildSeoMeta = (pageProps: {
}; };
}; };
const constructImage = (name: string, avatar: string, description: string): string => { const constructImage = (name: string, description: string, username: string): string => {
return ( return (
encodeURIComponent("Meet **" + name + "** <br>" + description).replace(/'/g, "%27") + encodeURIComponent("Meet **" + name + "** <br>" + description).replace(/'/g, "%27") +
".png?md=1&images=https%3A%2F%2Fcal.com%2Flogo-white.svg&images=" + ".png?md=1&images=https%3A%2F%2Fcal.com%2Flogo-white.svg&images=" +
encodeURIComponent(avatar) (process.env.NEXT_PUBLIC_APP_URL || process.env.BASE_URL) +
"/" +
username +
"/avatar.png"
); );
}; };
@ -82,18 +82,31 @@ export const HeadSeo: React.FC<HeadSeoProps & { children?: never }> = (props) =>
title, title,
description, description,
name = null, name = null,
avatar = null, username = null,
siteName, siteName,
canonical = defaultUrl, canonical = defaultUrl,
nextSeoProps = {}, nextSeoProps = {},
} = props; } = props;
const truncatedDescription = description.length > 32 ? description.substring(0, 31) + "..." : description;
const pageTitle = title + " | Cal.com"; const pageTitle = title + " | Cal.com";
let seoObject = buildSeoMeta({ title: pageTitle, image, description, canonical, siteName }); let seoObject = buildSeoMeta({
title: pageTitle,
image,
description: truncatedDescription,
canonical,
siteName,
});
if (name && avatar) { if (name && username) {
const pageImage = getSeoImage("ogImage") + constructImage(name, avatar, description); const pageImage = getSeoImage("ogImage") + constructImage(name, truncatedDescription, username);
seoObject = buildSeoMeta({ title: pageTitle, description, image: pageImage, canonical, siteName }); seoObject = buildSeoMeta({
title: pageTitle,
description: truncatedDescription,
image: pageImage,
canonical,
siteName,
});
} }
const seoProps: NextSeoProps = merge(nextSeoProps, seoObject); const seoProps: NextSeoProps = merge(nextSeoProps, seoObject);

View file

@ -72,6 +72,14 @@ module.exports = () => plugins.reduce((acc, next) => next(acc), {
return config; return config;
}, },
async rewrites() {
return [
{
source: "/:user/avatar.png",
destination: "/api/user/avatar?username=:user",
},
]
},
async redirects() { async redirects() {
return [ return [
{ {

View file

@ -29,9 +29,10 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
<> <>
<HeadSeo <HeadSeo
title={nameOrUsername} title={nameOrUsername}
description={nameOrUsername} description={(user.bio as string) || ""}
name={nameOrUsername} name={nameOrUsername}
avatar={user.avatar || undefined} username={(user.username as string) || ""}
// avatar={user.avatar || undefined}
/> />
{isReady && ( {isReady && (
<div className="h-screen bg-neutral-50 dark:bg-black"> <div className="h-screen bg-neutral-50 dark:bg-black">

42
pages/api/user/avatar.ts Normal file
View file

@ -0,0 +1,42 @@
import crypto from "crypto";
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@lib/prisma";
import { defaultAvatarSrc } from "@lib/profile";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// const username = req.url?.substring(1, req.url.lastIndexOf("/"));
const username = req.query.username as string;
const user = await prisma.user.findUnique({
where: {
username: username,
},
select: {
avatar: true,
email: true,
},
});
const emailMd5 = crypto
.createHash("md5")
.update(user?.email as string)
.digest("hex");
const img = user?.avatar;
if (img) {
const decoded = img
.toString()
.replace("data:image/png;base64,", "")
.replace("data:image/jpeg;base64,", "");
const imageResp = Buffer.from(decoded, "base64");
res.writeHead(200, {
"Content-Type": "image/png",
"Content-Length": imageResp.length,
});
res.end(imageResp);
} else {
res.writeHead(302, {
Location: defaultAvatarSrc({ md5: emailMd5 }),
});
res.end();
}
}

View file

@ -220,7 +220,6 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
/> />
</div> </div>
</div> </div>
<div className="block sm:flex"> <div className="block sm:flex">
<div className="w-full mb-6 sm:w-1/2 sm:mr-2"> <div className="w-full mb-6 sm:w-1/2 sm:mr-2">
<label htmlFor="email" className="block text-sm font-medium text-gray-700"> <label htmlFor="email" className="block text-sm font-medium text-gray-700">