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 -> getTeamLogospull/8393/head^2
parent
c38dd2d072
commit
e1403bfbeb
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue