145 lines
4.1 KiB
TypeScript
145 lines
4.1 KiB
TypeScript
import { gql, GraphQLClient } from "graphql-request";
|
|
import type { PageData } from "@/types/page";
|
|
import { getBaseUrl } from "./gr-client";
|
|
import * as Sentry from "@sentry/nextjs";
|
|
|
|
|
|
const PageQuery = gql/* GraphQL */ `
|
|
query PageQuery($slug: String!) {
|
|
pageBySlug(slug: $slug) {
|
|
id
|
|
title
|
|
description
|
|
}
|
|
}
|
|
`;
|
|
|
|
const BlockQuery = gql/* GraphQL */ `
|
|
query BlockQuery($pageId: String!) {
|
|
pageBlocks(pageId: $pageId) {
|
|
__typename
|
|
... on TextBlockType { id markdown }
|
|
... on ChartBlockType { id title series { x y } }
|
|
... on SettingsBlockType { id category editable }
|
|
... on HeroBlockType { id title subtitle backgroundImage ctaText ctaLink }
|
|
}
|
|
}
|
|
`;
|
|
|
|
|
|
export async function fetchPage(slug: string, jwt?: string): Promise<PageData | null> {
|
|
return Sentry.startSpan(
|
|
{
|
|
op: "http.client",
|
|
name: `GraphQL fetchPage: ${slug}`,
|
|
},
|
|
async (span) => {
|
|
span.setAttribute("page.slug", slug);
|
|
span.setAttribute("auth.hasJwt", !!jwt);
|
|
|
|
const client = new GraphQLClient(getBaseUrl());
|
|
|
|
if (jwt) {
|
|
client.setHeader('Authorization', `Bearer ${jwt}`);
|
|
}
|
|
|
|
try {
|
|
// 获取页面基本信息
|
|
const pageResponse: any = await Sentry.startSpan(
|
|
{
|
|
op: "http.client",
|
|
name: "GraphQL PageQuery",
|
|
},
|
|
() => client.request(PageQuery, { slug })
|
|
);
|
|
|
|
if (!pageResponse?.pageBySlug) {
|
|
throw new Error('Page not found');
|
|
}
|
|
|
|
// 获取页面块数据
|
|
const blocksResponse: any = await Sentry.startSpan(
|
|
{
|
|
op: "http.client",
|
|
name: "GraphQL BlockQuery",
|
|
},
|
|
() => client.request(BlockQuery, {
|
|
pageId: pageResponse.pageBySlug.id
|
|
})
|
|
);
|
|
|
|
if (!blocksResponse?.pageBlocks) {
|
|
throw new Error('Failed to fetch page blocks');
|
|
}
|
|
|
|
span.setAttribute("page.blocksCount", blocksResponse.pageBlocks.length);
|
|
|
|
// 合并数据
|
|
return {
|
|
...pageResponse.pageBySlug,
|
|
blocks: blocksResponse.pageBlocks,
|
|
};
|
|
|
|
} catch (error) {
|
|
Sentry.captureException(error, {
|
|
tags: { operation: 'fetchPage' },
|
|
extra: { slug, hasJwt: !!jwt }
|
|
});
|
|
console.error('Failed to fetch page:', error);
|
|
return null;
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
export async function getSiteConfigs() {
|
|
return Sentry.startSpan(
|
|
{
|
|
op: "http.client",
|
|
name: "Fetch Site Configs",
|
|
},
|
|
async (span) => {
|
|
try {
|
|
const baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000';
|
|
span.setAttribute("api.baseUrl", baseUrl);
|
|
|
|
const siteConfigs = await fetch(`${baseUrl}/api/site`, {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
// Add timeout to prevent hanging during build
|
|
signal: AbortSignal.timeout(5000)
|
|
});
|
|
|
|
span.setAttribute("http.status_code", siteConfigs.status);
|
|
|
|
if (!siteConfigs.ok) {
|
|
const error = new Error(`Failed to fetch site configs: ${siteConfigs.status} ${siteConfigs.statusText}`);
|
|
Sentry.captureException(error, {
|
|
tags: { operation: 'getSiteConfigs' },
|
|
extra: { status: siteConfigs.status, statusText: siteConfigs.statusText }
|
|
});
|
|
console.warn(`Failed to fetch site configs: ${siteConfigs.status} ${siteConfigs.statusText}`);
|
|
return getDefaultSiteConfigs();
|
|
}
|
|
|
|
const data = await siteConfigs.json();
|
|
span.setAttribute("configs.count", data.length);
|
|
return data;
|
|
} catch (error) {
|
|
Sentry.captureException(error, {
|
|
tags: { operation: 'getSiteConfigs' }
|
|
});
|
|
console.warn('Failed to fetch site configs, using defaults:', error);
|
|
return getDefaultSiteConfigs();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
function getDefaultSiteConfigs() {
|
|
return [
|
|
{ key: 'site.name', value: 'MosaicMap' },
|
|
{ key: 'site.description', value: 'Interactive map visualization with custom WebGL timeline' }
|
|
];
|
|
} |