Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions packages/commons/docs-loader/src/editable-docs-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,6 @@ export const createEditableDocsLoader = async ({
const docsLoader = await createCachedDocsLoader(host, encodeDocsLoaderDomain(domain, branchName), fernToken, {
returnRawMarkdown: true,
cacheConfig: {
// For editable docs, we want shorter TTL so that cache stays fresh
kvTtl: 5 * 60, // 5 minutes
cacheKeySuffix: "editable",
forceRevalidate
},
Expand Down
1 change: 0 additions & 1 deletion packages/fern-dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
"@fern-api/fdr-sdk": "workspace:*",
"@fern-api/ui-core-utils": "workspace:*",
"@fern-api/venus-api-sdk": "0.20.0",
"@fern-api/visual-editor-server": "workspace:*",
"@fern-docs/components": "workspace:*",
"@fern-docs/edge-config": "workspace:*",
"@fern-docs/mdx": "workspace:*",
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,34 +1,25 @@
import "server-only";

import { notFound } from "next/navigation";

import getGithubSourceMetadataHandler from "@/app/api/get-github-source-metadata/handler";
import { Suspense } from "react";
import { getGitHubAuthState } from "@/app/actions/getGithubMetadata";
import type { Auth0OrgName } from "@/app/services/auth0/types";
import getDocsSitesForOrg from "@/app/services/dal/fdr/getDocsSitesForOrg";
import getDocsGithubUrl from "@/app/services/dal/github/getDocsGithubUrl";
import { validateGithubRepoAccess } from "@/app/services/dal/github/validators";
import { getDocsGithubUrl } from "@/app/services/dal/github/getDocsGithubUrl";
import { getAuthenticatedSessionOrRedirect } from "@/app/services/dal/organization";
import { DocsSiteOverviewCard } from "@/components/docs-page/DocsSiteOverviewCard";
import { FernCliVersionDisplay } from "@/components/docs-page/FernCliVersionDisplay";
import { type GithubAuthState, GithubSource } from "@/components/docs-page/GithubSource";
import { VisualEditorLoadingCard } from "@/components/docs-page/visual-editor-section/VisualEditorLoadingCard";
import { VisualEditorSection } from "@/components/docs-page/visual-editor-section/VisualEditorSection";
import { getDocsSiteUrl } from "@/utils/getDocsSiteUrl";
import { parseDocsUrlParam } from "@/utils/parseDocsUrlParam";
import type { EncodedDocsUrl } from "@/utils/types";

export default async function Page(props: {
params: Promise<{ orgName: Auth0OrgName; docsUrl: EncodedDocsUrl }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}) {
export default async function Page(props: { params: Promise<{ orgName: Auth0OrgName; docsUrl: EncodedDocsUrl }> }) {
const { orgName, docsUrl: encodedDocsUrl } = await props.params;
const searchParams = await props.searchParams;

const session = await getAuthenticatedSessionOrRedirect(orgName);
const docsUrl = parseDocsUrlParam({ docsUrl: encodedDocsUrl });

// Only skip cache if explicitly requested via query param (e.g., ?refresh=true)
const skipCache = searchParams.refresh === "true";

// Validate that the docsUrl belongs to this organization so that we avoid errors in the page
const response = await getDocsSitesForOrg({
orgName,
Expand All @@ -43,106 +34,18 @@ export default async function Page(props: {
notFound();
}

let githubUrl = undefined;
let githubAuthState: GithubAuthState = {
validationResult: {
ok: false,
error: {
type: "UNEXPECTED_ERROR",
message: ""
}
},
sourceRepo: undefined,
isLoading: false
};

try {
const urlResult = await getDocsGithubUrl({
url: encodedDocsUrl,
token: session.accessToken
});

if (!urlResult.success) {
if (urlResult.error.type === "DOMAIN_NOT_REGISTERED") {
githubAuthState.validationResult = {
ok: false,
error: {
type: "UNEXPECTED_ERROR",
message: "Domain not registered."
}
};
} else {
githubAuthState.validationResult = {
ok: false,
error: urlResult.error
};
}
} else {
githubUrl = urlResult.githubUrl;

try {
// Parallelize validation and metadata fetching for better performance
const [validation, sourceRepo] = await Promise.all([
validateGithubRepoAccess(
orgName,
docsUrl,
{
type: "url",
githubUrl
},
true // Skip cache for now, since this cache was causing issues with validating repos
),
// Optimistically fetch metadata in parallel (will be used if validation succeeds)
getGithubSourceMetadataHandler({
githubUrl,
userId: session.user.sub
}).catch((error) => {
console.error("Failed to fetch source repo metadata:", error);
return undefined;
})
]);

githubAuthState = {
validationResult: validation,
// Only include sourceRepo if validation succeeded
sourceRepo: validation.ok ? sourceRepo : undefined,
isLoading: false
};
} catch (error) {
console.error("Failed to validate GitHub access:", error);
// Keep default false state
}
}
} catch (error) {
console.error(error);
}
// Start expensive operations in parallel without awaiting
Promise.all([
getDocsGithubUrl(docsUrl, session.accessToken),
getGitHubAuthState(docsUrl, session.accessToken, orgName, session)
]);

return (
<div className="flex w-full flex-col gap-4">
<DocsSiteOverviewCard
docsSite={currentDocsSite}
githubProtectedArea={
<div className="flex flex-wrap gap-x-10 gap-y-4">
<div className="flex w-fit flex-col gap-2">
<p>Source</p>
<GithubSource docsUrl={docsUrl} githubUrl={githubUrl} />
</div>
<FernCliVersionDisplay
orgName={orgName}
docsUrl={docsUrl}
githubUrl={githubUrl}
baseBranch={githubAuthState.sourceRepo?.baseBranch}
/>
</div>
}
/>
<VisualEditorSection
docsUrl={docsUrl}
session={session}
orgName={orgName}
githubAuthState={githubAuthState}
githubUrl={githubUrl}
/>
<DocsSiteOverviewCard docsUrl={docsUrl} docsSite={currentDocsSite} orgName={orgName} />
<Suspense fallback={<VisualEditorLoadingCard />}>
<VisualEditorSection docsUrl={docsUrl} session={session} orgName={orgName} />
</Suspense>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type React from "react";

import { ClientMDXProvider } from "@/app/[orgName]/context/ClientMDXProvider";
import { OrgNameProvider } from "@/app/[orgName]/context/OrgNameContext";
import getGithubSourceMetadata from "@/app/api/get-github-source-metadata/handler";
import { getGithubSourceMetadata } from "@/app/actions/getGithubSourceMetadata";
import type { Auth0OrgName } from "@/app/services/auth0/types";
import { assertAuthAndFetchGithubUrl } from "@/app/services/dal/github/assertAuthAndFetchGithubUrl";
import { getAuthenticatedSessionOrRedirect } from "@/app/services/dal/organization";
Expand Down
26 changes: 10 additions & 16 deletions packages/fern-dashboard/src/app/actions/getDocsGithubMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

import { fernToken_admin } from "@fern-api/docs-server";

import type { GithubAuthState } from "@/components/docs-page/GithubSource";

import getGithubSourceMetadataHandler from "../api/get-github-source-metadata/handler";
import type { GithubAuthState } from "@/components/docs-page/GithubSourceClient";
import type { DocsUrl } from "@/utils/types";
import { getDocsUrlMetadata } from "../api/utils/getDocsUrlMetadata";
import { type Auth0SessionData, getCurrentSessionOrThrow } from "../services/auth0/getCurrentSession";
import type { Auth0OrgName } from "../services/auth0/types";
import getDocsGithubUrl from "../services/dal/github/getDocsGithubUrl";
import { getDocsGithubUrl } from "../services/dal/github/getDocsGithubUrl";
import { validateGithubRepoAccess } from "../services/dal/github/validators";
import { getGithubSourceMetadata } from "./getGithubSourceMetadata";

async function getMetadata(encodedDocsUrl: string, session: Auth0SessionData, orgName: Auth0OrgName, docsUrl: string) {
async function getMetadata(encodedDocsUrl: DocsUrl, session: Auth0SessionData, orgName: Auth0OrgName, docsUrl: string) {
let githubAuthState: GithubAuthState = {
validationResult: {
ok: false,
Expand All @@ -25,10 +25,7 @@ async function getMetadata(encodedDocsUrl: string, session: Auth0SessionData, or
};
let githubUrl: string | undefined;
try {
const urlResult = await getDocsGithubUrl({
url: encodedDocsUrl,
token: session.accessToken
});
const urlResult = await getDocsGithubUrl(encodedDocsUrl, session.accessToken);

if (!urlResult.success) {
if (urlResult.error.type === "DOMAIN_NOT_REGISTERED") {
Expand Down Expand Up @@ -63,7 +60,7 @@ async function getMetadata(encodedDocsUrl: string, session: Auth0SessionData, or
true // Skip cache for now, since this cache was causing issues with validating repos
),
// Optimistically fetch metadata in parallel (will be used if validation succeeds)
getGithubSourceMetadataHandler({
getGithubSourceMetadata({
githubUrl,
userId: session.user.sub
}).catch((error: unknown) => {
Expand All @@ -88,7 +85,7 @@ async function getMetadata(encodedDocsUrl: string, session: Auth0SessionData, or
}
}

export async function getDocsGithubMetadata(docsUrl: string): Promise<{
export async function getDocsGithubMetadata(docsUrl: DocsUrl): Promise<{
success: boolean;
orgName?: Auth0OrgName;
githubUrl?: string;
Expand All @@ -102,10 +99,7 @@ export async function getDocsGithubMetadata(docsUrl: string): Promise<{
url: decodedUrl,
token: fernToken_admin() ?? session.accessToken
});
const githubMetadata = await getDocsGithubUrl({
url: decodedUrl,
token: fernToken_admin() ?? session.accessToken
});
const githubMetadata = await getDocsGithubUrl(docsUrl, fernToken_admin() ?? session.accessToken);
if (!githubMetadata.success) {
return { success: false, error: "Failed to fetch github metadata" };
}
Expand All @@ -116,7 +110,7 @@ export async function getDocsGithubMetadata(docsUrl: string): Promise<{

const orgName = docsMetadata.body.org as unknown as Auth0OrgName;

const metadata = await getMetadata(decodedUrl, session, orgName, decodedUrl);
const metadata = await getMetadata(docsUrl, session, orgName, decodedUrl);
if (!metadata?.success) {
return { success: false, error: "Failed to fetch metadata" };
}
Expand Down
75 changes: 75 additions & 0 deletions packages/fern-dashboard/src/app/actions/getGithubMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import "server-only";
import { cache } from "react";
import type { GithubAuthState } from "@/components/docs-page/GithubSourceClient";
import type { DocsUrl } from "@/utils/types";
import type { Auth0SessionData } from "../services/auth0/getCurrentSession";
import type { Auth0OrgName } from "../services/auth0/types";
import { type GetDocsGithubUrlResult, getDocsGithubUrl } from "../services/dal/github/getDocsGithubUrl";
import { validateGithubRepoAccess } from "../services/dal/github/validators";
import { getGithubSourceMetadata } from "./getGithubSourceMetadata";

type GetDocsGithubUrlError = Extract<GetDocsGithubUrlResult, { success: false }>["error"];

export type GetGitHubAuthStateResult = GithubAuthState | { success: false; error: GetDocsGithubUrlError };

export const getGitHubAuthState = cache(
async (
docsUrl: DocsUrl,
token: string,
orgName: Auth0OrgName,
session: Auth0SessionData
): Promise<GetGitHubAuthStateResult> => {
const urlResult = await getDocsGithubUrl(docsUrl, token);
if (!urlResult.success) {
return { success: false, error: urlResult.error };
}

const githubUrl = urlResult.githubUrl;
let githubAuthState: GithubAuthState = {
validationResult: {
ok: false,
error: {
type: "UNEXPECTED_ERROR",
message: ""
}
},
sourceRepo: undefined,
isLoading: false
};

try {
// Parallelize validation and metadata fetching for better performance
const [validation, sourceRepo] = await Promise.all([
validateGithubRepoAccess(
orgName,
docsUrl,
{
type: "url",
githubUrl
},
true // Skip cache for now, since this cache was causing issues with validating repos
),
// Optimistically fetch metadata in parallel (will be used if validation succeeds)
getGithubSourceMetadata({
githubUrl,
userId: session.user.sub
}).catch((error) => {
console.error("Failed to fetch source repo metadata:", error);
return undefined;
})
]);

githubAuthState = {
validationResult: validation,
// Only include sourceRepo if validation succeeded
sourceRepo: validation.ok ? sourceRepo : undefined,
isLoading: false
};
} catch (error) {
console.error("Failed to validate GitHub access:", error);
// Keep default false state
}

return githubAuthState;
}
);
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use server";

import { unstable_cache } from "next/cache";

import { getFernBotInstallationId, getFernBotOctokitForRepo } from "@/app/services/auth0/fernBotOctokit";
Expand All @@ -13,7 +15,7 @@ const EMPTY_RESPONSE: GithubSourceRepo = {
fernBotHasInstallationId: undefined
};

export default async function getGithubSourceMetadataHandler({
export async function getGithubSourceMetadata({
githubUrl,
userId,
skipCache = false
Expand All @@ -22,7 +24,7 @@ export default async function getGithubSourceMetadataHandler({
userId: string;
skipCache?: boolean;
}): Promise<GithubSourceRepo> {
async function getGithubSourceMetadata() {
async function fetchGithubSourceMetadata() {
if (githubUrl == null) {
throw new Error("NoGithubUrl");
}
Expand Down Expand Up @@ -67,14 +69,14 @@ export default async function getGithubSourceMetadataHandler({
try {
// Only cache successful responses; do not cache failures
const result = skipCache
? getGithubSourceMetadata()
: unstable_cache(getGithubSourceMetadata, [`github-source-${githubUrl}-${userId}`], {
? fetchGithubSourceMetadata()
: unstable_cache(fetchGithubSourceMetadata, [`github-source-${githubUrl}-${userId}`], {
revalidate: 300, // 5 minutes
tags: [`github-source-${githubUrl}`]
})();
return await result;
} catch (error) {
console.error("[getDocsGithubSourceHandler]", error);
console.error("[getGithubSourceMetadata]", error);
// On any error, return EMPTY_RESPONSE (but don't cache the error)
return EMPTY_RESPONSE;
}
Expand Down
Loading