Skip to content

Commit

Permalink
Merge branch 'main' into pr/12621
Browse files Browse the repository at this point in the history
  • Loading branch information
zomars committed Dec 12, 2023
2 parents c6dcf44 + add6ffd commit 7022a9e
Show file tree
Hide file tree
Showing 72 changed files with 1,574 additions and 381 deletions.
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,9 @@ AB_TEST_BUCKET_PROBABILITY=50
APP_ROUTER_EVENT_TYPES_ENABLED=1
APP_ROUTER_SETTINGS_ADMIN_ENABLED=1
APP_ROUTER_BOOKINGS_STATUS_ENABLED=1
APP_ROUTER_APPS_SLUG_ENABLED=1
APP_ROUTER_APPS_SLUG_SETUP_ENABLED=1
# whether we redirect to the future/apps/categories from /apps/categories or not
APP_ROUTER_APPS_CATEGORIES_ENABLED=1
# whether we redirect to the future/apps/categories/[category] from /apps/categories/[category] or not
APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED=1
1 change: 1 addition & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Fixes # (issue)
- [ ] Chore (refactoring code, technical debt, workflow improvements)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Tests (Unit/Integration/E2E or any other test)
- [ ] This change requires a documentation update

## How should this be tested?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
diff --git a/index.cjs b/index.cjs
index b645707a3549fc298508726e404243499bbed499..f34b0891e99b275a9218e253f303f43d31ef3f73 100644
index c83f700ae9998cd87b4c2d66ecbb2ad3d7b4603c..76a2200b57f0b9243e2c61464d578b67746ad5a4 100644
--- a/index.cjs
+++ b/index.cjs
@@ -13,8 +13,8 @@ function withMetadataArgument(func, _arguments) {
// https://github.com/babel/babel/issues/2212#issuecomment-131827986
// An alternative approach:
// https://www.npmjs.com/package/babel-plugin-add-module-exports
-exports = module.exports = min.parsePhoneNumberFromString
-exports['default'] = min.parsePhoneNumberFromString
+// exports = module.exports = min.parsePhoneNumberFromString
+// exports['default'] = min.parsePhoneNumberFromString

// `parsePhoneNumberFromString()` named export is now considered legacy:
// it has been promoted to a default export due to being too verbose.
// https://github.com/babel/babel/issues/2212#issuecomment-131827986
// An alternative approach:
// https://www.npmjs.com/package/babel-plugin-add-module-exports
-exports = module.exports = min.parsePhoneNumberFromString
-exports['default'] = min.parsePhoneNumberFromString
+// exports = module.exports = min.parsePhoneNumberFromString
+// exports['default'] = min.parsePhoneNumberFromString

// `parsePhoneNumberFromString()` named export is now considered legacy:
// it has been promoted to a default export due to being too verbose.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,6 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env

1. Copy and paste your `DATABASE_URL` from `.env` to `.env.appStore`.

1. Set a 24 character random string in your `.env` file for the `CALENDSO_ENCRYPTION_KEY` (You can use a command like `openssl rand -base64 24` to generate one).
1. Set up the database using the Prisma schema (found in `packages/prisma/schema.prisma`)

In a development environment, run:
Expand Down
4 changes: 4 additions & 0 deletions apps/web/abTest/middlewareFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ const ROUTES: [URLPattern, boolean][] = [
["/event-types", process.env.APP_ROUTER_EVENT_TYPES_ENABLED === "1"] as const,
["/settings/admin/:path*", process.env.APP_ROUTER_SETTINGS_ADMIN_ENABLED === "1"] as const,
["/bookings/:status", process.env.APP_ROUTER_BOOKINGS_STATUS_ENABLED === "1"] as const,
["/apps/:slug", process.env.APP_ROUTER_APPS_SLUG_ENABLED === "1"] as const,
["/apps/:slug/setup", process.env.APP_ROUTER_APPS_SLUG_SETUP_ENABLED === "1"] as const,
["/apps/categories", process.env.APP_ROUTER_APPS_CATEGORIES_ENABLED === "1"] as const,
["/apps/categories/:category", process.env.APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED === "1"] as const,
].map(([pathname, enabled]) => [
new URLPattern({
pathname,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { type ReactElement } from "react";

import PageWrapper from "@components/PageWrapperAppDir";

type EventTypesLayoutProps = {
children: ReactElement;
};

export default function Layout({ children }: EventTypesLayoutProps) {
return (
<PageWrapper getLayout={null} requiresLicense={false} nonce={undefined} themeBasis={null}>
{children}
</PageWrapper>
);
}
126 changes: 126 additions & 0 deletions apps/web/app/future/(individual-page-wrapper)/apps/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import AppPage from "@pages/apps/[slug]/index";
import { Prisma } from "@prisma/client";
import { _generateMetadata } from "app/_utils";
import fs from "fs";
import matter from "gray-matter";
import { notFound } from "next/navigation";
import path from "path";
import { z } from "zod";

import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
import { getAppAssetFullPath } from "@calcom/app-store/getAppAssetFullPath";
import { APP_NAME, IS_PRODUCTION } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";

const sourceSchema = z.object({
content: z.string(),
data: z.object({
description: z.string().optional(),
items: z
.array(
z.union([
z.string(),
z.object({
iframe: z.object({ src: z.string() }),
}),
])
)
.optional(),
}),
});

export const generateMetadata = async ({ params }: { params: Record<string, string | string[]> }) => {
const { data } = await getPageProps({ params });

return await _generateMetadata(
() => `${data.name} | ${APP_NAME}`,
() => data.description
);
};

export const generateStaticParams = async () => {
try {
const appStore = await prisma.app.findMany({ select: { slug: true } });
return appStore.map(({ slug }) => ({ 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 [];
};

const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
if (typeof params?.slug !== "string") {
notFound();
}

const appMeta = await getAppWithMetadata({
slug: params?.slug,
});

const appFromDb = await prisma.app.findUnique({
where: { slug: params.slug.toLowerCase() },
});

const isAppAvailableInFileSystem = appMeta;
const isAppDisabled = isAppAvailableInFileSystem && (!appFromDb || !appFromDb.enabled);

if (!IS_PRODUCTION && isAppDisabled) {
return {
isAppDisabled: true as const,
data: {
...appMeta,
},
};
}

if (!appFromDb || !appMeta || isAppDisabled) {
notFound();
}

const isTemplate = appMeta.isTemplate;
const appDirname = path.join(isTemplate ? "templates" : "", appFromDb.dirName);
const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/DESCRIPTION.md`);
const postFilePath = path.join(README_PATH);
let source = "";

try {
source = fs.readFileSync(postFilePath).toString();
source = source.replace(/{DESCRIPTION}/g, appMeta.description);
} catch (error) {
/* If the app doesn't have a README we fallback to the package description */
console.log(`No DESCRIPTION.md provided for: ${appDirname}`);
source = appMeta.description;
}

const result = matter(source);
const { content, data } = sourceSchema.parse({ content: result.content, data: result.data });
if (data.items) {
data.items = data.items.map((item) => {
if (typeof item === "string") {
return getAppAssetFullPath(item, {
dirName: appMeta.dirName,
isTemplate: appMeta.isTemplate,
});
}
return item;
});
}
return {
isAppDisabled: false as const,
source: { content, data },
data: appMeta,
};
};

export default async function Page({ params }: { params: Record<string, string | string[]> }) {
const pageProps = await getPageProps({ params });

return <AppPage {...pageProps} />;
}

export const dynamic = "force-static";
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import SetupPage from "@pages/apps/[slug]/setup";
import { _generateMetadata } from "app/_utils";
import type { GetServerSidePropsContext } from "next";
import { cookies, headers } from "next/headers";
import { notFound, redirect } from "next/navigation";

import { getServerSideProps } from "@calcom/app-store/_pages/setup/_getServerSideProps";
import { APP_NAME } from "@calcom/lib/constants";

export const generateMetadata = async ({ params }: { params: Record<string, string | string[]> }) => {
return await _generateMetadata(
() => `${params.slug} | ${APP_NAME}`,
() => ""
);
};

const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
const req = { headers: headers(), cookies: cookies() };

const result = await getServerSideProps({ params, req } as unknown as GetServerSidePropsContext);

if (!result || "notFound" in result) {
notFound();
}

if ("redirect" in result) {
redirect(result.redirect.destination);
}

return result.props;
};

export default async function Page({ params }: { params: Record<string, string | string[]> }) {
const pageProps = await getPageProps({ params });
return <SetupPage {...pageProps} />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import CategoryPage from "@pages/apps/categories/[category]";
import { Prisma } from "@prisma/client";
import { _generateMetadata } from "app/_utils";
import { notFound } from "next/navigation";
import z from "zod";

import { getAppRegistry } from "@calcom/app-store/_appRegistry";
import { APP_NAME } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";
import { AppCategories } from "@calcom/prisma/enums";

import PageWrapper from "@components/PageWrapperAppDir";

export const generateMetadata = async () => {
return await _generateMetadata(
() => `${APP_NAME} | ${APP_NAME}`,
() => ""
);
};

export const generateStaticParams = 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 [];
} else {
throw e;
}
}

return paths.map((category) => ({ category }));
};

const querySchema = z.object({
category: z.nativeEnum(AppCategories),
});

const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
const p = querySchema.safeParse(params);

if (!p.success) {
return notFound();
}

const appQuery = await prisma.app.findMany({
where: {
categories: {
has: p.data.category,
},
},
select: {
slug: true,
},
});

const dbAppsSlugs = appQuery.map((category) => category.slug);

const appStore = await getAppRegistry();

const apps = appStore.filter((app) => dbAppsSlugs.includes(app.slug));
return {
apps,
};
};

export default async function Page({ params }: { params: Record<string, string | string[]> }) {
const { apps } = await getPageProps({ params });
return (
<PageWrapper getLayout={null} requiresLicense={false} nonce={undefined} themeBasis={null}>
<CategoryPage apps={apps} />
</PageWrapper>
);
}

export const dynamic = "force-static";
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import LegacyPage from "@pages/apps/categories/index";
import { ssrInit } from "app/_trpc/ssrInit";
import { _generateMetadata } from "app/_utils";
import { cookies, headers } from "next/headers";

import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { APP_NAME } from "@calcom/lib/constants";

import PageWrapper from "@components/PageWrapperAppDir";

export const generateMetadata = async () => {
return await _generateMetadata(
() => `Categories | ${APP_NAME}`,
() => ""
);
};

async function getPageProps() {
const ssr = await ssrInit();
const req = { headers: headers(), cookies: cookies() };

// @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest | IncomingMessage
const session = await getServerSession({ req });

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;
}
return c;
}, {} as Record<string, number>);

return {
categories: Object.entries(categories).map(([name, count]) => ({ name, count })),
dehydratedState: await ssr.dehydrate(),
};
}

export default async function Page() {
const props = await getPageProps();
const h = headers();
const nonce = h.get("x-nonce") ?? undefined;

return (
<PageWrapper getLayout={null} requiresLicense={false} nonce={nonce} themeBasis={null} {...props}>
<LegacyPage {...props} />
</PageWrapper>
);
}
Loading

0 comments on commit 7022a9e

Please sign in to comment.