Initial implementation, getAppLogos() (#8405)

* Initial implementation, getAppLogos()

* Handle localhost:3000 correctly

* New URL wasn't working with all hostnames

* Only get the first of the subdomainParts, join not needed

* rename url -> hostname

* getAppLogos -> getTeamLogos
pull/8393/head^2
Alex van Andel 2023-04-20 15:43:06 +02:00 committed by GitHub
parent c38dd2d072
commit e1403bfbeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 53 additions and 79 deletions

View File

@ -1,106 +1,80 @@
import fs from "fs";
import mime from "mime-types";
import type { NextApiRequest, NextApiResponse } from "next";
import path from "path";
import { z } from "zod";
import { LOGO, LOGO_ICON } from "@calcom/lib/constants";
import logger from "@calcom/lib/logger";
const log = logger.getChildLogger({ prefix: [`[api/logo]`] });
import { IS_SELF_HOSTED, LOGO, LOGO_ICON, WEBAPP_URL } from "@calcom/lib/constants";
function removePort(url: string) {
return url.replace(/:\d+$/, "");
}
function extractSubdomainAndDomain(url: string): [string, string] | null {
try {
const parsedUrl = new URL(url);
const hostParts = parsedUrl.href.split(".");
if (hostParts.length > 2) {
const subdomain = hostParts.slice(0, hostParts.length - 2).join(".");
const domain = hostParts.slice(hostParts.length - 2).join(".");
return [subdomain, removePort(domain)];
} else if (hostParts.length === 2) {
const subdomain = "";
const domain = hostParts.join(".");
return [subdomain, removePort(domain)];
} else {
return null;
}
} catch (error) {
return null;
}
function extractSubdomainAndDomain(hostname: string) {
const hostParts = removePort(hostname).split(".");
const subdomainParts = hostParts.slice(0, hostParts.length - 2);
const domain = hostParts.slice(hostParts.length - 2).join(".");
return [subdomainParts[0], domain];
}
const logoApiSchema = z.object({
icon: z.coerce.boolean().optional(),
});
function handleDefaultLogo(
req: NextApiRequest,
res: NextApiResponse,
parsedQuery?: z.infer<typeof logoApiSchema>
) {
let fileNameParts = LOGO.split(".");
const SYSTEM_SUBDOMAINS = ["console", "app", "www"];
if (parsedQuery?.icon) {
fileNameParts = LOGO_ICON.split(".");
async function getTeamLogos(subdomain: string) {
if (
// if not cal.com
IS_SELF_HOSTED ||
// missing subdomain (empty string)
!subdomain ||
// in SYSTEM_SUBDOMAINS list
SYSTEM_SUBDOMAINS.includes(subdomain)
) {
return {
appLogo: `${WEBAPP_URL}${LOGO}`,
appIconLogo: `${WEBAPP_URL}${LOGO_ICON}`,
};
}
const { [fileNameParts.length - 1]: fileExtension } = fileNameParts;
const STATIC_PATH = path.join(process.cwd(), "public" + LOGO);
const imageBuffer = fs.readFileSync(STATIC_PATH);
const mimeType = mime.lookup(fileExtension);
if (mimeType) res.setHeader("Content-Type", mimeType);
res.setHeader("Cache-Control", "s-maxage=86400");
res.send(imageBuffer);
// load from DB
const { default: prisma } = await import("@calcom/prisma");
const team = await prisma.team.findUniqueOrThrow({
where: {
slug: subdomain,
},
select: {
appLogo: true,
appIconLogo: true,
},
});
// try to use team logos, otherwise default to LOGO/LOGO_ICON regardless
return {
appLogo: team.appLogo || `${WEBAPP_URL}${LOGO}`,
appIconLogo: team.appIconLogo || `${WEBAPP_URL}${LOGO_ICON}`,
};
}
/**
* This API endpoint is used to serve the logo associated with a team if no logo is found we serve our default logo
*/
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { query } = req;
const parsedQuery = logoApiSchema.parse(query);
const { query } = req;
const parsedQuery = logoApiSchema.parse(query);
const hostname = req?.headers["host"];
if (!hostname) throw new Error("No hostname");
const domains = extractSubdomainAndDomain(hostname);
if (!domains) throw new Error("No domains");
const [subdomain, domain] = domains;
// Only supported on cal.com and cal.dev
if (["cal.com", "cal.dev"].includes(domain)) return handleDefaultLogo(req, res, parsedQuery);
// Skip if no subdomain
if (!subdomain) throw new Error("No subdomain");
// Omit system subdomains
if (["console", "app", "www"].includes(subdomain)) return handleDefaultLogo(req, res, parsedQuery);
const hostname = req?.headers["host"];
if (!hostname) throw new Error("No hostname");
const domains = extractSubdomainAndDomain(hostname);
if (!domains) throw new Error("No domains");
const { default: prisma } = await import("@calcom/prisma");
const [subdomain] = domains;
const { appLogo, appIconLogo } = await getTeamLogos(subdomain);
const team = await prisma.team.findUnique({
where: {
slug: subdomain,
},
select: {
appLogo: true,
appIconLogo: true,
},
});
const filteredLogo = parsedQuery?.icon ? appIconLogo : appLogo;
const filteredLogo = parsedQuery?.icon ? team?.appIconLogo : team?.appLogo;
if (!filteredLogo) throw new Error("No team appLogo");
const response = await fetch(filteredLogo);
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
res.setHeader("Content-Type", response.headers.get("content-type") as string);
res.setHeader("Cache-Control", "s-maxage=86400");
res.send(buffer);
} catch (e) {
if (e instanceof Error) log.debug(e.message);
handleDefaultLogo(req, res);
}
const response = await fetch(filteredLogo);
const arrayBuffer = await response.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
res.setHeader("Content-Type", response.headers.get("content-type") as string);
res.setHeader("Cache-Control", "s-maxage=86400");
res.send(buffer);
}