move to update file routes

This commit is contained in:
Nate Kelley 2025-10-06 10:13:17 -06:00
parent 5ae926941e
commit 075cdb8f5a
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
21 changed files with 820 additions and 2053 deletions

View File

@ -107,15 +107,15 @@
"@tanstack/react-query": "^5.90.2",
"@tanstack/react-query-devtools": "^5.90.2",
"@tanstack/react-query-persist-client": "^5.90.2",
"@tanstack/react-router": "^1.131.50",
"@tanstack/react-router-devtools": "^1.131.50",
"@tanstack/react-router-ssr-query": "^1.131.50",
"@tanstack/react-router": "^1.132.41",
"@tanstack/react-router-devtools": "^1.132.41",
"@tanstack/react-router-ssr-query": "^1.132.41",
"@tanstack/react-router-with-query": "^1.130.17",
"@tanstack/react-start": "^1.131.50",
"@tanstack/react-start": "^1.132.43",
"@tanstack/react-store": "^0.7.7",
"@tanstack/react-table": "^8.21.3",
"@tanstack/react-virtual": "^3.13.12",
"@tanstack/router-plugin": "^1.131.50",
"@tanstack/router-plugin": "^1.132.41",
"@tanstack/store": "^0.7.7",
"@testing-library/jest-dom": "^6.9.1",
"@tiptap/core": "^3.6.5",

View File

@ -3,7 +3,7 @@ import { getCookie } from '@tanstack/react-start/server';
import { z } from 'zod';
export const getServerCookie = createServerFn({ method: 'GET' })
.validator(
.inputValidator(
z.object({
cookieName: z.string(),
})

View File

@ -1,8 +1,7 @@
import type { AuthError, Session, SupabaseClient } from '@supabase/supabase-js';
import type { AuthError, SupabaseClient } from '@supabase/supabase-js';
import { createServerFn } from '@tanstack/react-start';
import type { SimplifiedSupabaseSession } from '@/integrations/supabase/getSupabaseUserClient';
import { getSupabaseServerClient } from '@/integrations/supabase/server';
import { isServer } from '@/lib/window';
import { isTokenExpired } from '../auth_helpers/expiration-helpers';
export const extractSimplifiedSupabaseSession = async (
@ -30,7 +29,14 @@ export const getSupabaseSessionServerFn = createServerFn({ method: 'GET' }).hand
const { data, error } = await extractSimplifiedSupabaseSession(supabase);
return {
data,
error,
error: error
? {
code: error.code,
status: error.status,
name: error.name,
message: error.message,
}
: null,
};
} catch (error) {
// Final catch-all for any unhandled errors
@ -63,6 +69,13 @@ export const getSupabaseUserServerFn = createServerFn({ method: 'GET' }).handler
data: {
user: pickedUser,
},
error: userError,
error: userError
? {
code: userError.code,
status: userError.status,
name: userError.name,
message: userError.message,
}
: null,
};
});

View File

@ -60,15 +60,8 @@ const BusterListRowComponentInner = React.forwardRef(
...style,
};
const linkProps = row.link
? {
preloadDelay: row.preloadDelay,
preload: row.preload,
}
: undefined;
return (
<LinkWrapper link={link} {...linkProps}>
<LinkWrapper link={link} {...row}>
<div
onClick={onContainerClick}
style={rowStyles}
@ -169,7 +162,7 @@ const LinkWrapper: React.FC<
<Link
{...link}
preload={preload ?? false}
preloadDelay={preloadDelay}
preloadDelay={preloadDelay ?? 50}
activeOptions={activeOptions}
>
{children}

View File

@ -1,4 +1,3 @@
import type { AssetType } from '@buster/server-shared/assets';
import {
type StaticDataRouteOption,
useMatches,
@ -17,7 +16,6 @@ export const useSelectedAssetType = (): NonNullable<StaticDataRouteOption['asset
if (typeof lastMatch === 'number') {
return 'chat';
}
// @ts-expect-error - lastMatch is not undefined
const data = lastMatch?.staticData?.assetType as StaticDataRouteOption['assetType'];
const { messageId } = useParams({
strict: false,

View File

@ -1,8 +0,0 @@
// Global middleware registration for TanStack Start
import { registerGlobalMiddleware } from '@tanstack/react-start';
import { securityMiddleware } from './middleware/global-security';
// Register global middleware that runs for every server function
registerGlobalMiddleware({
middleware: [securityMiddleware],
});

View File

@ -6,7 +6,7 @@ import { getSupabaseUser } from './getSupabaseUserClient';
import { getSupabaseServerClient } from './server';
export const resetPasswordEmailSend = createServerFn({ method: 'POST' })
.validator(z.object({ email: z.string().email() }))
.inputValidator(z.object({ email: z.string().email() }))
.handler(async ({ data: { email } }) => {
const supabase = await getSupabaseServerClient();
@ -25,7 +25,7 @@ export const resetPasswordEmailSend = createServerFn({ method: 'POST' })
});
export const resetPassword = createServerFn({ method: 'POST' })
.validator(z.object({ password: z.string() }))
.inputValidator(z.object({ password: z.string() }))
.handler(async ({ data: { password } }) => {
const supabase = await getSupabaseServerClient();

View File

@ -1,5 +1,11 @@
import { type CookieOptions, createServerClient } from '@supabase/ssr';
import { parseCookies, setCookie } from '@tanstack/react-start/server';
import {
getCookie,
getCookies,
getRequest,
getResponseHeaders,
setCookie,
} from '@tanstack/react-start/server';
import { env } from '@/env';
export const COOKIE_OPTIONS: CookieOptions = {
@ -30,7 +36,8 @@ const safeSetCookie = (name: string, value: string, options: CookieOptions) => {
const safeParseCookies = () => {
try {
return parseCookies();
const cookies = getCookies();
return cookies;
} catch (error) {
if (error instanceof Error && error.message.includes('ERR_HTTP_HEADERS_SENT')) {
// Return empty object if we can't parse cookies

View File

@ -1,7 +1,7 @@
import { createServerFn } from '@tanstack/react-start';
import { z } from 'zod';
import { env } from '@/env';
import { ServerRoute as AuthCallbackRoute } from '../../routes/auth.callback';
import { Route as AuthCallbackRoute } from '../../routes/auth.callback';
import { getSupabaseServerClient } from './server';
const isValidRedirectUrl = (url: string): boolean => {
@ -44,7 +44,7 @@ const handleOAuthSignIn = async (
};
export const signInWithEmailAndPassword = createServerFn({ method: 'POST' })
.validator(
.inputValidator(
z.object({
email: z.string(),
password: z.string(),
@ -117,25 +117,25 @@ export const signInWithAnonymousUser = createServerFn({ method: 'POST' }).handle
const oAuthRedirectValidator = z.object({ redirectTo: z.string().nullable().optional() });
export const signInWithGoogle = createServerFn({ method: 'POST' })
.validator(oAuthRedirectValidator)
.inputValidator(oAuthRedirectValidator)
.handler(async ({ data: { redirectTo } }) => {
return handleOAuthSignIn('google', redirectTo);
});
export const signInWithGithub = createServerFn({ method: 'POST' })
.validator(oAuthRedirectValidator)
.inputValidator(oAuthRedirectValidator)
.handler(async ({ data: { redirectTo } }) => {
return handleOAuthSignIn('github', redirectTo);
});
export const signInWithAzure = createServerFn({ method: 'POST' })
.validator(oAuthRedirectValidator)
.inputValidator(oAuthRedirectValidator)
.handler(async ({ data: { redirectTo } }) => {
return handleOAuthSignIn('azure', redirectTo, { scopes: 'email' });
});
export const signUpWithEmailAndPassword = createServerFn({ method: 'POST' })
.validator(
.inputValidator(
z.object({
email: z.string(),
password: z.string(),

View File

@ -1,16 +1,20 @@
import { createMiddleware } from '@tanstack/react-start';
import { getWebRequest, setHeaders } from '@tanstack/react-start/server';
import { getRequest, getResponseHeaders } from '@tanstack/react-start/server';
import { createSecurityHeaders } from './csp-helper';
export const securityMiddleware = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
try {
// Check if this is an embed route by examining the request URL
const request = getWebRequest();
const request = getRequest();
const url = new URL(request.url);
const isEmbed = url.pathname.startsWith('/embed');
setHeaders(createSecurityHeaders(isEmbed));
const headers = getResponseHeaders();
const securityHeaders = createSecurityHeaders(isEmbed);
Object.entries(securityHeaders).forEach(([key, value]) => {
headers.set(key, value);
});
// Set appropriate cache headers for static assets
const pathname = url.pathname;
@ -27,14 +31,10 @@ export const securityMiddleware = createMiddleware({ type: 'function' }).server(
pathname.endsWith('.eot')
) {
// Static assets with hashed filenames can be cached for 1 year
setHeaders({
'Cache-Control': 'public, max-age=31536000, immutable', // 1 year
});
headers.set('Cache-Control', 'public, max-age=31536000, immutable');
} else if (pathname === '/manifest.json') {
// Manifest can be cached for a shorter time
setHeaders({
'Cache-Control': 'public, max-age=86400', // 1 day
});
headers.set('Cache-Control', 'public, max-age=86400');
}
} catch (error) {
// Ignore headers already sent errors to prevent crashes

View File

@ -9,7 +9,6 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { createFileRoute } from '@tanstack/react-router'
import { createServerRootRoute } from '@tanstack/react-start/server'
import { Route as rootRouteImport } from './routes/__root'
import { Route as HealthcheckRouteImport } from './routes/healthcheck'
@ -23,8 +22,11 @@ import { Route as InfoGettingStartedRouteImport } from './routes/info/getting-st
import { Route as AuthResetPasswordRouteImport } from './routes/auth.reset-password'
import { Route as AuthLogoutRouteImport } from './routes/auth.logout'
import { Route as AuthLoginRouteImport } from './routes/auth.login'
import { Route as AuthConfirmRouteImport } from './routes/auth.confirm'
import { Route as AuthCallbackRouteImport } from './routes/auth.callback'
import { Route as AppSettingsRouteImport } from './routes/app/_settings'
import { Route as AppAppRouteImport } from './routes/app/_app'
import { Route as ScreenshotsReportsReportIdRouteImport } from './routes/screenshots/reports.$reportId'
import { Route as EmbedReportReportIdRouteImport } from './routes/embed/report.$reportId'
import { Route as EmbedMetricMetricIdRouteImport } from './routes/embed/metric.$metricId'
import { Route as EmbedDashboardDashboardIdRouteImport } from './routes/embed/dashboard.$dashboardId'
@ -35,6 +37,9 @@ import { Route as AppAppTestPaginationRouteImport } from './routes/app/_app/test
import { Route as AppAppNewUserRouteImport } from './routes/app/_app/new-user'
import { Route as AppAppHomeRouteImport } from './routes/app/_app/home'
import { Route as AppAppAssetRouteImport } from './routes/app/_app/_asset'
import { Route as ScreenshotsMetricsMetricIdIndexRouteImport } from './routes/screenshots/metrics.$metricId.index'
import { Route as ScreenshotsDashboardsDashboardIdIndexRouteImport } from './routes/screenshots/dashboards.$dashboardId.index'
import { Route as ScreenshotsChatsChatIdIndexRouteImport } from './routes/screenshots/chats.$chatId.index'
import { Route as EmbedChatChatIdIndexRouteImport } from './routes/embed/chat.$chatId/index'
import { Route as AppSettingsSettingsIndexRouteImport } from './routes/app/_settings/settings.index'
import { Route as AppAppReportsIndexRouteImport } from './routes/app/_app/reports.index'
@ -157,12 +162,6 @@ import { Route as AppAppAssetChatsChatIdReportsReportIdMetricsMetricIdContentCha
import { Route as AppAppAssetChatsChatIdDashboardsDashboardIdMetricsMetricIdContentSqlRouteImport } from './routes/app/_app/_asset/chats.$chatId/dashboards.$dashboardId/metrics.$metricId/_content/sql'
import { Route as AppAppAssetChatsChatIdDashboardsDashboardIdMetricsMetricIdContentResultsRouteImport } from './routes/app/_app/_asset/chats.$chatId/dashboards.$dashboardId/metrics.$metricId/_content/results'
import { Route as AppAppAssetChatsChatIdDashboardsDashboardIdMetricsMetricIdContentChartRouteImport } from './routes/app/_app/_asset/chats.$chatId/dashboards.$dashboardId/metrics.$metricId/_content/chart'
import { ServerRoute as AuthConfirmServerRouteImport } from './routes/auth.confirm'
import { ServerRoute as AuthCallbackServerRouteImport } from './routes/auth.callback'
import { ServerRoute as ScreenshotsReportsReportIdServerRouteImport } from './routes/screenshots/reports.$reportId'
import { ServerRoute as ScreenshotsMetricsMetricIdIndexServerRouteImport } from './routes/screenshots/metrics.$metricId.index'
import { ServerRoute as ScreenshotsDashboardsDashboardIdIndexServerRouteImport } from './routes/screenshots/dashboards.$dashboardId.index'
import { ServerRoute as ScreenshotsChatsChatIdIndexServerRouteImport } from './routes/screenshots/chats.$chatId.index'
const ScreenshotsRouteImport = createFileRoute('/screenshots')()
const EmbedChatChatIdReportsReportIdRouteImport = createFileRoute(
@ -213,7 +212,6 @@ const AppAppAssetChatsChatIdDashboardsDashboardIdMetricsMetricIdRouteImport =
createFileRoute(
'/app/_app/_asset/chats/$chatId/dashboards/$dashboardId/metrics/$metricId',
)()
const rootServerRouteImport = createServerRootRoute()
const ScreenshotsRoute = ScreenshotsRouteImport.update({
id: '/screenshots',
@ -274,6 +272,16 @@ const AuthLoginRoute = AuthLoginRouteImport.update({
path: '/login',
getParentRoute: () => AuthRoute,
} as any)
const AuthConfirmRoute = AuthConfirmRouteImport.update({
id: '/confirm',
path: '/confirm',
getParentRoute: () => AuthRoute,
} as any)
const AuthCallbackRoute = AuthCallbackRouteImport.update({
id: '/callback',
path: '/callback',
getParentRoute: () => AuthRoute,
} as any)
const AppSettingsRoute = AppSettingsRouteImport.update({
id: '/_settings',
getParentRoute: () => AppRoute,
@ -282,6 +290,12 @@ const AppAppRoute = AppAppRouteImport.update({
id: '/_app',
getParentRoute: () => AppRoute,
} as any)
const ScreenshotsReportsReportIdRoute =
ScreenshotsReportsReportIdRouteImport.update({
id: '/reports/$reportId',
path: '/reports/$reportId',
getParentRoute: () => ScreenshotsRoute,
} as any)
const EmbedReportReportIdRoute = EmbedReportReportIdRouteImport.update({
id: '/report/$reportId',
path: '/report/$reportId',
@ -331,6 +345,24 @@ const AppAppAssetRoute = AppAppAssetRouteImport.update({
id: '/_asset',
getParentRoute: () => AppAppRoute,
} as any)
const ScreenshotsMetricsMetricIdIndexRoute =
ScreenshotsMetricsMetricIdIndexRouteImport.update({
id: '/metrics/$metricId/',
path: '/metrics/$metricId/',
getParentRoute: () => ScreenshotsRoute,
} as any)
const ScreenshotsDashboardsDashboardIdIndexRoute =
ScreenshotsDashboardsDashboardIdIndexRouteImport.update({
id: '/dashboards/$dashboardId/',
path: '/dashboards/$dashboardId/',
getParentRoute: () => ScreenshotsRoute,
} as any)
const ScreenshotsChatsChatIdIndexRoute =
ScreenshotsChatsChatIdIndexRouteImport.update({
id: '/chats/$chatId/',
path: '/chats/$chatId/',
getParentRoute: () => ScreenshotsRoute,
} as any)
const EmbedChatChatIdIndexRoute = EmbedChatChatIdIndexRouteImport.update({
id: '/',
path: '/',
@ -1213,40 +1245,6 @@ const AppAppAssetChatsChatIdDashboardsDashboardIdMetricsMetricIdContentChartRout
AppAppAssetChatsChatIdDashboardsDashboardIdMetricsMetricIdContentRoute,
} as any,
)
const AuthConfirmServerRoute = AuthConfirmServerRouteImport.update({
id: '/auth/confirm',
path: '/auth/confirm',
getParentRoute: () => rootServerRouteImport,
} as any)
const AuthCallbackServerRoute = AuthCallbackServerRouteImport.update({
id: '/auth/callback',
path: '/auth/callback',
getParentRoute: () => rootServerRouteImport,
} as any)
const ScreenshotsReportsReportIdServerRoute =
ScreenshotsReportsReportIdServerRouteImport.update({
id: '/screenshots/reports/$reportId',
path: '/screenshots/reports/$reportId',
getParentRoute: () => rootServerRouteImport,
} as any)
const ScreenshotsMetricsMetricIdIndexServerRoute =
ScreenshotsMetricsMetricIdIndexServerRouteImport.update({
id: '/screenshots/metrics/$metricId/',
path: '/screenshots/metrics/$metricId/',
getParentRoute: () => rootServerRouteImport,
} as any)
const ScreenshotsDashboardsDashboardIdIndexServerRoute =
ScreenshotsDashboardsDashboardIdIndexServerRouteImport.update({
id: '/screenshots/dashboards/$dashboardId/',
path: '/screenshots/dashboards/$dashboardId/',
getParentRoute: () => rootServerRouteImport,
} as any)
const ScreenshotsChatsChatIdIndexServerRoute =
ScreenshotsChatsChatIdIndexServerRouteImport.update({
id: '/screenshots/chats/$chatId/',
path: '/screenshots/chats/$chatId/',
getParentRoute: () => rootServerRouteImport,
} as any)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
@ -1254,6 +1252,8 @@ export interface FileRoutesByFullPath {
'/auth': typeof AuthRouteWithChildren
'/embed': typeof EmbedRouteWithChildren
'/healthcheck': typeof HealthcheckRoute
'/auth/callback': typeof AuthCallbackRoute
'/auth/confirm': typeof AuthConfirmRoute
'/auth/login': typeof AuthLoginRoute
'/auth/logout': typeof AuthLogoutRoute
'/auth/reset-password': typeof AuthResetPasswordRoute
@ -1267,6 +1267,7 @@ export interface FileRoutesByFullPath {
'/embed/dashboard/$dashboardId': typeof EmbedDashboardDashboardIdRoute
'/embed/metric/$metricId': typeof EmbedMetricMetricIdRoute
'/embed/report/$reportId': typeof EmbedReportReportIdRoute
'/screenshots/reports/$reportId': typeof ScreenshotsReportsReportIdRoute
'/app/datasets/$datasetId': typeof AppAppDatasetsDatasetIdRouteWithChildren
'/app/home/shortcuts': typeof AppAppHomeShortcutsRoute
'/app/chats': typeof AppAppChatsIndexRoute
@ -1280,6 +1281,9 @@ export interface FileRoutesByFullPath {
'/app/reports': typeof AppAppReportsIndexRoute
'/app/settings': typeof AppSettingsSettingsIndexRoute
'/embed/chat/$chatId/': typeof EmbedChatChatIdIndexRoute
'/screenshots/chats/$chatId': typeof ScreenshotsChatsChatIdIndexRoute
'/screenshots/dashboards/$dashboardId': typeof ScreenshotsDashboardsDashboardIdIndexRoute
'/screenshots/metrics/$metricId': typeof ScreenshotsMetricsMetricIdIndexRoute
'/app/chats/$chatId': typeof AppAppAssetChatsChatIdRouteWithChildren
'/app/datasets/$datasetId/editor': typeof AppAppDatasetsDatasetIdEditorRoute
'/app/datasets/$datasetId/overview': typeof AppAppDatasetsDatasetIdOverviewRoute
@ -1395,6 +1399,8 @@ export interface FileRoutesByTo {
'/embed': typeof EmbedRouteWithChildren
'/healthcheck': typeof HealthcheckRoute
'/app': typeof AppSettingsRestricted_layoutAdmin_onlyRouteWithChildren
'/auth/callback': typeof AuthCallbackRoute
'/auth/confirm': typeof AuthConfirmRoute
'/auth/login': typeof AuthLoginRoute
'/auth/logout': typeof AuthLogoutRoute
'/auth/reset-password': typeof AuthResetPasswordRoute
@ -1404,6 +1410,7 @@ export interface FileRoutesByTo {
'/embed/dashboard/$dashboardId': typeof EmbedDashboardDashboardIdRoute
'/embed/metric/$metricId': typeof EmbedMetricMetricIdRoute
'/embed/report/$reportId': typeof EmbedReportReportIdRoute
'/screenshots/reports/$reportId': typeof ScreenshotsReportsReportIdRoute
'/app/datasets/$datasetId': typeof AppAppDatasetsDatasetIdRouteWithChildren
'/app/home/shortcuts': typeof AppAppHomeShortcutsRoute
'/app/chats': typeof AppAppChatsIndexRoute
@ -1417,6 +1424,9 @@ export interface FileRoutesByTo {
'/app/reports': typeof AppAppReportsIndexRoute
'/app/settings': typeof AppSettingsSettingsIndexRoute
'/embed/chat/$chatId': typeof EmbedChatChatIdIndexRoute
'/screenshots/chats/$chatId': typeof ScreenshotsChatsChatIdIndexRoute
'/screenshots/dashboards/$dashboardId': typeof ScreenshotsDashboardsDashboardIdIndexRoute
'/screenshots/metrics/$metricId': typeof ScreenshotsMetricsMetricIdIndexRoute
'/app/datasets/$datasetId/editor': typeof AppAppDatasetsDatasetIdEditorRoute
'/app/datasets/$datasetId/overview': typeof AppAppDatasetsDatasetIdOverviewRoute
'/app/settings/profile': typeof AppSettingsRestricted_layoutSettingsProfileRoute
@ -1513,6 +1523,8 @@ export interface FileRoutesById {
'/healthcheck': typeof HealthcheckRoute
'/app/_app': typeof AppAppRouteWithChildren
'/app/_settings': typeof AppSettingsRouteWithChildren
'/auth/callback': typeof AuthCallbackRoute
'/auth/confirm': typeof AuthConfirmRoute
'/auth/login': typeof AuthLoginRoute
'/auth/logout': typeof AuthLogoutRoute
'/auth/reset-password': typeof AuthResetPasswordRoute
@ -1530,6 +1542,7 @@ export interface FileRoutesById {
'/embed/dashboard/$dashboardId': typeof EmbedDashboardDashboardIdRoute
'/embed/metric/$metricId': typeof EmbedMetricMetricIdRoute
'/embed/report/$reportId': typeof EmbedReportReportIdRoute
'/screenshots/reports/$reportId': typeof ScreenshotsReportsReportIdRoute
'/app/_app/datasets/$datasetId': typeof AppAppDatasetsDatasetIdRouteWithChildren
'/app/_app/home/shortcuts': typeof AppAppHomeShortcutsRoute
'/app/_settings/_restricted_layout/_admin_only': typeof AppSettingsRestricted_layoutAdmin_onlyRouteWithChildren
@ -1544,6 +1557,9 @@ export interface FileRoutesById {
'/app/_app/reports/': typeof AppAppReportsIndexRoute
'/app/_settings/settings/': typeof AppSettingsSettingsIndexRoute
'/embed/chat/$chatId/': typeof EmbedChatChatIdIndexRoute
'/screenshots/chats/$chatId/': typeof ScreenshotsChatsChatIdIndexRoute
'/screenshots/dashboards/$dashboardId/': typeof ScreenshotsDashboardsDashboardIdIndexRoute
'/screenshots/metrics/$metricId/': typeof ScreenshotsMetricsMetricIdIndexRoute
'/app/_app/_asset/chats/$chatId': typeof AppAppAssetChatsChatIdRouteWithChildren
'/app/_app/datasets/$datasetId/editor': typeof AppAppDatasetsDatasetIdEditorRoute
'/app/_app/datasets/$datasetId/overview': typeof AppAppDatasetsDatasetIdOverviewRoute
@ -1676,6 +1692,8 @@ export interface FileRouteTypes {
| '/auth'
| '/embed'
| '/healthcheck'
| '/auth/callback'
| '/auth/confirm'
| '/auth/login'
| '/auth/logout'
| '/auth/reset-password'
@ -1689,6 +1707,7 @@ export interface FileRouteTypes {
| '/embed/dashboard/$dashboardId'
| '/embed/metric/$metricId'
| '/embed/report/$reportId'
| '/screenshots/reports/$reportId'
| '/app/datasets/$datasetId'
| '/app/home/shortcuts'
| '/app/chats'
@ -1702,6 +1721,9 @@ export interface FileRouteTypes {
| '/app/reports'
| '/app/settings'
| '/embed/chat/$chatId/'
| '/screenshots/chats/$chatId'
| '/screenshots/dashboards/$dashboardId'
| '/screenshots/metrics/$metricId'
| '/app/chats/$chatId'
| '/app/datasets/$datasetId/editor'
| '/app/datasets/$datasetId/overview'
@ -1817,6 +1839,8 @@ export interface FileRouteTypes {
| '/embed'
| '/healthcheck'
| '/app'
| '/auth/callback'
| '/auth/confirm'
| '/auth/login'
| '/auth/logout'
| '/auth/reset-password'
@ -1826,6 +1850,7 @@ export interface FileRouteTypes {
| '/embed/dashboard/$dashboardId'
| '/embed/metric/$metricId'
| '/embed/report/$reportId'
| '/screenshots/reports/$reportId'
| '/app/datasets/$datasetId'
| '/app/home/shortcuts'
| '/app/chats'
@ -1839,6 +1864,9 @@ export interface FileRouteTypes {
| '/app/reports'
| '/app/settings'
| '/embed/chat/$chatId'
| '/screenshots/chats/$chatId'
| '/screenshots/dashboards/$dashboardId'
| '/screenshots/metrics/$metricId'
| '/app/datasets/$datasetId/editor'
| '/app/datasets/$datasetId/overview'
| '/app/settings/profile'
@ -1934,6 +1962,8 @@ export interface FileRouteTypes {
| '/healthcheck'
| '/app/_app'
| '/app/_settings'
| '/auth/callback'
| '/auth/confirm'
| '/auth/login'
| '/auth/logout'
| '/auth/reset-password'
@ -1951,6 +1981,7 @@ export interface FileRouteTypes {
| '/embed/dashboard/$dashboardId'
| '/embed/metric/$metricId'
| '/embed/report/$reportId'
| '/screenshots/reports/$reportId'
| '/app/_app/datasets/$datasetId'
| '/app/_app/home/shortcuts'
| '/app/_settings/_restricted_layout/_admin_only'
@ -1965,6 +1996,9 @@ export interface FileRouteTypes {
| '/app/_app/reports/'
| '/app/_settings/settings/'
| '/embed/chat/$chatId/'
| '/screenshots/chats/$chatId/'
| '/screenshots/dashboards/$dashboardId/'
| '/screenshots/metrics/$metricId/'
| '/app/_app/_asset/chats/$chatId'
| '/app/_app/datasets/$datasetId/editor'
| '/app/_app/datasets/$datasetId/overview'
@ -2099,66 +2133,6 @@ export interface RootRouteChildren {
InfoGettingStartedRoute: typeof InfoGettingStartedRoute
ScreenshotsRoute: typeof ScreenshotsRouteWithChildren
}
export interface FileServerRoutesByFullPath {
'/auth/callback': typeof AuthCallbackServerRoute
'/auth/confirm': typeof AuthConfirmServerRoute
'/screenshots/reports/$reportId': typeof ScreenshotsReportsReportIdServerRoute
'/screenshots/chats/$chatId': typeof ScreenshotsChatsChatIdIndexServerRoute
'/screenshots/dashboards/$dashboardId': typeof ScreenshotsDashboardsDashboardIdIndexServerRoute
'/screenshots/metrics/$metricId': typeof ScreenshotsMetricsMetricIdIndexServerRoute
}
export interface FileServerRoutesByTo {
'/auth/callback': typeof AuthCallbackServerRoute
'/auth/confirm': typeof AuthConfirmServerRoute
'/screenshots/reports/$reportId': typeof ScreenshotsReportsReportIdServerRoute
'/screenshots/chats/$chatId': typeof ScreenshotsChatsChatIdIndexServerRoute
'/screenshots/dashboards/$dashboardId': typeof ScreenshotsDashboardsDashboardIdIndexServerRoute
'/screenshots/metrics/$metricId': typeof ScreenshotsMetricsMetricIdIndexServerRoute
}
export interface FileServerRoutesById {
__root__: typeof rootServerRouteImport
'/auth/callback': typeof AuthCallbackServerRoute
'/auth/confirm': typeof AuthConfirmServerRoute
'/screenshots/reports/$reportId': typeof ScreenshotsReportsReportIdServerRoute
'/screenshots/chats/$chatId/': typeof ScreenshotsChatsChatIdIndexServerRoute
'/screenshots/dashboards/$dashboardId/': typeof ScreenshotsDashboardsDashboardIdIndexServerRoute
'/screenshots/metrics/$metricId/': typeof ScreenshotsMetricsMetricIdIndexServerRoute
}
export interface FileServerRouteTypes {
fileServerRoutesByFullPath: FileServerRoutesByFullPath
fullPaths:
| '/auth/callback'
| '/auth/confirm'
| '/screenshots/reports/$reportId'
| '/screenshots/chats/$chatId'
| '/screenshots/dashboards/$dashboardId'
| '/screenshots/metrics/$metricId'
fileServerRoutesByTo: FileServerRoutesByTo
to:
| '/auth/callback'
| '/auth/confirm'
| '/screenshots/reports/$reportId'
| '/screenshots/chats/$chatId'
| '/screenshots/dashboards/$dashboardId'
| '/screenshots/metrics/$metricId'
id:
| '__root__'
| '/auth/callback'
| '/auth/confirm'
| '/screenshots/reports/$reportId'
| '/screenshots/chats/$chatId/'
| '/screenshots/dashboards/$dashboardId/'
| '/screenshots/metrics/$metricId/'
fileServerRoutesById: FileServerRoutesById
}
export interface RootServerRouteChildren {
AuthCallbackServerRoute: typeof AuthCallbackServerRoute
AuthConfirmServerRoute: typeof AuthConfirmServerRoute
ScreenshotsReportsReportIdServerRoute: typeof ScreenshotsReportsReportIdServerRoute
ScreenshotsChatsChatIdIndexServerRoute: typeof ScreenshotsChatsChatIdIndexServerRoute
ScreenshotsDashboardsDashboardIdIndexServerRoute: typeof ScreenshotsDashboardsDashboardIdIndexServerRoute
ScreenshotsMetricsMetricIdIndexServerRoute: typeof ScreenshotsMetricsMetricIdIndexServerRoute
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
@ -2246,6 +2220,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthLoginRouteImport
parentRoute: typeof AuthRoute
}
'/auth/confirm': {
id: '/auth/confirm'
path: '/confirm'
fullPath: '/auth/confirm'
preLoaderRoute: typeof AuthConfirmRouteImport
parentRoute: typeof AuthRoute
}
'/auth/callback': {
id: '/auth/callback'
path: '/callback'
fullPath: '/auth/callback'
preLoaderRoute: typeof AuthCallbackRouteImport
parentRoute: typeof AuthRoute
}
'/app/_settings': {
id: '/app/_settings'
path: ''
@ -2260,6 +2248,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AppAppRouteImport
parentRoute: typeof AppRoute
}
'/screenshots/reports/$reportId': {
id: '/screenshots/reports/$reportId'
path: '/reports/$reportId'
fullPath: '/screenshots/reports/$reportId'
preLoaderRoute: typeof ScreenshotsReportsReportIdRouteImport
parentRoute: typeof ScreenshotsRoute
}
'/embed/report/$reportId': {
id: '/embed/report/$reportId'
path: '/report/$reportId'
@ -2330,6 +2325,27 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AppAppAssetRouteImport
parentRoute: typeof AppAppRoute
}
'/screenshots/metrics/$metricId/': {
id: '/screenshots/metrics/$metricId/'
path: '/metrics/$metricId'
fullPath: '/screenshots/metrics/$metricId'
preLoaderRoute: typeof ScreenshotsMetricsMetricIdIndexRouteImport
parentRoute: typeof ScreenshotsRoute
}
'/screenshots/dashboards/$dashboardId/': {
id: '/screenshots/dashboards/$dashboardId/'
path: '/dashboards/$dashboardId'
fullPath: '/screenshots/dashboards/$dashboardId'
preLoaderRoute: typeof ScreenshotsDashboardsDashboardIdIndexRouteImport
parentRoute: typeof ScreenshotsRoute
}
'/screenshots/chats/$chatId/': {
id: '/screenshots/chats/$chatId/'
path: '/chats/$chatId'
fullPath: '/screenshots/chats/$chatId'
preLoaderRoute: typeof ScreenshotsChatsChatIdIndexRouteImport
parentRoute: typeof ScreenshotsRoute
}
'/embed/chat/$chatId/': {
id: '/embed/chat/$chatId/'
path: '/'
@ -3291,52 +3307,6 @@ declare module '@tanstack/react-router' {
}
}
}
declare module '@tanstack/react-start/server' {
interface ServerFileRoutesByPath {
'/auth/confirm': {
id: '/auth/confirm'
path: '/auth/confirm'
fullPath: '/auth/confirm'
preLoaderRoute: typeof AuthConfirmServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/auth/callback': {
id: '/auth/callback'
path: '/auth/callback'
fullPath: '/auth/callback'
preLoaderRoute: typeof AuthCallbackServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/screenshots/reports/$reportId': {
id: '/screenshots/reports/$reportId'
path: '/screenshots/reports/$reportId'
fullPath: '/screenshots/reports/$reportId'
preLoaderRoute: typeof ScreenshotsReportsReportIdServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/screenshots/metrics/$metricId/': {
id: '/screenshots/metrics/$metricId/'
path: '/screenshots/metrics/$metricId'
fullPath: '/screenshots/metrics/$metricId'
preLoaderRoute: typeof ScreenshotsMetricsMetricIdIndexServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/screenshots/dashboards/$dashboardId/': {
id: '/screenshots/dashboards/$dashboardId/'
path: '/screenshots/dashboards/$dashboardId'
fullPath: '/screenshots/dashboards/$dashboardId'
preLoaderRoute: typeof ScreenshotsDashboardsDashboardIdIndexServerRouteImport
parentRoute: typeof rootServerRouteImport
}
'/screenshots/chats/$chatId/': {
id: '/screenshots/chats/$chatId/'
path: '/screenshots/chats/$chatId'
fullPath: '/screenshots/chats/$chatId'
preLoaderRoute: typeof ScreenshotsChatsChatIdIndexServerRouteImport
parentRoute: typeof rootServerRouteImport
}
}
}
interface AppAppAssetChatsChatIdDashboardsDashboardIdLayoutRouteChildren {
AppAppAssetChatsChatIdDashboardsDashboardIdLayoutIndexRoute: typeof AppAppAssetChatsChatIdDashboardsDashboardIdLayoutIndexRoute
@ -4099,12 +4069,16 @@ const AppRouteChildren: AppRouteChildren = {
const AppRouteWithChildren = AppRoute._addFileChildren(AppRouteChildren)
interface AuthRouteChildren {
AuthCallbackRoute: typeof AuthCallbackRoute
AuthConfirmRoute: typeof AuthConfirmRoute
AuthLoginRoute: typeof AuthLoginRoute
AuthLogoutRoute: typeof AuthLogoutRoute
AuthResetPasswordRoute: typeof AuthResetPasswordRoute
}
const AuthRouteChildren: AuthRouteChildren = {
AuthCallbackRoute: AuthCallbackRoute,
AuthConfirmRoute: AuthConfirmRoute,
AuthLoginRoute: AuthLoginRoute,
AuthLogoutRoute: AuthLogoutRoute,
AuthResetPasswordRoute: AuthResetPasswordRoute,
@ -4361,10 +4335,19 @@ const ScreenshotsContentRouteWithChildren =
interface ScreenshotsRouteChildren {
ScreenshotsContentRoute: typeof ScreenshotsContentRouteWithChildren
ScreenshotsReportsReportIdRoute: typeof ScreenshotsReportsReportIdRoute
ScreenshotsChatsChatIdIndexRoute: typeof ScreenshotsChatsChatIdIndexRoute
ScreenshotsDashboardsDashboardIdIndexRoute: typeof ScreenshotsDashboardsDashboardIdIndexRoute
ScreenshotsMetricsMetricIdIndexRoute: typeof ScreenshotsMetricsMetricIdIndexRoute
}
const ScreenshotsRouteChildren: ScreenshotsRouteChildren = {
ScreenshotsContentRoute: ScreenshotsContentRouteWithChildren,
ScreenshotsReportsReportIdRoute: ScreenshotsReportsReportIdRoute,
ScreenshotsChatsChatIdIndexRoute: ScreenshotsChatsChatIdIndexRoute,
ScreenshotsDashboardsDashboardIdIndexRoute:
ScreenshotsDashboardsDashboardIdIndexRoute,
ScreenshotsMetricsMetricIdIndexRoute: ScreenshotsMetricsMetricIdIndexRoute,
}
const ScreenshotsRouteWithChildren = ScreenshotsRoute._addFileChildren(
@ -4383,17 +4366,13 @@ const rootRouteChildren: RootRouteChildren = {
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
const rootServerRouteChildren: RootServerRouteChildren = {
AuthCallbackServerRoute: AuthCallbackServerRoute,
AuthConfirmServerRoute: AuthConfirmServerRoute,
ScreenshotsReportsReportIdServerRoute: ScreenshotsReportsReportIdServerRoute,
ScreenshotsChatsChatIdIndexServerRoute:
ScreenshotsChatsChatIdIndexServerRoute,
ScreenshotsDashboardsDashboardIdIndexServerRoute:
ScreenshotsDashboardsDashboardIdIndexServerRoute,
ScreenshotsMetricsMetricIdIndexServerRoute:
ScreenshotsMetricsMetricIdIndexServerRoute,
import type { getRouter } from './router.tsx'
import type { startInstance } from './start.ts'
declare module '@tanstack/react-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
config: Awaited<ReturnType<typeof startInstance.getOptions>>
}
}
export const serverRouteTree = rootServerRouteImport
._addFileChildren(rootServerRouteChildren)
._addFileTypes<FileServerRouteTypes>()

View File

@ -9,8 +9,6 @@ import { NotFoundCard } from '@/components/features/global/NotFoundCard';
import { FileIndeterminateLoader } from '@/components/features/loaders/FileIndeterminateLoader';
import * as TanstackQuery from './integrations/tanstack-query/query-client';
import { routeTree } from './routeTree.gen';
// Import global middleware to register it
import './global-middleware';
export interface AppRouterContext {
queryClient: QueryClient;

View File

@ -1,13 +1,11 @@
import { createFileRoute, matchByPath, Outlet, redirect } from '@tanstack/react-router';
import { createFileRoute, Outlet, redirect } from '@tanstack/react-router';
import last from 'lodash/last';
import { prefetchGetDatasetMetadata } from '@/api/buster_rest/datasets/queryRequests';
import { DatasetsIndividualLayout } from '@/controllers/DatasetsControllers/DatasetsIndividualLayout/DatasetsLayout';
export const Route = createFileRoute('/app/_app/datasets/$datasetId')({
beforeLoad: async ({ params, location }) => {
const isDatasetRoot = matchByPath('', location.pathname, {
to: '/app/datasets/$datasetId',
});
beforeLoad: async ({ params, matches }) => {
const isDatasetRoot = last(matches).path === '/app/datasets/$datasetId';
if (isDatasetRoot) {
throw redirect({
to: '/app/datasets/$datasetId/overview',

View File

@ -1,4 +1,4 @@
import { createServerFileRoute } from '@tanstack/react-start/server';
import { createFileRoute } from '@tanstack/react-router';
import { z } from 'zod';
import { env } from '@/env';
import { getSupabaseServerClient } from '../integrations/supabase/server';
@ -16,119 +16,126 @@ const searchParamsSchema = z.object({
// Type for the validated search parameters
type SearchParams = z.infer<typeof searchParamsSchema>;
export const ServerRoute = createServerFileRoute('/auth/callback').methods({
GET: async ({ request }) => {
// Parse query parameters from the URL
const url = new URL(request.url);
export const Route = createFileRoute('/auth/callback')({
server: {
handlers: {
GET: async ({ request }) => {
// Parse query parameters from the URL
const url = new URL(request.url);
// Extract and validate search parameters
const searchParams: SearchParams = {
code: url.searchParams.get('code') || undefined,
code_challenge: url.searchParams.get('code_challenge') || undefined,
error: url.searchParams.get('error') || undefined,
error_description: url.searchParams.get('error_description') || undefined,
next: url.searchParams.get('next') || AppHomeRoute.to || '/app/home',
};
// Extract and validate search parameters
const searchParams: SearchParams = {
code: url.searchParams.get('code') || undefined,
code_challenge: url.searchParams.get('code_challenge') || undefined,
error: url.searchParams.get('error') || undefined,
error_description: url.searchParams.get('error_description') || undefined,
next: url.searchParams.get('next') || AppHomeRoute.to || '/app/home',
};
// Validate the parameters (optional - provides runtime validation)
const validatedParams = searchParamsSchema.parse(searchParams);
// Validate the parameters (optional - provides runtime validation)
const validatedParams = searchParamsSchema.parse(searchParams);
const { code, code_challenge, error, error_description } = validatedParams;
const next = validatedParams.next || AppHomeRoute.to || '/app/home';
const { code, code_challenge, error, error_description } = validatedParams;
const next = validatedParams.next || AppHomeRoute.to || '/app/home';
// Handle OAuth errors first
if (error) {
console.error('OAuth error received:', error, error_description);
const errorMessage = error_description || error;
return new Response(`Authentication failed: ${errorMessage}`, { status: 400 });
}
// Handle OAuth errors first
if (error) {
console.error('OAuth error received:', error, error_description);
const errorMessage = error_description || error;
return new Response(`Authentication failed: ${errorMessage}`, { status: 400 });
}
// Use code first, fallback to code_challenge if code is not available
const authCode = code || code_challenge;
// Use code first, fallback to code_challenge if code is not available
const authCode = code || code_challenge;
if (!authCode) {
console.error('No authorization code or code_challenge found in callback');
return new Response('Missing authorization code', { status: 400 });
}
if (!authCode) {
console.error('No authorization code or code_challenge found in callback');
return new Response('Missing authorization code', { status: 400 });
}
console.info('Using auth code:', code ? 'code' : 'code_challenge');
console.info('Using auth code:', code ? 'code' : 'code_challenge');
try {
const supabase = getSupabaseServerClient();
try {
const supabase = getSupabaseServerClient();
// Exchange the authorization code for a session
console.info('Attempting to exchange authorization code for session');
const { data: sessionData, error: exchangeError } = await supabase.auth
.exchangeCodeForSession(authCode)
.catch((authError) => {
// Handle headers already sent errors gracefully
if (authError instanceof Error && authError.message.includes('ERR_HTTP_HEADERS_SENT')) {
console.warn('Headers already sent during code exchange, proceeding with redirect');
return { data: null, error: null };
// Exchange the authorization code for a session
console.info('Attempting to exchange authorization code for session');
const { data: sessionData, error: exchangeError } = await supabase.auth
.exchangeCodeForSession(authCode)
.catch((authError) => {
// Handle headers already sent errors gracefully
if (
authError instanceof Error &&
authError.message.includes('ERR_HTTP_HEADERS_SENT')
) {
console.warn('Headers already sent during code exchange, proceeding with redirect');
return { data: null, error: null };
}
// Return error for other cases
console.error('Unexpected error during code exchange:', authError);
return { data: null, error: authError };
});
if (exchangeError) {
console.error('Error exchanging code for session:', exchangeError);
return new Response(`Authentication failed: ${exchangeError.message}`, { status: 500 });
}
// Return error for other cases
console.error('Unexpected error during code exchange:', authError);
return { data: null, error: authError };
});
if (exchangeError) {
console.error('Error exchanging code for session:', exchangeError);
return new Response(`Authentication failed: ${exchangeError.message}`, { status: 500 });
}
if (sessionData?.session) {
console.info('Successfully exchanged code for session, user:', sessionData.user?.email);
} else {
console.info('Code exchange succeeded but no session data received');
}
if (sessionData?.session) {
console.info('Successfully exchanged code for session, user:', sessionData.user?.email);
} else {
console.info('Code exchange succeeded but no session data received');
}
// Construct the redirect URL
const forwardedHost = request.headers.get('x-forwarded-host');
const origin = request.headers.get('origin') || env.VITE_PUBLIC_URL || '';
const isLocalEnv = import.meta.env.DEV;
// Construct the redirect URL
const forwardedHost = request.headers.get('x-forwarded-host');
const origin = request.headers.get('origin') || env.VITE_PUBLIC_URL || '';
const isLocalEnv = import.meta.env.DEV;
// Ensure the redirect path is safe and starts with '/'
const safePath = next?.startsWith('/') ? next : AppHomeRoute.to || '/app/home';
// Ensure the redirect path is safe and starts with '/'
const safePath = next?.startsWith('/') ? next : AppHomeRoute.to || '/app/home';
let redirectUrl: string;
let redirectUrl: string;
if (isLocalEnv) {
redirectUrl = `${origin}${safePath}`;
} else if (forwardedHost) {
redirectUrl = `https://${forwardedHost}${safePath}`;
} else {
redirectUrl = `${origin}${safePath}`;
}
if (isLocalEnv) {
redirectUrl = `${origin}${safePath}`;
} else if (forwardedHost) {
redirectUrl = `https://${forwardedHost}${safePath}`;
} else {
redirectUrl = `${origin}${safePath}`;
}
console.info('Redirecting to:', redirectUrl);
console.info('Redirecting to:', redirectUrl);
return new Response(null, {
status: 302,
headers: {
Location: redirectUrl,
'Cache-Control': 'no-cache, no-store, must-revalidate',
},
});
} catch (error) {
// Final catch-all for any unhandled errors
if (error instanceof Error && error.message.includes('ERR_HTTP_HEADERS_SENT')) {
console.warn('Headers already sent in auth callback, attempting redirect anyway');
// Try to redirect anyway since the auth might have succeeded
const origin = request.headers.get('origin') || env.VITE_PUBLIC_URL || '';
const safePath = next?.startsWith('/') ? next : AppHomeRoute.to || '/app/home';
return new Response(null, {
status: 302,
headers: {
Location: `${origin}${safePath}`,
'Cache-Control': 'no-cache, no-store, must-revalidate',
},
});
}
console.error('Unexpected error in auth callback:', error);
return new Response(
`Internal server error: ${error instanceof Error ? error.message : 'Unknown error'}`,
{ status: 500 }
);
}
return new Response(null, {
status: 302,
headers: {
Location: redirectUrl,
'Cache-Control': 'no-cache, no-store, must-revalidate',
},
});
} catch (error) {
// Final catch-all for any unhandled errors
if (error instanceof Error && error.message.includes('ERR_HTTP_HEADERS_SENT')) {
console.warn('Headers already sent in auth callback, attempting redirect anyway');
// Try to redirect anyway since the auth might have succeeded
const origin = request.headers.get('origin') || env.VITE_PUBLIC_URL || '';
const safePath = next?.startsWith('/') ? next : AppHomeRoute.to || '/app/home';
return new Response(null, {
status: 302,
headers: {
Location: `${origin}${safePath}`,
'Cache-Control': 'no-cache, no-store, must-revalidate',
},
});
}
console.error('Unexpected error in auth callback:', error);
return new Response(
`Internal server error: ${error instanceof Error ? error.message : 'Unknown error'}`,
{ status: 500 }
);
}
},
},
},
});

View File

@ -1,6 +1,5 @@
import type { EmailOtpType } from '@supabase/supabase-js';
import { redirect } from '@tanstack/react-router';
import { createServerFileRoute } from '@tanstack/react-start/server';
import { createFileRoute, redirect } from '@tanstack/react-router';
import { z } from 'zod';
import { getSupabaseServerClient } from '@/integrations/supabase/server';
import { Route as AuthResetPasswordRoute } from './auth.reset-password';
@ -12,43 +11,47 @@ const searchParamsSchema = z.object({
type: z.string().optional(),
});
export const ServerRoute = createServerFileRoute('/auth/confirm').methods({
GET: async ({ request }) => {
const url = new URL(request.url);
const { data: searchParams } = searchParamsSchema.safeParse({
code: url.searchParams.get('code') || undefined,
token_hash: url.searchParams.get('token_hash') || undefined,
type: url.searchParams.get('type') || undefined,
next: url.searchParams.get('next') || undefined,
});
export const Route = createFileRoute('/auth/confirm')({
server: {
handlers: {
GET: async ({ request }) => {
const url = new URL(request.url);
const { data: searchParams } = searchParamsSchema.safeParse({
code: url.searchParams.get('code') || undefined,
token_hash: url.searchParams.get('token_hash') || undefined,
type: url.searchParams.get('type') || undefined,
next: url.searchParams.get('next') || undefined,
});
if (!searchParams) {
return new Response('Invalid search params', { status: 400 });
}
if (!searchParams) {
return new Response('Invalid search params', { status: 400 });
}
const supabase = await getSupabaseServerClient();
const supabase = await getSupabaseServerClient();
const { token_hash, type } = searchParams;
const { token_hash, type } = searchParams;
if (!token_hash || !type) {
return new Response('Invalid search params', { status: 400 });
}
if (!token_hash || !type) {
return new Response('Invalid search params', { status: 400 });
}
const { data, error } = await supabase.auth.verifyOtp({
token_hash: token_hash,
type: type as EmailOtpType,
});
const { data, error } = await supabase.auth.verifyOtp({
token_hash: token_hash,
type: type as EmailOtpType,
});
if (!error) {
throw redirect({
to: AuthResetPasswordRoute.to,
search: {
email: data.user?.email || '',
},
});
}
if (!error) {
throw redirect({
to: AuthResetPasswordRoute.to,
search: {
email: data.user?.email || '',
},
});
}
console.error('Error verifying OTP', error);
return new Response('Error verifying OTP', { status: 400 });
console.error('Error verifying OTP', error);
return new Response('Error verifying OTP', { status: 400 });
},
},
},
});

View File

@ -1,4 +1,4 @@
import { createServerFileRoute } from '@tanstack/react-start/server';
import { createFileRoute } from '@tanstack/react-router';
import { z } from 'zod';
import { browserLogin } from '@/api/server-functions/browser-login';
import { createScreenshotResponse } from '@/api/server-functions/screenshot-helpers';
@ -14,38 +14,42 @@ export const GetChatScreenshotQuerySchema = z.object({
type: z.enum(['png', 'jpeg']).default('png'),
});
export const ServerRoute = createServerFileRoute('/screenshots/chats/$chatId/').methods({
GET: async ({ request, params }) => {
const { chatId } = GetChatScreenshotParamsSchema.parse(params);
const { width, height, type } = GetChatScreenshotQuerySchema.parse(
Object.fromEntries(new URL(request.url).searchParams)
);
export const Route = createFileRoute('/screenshots/chats/$chatId/')({
server: {
handlers: {
GET: async ({ request, params }) => {
const { chatId } = GetChatScreenshotParamsSchema.parse(params);
const { width, height, type } = GetChatScreenshotQuerySchema.parse(
Object.fromEntries(new URL(request.url).searchParams)
);
try {
const { result: screenshotBuffer } = await browserLogin({
width,
height,
fullPath: createHrefFromLink({
to: '/screenshots/chats/$chatId/content',
params: { chatId },
search: { type, width, height },
}),
request,
callback: async ({ page }) => {
const screenshotBuffer = await page.screenshot({ type });
return screenshotBuffer;
},
});
try {
const { result: screenshotBuffer } = await browserLogin({
width,
height,
fullPath: createHrefFromLink({
to: '/screenshots/chats/$chatId/content',
params: { chatId },
search: { type, width, height },
}),
request,
callback: async ({ page }) => {
const screenshotBuffer = await page.screenshot({ type });
return screenshotBuffer;
},
});
return createScreenshotResponse({ screenshotBuffer });
} catch (error) {
console.error('Error capturing chat screenshot', error);
return new Response(
JSON.stringify({
message: 'Failed to capture screenshot',
}),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
return createScreenshotResponse({ screenshotBuffer });
} catch (error) {
console.error('Error capturing chat screenshot', error);
return new Response(
JSON.stringify({
message: 'Failed to capture screenshot',
}),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
},
},
},
});

View File

@ -1,5 +1,4 @@
import { createFileRoute } from '@tanstack/react-router';
import { createServerFileRoute } from '@tanstack/react-start/server';
import { z } from 'zod';
import { browserLogin } from '@/api/server-functions/browser-login';
import { createScreenshotResponse } from '@/api/server-functions/screenshot-helpers';
@ -16,38 +15,78 @@ export const GetDashboardScreenshotQuerySchema = z.object({
type: z.enum(['png', 'jpeg']).default('png'),
});
export const ServerRoute = createServerFileRoute('/screenshots/dashboards/$dashboardId/').methods({
GET: async ({ request, params }) => {
const { dashboardId } = GetDashboardScreenshotParamsSchema.parse(params);
const { version_number, width, height, type } = GetDashboardScreenshotQuerySchema.parse(
Object.fromEntries(new URL(request.url).searchParams)
);
export const Route = createFileRoute('/screenshots/dashboards/$dashboardId/')({
server: {
handlers: {
GET: async ({ request, params }) => {
const { dashboardId } = GetDashboardScreenshotParamsSchema.parse(params);
const { version_number, width, height, type } = GetDashboardScreenshotQuerySchema.parse(
Object.fromEntries(new URL(request.url).searchParams)
);
try {
const { result: screenshotBuffer } = await browserLogin({
width,
height,
fullPath: createHrefFromLink({
to: '/screenshots/dashboards/$dashboardId/content',
params: { dashboardId },
search: { version_number, type, width, height },
}),
request,
callback: async ({ page }) => {
const screenshotBuffer = await page.screenshot({ type });
return screenshotBuffer;
},
});
try {
const { result: screenshotBuffer } = await browserLogin({
width,
height,
fullPath: createHrefFromLink({
to: '/screenshots/dashboards/$dashboardId/content',
params: { dashboardId },
search: { version_number, type, width, height },
}),
request,
callback: async ({ page }) => {
const screenshotBuffer = await page.screenshot({ type });
return screenshotBuffer;
},
});
return createScreenshotResponse({ screenshotBuffer });
} catch (error) {
console.error('Error capturing dashboard screenshot', error);
return new Response(
JSON.stringify({
message: 'Failed to capture screenshot',
}),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
return createScreenshotResponse({ screenshotBuffer });
} catch (error) {
console.error('Error capturing dashboard screenshot', error);
return new Response(
JSON.stringify({
message: 'Failed to capture screenshot',
}),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
},
},
},
});
// export const ServerRoute = createServerFileRoute('/screenshots/dashboards/$dashboardId/').methods({
// GET: async ({ request, params }) => {
// const { dashboardId } = GetDashboardScreenshotParamsSchema.parse(params);
// const { version_number, width, height, type } = GetDashboardScreenshotQuerySchema.parse(
// Object.fromEntries(new URL(request.url).searchParams)
// );
// try {
// const { result: screenshotBuffer } = await browserLogin({
// width,
// height,
// fullPath: createHrefFromLink({
// to: '/screenshots/dashboards/$dashboardId/content',
// params: { dashboardId },
// search: { version_number, type, width, height },
// }),
// request,
// callback: async ({ page }) => {
// const screenshotBuffer = await page.screenshot({ type });
// return screenshotBuffer;
// },
// });
// return createScreenshotResponse({ screenshotBuffer });
// } catch (error) {
// console.error('Error capturing dashboard screenshot', error);
// return new Response(
// JSON.stringify({
// message: 'Failed to capture screenshot',
// }),
// { status: 500, headers: { 'Content-Type': 'application/json' } }
// );
// }
// },
// });

View File

@ -1,8 +1,7 @@
import { createServerFileRoute } from '@tanstack/react-start/server';
import { createFileRoute } from '@tanstack/react-router';
import { z } from 'zod';
import { browserLogin } from '@/api/server-functions/browser-login';
import { createScreenshotResponse } from '@/api/server-functions/screenshot-helpers';
import { getSupabaseServerClient } from '@/integrations/supabase/server';
import { createHrefFromLink } from '@/lib/routes';
export const GetMetricScreenshotParamsSchema = z.object({
@ -16,41 +15,45 @@ export const GetMetricScreenshotQuerySchema = z.object({
type: z.enum(['png', 'jpeg']).default('png'),
});
export const ServerRoute = createServerFileRoute('/screenshots/metrics/$metricId/').methods({
GET: async ({ request, params }) => {
const { metricId } = GetMetricScreenshotParamsSchema.parse(params);
const { version_number, type, width, height } = GetMetricScreenshotQuerySchema.parse(
Object.fromEntries(new URL(request.url).searchParams)
);
export const Route = createFileRoute('/screenshots/metrics/$metricId/')({
server: {
handlers: {
GET: async ({ request, params }) => {
const { metricId } = GetMetricScreenshotParamsSchema.parse(params);
const { version_number, type, width, height } = GetMetricScreenshotQuerySchema.parse(
Object.fromEntries(new URL(request.url).searchParams)
);
try {
const { result: screenshotBuffer } = await browserLogin({
width,
height,
fullPath: createHrefFromLink({
to: '/screenshots/metrics/$metricId/content',
params: { metricId },
search: { version_number, type, width, height },
}),
request,
callback: async ({ page }) => {
const screenshotBuffer = await page.screenshot({
type,
try {
const { result: screenshotBuffer } = await browserLogin({
width,
height,
fullPath: createHrefFromLink({
to: '/screenshots/metrics/$metricId/content',
params: { metricId },
search: { version_number, type, width, height },
}),
request,
callback: async ({ page }) => {
const screenshotBuffer = await page.screenshot({
type,
});
return screenshotBuffer;
},
});
return screenshotBuffer;
},
});
return createScreenshotResponse({ screenshotBuffer });
} catch (error) {
console.error('Error capturing metric screenshot', error);
return new Response(
JSON.stringify({
message: 'Failed to capture screenshot',
}),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
return createScreenshotResponse({ screenshotBuffer });
} catch (error) {
console.error('Error capturing metric screenshot', error);
return new Response(
JSON.stringify({
message: 'Failed to capture screenshot',
}),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
},
},
},
});

View File

@ -1,4 +1,4 @@
import { createServerFileRoute } from '@tanstack/react-start/server';
import { createFileRoute } from '@tanstack/react-router';
import { z } from 'zod';
export const GetReportScreenshotParamsSchema = z.object({
@ -12,11 +12,17 @@ export const GetReportScreenshotQuerySchema = z.object({
type: z.enum(['png', 'jpeg']).default('png'),
});
export const ServerRoute = createServerFileRoute('/screenshots/reports/$reportId').methods({
GET: async ({ request, params }) => {
const { reportId } = GetReportScreenshotParamsSchema.parse(params);
const { version_number, width, height, type } = GetReportScreenshotQuerySchema.parse(
Object.fromEntries(new URL(request.url).searchParams)
);
export const Route = createFileRoute('/screenshots/reports/$reportId')({
server: {
handlers: {
GET: async ({ request, params }) => {
const { reportId } = GetReportScreenshotParamsSchema.parse(params);
const { version_number, width, height, type } = GetReportScreenshotQuerySchema.parse(
Object.fromEntries(new URL(request.url).searchParams)
);
return new Response('Hello, World!');
},
},
},
});

8
apps/web/src/start.ts Normal file
View File

@ -0,0 +1,8 @@
import { createStart } from '@tanstack/react-start';
import { securityMiddleware } from './middleware/global-security';
export const startInstance = createStart(() => {
return {
functionMiddleware: [securityMiddleware],
};
});

File diff suppressed because it is too large Load Diff