feat: Make database optional when building (#8561)

* Make database optional when building

Solves #3026 and #6780

We have a few app-store related pages that require a database connection at build time.

This change adds try-catch blocks in getStaticPaths or getStaticProps for those pages, and when no database is available we either:

- getStaticPaths: Respond with no paths, but fall back to rendering the pages on-demand
- getStaticProps: Respond with no data, but enable revalidation until a future request does respond with data

This makes Cal.com more compatible with typical Docker-based workflows, that do not expect external services to be available at build time. This includes Docker-based hosting providers, such as Cloudron (#3026).

* Change apps/categories/index to be server-rendered

* Update yarn.lock

* Update yarn.lock

* Update [category].tsx

---------

Co-authored-by: zomars <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
This commit is contained in:
Rob Jackson
2023-05-31 03:04:06 +00:00
committed by GitHub
co-authored by zomars Peer Richelsen alannnc Keith Williams
parent 991167fb8b
commit 7a9a2fc76f
5 changed files with 73 additions and 7 deletions
+6
View File
@@ -66,6 +66,12 @@ jobs:
uses: ./.github/workflows/production-build.yml
secrets: inherit
build-without-database:
name: Production build (without database)
needs: env
uses: ./.github/workflows/production-build-without-database.yml
secrets: inherit
e2e:
name: E2E tests
needs: [changes, lint, build]
@@ -0,0 +1,17 @@
name: Production Build (without database)
on:
workflow_call:
jobs:
build:
name: Build
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
- uses: ./.github/actions/env-read-file
- uses: ./.github/actions/cache-db
- uses: ./.github/actions/cache-build
+13 -2
View File
@@ -1,3 +1,4 @@
import { Prisma } from "@prisma/client";
import fs from "fs";
import matter from "gray-matter";
import MarkdownIt from "markdown-it";
@@ -88,8 +89,18 @@ function SingleAppPage(props: inferSSRProps<typeof getStaticProps>) {
}
export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => {
const appStore = await prisma.app.findMany({ select: { slug: true } });
const paths = appStore.map(({ slug }) => ({ params: { slug } }));
let paths: { params: { slug: string } }[] = [];
try {
const appStore = await prisma.app.findMany({ select: { slug: true } });
paths = appStore.map(({ slug }) => ({ params: { slug } }));
} catch (e: unknown) {
if (e instanceof Prisma.PrismaClientInitializationError) {
// Database is not available at build time, but that's ok we fall back to resolving paths on demand
} else {
throw e;
}
}
return {
paths,
@@ -1,3 +1,4 @@
import { Prisma } from "@prisma/client";
import type { GetStaticPropsContext, InferGetStaticPropsType } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
@@ -54,6 +55,20 @@ Apps.PageWrapper = PageWrapper;
export const getStaticPaths = async () => {
const paths = Object.keys(AppCategories);
try {
await prisma.$queryRaw`SELECT 1`;
} catch (e: unknown) {
if (e instanceof Prisma.PrismaClientInitializationError) {
// Database is not available at build time. Make sure we fall back to building these pages on demand
return {
paths: [],
fallback: "blocking",
};
} else {
throw e;
}
}
return {
paths: paths.map((category) => ({ params: { category } })),
fallback: false,
+22 -5
View File
@@ -1,15 +1,19 @@
import type { InferGetStaticPropsType } from "next";
import type { GetServerSidePropsContext } from "next";
import Link from "next/link";
import { getAppRegistry } from "@calcom/app-store/_appRegistry";
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import Shell from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import { SkeletonText } from "@calcom/ui";
import { ArrowLeft, ArrowRight } from "@calcom/ui/components/icon";
import PageWrapper from "@components/PageWrapper";
export default function Apps({ categories }: InferGetStaticPropsType<typeof getStaticProps>) {
import { ssrInit } from "@server/lib/ssr";
export default function Apps({ categories }: inferSSRProps<typeof getServerSideProps>) {
const { t, isLocaleReady } = useLocale();
return (
@@ -47,8 +51,20 @@ export default function Apps({ categories }: InferGetStaticPropsType<typeof getS
Apps.PageWrapper = PageWrapper;
export const getStaticProps = async () => {
const appStore = await getAppRegistry();
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const { req, res } = context;
const ssr = await ssrInit(context);
const session = await getServerSession({ req, res });
let appStore;
if (session?.user?.id) {
appStore = await getAppRegistryWithCredentials(session.user.id);
} else {
appStore = await getAppRegistry();
}
const categories = appStore.reduce((c, app) => {
for (const category of app.categories) {
c[category] = c[category] ? c[category] + 1 : 1;
@@ -59,6 +75,7 @@ export const getStaticProps = async () => {
return {
props: {
categories: Object.entries(categories).map(([name, count]) => ({ name, count })),
trpcState: ssr.dehydrate(),
},
};
};