* create seed for org upgrade * migrate about/new pages * pages refactor + use persistant zustand store * invited members view + confirm button * add check to org slug * check reserved subdomain * remove quotes from subdomain env var * intro to creation + billing * create price * fix types * createWithPaymentIntent + permission check on teams * open stripe link in popup * intro to tests * organization price and seat override tests * move permissions to a new permission service * update env and permission check * intro to paid invoice trigger * dont use subId * wip * Get e2e working with migration of teams and members * fix ts errors * Get flow working again * Fix various issues and refactor * Some more fixes * Fix wrong page route link * Platform onboarding fix and moving members of team to org rix * Platform onboarding fix and moving members of team to org rix * Fix tests, found a bug * Get custom price flow working * Get admin impersonation flow working for a non-existent user * Fix unit test * Admin onboarding handover * fix admin onboarding tests * fix ts error * Get updateQuantity working * More fixes * fix test * fix schema name * Add tests * Add missing file * More tests * Add more tests * fix mt-2 moving down input into overflow * fix handover layout removing HOC * Update PR_TODO.md * fix ts error due to merge conflict --------- Co-authored-by: Hariom Balhara <hariombalhara@gmgmail.com> Co-authored-by: Hariom <hariombalhara@gmail.com> Co-authored-by: Omar López <zomars@me.com>
149 lines
4.1 KiB
TypeScript
149 lines
4.1 KiB
TypeScript
import z from "zod";
|
|
|
|
import { HttpError } from "@calcom/lib/http-error";
|
|
import { safeStringify } from "@calcom/lib/safeStringify";
|
|
|
|
import logger from "../../logger";
|
|
|
|
const log = logger.getSubLogger({ prefix: ["Vercel/DomainManager"] });
|
|
const vercelApiForProjectUrl = `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}`;
|
|
const vercelDomainApiResponseSchema = z.object({
|
|
error: z
|
|
.object({
|
|
code: z.string().nullish(),
|
|
domain: z.string().nullish(),
|
|
})
|
|
.optional(),
|
|
});
|
|
|
|
export const createDomain = async (domain: string) => {
|
|
assertVercelEnvVars(process.env);
|
|
log.info(`Creating domain in Vercel: ${domain}`);
|
|
const response = await fetch(`${vercelApiForProjectUrl}/domains?teamId=${process.env.TEAM_ID_VERCEL}`, {
|
|
body: JSON.stringify({ name: domain }),
|
|
headers: {
|
|
Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
method: "POST",
|
|
});
|
|
|
|
const data = vercelDomainApiResponseSchema.parse(await response.json());
|
|
|
|
if (!data.error) {
|
|
return true;
|
|
}
|
|
|
|
return handleDomainCreationError(data.error);
|
|
};
|
|
|
|
export const deleteDomain = async (domain: string) => {
|
|
log.info(`Deleting domain in Vercel: ${domain}`);
|
|
assertVercelEnvVars(process.env);
|
|
|
|
const response = await fetch(
|
|
`${vercelApiForProjectUrl}/domains/${domain}?teamId=${process.env.TEAM_ID_VERCEL}`,
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`,
|
|
},
|
|
method: "DELETE",
|
|
}
|
|
);
|
|
|
|
const data = vercelDomainApiResponseSchema.parse(await response.json());
|
|
if (!data.error) {
|
|
return true;
|
|
}
|
|
|
|
return handleDomainDeletionError(data.error);
|
|
};
|
|
|
|
function handleDomainCreationError(error: { code?: string | null; domain?: string | null }) {
|
|
// Domain is already owned by another team but you can request delegation to access it
|
|
if (error.code === "forbidden") {
|
|
const errorMessage = "Domain is already owned by another team";
|
|
log.error(
|
|
safeStringify({
|
|
errorMessage,
|
|
vercelError: error,
|
|
})
|
|
);
|
|
throw new HttpError({
|
|
message: errorMessage,
|
|
statusCode: 400,
|
|
});
|
|
}
|
|
|
|
if (error.code === "domain_taken") {
|
|
const errorMessage = "Domain is already being used by a different project";
|
|
log.error(
|
|
safeStringify({
|
|
errorMessage,
|
|
vercelError: error,
|
|
})
|
|
);
|
|
throw new HttpError({
|
|
message: errorMessage,
|
|
statusCode: 400,
|
|
});
|
|
}
|
|
|
|
if (error.code === "domain_already_in_use") {
|
|
// Domain is already configured correctly, this is not an error when it happens during creation as it could be re-attempt to create an existing domain
|
|
return true;
|
|
}
|
|
|
|
const errorMessage = `Failed to create domain on Vercel: ${error.domain}`;
|
|
log.error(safeStringify({ errorMessage, vercelError: error }));
|
|
throw new HttpError({
|
|
message: errorMessage,
|
|
statusCode: 400,
|
|
});
|
|
}
|
|
|
|
function handleDomainDeletionError(error: { code?: string | null; domain?: string | null }) {
|
|
if (error.code === "not_found") {
|
|
// Domain is already deleted
|
|
return true;
|
|
}
|
|
|
|
// Domain is already owned by another team but you can request delegation to access it
|
|
if (error.code === "forbidden") {
|
|
const errorMessage = "Domain is owned by another team";
|
|
log.error(
|
|
safeStringify({
|
|
errorMessage,
|
|
vercelError: error,
|
|
})
|
|
);
|
|
throw new HttpError({
|
|
message: errorMessage,
|
|
statusCode: 400,
|
|
});
|
|
}
|
|
|
|
const errorMessage = `Failed to take action for domain: ${error.domain}`;
|
|
log.error(safeStringify({ errorMessage, vercelError: error }));
|
|
throw new HttpError({
|
|
message: errorMessage,
|
|
statusCode: 400,
|
|
});
|
|
}
|
|
|
|
function assertVercelEnvVars(env: typeof process.env): asserts env is {
|
|
PROJECT_ID_VERCEL: string;
|
|
TEAM_ID_VERCEL: string;
|
|
AUTH_BEARER_TOKEN_VERCEL: string;
|
|
} & typeof process.env {
|
|
if (!env.PROJECT_ID_VERCEL) {
|
|
throw new Error("Missing env var: PROJECT_ID_VERCEL");
|
|
}
|
|
|
|
// TEAM_ID_VERCEL is optional
|
|
|
|
if (!env.AUTH_BEARER_TOKEN_VERCEL) {
|
|
throw new Error("Missing env var: AUTH_BEARER_TOKEN_VERCEL");
|
|
}
|
|
}
|