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:
parent
f0abf47ecc
commit
e24d8889fc
5 changed files with 77 additions and 14 deletions
|
@ -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);
|
||||||
|
|
|
@ -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 [
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
42
pages/api/user/avatar.ts
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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">
|
||||||
|
|
Loading…
Reference in a new issue