From b79d039a03496842357a1de9abd6d316b20e78aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20LAGACHE?= Date: Thu, 18 Jun 2026 11:01:53 +0200 Subject: [PATCH] feat(onboarding): add resolveOnboardingState server function --- src/features/onboarding/onboarding-state.ts | 121 ++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/features/onboarding/onboarding-state.ts diff --git a/src/features/onboarding/onboarding-state.ts b/src/features/onboarding/onboarding-state.ts new file mode 100644 index 00000000..49c4a689 --- /dev/null +++ b/src/features/onboarding/onboarding-state.ts @@ -0,0 +1,121 @@ +import "server-only"; + +import { currentUser } from "@/lib/auth/current-user"; +import { getOrganization } from "@/lib/auth/auth"; +import { getOrganizationAgents } from "@/db/services/agent"; +import type { AgentWith } from "@/db/schema/08_agent"; +import { getOrganizationChannels } from "@/db/services/notification-channel"; +import { getOrganizationStorageChannels } from "@/db/services/storage-channel"; +import { db } from "@/db"; +import * as drizzleDb from "@/db"; +import { eq } from "drizzle-orm"; +import { env } from "@/env.mjs"; +import type { OnboardingDatabase, OnboardingFlowData, OnboardingMeta } from "@/features/onboarding/onboarding.types"; + +export type ResolvedOnboardingState = + | { redirect: "/dashboard/home" } + | { stepId: string; flowData: Partial }; + +export async function resolveOnboardingState(): Promise { + // Check settings first (fastest exit) + const settings = await db.query.setting.findFirst(); + if (settings?.onboarding) { + return { redirect: "/dashboard/home" }; + } + + const meta: OnboardingMeta = { + passkeyEnabled: env.AUTH_PASSKEY_ENABLED === "true", + hasExistingUsers: false, + ssoProviders: [], + defaultUserMode: !!(env.AUTH_DEFAULT_USER && env.AUTH_DEFAULT_PASSWORD), + resumeStepId: "login", + }; + + // Check if any users exist + const anyUser = await db.query.user.findFirst(); + meta.hasExistingUsers = !!anyUser; + + // SSO providers from env + if (env.AUTH_OIDC_CLIENT) { + meta.ssoProviders.push({ + id: env.AUTH_OIDC_ID ?? "oidc", + label: env.AUTH_OIDC_TITLE ?? "SSO", + }); + } + + const user = await currentUser(); + if (!user) { + return { stepId: "login", flowData: { meta } }; + } + + // User exists — check org + const org = await getOrganization({}); + if (!org) { + meta.resumeStepId = "org-create"; + return { stepId: "org-create", flowData: { meta } }; + } + + const orgData = { id: org.id, name: org.name, logoDataUrl: org.logo ?? undefined }; + + // Check agents + const agents = await getOrganizationAgents(org.id); + if (!agents || agents.length === 0) { + meta.resumeStepId = "agent-create"; + return { + stepId: "agent-create", + flowData: { + meta, + org: orgData, + }, + }; + } + + const agentData = agents.map((a) => ({ + id: a.id, + name: a.name, + })); + + const databases = (agents as AgentWith[]).flatMap((a) => + (a.databases ?? []).map((d) => ({ + id: d.id, + name: d.name, + engine: (d.dbms === "postgresql" ? "postgres" : d.dbms) as OnboardingDatabase["engine"], + })) + ); + + // Check project + const project = await db.query.project.findFirst({ + where: eq(drizzleDb.schemas.project.organizationId, org.id), + }); + + if (!project) { + meta.resumeStepId = "agent-key"; + return { + stepId: "agent-key", + flowData: { + meta, + org: orgData, + agents: agentData, + databases, + }, + }; + } + + // All done + meta.resumeStepId = "finish"; + return { + stepId: "finish", + flowData: { + meta, + org: orgData, + agents: agentData, + databases, + project: { + id: project.id, + name: project.name, + description: "", + databaseIds: [], + }, + }, + }; +}