diff --git a/apps/web/src/actions/copilot/refinePrompt.ts b/apps/web/src/actions/copilot/refinePrompt.ts index 405f8b3dd..00497a5ee 100644 --- a/apps/web/src/actions/copilot/refinePrompt.ts +++ b/apps/web/src/actions/copilot/refinePrompt.ts @@ -70,6 +70,7 @@ export const refinePromptAction = authProcedure ) const sdk = await createSdk({ + workspace: ctx.workspace, apiKey: env.DATASET_GENERATOR_WORKSPACE_APIKEY, projectId: env.COPILOT_PROJECT_ID, }).then((r) => r.unwrap()) diff --git a/apps/web/src/actions/copilot/requestSuggestion.ts b/apps/web/src/actions/copilot/requestSuggestion.ts index 10e5e25f6..f5704f7ca 100644 --- a/apps/web/src/actions/copilot/requestSuggestion.ts +++ b/apps/web/src/actions/copilot/requestSuggestion.ts @@ -56,6 +56,7 @@ export const requestSuggestionAction = authProcedure ) const sdk = await createSdk({ + workspace: ctx.workspace, apiKey: env.DATASET_GENERATOR_WORKSPACE_APIKEY, projectId: env.COPILOT_PROJECT_ID, }).then((r) => r.unwrap()) diff --git a/apps/web/src/actions/datasets/generateDataset.ts b/apps/web/src/actions/datasets/generateDataset.ts index b6c36b73d..750941459 100644 --- a/apps/web/src/actions/datasets/generateDataset.ts +++ b/apps/web/src/actions/datasets/generateDataset.ts @@ -12,7 +12,7 @@ import { env } from '@latitude-data/env' import { ChainEventDto } from '@latitude-data/sdk' import slugify from '@sindresorhus/slugify' import { createSdk } from '$/app/(private)/_lib/createSdk' -import { getCurrentUser } from '$/services/auth/getCurrentUser' +import { getCurrentUserOrError } from '$/services/auth/getCurrentUser' import { createStreamableValue } from 'ai/rsc' type GenerateDatasetActionProps = { @@ -39,12 +39,14 @@ export async function generateDatasetAction({ } let response: Dataset | undefined - const { user, workspace } = await getCurrentUser() + const { user, workspace } = await getCurrentUserOrError() + const stream = createStreamableValue< { event: StreamEventTypes; data: ChainEventDto }, Error >() const sdk = await createSdk({ + workspace, apiKey: env.DATASET_GENERATOR_WORKSPACE_APIKEY, projectId: env.DATASET_GENERATOR_PROJECT_ID, __internal: { source: LogSources.Playground }, diff --git a/apps/web/src/actions/evaluations/generateSuggestedEvaluations.ts b/apps/web/src/actions/evaluations/generateSuggestedEvaluations.ts index 5398afc85..2de9dda44 100644 --- a/apps/web/src/actions/evaluations/generateSuggestedEvaluations.ts +++ b/apps/web/src/actions/evaluations/generateSuggestedEvaluations.ts @@ -19,7 +19,7 @@ export const generateSuggestedEvaluationsAction = authProcedure documentContent: z.string(), }), ) - .handler(async ({ input }) => { + .handler(async ({ input, ctx }) => { if (!env.COPILOT_WORKSPACE_API_KEY) { throw new BadRequestError('COPILOT_WORKSPACE_API_KEY is not set') } @@ -43,6 +43,7 @@ export const generateSuggestedEvaluationsAction = authProcedure } const sdk = await createSdk({ + workspace: ctx.workspace, apiKey: env.COPILOT_WORKSPACE_API_KEY, projectId: env.COPILOT_PROJECT_ID, }).then((r) => r.unwrap()) diff --git a/apps/web/src/actions/procedures/index.ts b/apps/web/src/actions/procedures/index.ts index 32512117c..d80c48412 100644 --- a/apps/web/src/actions/procedures/index.ts +++ b/apps/web/src/actions/procedures/index.ts @@ -4,14 +4,14 @@ import { ProjectsRepository, } from '@latitude-data/core/repositories' import * as Sentry from '@sentry/nextjs' -import { getCurrentUser } from '$/services/auth/getCurrentUser' +import { getCurrentUserOrError } from '$/services/auth/getCurrentUser' import { z } from 'zod' import { createServerActionProcedure } from 'zsa' export const errorHandlingProcedure = createServerActionProcedure() .onError(async (error) => { try { - const data = await getCurrentUser() + const data = await getCurrentUserOrError() Sentry.captureException(error, { user: { @@ -30,7 +30,7 @@ export const authProcedure = createServerActionProcedure( errorHandlingProcedure, ).handler(async () => { try { - const data = await getCurrentUser() + const data = await getCurrentUserOrError() return { session: data.session!, diff --git a/apps/web/src/actions/sdk/addMessagesAction.ts b/apps/web/src/actions/sdk/addMessagesAction.ts index 421c6283f..6f30852b3 100644 --- a/apps/web/src/actions/sdk/addMessagesAction.ts +++ b/apps/web/src/actions/sdk/addMessagesAction.ts @@ -8,7 +8,7 @@ import { type StreamChainResponse, } from '@latitude-data/sdk' import { createSdk } from '$/app/(private)/_lib/createSdk' -import { getCurrentUser } from '$/services/auth/getCurrentUser' +import { getCurrentUserOrError } from '$/services/auth/getCurrentUser' import { createStreamableValue, StreamableValue } from 'ai/rsc' type AddMessagesActionProps = { @@ -27,7 +27,7 @@ export async function addMessagesAction({ documentLogUuid, messages, }: AddMessagesActionProps) { - const { workspace, user } = await getCurrentUser() + const { workspace, user } = await getCurrentUserOrError() publisher.publishLater({ type: 'chatMessageRequested', @@ -40,6 +40,7 @@ export async function addMessagesAction({ }) const sdk = await createSdk({ + workspace, __internal: { source: LogSources.Playground }, }).then((r) => r.unwrap()) const stream = createStreamableValue< diff --git a/apps/web/src/actions/sdk/generateDatasetPreviewAction.ts b/apps/web/src/actions/sdk/generateDatasetPreviewAction.ts index 7855e9a62..075583177 100644 --- a/apps/web/src/actions/sdk/generateDatasetPreviewAction.ts +++ b/apps/web/src/actions/sdk/generateDatasetPreviewAction.ts @@ -5,6 +5,7 @@ import { BadRequestError } from '@latitude-data/core/lib/errors' import { env } from '@latitude-data/env' import { ChainEventDto } from '@latitude-data/sdk' import { createSdk } from '$/app/(private)/_lib/createSdk' +import { getCurrentUserOrError } from '$/services/auth/getCurrentUser' import { createStreamableValue } from 'ai/rsc' type RunDocumentActionProps = { @@ -32,7 +33,9 @@ export async function generateDatasetPreviewAction({ throw new BadRequestError('DATASET_GENERATOR_WORKSPACE_APIKEY is not set') } + const { workspace } = await getCurrentUserOrError() const sdk = await createSdk({ + workspace, apiKey: env.DATASET_GENERATOR_WORKSPACE_APIKEY, projectId: env.DATASET_GENERATOR_PROJECT_ID, __internal: { source: LogSources.Playground }, diff --git a/apps/web/src/actions/sdk/runDocumentAction.ts b/apps/web/src/actions/sdk/runDocumentAction.ts index 738d73b9e..fbca86f93 100644 --- a/apps/web/src/actions/sdk/runDocumentAction.ts +++ b/apps/web/src/actions/sdk/runDocumentAction.ts @@ -4,7 +4,7 @@ import { LogSources, StreamEventTypes } from '@latitude-data/core/browser' import { publisher } from '@latitude-data/core/events/publisher' import { Latitude, type ChainEventDto } from '@latitude-data/sdk' import { createSdk } from '$/app/(private)/_lib/createSdk' -import { getCurrentUser } from '$/services/auth/getCurrentUser' +import { getCurrentUserOrError } from '$/services/auth/getCurrentUser' import { createStreamableValue, StreamableValue } from 'ai/rsc' type RunDocumentActionProps = { @@ -27,7 +27,7 @@ export async function runDocumentAction({ commitUuid, parameters, }: RunDocumentActionProps) { - const { workspace, user } = await getCurrentUser() + const { workspace, user } = await getCurrentUserOrError() publisher.publishLater({ type: 'documentRunRequested', @@ -42,6 +42,7 @@ export async function runDocumentAction({ }) const sdk = await createSdk({ + workspace, projectId, __internal: { source: LogSources.Playground }, }).then((r) => r.unwrap()) diff --git a/apps/web/src/app/(admin)/backoffice/layout.tsx b/apps/web/src/app/(admin)/backoffice/layout.tsx index 769697556..3a02bd177 100644 --- a/apps/web/src/app/(admin)/backoffice/layout.tsx +++ b/apps/web/src/app/(admin)/backoffice/layout.tsx @@ -17,7 +17,7 @@ export default async function AdminLayout({ if (!data.session) redirect(ROUTES.root) const { user, workspace } = await getCurrentUser() - if (!user.admin) redirect(ROUTES.root) + if (!user?.admin) redirect(ROUTES.root) return ( diff --git a/apps/web/src/app/(private)/_lib/createSdk.ts b/apps/web/src/app/(private)/_lib/createSdk.ts index 6df77f683..9f2e2db74 100644 --- a/apps/web/src/app/(private)/_lib/createSdk.ts +++ b/apps/web/src/app/(private)/_lib/createSdk.ts @@ -1,15 +1,13 @@ -import { LogSources } from '@latitude-data/core/browser' +import { LogSources, Workspace } from '@latitude-data/core/browser' import { compactObject } from '@latitude-data/core/lib/compactObject' import { NotFoundError } from '@latitude-data/core/lib/errors' import { Result } from '@latitude-data/core/lib/Result' import { LatitudeApiKeysRepository } from '@latitude-data/core/repositories' import { env } from '@latitude-data/env' import { Latitude } from '@latitude-data/sdk' -import { getCurrentUser } from '$/services/auth/getCurrentUser' // NOTE: this would be a great candidate for a cache function with redis -async function getLatitudeApiKey() { - const { workspace } = await getCurrentUser() +async function getLatitudeApiKey(workspace: Workspace) { const repo = new LatitudeApiKeysRepository(workspace.id) const firstApiKey = await repo.findFirst().then((r) => r.unwrap()) @@ -23,16 +21,18 @@ async function getLatitudeApiKey() { } export async function createSdk({ + workspace, projectId, apiKey, __internal, }: { + workspace: Workspace projectId?: number apiKey?: string __internal?: { source: LogSources } -} = {}) { +}) { if (!apiKey) { - const result = await getLatitudeApiKey() + const result = await getLatitudeApiKey(workspace) if (result.error) return result apiKey = result.value.token diff --git a/apps/web/src/app/(private)/dashboard/layout.tsx b/apps/web/src/app/(private)/dashboard/layout.tsx index bd6e635f5..a091e18fa 100644 --- a/apps/web/src/app/(private)/dashboard/layout.tsx +++ b/apps/web/src/app/(private)/dashboard/layout.tsx @@ -7,10 +7,8 @@ import { } from '@latitude-data/web-ui' import { AppTabs } from '$/app/(private)/AppTabs' import { getCurrentUser } from '$/services/auth/getCurrentUser' -import { getSession } from '$/services/auth/getSession' import { ROUTES } from '$/services/routes' import Link from 'next/link' -import { redirect } from 'next/navigation' import { getActiveProjectsCached } from '../_data-access' import { ProjectsTable } from './_components/ProjectsTable' @@ -20,12 +18,8 @@ export default async function DashboardLayout({ }: Readonly<{ children: ReactNode }>) { - const data = await getSession() - if (!data.session) return redirect(ROUTES.auth.login) - const { workspace } = await getCurrentUser() const projects = await getActiveProjectsCached({ workspaceId: workspace.id }) - return ( diff --git a/apps/web/src/app/(private)/layout.tsx b/apps/web/src/app/(private)/layout.tsx index b8d3bcd95..90eeabc1d 100644 --- a/apps/web/src/app/(private)/layout.tsx +++ b/apps/web/src/app/(private)/layout.tsx @@ -26,6 +26,8 @@ export default async function PrivateLayout({ if (!data.session) return redirect(ROUTES.auth.login) const { workspace, user } = await getCurrentUser() + if (!user) return redirect(ROUTES.auth.login) + const supportIdentity = createSupportUserIdentity(user) return ( diff --git a/apps/web/src/app/(private)/settings/_components/WorkspaceApiKeys/index.tsx b/apps/web/src/app/(private)/settings/_components/WorkspaceApiKeys/index.tsx index 12e8d5c17..93f3d94e5 100644 --- a/apps/web/src/app/(private)/settings/_components/WorkspaceApiKeys/index.tsx +++ b/apps/web/src/app/(private)/settings/_components/WorkspaceApiKeys/index.tsx @@ -44,6 +44,7 @@ export default function WorkspaceApiKeys() { { let user, workspace try { - const { user: uzer, workspace: workzpace } = await getCurrentUser() + const { user: uzer, workspace: workzpace } = await getCurrentUserOrError() user = uzer workspace = workzpace } catch (error) { diff --git a/apps/web/src/services/auth/getCurrentUser.ts b/apps/web/src/services/auth/getCurrentUser.ts index 1be815036..cb6d74529 100644 --- a/apps/web/src/services/auth/getCurrentUser.ts +++ b/apps/web/src/services/auth/getCurrentUser.ts @@ -1,7 +1,10 @@ import { cache } from 'react' import { User, Workspace } from '@latitude-data/core/browser' -import { getCurrentUserFromDB } from '$/data-access' +import { + getCurrentUserFromDB, + unsafelyGetCurrentUserFromDb, +} from '$/data-access' import { Session } from 'lucia' import { getSession } from './getSession' @@ -11,25 +14,33 @@ export type SessionData = { user: User workspace: Workspace } + +/** + * This method is used in places where session has been already checked + * An example of it are inner `app/(private)` routes. Session was check in main + * layout. We don't want to throw a not found error if session is not present + */ export const getCurrentUser = cache(async () => { const sessionData = await getSession() - const result = await getCurrentUserFromDB({ userId: sessionData?.user?.id }) - const { user, workspace } = result.unwrap() + const { user, workspace } = await unsafelyGetCurrentUserFromDb({ + userId: sessionData?.user?.id, + }) return { session: sessionData.session!, - user, - workspace, + user: user!, + workspace: workspace!, } }) -export const getSafeCurrentUser = cache(async () => { +/** + * This method is used in places where session has to be check + * If something is not present it will throw an error + */ +export const getCurrentUserOrError = cache(async () => { const sessionData = await getSession() const result = await getCurrentUserFromDB({ userId: sessionData?.user?.id }) - - if (result.error) return null - - const { user, workspace } = result.value + const { user, workspace } = result.unwrap() return { session: sessionData.session!, diff --git a/packages/core/src/data-access/workspaces.ts b/packages/core/src/data-access/workspaces.ts index 1582ee763..012c799b9 100644 --- a/packages/core/src/data-access/workspaces.ts +++ b/packages/core/src/data-access/workspaces.ts @@ -28,9 +28,11 @@ export async function unsafelyFindWorkspace(id: number, db = database) { } export async function unsafelyFindWorkspacesFromUser( - userId: string, + userId: string | undefined, db = database, ) { + if (!userId) return [] + return await db .select(workspacesDtoColumns) .from(workspaces)