diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 7a4b887cbf7..4077a3f8e18 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -77,6 +77,7 @@ "@hookform/resolvers": "^5.2.2", "@lezer/highlight": "^1.2.3", "@mastra/core": "1.26.0-alpha.3", + "@paper-design/shaders-react": "^0.0.76", "@parcel/watcher": "^2.5.6", "@pierre/diffs": "1.1.3", "@radix-ui/react-dialog": "^1.1.15", diff --git a/apps/desktop/plans/20260421-v2-main-workspace-creation.md b/apps/desktop/plans/20260421-v2-main-workspace-creation.md new file mode 100644 index 00000000000..ef21df5a599 --- /dev/null +++ b/apps/desktop/plans/20260421-v2-main-workspace-creation.md @@ -0,0 +1,123 @@ +# V2 Main Workspace Creation + +## Problem + +V1 auto-creates a singleton `type='branch'` workspace per project via +`ensureMainWorkspace` (`apps/desktop/src/lib/trpc/routers/projects/projects.ts:174`), +called inline from five mutations. V2 has no equivalent — `v2_workspaces` rows only +come from `workspaceCreation`, which always produces worktrees. Users finish +`project.setup` and see an empty sidebar. + +## Goals + +- Each `(projectId, hostId)` gets one "main" workspace whose path == the host's + `repoPath`. Multiple mains per project (one per host) are allowed. +- Created automatically on project create/setup success — no type picker, no UI + step. +- Main workspaces are real v2 workspace rows but are not deletable through normal + workspace delete flows. Users can remove them from the sidebar; host/project + removal owns deleting the cloud row. + +## Design + +### Schema + +Add to `v2_workspaces` (`packages/db/src/schema/schema.ts:524`): + +```ts +type: v2WorkspaceType().notNull().default("worktree"), +``` + +Backed by a `pgEnum("v2_workspace_type", ["main", "worktree"])` for DB-level +enforcement, matching the `v2ClientType` / `v2UsersHostRole` precedent. +Partial unique index: `(projectId, hostId) WHERE type = 'main'`. + +Column name `type` over `isMain: boolean` so the workspace-creation modal's +contract is explicit — it only ever writes `'worktree'`. + +No `pinnedAt`/pinning column. Sidebar membership remains renderer-local in +`v2WorkspaceLocalState`, and main workspace auto-visibility is derived from +`type='main'`, current host, and project sidebar membership. + +### `ensureMainWorkspace` helper (host-service) + +New helper in `packages/host-service/src/trpc/router/project/`. Given +`(projectId, repoPath)`: + +1. `ensureV2Host` (reuse call from `workspace-creation.ts:372`). +2. Resolve current branch: `git symbolic-ref --short HEAD` at `repoPath`. +3. `ctx.api.v2Workspace.create.mutate({ ..., type: "main", branch, name: branch })`. + Skip if the unique index rejects — idempotent. +4. Insert local `workspaces` row (`packages/host-service/src/db/schema.ts:95`) + with `worktreePath = repoPath`. The column is named `worktreePath` but holds + any absolute checkout path; for main that's the repo root. + +Log-and-continue on failure: any cloud/local error is caught, logged, and +swallowed (the helper returns `null`). `project.setup` doesn't regress when a +transient cloud blip hits — the startup sweep backfills on the next boot. +Idempotency via the partial unique index handles duplicates on retry. + +If a main row already exists and the repo root branch changed, cloud branch +metadata is refreshed. The display name only follows the branch while it still +equals the previous branch name; user-renamed main workspaces keep their label. + +### Call sites + +1. **`project.create` success** — after cloning/importing a new cloud project and + persisting the local project row. +2. **`project.setup` success** (`packages/host-service/src/trpc/router/project/project.ts:134`) — + after `persistLocalProject` in both `clone` and `import` branches. +3. **Workspace creation/adoption/checkout preflight** — before creating or + adopting a worktree, call the helper for the local project. This keeps normal + workspace flows self-healing if setup returned before main creation completed. +4. **Host-service startup sweep** — on boot, iterate local `projects` rows and + call the helper for each. Idempotent via the unique index, so it's safe on + every boot; in practice only does work once per pre-existing project. This is + the recovery path for projects already set up before this change ships. + +### Sidebar and modal + +Workspace-creation modal continues to write worktree workspaces only. Project +setup/create responses include `mainWorkspaceId` when the helper succeeds, so the +renderer can add the main workspace to the sidebar immediately. + +Main workspaces are auto-visible when: + +- the workspace is `type='main'` +- the host is the current machine +- the project is in the sidebar +- no renderer-local hidden tombstone exists for that workspace + +Removing a main workspace from the sidebar sets +`v2WorkspaceLocalState.sidebarState.isHidden = true` instead of deleting the local +state row. This prevents auto-visibility from immediately re-adding the row and +keeps hidden-row filtering centralized through the dashboard sidebar local-state +visibility helper. + +### Delete behavior + +`v2Workspace.delete` rejects `type='main'` rows. Regular workspace cleanup/delete +flows are worktree-only. Host project removal uses the explicit +`v2Workspace.deleteMainForHost` endpoint for the repo-root main row, then removes +the local project row. Cloud project deletion still cascades through the database +like any other project-level delete. + +## Migration + +`bunx drizzle-kit generate --name="v2_workspaces_main_type"`. No SQL backfill — +the cloud doesn't know `repoPath` or current branch. Existing setups are filled +in by the startup sweep the first time the updated host-service boots. + +## Rollout + +Cloud/API before desktop (per deploy ordering). Verify: + +- Fresh `project.setup` creates exactly one main row + local row. +- Re-running `project.setup` on the same host is idempotent. +- Two hosts on one project each get their own main row. +- Startup sweep fills in a main row for a project set up pre-update, without + duplicating on subsequent boots. +- `project.remove` cleans up the main row alongside worktrees. +- Normal workspace delete and cleanup flows reject main workspaces. +- Removing a main workspace from the sidebar hides it and does not re-add it on + the next sidebar recompute. diff --git a/apps/desktop/plans/20260422-2100-v1-to-v2-port.md b/apps/desktop/plans/20260422-2100-v1-to-v2-port.md index 99313a7e924..55fd466329c 100644 --- a/apps/desktop/plans/20260422-2100-v1-to-v2-port.md +++ b/apps/desktop/plans/20260422-2100-v1-to-v2-port.md @@ -80,11 +80,13 @@ No wizard steps, no drag-and-drop, no per-row edit UI. If users want to rename a ## Duplicate project prevention -v2's schema has two uniques per org: +v2's schema only requires unique slugs per org: - `(organization_id, slug)` -- `(organization_id, lower(repo_clone_url))` — NULLs don't collide -The migration relies on both, plus explicit dedup logic: +`repo_clone_url` is intentionally not unique. Multiple v2 projects may point at +the same GitHub repository. + +The migration relies on explicit dedup logic: ### Happy-path flow per project @@ -101,10 +103,6 @@ if parsed_url: else: try v2Project.create({ orgId, name, slug, repoCloneUrl: parsed_url }) on success: record mapping, continue - on UNIQUE_VIOLATION(repo_clone_url): - # race: someone else in the org just created it - existing = v2Project.findByGitHubRemote(...) # should now hit - link to existing on UNIQUE_VIOLATION(slug): retry with slug-2, slug-3, ... (bounded: 10 attempts) else: diff --git a/apps/desktop/src/lib/trpc/routers/auth/index.ts b/apps/desktop/src/lib/trpc/routers/auth/index.ts index 8259f638d18..d622d9425b3 100644 --- a/apps/desktop/src/lib/trpc/routers/auth/index.ts +++ b/apps/desktop/src/lib/trpc/routers/auth/index.ts @@ -1,7 +1,7 @@ import crypto from "node:crypto"; import fs from "node:fs/promises"; import { AUTH_PROVIDERS } from "@superset/shared/constants"; -import { getDeviceName, getHashedDeviceId } from "@superset/shared/device-info"; +import { getHostId, getHostName } from "@superset/shared/host-info"; import { observable } from "@trpc/server/observable"; import { shell } from "electron"; import { env } from "main/env.main"; @@ -23,8 +23,8 @@ export const createAuthRouter = () => { getStoredToken: publicProcedure.query(() => loadToken()), getDeviceInfo: publicProcedure.query(() => ({ - deviceId: getHashedDeviceId(), - deviceName: getDeviceName(), + deviceId: getHostId(), + deviceName: getHostName(), })), persistToken: publicProcedure diff --git a/apps/desktop/src/lib/trpc/routers/auth/utils/crypto-storage.ts b/apps/desktop/src/lib/trpc/routers/auth/utils/crypto-storage.ts index 1bf8fc3df3b..9b2ec3ae2ad 100644 --- a/apps/desktop/src/lib/trpc/routers/auth/utils/crypto-storage.ts +++ b/apps/desktop/src/lib/trpc/routers/auth/utils/crypto-storage.ts @@ -4,7 +4,7 @@ import { randomBytes, scryptSync, } from "node:crypto"; -import { getMachineId } from "@superset/shared/device-info"; +import { getMachineId } from "@superset/shared/host-info"; const ALGORITHM = "aes-256-gcm"; const KEY_LENGTH = 32; diff --git a/apps/desktop/src/lib/trpc/routers/migration/index.ts b/apps/desktop/src/lib/trpc/routers/migration/index.ts index 6f18c5b39b6..f7d3bd2643b 100644 --- a/apps/desktop/src/lib/trpc/routers/migration/index.ts +++ b/apps/desktop/src/lib/trpc/routers/migration/index.ts @@ -5,7 +5,7 @@ import { workspaces, worktrees, } from "@superset/local-db"; -import { and, eq, isNotNull, isNull } from "drizzle-orm"; +import { eq, isNotNull, isNull } from "drizzle-orm"; import { localDb } from "main/lib/local-db"; import { z } from "zod"; import { publicProcedure, router } from "../.."; @@ -96,22 +96,5 @@ export const createMigrationRouter = () => { .where(eq(v1MigrationState.organizationId, input.organizationId)) .run(); }), - - findMigrationByOtherOrg: publicProcedure - .input(z.object({ organizationId: z.string().min(1) })) - .query(({ input }) => { - const other = localDb - .select({ organizationId: v1MigrationState.organizationId }) - .from(v1MigrationState) - .where( - and( - eq(v1MigrationState.kind, "project"), - eq(v1MigrationState.status, "success"), - ), - ) - .all() - .find((row) => row.organizationId !== input.organizationId); - return other?.organizationId ?? null; - }), }); }; diff --git a/apps/desktop/src/main/lib/host-service-coordinator.ts b/apps/desktop/src/main/lib/host-service-coordinator.ts index f793f27c0c9..ae33fbaf364 100644 --- a/apps/desktop/src/main/lib/host-service-coordinator.ts +++ b/apps/desktop/src/main/lib/host-service-coordinator.ts @@ -4,7 +4,7 @@ import { EventEmitter } from "node:events"; import * as fs from "node:fs"; import path from "node:path"; import { settings } from "@superset/local-db"; -import { getDeviceName, getHashedDeviceId } from "@superset/shared/device-info"; +import { getHostId, getHostName } from "@superset/shared/host-info"; import { app } from "electron"; import { env } from "main/env.main"; import semver from "semver"; @@ -30,8 +30,18 @@ import { localDb } from "./local-db"; import { killPersistentScope, spawnPersistent } from "./process-persistence"; import { HOOK_PROTOCOL_VERSION } from "./terminal/env"; -/** Minimum host-service version this app can work with. */ -const MIN_HOST_SERVICE_VERSION = "0.1.0"; +/** + * Minimum host-service version this app can work with. Bumping this forces + * the coordinator to kill + respawn any adopted service older than this, + * which is how we prevent the renderer from talking to a stale host-service + * that's missing newly-added procedures/params. + * + * 0.3.0: host-service registers via cloud `host.ensure` (was + * `device.ensureV2Host`); v2_hosts/v2_users_hosts/v2_workspaces use + * machineId text instead of uuid surrogates. + * 0.2.0: `workspaceCreation.adopt` gained optional `worktreePath`. + */ +const MIN_HOST_SERVICE_VERSION = "0.3.0"; export type HostServiceStatus = "starting" | "running" | "stopped"; @@ -77,7 +87,7 @@ export class HostServiceCoordinator extends EventEmitter { ReturnType >(); private scriptPath = path.join(__dirname, "host-service.js"); - private machineId = getHashedDeviceId(); + private machineId = getHostId(); private devReloadWatcher: fs.FSWatcher | null = null; async start( @@ -514,8 +524,8 @@ export class HostServiceCoordinator extends EventEmitter { ...(process.env as Record), ELECTRON_RUN_AS_NODE: "1", ORGANIZATION_ID: organizationId, - DEVICE_CLIENT_ID: getHashedDeviceId(), - DEVICE_NAME: getDeviceName(), + HOST_CLIENT_ID: getHostId(), + HOST_NAME: getHostName(), HOST_SERVICE_SECRET: secret, HOST_SERVICE_PORT: String(port), HOST_MANIFEST_DIR: organizationDir, diff --git a/apps/desktop/src/renderer/hooks/host-service/useHostTargetUrl/index.ts b/apps/desktop/src/renderer/hooks/host-service/useHostTargetUrl/index.ts new file mode 100644 index 00000000000..c6e517c7d34 --- /dev/null +++ b/apps/desktop/src/renderer/hooks/host-service/useHostTargetUrl/index.ts @@ -0,0 +1 @@ +export { useHostTargetUrl } from "./useHostTargetUrl"; diff --git a/apps/desktop/src/renderer/hooks/host-service/useHostTargetUrl/useHostTargetUrl.ts b/apps/desktop/src/renderer/hooks/host-service/useHostTargetUrl/useHostTargetUrl.ts new file mode 100644 index 00000000000..a3edd0ee1a0 --- /dev/null +++ b/apps/desktop/src/renderer/hooks/host-service/useHostTargetUrl/useHostTargetUrl.ts @@ -0,0 +1,25 @@ +import { buildHostRoutingKey } from "@superset/shared/host-routing"; +import { useMemo } from "react"; +import { env } from "renderer/env.renderer"; +import { authClient } from "renderer/lib/auth-client"; +import type { WorkspaceHostTarget } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DevicePicker/types"; +import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; + +export function useHostTargetUrl( + hostTarget: WorkspaceHostTarget | null | undefined, +): string | null { + const { activeHostUrl } = useLocalHostService(); + const { data: session } = authClient.useSession(); + const activeOrganizationId = session?.session?.activeOrganizationId ?? null; + + return useMemo(() => { + if (!hostTarget) return null; + if (hostTarget.kind === "local") return activeHostUrl; + if (!activeOrganizationId) return null; + const routingKey = buildHostRoutingKey( + activeOrganizationId, + hostTarget.hostId, + ); + return `${env.RELAY_URL}/hosts/${routingKey}`; + }, [hostTarget, activeOrganizationId, activeHostUrl]); +} diff --git a/apps/desktop/src/renderer/hooks/host-service/useWorkspaceHostUrl/useWorkspaceHostUrl.ts b/apps/desktop/src/renderer/hooks/host-service/useWorkspaceHostUrl/useWorkspaceHostUrl.ts index a9539f56cba..bd957947f07 100644 --- a/apps/desktop/src/renderer/hooks/host-service/useWorkspaceHostUrl/useWorkspaceHostUrl.ts +++ b/apps/desktop/src/renderer/hooks/host-service/useWorkspaceHostUrl/useWorkspaceHostUrl.ts @@ -1,3 +1,4 @@ +import { buildHostRoutingKey } from "@superset/shared/host-routing"; import { eq } from "@tanstack/db"; import { useLiveQuery } from "@tanstack/react-db"; import { useMemo } from "react"; @@ -18,10 +19,11 @@ export function useWorkspaceHostUrl(workspaceId: string | null): string | null { q .from({ workspaces: collections.v2Workspaces }) .leftJoin({ hosts: collections.v2Hosts }, ({ workspaces, hosts }) => - eq(workspaces.hostId, hosts.id), + eq(workspaces.hostId, hosts.machineId), ) .where(({ workspaces }) => eq(workspaces.id, workspaceId ?? "")) .select(({ workspaces, hosts }) => ({ + organizationId: workspaces.organizationId, hostId: workspaces.hostId, hostMachineId: hosts?.machineId ?? null, })), @@ -33,6 +35,7 @@ export function useWorkspaceHostUrl(workspaceId: string | null): string | null { return useMemo(() => { if (!match) return null; if (match.hostMachineId === machineId) return activeHostUrl; - return `${env.RELAY_URL}/hosts/${match.hostId}`; + const routingKey = buildHostRoutingKey(match.organizationId, match.hostId); + return `${env.RELAY_URL}/hosts/${routingKey}`; }, [match, machineId, activeHostUrl]); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/hooks/useProjectFileSearch/useProjectFileSearch.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/hooks/useProjectFileSearch/useProjectFileSearch.ts index 0052c1df0ad..116930ee6be 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/hooks/useProjectFileSearch/useProjectFileSearch.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/hooks/useProjectFileSearch/useProjectFileSearch.ts @@ -1,9 +1,8 @@ import { useCallback } from "react"; import type { FileMentionSearchFn } from "renderer/components/MarkdownEditor/components/FileMention"; -import { env } from "renderer/env.renderer"; +import { useHostTargetUrl } from "renderer/hooks/host-service/useHostTargetUrl"; import { getHostServiceClientByUrl } from "renderer/lib/host-service-client"; import type { WorkspaceHostTarget } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DevicePicker/types"; -import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; const SEARCH_LIMIT = 15; @@ -14,12 +13,7 @@ export function useProjectFileSearch({ hostTarget: WorkspaceHostTarget; projectId: string | null; }): FileMentionSearchFn | undefined { - const { activeHostUrl } = useLocalHostService(); - - const hostUrl = - hostTarget.kind === "local" - ? activeHostUrl - : `${env.RELAY_URL}/hosts/${hostTarget.hostId}`; + const hostUrl = useHostTargetUrl(hostTarget); return useCallback( async (query) => { diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/page.tsx index 5696520e8ff..a395f53eb3d 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/page.tsx @@ -136,7 +136,7 @@ function AutomationsPage() { (q) => q .from({ h: collections.v2Hosts }) - .select(({ h }) => ({ id: h.id, name: h.name })), + .select(({ h }) => ({ machineId: h.machineId, name: h.name })), [collections.v2Hosts], ); @@ -167,7 +167,10 @@ function AutomationsPage() { const hostsById = useMemo( () => new Map( - (hostRows as Pick[]).map((h) => [h.id, h]), + (hostRows as Pick[]).map((h) => [ + h.machineId, + h, + ]), ), [hostRows], ); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/components/NewProjectModal/NewProjectModal.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/components/NewProjectModal/NewProjectModal.tsx index 4cf0ced1348..feff913ccbd 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/components/NewProjectModal/NewProjectModal.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/components/NewProjectModal/NewProjectModal.tsx @@ -27,7 +27,11 @@ type NewProjectMode = "clone" | "empty" | "template"; interface NewProjectModalProps { open: boolean; onOpenChange: (open: boolean) => void; - onSuccess?: (result: { projectId: string; repoPath: string }) => void; + onSuccess?: (result: { + projectId: string; + repoPath: string; + mainWorkspaceId: string | null; + }) => void; onError?: (message: string) => void; } @@ -72,7 +76,8 @@ export function NewProjectModal({ onError, }: NewProjectModalProps) { const { activeHostUrl } = useLocalHostService(); - const { ensureProjectInSidebar } = useDashboardSidebarState(); + const { ensureProjectInSidebar, ensureWorkspaceInSidebar } = + useDashboardSidebarState(); const selectDirectory = electronTrpc.window.selectDirectory.useMutation(); const { data: homeDir } = electronTrpc.window.getHomeDir.useQuery(); @@ -142,7 +147,11 @@ export function NewProjectModal({ name, mode: { kind: "clone", parentDir: trimmedParent, url: trimmedUrl }, }); - ensureProjectInSidebar(result.projectId); + if (result.mainWorkspaceId) { + ensureWorkspaceInSidebar(result.mainWorkspaceId, result.projectId); + } else { + ensureProjectInSidebar(result.projectId); + } onSuccess?.(result); reset(); onOpenChange(false); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport/useFolderFirstImport.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport/useFolderFirstImport.ts index b44b1bd88fc..794df2538b8 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport/useFolderFirstImport.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/AddRepositoryModals/hooks/useFolderFirstImport/useFolderFirstImport.ts @@ -5,31 +5,49 @@ import { getBaseName } from "renderer/lib/pathBasename"; import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/useDashboardSidebarState"; import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; +interface ProjectSetupResult { + projectId: string; + repoPath: string; + mainWorkspaceId: string | null; +} + export interface UseFolderFirstImportResult { start: () => Promise; } +interface MatchingProject { + id: string; + name: string; +} + export function useFolderFirstImport(options?: { - onSuccess?: (result: { projectId: string; repoPath: string }) => void; + onSuccess?: (result: ProjectSetupResult) => void; onError?: (message: string) => void; + onMultipleProjects?: (input: { candidates: MatchingProject[] }) => void; }): UseFolderFirstImportResult { const { activeHostUrl } = useLocalHostService(); - const { ensureProjectInSidebar } = useDashboardSidebarState(); + const { ensureProjectInSidebar, ensureWorkspaceInSidebar } = + useDashboardSidebarState(); const selectDirectory = electronTrpc.window.selectDirectory.useMutation(); + const { onError, onMultipleProjects, onSuccess } = options ?? {}; const reportSuccess = useCallback( - (result: { projectId: string; repoPath: string }) => { - ensureProjectInSidebar(result.projectId); - options?.onSuccess?.(result); + (result: ProjectSetupResult) => { + if (result.mainWorkspaceId) { + ensureWorkspaceInSidebar(result.mainWorkspaceId, result.projectId); + } else { + ensureProjectInSidebar(result.projectId); + } + onSuccess?.(result); }, - [ensureProjectInSidebar, options], + [ensureProjectInSidebar, ensureWorkspaceInSidebar, onSuccess], ); const reportError = useCallback( (message: string) => { - options?.onError?.(message); + onError?.(message); }, - [options], + [onError], ); const start = useCallback(async () => { @@ -51,7 +69,7 @@ export function useFolderFirstImport(options?: { } const client = getHostServiceClientByUrl(activeHostUrl); - let candidates: Array<{ id: string }>; + let candidates: MatchingProject[]; try { const response = await client.project.findByPath.query({ repoPath }); candidates = response.candidates; @@ -62,12 +80,13 @@ export function useFolderFirstImport(options?: { const [only, ...rest] = candidates; if (rest.length > 0) { - // Unreachable given single-org findByGitHubRemote + the unique - // index on (organizationId, lower(repoCloneUrl)). Surface loudly - // if we ever hit it — means the invariants broke. - reportError( - `Multiple matching projects returned (${candidates.length}) — please report this`, - ); + if (onMultipleProjects) { + onMultipleProjects({ candidates }); + } else { + reportError( + `Multiple projects use this repository (${candidates.length}). Open the project you want from settings to set it up on this device.`, + ); + } return; } @@ -77,7 +96,11 @@ export function useFolderFirstImport(options?: { projectId: only.id, mode: { kind: "import", repoPath }, }); - reportSuccess({ projectId: only.id, repoPath: result.repoPath }); + reportSuccess({ + projectId: only.id, + repoPath: result.repoPath, + mainWorkspaceId: result.mainWorkspaceId, + }); } else { const result = await client.project.create.mutate({ name: getBaseName(repoPath), @@ -88,7 +111,13 @@ export function useFolderFirstImport(options?: { } catch (err) { reportError(err instanceof Error ? err.message : String(err)); } - }, [activeHostUrl, reportError, reportSuccess, selectDirectory]); + }, [ + activeHostUrl, + onMultipleProjects, + reportError, + reportSuccess, + selectDirectory, + ]); return { start }; } diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx index 024de48a058..b42dd90380d 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHeader/DashboardSidebarHeader.tsx @@ -38,6 +38,7 @@ export function DashboardSidebarHeader({ }: DashboardSidebarHeaderProps) { const openModal = useOpenNewWorkspaceModal(); const openNewProject = useOpenNewProjectModal(); + const navigate = useNavigate(); const folderImport = useFolderFirstImport({ onSuccess: () => { toast.success("Project ready — open it from the sidebar."); @@ -45,9 +46,17 @@ export function DashboardSidebarHeader({ onError: (message) => { toast.error(`Import failed: ${message}`); }, + onMultipleProjects: ({ candidates }) => { + toast.error("Import failed", { + description: `Multiple projects use this repository (${candidates.length}). Choose the project in settings to set it up on this device.`, + action: { + label: "Open Projects", + onClick: () => navigate({ to: "/settings/projects" }), + }, + }); + }, }); const shortcutText = useHotkeyDisplay("NEW_WORKSPACE").text; - const navigate = useNavigate(); const matchRoute = useMatchRoute(); const { gateFeature } = usePaywall(); const isWorkspacesListOpen = !!matchRoute({ to: "/v2-workspaces" }); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarPortsList/hooks/useDashboardSidebarPortsData/useDashboardSidebarPortsData.test.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarPortsList/hooks/useDashboardSidebarPortsData/useDashboardSidebarPortsData.test.ts index 5c16040b339..117817de03a 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarPortsList/hooks/useDashboardSidebarPortsData/useDashboardSidebarPortsData.test.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarPortsList/hooks/useDashboardSidebarPortsData/useDashboardSidebarPortsData.test.ts @@ -154,8 +154,16 @@ describe("deriveHostPortQueryTargets", () => { const targets = deriveHostPortQueryTargets({ activeHostUrl: "http://127.0.0.1:4567", hosts: [ - { id: "remote-host", isOnline: true, machineId: "remote-machine" }, - { id: "local-host", isOnline: true, machineId: "local-machine" }, + { + organizationId: "org-1", + machineId: "remote-machine", + isOnline: true, + }, + { + organizationId: "org-1", + machineId: "local-machine", + isOnline: true, + }, ], machineId: "local-machine", relayUrl: "https://relay.example.com", @@ -163,19 +171,19 @@ describe("deriveHostPortQueryTargets", () => { { id: "workspace-b", name: "Workspace B", - hostId: "local-host", + hostId: "local-machine", hostMachineId: "local-machine", }, { id: "workspace-a", name: "Workspace A", - hostId: "local-host", + hostId: "local-machine", hostMachineId: "local-machine", }, { id: "workspace-c", name: "Workspace C", - hostId: "remote-host", + hostId: "remote-machine", hostMachineId: "remote-machine", }, ], @@ -183,13 +191,13 @@ describe("deriveHostPortQueryTargets", () => { expect(targets).toEqual([ { - id: "remote-host", + machineId: "remote-machine", hostType: "remote-device", - hostUrl: "https://relay.example.com/hosts/remote-host", + hostUrl: "https://relay.example.com/hosts/org-1:remote-machine", workspaceIds: ["workspace-c"], }, { - id: "local-host", + machineId: "local-machine", hostType: "local-device", hostUrl: "http://127.0.0.1:4567", workspaceIds: ["workspace-a", "workspace-b"], @@ -201,8 +209,16 @@ describe("deriveHostPortQueryTargets", () => { const targets = deriveHostPortQueryTargets({ activeHostUrl: null, hosts: [ - { id: "offline-host", isOnline: false, machineId: "remote-machine" }, - { id: "local-host", isOnline: true, machineId: "local-machine" }, + { + organizationId: "org-1", + machineId: "remote-machine", + isOnline: false, + }, + { + organizationId: "org-1", + machineId: "local-machine", + isOnline: true, + }, ], machineId: "local-machine", relayUrl: "https://relay.example.com", @@ -210,13 +226,13 @@ describe("deriveHostPortQueryTargets", () => { { id: "workspace-remote", name: "Remote", - hostId: "offline-host", + hostId: "remote-machine", hostMachineId: "remote-machine", }, { id: "workspace-local", name: "Local", - hostId: "local-host", + hostId: "local-machine", hostMachineId: "local-machine", }, ], diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarPortsList/hooks/useDashboardSidebarPortsData/useDashboardSidebarPortsData.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarPortsList/hooks/useDashboardSidebarPortsData/useDashboardSidebarPortsData.ts index 2af64cf4899..0992c72266d 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarPortsList/hooks/useDashboardSidebarPortsData/useDashboardSidebarPortsData.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarPortsList/hooks/useDashboardSidebarPortsData/useDashboardSidebarPortsData.ts @@ -41,9 +41,9 @@ export function useDashboardSidebarPortsData(): { const { data: hosts = [] } = useLiveQuery( (q) => q.from({ hosts: collections.v2Hosts }).select(({ hosts }) => ({ - id: hosts.id, - isOnline: hosts.isOnline, + organizationId: hosts.organizationId, machineId: hosts.machineId, + isOnline: hosts.isOnline, })), [collections], ); @@ -53,7 +53,7 @@ export function useDashboardSidebarPortsData(): { q .from({ workspaces: collections.v2Workspaces }) .leftJoin({ hosts: collections.v2Hosts }, ({ workspaces, hosts }) => - eq(workspaces.hostId, hosts.id), + eq(workspaces.hostId, hosts.machineId), ) .select(({ workspaces, hosts }) => ({ id: workspaces.id, @@ -86,7 +86,7 @@ export function useDashboardSidebarPortsData(): { workspaceIds: host.workspaceIds, }); return { - hostId: host.id, + hostId: host.machineId, hostType: host.hostType, hostUrl: host.hostUrl, ports, @@ -110,7 +110,7 @@ export function useDashboardSidebarPortsData(): { getHostPortsQueryKey(host), (result) => applyPortEventsToHostPortsResult(result, events, { - hostId: host.id, + hostId: host.machineId, hostType: host.hostType, hostUrl: host.hostUrl, }), @@ -175,7 +175,7 @@ export function useDashboardSidebarPortsData(): { if (!host) return []; return [ { - hostId: host.id, + hostId: host.machineId, hostType: host.hostType, message: query.error instanceof Error diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarPortsList/hooks/useDashboardSidebarPortsData/useDashboardSidebarPortsData.utils.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarPortsList/hooks/useDashboardSidebarPortsData/useDashboardSidebarPortsData.utils.ts index 3fbf9df26d8..3bfffbc160a 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarPortsList/hooks/useDashboardSidebarPortsData/useDashboardSidebarPortsData.utils.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarPortsList/hooks/useDashboardSidebarPortsData/useDashboardSidebarPortsData.utils.ts @@ -1,3 +1,4 @@ +import { buildHostRoutingKey } from "@superset/shared/host-routing"; import type { PortChangedPayload } from "@superset/workspace-client"; import type { DetectedPort } from "shared/types"; import type { DashboardSidebarWorkspaceHostType } from "../../../../types"; @@ -38,16 +39,16 @@ type HostPortsMetadata = Pick< >; export interface HostPortsQueryTarget { - id: string; + machineId: string; hostType: DashboardSidebarWorkspaceHostType; hostUrl: string; workspaceIds: string[]; } export interface DashboardSidebarHostRow { - id: string; + organizationId: string; + machineId: string; isOnline: boolean; - machineId: string | null | undefined; } export interface DashboardSidebarWorkspaceRow { @@ -62,7 +63,7 @@ export function getHostPortsQueryKey(host: HostPortsQueryTarget) { "host-service", "ports", "getAll", - host.id, + host.machineId, host.hostUrl, host.workspaceIds, ] as const; @@ -139,18 +140,20 @@ export function deriveHostPortQueryTargets({ } return hosts.flatMap((host) => { - const workspaceIds = workspaceIdsByHostId.get(host.id); + const workspaceIds = workspaceIdsByHostId.get(host.machineId); if (!workspaceIds || workspaceIds.length === 0) return []; const isLocal = host.machineId === machineId; if (!isLocal && !host.isOnline) return []; - const hostUrl = isLocal ? activeHostUrl : `${relayUrl}/hosts/${host.id}`; + const hostUrl = isLocal + ? activeHostUrl + : `${relayUrl}/hosts/${buildHostRoutingKey(host.organizationId, host.machineId)}`; if (!hostUrl) return []; return [ { - id: host.id, + machineId: host.machineId, hostType: isLocal ? ("local-device" as const) : ("remote-device" as const), diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx index 85533f43c23..0878c23cd53 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx @@ -1,5 +1,6 @@ import { useNavigate } from "@tanstack/react-router"; import { useDiffStats } from "renderer/hooks/host-service/useDiffStats"; +import { useDeletingWorkspaces } from "renderer/routes/_authenticated/providers/DeletingWorkspacesProvider"; import { useV2WorkspaceNotificationStatus } from "renderer/stores/v2-notifications"; import type { DashboardSidebarWorkspace } from "../../types"; import { DashboardSidebarDeleteDialog } from "../DashboardSidebarDeleteDialog"; @@ -34,23 +35,24 @@ export function DashboardSidebarWorkspaceItem({ branch, creationStatus, } = workspace; + const isMainWorkspace = workspace.type === "main"; const diffStats = useDiffStats(id); const workspaceStatus = useV2WorkspaceNotificationStatus(id); const { cancelRename, handleClick, - handleCopyBranchName, handleCopyPath, + handleCopyBranchName, handleCreateSection, handleDeleted, handleOpenInFinder, + handleRemoveFromSidebar, handleToggleUnread, isActive, isDeleteDialogOpen, isUnread, isRenaming, moveWorkspaceToSection, - removeWorkspaceFromSidebar, renameValue, setIsDeleteDialogOpen, setRenameValue, @@ -61,10 +63,14 @@ export function DashboardSidebarWorkspaceItem({ projectId, workspaceName: name, branch, + isMainWorkspace, }); const navigate = useNavigate(); const isPending = !!creationStatus; + // Keep the delete dialog outside the hidden wrapper below — the destroy + // flow reopens it into an error pane on conflict/teardown-failed. + const isDeleting = useDeletingWorkspaces().isDeleting(id); const handlePendingClick = isPending ? () => { void navigate({ @@ -101,40 +107,44 @@ export function DashboardSidebarWorkspaceItem({ return ( <> - {isPending ? ( - content - ) : ( - - } - isLocalWorkspace={hostType === "local-device"} - onCreateSection={handleCreateSection} - onMoveToSection={(targetSectionId) => - moveWorkspaceToSection(id, projectId, targetSectionId) - } - onOpenInFinder={handleOpenInFinder} - onCopyPath={handleCopyPath} - onCopyBranchName={handleCopyBranchName} - onRemoveFromSidebar={() => removeWorkspaceFromSidebar(id)} - onRename={startRename} - onDelete={() => setIsDeleteDialogOpen(true)} - onToggleUnread={handleToggleUnread} - > - {content} - - )} + - {!isPending && ( + {!isPending && !isMainWorkspace && ( setIsDeleteDialogOpen(true)} + onDeleteClick={ + isMainWorkspace + ? handleRemoveFromSidebar + : () => setIsDeleteDialogOpen(true) + } onRenameValueChange={setRenameValue} onSubmitRename={submitRename} onCancelRename={cancelRename} @@ -167,40 +181,44 @@ export function DashboardSidebarWorkspaceItem({ return ( <> - {isPending ? ( - expandedContent - ) : ( - - } - onCreateSection={handleCreateSection} - onMoveToSection={(targetSectionId) => - moveWorkspaceToSection(id, projectId, targetSectionId) - } - isLocalWorkspace={hostType === "local-device"} - onOpenInFinder={handleOpenInFinder} - onCopyPath={handleCopyPath} - onCopyBranchName={handleCopyBranchName} - onRemoveFromSidebar={() => removeWorkspaceFromSidebar(id)} - onRename={startRename} - onDelete={() => setIsDeleteDialogOpen(true)} - onToggleUnread={handleToggleUnread} - > - {expandedContent} - - )} + - {!isPending && ( + {!isPending && !isMainWorkspace && ( getCreationStatusText(creationStatus), [creationStatus], ); + const isMainWorkspace = workspace.type === "main"; + const workspaceKindTitle = isMainWorkspace + ? "Main workspace" + : "Worktree workspace"; + const workspaceKindDescription = isMainWorkspace + ? "Uses the repository checkout on this host" + : "Isolated copy for parallel development"; + const closeLabel = isMainWorkspace + ? "Remove from sidebar" + : "Close workspace"; return ( // biome-ignore lint/a11y/noStaticElementInteractions: Mirrors the legacy sidebar row UI, which includes nested action buttons. @@ -192,22 +202,26 @@ export const DashboardSidebarExpandedWorkspaceRow = forwardRef< ) : ( <>

- {hostType === "local-device" - ? "Local workspace" - : hostType === "remote-device" - ? hostIsOnline === false - ? "Remote workspace — device offline" - : "Remote workspace" - : "Cloud workspace"} + {isMainWorkspace + ? workspaceKindTitle + : hostType === "local-device" + ? "Local workspace" + : hostType === "remote-device" + ? hostIsOnline === false + ? "Remote workspace — device offline" + : "Remote workspace" + : "Cloud workspace"}

- {hostType === "local-device" - ? "Running on this device" - : hostType === "remote-device" - ? hostIsOnline === false - ? "The associated device isn't reachable right now" - : "Running on a paired device" - : "Hosted in the cloud"} + {isMainWorkspace + ? workspaceKindDescription + : hostType === "local-device" + ? "Running on this device" + : hostType === "remote-device" + ? hostIsOnline === false + ? "The associated device isn't reachable right now" + : "Running on a paired device" + : "Hosted in the cloud"}

)} @@ -273,15 +287,19 @@ export const DashboardSidebarExpandedWorkspaceRow = forwardRef< onDeleteClick(); }} className="flex items-center justify-center text-muted-foreground hover:text-foreground" - aria-label="Close workspace" + aria-label={closeLabel} > diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx index e1b4dcad459..8c74ff2ede4 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx @@ -45,7 +45,7 @@ interface DashboardSidebarWorkspaceContextMenuProps { onCopyBranchName: () => void; onRemoveFromSidebar: () => void; onRename: () => void; - onDelete: () => void; + onDelete?: () => void; onToggleUnread: () => void; children: React.ReactNode; } @@ -168,13 +168,15 @@ export function DashboardSidebarWorkspaceContextMenu({ Remove from Sidebar - - - Delete - + {onDelete ? ( + + + Delete + + ) : null} ); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/hooks/useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/hooks/useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts index f79b7b6fb60..90455b6a8a9 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/hooks/useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/hooks/useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts @@ -1,4 +1,3 @@ -import { getActiveIdAfterRemoval } from "@superset/panes"; import { toast } from "@superset/ui/sonner"; import { useMatchRoute, useNavigate } from "@tanstack/react-router"; import { useState } from "react"; @@ -6,11 +5,9 @@ import { useCopyToClipboard } from "renderer/hooks/useCopyToClipboard"; import { getHostServiceClientByUrl } from "renderer/lib/host-service-client"; import { electronTrpcClient } from "renderer/lib/trpc-client"; import { useDashboardSidebarSectionRename } from "renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarSectionRenameContext"; -import { getFlattenedV2WorkspaceIds } from "renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/getFlattenedV2WorkspaceIds"; -import { navigateToV2Workspace } from "renderer/routes/_authenticated/_dashboard/utils/workspace-navigation"; +import { useNavigateAwayFromWorkspace } from "renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useNavigateAwayFromWorkspace"; import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/useDashboardSidebarState"; import { useOptimisticCollectionActions } from "renderer/routes/_authenticated/hooks/useOptimisticCollectionActions"; -import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; import { useV2NotificationStore, @@ -22,6 +19,7 @@ interface UseDashboardSidebarWorkspaceItemActionsOptions { projectId: string; workspaceName: string; branch: string; + isMainWorkspace?: boolean; } export function useDashboardSidebarWorkspaceItemActions({ @@ -29,10 +27,11 @@ export function useDashboardSidebarWorkspaceItemActions({ projectId, workspaceName, branch, + isMainWorkspace = false, }: UseDashboardSidebarWorkspaceItemActionsOptions) { const navigate = useNavigate(); const matchRoute = useMatchRoute(); - const collections = useCollections(); + const navigateAway = useNavigateAwayFromWorkspace(); const { activeHostUrl } = useLocalHostService(); const { copyToClipboard } = useCopyToClipboard(); const { v2Workspaces: workspaceActions } = useOptimisticCollectionActions(); @@ -42,8 +41,12 @@ export function useDashboardSidebarWorkspaceItemActions({ ); const setManualUnread = useV2NotificationStore((s) => s.setManualUnread); const isUnread = useV2WorkspaceIsUnread(workspaceId); - const { createSection, moveWorkspaceToSection, removeWorkspaceFromSidebar } = - useDashboardSidebarState(); + const { + createSection, + hideWorkspaceInSidebar, + moveWorkspaceToSection, + removeWorkspaceFromSidebar, + } = useDashboardSidebarState(); const [isRenaming, setIsRenaming] = useState(false); const [renameValue, setRenameValue] = useState(workspaceName); @@ -81,28 +84,17 @@ export function useDashboardSidebarWorkspaceItemActions({ workspaceActions.renameWorkspace(workspaceId, trimmed); }; - /** - * Runs after `workspaceCleanup.destroy` succeeds. Removes the row from - * the sidebar and, if we were viewing the deleted workspace, navigates - * to the next sibling or home. - */ const handleDeleted = () => { - const focusTargetId = isActive - ? getActiveIdAfterRemoval( - getFlattenedV2WorkspaceIds(collections), - workspaceId, - workspaceId, - ) - : null; - removeWorkspaceFromSidebar(workspaceId); + }; - if (!isActive) return; - if (focusTargetId) { - void navigateToV2Workspace(focusTargetId, navigate); - } else { - void navigate({ to: "/" }); + const handleRemoveFromSidebar = () => { + navigateAway(workspaceId); + if (isMainWorkspace) { + hideWorkspaceInSidebar(workspaceId, projectId); + return; } + removeWorkspaceFromSidebar(workspaceId); }; const handleCreateSection = () => { @@ -182,13 +174,13 @@ export function useDashboardSidebarWorkspaceItemActions({ handleCreateSection, handleDeleted, handleOpenInFinder, + handleRemoveFromSidebar, handleToggleUnread, isActive, isDeleteDialogOpen, isRenaming, isUnread, moveWorkspaceToSection, - removeWorkspaceFromSidebar, renameValue, setIsDeleteDialogOpen, setRenameValue, diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts index 7146ad215ce..1bbe24c88ce 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts @@ -7,6 +7,7 @@ import { authClient } from "renderer/lib/auth-client"; import { getHostServiceClientByUrl } from "renderer/lib/host-service-client"; import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/useDashboardSidebarState"; import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; +import { getVisibleSidebarWorkspaces } from "renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal"; import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; import { MOCK_ORG_ID } from "shared/constants"; import type { @@ -19,6 +20,7 @@ import type { // Sits above every real workspace so the pending row lines up with the real one, // which is inserted via getPrependTabOrder. const PENDING_WORKSPACE_TAB_ORDER = Number.MIN_SAFE_INTEGER; +const MAIN_WORKSPACE_TAB_ORDER = Number.MIN_SAFE_INTEGER; type LocalPullRequest = DashboardSidebarWorkspace["pullRequest"]; type PullRequestWorkspaceRow = { @@ -221,7 +223,7 @@ export function useDashboardSidebarData() { [collections], ); - const { data: sidebarWorkspaces = [] } = useLiveQuery( + const { data: rawSidebarWorkspaces = [] } = useLiveQuery( (q) => q .from({ sidebarWorkspaces: collections.v2WorkspaceLocalState }) @@ -231,7 +233,7 @@ export function useDashboardSidebarData() { eq(sidebarWorkspaces.workspaceId, workspaces.id), ) .leftJoin({ hosts: collections.v2Hosts }, ({ workspaces, hosts }) => - eq(workspaces.hostId, hosts.id), + eq(workspaces.hostId, hosts.machineId), ) .orderBy( ({ sidebarWorkspaces }) => sidebarWorkspaces.sidebarState.tabOrder, @@ -242,6 +244,7 @@ export function useDashboardSidebarData() { projectId: sidebarWorkspaces.sidebarState.projectId, hostId: workspaces.hostId, hostMachineId: hosts?.machineId ?? null, + type: workspaces.type, hostIsOnline: hosts?.isOnline ?? null, name: workspaces.name, branch: workspaces.branch, @@ -249,13 +252,70 @@ export function useDashboardSidebarData() { updatedAt: workspaces.updatedAt, tabOrder: sidebarWorkspaces.sidebarState.tabOrder, sectionId: sidebarWorkspaces.sidebarState.sectionId, + isHidden: sidebarWorkspaces.sidebarState.isHidden, })), [collections], ); + const sidebarWorkspaces = useMemo( + () => getVisibleSidebarWorkspaces(rawSidebarWorkspaces), + [rawSidebarWorkspaces], + ); + + const localStateWorkspaceIds = useMemo( + () => new Set(rawSidebarWorkspaces.map((workspace) => workspace.id)), + [rawSidebarWorkspaces], + ); + + const { data: localMainWorkspaces = [] } = useLiveQuery( + (q) => + q + .from({ workspaces: collections.v2Workspaces }) + .leftJoin({ hosts: collections.v2Hosts }, ({ workspaces, hosts }) => + eq(workspaces.hostId, hosts.machineId), + ) + .where(({ workspaces }) => eq(workspaces.type, "main")) + .select(({ workspaces, hosts }) => ({ + id: workspaces.id, + projectId: workspaces.projectId, + hostId: workspaces.hostId, + hostMachineId: hosts?.machineId ?? null, + type: workspaces.type, + hostIsOnline: hosts?.isOnline ?? null, + name: workspaces.name, + branch: workspaces.branch, + createdAt: workspaces.createdAt, + updatedAt: workspaces.updatedAt, + tabOrder: MAIN_WORKSPACE_TAB_ORDER, + sectionId: null as string | null, + })), + [collections], + ); + + const visibleSidebarWorkspaces = useMemo(() => { + const sidebarProjectIds = new Set( + sidebarProjects.map((project) => project.id), + ); + const autoLocalMainWorkspaces = localMainWorkspaces.filter( + (workspace) => + !localStateWorkspaceIds.has(workspace.id) && + workspace.hostMachineId != null && + workspace.hostMachineId === machineId && + sidebarProjectIds.has(workspace.projectId), + ); + + return [...autoLocalMainWorkspaces, ...sidebarWorkspaces]; + }, [ + localMainWorkspaces, + localStateWorkspaceIds, + machineId, + sidebarProjects, + sidebarWorkspaces, + ]); + const computedLocalWorkspaceIds = useMemo( () => - sidebarWorkspaces + visibleSidebarWorkspaces .filter( (workspace) => workspace.hostMachineId != null && @@ -263,7 +323,7 @@ export function useDashboardSidebarData() { ) .map((workspace) => workspace.id) .sort(), - [machineId, sidebarWorkspaces], + [machineId, visibleSidebarWorkspaces], ); const localWorkspaceIds = useStableStringArray(computedLocalWorkspaceIds); @@ -339,7 +399,7 @@ export function useDashboardSidebarData() { }); } - for (const workspace of sidebarWorkspaces) { + for (const workspace of visibleSidebarWorkspaces) { const project = projectsById.get(workspace.projectId); if (!project) continue; @@ -355,6 +415,7 @@ export function useDashboardSidebarData() { projectId: workspace.projectId, hostId: workspace.hostId, hostType, + type: workspace.type, hostIsOnline: hostType === "remote-device" ? (workspace.hostIsOnline ?? null) @@ -410,6 +471,7 @@ export function useDashboardSidebarData() { projectId: pw.projectId, hostId: "", hostType: "local-device", + type: "worktree", hostIsOnline: null, accentColor: null, name: pw.name, @@ -478,7 +540,7 @@ export function useDashboardSidebarData() { pendingWorkspaces, sidebarProjects, sidebarSections, - sidebarWorkspaces, + visibleSidebarWorkspaces, ]); const groups = useStableDashboardSidebarProjects(computedGroups); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/types.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/types.ts index e2ff2406ec2..1f5f40bbded 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/types.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/types.ts @@ -3,6 +3,8 @@ export type DashboardSidebarWorkspaceHostType = | "remote-device" | "cloud"; +export type DashboardSidebarWorkspaceType = "main" | "worktree"; + export interface DashboardSidebarWorkspacePullRequestCheck { name: string; status: "success" | "failure" | "pending" | "skipped" | "cancelled"; @@ -25,6 +27,7 @@ export interface DashboardSidebarWorkspace { projectId: string; hostId: string; hostType: DashboardSidebarWorkspaceHostType; + type: DashboardSidebarWorkspaceType; hostIsOnline: boolean | null; accentColor: string | null; name: string; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/getFlattenedV2WorkspaceIds/getFlattenedV2WorkspaceIds.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/getFlattenedV2WorkspaceIds/getFlattenedV2WorkspaceIds.ts index 04a8aad2735..6bb07fc8690 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/getFlattenedV2WorkspaceIds/getFlattenedV2WorkspaceIds.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/utils/getFlattenedV2WorkspaceIds/getFlattenedV2WorkspaceIds.ts @@ -1,4 +1,5 @@ import type { AppCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider/collections"; +import { getVisibleSidebarWorkspaces } from "renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal"; type TopLevelItem = | { kind: "workspace"; tabOrder: number; workspaceId: string } @@ -17,11 +18,12 @@ export function getFlattenedV2WorkspaceIds( const allWorkspaces = Array.from( collections.v2WorkspaceLocalState.state.values(), ); + const visibleWorkspaces = getVisibleSidebarWorkspaces(allWorkspaces); const result: string[] = []; for (const project of projects) { - const projectWorkspaces = allWorkspaces.filter( + const projectWorkspaces = visibleWorkspaces.filter( (workspace) => workspace.sidebarState.projectId === project.projectId, ); const projectSections = allSections.filter( diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/ResourceConsumption/ResourceConsumption.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/ResourceConsumption/ResourceConsumption.tsx index 0a834419eef..865e7c7b3d8 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/ResourceConsumption/ResourceConsumption.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/ResourceConsumption/ResourceConsumption.tsx @@ -17,6 +17,7 @@ import { } from "react-icons/hi2"; import { electronTrpc } from "renderer/lib/electron-trpc"; import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; +import { getVisibleSidebarWorkspaces } from "renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal"; import { useTabsStore } from "renderer/stores/tabs/store"; import { AppResourceSection } from "./components/AppResourceSection"; import { MetricBadge } from "./components/MetricBadge"; @@ -83,7 +84,10 @@ export function ResourceConsumption() { q .from({ ws: collections.v2WorkspaceLocalState }) .orderBy(({ ws }) => ws.sidebarState.tabOrder, "asc") - .select(({ ws }) => ({ workspaceId: ws.workspaceId })), + .select(({ ws }) => ({ + workspaceId: ws.workspaceId, + isHidden: ws.sidebarState.isHidden, + })), [collections], ); @@ -93,7 +97,10 @@ export function ResourceConsumption() { ); const sidebarWorkspaceOrder = useMemo( - () => rawSidebarWorkspaces.map((w) => w.workspaceId), + () => + getVisibleSidebarWorkspaces(rawSidebarWorkspaces).map( + (w) => w.workspaceId, + ), [rawSidebarWorkspaces], ); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2WorkspaceOpenInButton/V2WorkspaceOpenInButton.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2WorkspaceOpenInButton/V2WorkspaceOpenInButton.tsx index eab12edd4d6..161451163d2 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2WorkspaceOpenInButton/V2WorkspaceOpenInButton.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/components/V2WorkspaceOpenInButton/V2WorkspaceOpenInButton.tsx @@ -21,7 +21,7 @@ export function V2WorkspaceOpenInButton({ q .from({ workspaces: collections.v2Workspaces }) .leftJoin({ hosts: collections.v2Hosts }, ({ workspaces, hosts }) => - eq(workspaces.hostId, hosts.id), + eq(workspaces.hostId, hosts.machineId), ) .where(({ workspaces }) => eq(workspaces.id, workspaceId)) .select(({ workspaces, hosts }) => ({ diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx index a31d9f9d4b0..43743406f27 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx @@ -15,7 +15,6 @@ import { import { useHotkey } from "renderer/hotkeys"; import { electronTrpc } from "renderer/lib/electron-trpc"; import { DashboardSidebar } from "renderer/routes/_authenticated/_dashboard/components/DashboardSidebar"; -import { V1MigrationSummaryModal } from "renderer/routes/_authenticated/components/V1MigrationSummaryModal"; import { useDevSeedV2Sidebar } from "renderer/routes/_authenticated/hooks/useDevSeedV2Sidebar"; import { useMigrateV1DataToV2 } from "renderer/routes/_authenticated/hooks/useMigrateV1DataToV2"; import { ResizablePanel } from "renderer/screens/main/components/ResizablePanel"; @@ -147,7 +146,6 @@ function DashboardLayout() { - {deleteTarget && ( { @@ -65,7 +68,11 @@ export async function dispatchForkLaunch({ agentConfigCount: agentConfigs.length, }); - const hostUrl = resolveHostUrl(pending.hostTarget, activeHostUrl); + const hostUrl = resolveHostUrl( + pending.hostTarget, + activeHostUrl, + activeOrganizationId, + ); const hostClient = hostUrl ? getHostServiceClientByUrl(hostUrl) : undefined; let build: Awaited>; @@ -159,9 +166,15 @@ export async function dispatchForkLaunch({ function resolveHostUrl( hostTarget: PendingWorkspaceRow["hostTarget"], activeHostUrl: string | null, + activeOrganizationId: string | null, ): string | null { if (hostTarget.kind === "local") return activeHostUrl; - return `${env.RELAY_URL}/hosts/${hostTarget.hostId}`; + if (!activeOrganizationId) return null; + const routingKey = buildHostRoutingKey( + activeOrganizationId, + hostTarget.hostId, + ); + return `${env.RELAY_URL}/hosts/${routingKey}`; } async function writeAttachmentsToWorktree({ diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx index 50df503d2d7..4c089eb9c87 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/page.tsx @@ -6,7 +6,8 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { useCallback, useEffect, useRef, useState } from "react"; import { GoGitBranch } from "react-icons/go"; import { HiCheck, HiExclamationTriangle } from "react-icons/hi2"; -import { env } from "renderer/env.renderer"; +import { useHostTargetUrl } from "renderer/hooks/host-service/useHostTargetUrl"; +import { authClient } from "renderer/lib/auth-client"; import { electronTrpc } from "renderer/lib/electron-trpc"; import { formatRelativeTime } from "renderer/lib/formatRelativeTime"; import { getHostServiceClientByUrl } from "renderer/lib/host-service-client"; @@ -59,6 +60,9 @@ function useFireIntent(pendingId: string, pending: PendingWorkspaceRow | null) { const trpcUtils = electronTrpc.useUtils(); const { activeHostUrl } = useLocalHostService(); const { ensureWorkspaceInSidebar } = useDashboardSidebarState(); + const hostUrl = useHostTargetUrl(pending?.hostTarget ?? null); + const { data: session } = authClient.useSession(); + const activeOrganizationId = session?.session?.activeOrganizationId ?? null; const fire = useCallback(async () => { if (!pending) return; @@ -113,10 +117,6 @@ function useFireIntent(pendingId: string, pending: PendingWorkspaceRow | null) { if (!pending.linkedPR) { throw new Error("pr-checkout intent requires a linkedPR"); } - const hostUrl = - pending.hostTarget.kind === "local" - ? activeHostUrl - : `${env.RELAY_URL}/hosts/${pending.hostTarget.hostId}`; if (!hostUrl) { throw new Error("Host service not available"); } @@ -174,6 +174,7 @@ function useFireIntent(pendingId: string, pending: PendingWorkspaceRow | null) { loadedAttachments, agentConfigs, activeHostUrl, + activeOrganizationId, resolvedPr, onApplyToRow: (patch) => { collections.pendingWorkspaces.update(pendingId, (draft) => { @@ -212,6 +213,8 @@ function useFireIntent(pendingId: string, pending: PendingWorkspaceRow | null) { pendingId, trpcUtils, activeHostUrl, + activeOrganizationId, + hostUrl, ]); return fire; @@ -221,7 +224,6 @@ function PendingWorkspacePage() { const { pendingId } = Route.useParams(); const navigate = useNavigate(); const collections = useCollections(); - const { activeHostUrl } = useLocalHostService(); const { ensureWorkspaceInSidebar } = useDashboardSidebarState(); const navigatedRef = useRef(false); const firedRef = useRef(false); @@ -278,11 +280,7 @@ function PendingWorkspacePage() { // adopt is fast and doesn't instrument progress). const intentHasProgress = pending?.intent === "fork" || pending?.intent === "checkout"; - const hostUrl = !pending - ? activeHostUrl - : pending.hostTarget.kind === "local" - ? activeHostUrl - : `${env.RELAY_URL}/hosts/${pending.hostTarget.hostId}`; + const hostUrl = useHostTargetUrl(pending?.hostTarget ?? null); const { data: progress } = useQuery({ queryKey: ["workspaceCreation", "getProgress", pendingId, hostUrl], diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useOpenInExternalEditor/useOpenInExternalEditor.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useOpenInExternalEditor/useOpenInExternalEditor.ts index eb9058a466b..215154de0d5 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useOpenInExternalEditor/useOpenInExternalEditor.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useOpenInExternalEditor/useOpenInExternalEditor.ts @@ -21,7 +21,7 @@ export function useOpenInExternalEditor(workspaceId: string) { q .from({ workspaces: collections.v2Workspaces }) .leftJoin({ hosts: collections.v2Hosts }, ({ workspaces, hosts }) => - eq(workspaces.hostId, hosts.id), + eq(workspaces.hostId, hosts.machineId), ) .where(({ workspaces }) => eq(workspaces.id, workspaceId)) .select(({ workspaces, hosts }) => ({ diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx index 392d4fd56de..5cd7cbebe16 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx @@ -1,3 +1,4 @@ +import { buildHostRoutingKey } from "@superset/shared/host-routing"; import { eq } from "@tanstack/db"; import { useLiveQuery } from "@tanstack/react-db"; import { createFileRoute, Outlet, useMatchRoute } from "@tanstack/react-router"; @@ -35,11 +36,12 @@ function V2WorkspaceLayout() { q .from({ v2Workspaces: collections.v2Workspaces }) .leftJoin({ hosts: collections.v2Hosts }, ({ v2Workspaces, hosts }) => - eq(v2Workspaces.hostId, hosts.id), + eq(v2Workspaces.hostId, hosts.machineId), ) .where(({ v2Workspaces }) => eq(v2Workspaces.id, workspaceId ?? "")) .select(({ v2Workspaces, hosts }) => ({ id: v2Workspaces.id, + organizationId: v2Workspaces.organizationId, hostId: v2Workspaces.hostId, hostMachineId: hosts?.machineId ?? null, projectId: v2Workspaces.projectId, @@ -54,7 +56,7 @@ function V2WorkspaceLayout() { ? null : isLocal ? activeHostUrl - : `${env.RELAY_URL}/hosts/${workspace.hostId}`; + : `${env.RELAY_URL}/hosts/${buildHostRoutingKey(workspace.organizationId, workspace.hostId)}`; const lastEnsuredWorkspaceIdRef = useRef(null); diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/V2WorkspaceRow.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/V2WorkspaceRow.tsx index 50cf05473f0..3f781f11471 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/V2WorkspaceRow.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/components/V2WorkspacesList/components/V2WorkspaceRow/V2WorkspaceRow.tsx @@ -41,8 +41,12 @@ export function V2WorkspaceRow({ isCurrentRoute, }: V2WorkspaceRowProps) { const navigate = useNavigate(); - const { ensureWorkspaceInSidebar, removeWorkspaceFromSidebar } = - useDashboardSidebarState(); + const { + ensureWorkspaceInSidebar, + hideWorkspaceInSidebar, + removeWorkspaceFromSidebar, + } = useDashboardSidebarState(); + const isMainWorkspace = workspace.type === "main"; const HostIcon = hostIconFor(workspace.hostType); @@ -70,9 +74,20 @@ export function V2WorkspaceRow({ event.preventDefault(); return; } + if (isMainWorkspace) { + hideWorkspaceInSidebar(workspace.id, workspace.projectId); + return; + } removeWorkspaceFromSidebar(workspace.id); }, - [isCurrentRoute, removeWorkspaceFromSidebar, workspace.id], + [ + hideWorkspaceInSidebar, + isCurrentRoute, + isMainWorkspace, + removeWorkspaceFromSidebar, + workspace.id, + workspace.projectId, + ], ); const creatorLabel = workspace.isCreatedByCurrentUser diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/hooks/useAccessibleV2Workspaces/useAccessibleV2Workspaces.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/hooks/useAccessibleV2Workspaces/useAccessibleV2Workspaces.ts index 764feb6241a..e34b2d481d8 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/hooks/useAccessibleV2Workspaces/useAccessibleV2Workspaces.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspaces/hooks/useAccessibleV2Workspaces/useAccessibleV2Workspaces.ts @@ -4,6 +4,7 @@ import { useMemo } from "react"; import { env } from "renderer/env.renderer"; import { authClient } from "renderer/lib/auth-client"; import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; +import { isSidebarWorkspaceVisible } from "renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal"; import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; import { MOCK_ORG_ID } from "shared/constants"; @@ -13,6 +14,7 @@ export interface AccessibleV2Workspace { id: string; name: string; branch: string; + type: "main" | "worktree"; createdAt: Date; createdByUserId: string | null; createdByName: string | null; @@ -85,11 +87,11 @@ export function useAccessibleV2Workspaces( q .from({ workspaces: collections.v2Workspaces }) .innerJoin({ hosts: collections.v2Hosts }, ({ workspaces, hosts }) => - eq(workspaces.hostId, hosts.id), + eq(workspaces.hostId, hosts.machineId), ) .innerJoin( { userHosts: collections.v2UsersHosts }, - ({ hosts, userHosts }) => eq(userHosts.hostId, hosts.id), + ({ hosts, userHosts }) => eq(userHosts.hostId, hosts.machineId), ) .innerJoin( { projects: collections.v2Projects }, @@ -100,6 +102,11 @@ export function useAccessibleV2Workspaces( ({ workspaces, sidebarState }) => eq(sidebarState.workspaceId, workspaces.id), ) + .leftJoin( + { sidebarProject: collections.v2SidebarProjects }, + ({ projects, sidebarProject }) => + eq(sidebarProject.projectId, projects.id), + ) .leftJoin({ creators: collections.users }, ({ workspaces, creators }) => eq(workspaces.createdByUserId, creators.id), ) @@ -109,22 +116,34 @@ export function useAccessibleV2Workspaces( eq(userHosts.userId, currentUserId ?? ""), ), ) - .select(({ workspaces, hosts, projects, sidebarState, creators }) => ({ - id: workspaces.id, - name: workspaces.name, - branch: workspaces.branch, - createdAt: workspaces.createdAt, - createdByUserId: workspaces.createdByUserId, - createdByName: creators?.name ?? null, - createdByImage: creators?.image ?? null, - projectId: projects.id, - projectName: projects.name, - hostId: hosts.id, - hostName: hosts.name, - hostMachineId: hosts.machineId, - hostIsOnline: hosts.isOnline, - sidebarWorkspaceId: sidebarState?.workspaceId ?? null, - })), + .select( + ({ + workspaces, + hosts, + projects, + sidebarState, + sidebarProject, + creators, + }) => ({ + id: workspaces.id, + name: workspaces.name, + branch: workspaces.branch, + type: workspaces.type, + createdAt: workspaces.createdAt, + createdByUserId: workspaces.createdByUserId, + createdByName: creators?.name ?? null, + createdByImage: creators?.image ?? null, + projectId: projects.id, + projectName: projects.name, + hostId: hosts.machineId, + hostName: hosts.name, + hostMachineId: hosts.machineId, + hostIsOnline: hosts.isOnline, + sidebarProjectId: sidebarProject?.projectId ?? null, + sidebarWorkspaceId: sidebarState?.workspaceId ?? null, + sidebarIsHidden: sidebarState?.sidebarState.isHidden ?? false, + }), + ), [activeOrganizationId, collections, currentUserId], ); @@ -138,10 +157,20 @@ export function useAccessibleV2Workspaces( : row.hostMachineId === machineId ? "local-device" : "remote-device"; + const isAutoVisibleMain = + row.type === "main" && + row.hostMachineId != null && + row.hostMachineId === machineId && + row.sidebarProjectId != null; + const isInSidebar = + isSidebarWorkspaceVisible({ isHidden: row.sidebarIsHidden }) && + (row.sidebarWorkspaceId != null || isAutoVisibleMain); + deduped.set(row.id, { id: row.id, name: row.name, branch: row.branch, + type: row.type, createdAt: new Date(row.createdAt), createdByUserId: row.createdByUserId, createdByName: row.createdByName ?? null, @@ -155,7 +184,7 @@ export function useAccessibleV2Workspaces( hostMachineId: row.hostMachineId, hostIsOnline: row.hostIsOnline, hostType, - isInSidebar: row.sidebarWorkspaceId != null, + isInSidebar, }); } return Array.from(deduped.values()).sort( diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/GitHubIssueLinkCommand/GitHubIssueLinkCommand.tsx b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/GitHubIssueLinkCommand/GitHubIssueLinkCommand.tsx index 68c7b8613c5..1aa769321f5 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/GitHubIssueLinkCommand/GitHubIssueLinkCommand.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/GitHubIssueLinkCommand/GitHubIssueLinkCommand.tsx @@ -12,10 +12,9 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { useQuery } from "@tanstack/react-query"; import type { ReactNode } from "react"; import { useId, useState } from "react"; -import { env } from "renderer/env.renderer"; +import { useHostTargetUrl } from "renderer/hooks/host-service/useHostTargetUrl"; import { useDebouncedValue } from "renderer/hooks/useDebouncedValue"; import { getHostServiceClientByUrl } from "renderer/lib/host-service-client"; -import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; import { IssueIcon, type IssueState, @@ -54,17 +53,12 @@ export function GitHubIssueLinkCommand({ const [showClosed, setShowClosed] = useState(false); const showClosedId = useId(); const debouncedQuery = useDebouncedValue(searchQuery, 300); - const { activeHostUrl } = useLocalHostService(); + const hostUrl = useHostTargetUrl(hostTarget); const trimmedQuery = searchQuery.trim(); const debouncedTrimmed = debouncedQuery.trim(); const isPendingDebounce = trimmedQuery !== debouncedTrimmed; - const hostUrl = - hostTarget.kind === "local" - ? activeHostUrl - : `${env.RELAY_URL}/hosts/${hostTarget.hostId}`; - const { data, isFetching } = useQuery({ queryKey: [ "workspaceCreation", diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/PRLinkCommand/PRLinkCommand.tsx b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/PRLinkCommand/PRLinkCommand.tsx index 4bfabc827e1..2603272f665 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/PRLinkCommand/PRLinkCommand.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/components/PRLinkCommand/PRLinkCommand.tsx @@ -12,10 +12,9 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; import { useQuery } from "@tanstack/react-query"; import type { ReactNode } from "react"; import { useId, useState } from "react"; -import { env } from "renderer/env.renderer"; +import { useHostTargetUrl } from "renderer/hooks/host-service/useHostTargetUrl"; import { useDebouncedValue } from "renderer/hooks/useDebouncedValue"; import { getHostServiceClientByUrl } from "renderer/lib/host-service-client"; -import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; import { PRIcon, type PRState, @@ -55,17 +54,12 @@ export function PRLinkCommand({ const [showClosed, setShowClosed] = useState(false); const showClosedId = useId(); const debouncedQuery = useDebouncedValue(searchQuery, 300); - const { activeHostUrl } = useLocalHostService(); + const hostUrl = useHostTargetUrl(hostTarget); const trimmedQuery = searchQuery.trim(); const debouncedTrimmed = debouncedQuery.trim(); const isPendingDebounce = trimmedQuery !== debouncedTrimmed; - const hostUrl = - hostTarget.kind === "local" - ? activeHostUrl - : `${env.RELAY_URL}/hosts/${hostTarget.hostId}`; - const { data, isFetching } = useQuery({ queryKey: [ "workspaceCreation", diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useBranchPickerController/useBranchPickerController.ts b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useBranchPickerController/useBranchPickerController.ts index fb0d1dac769..b3d54371850 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useBranchPickerController/useBranchPickerController.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useBranchPickerController/useBranchPickerController.ts @@ -87,7 +87,7 @@ export function useBranchPickerController(args: UseBranchPickerControllerArgs) { const targetHostId = useMemo(() => { if (hostTarget.kind === "host") return hostTarget.hostId; if (!machineId || !allHosts) return null; - return allHosts.find((h) => h.machineId === machineId)?.id ?? null; + return allHosts.find((h) => h.machineId === machineId)?.machineId ?? null; }, [hostTarget, allHosts, machineId]); const workspaceByBranch = useMemo(() => { diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DevicePicker/hooks/useWorkspaceHostOptions/useWorkspaceHostOptions.ts b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DevicePicker/hooks/useWorkspaceHostOptions/useWorkspaceHostOptions.ts index 83f96a9c775..27c4b05c5fd 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DevicePicker/hooks/useWorkspaceHostOptions/useWorkspaceHostOptions.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/components/DevicePicker/hooks/useWorkspaceHostOptions/useWorkspaceHostOptions.ts @@ -16,7 +16,7 @@ export interface WorkspaceHostOption { interface UseWorkspaceHostOptionsResult { currentDeviceName: string | null; - /** v2_hosts.id for the current device (the one running this desktop app). */ + /** machineId of the current device (the one running this desktop app). */ localHostId: string | null; activeHostUrl: string | null; otherHosts: WorkspaceHostOption[]; @@ -37,7 +37,7 @@ export function useWorkspaceHostOptions(): UseWorkspaceHostOptionsResult { q .from({ userHosts: collections.v2UsersHosts }) .innerJoin({ hosts: collections.v2Hosts }, ({ userHosts, hosts }) => - eq(userHosts.hostId, hosts.id), + eq(userHosts.hostId, hosts.machineId), ) .where(({ userHosts, hosts }) => and( @@ -46,7 +46,6 @@ export function useWorkspaceHostOptions(): UseWorkspaceHostOptionsResult { ), ) .select(({ hosts }) => ({ - id: hosts.id, machineId: hosts.machineId, name: hosts.name, isOnline: hosts.isOnline, @@ -64,7 +63,7 @@ export function useWorkspaceHostOptions(): UseWorkspaceHostOptionsResult { accessibleHosts .filter((host) => host.machineId !== machineId) .map((host) => ({ - id: host.id, + id: host.machineId, name: host.name, isCloud: host.machineId == null, isOnline: host.isOnline ?? false, @@ -75,7 +74,7 @@ export function useWorkspaceHostOptions(): UseWorkspaceHostOptionsResult { return { currentDeviceName: localHost?.name ?? null, - localHostId: localHost?.id ?? null, + localHostId: localHost?.machineId ?? null, activeHostUrl, otherHosts, }; diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useBranchContext/useBranchContext.ts b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useBranchContext/useBranchContext.ts index fb089dbc211..96c4dfa879b 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useBranchContext/useBranchContext.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/hooks/useBranchContext/useBranchContext.ts @@ -2,9 +2,8 @@ import type { AppRouter } from "@superset/host-service"; import { useInfiniteQuery } from "@tanstack/react-query"; import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server"; import { useMemo } from "react"; -import { env } from "renderer/env.renderer"; +import { useHostTargetUrl } from "renderer/hooks/host-service/useHostTargetUrl"; import { getHostServiceClientByUrl } from "renderer/lib/host-service-client"; -import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; import type { WorkspaceHostTarget } from "../../components/DevicePicker"; type SearchBranchesInput = @@ -29,11 +28,7 @@ export function useBranchContext( query: string, filter: BranchFilter = "branch", ) { - const { activeHostUrl } = useLocalHostService(); - const hostUrl = - hostTarget.kind === "local" - ? activeHostUrl - : `${env.RELAY_URL}/hosts/${hostTarget.hostId}`; + const hostUrl = useHostTargetUrl(hostTarget); const q = useInfiniteQuery({ queryKey: [ diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/hooks/useSelectedHostProjectIds/useSelectedHostProjectIds.ts b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/hooks/useSelectedHostProjectIds/useSelectedHostProjectIds.ts index 0651c64dff1..e180421ed07 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/hooks/useSelectedHostProjectIds/useSelectedHostProjectIds.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceModalContent/hooks/useSelectedHostProjectIds/useSelectedHostProjectIds.ts @@ -1,7 +1,6 @@ import { useQuery } from "@tanstack/react-query"; -import { env } from "renderer/env.renderer"; +import { useHostTargetUrl } from "renderer/hooks/host-service/useHostTargetUrl"; import { getHostServiceClientByUrl } from "renderer/lib/host-service-client"; -import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; import type { WorkspaceHostTarget } from "../../../DashboardNewWorkspaceForm/components/DevicePicker/types"; /** @@ -11,11 +10,7 @@ import type { WorkspaceHostTarget } from "../../../DashboardNewWorkspaceForm/com export function useSelectedHostProjectIds( hostTarget: WorkspaceHostTarget, ): Set | null { - const { activeHostUrl } = useLocalHostService(); - const hostUrl = - hostTarget.kind === "local" - ? activeHostUrl - : `${env.RELAY_URL}/hosts/${hostTarget.hostId}`; + const hostUrl = useHostTargetUrl(hostTarget); const { data } = useQuery({ queryKey: ["project", "list", hostUrl], diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useAdoptWorktree/useAdoptWorktree.ts b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useAdoptWorktree/useAdoptWorktree.ts index 9c1ff0f32d0..b3415730c9e 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useAdoptWorktree/useAdoptWorktree.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useAdoptWorktree/useAdoptWorktree.ts @@ -1,5 +1,7 @@ +import { buildHostRoutingKey } from "@superset/shared/host-routing"; import { useCallback } from "react"; import { env } from "renderer/env.renderer"; +import { authClient } from "renderer/lib/auth-client"; import { getHostServiceClientByUrl } from "renderer/lib/host-service-client"; import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; import type { WorkspaceHostTarget } from "../../components/DashboardNewWorkspaceForm/components/DevicePicker"; @@ -17,13 +19,17 @@ export interface AdoptWorktreeInput { */ export function useAdoptWorktree() { const { activeHostUrl } = useLocalHostService(); + const { data: session } = authClient.useSession(); + const activeOrganizationId = session?.session?.activeOrganizationId ?? null; return useCallback( async (input: AdoptWorktreeInput) => { const hostUrl = input.hostTarget.kind === "local" ? activeHostUrl - : `${env.RELAY_URL}/hosts/${input.hostTarget.hostId}`; + : activeOrganizationId + ? `${env.RELAY_URL}/hosts/${buildHostRoutingKey(activeOrganizationId, input.hostTarget.hostId)}` + : null; if (!hostUrl) throw new Error("Host service not available"); const client = getHostServiceClientByUrl(hostUrl); return client.workspaceCreation.adopt.mutate({ @@ -32,6 +38,6 @@ export function useAdoptWorktree() { branch: input.branch, }); }, - [activeHostUrl], + [activeHostUrl, activeOrganizationId], ); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useCheckoutDashboardWorkspace/useCheckoutDashboardWorkspace.ts b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useCheckoutDashboardWorkspace/useCheckoutDashboardWorkspace.ts index 30a9bf145da..f0718e1f921 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useCheckoutDashboardWorkspace/useCheckoutDashboardWorkspace.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useCheckoutDashboardWorkspace/useCheckoutDashboardWorkspace.ts @@ -1,5 +1,7 @@ +import { buildHostRoutingKey } from "@superset/shared/host-routing"; import { useCallback } from "react"; import { env } from "renderer/env.renderer"; +import { authClient } from "renderer/lib/auth-client"; import { getHostServiceClientByUrl } from "renderer/lib/host-service-client"; import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; import type { WorkspaceHostTarget } from "../../components/DashboardNewWorkspaceForm/components/DevicePicker"; @@ -52,13 +54,17 @@ export interface CheckoutWorkspaceInput { */ export function useCheckoutDashboardWorkspace() { const { activeHostUrl } = useLocalHostService(); + const { data: session } = authClient.useSession(); + const activeOrganizationId = session?.session?.activeOrganizationId ?? null; return useCallback( async (input: CheckoutWorkspaceInput) => { const hostUrl = input.hostTarget.kind === "local" ? activeHostUrl - : `${env.RELAY_URL}/hosts/${input.hostTarget.hostId}`; + : activeOrganizationId + ? `${env.RELAY_URL}/hosts/${buildHostRoutingKey(activeOrganizationId, input.hostTarget.hostId)}` + : null; if (!hostUrl) { throw new Error("Host service not available"); @@ -76,6 +82,6 @@ export function useCheckoutDashboardWorkspace() { linkedContext: input.linkedContext, }); }, - [activeHostUrl], + [activeHostUrl, activeOrganizationId], ); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useCreateDashboardWorkspace/useCreateDashboardWorkspace.ts b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useCreateDashboardWorkspace/useCreateDashboardWorkspace.ts index f2ae0a58ea2..d21beaaee62 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useCreateDashboardWorkspace/useCreateDashboardWorkspace.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/hooks/useCreateDashboardWorkspace/useCreateDashboardWorkspace.ts @@ -1,5 +1,7 @@ +import { buildHostRoutingKey } from "@superset/shared/host-routing"; import { useCallback } from "react"; import { env } from "renderer/env.renderer"; +import { authClient } from "renderer/lib/auth-client"; import { getHostServiceClientByUrl } from "renderer/lib/host-service-client"; import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; import type { WorkspaceHostTarget } from "../../components/DashboardNewWorkspaceForm/components/DevicePicker"; @@ -36,13 +38,17 @@ export interface CreateWorkspaceInput { */ export function useCreateDashboardWorkspace() { const { activeHostUrl } = useLocalHostService(); + const { data: session } = authClient.useSession(); + const activeOrganizationId = session?.session?.activeOrganizationId ?? null; return useCallback( async (input: CreateWorkspaceInput) => { const hostUrl = input.hostTarget.kind === "local" ? activeHostUrl - : `${env.RELAY_URL}/hosts/${input.hostTarget.hostId}`; + : activeOrganizationId + ? `${env.RELAY_URL}/hosts/${buildHostRoutingKey(activeOrganizationId, input.hostTarget.hostId)}` + : null; if (!hostUrl) { throw new Error("Host service not available"); @@ -58,6 +64,6 @@ export function useCreateDashboardWorkspace() { linkedContext: input.linkedContext, }); }, - [activeHostUrl], + [activeHostUrl, activeOrganizationId], ); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/GlobalTerminalLifecycle/hooks/useGlobalTerminalLifecycle/useGlobalTerminalLifecycle.ts b/apps/desktop/src/renderer/routes/_authenticated/components/GlobalTerminalLifecycle/hooks/useGlobalTerminalLifecycle/useGlobalTerminalLifecycle.ts index ee39bff4278..3491bfdd032 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/GlobalTerminalLifecycle/hooks/useGlobalTerminalLifecycle/useGlobalTerminalLifecycle.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/components/GlobalTerminalLifecycle/hooks/useGlobalTerminalLifecycle/useGlobalTerminalLifecycle.ts @@ -1,4 +1,5 @@ import type { WorkspaceState } from "@superset/panes"; +import { buildHostRoutingKey } from "@superset/shared/host-routing"; import { eq } from "@tanstack/db"; import { useLiveQuery } from "@tanstack/react-db"; import { useEffect, useMemo, useRef } from "react"; @@ -133,10 +134,11 @@ export function useGlobalTerminalLifecycle() { query .from({ v2Workspaces: collections.v2Workspaces }) .leftJoin({ hosts: collections.v2Hosts }, ({ v2Workspaces, hosts }) => - eq(v2Workspaces.hostId, hosts.id), + eq(v2Workspaces.hostId, hosts.machineId), ) .select(({ v2Workspaces, hosts }) => ({ workspaceId: v2Workspaces.id, + organizationId: v2Workspaces.organizationId, hostId: v2Workspaces.hostId, hostMachineId: hosts?.machineId ?? null, })), @@ -156,7 +158,7 @@ export function useGlobalTerminalLifecycle() { if (workspace.hostId) { urls.set( workspace.workspaceId, - `${env.RELAY_URL}/hosts/${workspace.hostId}`, + `${env.RELAY_URL}/hosts/${buildHostRoutingKey(workspace.organizationId, workspace.hostId)}`, ); } } diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/V1MigrationSummaryModal/V1MigrationSummaryModal.tsx b/apps/desktop/src/renderer/routes/_authenticated/components/V1MigrationSummaryModal/V1MigrationSummaryModal.tsx index 3001f94ab6d..69127f92daf 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/V1MigrationSummaryModal/V1MigrationSummaryModal.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/components/V1MigrationSummaryModal/V1MigrationSummaryModal.tsx @@ -6,11 +6,10 @@ import { DialogDescription, DialogTitle, } from "@superset/ui/dialog"; -import { MeshGradient } from "@superset/ui/mesh-gradient"; +import { toast } from "@superset/ui/sonner"; import { cn } from "@superset/ui/utils"; -import { useEffect, useState } from "react"; +import { lazy, Suspense, useEffect, useState } from "react"; import { - LuCheck, LuChevronDown, LuChevronRight, LuFolder, @@ -18,12 +17,21 @@ import { LuTriangle, } from "react-icons/lu"; import { env } from "renderer/env.renderer"; +import { apiTrpcClient } from "renderer/lib/api-trpc-client"; import { authClient } from "renderer/lib/auth-client"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { V1_MIGRATION_SUMMARY_EVENT } from "renderer/routes/_authenticated/hooks/useMigrateV1DataToV2"; import { MOCK_ORG_ID } from "shared/constants"; -type ProjectStatus = "created" | "linked" | "error"; -type WorkspaceStatus = "adopted" | "skipped" | "error"; +const Dithering = lazy(() => + import("@paper-design/shaders-react").then((mod) => ({ + default: mod.Dithering, + })), +); + +type MigrationPage = "welcome" | "results"; +type ProjectStatus = "created" | "linked" | "synced" | "error"; +type WorkspaceStatus = "adopted" | "synced" | "skipped" | "error"; interface ProjectEntry { name: string; @@ -55,11 +63,23 @@ interface StoredEntry { createdAt: number; } +interface ModalUiState { + page: MigrationPage; + isTransitioning: boolean; + expandedSection: "projects" | "workspaces" | "errors" | null; +} + +const INITIAL_MODAL_UI_STATE: ModalUiState = { + page: "welcome", + isTransitioning: false, + expandedSection: null, +}; + const GRADIENT_COLORS = [ - "#3b82f6", - "#6366f1", - "#8b5cf6", - "#1e1b4b", + "#f97316", + "#fb923c", + "#f59e0b", + "#431407", ] as const satisfies readonly [string, string, string, string]; function summaryKey(organizationId: string): string { @@ -84,13 +104,15 @@ export function V1MigrationSummaryModal() { ? MOCK_ORG_ID : (session?.session?.activeOrganizationId ?? null); const [summary, setSummary] = useState(null); - const [expandedSection, setExpandedSection] = useState< - "projects" | "workspaces" | null - >(null); + const [modalUiState, setModalUiState] = useState( + INITIAL_MODAL_UI_STATE, + ); + const { page, isTransitioning, expandedSection } = modalUiState; useEffect(() => { if (!organizationId) { setSummary(null); + setModalUiState(INITIAL_MODAL_UI_STATE); return; } setSummary(readSummary(organizationId)); @@ -99,6 +121,7 @@ export function V1MigrationSummaryModal() { const detail = (event as CustomEvent<{ organizationId: string }>).detail; if (detail?.organizationId === organizationId) { setSummary(readSummary(organizationId)); + setModalUiState(INITIAL_MODAL_UI_STATE); } }; window.addEventListener(V1_MIGRATION_SUMMARY_EVENT, onUpdate); @@ -111,133 +134,298 @@ export function V1MigrationSummaryModal() { const dismiss = () => { if (organizationId) localStorage.removeItem(summaryKey(organizationId)); setSummary(null); - setExpandedSection(null); + setModalUiState(INITIAL_MODAL_UI_STATE); }; - const toggleSection = (section: "projects" | "workspaces") => { - setExpandedSection((current) => (current === section ? null : section)); + const transitionToPage = (nextPage: MigrationPage) => { + if (page === nextPage || isTransitioning) return; + setModalUiState((current) => ({ ...current, isTransitioning: true })); + window.setTimeout(() => { + setModalUiState((current) => ({ + ...current, + page: nextPage, + isTransitioning: false, + })); + }, 160); }; - if (!summary) return null; + const toggleSection = (section: "projects" | "workspaces" | "errors") => { + setModalUiState((current) => ({ + ...current, + expandedSection: current.expandedSection === section ? null : section, + })); + }; - const projectsTotal = summary.projectsCreated + summary.projectsLinked; - const workspacesTotal = summary.workspacesCreated; - const hasErrors = summary.errors.length > 0; + if (!summary) return null; return ( event.preventDefault()} onPointerDownOutside={(event) => event.preventDefault()} onInteractOutside={(event) => event.preventDefault()} > - Welcome to Superset v2 + + {page === "welcome" + ? "Welcome to Superset v2" + : "V1 migration results"} + - We imported your v1 data. Review the summary and click Got it to - continue. + Review the migration summary and click Done to continue. -
- -
-
- -
-
- Welcome to Superset v2 -
-
- We imported your v1 data -
-
+ +
+ {page === "welcome" ? ( + + ) : ( + + )}
-
- 0 - ? `${summary.projectsLinked} linked` - : null, - summary.projectsCreated > 0 - ? `${summary.projectsCreated} created` - : null, - ] - .filter(Boolean) - .join(" · ")} - expanded={expandedSection === "projects"} - onToggle={ - summary.projects.length > 0 - ? () => toggleSection("projects") - : undefined - } +
+ {page === "results" ? ( + + ) : ( +
+ )} + +
+ +
+ ); +} + +function WelcomePage() { + return ( +
+ +
+
+
+ Welcome to Superset v2 +
+
+
+ ); +} + +interface DitheredBackgroundProps { + colors: readonly [string, string, string, string]; + className?: string; +} + +function DitheredBackground({ + colors, + className = "", +}: DitheredBackgroundProps) { + return ( +
+ + + +
+ ); +} + +interface ResultsPageProps { + summary: MigrationSummary; + expandedSection: "projects" | "workspaces" | "errors" | null; + onToggleSection: (section: "projects" | "workspaces" | "errors") => void; +} + +function ResultsPage({ + summary, + expandedSection, + onToggleSection, +}: ResultsPageProps) { + const copyText = electronTrpc.external.copyText.useMutation(); + const [isSendingSupportReport, setIsSendingSupportReport] = useState(false); + const projectsTotal = summary.projects.filter( + (project) => project.status !== "error", + ).length; + const workspacesTotal = summary.workspaces.filter( + (workspace) => workspace.status !== "error", + ).length; + const hasErrors = summary.errors.length > 0; + const projectDetail = [ + countByStatus(summary.projects, "synced", "synced"), + countByStatus(summary.projects, "linked", "linked"), + countByStatus(summary.projects, "created", "created"), + ] + .filter(Boolean) + .join(" · "); + const workspaceDetail = [ + countByStatus(summary.workspaces, "synced", "synced"), + countByStatus(summary.workspaces, "adopted", "adopted"), + countByStatus(summary.workspaces, "skipped", "skipped"), + ] + .filter(Boolean) + .join(" · "); + const contactSupport = async () => { + const report = buildMigrationSupportReport(summary); + setIsSendingSupportReport(true); + try { + await apiTrpcClient.support.sendMigrationReport.mutate({ report }); + toast.success("Migration details sent to support"); + } catch (error) { + console.warn("[v1-migration] Failed to send support report:", error); + try { + await copyText.mutateAsync(report); + toast.success("Migration details copied to clipboard"); + } catch (copyError) { + console.warn( + "[v1-migration] Failed to copy support report:", + copyError, + ); + toast.error("Could not send migration details"); + } + } finally { + setIsSendingSupportReport(false); + } + }; + + return ( +
+
+
+ Migration results +
+

+ Ran into issues?{" "} + + . +

+
+ +
+ 0 + ? () => onToggleSection("projects") + : undefined + } + > + + {summary.projects.map((p) => ( + + ))} + + + 0 + ? () => onToggleSection("workspaces") + : undefined + } + > + + {summary.workspaces.map((w) => ( + + ))} + + + {hasErrors && ( 0 - ? `${summary.workspacesSkipped} skipped` - : undefined - } - expanded={expandedSection === "workspaces"} - onToggle={ - summary.workspaces.length > 0 - ? () => toggleSection("workspaces") - : undefined - } + icon={LuTriangle} + label="Errors" + count={summary.errors.length} + detail={summary.errors[0]?.message} + variant="error" + expanded={expandedSection === "errors"} + onToggle={() => onToggleSection("errors")} > - {summary.workspaces.map((w, index) => ( + {summary.errors.map((error) => ( ))} - {hasErrors && ( - - )} -
- -
- - v1 data is preserved - - -
- - + )} +
+
); } @@ -249,6 +437,60 @@ function entryTone( return "success"; } +function countByStatus( + entries: T[], + status: T["status"], + label: string, +): string | null { + const count = entries.filter((entry) => entry.status === status).length; + if (count === 0) return null; + return `${count} ${label}`; +} + +function buildMigrationSupportReport(summary: MigrationSummary): string { + const lines = [ + "Hi Superset team,", + "", + "I ran into an issue with the V1 to V2 migration.", + "", + "Migration summary:", + `- Projects: ${summary.projectsCreated} created, ${summary.projectsLinked} linked, ${summary.projectsErrored} errored`, + `- Workspaces: ${summary.workspacesCreated} created, ${summary.workspacesSkipped} skipped, ${summary.workspacesErrored} errored`, + ]; + + const relevantEntries = [ + ...summary.errors.map( + (error) => `${error.kind}: ${error.name} - ${error.message}`, + ), + ...summary.workspaces + .filter((workspace) => workspace.status === "skipped") + .map( + (workspace) => + `workspace: ${workspace.name} (${workspace.branch}) - ${workspace.reason ?? workspace.status}`, + ), + ]; + + if (relevantEntries.length > 0) { + lines.push( + "", + "Migration errors and skipped items:", + ...relevantEntries + .slice(0, 20) + .map((entry) => `- ${truncateSupportLine(entry)}`), + ); + if (relevantEntries.length > 20) { + lines.push(`- ${relevantEntries.length - 20} more item(s) not included`); + } + } + + return lines.join("\n"); +} + +function truncateSupportLine(value: string): string { + if (value.length <= 240) return value; + return `${value.slice(0, 237)}...`; +} + interface SummaryRowProps { icon: React.ComponentType<{ className?: string; strokeWidth?: number }>; label: string; @@ -257,40 +499,6 @@ interface SummaryRowProps { variant?: "default" | "error"; } -function SummaryRow({ - icon: Icon, - label, - count, - detail, - variant = "default", -}: SummaryRowProps) { - return ( -
-
- -
-
-
- {label} - - {count} - -
- {detail && ( - {detail} - )} -
-
- ); -} - interface ExpandableSummaryRowProps extends SummaryRowProps { expanded: boolean; onToggle?: () => void; @@ -302,6 +510,7 @@ function ExpandableSummaryRow({ label, count, detail, + variant = "default", expanded, onToggle, children, @@ -319,7 +528,14 @@ function ExpandableSummaryRow({ !clickable && "cursor-default", )} > -
+
@@ -331,7 +547,9 @@ function ExpandableSummaryRow({
{detail && ( - {detail} + + {detail} + )} {clickable && (expanded ? ( diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/V2NotificationController/V2NotificationController.tsx b/apps/desktop/src/renderer/routes/_authenticated/components/V2NotificationController/V2NotificationController.tsx index 5a5f21f9219..0d6ed75437b 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/V2NotificationController/V2NotificationController.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/components/V2NotificationController/V2NotificationController.tsx @@ -1,4 +1,5 @@ import type { WorkspaceState } from "@superset/panes"; +import { buildHostRoutingKey } from "@superset/shared/host-routing"; import { eq } from "@tanstack/db"; import { useLiveQuery } from "@tanstack/react-db"; import { useMemo } from "react"; @@ -13,6 +14,7 @@ import { interface WorkspaceHostRow { workspaceId: string; + organizationId: string; hostId: string; hostMachineId: string | null | undefined; } @@ -40,10 +42,12 @@ export function V2NotificationController() { .from({ v2Workspaces: collections.v2Workspaces }) .leftJoin( { v2Hosts: collections.v2Hosts }, - ({ v2Workspaces, v2Hosts }) => eq(v2Workspaces.hostId, v2Hosts.id), + ({ v2Workspaces, v2Hosts }) => + eq(v2Workspaces.hostId, v2Hosts.machineId), ) .select(({ v2Workspaces, v2Hosts }) => ({ workspaceId: v2Workspaces.id, + organizationId: v2Workspaces.organizationId, hostId: v2Workspaces.hostId, hostMachineId: v2Hosts?.machineId ?? null, })), @@ -107,6 +111,7 @@ function groupWorkspacesByHostUrl({ for (const workspace of workspaceHosts) { const hostUrl = getHostUrlForWorkspace({ + organizationId: workspace.organizationId, hostId: workspace.hostId, hostMachineId: workspace.hostMachineId, machineId, @@ -129,11 +134,13 @@ function groupWorkspacesByHostUrl({ } function getHostUrlForWorkspace({ + organizationId, hostId, hostMachineId, machineId, activeHostUrl, }: { + organizationId: string; hostId: string; hostMachineId: string | null | undefined; machineId: string | null; @@ -142,5 +149,5 @@ function getHostUrlForWorkspace({ if (hostMachineId && machineId && hostMachineId === machineId) { return activeHostUrl; } - return `${env.RELAY_URL}/hosts/${hostId}`; + return `${env.RELAY_URL}/hosts/${buildHostRoutingKey(organizationId, hostId)}`; } diff --git a/apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts b/apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts index 6460b5366af..108ea0afb1f 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/hooks/useDashboardSidebarState/useDashboardSidebarState.ts @@ -8,6 +8,7 @@ import { } from "renderer/routes/_authenticated/components/utils/paneLifecycleRows"; import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; import type { AppCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider/collections"; +import { isSidebarWorkspaceVisible } from "renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal"; import { PROJECT_CUSTOM_COLORS } from "shared/constants/project-colors"; function getNextTabOrder(items: Array<{ tabOrder: number }>): number { @@ -58,6 +59,7 @@ function getProjectTopLevelItems( .filter( (item) => item.sidebarState.projectId === projectId && + isSidebarWorkspaceVisible(item) && item.sidebarState.sectionId === null && item.workspaceId !== options.excludeWorkspaceId, ) @@ -85,6 +87,14 @@ function getFirstSectionIndex(items: ProjectTopLevelItem[]): number { return firstSectionIndex === -1 ? items.length : firstSectionIndex; } +function createEmptyPaneLayout(): WorkspaceState { + return { + version: 1, + tabs: [], + activeTabId: null, + } satisfies WorkspaceState; +} + /** * Rewrites the flat top-level project lane. Workspace items are explicitly * ungrouped by setting sidebarState.projectId and clearing sidebarState.sectionId. @@ -102,6 +112,7 @@ function writeProjectTopLevelOrder( draft.sidebarState.projectId = projectId; draft.sidebarState.sectionId = null; draft.sidebarState.tabOrder = tabOrder; + draft.sidebarState.isHidden = false; }); return; } @@ -139,12 +150,23 @@ function ensureSidebarWorkspaceRecord( workspaceId: string, projectId: string, ): void { - if (collections.v2WorkspaceLocalState.get(workspaceId)) { + const existing = collections.v2WorkspaceLocalState.get(workspaceId); + if (existing && isSidebarWorkspaceVisible(existing)) { return; } const topLevelItems = getProjectTopLevelItems(collections, projectId); + if (existing) { + collections.v2WorkspaceLocalState.update(workspaceId, (draft) => { + draft.sidebarState.projectId = projectId; + draft.sidebarState.tabOrder = getPrependTabOrder(topLevelItems); + draft.sidebarState.sectionId = null; + draft.sidebarState.isHidden = false; + }); + return; + } + collections.v2WorkspaceLocalState.insert({ workspaceId, createdAt: new Date(), @@ -152,12 +174,9 @@ function ensureSidebarWorkspaceRecord( projectId, tabOrder: getPrependTabOrder(topLevelItems), sectionId: null, + isHidden: false, }, - paneLayout: { - version: 1, - tabs: [], - activeTabId: null, - } satisfies WorkspaceState, + paneLayout: createEmptyPaneLayout(), }); } @@ -228,6 +247,7 @@ export function useDashboardSidebarState() { if (!collections.v2WorkspaceLocalState.get(workspaceId)) return; collections.v2WorkspaceLocalState.update(workspaceId, (draft) => { draft.sidebarState.tabOrder = index + 1; + draft.sidebarState.isHidden = false; }); }); }, @@ -247,6 +267,7 @@ export function useDashboardSidebarState() { draft.sidebarState.tabOrder = tabOrder; draft.sidebarState.sectionId = null; draft.sidebarState.projectId = projectId; + draft.sidebarState.isHidden = false; }); } else { if (!collections.v2SidebarSections.get(item.id)) return; @@ -274,6 +295,7 @@ export function useDashboardSidebarState() { .filter( (item) => item.sidebarState.projectId === projectId && + isSidebarWorkspaceVisible(item) && item.workspaceId !== workspaceId && item.sidebarState.sectionId === sectionId, ) @@ -285,6 +307,7 @@ export function useDashboardSidebarState() { draft.sidebarState.tabOrder = i + 1; draft.sidebarState.sectionId = sectionId; draft.sidebarState.projectId = projectId; + draft.sidebarState.isHidden = false; }); }); }, @@ -376,14 +399,17 @@ export function useDashboardSidebarState() { .filter( (item) => item.sidebarState.projectId === projectId && + isSidebarWorkspaceVisible(item) && item.workspaceId !== workspaceId && item.sidebarState.sectionId === sectionId, ) .map((item) => ({ tabOrder: item.sidebarState.tabOrder })); collections.v2WorkspaceLocalState.update(workspaceId, (draft) => { + draft.sidebarState.projectId = projectId; draft.sidebarState.sectionId = sectionId; draft.sidebarState.tabOrder = getNextTabOrder(siblingRows); + draft.sidebarState.isHidden = false; }); }, [collections], @@ -405,6 +431,7 @@ export function useDashboardSidebarState() { .filter( (item) => item.sidebarState.projectId === section.projectId && + isSidebarWorkspaceVisible(item) && item.sidebarState.sectionId === sectionId, ) .sort( @@ -439,6 +466,35 @@ export function useDashboardSidebarState() { [collections], ); + const hideWorkspaceInSidebar = useCallback( + (workspaceId: string, projectId: string) => { + const workspace = collections.v2WorkspaceLocalState.get(workspaceId); + if (!workspace) { + collections.v2WorkspaceLocalState.insert({ + workspaceId, + createdAt: new Date(), + sidebarState: { + projectId, + tabOrder: 0, + sectionId: null, + isHidden: true, + }, + paneLayout: createEmptyPaneLayout(), + }); + return; + } + + cleanupWorkspacePaneRuntimes([workspace]); + collections.v2WorkspaceLocalState.update(workspaceId, (draft) => { + draft.sidebarState.projectId = projectId; + draft.sidebarState.sectionId = null; + draft.sidebarState.isHidden = true; + draft.paneLayout = createEmptyPaneLayout(); + }); + }, + [collections], + ); + const removeProjectFromSidebar = useCallback( (projectId: string) => { const workspaceRows = Array.from( @@ -470,6 +526,7 @@ export function useDashboardSidebarState() { deleteSection, ensureProjectInSidebar, ensureWorkspaceInSidebar, + hideWorkspaceInSidebar, moveWorkspaceToSection, moveWorkspaceToSectionAtIndex, removeProjectFromSidebar, diff --git a/apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.test.ts b/apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.test.ts index a0e19e7cc28..2c8eba026a2 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.test.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.test.ts @@ -28,6 +28,7 @@ interface V1WorkspaceRow { interface V1WorktreeRow { id: string; path: string; + baseBranch?: string | null; } interface V1SectionRow { @@ -58,11 +59,25 @@ interface FakeEnv { v1Worktrees: V1WorktreeRow[]; v1Sections: V1SectionRow[]; state: Map; - otherOrg: string | null; findByPath: Map; + failNextStateWriteFor: Set; + hostProjectsByPath: Map; + hostWorkspacesByKey: Map; setupThrowsFor: Set; createThrowsFor: Set; adoptThrowsFor: Map; + adoptThrowsForPath: Map; + createCalls: Array<{ name: string; repoPath: string }>; + createdProjectIds: string[]; + setupCalls: Array<{ projectId: string; repoPath?: string }>; + adoptCalls: Array<{ + projectId: string; + branch: string; + worktreePath?: string; + baseBranch?: string; + existingWorkspaceId?: string; + }>; + createdWorkspaceIds: string[]; } function makeFakeEnv(overrides: Partial = {}): FakeEnv { @@ -72,11 +87,19 @@ function makeFakeEnv(overrides: Partial = {}): FakeEnv { v1Worktrees: [], v1Sections: [], state: new Map(), - otherOrg: null, findByPath: new Map(), + failNextStateWriteFor: new Set(), + hostProjectsByPath: new Map(), + hostWorkspacesByKey: new Map(), setupThrowsFor: new Set(), createThrowsFor: new Set(), adoptThrowsFor: new Map(), + adoptThrowsForPath: new Map(), + createCalls: [], + createdProjectIds: [], + setupCalls: [], + adoptCalls: [], + createdWorkspaceIds: [], ...overrides, }; } @@ -105,12 +128,13 @@ function makeElectronTrpc(env: FakeEnv): ElectronTrpcClient { (r) => r.organizationId === organizationId, ), }, - findMigrationByOtherOrg: { - query: async (_: { organizationId: string }) => env.otherOrg, - }, upsertState: { mutate: async (row: Omit) => { - env.state.set(`${row.kind}:${row.v1Id}`, { + const key = `${row.kind}:${row.v1Id}`; + if (env.failNextStateWriteFor.delete(key)) { + throw new Error(`failed to write migration state for ${key}`); + } + env.state.set(key, { v1Id: row.v1Id, v2Id: row.v2Id, organizationId: row.organizationId, @@ -134,37 +158,115 @@ function makeHostService(env: FakeEnv): HostServiceClient { findByPath: { query: async ({ repoPath }: { repoPath: string }) => { const result = env.findByPath.get(repoPath); - if (!result) return { candidates: [] }; - if ("err" in result) { - throw new Error(`path not a git repo: ${repoPath}`); + if (result) { + if ("err" in result) { + throw new Error(`path not a git repo: ${repoPath}`); + } + return result; } - return result; + const existingId = env.hostProjectsByPath.get(repoPath); + if (existingId) return { candidates: [{ id: existingId }] }; + return { candidates: [] }; }, }, setup: { - mutate: async ({ projectId }: { projectId: string }) => { + mutate: async ({ + projectId, + mode, + }: { + projectId: string; + mode: { repoPath: string; allowRelocate?: boolean }; + }) => { + env.setupCalls.push({ projectId, repoPath: mode.repoPath }); if (env.setupThrowsFor.has(projectId)) { throw trpcErr("CONFLICT", "already set up elsewhere"); } + env.hostProjectsByPath.set(mode.repoPath, projectId); return { repoPath: "/fake" }; }, }, create: { - mutate: async ({ name }: { name: string }) => { + mutate: async ({ + name, + mode, + }: { + name: string; + mode: { repoPath: string }; + }) => { if (env.createThrowsFor.has(name)) { throw new Error(`cloud create failed for ${name}`); } - return { projectId: nextId("v2-proj"), repoPath: "/fake" }; + env.createCalls.push({ name, repoPath: mode.repoPath }); + const projectId = nextId("v2-proj"); + env.createdProjectIds.push(projectId); + env.hostProjectsByPath.set(mode.repoPath, projectId); + return { projectId, repoPath: mode.repoPath }; + }, + }, + }, + workspace: { + get: { + query: async ({ id }: { id: string }) => { + if (![...env.hostWorkspacesByKey.values()].includes(id)) { + throw trpcErr("NOT_FOUND", "Workspace not found"); + } + return { id }; }, }, }, workspaceCreation: { adopt: { - mutate: async ({ branch }: { branch: string }) => { + mutate: async ({ + projectId, + branch, + worktreePath, + baseBranch, + existingWorkspaceId, + }: { + projectId: string; + branch: string; + worktreePath?: string; + baseBranch?: string; + existingWorkspaceId?: string; + }) => { + const call = { + projectId, + branch, + worktreePath, + baseBranch, + } as (typeof env.adoptCalls)[number]; + if (existingWorkspaceId) + call.existingWorkspaceId = existingWorkspaceId; + env.adoptCalls.push(call); + const pathBehavior = worktreePath + ? env.adoptThrowsForPath.get(worktreePath) + : undefined; + if (pathBehavior) + throw trpcErr(pathBehavior.code, pathBehavior.message); const behavior = env.adoptThrowsFor.get(branch); if (behavior) throw trpcErr(behavior.code, behavior.message); + const key = `${projectId}:${worktreePath ?? branch}`; + const existingId = env.hostWorkspacesByKey.get(key); + if (existingId) { + return { + workspace: { id: existingId, branch }, + terminals: [], + warnings: [], + }; + } + if (existingWorkspaceId) { + env.hostWorkspacesByKey.set(key, existingWorkspaceId); + return { + workspace: { id: existingWorkspaceId, branch }, + terminals: [], + warnings: [], + }; + } + const workspaceId = nextId("v2-ws"); + env.createdWorkspaceIds.push(workspaceId); + env.hostWorkspacesByKey.set(key, workspaceId); return { - workspace: { id: nextId("v2-ws"), branch }, + workspace: { id: workspaceId, branch }, terminals: [], warnings: [], }; @@ -278,7 +380,7 @@ describe("migrateV1DataToV2", () => { expect(env.state.get("project:p1")?.status).toBe("linked"); }); - test("CONFLICT on setup after link is swallowed (already set up elsewhere)", async () => { + test("CONFLICT on setup after link records an error", async () => { const env = makeFakeEnv({ v1Projects: [project("p1")], findByPath: new Map([ @@ -294,8 +396,10 @@ describe("migrateV1DataToV2", () => { collections: makeCollections(), }); - expect(summary.projectsLinked).toBe(1); - expect(summary.errors).toHaveLength(0); + expect(summary.projectsLinked).toBe(0); + expect(summary.projectsErrored).toBe(1); + expect(summary.errors).toHaveLength(1); + expect(env.state.get("project:p1")?.status).toBe("error"); }); test("project create failure records error and skips its workspaces", async () => { @@ -338,6 +442,7 @@ describe("migrateV1DataToV2", () => { }), ], v1Worktrees: [], + adoptThrowsFor: new Map([["branch-w-orphan", { code: "NOT_FOUND" }]]), }); const summary = await migrateV1DataToV2({ @@ -349,7 +454,39 @@ describe("migrateV1DataToV2", () => { expect(summary.workspacesSkipped).toBe(1); expect(summary.workspacesCreated).toBe(0); - expect(env.state.get("workspace:w-orphan")?.reason).toBe("orphan_worktree"); + expect(env.state.get("workspace:w-orphan")?.reason).toBe( + "worktree_not_registered", + ); + }); + + test("missing v1 worktree row falls back to branch adoption", async () => { + const env = makeFakeEnv({ + v1Projects: [project("p1")], + v1Workspaces: [ + workspace("w1", "p1", { + type: "worktree", + worktreeId: "missing", + }), + ], + v1Worktrees: [], + }); + + const summary = await migrateV1DataToV2({ + organizationId: ORG, + electronTrpc: makeElectronTrpc(env), + hostService: makeHostService(env), + collections: makeCollections(), + }); + + expect(summary.workspacesCreated).toBe(1); + expect(summary.workspacesSkipped).toBe(0); + expect(env.adoptCalls).toContainEqual({ + projectId: "v2-proj-1", + branch: "branch-w1", + worktreePath: undefined, + baseBranch: undefined, + }); + expect(env.state.get("workspace:w1")?.status).toBe("success"); }); test("adopt NOT_FOUND is skipped, not errored", async () => { @@ -380,6 +517,45 @@ describe("migrateV1DataToV2", () => { ); }); + test("stale v1 worktree path falls back to branch adoption", async () => { + const env = makeFakeEnv({ + v1Projects: [project("p1")], + v1Workspaces: [ + workspace("w1", "p1", { + worktreeId: "wt1", + type: "worktree", + }), + ], + v1Worktrees: [{ id: "wt1", path: "/stale-worktree" }], + adoptThrowsForPath: new Map([["/stale-worktree", { code: "NOT_FOUND" }]]), + }); + + const summary = await migrateV1DataToV2({ + organizationId: ORG, + electronTrpc: makeElectronTrpc(env), + hostService: makeHostService(env), + collections: makeCollections(), + }); + + expect(summary.workspacesCreated).toBe(1); + expect(summary.workspacesSkipped).toBe(0); + expect(env.adoptCalls).toEqual([ + { + projectId: "v2-proj-1", + branch: "branch-w1", + worktreePath: "/stale-worktree", + baseBranch: undefined, + }, + { + projectId: "v2-proj-1", + branch: "branch-w1", + worktreePath: undefined, + baseBranch: undefined, + }, + ]); + expect(env.state.get("workspace:w1")?.status).toBe("success"); + }); + test("adopt non-NOT_FOUND error is recorded as error", async () => { const env = makeFakeEnv({ v1Projects: [project("p1")], @@ -408,23 +584,38 @@ describe("migrateV1DataToV2", () => { expect(env.state.get("workspace:w1")?.status).toBe("error"); }); - test("other-org guard rejects migration", async () => { + test("other-org state does not block migration for the active organization", async () => { const env = makeFakeEnv({ - otherOrg: "some-other-org", v1Projects: [project("p1")], + state: new Map([ + [ + "project:p1", + { + v1Id: "p1", + v2Id: "v2-other-org-project", + organizationId: "some-other-org", + kind: "project", + status: "success", + reason: null, + }, + ], + ]), }); - await expect( - migrateV1DataToV2({ - organizationId: ORG, - electronTrpc: makeElectronTrpc(env), - hostService: makeHostService(env), - collections: makeCollections(), - }), - ).rejects.toThrow(/already been migrated/); + const summary = await migrateV1DataToV2({ + organizationId: ORG, + electronTrpc: makeElectronTrpc(env), + hostService: makeHostService(env), + collections: makeCollections(), + }); + + expect(summary.projectsCreated).toBe(1); + expect(summary.errors).toHaveLength(0); + expect(env.state.get("project:p1")?.organizationId).toBe(ORG); + expect(env.state.get("project:p1")?.status).toBe("success"); }); - test("rerun skips rows already in success/linked state, retries error rows", async () => { + test("rerun skips rows already in success/linked state, retries error rows and skipped workspaces", async () => { // Pre-populate state as if a prior run completed p1 but errored on p2. const prior = new Map([ [ @@ -449,9 +640,24 @@ describe("migrateV1DataToV2", () => { reason: "prior failure", }, ], + [ + "workspace:w2", + { + v1Id: "w2", + v2Id: null, + organizationId: ORG, + kind: "workspace", + status: "skipped", + reason: "parent_project_unresolved", + }, + ], ]); const env = makeFakeEnv({ v1Projects: [project("p1"), project("p2")], + v1Workspaces: [ + workspace("w2", "p2", { worktreeId: "wt2", type: "worktree" }), + ], + v1Worktrees: [{ id: "wt2", path: "/worktrees/w2" }], state: prior, }); @@ -464,8 +670,402 @@ describe("migrateV1DataToV2", () => { // p1 was success → skipped. p2 was error → retried and succeeded this run. expect(summary.projectsCreated).toBe(1); + expect(summary.workspacesCreated).toBe(1); expect(env.state.get("project:p2")?.status).toBe("success"); + expect(env.state.get("workspace:w2")?.status).toBe("success"); expect(env.state.get("project:p1")?.status).toBe("success"); // unchanged + expect(env.setupCalls).toContainEqual({ + projectId: "v2-p1", + repoPath: "/repos/p1", + }); + }); + + test("idempotent rerun reports already synced rows without counting them as changes", async () => { + const env = makeFakeEnv({ + v1Projects: [project("p1")], + v1Workspaces: [ + workspace("w1", "p1", { worktreeId: "wt1", type: "worktree" }), + ], + v1Worktrees: [{ id: "wt1", path: "/worktrees/w1" }], + hostWorkspacesByKey: new Map([["v2-p1:/worktrees/w1", "v2-w1"]]), + state: new Map([ + [ + "project:p1", + { + v1Id: "p1", + v2Id: "v2-p1", + organizationId: ORG, + kind: "project", + status: "success", + reason: null, + }, + ], + [ + "workspace:w1", + { + v1Id: "w1", + v2Id: "v2-w1", + organizationId: ORG, + kind: "workspace", + status: "success", + reason: null, + }, + ], + ]), + }); + + const summary = await migrateV1DataToV2({ + organizationId: ORG, + electronTrpc: makeElectronTrpc(env), + hostService: makeHostService(env), + collections: makeCollections(), + }); + + expect(summary.projectsCreated).toBe(0); + expect(summary.projectsLinked).toBe(0); + expect(summary.workspacesCreated).toBe(0); + expect(summary.workspacesSkipped).toBe(0); + expect(summary.projects).toEqual([ + { name: "project-p1", status: "synced", reason: "Already imported" }, + ]); + expect(summary.workspaces).toEqual([ + { + name: "workspace-w1", + branch: "branch-w1", + status: "synced", + reason: "Already imported", + }, + ]); + expect(env.adoptCalls).toHaveLength(0); + }); + + test("running a completed migration again does not create duplicate projects or workspaces", async () => { + const env = makeFakeEnv({ + v1Projects: [project("p1")], + v1Workspaces: [ + workspace("w1", "p1", { worktreeId: "wt1", type: "worktree" }), + ], + v1Worktrees: [{ id: "wt1", path: "/worktrees/w1" }], + }); + const electronTrpc = makeElectronTrpc(env); + const hostService = makeHostService(env); + const collections = makeCollections(); + + const first = await migrateV1DataToV2({ + organizationId: ORG, + electronTrpc, + hostService, + collections, + }); + + expect(first.projectsCreated).toBe(1); + expect(first.workspacesCreated).toBe(1); + expect(env.createCalls).toHaveLength(1); + expect(env.adoptCalls).toHaveLength(1); + expect(env.createdWorkspaceIds).toHaveLength(1); + + const second = await migrateV1DataToV2({ + organizationId: ORG, + electronTrpc, + hostService, + collections, + }); + + expect(second.projectsCreated).toBe(0); + expect(second.projectsLinked).toBe(0); + expect(second.workspacesCreated).toBe(0); + expect(second.workspacesErrored).toBe(0); + expect(second.projects).toEqual([ + { name: "project-p1", status: "synced", reason: "Already imported" }, + ]); + expect(second.workspaces).toEqual([ + { + name: "workspace-w1", + branch: "branch-w1", + status: "synced", + reason: "Already imported", + }, + ]); + expect(env.createCalls).toHaveLength(1); + expect(env.adoptCalls).toHaveLength(1); + expect(env.createdWorkspaceIds).toHaveLength(1); + expect(env.setupCalls).toEqual([ + { projectId: "v2-proj-1", repoPath: "/repos/p1" }, + ]); + }); + + test("project state write failure does not migrate child workspaces until rerun reconciles the project", async () => { + const env = makeFakeEnv({ + v1Projects: [project("p1")], + v1Workspaces: [ + workspace("w1", "p1", { worktreeId: "wt1", type: "worktree" }), + ], + v1Worktrees: [{ id: "wt1", path: "/worktrees/w1" }], + failNextStateWriteFor: new Set(["project:p1"]), + }); + const electronTrpc = makeElectronTrpc(env); + const hostService = makeHostService(env); + + const first = await migrateV1DataToV2({ + organizationId: ORG, + electronTrpc, + hostService, + collections: makeCollections(), + }); + + expect(first.projectsErrored).toBe(1); + expect(first.workspacesSkipped).toBe(1); + expect(env.createCalls).toHaveLength(1); + expect(env.createdProjectIds).toEqual(["v2-proj-1"]); + expect(env.adoptCalls).toHaveLength(0); + expect(env.state.get("project:p1")?.status).toBe("error"); + expect(env.state.get("workspace:w1")?.reason).toBe( + "parent_project_unresolved", + ); + + const second = await migrateV1DataToV2({ + organizationId: ORG, + electronTrpc, + hostService, + collections: makeCollections(), + }); + + expect(second.projectsLinked).toBe(1); + expect(second.workspacesCreated).toBe(1); + expect(env.createCalls).toHaveLength(1); + expect(env.createdProjectIds).toEqual(["v2-proj-1"]); + expect(env.state.get("project:p1")?.v2Id).toBe("v2-proj-1"); + expect(env.state.get("project:p1")?.status).toBe("linked"); + expect(env.state.get("workspace:w1")?.status).toBe("success"); + }); + + test("workspace state write failure reruns adoption without creating a duplicate workspace", async () => { + const env = makeFakeEnv({ + v1Projects: [project("p1")], + v1Workspaces: [ + workspace("w1", "p1", { worktreeId: "wt1", type: "worktree" }), + ], + v1Worktrees: [{ id: "wt1", path: "/worktrees/w1" }], + failNextStateWriteFor: new Set(["workspace:w1"]), + }); + const electronTrpc = makeElectronTrpc(env); + const hostService = makeHostService(env); + + const first = await migrateV1DataToV2({ + organizationId: ORG, + electronTrpc, + hostService, + collections: makeCollections(), + }); + + expect(first.workspacesErrored).toBe(1); + expect(env.createdWorkspaceIds).toEqual(["v2-ws-2"]); + expect(env.state.get("workspace:w1")?.status).toBe("error"); + + const second = await migrateV1DataToV2({ + organizationId: ORG, + electronTrpc, + hostService, + collections: makeCollections(), + }); + + expect(second.workspacesCreated).toBe(1); + expect(env.createdWorkspaceIds).toEqual(["v2-ws-2"]); + expect(env.state.get("workspace:w1")?.status).toBe("success"); + expect(env.state.get("workspace:w1")?.v2Id).toBe("v2-ws-2"); + }); + + test("completed workspace with missing host db row is relinked to existing cloud workspace", async () => { + const env = makeFakeEnv({ + v1Projects: [project("p1")], + v1Workspaces: [ + workspace("w1", "p1", { worktreeId: "wt1", type: "worktree" }), + ], + v1Worktrees: [{ id: "wt1", path: "/worktrees/w1" }], + state: new Map([ + [ + "project:p1", + { + v1Id: "p1", + v2Id: "v2-p1", + organizationId: ORG, + kind: "project", + status: "success", + reason: null, + }, + ], + [ + "workspace:w1", + { + v1Id: "w1", + v2Id: "v2-ws-existing", + organizationId: ORG, + kind: "workspace", + status: "success", + reason: null, + }, + ], + ]), + }); + + const summary = await migrateV1DataToV2({ + organizationId: ORG, + electronTrpc: makeElectronTrpc(env), + hostService: makeHostService(env), + collections: makeCollections(), + }); + + expect(summary.workspacesCreated).toBe(1); + expect(env.createdWorkspaceIds).toEqual([]); + expect(env.adoptCalls).toContainEqual({ + projectId: "v2-p1", + branch: "branch-w1", + worktreePath: "/worktrees/w1", + baseBranch: undefined, + existingWorkspaceId: "v2-ws-existing", + }); + expect(env.state.get("workspace:w1")?.status).toBe("success"); + expect(env.state.get("workspace:w1")?.v2Id).toBe("v2-ws-existing"); + }); + + test("rerun retries previous worktree skips so old skipped state can recover", async () => { + const env = makeFakeEnv({ + v1Projects: [project("p1")], + v1Workspaces: [ + workspace("w-orphan", "p1", { + type: "worktree", + worktreeId: "missing", + }), + ], + v1Worktrees: [], + state: new Map([ + [ + "project:p1", + { + v1Id: "p1", + v2Id: "v2-p1", + organizationId: ORG, + kind: "project", + status: "success", + reason: null, + }, + ], + [ + "workspace:w-orphan", + { + v1Id: "w-orphan", + v2Id: null, + organizationId: ORG, + kind: "workspace", + status: "skipped", + reason: "orphan_worktree", + }, + ], + ]), + }); + + const summary = await migrateV1DataToV2({ + organizationId: ORG, + electronTrpc: makeElectronTrpc(env), + hostService: makeHostService(env), + collections: makeCollections(), + }); + + expect(summary.workspacesCreated).toBe(1); + expect(summary.workspacesSkipped).toBe(0); + expect(env.adoptCalls).toContainEqual({ + projectId: "v2-p1", + branch: "branch-w-orphan", + worktreePath: undefined, + baseBranch: undefined, + }); + expect(env.state.get("workspace:w-orphan")?.status).toBe("success"); + }); + + test("failed retry of previous missing-worktree skip does not count as new work", async () => { + const env = makeFakeEnv({ + v1Projects: [project("p1")], + v1Workspaces: [ + workspace("w-orphan", "p1", { + type: "worktree", + worktreeId: "missing", + }), + ], + v1Worktrees: [], + adoptThrowsFor: new Map([["branch-w-orphan", { code: "NOT_FOUND" }]]), + state: new Map([ + [ + "project:p1", + { + v1Id: "p1", + v2Id: "v2-p1", + organizationId: ORG, + kind: "project", + status: "success", + reason: null, + }, + ], + [ + "workspace:w-orphan", + { + v1Id: "w-orphan", + v2Id: null, + organizationId: ORG, + kind: "workspace", + status: "skipped", + reason: "worktree_not_registered", + }, + ], + ]), + }); + + const summary = await migrateV1DataToV2({ + organizationId: ORG, + electronTrpc: makeElectronTrpc(env), + hostService: makeHostService(env), + collections: makeCollections(), + }); + + expect(summary.workspacesCreated).toBe(0); + expect(summary.workspacesSkipped).toBe(0); + expect(env.adoptCalls).toHaveLength(1); + expect(summary.workspaces).toHaveLength(1); + expect(summary.workspaces).toContainEqual({ + name: "workspace-w-orphan", + branch: "branch-w-orphan", + status: "skipped", + reason: "worktree no longer exists", + }); + expect(env.state.get("workspace:w-orphan")?.status).toBe("skipped"); + expect(env.state.get("workspace:w-orphan")?.reason).toBe( + "worktree_not_registered", + ); + }); + + test("passes v1 worktree base branch into adoption", async () => { + const env = makeFakeEnv({ + v1Projects: [project("p1")], + v1Workspaces: [ + workspace("w1", "p1", { worktreeId: "wt1", type: "worktree" }), + ], + v1Worktrees: [ + { id: "wt1", path: "/worktrees/w1", baseBranch: "develop" }, + ], + }); + + await migrateV1DataToV2({ + organizationId: ORG, + electronTrpc: makeElectronTrpc(env), + hostService: makeHostService(env), + collections: makeCollections(), + }); + + expect(env.adoptCalls).toContainEqual({ + projectId: "v2-proj-1", + branch: "branch-w1", + worktreePath: "/worktrees/w1", + baseBranch: "develop", + }); }); test("workspace with sectionId that lacks a v1 section record lands at top level", async () => { diff --git a/apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.ts b/apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.ts index f64b373f530..09fb79ba456 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/migrate.ts @@ -5,8 +5,8 @@ import { writeV2SidebarState } from "./writeSidebarState"; type ElectronTrpcClient = typeof electronTrpcClient; -export type ProjectStatus = "created" | "linked" | "error"; -export type WorkspaceStatus = "adopted" | "skipped" | "error"; +export type ProjectStatus = "created" | "linked" | "synced" | "error"; +export type WorkspaceStatus = "adopted" | "synced" | "skipped" | "error"; export interface ProjectEntry { name: string; @@ -62,6 +62,120 @@ function errorMessage(err: unknown): string { return String(err); } +async function setupProjectImport( + hostService: HostServiceClient, + projectId: string, + repoPath: string, +): Promise { + await hostService.project.setup.mutate({ + projectId, + mode: { kind: "import", repoPath }, + }); +} + +function shouldRetryWorkspace( + existing: { status: string; reason?: string | null } | undefined, +): boolean { + if (!existing) return true; + if (existing.status === "success") return false; + if (existing.status === "error") return true; + return ( + existing.status === "skipped" && + (existing.reason === "parent_project_unresolved" || + existing.reason === "orphan_worktree" || + existing.reason === "worktree_not_registered") + ); +} + +async function hasLocalWorkspace( + hostService: HostServiceClient, + workspaceId: string, +): Promise { + try { + await hostService.workspace.get.query({ id: workspaceId }); + return true; + } catch (err) { + if (trpcCode(err) === "NOT_FOUND") return false; + throw err; + } +} + +function addProjectError( + summary: MigrationSummary, + name: string, + message: string, +): void { + summary.projectsErrored += 1; + summary.projects.push({ + name, + status: "error", + reason: message, + }); + summary.errors.push({ + kind: "project", + name, + message, + }); +} + +function addWorkspaceSkip( + summary: MigrationSummary, + name: string, + branch: string, + reason: string, +): void { + summary.workspacesSkipped += 1; + summary.workspaces.push({ + name, + branch, + status: "skipped", + reason, + }); +} + +function skippedWorkspaceReason(reason: string | null | undefined): string { + switch (reason) { + case "orphan_worktree": + return "worktree record missing"; + case "worktree_not_registered": + return "worktree no longer exists"; + case "parent_project_unresolved": + return "parent project did not migrate"; + default: + return reason ?? "skipped"; + } +} + +function wasAlreadyMissingWorktreeSkip( + existing: { status: string; reason?: string | null } | undefined, +): boolean { + return ( + existing?.status === "skipped" && + (existing.reason === "orphan_worktree" || + existing.reason === "worktree_not_registered") + ); +} + +function addWorkspaceError( + summary: MigrationSummary, + name: string, + branch: string, + message: string, +): void { + summary.workspacesErrored += 1; + summary.workspaces.push({ + name, + branch, + status: "error", + reason: message, + }); + summary.errors.push({ + kind: "workspace", + name, + message, + }); +} + interface Args { organizationId: string; electronTrpc: ElectronTrpcClient; @@ -73,28 +187,14 @@ export async function migrateV1DataToV2(args: Args): Promise { const { organizationId, electronTrpc, hostService, collections } = args; const summary = emptySummary(); - const [ - v1Projects, - v1Workspaces, - v1Worktrees, - v1Sections, - existingState, - otherOrg, - ] = await Promise.all([ - electronTrpc.migration.readV1Projects.query(), - electronTrpc.migration.readV1Workspaces.query(), - electronTrpc.migration.readV1Worktrees.query(), - electronTrpc.migration.readV1WorkspaceSections.query(), - electronTrpc.migration.listState.query({ organizationId }), - electronTrpc.migration.findMigrationByOtherOrg.query({ organizationId }), - ]); - - if (otherOrg) { - throw new Error( - `v1 data has already been migrated to organization ${otherOrg}. ` + - "Contact support if you need to migrate to a different organization.", - ); - } + const [v1Projects, v1Workspaces, v1Worktrees, v1Sections, existingState] = + await Promise.all([ + electronTrpc.migration.readV1Projects.query(), + electronTrpc.migration.readV1Workspaces.query(), + electronTrpc.migration.readV1Worktrees.query(), + electronTrpc.migration.readV1WorkspaceSections.query(), + electronTrpc.migration.listState.query({ organizationId }), + ]); const stateByKey = new Map(); for (const row of existingState) { @@ -106,7 +206,11 @@ export async function migrateV1DataToV2(args: Args): Promise { const projectV1ToV2 = new Map(); for (const row of existingState) { - if (row.kind === "project" && row.v2Id) { + if ( + row.kind === "project" && + row.v2Id && + (row.status === "success" || row.status === "linked") + ) { projectV1ToV2.set(row.v1Id, row.v2Id); } } @@ -121,7 +225,40 @@ export async function migrateV1DataToV2(args: Args): Promise { for (const project of v1Projects) { const key = `project:${project.id}`; const existing = stateByKey.get(key); - if (existing && existing.status !== "error") { + if ( + existing?.v2Id && + (existing.status === "success" || existing.status === "linked") + ) { + try { + await setupProjectImport( + hostService, + existing.v2Id, + project.mainRepoPath, + ); + projectV1ToV2.set(project.id, existing.v2Id); + summary.projects.push({ + name: project.name, + status: "synced", + reason: "Already imported", + }); + } catch (err) { + const message = errorMessage(err); + await electronTrpc.migration.upsertState.mutate({ + v1Id: project.id, + kind: "project", + v2Id: existing.v2Id, + organizationId, + status: "error", + reason: message, + }); + projectV1ToV2.delete(project.id); + addProjectError(summary, project.name, message); + console.error( + "[v1-migration] existing project setup failed", + project.name, + err, + ); + } continue; } @@ -137,23 +274,17 @@ export async function migrateV1DataToV2(args: Args): Promise { const candidate = found.candidates[0]; if (!candidate) throw new Error("findByPath returned empty candidate"); if (found.candidates.length > 1) { - // Shouldn't happen — v2 has a unique index on - // (organization_id, lower(repo_clone_url)). Log so it's - // diagnosable if the constraint ever slips. console.warn( - `[v1-migration] findByPath for ${project.mainRepoPath} returned ${found.candidates.length} candidates; linking to first (${candidate.id})`, + `[v1-migration] findByPath for ${project.mainRepoPath} returned ${found.candidates.length} candidates; migration has no project picker, linking to first (${candidate.id})`, ); } v2ProjectId = candidate.id; status = "linked"; - try { - await hostService.project.setup.mutate({ - projectId: candidate.id, - mode: { kind: "import", repoPath: project.mainRepoPath }, - }); - } catch (err) { - if (trpcCode(err) !== "CONFLICT") throw err; - } + await setupProjectImport( + hostService, + candidate.id, + project.mainRepoPath, + ); } else { const created = await hostService.project.create.mutate({ name: project.name, @@ -166,7 +297,6 @@ export async function migrateV1DataToV2(args: Args): Promise { status = "success"; } - projectV1ToV2.set(project.id, v2ProjectId); await electronTrpc.migration.upsertState.mutate({ v1Id: project.id, kind: "project", @@ -175,6 +305,7 @@ export async function migrateV1DataToV2(args: Args): Promise { status, reason: null, }); + projectV1ToV2.set(project.id, v2ProjectId); if (status === "success") { summary.projectsCreated += 1; summary.projects.push({ name: project.name, status: "created" }); @@ -184,6 +315,7 @@ export async function migrateV1DataToV2(args: Args): Promise { } } catch (err) { const message = errorMessage(err); + projectV1ToV2.delete(project.id); await electronTrpc.migration.upsertState.mutate({ v1Id: project.id, kind: "project", @@ -192,17 +324,7 @@ export async function migrateV1DataToV2(args: Args): Promise { status: "error", reason: message, }); - summary.projectsErrored += 1; - summary.projects.push({ - name: project.name, - status: "error", - reason: message, - }); - summary.errors.push({ - kind: "project", - name: project.name, - message, - }); + addProjectError(summary, project.name, message); console.error("[v1-migration] project failed", project.name, err); } } @@ -210,7 +332,48 @@ export async function migrateV1DataToV2(args: Args): Promise { for (const workspace of v1Workspaces) { const key = `workspace:${workspace.id}`; const existing = stateByKey.get(key); - if (existing && existing.status !== "error") { + let recoverCompletedWorkspace = false; + if (existing?.status === "success" && existing.v2Id) { + try { + if (await hasLocalWorkspace(hostService, existing.v2Id)) { + workspaceV1ToV2.set(workspace.id, existing.v2Id); + summary.workspaces.push({ + name: workspace.name, + branch: workspace.branch, + status: "synced", + reason: "Already imported", + }); + continue; + } + recoverCompletedWorkspace = true; + } catch (err) { + const message = errorMessage(err); + await electronTrpc.migration.upsertState.mutate({ + v1Id: workspace.id, + kind: "workspace", + v2Id: existing.v2Id, + organizationId, + status: "error", + reason: message, + }); + addWorkspaceError(summary, workspace.name, workspace.branch, message); + console.error( + "[v1-migration] workspace local reconciliation failed", + workspace.name, + err, + ); + continue; + } + } + if (!recoverCompletedWorkspace && !shouldRetryWorkspace(existing)) { + if (existing?.status === "skipped") { + summary.workspaces.push({ + name: workspace.name, + branch: workspace.branch, + status: "skipped", + reason: skippedWorkspaceReason(existing.reason), + }); + } continue; } @@ -224,88 +387,67 @@ export async function migrateV1DataToV2(args: Args): Promise { status: "skipped", reason: "parent_project_unresolved", }); - summary.workspacesSkipped += 1; - summary.workspaces.push({ - name: workspace.name, - branch: workspace.branch, - status: "skipped", - reason: "parent project did not migrate", - }); + addWorkspaceSkip( + summary, + workspace.name, + workspace.branch, + "parent project did not migrate", + ); continue; } - if (workspace.type === "worktree") { - if (!workspace.worktreeId || !worktreesById.has(workspace.worktreeId)) { - await electronTrpc.migration.upsertState.mutate({ - v1Id: workspace.id, - kind: "workspace", - v2Id: null, - organizationId, - status: "skipped", - reason: "orphan_worktree", - }); - summary.workspacesSkipped += 1; - summary.workspaces.push({ - name: workspace.name, - branch: workspace.branch, - status: "skipped", - reason: "worktree record missing", - }); - continue; - } - } - - // For type="worktree" workspaces, use the associated worktree's path. - // For type="branch" workspaces (no worktreeId), fall back to the parent - // project's mainRepoPath so adopt() can resolve the branch inside the - // primary checkout (listWorktreeBranches excludes the primary working tree). - // Without this, branch-type workspaces skip with "worktree_not_registered". - const v1WorktreePath = workspace.worktreeId - ? worktreesById.get(workspace.worktreeId)?.path - : workspace.type === "branch" + const v1Worktree = workspace.worktreeId + ? worktreesById.get(workspace.worktreeId) + : undefined; + // FORK NOTE: branch-type workspaces (no worktreeId) fall back to the + // parent project's mainRepoPath so adopt() can resolve the branch + // inside the primary checkout. listWorktreeBranches excludes the + // primary working tree, otherwise these workspaces skip with + // "worktree_not_registered". + const v1WorktreePath = + v1Worktree?.path ?? + (workspace.type === "branch" ? v1Projects.find((p) => p.id === workspace.projectId)?.mainRepoPath - : undefined; + : undefined); + const v1BaseBranch = v1Worktree?.baseBranch; - try { - const result = await hostService.workspaceCreation.adopt.mutate({ + const adoptWorkspace = (worktreePath: string | undefined) => + hostService.workspaceCreation.adopt.mutate({ projectId: v2ProjectId, workspaceName: workspace.name, branch: workspace.branch, - worktreePath: v1WorktreePath, + baseBranch: v1BaseBranch ?? undefined, + existingWorkspaceId: existing?.v2Id ?? undefined, + worktreePath, }); - await electronTrpc.migration.upsertState.mutate({ - v1Id: workspace.id, - kind: "workspace", - v2Id: result.workspace.id, - organizationId, - status: "success", - reason: null, - }); - workspaceV1ToV2.set(workspace.id, result.workspace.id); - summary.workspacesCreated += 1; - summary.workspaces.push({ - name: workspace.name, - branch: workspace.branch, - status: "adopted", - }); - } catch (err) { + + const recordAdoptFailure = async (err: unknown) => { if (trpcCode(err) === "NOT_FOUND") { + const reason = "worktree_not_registered"; await electronTrpc.migration.upsertState.mutate({ v1Id: workspace.id, kind: "workspace", v2Id: null, organizationId, status: "skipped", - reason: "worktree_not_registered", + reason, }); - summary.workspacesSkipped += 1; - summary.workspaces.push({ - name: workspace.name, - branch: workspace.branch, - status: "skipped", - reason: "worktree no longer exists", - }); - continue; + if (wasAlreadyMissingWorktreeSkip(existing)) { + summary.workspaces.push({ + name: workspace.name, + branch: workspace.branch, + status: "skipped", + reason: skippedWorkspaceReason(reason), + }); + return; + } + addWorkspaceSkip( + summary, + workspace.name, + workspace.branch, + "worktree no longer exists", + ); + return; } const message = errorMessage(err); await electronTrpc.migration.upsertState.mutate({ @@ -316,19 +458,41 @@ export async function migrateV1DataToV2(args: Args): Promise { status: "error", reason: message, }); - summary.workspacesErrored += 1; + addWorkspaceError(summary, workspace.name, workspace.branch, message); + console.error("[v1-migration] workspace failed", workspace.name, err); + }; + + try { + let result: Awaited>; + try { + result = await adoptWorkspace(v1WorktreePath); + } catch (err) { + if (trpcCode(err) !== "NOT_FOUND" || !v1WorktreePath) { + throw err; + } + + // v1 worktree rows can be stale while git still has the branch + // registered at a different path. Retry by branch before giving up. + result = await adoptWorkspace(undefined); + } + + await electronTrpc.migration.upsertState.mutate({ + v1Id: workspace.id, + kind: "workspace", + v2Id: result.workspace.id, + organizationId, + status: "success", + reason: null, + }); + workspaceV1ToV2.set(workspace.id, result.workspace.id); + summary.workspacesCreated += 1; summary.workspaces.push({ name: workspace.name, branch: workspace.branch, - status: "error", - reason: message, - }); - summary.errors.push({ - kind: "workspace", - name: workspace.name, - message, + status: "adopted", }); - console.error("[v1-migration] workspace failed", workspace.name, err); + } catch (err) { + await recordAdoptFailure(err); } } diff --git a/apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/useMigrateV1DataToV2.ts b/apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/useMigrateV1DataToV2.ts index 7e125a8d436..fd97482adfa 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/useMigrateV1DataToV2.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/hooks/useMigrateV1DataToV2/useMigrateV1DataToV2.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { env } from "renderer/env.renderer"; import { useIsV2CloudEnabled } from "renderer/hooks/useIsV2CloudEnabled"; import { authClient } from "renderer/lib/auth-client"; @@ -9,6 +9,10 @@ import { useLocalHostService } from "renderer/routes/_authenticated/providers/Lo import { MOCK_ORG_ID } from "shared/constants"; import { type MigrationSummary, migrateV1DataToV2 } from "./migrate"; +export type MigrationRunResult = + | { completed: true; summary: MigrationSummary } + | { completed: false; reason: string }; + function getAttemptKey(organizationId: string): string { return `v1-migration-attempted-${organizationId}`; } @@ -34,37 +38,66 @@ function persistSummary(organizationId: string, summary: MigrationSummary) { * with v2 enabled. Idempotent by design: * - sessionStorage marker dedups within a session (blocks strict-mode double-invoke) * - migration_state in the local DB tracks completed rows; subsequent runs - * skip rows already recorded as success/linked/skipped and retry error rows + * reconcile success/linked project rows, skip completed workspace rows, and + * retry error rows plus parent-dependent workspace skips * * Reruns happen implicitly on app relaunch. No automatic retry timer or online * listener — v2 requires the cloud to be reachable anyway, so a transient * offline error resolves on the next launch. */ -export function useMigrateV1DataToV2() { +export function useMigrateV1DataToV2({ + autoRun = true, +}: { + autoRun?: boolean; +} = {}) { const { data: session } = authClient.useSession(); const { activeHostUrl } = useLocalHostService(); const { isV2CloudEnabled } = useIsV2CloudEnabled(); const collections = useCollections(); + const [isRunning, setIsRunning] = useState(false); const organizationId = env.SKIP_ENV_VALIDATION ? MOCK_ORG_ID : (session?.session?.activeOrganizationId ?? null); const attemptedRef = useRef(null); + const isRunningRef = useRef(false); - useEffect(() => { - if (!isV2CloudEnabled) return; - if (!organizationId || !activeHostUrl) return; - if (attemptedRef.current === organizationId) return; + const runMigration = useCallback( + async ({ manual }: { manual: boolean }): Promise => { + if (!isV2CloudEnabled) { + return { completed: false, reason: "Superset v2 is not enabled" }; + } + if (!organizationId) { + return { completed: false, reason: "No active organization" }; + } + if (!activeHostUrl) { + return { completed: false, reason: "Host service is not ready" }; + } + if (isRunningRef.current) { + return { completed: false, reason: "Migration is already running" }; + } - const attemptKey = getAttemptKey(organizationId); - if (sessionStorage.getItem(attemptKey) === "1") { - attemptedRef.current = organizationId; - return; - } + const attemptKey = getAttemptKey(organizationId); + if (!manual) { + if (attemptedRef.current === organizationId) { + return { + completed: false, + reason: "Migration already ran in this session", + }; + } + if (sessionStorage.getItem(attemptKey) === "1") { + attemptedRef.current = organizationId; + return { + completed: false, + reason: "Migration already ran in this session", + }; + } + } - attemptedRef.current = organizationId; - sessionStorage.setItem(attemptKey, "1"); + attemptedRef.current = organizationId; + sessionStorage.setItem(attemptKey, "1"); + isRunningRef.current = true; + setIsRunning(true); - void (async () => { try { const hostService = getHostServiceClientByUrl(activeHostUrl); const summary = await migrateV1DataToV2({ @@ -80,22 +113,48 @@ export function useMigrateV1DataToV2() { const didAnything = summary.projectsCreated + summary.projectsLinked + - summary.workspacesCreated > + summary.projectsErrored + + summary.workspacesCreated + + summary.workspacesSkipped + + summary.workspacesErrored > 0; - if (didAnything) { + if (manual || didAnything) { persistSummary(organizationId, summary); } if (summary.errors.length > 0) { console.error("[v1-migration] errors", summary.errors); } + return { completed: true, summary }; } catch (err) { // Clear marker so a relaunch can retry (e.g., transient cloud unreach // before session fully hydrated). sessionStorage.removeItem(attemptKey); attemptedRef.current = null; console.error("[v1-migration] fatal", err); + const reason = err instanceof Error ? err.message : String(err); + return { completed: false, reason }; + } finally { + isRunningRef.current = false; + setIsRunning(false); } - })(); - }, [isV2CloudEnabled, organizationId, activeHostUrl, collections]); + }, + [activeHostUrl, collections, isV2CloudEnabled, organizationId], + ); + + useEffect(() => { + if (!autoRun) return; + void runMigration({ manual: false }); + }, [autoRun, runMigration]); + + const rerun = useCallback(async (): Promise => { + if (!organizationId) { + return { completed: false, reason: "No active organization" }; + } + sessionStorage.removeItem(getAttemptKey(organizationId)); + attemptedRef.current = null; + return runMigration({ manual: true }); + }, [organizationId, runMigration]); + + return { rerun, isRunning }; } diff --git a/apps/desktop/src/renderer/routes/_authenticated/hooks/useOptimisticCollectionActions/useOptimisticCollectionActions.ts b/apps/desktop/src/renderer/routes/_authenticated/hooks/useOptimisticCollectionActions/useOptimisticCollectionActions.ts index fcbb5f6657f..1a762a7434e 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/hooks/useOptimisticCollectionActions/useOptimisticCollectionActions.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/hooks/useOptimisticCollectionActions/useOptimisticCollectionActions.ts @@ -212,7 +212,6 @@ export function useOptimisticCollectionActions() { runUsersHostsMutation("Failed to add member", () => { const now = new Date(); return collections.v2UsersHosts.insert({ - id: crypto.randomUUID(), hostId: input.hostId, userId: input.userId, organizationId: input.organizationId, @@ -221,13 +220,13 @@ export function useOptimisticCollectionActions() { updatedAt: now, }); }), - removeMember: (rowId: string) => + removeMember: (rowKey: string) => runUsersHostsMutation("Failed to remove member", () => - collections.v2UsersHosts.delete(rowId), + collections.v2UsersHosts.delete(rowKey), ), - setMemberRole: (rowId: string, role: V2UsersHostRole) => + setMemberRole: (rowKey: string, role: V2UsersHostRole) => runUsersHostsMutation("Failed to update role", () => - collections.v2UsersHosts.update(rowId, (draft) => { + collections.v2UsersHosts.update(rowKey, (draft) => { draft.role = role; }), ), diff --git a/apps/desktop/src/renderer/routes/_authenticated/layout.tsx b/apps/desktop/src/renderer/routes/_authenticated/layout.tsx index fda0d4a21f1..d29cdcbb801 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/layout.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/layout.tsx @@ -25,7 +25,9 @@ import { showWorkspaceAutoNameWarningToast } from "renderer/lib/workspaces/showW import { LanguageServicesProvider } from "renderer/providers/LanguageServicesProvider"; import { InitGitDialog } from "renderer/react-query/projects/InitGitDialog"; import { DashboardNewWorkspaceModal } from "renderer/routes/_authenticated/components/DashboardNewWorkspaceModal"; +import { V1MigrationSummaryModal } from "renderer/routes/_authenticated/components/V1MigrationSummaryModal"; import { GitOperationDialog } from "renderer/screens/main/components/GitOperationDialog"; +import { WorkspaceInitEffects } from "renderer/screens/main/components/WorkspaceInitEffects"; import { useSettingsStore } from "renderer/stores/settings-state"; import { useTabsStore } from "renderer/stores/tabs/store"; import { useAgentHookListener } from "renderer/stores/tabs/useAgentHookListener"; @@ -224,6 +226,8 @@ function AuthenticatedLayout() { + + {isV2CloudEnabled ? ( ) : ( diff --git a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts index 03a9b45f119..aba18dad1a0 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts @@ -11,7 +11,6 @@ import type { SelectMember, SelectOrganization, SelectProject, - SelectSessionHost, SelectSubscription, SelectTask, SelectTaskStatus, @@ -101,7 +100,6 @@ export interface OrgCollections { subscriptions: Collection; apiKeys: Collection; chatSessions: Collection; - sessionHosts: Collection; githubRepositories: Collection; githubPullRequests: Collection; automations: Collection; @@ -302,7 +300,9 @@ function createOrgCollections(organizationId: string): OrgCollections { headers: electricHeaders, columnMapper, }, - getKey: (item) => item.id, + // Composite PK on (organization_id, machine_id); within an + // org-scoped collection, machineId alone is unique. + getKey: (item) => item.machineId, }), ); @@ -318,7 +318,9 @@ function createOrgCollections(organizationId: string): OrgCollections { headers: electricHeaders, columnMapper, }, - getKey: (item) => item.id, + // Composite PK on (organization_id, user_id, machine_id); within + // an org-scoped collection, (user_id, machine_id) is unique. + getKey: (item) => `${item.userId}:${item.machineId}`, }), ); @@ -334,11 +336,10 @@ function createOrgCollections(organizationId: string): OrgCollections { headers: electricHeaders, columnMapper, }, - getKey: (item) => item.id, + getKey: (item) => `${item.userId}:${item.hostId}`, onInsert: async ({ transaction }) => { const item = transaction.mutations[0].modified; const result = await apiClient.v2Host.addMember.mutate({ - id: item.id, hostId: item.hostId, userId: item.userId, role: item.role, @@ -557,22 +558,6 @@ function createOrgCollections(organizationId: string): OrgCollections { }), ); - const sessionHosts = createIndexedCollection( - electricCollectionOptions({ - id: `session_hosts-${organizationId}`, - shapeOptions: { - url: electricUrl, - params: { - table: "session_hosts", - organizationId, - }, - headers: electricHeaders, - columnMapper, - }, - getKey: (item) => item.id, - }), - ); - const githubRepositories = createIndexedCollection( electricCollectionOptions({ id: `github_repositories-${organizationId}`, @@ -712,7 +697,6 @@ function createOrgCollections(organizationId: string): OrgCollections { subscriptions, apiKeys, chatSessions, - sessionHosts, githubRepositories, githubPullRequests, automations, diff --git a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/index.ts b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/index.ts index 686fbd9e9f9..53b72976220 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/index.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/index.ts @@ -1 +1,2 @@ export * from "./schema"; +export * from "./sidebarVisibility"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts index 0d5ab69822b..144ce2648c1 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts @@ -38,6 +38,7 @@ export const workspaceLocalStateSchema = z.object({ changesFilter: changesFilterSchema.default({ kind: "all" }), activeTab: z.enum(["changes", "files"]).default("changes"), changesSubtab: z.enum(["diffs", "review"]).default("diffs"), + isHidden: z.boolean().default(false), }), paneLayout: paneWorkspaceStateSchema, rightSidebarOpen: z.boolean().default(false), diff --git a/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/sidebarVisibility.ts b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/sidebarVisibility.ts new file mode 100644 index 00000000000..9cf2b5c7d11 --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/sidebarVisibility.ts @@ -0,0 +1,24 @@ +type SidebarWorkspaceVisibilitySource = + | { isHidden?: boolean | null } + | { sidebarState: { isHidden?: boolean | null } }; + +export function getSidebarWorkspaceIsHidden( + workspace: SidebarWorkspaceVisibilitySource, +): boolean { + if ("sidebarState" in workspace) { + return workspace.sidebarState.isHidden === true; + } + return workspace.isHidden === true; +} + +export function isSidebarWorkspaceVisible( + workspace: SidebarWorkspaceVisibilitySource, +): boolean { + return !getSidebarWorkspaceIsHidden(workspace); +} + +export function getVisibleSidebarWorkspaces< + Workspace extends SidebarWorkspaceVisibilitySource, +>(workspaces: readonly Workspace[]): Workspace[] { + return workspaces.filter(isSidebarWorkspaceVisible); +} diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/HostSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/HostSettings.tsx index 6253dc77f0c..cfc6d6b81c4 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/HostSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/hosts/$hostId/components/HostSettings/HostSettings.tsx @@ -38,7 +38,7 @@ export function HostSettings({ hostId }: HostSettingsProps) { (q) => q .from({ hosts: collections.v2Hosts }) - .where(({ hosts }) => eq(hosts.id, hostId)) + .where(({ hosts }) => eq(hosts.machineId, hostId)) .select(({ hosts }) => ({ ...hosts })), [collections, hostId], ); @@ -85,7 +85,7 @@ export function HostSettings({ hostId }: HostSettingsProps) { .map((row) => { const u = userMap.get(row.userId); return { - usersHostsId: row.id, + usersHostsId: `${row.userId}:${row.hostId}`, userId: row.userId, role: row.role as "owner" | "member", name: u?.name ?? "Unknown user", diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/hosts/components/HostsSettingsSidebar/HostsSettingsSidebar.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/hosts/components/HostsSettingsSidebar/HostsSettingsSidebar.tsx index 4b224d58fb4..c30081b1170 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/hosts/components/HostsSettingsSidebar/HostsSettingsSidebar.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/hosts/components/HostsSettingsSidebar/HostsSettingsSidebar.tsx @@ -39,7 +39,7 @@ export function HostsSettingsSidebar({ eq(hosts.organizationId, activeOrganizationId ?? ""), ) .select(({ hosts }) => ({ - id: hosts.id, + id: hosts.machineId, name: hosts.name, machineId: hosts.machineId, isOnline: hosts.isOnline, diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/hosts/page.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/hosts/page.tsx index d83aac0f6f1..643c8e8adbd 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/hosts/page.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/hosts/page.tsx @@ -28,7 +28,7 @@ function HostsIndexPage() { eq(hosts.organizationId, activeOrganizationId ?? ""), ) .select(({ hosts }) => ({ - id: hosts.id, + id: hosts.machineId, name: hosts.name, isOnline: hosts.isOnline, })), diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/ProjectLocationSection/ProjectLocationSection.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/ProjectLocationSection/ProjectLocationSection.tsx index 0fa19d979e1..8f0f38e2c83 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/ProjectLocationSection/ProjectLocationSection.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/components/ProjectLocationSection/ProjectLocationSection.tsx @@ -16,6 +16,7 @@ import { useNavigate } from "@tanstack/react-router"; import { useState } from "react"; import { electronTrpc } from "renderer/lib/electron-trpc"; import { getHostServiceClientByUrl } from "renderer/lib/host-service-client"; +import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/useDashboardSidebarState"; import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider"; import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider"; import { ClickablePath } from "../../../../../../components/ClickablePath"; @@ -42,6 +43,8 @@ export function ProjectLocationSection({ const selectDirectory = electronTrpc.window.selectDirectory.useMutation(); const navigate = useNavigate(); const collections = useCollections(); + const { ensureProjectInSidebar, ensureWorkspaceInSidebar } = + useDashboardSidebarState(); const { data: hostRows } = useLiveQuery( (q) => @@ -76,6 +79,11 @@ export function ProjectLocationSection({ ? `Project relocated to ${result.repoPath}` : `Project set up at ${result.repoPath}`, ); + if (result.mainWorkspaceId) { + ensureWorkspaceInSidebar(result.mainWorkspaceId, projectId); + } else { + ensureProjectInSidebar(projectId); + } onChanged?.(); return true; } catch (err) { @@ -96,6 +104,11 @@ export function ProjectLocationSection({ mode: { kind: "clone", parentDir }, }); toast.success(`Cloned to ${result.repoPath}`); + if (result.mainWorkspaceId) { + ensureWorkspaceInSidebar(result.mainWorkspaceId, projectId); + } else { + ensureProjectInSidebar(projectId); + } onChanged?.(); return true; } catch (err) { diff --git a/apps/electric-proxy/src/where.ts b/apps/electric-proxy/src/where.ts index 717a58c5460..12c31e66b20 100644 --- a/apps/electric-proxy/src/where.ts +++ b/apps/electric-proxy/src/where.ts @@ -11,7 +11,6 @@ import { members, organizations, projects, - sessionHosts, subscriptions, taskStatuses, tasks, @@ -129,9 +128,6 @@ export function buildWhereClause( case "chat_sessions": return build(chatSessions, chatSessions.organizationId, organizationId); - case "session_hosts": - return build(sessionHosts, sessionHosts.organizationId, organizationId); - case "github_repositories": return build( githubRepositories, diff --git a/apps/relay/src/access.ts b/apps/relay/src/access.ts index b80a75556bc..b8b0d793718 100644 --- a/apps/relay/src/access.ts +++ b/apps/relay/src/access.ts @@ -15,7 +15,7 @@ export async function checkHostAccess( try { const client = createApiClient(token); - const result = await client.device.checkHostAccess.query({ hostId }); + const result = await client.host.checkAccess.query({ hostId }); if (result.allowed) { allowedCache.set(key, true); } diff --git a/apps/relay/src/tunnel.ts b/apps/relay/src/tunnel.ts index e9b8593333c..9eeaac1ce00 100644 --- a/apps/relay/src/tunnel.ts +++ b/apps/relay/src/tunnel.ts @@ -62,7 +62,7 @@ export class TunnelManager { }, PING_INTERVAL_MS); void createApiClient(token) - .device.setHostOnline.mutate({ hostId, isOnline: true }) + .host.setOnline.mutate({ hostId, isOnline: true }) .catch(() => {}); console.log(`[relay] tunnel registered: ${hostId}`); } @@ -83,7 +83,7 @@ export class TunnelManager { } void createApiClient(tunnel.token) - .device.setHostOnline.mutate({ hostId, isOnline: false }) + .host.setOnline.mutate({ hostId, isOnline: false }) .catch(() => {}); this.tunnels.delete(hostId); console.log(`[relay] tunnel unregistered: ${hostId}`); diff --git a/bun.lock b/bun.lock index 969a2fd7596..1aedc17e7dd 100644 --- a/bun.lock +++ b/bun.lock @@ -152,6 +152,7 @@ "@hookform/resolvers": "^5.2.2", "@lezer/highlight": "^1.2.3", "@mastra/core": "1.26.0-alpha.3", + "@paper-design/shaders-react": "^0.0.76", "@parcel/watcher": "^2.5.6", "@pierre/diffs": "1.1.3", "@radix-ui/react-dialog": "^1.1.15", @@ -962,9 +963,12 @@ "@t3-oss/env-core": "^0.13.8", "@trpc/server": "^11.7.1", "@upstash/qstash": "^2.8.4", + "@upstash/ratelimit": "^2.0.4", + "@upstash/redis": "^1.34.3", "@vercel/blob": "^2.0.0", "@vercel/kv": "^3.0.0", "drizzle-orm": "0.45.2", + "resend": "^4.0.1", "superjson": "^2.2.5", "zod": "^4.3.5", }, diff --git a/packages/db/drizzle/0036_drop_pending_automation_status.sql b/packages/db/drizzle/0036_drop_pending_automation_status.sql new file mode 100644 index 00000000000..71332989c13 --- /dev/null +++ b/packages/db/drizzle/0036_drop_pending_automation_status.sql @@ -0,0 +1,5 @@ +ALTER TABLE "automation_runs" ALTER COLUMN "status" DROP DEFAULT;--> statement-breakpoint +ALTER TABLE "automation_runs" ALTER COLUMN "status" SET DATA TYPE text;--> statement-breakpoint +DROP TYPE "public"."automation_run_status";--> statement-breakpoint +CREATE TYPE "public"."automation_run_status" AS ENUM('dispatching', 'dispatched', 'skipped_offline', 'dispatch_failed');--> statement-breakpoint +ALTER TABLE "automation_runs" ALTER COLUMN "status" SET DATA TYPE "public"."automation_run_status" USING "status"::"public"."automation_run_status"; diff --git a/packages/db/drizzle/0037_drop_v2_project_repo_clone_url_unique.sql b/packages/db/drizzle/0037_drop_v2_project_repo_clone_url_unique.sql new file mode 100644 index 00000000000..561d273a262 --- /dev/null +++ b/packages/db/drizzle/0037_drop_v2_project_repo_clone_url_unique.sql @@ -0,0 +1 @@ +DROP INDEX "v2_projects_org_repo_clone_url_unique"; \ No newline at end of file diff --git a/packages/db/drizzle/0038_v2_workspaces_main_type.sql b/packages/db/drizzle/0038_v2_workspaces_main_type.sql new file mode 100644 index 00000000000..9fd2ad790f0 --- /dev/null +++ b/packages/db/drizzle/0038_v2_workspaces_main_type.sql @@ -0,0 +1,3 @@ +CREATE TYPE "public"."v2_workspace_type" AS ENUM('main', 'worktree');--> statement-breakpoint +ALTER TABLE "v2_workspaces" ADD COLUMN "type" "v2_workspace_type" DEFAULT 'worktree' NOT NULL;--> statement-breakpoint +CREATE UNIQUE INDEX "v2_workspaces_one_main_per_host" ON "v2_workspaces" USING btree ("project_id","host_id") WHERE "v2_workspaces"."type" = 'main'; \ No newline at end of file diff --git a/packages/db/drizzle/0039_consolidate_host_client_machine_id.sql b/packages/db/drizzle/0039_consolidate_host_client_machine_id.sql new file mode 100644 index 00000000000..7eabd44b84f --- /dev/null +++ b/packages/db/drizzle/0039_consolidate_host_client_machine_id.sql @@ -0,0 +1,69 @@ +-- session_hosts is dead (no writes anywhere); drop with CASCADE. +ALTER TABLE "session_hosts" DISABLE ROW LEVEL SECURITY;--> statement-breakpoint +DROP TABLE "session_hosts" CASCADE;--> statement-breakpoint + +-- Delete orphan FK rows in NOT NULL columns BEFORE the type change. +-- Once host_id is text, we can't easily distinguish orphans from valid uuids, +-- and the post-translation rows would still have stringified-UUID values that +-- can't satisfy the new composite FK to v2_hosts(organization_id, machine_id). +DELETE FROM "v2_users_hosts" WHERE "host_id" NOT IN (SELECT "id" FROM "v2_hosts");--> statement-breakpoint +DELETE FROM "v2_workspaces" WHERE "host_id" NOT IN (SELECT "id" FROM "v2_hosts");--> statement-breakpoint + +-- Drop old unique constraints (replaced by composite PKs below). +ALTER TABLE "v2_clients" DROP CONSTRAINT "v2_clients_org_user_machine_unique";--> statement-breakpoint +ALTER TABLE "v2_hosts" DROP CONSTRAINT "v2_hosts_org_machine_id_unique";--> statement-breakpoint +ALTER TABLE "v2_users_hosts" DROP CONSTRAINT "v2_users_hosts_org_user_host_unique";--> statement-breakpoint + +-- Drop old FK constraints (column type change requires no incoming FK). +ALTER TABLE "automation_runs" DROP CONSTRAINT "automation_runs_host_id_v2_hosts_id_fk";--> statement-breakpoint +ALTER TABLE "automations" DROP CONSTRAINT "automations_target_host_id_v2_hosts_id_fk";--> statement-breakpoint +ALTER TABLE "v2_users_hosts" DROP CONSTRAINT "v2_users_hosts_host_id_v2_hosts_id_fk";--> statement-breakpoint +ALTER TABLE "v2_workspaces" DROP CONSTRAINT "v2_workspaces_host_id_v2_hosts_id_fk";--> statement-breakpoint + +-- Drop the partial unique index on v2_workspaces (project_id, host_id) WHERE +-- type='main' added by 0038. Recreated below after host_id becomes text. +DROP INDEX "v2_workspaces_one_main_per_host";--> statement-breakpoint + +-- Cast FK columns uuid -> text. PostgreSQL has no implicit uuid->text cast, +-- so USING is required. We stringify the uuid here and translate to +-- machine_id in the UPDATEs below. +ALTER TABLE "automation_runs" ALTER COLUMN "host_id" SET DATA TYPE text USING "host_id"::text;--> statement-breakpoint +ALTER TABLE "automations" ALTER COLUMN "target_host_id" SET DATA TYPE text USING "target_host_id"::text;--> statement-breakpoint +ALTER TABLE "v2_users_hosts" ALTER COLUMN "host_id" SET DATA TYPE text USING "host_id"::text;--> statement-breakpoint +ALTER TABLE "v2_workspaces" ALTER COLUMN "host_id" SET DATA TYPE text USING "host_id"::text;--> statement-breakpoint + +-- Translate stringified UUIDs to machine_ids via UPDATE FROM v2_hosts. +-- v2_hosts.id still exists at this point (dropped further down); cast to text +-- to compare against the now-text host_id columns. +UPDATE "automation_runs" SET "host_id" = h."machine_id" FROM "v2_hosts" h WHERE "automation_runs"."host_id" = h."id"::text;--> statement-breakpoint +UPDATE "automations" SET "target_host_id" = h."machine_id" FROM "v2_hosts" h WHERE "automations"."target_host_id" = h."id"::text;--> statement-breakpoint +UPDATE "v2_users_hosts" SET "host_id" = h."machine_id" FROM "v2_hosts" h WHERE "v2_users_hosts"."host_id" = h."id"::text;--> statement-breakpoint +UPDATE "v2_workspaces" SET "host_id" = h."machine_id" FROM "v2_hosts" h WHERE "v2_workspaces"."host_id" = h."id"::text;--> statement-breakpoint + +-- For nullable columns, NULL out any rows that don't have a matching +-- (organization_id, machine_id) pair in v2_hosts. NOT EXISTS with the +-- composite check rather than a global machine_id IN (...) so the migration +-- doesn't rely on an implicit "automation_runs.organization_id always matches +-- the host's organization_id" invariant — if any cross-org row exists, the +-- translation step above would have written the wrong-org's machine_id and +-- the new composite FK would reject it. +UPDATE "automation_runs" SET "host_id" = NULL WHERE "host_id" IS NOT NULL AND NOT EXISTS (SELECT 1 FROM "v2_hosts" h WHERE h."machine_id" = "automation_runs"."host_id" AND h."organization_id" = "automation_runs"."organization_id");--> statement-breakpoint +UPDATE "automations" SET "target_host_id" = NULL WHERE "target_host_id" IS NOT NULL AND NOT EXISTS (SELECT 1 FROM "v2_hosts" h WHERE h."machine_id" = "automations"."target_host_id" AND h."organization_id" = "automations"."organization_id");--> statement-breakpoint + +-- Drop old uuid `id` columns. Implicitly drops the old PRIMARY KEY constraints, +-- freeing the tables to receive composite PKs below. +ALTER TABLE "v2_clients" DROP COLUMN "id";--> statement-breakpoint +ALTER TABLE "v2_hosts" DROP COLUMN "id";--> statement-breakpoint +ALTER TABLE "v2_users_hosts" DROP COLUMN "id";--> statement-breakpoint + +-- Add new composite PRIMARY KEYs. +ALTER TABLE "v2_clients" ADD CONSTRAINT "v2_clients_organization_id_user_id_machine_id_pk" PRIMARY KEY("organization_id","user_id","machine_id");--> statement-breakpoint +ALTER TABLE "v2_hosts" ADD CONSTRAINT "v2_hosts_organization_id_machine_id_pk" PRIMARY KEY("organization_id","machine_id");--> statement-breakpoint +ALTER TABLE "v2_users_hosts" ADD CONSTRAINT "v2_users_hosts_organization_id_user_id_host_id_pk" PRIMARY KEY("organization_id","user_id","host_id");--> statement-breakpoint + +-- Add new composite FOREIGN KEYs (now that v2_hosts has its composite PK). +ALTER TABLE "v2_users_hosts" ADD CONSTRAINT "v2_users_hosts_host_fk" FOREIGN KEY ("organization_id","host_id") REFERENCES "public"."v2_hosts"("organization_id","machine_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "v2_workspaces" ADD CONSTRAINT "v2_workspaces_host_fk" FOREIGN KEY ("organization_id","host_id") REFERENCES "public"."v2_hosts"("organization_id","machine_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint + +-- Recreate the partial unique index on v2_workspaces against the now-text host_id. +CREATE UNIQUE INDEX "v2_workspaces_one_main_per_host" ON "v2_workspaces" USING btree ("project_id","host_id") WHERE "v2_workspaces"."type" = 'main'; diff --git a/packages/db/drizzle/meta/0036_snapshot.json b/packages/db/drizzle/meta/0036_snapshot.json new file mode 100644 index 00000000000..45cd67e92b9 --- /dev/null +++ b/packages/db/drizzle/meta/0036_snapshot.json @@ -0,0 +1,5764 @@ +{ + "id": "8b691b52-7bc4-4149-8451-0bc6d0bb96b3", + "prevId": "b8be4250-5f27-4936-b4db-1273efaf1428", + "version": "7", + "dialect": "postgresql", + "tables": { + "auth.accounts": { + "name": "accounts", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "accounts_user_id_idx": { + "name": "accounts_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.apikeys": { + "name": "apikeys", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "config_id": { + "name": "config_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 86400000 + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "apikeys_configId_idx": { + "name": "apikeys_configId_idx", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "apikeys_referenceId_idx": { + "name": "apikeys_referenceId_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "apikeys_key_idx": { + "name": "apikeys_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.device_codes": { + "name": "device_codes", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "device_code": { + "name": "device_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_code": { + "name": "user_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_polled_at": { + "name": "last_polled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "polling_interval": { + "name": "polling_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.invitations": { + "name": "invitations", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "inviter_id": { + "name": "inviter_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitations_organization_id_idx": { + "name": "invitations_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitations_email_idx": { + "name": "invitations_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitations_organization_id_organizations_id_fk": { + "name": "invitations_organization_id_organizations_id_fk", + "tableFrom": "invitations", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitations_inviter_id_users_id_fk": { + "name": "invitations_inviter_id_users_id_fk", + "tableFrom": "invitations", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.jwkss": { + "name": "jwkss", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.members": { + "name": "members", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "members_organization_id_idx": { + "name": "members_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "members_user_id_idx": { + "name": "members_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "members_organization_id_organizations_id_fk": { + "name": "members_organization_id_organizations_id_fk", + "tableFrom": "members", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "members_user_id_users_id_fk": { + "name": "members_user_id_users_id_fk", + "tableFrom": "members", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_access_tokens": { + "name": "oauth_access_tokens", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_access_tokens_client_id_oauth_clients_client_id_fk": { + "name": "oauth_access_tokens_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "oauth_clients", + "schemaTo": "auth", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_tokens_session_id_sessions_id_fk": { + "name": "oauth_access_tokens_session_id_sessions_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "sessions", + "schemaTo": "auth", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_access_tokens_user_id_users_id_fk": { + "name": "oauth_access_tokens_user_id_users_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_tokens_refresh_id_oauth_refresh_tokens_id_fk": { + "name": "oauth_access_tokens_refresh_id_oauth_refresh_tokens_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "oauth_refresh_tokens", + "schemaTo": "auth", + "columnsFrom": [ + "refresh_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_tokens_token_unique": { + "name": "oauth_access_tokens_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_clients": { + "name": "oauth_clients", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_clients_user_id_users_id_fk": { + "name": "oauth_clients_user_id_users_id_fk", + "tableFrom": "oauth_clients", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_clients_client_id_unique": { + "name": "oauth_clients_client_id_unique", + "nullsNotDistinct": false, + "columns": [ + "client_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_consents": { + "name": "oauth_consents", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_consents_client_id_oauth_clients_client_id_fk": { + "name": "oauth_consents_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_consents", + "tableTo": "oauth_clients", + "schemaTo": "auth", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consents_user_id_users_id_fk": { + "name": "oauth_consents_user_id_users_id_fk", + "tableFrom": "oauth_consents", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_refresh_tokens": { + "name": "oauth_refresh_tokens", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked": { + "name": "revoked", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "auth_time": { + "name": "auth_time", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_refresh_tokens_client_id_oauth_clients_client_id_fk": { + "name": "oauth_refresh_tokens_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_refresh_tokens", + "tableTo": "oauth_clients", + "schemaTo": "auth", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_tokens_session_id_sessions_id_fk": { + "name": "oauth_refresh_tokens_session_id_sessions_id_fk", + "tableFrom": "oauth_refresh_tokens", + "tableTo": "sessions", + "schemaTo": "auth", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_refresh_tokens_user_id_users_id_fk": { + "name": "oauth_refresh_tokens_user_id_users_id_fk", + "tableFrom": "oauth_refresh_tokens", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.organizations": { + "name": "organizations", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_domains": { + "name": "allowed_domains", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + } + }, + "indexes": { + "organizations_slug_idx": { + "name": "organizations_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "organizations_allowed_domains_idx": { + "name": "organizations_allowed_domains_idx", + "columns": [ + { + "expression": "allowed_domains", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.sessions": { + "name": "sessions", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_token_unique": { + "name": "sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.users": { + "name": "users", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_ids": { + "name": "organization_ids", + "type": "uuid[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.verifications": { + "name": "verifications", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verifications_identifier_idx": { + "name": "verifications_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_installations": { + "name": "github_installations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connected_by_user_id": { + "name": "connected_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_login": { + "name": "account_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_type": { + "name": "account_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "suspended": { + "name": "suspended", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "github_installations_installation_id_idx": { + "name": "github_installations_installation_id_idx", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_installations_organization_id_organizations_id_fk": { + "name": "github_installations_organization_id_organizations_id_fk", + "tableFrom": "github_installations", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_installations_connected_by_user_id_users_id_fk": { + "name": "github_installations_connected_by_user_id_users_id_fk", + "tableFrom": "github_installations", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "connected_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_installations_installation_id_unique": { + "name": "github_installations_installation_id_unique", + "nullsNotDistinct": false, + "columns": [ + "installation_id" + ] + }, + "github_installations_org_unique": { + "name": "github_installations_org_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_pull_requests": { + "name": "github_pull_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "repository_id": { + "name": "repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "head_branch": { + "name": "head_branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "head_sha": { + "name": "head_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "base_branch": { + "name": "base_branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_login": { + "name": "author_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_avatar_url": { + "name": "author_avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_draft": { + "name": "is_draft", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "additions": { + "name": "additions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "deletions": { + "name": "deletions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "changed_files": { + "name": "changed_files", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "review_decision": { + "name": "review_decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checks_status": { + "name": "checks_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "checks": { + "name": "checks", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "merged_at": { + "name": "merged_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "github_pull_requests_repository_id_idx": { + "name": "github_pull_requests_repository_id_idx", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_requests_state_idx": { + "name": "github_pull_requests_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_requests_head_branch_idx": { + "name": "github_pull_requests_head_branch_idx", + "columns": [ + { + "expression": "head_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_requests_org_id_idx": { + "name": "github_pull_requests_org_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_pull_requests_repository_id_github_repositories_id_fk": { + "name": "github_pull_requests_repository_id_github_repositories_id_fk", + "tableFrom": "github_pull_requests", + "tableTo": "github_repositories", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_pull_requests_organization_id_organizations_id_fk": { + "name": "github_pull_requests_organization_id_organizations_id_fk", + "tableFrom": "github_pull_requests", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_pull_requests_repo_pr_unique": { + "name": "github_pull_requests_repo_pr_unique", + "nullsNotDistinct": false, + "columns": [ + "repository_id", + "pr_number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_repositories": { + "name": "github_repositories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "installation_id": { + "name": "installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repo_id": { + "name": "repo_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'main'" + }, + "is_private": { + "name": "is_private", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "github_repositories_installation_id_idx": { + "name": "github_repositories_installation_id_idx", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_repositories_full_name_idx": { + "name": "github_repositories_full_name_idx", + "columns": [ + { + "expression": "full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_repositories_org_id_idx": { + "name": "github_repositories_org_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_repositories_installation_id_github_installations_id_fk": { + "name": "github_repositories_installation_id_github_installations_id_fk", + "tableFrom": "github_repositories", + "tableTo": "github_installations", + "columnsFrom": [ + "installation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_repositories_organization_id_organizations_id_fk": { + "name": "github_repositories_organization_id_organizations_id_fk", + "tableFrom": "github_repositories", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_repositories_repo_id_unique": { + "name": "github_repositories_repo_id_unique", + "nullsNotDistinct": false, + "columns": [ + "repo_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "ingest.webhook_events": { + "name": "webhook_events", + "schema": "ingest", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "provider": { + "name": "provider", + "type": "integration_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "retry_count": { + "name": "retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "received_at": { + "name": "received_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "webhook_events_provider_status_idx": { + "name": "webhook_events_provider_status_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_events_provider_event_id_idx": { + "name": "webhook_events_provider_event_id_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_events_received_at_idx": { + "name": "webhook_events_received_at_idx", + "columns": [ + { + "expression": "received_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_commands": { + "name": "agent_commands", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_device_id": { + "name": "target_device_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_device_type": { + "name": "target_device_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool": { + "name": "tool", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "params": { + "name": "params", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "parent_command_id": { + "name": "parent_command_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "command_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "executed_at": { + "name": "executed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "timeout_at": { + "name": "timeout_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "agent_commands_user_status_idx": { + "name": "agent_commands_user_status_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_commands_target_device_status_idx": { + "name": "agent_commands_target_device_status_idx", + "columns": [ + { + "expression": "target_device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_commands_org_created_idx": { + "name": "agent_commands_org_created_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_commands_user_id_users_id_fk": { + "name": "agent_commands_user_id_users_id_fk", + "tableFrom": "agent_commands", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_commands_organization_id_organizations_id_fk": { + "name": "agent_commands_organization_id_organizations_id_fk", + "tableFrom": "agent_commands", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automation_runs": { + "name": "automation_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "automation_id": { + "name": "automation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scheduled_for": { + "name": "scheduled_for", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "v2_workspace_id": { + "name": "v2_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_kind": { + "name": "session_kind", + "type": "automation_session_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "chat_session_id": { + "name": "chat_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "terminal_session_id": { + "name": "terminal_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "automation_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dispatched_at": { + "name": "dispatched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "automation_runs_dedup_idx": { + "name": "automation_runs_dedup_idx", + "columns": [ + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduled_for", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automation_runs_history_idx": { + "name": "automation_runs_history_idx", + "columns": [ + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automation_runs_status_idx": { + "name": "automation_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automation_runs_workspace_idx": { + "name": "automation_runs_workspace_idx", + "columns": [ + { + "expression": "v2_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automation_runs_automation_id_automations_id_fk": { + "name": "automation_runs_automation_id_automations_id_fk", + "tableFrom": "automation_runs", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_runs_organization_id_organizations_id_fk": { + "name": "automation_runs_organization_id_organizations_id_fk", + "tableFrom": "automation_runs", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_runs_host_id_v2_hosts_id_fk": { + "name": "automation_runs_host_id_v2_hosts_id_fk", + "tableFrom": "automation_runs", + "tableTo": "v2_hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "automation_runs_chat_session_id_chat_sessions_id_fk": { + "name": "automation_runs_chat_session_id_chat_sessions_id_fk", + "tableFrom": "automation_runs", + "tableTo": "chat_sessions", + "columnsFrom": [ + "chat_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automations": { + "name": "automations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_config": { + "name": "agent_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "target_host_id": { + "name": "target_host_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "v2_project_id": { + "name": "v2_project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "v2_workspace_id": { + "name": "v2_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "rrule": { + "name": "rrule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dtstart": { + "name": "dtstart", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "mcp_scope": { + "name": "mcp_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "automations_dispatcher_idx": { + "name": "automations_dispatcher_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automations_owner_idx": { + "name": "automations_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automations_organization_idx": { + "name": "automations_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automations_organization_id_organizations_id_fk": { + "name": "automations_organization_id_organizations_id_fk", + "tableFrom": "automations", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automations_owner_user_id_users_id_fk": { + "name": "automations_owner_user_id_users_id_fk", + "tableFrom": "automations", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automations_target_host_id_v2_hosts_id_fk": { + "name": "automations_target_host_id_v2_hosts_id_fk", + "tableFrom": "automations", + "tableTo": "v2_hosts", + "columnsFrom": [ + "target_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "automations_v2_project_id_v2_projects_id_fk": { + "name": "automations_v2_project_id_v2_projects_id_fk", + "tableFrom": "automations", + "tableTo": "v2_projects", + "columnsFrom": [ + "v2_project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_sessions": { + "name": "chat_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "v2_workspace_id": { + "name": "v2_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_sessions_org_idx": { + "name": "chat_sessions_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_sessions_created_by_idx": { + "name": "chat_sessions_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_sessions_last_active_idx": { + "name": "chat_sessions_last_active_idx", + "columns": [ + { + "expression": "last_active_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_sessions_organization_id_organizations_id_fk": { + "name": "chat_sessions_organization_id_organizations_id_fk", + "tableFrom": "chat_sessions", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_sessions_created_by_users_id_fk": { + "name": "chat_sessions_created_by_users_id_fk", + "tableFrom": "chat_sessions", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_sessions_workspace_id_workspaces_id_fk": { + "name": "chat_sessions_workspace_id_workspaces_id_fk", + "tableFrom": "chat_sessions", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_sessions_v2_workspace_id_v2_workspaces_id_fk": { + "name": "chat_sessions_v2_workspace_id_v2_workspaces_id_fk", + "tableFrom": "chat_sessions", + "tableTo": "v2_workspaces", + "columnsFrom": [ + "v2_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.device_presence": { + "name": "device_presence", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_name": { + "name": "device_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "device_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "device_presence_user_org_idx": { + "name": "device_presence_user_org_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "device_presence_user_device_idx": { + "name": "device_presence_user_device_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "device_presence_last_seen_idx": { + "name": "device_presence_last_seen_idx", + "columns": [ + { + "expression": "last_seen_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "device_presence_user_id_users_id_fk": { + "name": "device_presence_user_id_users_id_fk", + "tableFrom": "device_presence", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "device_presence_organization_id_organizations_id_fk": { + "name": "device_presence_organization_id_organizations_id_fk", + "tableFrom": "device_presence", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integration_connections": { + "name": "integration_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connected_by_user_id": { + "name": "connected_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "integration_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_expires_at": { + "name": "token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "external_org_id": { + "name": "external_org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_org_name": { + "name": "external_org_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "integration_connections_org_idx": { + "name": "integration_connections_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "integration_connections_organization_id_organizations_id_fk": { + "name": "integration_connections_organization_id_organizations_id_fk", + "tableFrom": "integration_connections", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integration_connections_connected_by_user_id_users_id_fk": { + "name": "integration_connections_connected_by_user_id_users_id_fk", + "tableFrom": "integration_connections", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "connected_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "integration_connections_unique": { + "name": "integration_connections_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "provider" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_repository_id": { + "name": "github_repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repo_owner": { + "name": "repo_owner", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_name": { + "name": "repo_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'main'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_organization_id_idx": { + "name": "projects_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_organization_id_organizations_id_fk": { + "name": "projects_organization_id_organizations_id_fk", + "tableFrom": "projects", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "projects_github_repository_id_github_repositories_id_fk": { + "name": "projects_github_repository_id_github_repositories_id_fk", + "tableFrom": "projects", + "tableTo": "github_repositories", + "columnsFrom": [ + "github_repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "projects_org_slug_unique": { + "name": "projects_org_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sandbox_images": { + "name": "sandbox_images", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "setup_commands": { + "name": "setup_commands", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "base_image": { + "name": "base_image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_packages": { + "name": "system_packages", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sandbox_images_organization_id_idx": { + "name": "sandbox_images_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sandbox_images_organization_id_organizations_id_fk": { + "name": "sandbox_images_organization_id_organizations_id_fk", + "tableFrom": "sandbox_images", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sandbox_images_project_id_projects_id_fk": { + "name": "sandbox_images_project_id_projects_id_fk", + "tableFrom": "sandbox_images", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sandbox_images_project_unique": { + "name": "sandbox_images_project_unique", + "nullsNotDistinct": false, + "columns": [ + "project_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secrets": { + "name": "secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_value": { + "name": "encrypted_value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensitive": { + "name": "sensitive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secrets_project_id_idx": { + "name": "secrets_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secrets_organization_id_idx": { + "name": "secrets_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secrets_organization_id_organizations_id_fk": { + "name": "secrets_organization_id_organizations_id_fk", + "tableFrom": "secrets", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "secrets_project_id_projects_id_fk": { + "name": "secrets_project_id_projects_id_fk", + "tableFrom": "secrets", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "secrets_created_by_user_id_users_id_fk": { + "name": "secrets_created_by_user_id_users_id_fk", + "tableFrom": "secrets", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "secrets_project_key_unique": { + "name": "secrets_project_key_unique", + "nullsNotDistinct": false, + "columns": [ + "project_id", + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_hosts": { + "name": "session_hosts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "session_hosts_session_id_idx": { + "name": "session_hosts_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_hosts_org_idx": { + "name": "session_hosts_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_hosts_device_id_idx": { + "name": "session_hosts_device_id_idx", + "columns": [ + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_hosts_session_id_chat_sessions_id_fk": { + "name": "session_hosts_session_id_chat_sessions_id_fk", + "tableFrom": "session_hosts", + "tableTo": "chat_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_hosts_organization_id_organizations_id_fk": { + "name": "session_hosts_organization_id_organizations_id_fk", + "tableFrom": "session_hosts", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscriptions": { + "name": "subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'incomplete'" + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cancel_at": { + "name": "cancel_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "subscriptions_reference_id_idx": { + "name": "subscriptions_reference_id_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "subscriptions_stripe_customer_id_idx": { + "name": "subscriptions_stripe_customer_id_idx", + "columns": [ + { + "expression": "stripe_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "subscriptions_status_idx": { + "name": "subscriptions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "subscriptions_reference_id_organizations_id_fk": { + "name": "subscriptions_reference_id_organizations_id_fk", + "tableFrom": "subscriptions", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "reference_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_statuses": { + "name": "task_statuses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "progress_percent": { + "name": "progress_percent", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "external_provider": { + "name": "external_provider", + "type": "integration_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "task_statuses_organization_id_idx": { + "name": "task_statuses_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_statuses_type_idx": { + "name": "task_statuses_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_statuses_organization_id_organizations_id_fk": { + "name": "task_statuses_organization_id_organizations_id_fk", + "tableFrom": "task_statuses", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "task_statuses_org_external_unique": { + "name": "task_statuses_org_external_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "external_provider", + "external_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tasks": { + "name": "tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_id": { + "name": "status_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "task_priority", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assignee_id": { + "name": "assignee_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "estimate": { + "name": "estimate", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "due_date": { + "name": "due_date", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "labels": { + "name": "labels", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_provider": { + "name": "external_provider", + "type": "integration_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_key": { + "name": "external_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_url": { + "name": "external_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "sync_error": { + "name": "sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_external_id": { + "name": "assignee_external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_display_name": { + "name": "assignee_display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_avatar_url": { + "name": "assignee_avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tasks_slug_idx": { + "name": "tasks_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_organization_id_idx": { + "name": "tasks_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_assignee_id_idx": { + "name": "tasks_assignee_id_idx", + "columns": [ + { + "expression": "assignee_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_creator_id_idx": { + "name": "tasks_creator_id_idx", + "columns": [ + { + "expression": "creator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_status_id_idx": { + "name": "tasks_status_id_idx", + "columns": [ + { + "expression": "status_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_created_at_idx": { + "name": "tasks_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_external_provider_idx": { + "name": "tasks_external_provider_idx", + "columns": [ + { + "expression": "external_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_assignee_external_id_idx": { + "name": "tasks_assignee_external_id_idx", + "columns": [ + { + "expression": "assignee_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tasks_status_id_task_statuses_id_fk": { + "name": "tasks_status_id_task_statuses_id_fk", + "tableFrom": "tasks", + "tableTo": "task_statuses", + "columnsFrom": [ + "status_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tasks_organization_id_organizations_id_fk": { + "name": "tasks_organization_id_organizations_id_fk", + "tableFrom": "tasks", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tasks_assignee_id_users_id_fk": { + "name": "tasks_assignee_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "assignee_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_creator_id_users_id_fk": { + "name": "tasks_creator_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tasks_external_unique": { + "name": "tasks_external_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "external_provider", + "external_id" + ] + }, + "tasks_org_slug_unique": { + "name": "tasks_org_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users__slack_users": { + "name": "users__slack_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slack_user_id": { + "name": "slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "model_preference": { + "name": "model_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users__slack_users_user_idx": { + "name": "users__slack_users_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users__slack_users_org_idx": { + "name": "users__slack_users_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "users__slack_users_user_id_users_id_fk": { + "name": "users__slack_users_user_id_users_id_fk", + "tableFrom": "users__slack_users", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "users__slack_users_organization_id_organizations_id_fk": { + "name": "users__slack_users_organization_id_organizations_id_fk", + "tableFrom": "users__slack_users", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users__slack_users_unique": { + "name": "users__slack_users_unique", + "nullsNotDistinct": false, + "columns": [ + "slack_user_id", + "team_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_clients": { + "name": "v2_clients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "v2_client_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_clients_organization_id_idx": { + "name": "v2_clients_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_clients_user_id_idx": { + "name": "v2_clients_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_clients_organization_id_organizations_id_fk": { + "name": "v2_clients_organization_id_organizations_id_fk", + "tableFrom": "v2_clients", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_clients_user_id_users_id_fk": { + "name": "v2_clients_user_id_users_id_fk", + "tableFrom": "v2_clients", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "v2_clients_org_user_machine_unique": { + "name": "v2_clients_org_user_machine_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "user_id", + "machine_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_hosts": { + "name": "v2_hosts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_online": { + "name": "is_online", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_hosts_organization_id_idx": { + "name": "v2_hosts_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_hosts_organization_id_organizations_id_fk": { + "name": "v2_hosts_organization_id_organizations_id_fk", + "tableFrom": "v2_hosts", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_hosts_created_by_user_id_users_id_fk": { + "name": "v2_hosts_created_by_user_id_users_id_fk", + "tableFrom": "v2_hosts", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "v2_hosts_org_machine_id_unique": { + "name": "v2_hosts_org_machine_id_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "machine_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_projects": { + "name": "v2_projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_clone_url": { + "name": "repo_clone_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_repository_id": { + "name": "github_repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_projects_organization_id_idx": { + "name": "v2_projects_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_projects_org_repo_clone_url_unique": { + "name": "v2_projects_org_repo_clone_url_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"repo_clone_url\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_projects_organization_id_organizations_id_fk": { + "name": "v2_projects_organization_id_organizations_id_fk", + "tableFrom": "v2_projects", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_projects_github_repository_id_github_repositories_id_fk": { + "name": "v2_projects_github_repository_id_github_repositories_id_fk", + "tableFrom": "v2_projects", + "tableTo": "github_repositories", + "columnsFrom": [ + "github_repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "v2_projects_org_slug_unique": { + "name": "v2_projects_org_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_users_hosts": { + "name": "v2_users_hosts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "v2_users_host_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_users_hosts_organization_id_idx": { + "name": "v2_users_hosts_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_users_hosts_user_id_idx": { + "name": "v2_users_hosts_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_users_hosts_host_id_idx": { + "name": "v2_users_hosts_host_id_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_users_hosts_organization_id_organizations_id_fk": { + "name": "v2_users_hosts_organization_id_organizations_id_fk", + "tableFrom": "v2_users_hosts", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_users_hosts_user_id_users_id_fk": { + "name": "v2_users_hosts_user_id_users_id_fk", + "tableFrom": "v2_users_hosts", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_users_hosts_host_id_v2_hosts_id_fk": { + "name": "v2_users_hosts_host_id_v2_hosts_id_fk", + "tableFrom": "v2_users_hosts", + "tableTo": "v2_hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "v2_users_hosts_org_user_host_unique": { + "name": "v2_users_hosts_org_user_host_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "user_id", + "host_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_workspaces": { + "name": "v2_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_workspaces_project_id_idx": { + "name": "v2_workspaces_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_workspaces_organization_id_idx": { + "name": "v2_workspaces_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_workspaces_host_id_idx": { + "name": "v2_workspaces_host_id_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_workspaces_organization_id_organizations_id_fk": { + "name": "v2_workspaces_organization_id_organizations_id_fk", + "tableFrom": "v2_workspaces", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_workspaces_project_id_v2_projects_id_fk": { + "name": "v2_workspaces_project_id_v2_projects_id_fk", + "tableFrom": "v2_workspaces", + "tableTo": "v2_projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_workspaces_host_id_v2_hosts_id_fk": { + "name": "v2_workspaces_host_id_v2_hosts_id_fk", + "tableFrom": "v2_workspaces", + "tableTo": "v2_hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "v2_workspaces_created_by_user_id_users_id_fk": { + "name": "v2_workspaces_created_by_user_id_users_id_fk", + "tableFrom": "v2_workspaces", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspaces": { + "name": "workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "workspace_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspaces_project_id_idx": { + "name": "workspaces_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspaces_organization_id_idx": { + "name": "workspaces_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspaces_type_idx": { + "name": "workspaces_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspaces_organization_id_organizations_id_fk": { + "name": "workspaces_organization_id_organizations_id_fk", + "tableFrom": "workspaces", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_project_id_projects_id_fk": { + "name": "workspaces_project_id_projects_id_fk", + "tableFrom": "workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_created_by_user_id_users_id_fk": { + "name": "workspaces_created_by_user_id_users_id_fk", + "tableFrom": "workspaces", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.automation_run_status": { + "name": "automation_run_status", + "schema": "public", + "values": [ + "dispatching", + "dispatched", + "skipped_offline", + "dispatch_failed" + ] + }, + "public.automation_session_kind": { + "name": "automation_session_kind", + "schema": "public", + "values": [ + "chat", + "terminal" + ] + }, + "public.command_status": { + "name": "command_status", + "schema": "public", + "values": [ + "pending", + "completed", + "failed", + "timeout" + ] + }, + "public.device_type": { + "name": "device_type", + "schema": "public", + "values": [ + "desktop", + "mobile", + "web" + ] + }, + "public.integration_provider": { + "name": "integration_provider", + "schema": "public", + "values": [ + "linear", + "github", + "slack" + ] + }, + "public.task_priority": { + "name": "task_priority", + "schema": "public", + "values": [ + "urgent", + "high", + "medium", + "low", + "none" + ] + }, + "public.task_status": { + "name": "task_status", + "schema": "public", + "values": [ + "backlog", + "todo", + "planning", + "working", + "needs-feedback", + "ready-to-merge", + "completed", + "canceled" + ] + }, + "public.v2_client_type": { + "name": "v2_client_type", + "schema": "public", + "values": [ + "desktop", + "mobile", + "web" + ] + }, + "public.v2_users_host_role": { + "name": "v2_users_host_role", + "schema": "public", + "values": [ + "owner", + "member" + ] + }, + "public.workspace_type": { + "name": "workspace_type", + "schema": "public", + "values": [ + "local", + "cloud" + ] + } + }, + "schemas": { + "auth": "auth", + "ingest": "ingest" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/drizzle/meta/0037_snapshot.json b/packages/db/drizzle/meta/0037_snapshot.json new file mode 100644 index 00000000000..2466a005efb --- /dev/null +++ b/packages/db/drizzle/meta/0037_snapshot.json @@ -0,0 +1,5743 @@ +{ + "id": "16fdad31-9719-48da-880a-05e1c736dee7", + "prevId": "8b691b52-7bc4-4149-8451-0bc6d0bb96b3", + "version": "7", + "dialect": "postgresql", + "tables": { + "auth.accounts": { + "name": "accounts", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "accounts_user_id_idx": { + "name": "accounts_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.apikeys": { + "name": "apikeys", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "config_id": { + "name": "config_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 86400000 + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "apikeys_configId_idx": { + "name": "apikeys_configId_idx", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "apikeys_referenceId_idx": { + "name": "apikeys_referenceId_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "apikeys_key_idx": { + "name": "apikeys_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.device_codes": { + "name": "device_codes", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "device_code": { + "name": "device_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_code": { + "name": "user_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_polled_at": { + "name": "last_polled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "polling_interval": { + "name": "polling_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.invitations": { + "name": "invitations", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "inviter_id": { + "name": "inviter_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitations_organization_id_idx": { + "name": "invitations_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitations_email_idx": { + "name": "invitations_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitations_organization_id_organizations_id_fk": { + "name": "invitations_organization_id_organizations_id_fk", + "tableFrom": "invitations", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitations_inviter_id_users_id_fk": { + "name": "invitations_inviter_id_users_id_fk", + "tableFrom": "invitations", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.jwkss": { + "name": "jwkss", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.members": { + "name": "members", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "members_organization_id_idx": { + "name": "members_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "members_user_id_idx": { + "name": "members_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "members_organization_id_organizations_id_fk": { + "name": "members_organization_id_organizations_id_fk", + "tableFrom": "members", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "members_user_id_users_id_fk": { + "name": "members_user_id_users_id_fk", + "tableFrom": "members", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_access_tokens": { + "name": "oauth_access_tokens", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_access_tokens_client_id_oauth_clients_client_id_fk": { + "name": "oauth_access_tokens_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "oauth_clients", + "schemaTo": "auth", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_tokens_session_id_sessions_id_fk": { + "name": "oauth_access_tokens_session_id_sessions_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "sessions", + "schemaTo": "auth", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_access_tokens_user_id_users_id_fk": { + "name": "oauth_access_tokens_user_id_users_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_tokens_refresh_id_oauth_refresh_tokens_id_fk": { + "name": "oauth_access_tokens_refresh_id_oauth_refresh_tokens_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "oauth_refresh_tokens", + "schemaTo": "auth", + "columnsFrom": [ + "refresh_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_tokens_token_unique": { + "name": "oauth_access_tokens_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_clients": { + "name": "oauth_clients", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_clients_user_id_users_id_fk": { + "name": "oauth_clients_user_id_users_id_fk", + "tableFrom": "oauth_clients", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_clients_client_id_unique": { + "name": "oauth_clients_client_id_unique", + "nullsNotDistinct": false, + "columns": [ + "client_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_consents": { + "name": "oauth_consents", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_consents_client_id_oauth_clients_client_id_fk": { + "name": "oauth_consents_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_consents", + "tableTo": "oauth_clients", + "schemaTo": "auth", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consents_user_id_users_id_fk": { + "name": "oauth_consents_user_id_users_id_fk", + "tableFrom": "oauth_consents", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_refresh_tokens": { + "name": "oauth_refresh_tokens", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked": { + "name": "revoked", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "auth_time": { + "name": "auth_time", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_refresh_tokens_client_id_oauth_clients_client_id_fk": { + "name": "oauth_refresh_tokens_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_refresh_tokens", + "tableTo": "oauth_clients", + "schemaTo": "auth", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_tokens_session_id_sessions_id_fk": { + "name": "oauth_refresh_tokens_session_id_sessions_id_fk", + "tableFrom": "oauth_refresh_tokens", + "tableTo": "sessions", + "schemaTo": "auth", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_refresh_tokens_user_id_users_id_fk": { + "name": "oauth_refresh_tokens_user_id_users_id_fk", + "tableFrom": "oauth_refresh_tokens", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.organizations": { + "name": "organizations", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_domains": { + "name": "allowed_domains", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + } + }, + "indexes": { + "organizations_slug_idx": { + "name": "organizations_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "organizations_allowed_domains_idx": { + "name": "organizations_allowed_domains_idx", + "columns": [ + { + "expression": "allowed_domains", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.sessions": { + "name": "sessions", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_token_unique": { + "name": "sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.users": { + "name": "users", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_ids": { + "name": "organization_ids", + "type": "uuid[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.verifications": { + "name": "verifications", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verifications_identifier_idx": { + "name": "verifications_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_installations": { + "name": "github_installations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connected_by_user_id": { + "name": "connected_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_login": { + "name": "account_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_type": { + "name": "account_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "suspended": { + "name": "suspended", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "github_installations_installation_id_idx": { + "name": "github_installations_installation_id_idx", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_installations_organization_id_organizations_id_fk": { + "name": "github_installations_organization_id_organizations_id_fk", + "tableFrom": "github_installations", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_installations_connected_by_user_id_users_id_fk": { + "name": "github_installations_connected_by_user_id_users_id_fk", + "tableFrom": "github_installations", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "connected_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_installations_installation_id_unique": { + "name": "github_installations_installation_id_unique", + "nullsNotDistinct": false, + "columns": [ + "installation_id" + ] + }, + "github_installations_org_unique": { + "name": "github_installations_org_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_pull_requests": { + "name": "github_pull_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "repository_id": { + "name": "repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "head_branch": { + "name": "head_branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "head_sha": { + "name": "head_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "base_branch": { + "name": "base_branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_login": { + "name": "author_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_avatar_url": { + "name": "author_avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_draft": { + "name": "is_draft", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "additions": { + "name": "additions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "deletions": { + "name": "deletions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "changed_files": { + "name": "changed_files", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "review_decision": { + "name": "review_decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checks_status": { + "name": "checks_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "checks": { + "name": "checks", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "merged_at": { + "name": "merged_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "github_pull_requests_repository_id_idx": { + "name": "github_pull_requests_repository_id_idx", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_requests_state_idx": { + "name": "github_pull_requests_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_requests_head_branch_idx": { + "name": "github_pull_requests_head_branch_idx", + "columns": [ + { + "expression": "head_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_requests_org_id_idx": { + "name": "github_pull_requests_org_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_pull_requests_repository_id_github_repositories_id_fk": { + "name": "github_pull_requests_repository_id_github_repositories_id_fk", + "tableFrom": "github_pull_requests", + "tableTo": "github_repositories", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_pull_requests_organization_id_organizations_id_fk": { + "name": "github_pull_requests_organization_id_organizations_id_fk", + "tableFrom": "github_pull_requests", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_pull_requests_repo_pr_unique": { + "name": "github_pull_requests_repo_pr_unique", + "nullsNotDistinct": false, + "columns": [ + "repository_id", + "pr_number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_repositories": { + "name": "github_repositories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "installation_id": { + "name": "installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repo_id": { + "name": "repo_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'main'" + }, + "is_private": { + "name": "is_private", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "github_repositories_installation_id_idx": { + "name": "github_repositories_installation_id_idx", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_repositories_full_name_idx": { + "name": "github_repositories_full_name_idx", + "columns": [ + { + "expression": "full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_repositories_org_id_idx": { + "name": "github_repositories_org_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_repositories_installation_id_github_installations_id_fk": { + "name": "github_repositories_installation_id_github_installations_id_fk", + "tableFrom": "github_repositories", + "tableTo": "github_installations", + "columnsFrom": [ + "installation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_repositories_organization_id_organizations_id_fk": { + "name": "github_repositories_organization_id_organizations_id_fk", + "tableFrom": "github_repositories", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_repositories_repo_id_unique": { + "name": "github_repositories_repo_id_unique", + "nullsNotDistinct": false, + "columns": [ + "repo_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "ingest.webhook_events": { + "name": "webhook_events", + "schema": "ingest", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "provider": { + "name": "provider", + "type": "integration_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "retry_count": { + "name": "retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "received_at": { + "name": "received_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "webhook_events_provider_status_idx": { + "name": "webhook_events_provider_status_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_events_provider_event_id_idx": { + "name": "webhook_events_provider_event_id_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_events_received_at_idx": { + "name": "webhook_events_received_at_idx", + "columns": [ + { + "expression": "received_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_commands": { + "name": "agent_commands", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_device_id": { + "name": "target_device_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_device_type": { + "name": "target_device_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool": { + "name": "tool", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "params": { + "name": "params", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "parent_command_id": { + "name": "parent_command_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "command_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "executed_at": { + "name": "executed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "timeout_at": { + "name": "timeout_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "agent_commands_user_status_idx": { + "name": "agent_commands_user_status_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_commands_target_device_status_idx": { + "name": "agent_commands_target_device_status_idx", + "columns": [ + { + "expression": "target_device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_commands_org_created_idx": { + "name": "agent_commands_org_created_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_commands_user_id_users_id_fk": { + "name": "agent_commands_user_id_users_id_fk", + "tableFrom": "agent_commands", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_commands_organization_id_organizations_id_fk": { + "name": "agent_commands_organization_id_organizations_id_fk", + "tableFrom": "agent_commands", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automation_runs": { + "name": "automation_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "automation_id": { + "name": "automation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scheduled_for": { + "name": "scheduled_for", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "v2_workspace_id": { + "name": "v2_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_kind": { + "name": "session_kind", + "type": "automation_session_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "chat_session_id": { + "name": "chat_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "terminal_session_id": { + "name": "terminal_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "automation_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dispatched_at": { + "name": "dispatched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "automation_runs_dedup_idx": { + "name": "automation_runs_dedup_idx", + "columns": [ + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduled_for", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automation_runs_history_idx": { + "name": "automation_runs_history_idx", + "columns": [ + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automation_runs_status_idx": { + "name": "automation_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automation_runs_workspace_idx": { + "name": "automation_runs_workspace_idx", + "columns": [ + { + "expression": "v2_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automation_runs_automation_id_automations_id_fk": { + "name": "automation_runs_automation_id_automations_id_fk", + "tableFrom": "automation_runs", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_runs_organization_id_organizations_id_fk": { + "name": "automation_runs_organization_id_organizations_id_fk", + "tableFrom": "automation_runs", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_runs_host_id_v2_hosts_id_fk": { + "name": "automation_runs_host_id_v2_hosts_id_fk", + "tableFrom": "automation_runs", + "tableTo": "v2_hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "automation_runs_chat_session_id_chat_sessions_id_fk": { + "name": "automation_runs_chat_session_id_chat_sessions_id_fk", + "tableFrom": "automation_runs", + "tableTo": "chat_sessions", + "columnsFrom": [ + "chat_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automations": { + "name": "automations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_config": { + "name": "agent_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "target_host_id": { + "name": "target_host_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "v2_project_id": { + "name": "v2_project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "v2_workspace_id": { + "name": "v2_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "rrule": { + "name": "rrule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dtstart": { + "name": "dtstart", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "mcp_scope": { + "name": "mcp_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "automations_dispatcher_idx": { + "name": "automations_dispatcher_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automations_owner_idx": { + "name": "automations_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automations_organization_idx": { + "name": "automations_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automations_organization_id_organizations_id_fk": { + "name": "automations_organization_id_organizations_id_fk", + "tableFrom": "automations", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automations_owner_user_id_users_id_fk": { + "name": "automations_owner_user_id_users_id_fk", + "tableFrom": "automations", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automations_target_host_id_v2_hosts_id_fk": { + "name": "automations_target_host_id_v2_hosts_id_fk", + "tableFrom": "automations", + "tableTo": "v2_hosts", + "columnsFrom": [ + "target_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "automations_v2_project_id_v2_projects_id_fk": { + "name": "automations_v2_project_id_v2_projects_id_fk", + "tableFrom": "automations", + "tableTo": "v2_projects", + "columnsFrom": [ + "v2_project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_sessions": { + "name": "chat_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "v2_workspace_id": { + "name": "v2_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_sessions_org_idx": { + "name": "chat_sessions_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_sessions_created_by_idx": { + "name": "chat_sessions_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_sessions_last_active_idx": { + "name": "chat_sessions_last_active_idx", + "columns": [ + { + "expression": "last_active_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_sessions_organization_id_organizations_id_fk": { + "name": "chat_sessions_organization_id_organizations_id_fk", + "tableFrom": "chat_sessions", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_sessions_created_by_users_id_fk": { + "name": "chat_sessions_created_by_users_id_fk", + "tableFrom": "chat_sessions", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_sessions_workspace_id_workspaces_id_fk": { + "name": "chat_sessions_workspace_id_workspaces_id_fk", + "tableFrom": "chat_sessions", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_sessions_v2_workspace_id_v2_workspaces_id_fk": { + "name": "chat_sessions_v2_workspace_id_v2_workspaces_id_fk", + "tableFrom": "chat_sessions", + "tableTo": "v2_workspaces", + "columnsFrom": [ + "v2_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.device_presence": { + "name": "device_presence", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_name": { + "name": "device_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "device_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "device_presence_user_org_idx": { + "name": "device_presence_user_org_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "device_presence_user_device_idx": { + "name": "device_presence_user_device_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "device_presence_last_seen_idx": { + "name": "device_presence_last_seen_idx", + "columns": [ + { + "expression": "last_seen_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "device_presence_user_id_users_id_fk": { + "name": "device_presence_user_id_users_id_fk", + "tableFrom": "device_presence", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "device_presence_organization_id_organizations_id_fk": { + "name": "device_presence_organization_id_organizations_id_fk", + "tableFrom": "device_presence", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integration_connections": { + "name": "integration_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connected_by_user_id": { + "name": "connected_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "integration_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_expires_at": { + "name": "token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "external_org_id": { + "name": "external_org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_org_name": { + "name": "external_org_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "integration_connections_org_idx": { + "name": "integration_connections_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "integration_connections_organization_id_organizations_id_fk": { + "name": "integration_connections_organization_id_organizations_id_fk", + "tableFrom": "integration_connections", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integration_connections_connected_by_user_id_users_id_fk": { + "name": "integration_connections_connected_by_user_id_users_id_fk", + "tableFrom": "integration_connections", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "connected_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "integration_connections_unique": { + "name": "integration_connections_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "provider" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_repository_id": { + "name": "github_repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repo_owner": { + "name": "repo_owner", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_name": { + "name": "repo_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'main'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_organization_id_idx": { + "name": "projects_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_organization_id_organizations_id_fk": { + "name": "projects_organization_id_organizations_id_fk", + "tableFrom": "projects", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "projects_github_repository_id_github_repositories_id_fk": { + "name": "projects_github_repository_id_github_repositories_id_fk", + "tableFrom": "projects", + "tableTo": "github_repositories", + "columnsFrom": [ + "github_repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "projects_org_slug_unique": { + "name": "projects_org_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sandbox_images": { + "name": "sandbox_images", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "setup_commands": { + "name": "setup_commands", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "base_image": { + "name": "base_image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_packages": { + "name": "system_packages", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sandbox_images_organization_id_idx": { + "name": "sandbox_images_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sandbox_images_organization_id_organizations_id_fk": { + "name": "sandbox_images_organization_id_organizations_id_fk", + "tableFrom": "sandbox_images", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sandbox_images_project_id_projects_id_fk": { + "name": "sandbox_images_project_id_projects_id_fk", + "tableFrom": "sandbox_images", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sandbox_images_project_unique": { + "name": "sandbox_images_project_unique", + "nullsNotDistinct": false, + "columns": [ + "project_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secrets": { + "name": "secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_value": { + "name": "encrypted_value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensitive": { + "name": "sensitive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secrets_project_id_idx": { + "name": "secrets_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secrets_organization_id_idx": { + "name": "secrets_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secrets_organization_id_organizations_id_fk": { + "name": "secrets_organization_id_organizations_id_fk", + "tableFrom": "secrets", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "secrets_project_id_projects_id_fk": { + "name": "secrets_project_id_projects_id_fk", + "tableFrom": "secrets", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "secrets_created_by_user_id_users_id_fk": { + "name": "secrets_created_by_user_id_users_id_fk", + "tableFrom": "secrets", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "secrets_project_key_unique": { + "name": "secrets_project_key_unique", + "nullsNotDistinct": false, + "columns": [ + "project_id", + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_hosts": { + "name": "session_hosts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "session_hosts_session_id_idx": { + "name": "session_hosts_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_hosts_org_idx": { + "name": "session_hosts_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_hosts_device_id_idx": { + "name": "session_hosts_device_id_idx", + "columns": [ + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_hosts_session_id_chat_sessions_id_fk": { + "name": "session_hosts_session_id_chat_sessions_id_fk", + "tableFrom": "session_hosts", + "tableTo": "chat_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_hosts_organization_id_organizations_id_fk": { + "name": "session_hosts_organization_id_organizations_id_fk", + "tableFrom": "session_hosts", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscriptions": { + "name": "subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'incomplete'" + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cancel_at": { + "name": "cancel_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "subscriptions_reference_id_idx": { + "name": "subscriptions_reference_id_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "subscriptions_stripe_customer_id_idx": { + "name": "subscriptions_stripe_customer_id_idx", + "columns": [ + { + "expression": "stripe_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "subscriptions_status_idx": { + "name": "subscriptions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "subscriptions_reference_id_organizations_id_fk": { + "name": "subscriptions_reference_id_organizations_id_fk", + "tableFrom": "subscriptions", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "reference_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_statuses": { + "name": "task_statuses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "progress_percent": { + "name": "progress_percent", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "external_provider": { + "name": "external_provider", + "type": "integration_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "task_statuses_organization_id_idx": { + "name": "task_statuses_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_statuses_type_idx": { + "name": "task_statuses_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_statuses_organization_id_organizations_id_fk": { + "name": "task_statuses_organization_id_organizations_id_fk", + "tableFrom": "task_statuses", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "task_statuses_org_external_unique": { + "name": "task_statuses_org_external_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "external_provider", + "external_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tasks": { + "name": "tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_id": { + "name": "status_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "task_priority", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assignee_id": { + "name": "assignee_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "estimate": { + "name": "estimate", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "due_date": { + "name": "due_date", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "labels": { + "name": "labels", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_provider": { + "name": "external_provider", + "type": "integration_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_key": { + "name": "external_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_url": { + "name": "external_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "sync_error": { + "name": "sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_external_id": { + "name": "assignee_external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_display_name": { + "name": "assignee_display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_avatar_url": { + "name": "assignee_avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tasks_slug_idx": { + "name": "tasks_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_organization_id_idx": { + "name": "tasks_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_assignee_id_idx": { + "name": "tasks_assignee_id_idx", + "columns": [ + { + "expression": "assignee_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_creator_id_idx": { + "name": "tasks_creator_id_idx", + "columns": [ + { + "expression": "creator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_status_id_idx": { + "name": "tasks_status_id_idx", + "columns": [ + { + "expression": "status_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_created_at_idx": { + "name": "tasks_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_external_provider_idx": { + "name": "tasks_external_provider_idx", + "columns": [ + { + "expression": "external_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_assignee_external_id_idx": { + "name": "tasks_assignee_external_id_idx", + "columns": [ + { + "expression": "assignee_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tasks_status_id_task_statuses_id_fk": { + "name": "tasks_status_id_task_statuses_id_fk", + "tableFrom": "tasks", + "tableTo": "task_statuses", + "columnsFrom": [ + "status_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tasks_organization_id_organizations_id_fk": { + "name": "tasks_organization_id_organizations_id_fk", + "tableFrom": "tasks", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tasks_assignee_id_users_id_fk": { + "name": "tasks_assignee_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "assignee_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_creator_id_users_id_fk": { + "name": "tasks_creator_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tasks_external_unique": { + "name": "tasks_external_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "external_provider", + "external_id" + ] + }, + "tasks_org_slug_unique": { + "name": "tasks_org_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users__slack_users": { + "name": "users__slack_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slack_user_id": { + "name": "slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "model_preference": { + "name": "model_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users__slack_users_user_idx": { + "name": "users__slack_users_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users__slack_users_org_idx": { + "name": "users__slack_users_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "users__slack_users_user_id_users_id_fk": { + "name": "users__slack_users_user_id_users_id_fk", + "tableFrom": "users__slack_users", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "users__slack_users_organization_id_organizations_id_fk": { + "name": "users__slack_users_organization_id_organizations_id_fk", + "tableFrom": "users__slack_users", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users__slack_users_unique": { + "name": "users__slack_users_unique", + "nullsNotDistinct": false, + "columns": [ + "slack_user_id", + "team_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_clients": { + "name": "v2_clients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "v2_client_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_clients_organization_id_idx": { + "name": "v2_clients_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_clients_user_id_idx": { + "name": "v2_clients_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_clients_organization_id_organizations_id_fk": { + "name": "v2_clients_organization_id_organizations_id_fk", + "tableFrom": "v2_clients", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_clients_user_id_users_id_fk": { + "name": "v2_clients_user_id_users_id_fk", + "tableFrom": "v2_clients", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "v2_clients_org_user_machine_unique": { + "name": "v2_clients_org_user_machine_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "user_id", + "machine_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_hosts": { + "name": "v2_hosts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_online": { + "name": "is_online", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_hosts_organization_id_idx": { + "name": "v2_hosts_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_hosts_organization_id_organizations_id_fk": { + "name": "v2_hosts_organization_id_organizations_id_fk", + "tableFrom": "v2_hosts", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_hosts_created_by_user_id_users_id_fk": { + "name": "v2_hosts_created_by_user_id_users_id_fk", + "tableFrom": "v2_hosts", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "v2_hosts_org_machine_id_unique": { + "name": "v2_hosts_org_machine_id_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "machine_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_projects": { + "name": "v2_projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_clone_url": { + "name": "repo_clone_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_repository_id": { + "name": "github_repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_projects_organization_id_idx": { + "name": "v2_projects_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_projects_organization_id_organizations_id_fk": { + "name": "v2_projects_organization_id_organizations_id_fk", + "tableFrom": "v2_projects", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_projects_github_repository_id_github_repositories_id_fk": { + "name": "v2_projects_github_repository_id_github_repositories_id_fk", + "tableFrom": "v2_projects", + "tableTo": "github_repositories", + "columnsFrom": [ + "github_repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "v2_projects_org_slug_unique": { + "name": "v2_projects_org_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_users_hosts": { + "name": "v2_users_hosts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "v2_users_host_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_users_hosts_organization_id_idx": { + "name": "v2_users_hosts_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_users_hosts_user_id_idx": { + "name": "v2_users_hosts_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_users_hosts_host_id_idx": { + "name": "v2_users_hosts_host_id_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_users_hosts_organization_id_organizations_id_fk": { + "name": "v2_users_hosts_organization_id_organizations_id_fk", + "tableFrom": "v2_users_hosts", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_users_hosts_user_id_users_id_fk": { + "name": "v2_users_hosts_user_id_users_id_fk", + "tableFrom": "v2_users_hosts", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_users_hosts_host_id_v2_hosts_id_fk": { + "name": "v2_users_hosts_host_id_v2_hosts_id_fk", + "tableFrom": "v2_users_hosts", + "tableTo": "v2_hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "v2_users_hosts_org_user_host_unique": { + "name": "v2_users_hosts_org_user_host_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "user_id", + "host_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_workspaces": { + "name": "v2_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_workspaces_project_id_idx": { + "name": "v2_workspaces_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_workspaces_organization_id_idx": { + "name": "v2_workspaces_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_workspaces_host_id_idx": { + "name": "v2_workspaces_host_id_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_workspaces_organization_id_organizations_id_fk": { + "name": "v2_workspaces_organization_id_organizations_id_fk", + "tableFrom": "v2_workspaces", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_workspaces_project_id_v2_projects_id_fk": { + "name": "v2_workspaces_project_id_v2_projects_id_fk", + "tableFrom": "v2_workspaces", + "tableTo": "v2_projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_workspaces_host_id_v2_hosts_id_fk": { + "name": "v2_workspaces_host_id_v2_hosts_id_fk", + "tableFrom": "v2_workspaces", + "tableTo": "v2_hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "v2_workspaces_created_by_user_id_users_id_fk": { + "name": "v2_workspaces_created_by_user_id_users_id_fk", + "tableFrom": "v2_workspaces", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspaces": { + "name": "workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "workspace_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspaces_project_id_idx": { + "name": "workspaces_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspaces_organization_id_idx": { + "name": "workspaces_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspaces_type_idx": { + "name": "workspaces_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspaces_organization_id_organizations_id_fk": { + "name": "workspaces_organization_id_organizations_id_fk", + "tableFrom": "workspaces", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_project_id_projects_id_fk": { + "name": "workspaces_project_id_projects_id_fk", + "tableFrom": "workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_created_by_user_id_users_id_fk": { + "name": "workspaces_created_by_user_id_users_id_fk", + "tableFrom": "workspaces", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.automation_run_status": { + "name": "automation_run_status", + "schema": "public", + "values": [ + "dispatching", + "dispatched", + "skipped_offline", + "dispatch_failed" + ] + }, + "public.automation_session_kind": { + "name": "automation_session_kind", + "schema": "public", + "values": [ + "chat", + "terminal" + ] + }, + "public.command_status": { + "name": "command_status", + "schema": "public", + "values": [ + "pending", + "completed", + "failed", + "timeout" + ] + }, + "public.device_type": { + "name": "device_type", + "schema": "public", + "values": [ + "desktop", + "mobile", + "web" + ] + }, + "public.integration_provider": { + "name": "integration_provider", + "schema": "public", + "values": [ + "linear", + "github", + "slack" + ] + }, + "public.task_priority": { + "name": "task_priority", + "schema": "public", + "values": [ + "urgent", + "high", + "medium", + "low", + "none" + ] + }, + "public.task_status": { + "name": "task_status", + "schema": "public", + "values": [ + "backlog", + "todo", + "planning", + "working", + "needs-feedback", + "ready-to-merge", + "completed", + "canceled" + ] + }, + "public.v2_client_type": { + "name": "v2_client_type", + "schema": "public", + "values": [ + "desktop", + "mobile", + "web" + ] + }, + "public.v2_users_host_role": { + "name": "v2_users_host_role", + "schema": "public", + "values": [ + "owner", + "member" + ] + }, + "public.workspace_type": { + "name": "workspace_type", + "schema": "public", + "values": [ + "local", + "cloud" + ] + } + }, + "schemas": { + "auth": "auth", + "ingest": "ingest" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/drizzle/meta/0038_snapshot.json b/packages/db/drizzle/meta/0038_snapshot.json new file mode 100644 index 00000000000..016ce23212a --- /dev/null +++ b/packages/db/drizzle/meta/0038_snapshot.json @@ -0,0 +1,5781 @@ +{ + "id": "ef3bd128-1ac9-4166-a066-b1aa6811772a", + "prevId": "16fdad31-9719-48da-880a-05e1c736dee7", + "version": "7", + "dialect": "postgresql", + "tables": { + "auth.accounts": { + "name": "accounts", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "accounts_user_id_idx": { + "name": "accounts_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.apikeys": { + "name": "apikeys", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "config_id": { + "name": "config_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 86400000 + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "apikeys_configId_idx": { + "name": "apikeys_configId_idx", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "apikeys_referenceId_idx": { + "name": "apikeys_referenceId_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "apikeys_key_idx": { + "name": "apikeys_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.device_codes": { + "name": "device_codes", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "device_code": { + "name": "device_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_code": { + "name": "user_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_polled_at": { + "name": "last_polled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "polling_interval": { + "name": "polling_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.invitations": { + "name": "invitations", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "inviter_id": { + "name": "inviter_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitations_organization_id_idx": { + "name": "invitations_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitations_email_idx": { + "name": "invitations_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitations_organization_id_organizations_id_fk": { + "name": "invitations_organization_id_organizations_id_fk", + "tableFrom": "invitations", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitations_inviter_id_users_id_fk": { + "name": "invitations_inviter_id_users_id_fk", + "tableFrom": "invitations", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.jwkss": { + "name": "jwkss", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.members": { + "name": "members", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "members_organization_id_idx": { + "name": "members_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "members_user_id_idx": { + "name": "members_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "members_organization_id_organizations_id_fk": { + "name": "members_organization_id_organizations_id_fk", + "tableFrom": "members", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "members_user_id_users_id_fk": { + "name": "members_user_id_users_id_fk", + "tableFrom": "members", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_access_tokens": { + "name": "oauth_access_tokens", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_access_tokens_client_id_oauth_clients_client_id_fk": { + "name": "oauth_access_tokens_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "oauth_clients", + "schemaTo": "auth", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_tokens_session_id_sessions_id_fk": { + "name": "oauth_access_tokens_session_id_sessions_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "sessions", + "schemaTo": "auth", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_access_tokens_user_id_users_id_fk": { + "name": "oauth_access_tokens_user_id_users_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_tokens_refresh_id_oauth_refresh_tokens_id_fk": { + "name": "oauth_access_tokens_refresh_id_oauth_refresh_tokens_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "oauth_refresh_tokens", + "schemaTo": "auth", + "columnsFrom": [ + "refresh_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_tokens_token_unique": { + "name": "oauth_access_tokens_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_clients": { + "name": "oauth_clients", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_clients_user_id_users_id_fk": { + "name": "oauth_clients_user_id_users_id_fk", + "tableFrom": "oauth_clients", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_clients_client_id_unique": { + "name": "oauth_clients_client_id_unique", + "nullsNotDistinct": false, + "columns": [ + "client_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_consents": { + "name": "oauth_consents", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_consents_client_id_oauth_clients_client_id_fk": { + "name": "oauth_consents_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_consents", + "tableTo": "oauth_clients", + "schemaTo": "auth", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consents_user_id_users_id_fk": { + "name": "oauth_consents_user_id_users_id_fk", + "tableFrom": "oauth_consents", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_refresh_tokens": { + "name": "oauth_refresh_tokens", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked": { + "name": "revoked", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "auth_time": { + "name": "auth_time", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_refresh_tokens_client_id_oauth_clients_client_id_fk": { + "name": "oauth_refresh_tokens_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_refresh_tokens", + "tableTo": "oauth_clients", + "schemaTo": "auth", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_tokens_session_id_sessions_id_fk": { + "name": "oauth_refresh_tokens_session_id_sessions_id_fk", + "tableFrom": "oauth_refresh_tokens", + "tableTo": "sessions", + "schemaTo": "auth", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_refresh_tokens_user_id_users_id_fk": { + "name": "oauth_refresh_tokens_user_id_users_id_fk", + "tableFrom": "oauth_refresh_tokens", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.organizations": { + "name": "organizations", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_domains": { + "name": "allowed_domains", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + } + }, + "indexes": { + "organizations_slug_idx": { + "name": "organizations_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "organizations_allowed_domains_idx": { + "name": "organizations_allowed_domains_idx", + "columns": [ + { + "expression": "allowed_domains", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.sessions": { + "name": "sessions", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_token_unique": { + "name": "sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.users": { + "name": "users", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_ids": { + "name": "organization_ids", + "type": "uuid[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.verifications": { + "name": "verifications", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verifications_identifier_idx": { + "name": "verifications_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_installations": { + "name": "github_installations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connected_by_user_id": { + "name": "connected_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_login": { + "name": "account_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_type": { + "name": "account_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "suspended": { + "name": "suspended", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "github_installations_installation_id_idx": { + "name": "github_installations_installation_id_idx", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_installations_organization_id_organizations_id_fk": { + "name": "github_installations_organization_id_organizations_id_fk", + "tableFrom": "github_installations", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_installations_connected_by_user_id_users_id_fk": { + "name": "github_installations_connected_by_user_id_users_id_fk", + "tableFrom": "github_installations", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "connected_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_installations_installation_id_unique": { + "name": "github_installations_installation_id_unique", + "nullsNotDistinct": false, + "columns": [ + "installation_id" + ] + }, + "github_installations_org_unique": { + "name": "github_installations_org_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_pull_requests": { + "name": "github_pull_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "repository_id": { + "name": "repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "head_branch": { + "name": "head_branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "head_sha": { + "name": "head_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "base_branch": { + "name": "base_branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_login": { + "name": "author_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_avatar_url": { + "name": "author_avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_draft": { + "name": "is_draft", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "additions": { + "name": "additions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "deletions": { + "name": "deletions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "changed_files": { + "name": "changed_files", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "review_decision": { + "name": "review_decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checks_status": { + "name": "checks_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "checks": { + "name": "checks", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "merged_at": { + "name": "merged_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "github_pull_requests_repository_id_idx": { + "name": "github_pull_requests_repository_id_idx", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_requests_state_idx": { + "name": "github_pull_requests_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_requests_head_branch_idx": { + "name": "github_pull_requests_head_branch_idx", + "columns": [ + { + "expression": "head_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_requests_org_id_idx": { + "name": "github_pull_requests_org_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_pull_requests_repository_id_github_repositories_id_fk": { + "name": "github_pull_requests_repository_id_github_repositories_id_fk", + "tableFrom": "github_pull_requests", + "tableTo": "github_repositories", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_pull_requests_organization_id_organizations_id_fk": { + "name": "github_pull_requests_organization_id_organizations_id_fk", + "tableFrom": "github_pull_requests", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_pull_requests_repo_pr_unique": { + "name": "github_pull_requests_repo_pr_unique", + "nullsNotDistinct": false, + "columns": [ + "repository_id", + "pr_number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_repositories": { + "name": "github_repositories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "installation_id": { + "name": "installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repo_id": { + "name": "repo_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'main'" + }, + "is_private": { + "name": "is_private", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "github_repositories_installation_id_idx": { + "name": "github_repositories_installation_id_idx", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_repositories_full_name_idx": { + "name": "github_repositories_full_name_idx", + "columns": [ + { + "expression": "full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_repositories_org_id_idx": { + "name": "github_repositories_org_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_repositories_installation_id_github_installations_id_fk": { + "name": "github_repositories_installation_id_github_installations_id_fk", + "tableFrom": "github_repositories", + "tableTo": "github_installations", + "columnsFrom": [ + "installation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_repositories_organization_id_organizations_id_fk": { + "name": "github_repositories_organization_id_organizations_id_fk", + "tableFrom": "github_repositories", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_repositories_repo_id_unique": { + "name": "github_repositories_repo_id_unique", + "nullsNotDistinct": false, + "columns": [ + "repo_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "ingest.webhook_events": { + "name": "webhook_events", + "schema": "ingest", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "provider": { + "name": "provider", + "type": "integration_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "retry_count": { + "name": "retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "received_at": { + "name": "received_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "webhook_events_provider_status_idx": { + "name": "webhook_events_provider_status_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_events_provider_event_id_idx": { + "name": "webhook_events_provider_event_id_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_events_received_at_idx": { + "name": "webhook_events_received_at_idx", + "columns": [ + { + "expression": "received_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_commands": { + "name": "agent_commands", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_device_id": { + "name": "target_device_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_device_type": { + "name": "target_device_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool": { + "name": "tool", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "params": { + "name": "params", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "parent_command_id": { + "name": "parent_command_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "command_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "executed_at": { + "name": "executed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "timeout_at": { + "name": "timeout_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "agent_commands_user_status_idx": { + "name": "agent_commands_user_status_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_commands_target_device_status_idx": { + "name": "agent_commands_target_device_status_idx", + "columns": [ + { + "expression": "target_device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_commands_org_created_idx": { + "name": "agent_commands_org_created_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_commands_user_id_users_id_fk": { + "name": "agent_commands_user_id_users_id_fk", + "tableFrom": "agent_commands", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_commands_organization_id_organizations_id_fk": { + "name": "agent_commands_organization_id_organizations_id_fk", + "tableFrom": "agent_commands", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automation_runs": { + "name": "automation_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "automation_id": { + "name": "automation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scheduled_for": { + "name": "scheduled_for", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "v2_workspace_id": { + "name": "v2_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_kind": { + "name": "session_kind", + "type": "automation_session_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "chat_session_id": { + "name": "chat_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "terminal_session_id": { + "name": "terminal_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "automation_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dispatched_at": { + "name": "dispatched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "automation_runs_dedup_idx": { + "name": "automation_runs_dedup_idx", + "columns": [ + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduled_for", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automation_runs_history_idx": { + "name": "automation_runs_history_idx", + "columns": [ + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automation_runs_status_idx": { + "name": "automation_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automation_runs_workspace_idx": { + "name": "automation_runs_workspace_idx", + "columns": [ + { + "expression": "v2_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automation_runs_automation_id_automations_id_fk": { + "name": "automation_runs_automation_id_automations_id_fk", + "tableFrom": "automation_runs", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_runs_organization_id_organizations_id_fk": { + "name": "automation_runs_organization_id_organizations_id_fk", + "tableFrom": "automation_runs", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_runs_host_id_v2_hosts_id_fk": { + "name": "automation_runs_host_id_v2_hosts_id_fk", + "tableFrom": "automation_runs", + "tableTo": "v2_hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "automation_runs_chat_session_id_chat_sessions_id_fk": { + "name": "automation_runs_chat_session_id_chat_sessions_id_fk", + "tableFrom": "automation_runs", + "tableTo": "chat_sessions", + "columnsFrom": [ + "chat_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automations": { + "name": "automations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_config": { + "name": "agent_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "target_host_id": { + "name": "target_host_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "v2_project_id": { + "name": "v2_project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "v2_workspace_id": { + "name": "v2_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "rrule": { + "name": "rrule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dtstart": { + "name": "dtstart", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "mcp_scope": { + "name": "mcp_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "automations_dispatcher_idx": { + "name": "automations_dispatcher_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automations_owner_idx": { + "name": "automations_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automations_organization_idx": { + "name": "automations_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automations_organization_id_organizations_id_fk": { + "name": "automations_organization_id_organizations_id_fk", + "tableFrom": "automations", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automations_owner_user_id_users_id_fk": { + "name": "automations_owner_user_id_users_id_fk", + "tableFrom": "automations", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automations_target_host_id_v2_hosts_id_fk": { + "name": "automations_target_host_id_v2_hosts_id_fk", + "tableFrom": "automations", + "tableTo": "v2_hosts", + "columnsFrom": [ + "target_host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "automations_v2_project_id_v2_projects_id_fk": { + "name": "automations_v2_project_id_v2_projects_id_fk", + "tableFrom": "automations", + "tableTo": "v2_projects", + "columnsFrom": [ + "v2_project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_sessions": { + "name": "chat_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "v2_workspace_id": { + "name": "v2_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_sessions_org_idx": { + "name": "chat_sessions_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_sessions_created_by_idx": { + "name": "chat_sessions_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_sessions_last_active_idx": { + "name": "chat_sessions_last_active_idx", + "columns": [ + { + "expression": "last_active_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_sessions_organization_id_organizations_id_fk": { + "name": "chat_sessions_organization_id_organizations_id_fk", + "tableFrom": "chat_sessions", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_sessions_created_by_users_id_fk": { + "name": "chat_sessions_created_by_users_id_fk", + "tableFrom": "chat_sessions", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_sessions_workspace_id_workspaces_id_fk": { + "name": "chat_sessions_workspace_id_workspaces_id_fk", + "tableFrom": "chat_sessions", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_sessions_v2_workspace_id_v2_workspaces_id_fk": { + "name": "chat_sessions_v2_workspace_id_v2_workspaces_id_fk", + "tableFrom": "chat_sessions", + "tableTo": "v2_workspaces", + "columnsFrom": [ + "v2_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.device_presence": { + "name": "device_presence", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_name": { + "name": "device_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "device_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "device_presence_user_org_idx": { + "name": "device_presence_user_org_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "device_presence_user_device_idx": { + "name": "device_presence_user_device_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "device_presence_last_seen_idx": { + "name": "device_presence_last_seen_idx", + "columns": [ + { + "expression": "last_seen_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "device_presence_user_id_users_id_fk": { + "name": "device_presence_user_id_users_id_fk", + "tableFrom": "device_presence", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "device_presence_organization_id_organizations_id_fk": { + "name": "device_presence_organization_id_organizations_id_fk", + "tableFrom": "device_presence", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integration_connections": { + "name": "integration_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connected_by_user_id": { + "name": "connected_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "integration_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_expires_at": { + "name": "token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "external_org_id": { + "name": "external_org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_org_name": { + "name": "external_org_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "integration_connections_org_idx": { + "name": "integration_connections_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "integration_connections_organization_id_organizations_id_fk": { + "name": "integration_connections_organization_id_organizations_id_fk", + "tableFrom": "integration_connections", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integration_connections_connected_by_user_id_users_id_fk": { + "name": "integration_connections_connected_by_user_id_users_id_fk", + "tableFrom": "integration_connections", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "connected_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "integration_connections_unique": { + "name": "integration_connections_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "provider" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_repository_id": { + "name": "github_repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repo_owner": { + "name": "repo_owner", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_name": { + "name": "repo_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'main'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_organization_id_idx": { + "name": "projects_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_organization_id_organizations_id_fk": { + "name": "projects_organization_id_organizations_id_fk", + "tableFrom": "projects", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "projects_github_repository_id_github_repositories_id_fk": { + "name": "projects_github_repository_id_github_repositories_id_fk", + "tableFrom": "projects", + "tableTo": "github_repositories", + "columnsFrom": [ + "github_repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "projects_org_slug_unique": { + "name": "projects_org_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sandbox_images": { + "name": "sandbox_images", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "setup_commands": { + "name": "setup_commands", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "base_image": { + "name": "base_image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_packages": { + "name": "system_packages", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sandbox_images_organization_id_idx": { + "name": "sandbox_images_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sandbox_images_organization_id_organizations_id_fk": { + "name": "sandbox_images_organization_id_organizations_id_fk", + "tableFrom": "sandbox_images", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sandbox_images_project_id_projects_id_fk": { + "name": "sandbox_images_project_id_projects_id_fk", + "tableFrom": "sandbox_images", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sandbox_images_project_unique": { + "name": "sandbox_images_project_unique", + "nullsNotDistinct": false, + "columns": [ + "project_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secrets": { + "name": "secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_value": { + "name": "encrypted_value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensitive": { + "name": "sensitive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secrets_project_id_idx": { + "name": "secrets_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secrets_organization_id_idx": { + "name": "secrets_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secrets_organization_id_organizations_id_fk": { + "name": "secrets_organization_id_organizations_id_fk", + "tableFrom": "secrets", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "secrets_project_id_projects_id_fk": { + "name": "secrets_project_id_projects_id_fk", + "tableFrom": "secrets", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "secrets_created_by_user_id_users_id_fk": { + "name": "secrets_created_by_user_id_users_id_fk", + "tableFrom": "secrets", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "secrets_project_key_unique": { + "name": "secrets_project_key_unique", + "nullsNotDistinct": false, + "columns": [ + "project_id", + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_hosts": { + "name": "session_hosts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "session_hosts_session_id_idx": { + "name": "session_hosts_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_hosts_org_idx": { + "name": "session_hosts_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_hosts_device_id_idx": { + "name": "session_hosts_device_id_idx", + "columns": [ + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_hosts_session_id_chat_sessions_id_fk": { + "name": "session_hosts_session_id_chat_sessions_id_fk", + "tableFrom": "session_hosts", + "tableTo": "chat_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_hosts_organization_id_organizations_id_fk": { + "name": "session_hosts_organization_id_organizations_id_fk", + "tableFrom": "session_hosts", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscriptions": { + "name": "subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'incomplete'" + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cancel_at": { + "name": "cancel_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "subscriptions_reference_id_idx": { + "name": "subscriptions_reference_id_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "subscriptions_stripe_customer_id_idx": { + "name": "subscriptions_stripe_customer_id_idx", + "columns": [ + { + "expression": "stripe_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "subscriptions_status_idx": { + "name": "subscriptions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "subscriptions_reference_id_organizations_id_fk": { + "name": "subscriptions_reference_id_organizations_id_fk", + "tableFrom": "subscriptions", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "reference_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_statuses": { + "name": "task_statuses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "progress_percent": { + "name": "progress_percent", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "external_provider": { + "name": "external_provider", + "type": "integration_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "task_statuses_organization_id_idx": { + "name": "task_statuses_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_statuses_type_idx": { + "name": "task_statuses_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_statuses_organization_id_organizations_id_fk": { + "name": "task_statuses_organization_id_organizations_id_fk", + "tableFrom": "task_statuses", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "task_statuses_org_external_unique": { + "name": "task_statuses_org_external_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "external_provider", + "external_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tasks": { + "name": "tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_id": { + "name": "status_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "task_priority", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assignee_id": { + "name": "assignee_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "estimate": { + "name": "estimate", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "due_date": { + "name": "due_date", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "labels": { + "name": "labels", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_provider": { + "name": "external_provider", + "type": "integration_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_key": { + "name": "external_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_url": { + "name": "external_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "sync_error": { + "name": "sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_external_id": { + "name": "assignee_external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_display_name": { + "name": "assignee_display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_avatar_url": { + "name": "assignee_avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tasks_slug_idx": { + "name": "tasks_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_organization_id_idx": { + "name": "tasks_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_assignee_id_idx": { + "name": "tasks_assignee_id_idx", + "columns": [ + { + "expression": "assignee_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_creator_id_idx": { + "name": "tasks_creator_id_idx", + "columns": [ + { + "expression": "creator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_status_id_idx": { + "name": "tasks_status_id_idx", + "columns": [ + { + "expression": "status_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_created_at_idx": { + "name": "tasks_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_external_provider_idx": { + "name": "tasks_external_provider_idx", + "columns": [ + { + "expression": "external_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_assignee_external_id_idx": { + "name": "tasks_assignee_external_id_idx", + "columns": [ + { + "expression": "assignee_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tasks_status_id_task_statuses_id_fk": { + "name": "tasks_status_id_task_statuses_id_fk", + "tableFrom": "tasks", + "tableTo": "task_statuses", + "columnsFrom": [ + "status_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tasks_organization_id_organizations_id_fk": { + "name": "tasks_organization_id_organizations_id_fk", + "tableFrom": "tasks", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tasks_assignee_id_users_id_fk": { + "name": "tasks_assignee_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "assignee_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_creator_id_users_id_fk": { + "name": "tasks_creator_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tasks_external_unique": { + "name": "tasks_external_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "external_provider", + "external_id" + ] + }, + "tasks_org_slug_unique": { + "name": "tasks_org_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users__slack_users": { + "name": "users__slack_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slack_user_id": { + "name": "slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "model_preference": { + "name": "model_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users__slack_users_user_idx": { + "name": "users__slack_users_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users__slack_users_org_idx": { + "name": "users__slack_users_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "users__slack_users_user_id_users_id_fk": { + "name": "users__slack_users_user_id_users_id_fk", + "tableFrom": "users__slack_users", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "users__slack_users_organization_id_organizations_id_fk": { + "name": "users__slack_users_organization_id_organizations_id_fk", + "tableFrom": "users__slack_users", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users__slack_users_unique": { + "name": "users__slack_users_unique", + "nullsNotDistinct": false, + "columns": [ + "slack_user_id", + "team_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_clients": { + "name": "v2_clients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "v2_client_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_clients_organization_id_idx": { + "name": "v2_clients_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_clients_user_id_idx": { + "name": "v2_clients_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_clients_organization_id_organizations_id_fk": { + "name": "v2_clients_organization_id_organizations_id_fk", + "tableFrom": "v2_clients", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_clients_user_id_users_id_fk": { + "name": "v2_clients_user_id_users_id_fk", + "tableFrom": "v2_clients", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "v2_clients_org_user_machine_unique": { + "name": "v2_clients_org_user_machine_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "user_id", + "machine_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_hosts": { + "name": "v2_hosts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_online": { + "name": "is_online", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_hosts_organization_id_idx": { + "name": "v2_hosts_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_hosts_organization_id_organizations_id_fk": { + "name": "v2_hosts_organization_id_organizations_id_fk", + "tableFrom": "v2_hosts", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_hosts_created_by_user_id_users_id_fk": { + "name": "v2_hosts_created_by_user_id_users_id_fk", + "tableFrom": "v2_hosts", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "v2_hosts_org_machine_id_unique": { + "name": "v2_hosts_org_machine_id_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "machine_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_projects": { + "name": "v2_projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_clone_url": { + "name": "repo_clone_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_repository_id": { + "name": "github_repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_projects_organization_id_idx": { + "name": "v2_projects_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_projects_organization_id_organizations_id_fk": { + "name": "v2_projects_organization_id_organizations_id_fk", + "tableFrom": "v2_projects", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_projects_github_repository_id_github_repositories_id_fk": { + "name": "v2_projects_github_repository_id_github_repositories_id_fk", + "tableFrom": "v2_projects", + "tableTo": "github_repositories", + "columnsFrom": [ + "github_repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "v2_projects_org_slug_unique": { + "name": "v2_projects_org_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_users_hosts": { + "name": "v2_users_hosts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "v2_users_host_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_users_hosts_organization_id_idx": { + "name": "v2_users_hosts_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_users_hosts_user_id_idx": { + "name": "v2_users_hosts_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_users_hosts_host_id_idx": { + "name": "v2_users_hosts_host_id_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_users_hosts_organization_id_organizations_id_fk": { + "name": "v2_users_hosts_organization_id_organizations_id_fk", + "tableFrom": "v2_users_hosts", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_users_hosts_user_id_users_id_fk": { + "name": "v2_users_hosts_user_id_users_id_fk", + "tableFrom": "v2_users_hosts", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_users_hosts_host_id_v2_hosts_id_fk": { + "name": "v2_users_hosts_host_id_v2_hosts_id_fk", + "tableFrom": "v2_users_hosts", + "tableTo": "v2_hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "v2_users_hosts_org_user_host_unique": { + "name": "v2_users_hosts_org_user_host_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "user_id", + "host_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_workspaces": { + "name": "v2_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "v2_workspace_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'worktree'" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_workspaces_project_id_idx": { + "name": "v2_workspaces_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_workspaces_organization_id_idx": { + "name": "v2_workspaces_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_workspaces_host_id_idx": { + "name": "v2_workspaces_host_id_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_workspaces_one_main_per_host": { + "name": "v2_workspaces_one_main_per_host", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"v2_workspaces\".\"type\" = 'main'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_workspaces_organization_id_organizations_id_fk": { + "name": "v2_workspaces_organization_id_organizations_id_fk", + "tableFrom": "v2_workspaces", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_workspaces_project_id_v2_projects_id_fk": { + "name": "v2_workspaces_project_id_v2_projects_id_fk", + "tableFrom": "v2_workspaces", + "tableTo": "v2_projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_workspaces_host_id_v2_hosts_id_fk": { + "name": "v2_workspaces_host_id_v2_hosts_id_fk", + "tableFrom": "v2_workspaces", + "tableTo": "v2_hosts", + "columnsFrom": [ + "host_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "v2_workspaces_created_by_user_id_users_id_fk": { + "name": "v2_workspaces_created_by_user_id_users_id_fk", + "tableFrom": "v2_workspaces", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspaces": { + "name": "workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "workspace_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspaces_project_id_idx": { + "name": "workspaces_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspaces_organization_id_idx": { + "name": "workspaces_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspaces_type_idx": { + "name": "workspaces_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspaces_organization_id_organizations_id_fk": { + "name": "workspaces_organization_id_organizations_id_fk", + "tableFrom": "workspaces", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_project_id_projects_id_fk": { + "name": "workspaces_project_id_projects_id_fk", + "tableFrom": "workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_created_by_user_id_users_id_fk": { + "name": "workspaces_created_by_user_id_users_id_fk", + "tableFrom": "workspaces", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.automation_run_status": { + "name": "automation_run_status", + "schema": "public", + "values": [ + "dispatching", + "dispatched", + "skipped_offline", + "dispatch_failed" + ] + }, + "public.automation_session_kind": { + "name": "automation_session_kind", + "schema": "public", + "values": [ + "chat", + "terminal" + ] + }, + "public.command_status": { + "name": "command_status", + "schema": "public", + "values": [ + "pending", + "completed", + "failed", + "timeout" + ] + }, + "public.device_type": { + "name": "device_type", + "schema": "public", + "values": [ + "desktop", + "mobile", + "web" + ] + }, + "public.integration_provider": { + "name": "integration_provider", + "schema": "public", + "values": [ + "linear", + "github", + "slack" + ] + }, + "public.task_priority": { + "name": "task_priority", + "schema": "public", + "values": [ + "urgent", + "high", + "medium", + "low", + "none" + ] + }, + "public.task_status": { + "name": "task_status", + "schema": "public", + "values": [ + "backlog", + "todo", + "planning", + "working", + "needs-feedback", + "ready-to-merge", + "completed", + "canceled" + ] + }, + "public.v2_client_type": { + "name": "v2_client_type", + "schema": "public", + "values": [ + "desktop", + "mobile", + "web" + ] + }, + "public.v2_users_host_role": { + "name": "v2_users_host_role", + "schema": "public", + "values": [ + "owner", + "member" + ] + }, + "public.v2_workspace_type": { + "name": "v2_workspace_type", + "schema": "public", + "values": [ + "main", + "worktree" + ] + }, + "public.workspace_type": { + "name": "workspace_type", + "schema": "public", + "values": [ + "local", + "cloud" + ] + } + }, + "schemas": { + "auth": "auth", + "ingest": "ingest" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/drizzle/meta/0039_snapshot.json b/packages/db/drizzle/meta/0039_snapshot.json new file mode 100644 index 00000000000..4428ce94939 --- /dev/null +++ b/packages/db/drizzle/meta/0039_snapshot.json @@ -0,0 +1,5616 @@ +{ + "id": "93706e33-59b4-4ec5-9266-49a93063f608", + "prevId": "ef3bd128-1ac9-4166-a066-b1aa6811772a", + "version": "7", + "dialect": "postgresql", + "tables": { + "auth.accounts": { + "name": "accounts", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "accounts_user_id_idx": { + "name": "accounts_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.apikeys": { + "name": "apikeys", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "config_id": { + "name": "config_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 86400000 + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "apikeys_configId_idx": { + "name": "apikeys_configId_idx", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "apikeys_referenceId_idx": { + "name": "apikeys_referenceId_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "apikeys_key_idx": { + "name": "apikeys_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.device_codes": { + "name": "device_codes", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "device_code": { + "name": "device_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_code": { + "name": "user_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_polled_at": { + "name": "last_polled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "polling_interval": { + "name": "polling_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.invitations": { + "name": "invitations", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "inviter_id": { + "name": "inviter_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "invitations_organization_id_idx": { + "name": "invitations_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitations_email_idx": { + "name": "invitations_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitations_organization_id_organizations_id_fk": { + "name": "invitations_organization_id_organizations_id_fk", + "tableFrom": "invitations", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitations_inviter_id_users_id_fk": { + "name": "invitations_inviter_id_users_id_fk", + "tableFrom": "invitations", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.jwkss": { + "name": "jwkss", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.members": { + "name": "members", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "members_organization_id_idx": { + "name": "members_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "members_user_id_idx": { + "name": "members_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "members_organization_id_organizations_id_fk": { + "name": "members_organization_id_organizations_id_fk", + "tableFrom": "members", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "members_user_id_users_id_fk": { + "name": "members_user_id_users_id_fk", + "tableFrom": "members", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_access_tokens": { + "name": "oauth_access_tokens", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_access_tokens_client_id_oauth_clients_client_id_fk": { + "name": "oauth_access_tokens_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "oauth_clients", + "schemaTo": "auth", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_tokens_session_id_sessions_id_fk": { + "name": "oauth_access_tokens_session_id_sessions_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "sessions", + "schemaTo": "auth", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_access_tokens_user_id_users_id_fk": { + "name": "oauth_access_tokens_user_id_users_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_tokens_refresh_id_oauth_refresh_tokens_id_fk": { + "name": "oauth_access_tokens_refresh_id_oauth_refresh_tokens_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "oauth_refresh_tokens", + "schemaTo": "auth", + "columnsFrom": [ + "refresh_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_tokens_token_unique": { + "name": "oauth_access_tokens_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_clients": { + "name": "oauth_clients", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_clients_user_id_users_id_fk": { + "name": "oauth_clients_user_id_users_id_fk", + "tableFrom": "oauth_clients", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_clients_client_id_unique": { + "name": "oauth_clients_client_id_unique", + "nullsNotDistinct": false, + "columns": [ + "client_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_consents": { + "name": "oauth_consents", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_consents_client_id_oauth_clients_client_id_fk": { + "name": "oauth_consents_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_consents", + "tableTo": "oauth_clients", + "schemaTo": "auth", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consents_user_id_users_id_fk": { + "name": "oauth_consents_user_id_users_id_fk", + "tableFrom": "oauth_consents", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.oauth_refresh_tokens": { + "name": "oauth_refresh_tokens", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked": { + "name": "revoked", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "auth_time": { + "name": "auth_time", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_refresh_tokens_client_id_oauth_clients_client_id_fk": { + "name": "oauth_refresh_tokens_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_refresh_tokens", + "tableTo": "oauth_clients", + "schemaTo": "auth", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_tokens_session_id_sessions_id_fk": { + "name": "oauth_refresh_tokens_session_id_sessions_id_fk", + "tableFrom": "oauth_refresh_tokens", + "tableTo": "sessions", + "schemaTo": "auth", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_refresh_tokens_user_id_users_id_fk": { + "name": "oauth_refresh_tokens_user_id_users_id_fk", + "tableFrom": "oauth_refresh_tokens", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.organizations": { + "name": "organizations", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_domains": { + "name": "allowed_domains", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + } + }, + "indexes": { + "organizations_slug_idx": { + "name": "organizations_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "organizations_allowed_domains_idx": { + "name": "organizations_allowed_domains_idx", + "columns": [ + { + "expression": "allowed_domains", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.sessions": { + "name": "sessions", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_token_unique": { + "name": "sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.users": { + "name": "users", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_ids": { + "name": "organization_ids", + "type": "uuid[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.verifications": { + "name": "verifications", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verifications_identifier_idx": { + "name": "verifications_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_installations": { + "name": "github_installations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connected_by_user_id": { + "name": "connected_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_login": { + "name": "account_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_type": { + "name": "account_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "suspended": { + "name": "suspended", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "github_installations_installation_id_idx": { + "name": "github_installations_installation_id_idx", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_installations_organization_id_organizations_id_fk": { + "name": "github_installations_organization_id_organizations_id_fk", + "tableFrom": "github_installations", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_installations_connected_by_user_id_users_id_fk": { + "name": "github_installations_connected_by_user_id_users_id_fk", + "tableFrom": "github_installations", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "connected_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_installations_installation_id_unique": { + "name": "github_installations_installation_id_unique", + "nullsNotDistinct": false, + "columns": [ + "installation_id" + ] + }, + "github_installations_org_unique": { + "name": "github_installations_org_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_pull_requests": { + "name": "github_pull_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "repository_id": { + "name": "repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "head_branch": { + "name": "head_branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "head_sha": { + "name": "head_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "base_branch": { + "name": "base_branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_login": { + "name": "author_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_avatar_url": { + "name": "author_avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_draft": { + "name": "is_draft", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "additions": { + "name": "additions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "deletions": { + "name": "deletions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "changed_files": { + "name": "changed_files", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "review_decision": { + "name": "review_decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checks_status": { + "name": "checks_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "checks": { + "name": "checks", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "merged_at": { + "name": "merged_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "github_pull_requests_repository_id_idx": { + "name": "github_pull_requests_repository_id_idx", + "columns": [ + { + "expression": "repository_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_requests_state_idx": { + "name": "github_pull_requests_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_requests_head_branch_idx": { + "name": "github_pull_requests_head_branch_idx", + "columns": [ + { + "expression": "head_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_pull_requests_org_id_idx": { + "name": "github_pull_requests_org_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_pull_requests_repository_id_github_repositories_id_fk": { + "name": "github_pull_requests_repository_id_github_repositories_id_fk", + "tableFrom": "github_pull_requests", + "tableTo": "github_repositories", + "columnsFrom": [ + "repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_pull_requests_organization_id_organizations_id_fk": { + "name": "github_pull_requests_organization_id_organizations_id_fk", + "tableFrom": "github_pull_requests", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_pull_requests_repo_pr_unique": { + "name": "github_pull_requests_repo_pr_unique", + "nullsNotDistinct": false, + "columns": [ + "repository_id", + "pr_number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_repositories": { + "name": "github_repositories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "installation_id": { + "name": "installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repo_id": { + "name": "repo_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'main'" + }, + "is_private": { + "name": "is_private", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "github_repositories_installation_id_idx": { + "name": "github_repositories_installation_id_idx", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_repositories_full_name_idx": { + "name": "github_repositories_full_name_idx", + "columns": [ + { + "expression": "full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "github_repositories_org_id_idx": { + "name": "github_repositories_org_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_repositories_installation_id_github_installations_id_fk": { + "name": "github_repositories_installation_id_github_installations_id_fk", + "tableFrom": "github_repositories", + "tableTo": "github_installations", + "columnsFrom": [ + "installation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_repositories_organization_id_organizations_id_fk": { + "name": "github_repositories_organization_id_organizations_id_fk", + "tableFrom": "github_repositories", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "github_repositories_repo_id_unique": { + "name": "github_repositories_repo_id_unique", + "nullsNotDistinct": false, + "columns": [ + "repo_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "ingest.webhook_events": { + "name": "webhook_events", + "schema": "ingest", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "provider": { + "name": "provider", + "type": "integration_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "retry_count": { + "name": "retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "received_at": { + "name": "received_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "webhook_events_provider_status_idx": { + "name": "webhook_events_provider_status_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_events_provider_event_id_idx": { + "name": "webhook_events_provider_event_id_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_events_received_at_idx": { + "name": "webhook_events_received_at_idx", + "columns": [ + { + "expression": "received_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_commands": { + "name": "agent_commands", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "target_device_id": { + "name": "target_device_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_device_type": { + "name": "target_device_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool": { + "name": "tool", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "params": { + "name": "params", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "parent_command_id": { + "name": "parent_command_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "command_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "executed_at": { + "name": "executed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "timeout_at": { + "name": "timeout_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "agent_commands_user_status_idx": { + "name": "agent_commands_user_status_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_commands_target_device_status_idx": { + "name": "agent_commands_target_device_status_idx", + "columns": [ + { + "expression": "target_device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_commands_org_created_idx": { + "name": "agent_commands_org_created_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_commands_user_id_users_id_fk": { + "name": "agent_commands_user_id_users_id_fk", + "tableFrom": "agent_commands", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_commands_organization_id_organizations_id_fk": { + "name": "agent_commands_organization_id_organizations_id_fk", + "tableFrom": "agent_commands", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automation_runs": { + "name": "automation_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "automation_id": { + "name": "automation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scheduled_for": { + "name": "scheduled_for", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "v2_workspace_id": { + "name": "v2_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_kind": { + "name": "session_kind", + "type": "automation_session_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "chat_session_id": { + "name": "chat_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "terminal_session_id": { + "name": "terminal_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "automation_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dispatched_at": { + "name": "dispatched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "automation_runs_dedup_idx": { + "name": "automation_runs_dedup_idx", + "columns": [ + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scheduled_for", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automation_runs_history_idx": { + "name": "automation_runs_history_idx", + "columns": [ + { + "expression": "automation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automation_runs_status_idx": { + "name": "automation_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automation_runs_workspace_idx": { + "name": "automation_runs_workspace_idx", + "columns": [ + { + "expression": "v2_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automation_runs_automation_id_automations_id_fk": { + "name": "automation_runs_automation_id_automations_id_fk", + "tableFrom": "automation_runs", + "tableTo": "automations", + "columnsFrom": [ + "automation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_runs_organization_id_organizations_id_fk": { + "name": "automation_runs_organization_id_organizations_id_fk", + "tableFrom": "automation_runs", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automation_runs_chat_session_id_chat_sessions_id_fk": { + "name": "automation_runs_chat_session_id_chat_sessions_id_fk", + "tableFrom": "automation_runs", + "tableTo": "chat_sessions", + "columnsFrom": [ + "chat_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.automations": { + "name": "automations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_config": { + "name": "agent_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "target_host_id": { + "name": "target_host_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "v2_project_id": { + "name": "v2_project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "v2_workspace_id": { + "name": "v2_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "rrule": { + "name": "rrule", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dtstart": { + "name": "dtstart", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "mcp_scope": { + "name": "mcp_scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "automations_dispatcher_idx": { + "name": "automations_dispatcher_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automations_owner_idx": { + "name": "automations_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "automations_organization_idx": { + "name": "automations_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "automations_organization_id_organizations_id_fk": { + "name": "automations_organization_id_organizations_id_fk", + "tableFrom": "automations", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automations_owner_user_id_users_id_fk": { + "name": "automations_owner_user_id_users_id_fk", + "tableFrom": "automations", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "automations_v2_project_id_v2_projects_id_fk": { + "name": "automations_v2_project_id_v2_projects_id_fk", + "tableFrom": "automations", + "tableTo": "v2_projects", + "columnsFrom": [ + "v2_project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_sessions": { + "name": "chat_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "v2_workspace_id": { + "name": "v2_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_sessions_org_idx": { + "name": "chat_sessions_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_sessions_created_by_idx": { + "name": "chat_sessions_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_sessions_last_active_idx": { + "name": "chat_sessions_last_active_idx", + "columns": [ + { + "expression": "last_active_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_sessions_organization_id_organizations_id_fk": { + "name": "chat_sessions_organization_id_organizations_id_fk", + "tableFrom": "chat_sessions", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_sessions_created_by_users_id_fk": { + "name": "chat_sessions_created_by_users_id_fk", + "tableFrom": "chat_sessions", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_sessions_workspace_id_workspaces_id_fk": { + "name": "chat_sessions_workspace_id_workspaces_id_fk", + "tableFrom": "chat_sessions", + "tableTo": "workspaces", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "chat_sessions_v2_workspace_id_v2_workspaces_id_fk": { + "name": "chat_sessions_v2_workspace_id_v2_workspaces_id_fk", + "tableFrom": "chat_sessions", + "tableTo": "v2_workspaces", + "columnsFrom": [ + "v2_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.device_presence": { + "name": "device_presence", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_name": { + "name": "device_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "device_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "device_presence_user_org_idx": { + "name": "device_presence_user_org_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "device_presence_user_device_idx": { + "name": "device_presence_user_device_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "device_presence_last_seen_idx": { + "name": "device_presence_last_seen_idx", + "columns": [ + { + "expression": "last_seen_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "device_presence_user_id_users_id_fk": { + "name": "device_presence_user_id_users_id_fk", + "tableFrom": "device_presence", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "device_presence_organization_id_organizations_id_fk": { + "name": "device_presence_organization_id_organizations_id_fk", + "tableFrom": "device_presence", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integration_connections": { + "name": "integration_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connected_by_user_id": { + "name": "connected_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "integration_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_expires_at": { + "name": "token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "external_org_id": { + "name": "external_org_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_org_name": { + "name": "external_org_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "integration_connections_org_idx": { + "name": "integration_connections_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "integration_connections_organization_id_organizations_id_fk": { + "name": "integration_connections_organization_id_organizations_id_fk", + "tableFrom": "integration_connections", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integration_connections_connected_by_user_id_users_id_fk": { + "name": "integration_connections_connected_by_user_id_users_id_fk", + "tableFrom": "integration_connections", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "connected_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "integration_connections_unique": { + "name": "integration_connections_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "provider" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_repository_id": { + "name": "github_repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repo_owner": { + "name": "repo_owner", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_name": { + "name": "repo_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_url": { + "name": "repo_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'main'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "projects_organization_id_idx": { + "name": "projects_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "projects_organization_id_organizations_id_fk": { + "name": "projects_organization_id_organizations_id_fk", + "tableFrom": "projects", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "projects_github_repository_id_github_repositories_id_fk": { + "name": "projects_github_repository_id_github_repositories_id_fk", + "tableFrom": "projects", + "tableTo": "github_repositories", + "columnsFrom": [ + "github_repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "projects_org_slug_unique": { + "name": "projects_org_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sandbox_images": { + "name": "sandbox_images", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "setup_commands": { + "name": "setup_commands", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "base_image": { + "name": "base_image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_packages": { + "name": "system_packages", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sandbox_images_organization_id_idx": { + "name": "sandbox_images_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sandbox_images_organization_id_organizations_id_fk": { + "name": "sandbox_images_organization_id_organizations_id_fk", + "tableFrom": "sandbox_images", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sandbox_images_project_id_projects_id_fk": { + "name": "sandbox_images_project_id_projects_id_fk", + "tableFrom": "sandbox_images", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sandbox_images_project_unique": { + "name": "sandbox_images_project_unique", + "nullsNotDistinct": false, + "columns": [ + "project_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secrets": { + "name": "secrets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_value": { + "name": "encrypted_value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sensitive": { + "name": "sensitive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secrets_project_id_idx": { + "name": "secrets_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secrets_organization_id_idx": { + "name": "secrets_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secrets_organization_id_organizations_id_fk": { + "name": "secrets_organization_id_organizations_id_fk", + "tableFrom": "secrets", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "secrets_project_id_projects_id_fk": { + "name": "secrets_project_id_projects_id_fk", + "tableFrom": "secrets", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "secrets_created_by_user_id_users_id_fk": { + "name": "secrets_created_by_user_id_users_id_fk", + "tableFrom": "secrets", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "secrets_project_key_unique": { + "name": "secrets_project_key_unique", + "nullsNotDistinct": false, + "columns": [ + "project_id", + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscriptions": { + "name": "subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'incomplete'" + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cancel_at": { + "name": "cancel_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "subscriptions_reference_id_idx": { + "name": "subscriptions_reference_id_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "subscriptions_stripe_customer_id_idx": { + "name": "subscriptions_stripe_customer_id_idx", + "columns": [ + { + "expression": "stripe_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "subscriptions_status_idx": { + "name": "subscriptions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "subscriptions_reference_id_organizations_id_fk": { + "name": "subscriptions_reference_id_organizations_id_fk", + "tableFrom": "subscriptions", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "reference_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.task_statuses": { + "name": "task_statuses", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "progress_percent": { + "name": "progress_percent", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "external_provider": { + "name": "external_provider", + "type": "integration_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "task_statuses_organization_id_idx": { + "name": "task_statuses_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "task_statuses_type_idx": { + "name": "task_statuses_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "task_statuses_organization_id_organizations_id_fk": { + "name": "task_statuses_organization_id_organizations_id_fk", + "tableFrom": "task_statuses", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "task_statuses_org_external_unique": { + "name": "task_statuses_org_external_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "external_provider", + "external_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tasks": { + "name": "tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_id": { + "name": "status_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "task_priority", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "assignee_id": { + "name": "assignee_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "estimate": { + "name": "estimate", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "due_date": { + "name": "due_date", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "labels": { + "name": "labels", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_provider": { + "name": "external_provider", + "type": "integration_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_key": { + "name": "external_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_url": { + "name": "external_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "sync_error": { + "name": "sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_external_id": { + "name": "assignee_external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_display_name": { + "name": "assignee_display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assignee_avatar_url": { + "name": "assignee_avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tasks_slug_idx": { + "name": "tasks_slug_idx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_organization_id_idx": { + "name": "tasks_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_assignee_id_idx": { + "name": "tasks_assignee_id_idx", + "columns": [ + { + "expression": "assignee_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_creator_id_idx": { + "name": "tasks_creator_id_idx", + "columns": [ + { + "expression": "creator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_status_id_idx": { + "name": "tasks_status_id_idx", + "columns": [ + { + "expression": "status_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_created_at_idx": { + "name": "tasks_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_external_provider_idx": { + "name": "tasks_external_provider_idx", + "columns": [ + { + "expression": "external_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasks_assignee_external_id_idx": { + "name": "tasks_assignee_external_id_idx", + "columns": [ + { + "expression": "assignee_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tasks_status_id_task_statuses_id_fk": { + "name": "tasks_status_id_task_statuses_id_fk", + "tableFrom": "tasks", + "tableTo": "task_statuses", + "columnsFrom": [ + "status_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tasks_organization_id_organizations_id_fk": { + "name": "tasks_organization_id_organizations_id_fk", + "tableFrom": "tasks", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tasks_assignee_id_users_id_fk": { + "name": "tasks_assignee_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "assignee_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_creator_id_users_id_fk": { + "name": "tasks_creator_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tasks_external_unique": { + "name": "tasks_external_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "external_provider", + "external_id" + ] + }, + "tasks_org_slug_unique": { + "name": "tasks_org_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users__slack_users": { + "name": "users__slack_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "slack_user_id": { + "name": "slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "model_preference": { + "name": "model_preference", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users__slack_users_user_idx": { + "name": "users__slack_users_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users__slack_users_org_idx": { + "name": "users__slack_users_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "users__slack_users_user_id_users_id_fk": { + "name": "users__slack_users_user_id_users_id_fk", + "tableFrom": "users__slack_users", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "users__slack_users_organization_id_organizations_id_fk": { + "name": "users__slack_users_organization_id_organizations_id_fk", + "tableFrom": "users__slack_users", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users__slack_users_unique": { + "name": "users__slack_users_unique", + "nullsNotDistinct": false, + "columns": [ + "slack_user_id", + "team_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_clients": { + "name": "v2_clients", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "v2_client_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_clients_organization_id_idx": { + "name": "v2_clients_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_clients_user_id_idx": { + "name": "v2_clients_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_clients_organization_id_organizations_id_fk": { + "name": "v2_clients_organization_id_organizations_id_fk", + "tableFrom": "v2_clients", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_clients_user_id_users_id_fk": { + "name": "v2_clients_user_id_users_id_fk", + "tableFrom": "v2_clients", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "v2_clients_organization_id_user_id_machine_id_pk": { + "name": "v2_clients_organization_id_user_id_machine_id_pk", + "columns": [ + "organization_id", + "user_id", + "machine_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_hosts": { + "name": "v2_hosts", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_online": { + "name": "is_online", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_hosts_organization_id_idx": { + "name": "v2_hosts_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_hosts_organization_id_organizations_id_fk": { + "name": "v2_hosts_organization_id_organizations_id_fk", + "tableFrom": "v2_hosts", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_hosts_created_by_user_id_users_id_fk": { + "name": "v2_hosts_created_by_user_id_users_id_fk", + "tableFrom": "v2_hosts", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "v2_hosts_organization_id_machine_id_pk": { + "name": "v2_hosts_organization_id_machine_id_pk", + "columns": [ + "organization_id", + "machine_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_projects": { + "name": "v2_projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_clone_url": { + "name": "repo_clone_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_repository_id": { + "name": "github_repository_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_projects_organization_id_idx": { + "name": "v2_projects_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_projects_organization_id_organizations_id_fk": { + "name": "v2_projects_organization_id_organizations_id_fk", + "tableFrom": "v2_projects", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_projects_github_repository_id_github_repositories_id_fk": { + "name": "v2_projects_github_repository_id_github_repositories_id_fk", + "tableFrom": "v2_projects", + "tableTo": "github_repositories", + "columnsFrom": [ + "github_repository_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "v2_projects_org_slug_unique": { + "name": "v2_projects_org_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_users_hosts": { + "name": "v2_users_hosts", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "v2_users_host_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_users_hosts_organization_id_idx": { + "name": "v2_users_hosts_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_users_hosts_user_id_idx": { + "name": "v2_users_hosts_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_users_hosts_host_id_idx": { + "name": "v2_users_hosts_host_id_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_users_hosts_organization_id_organizations_id_fk": { + "name": "v2_users_hosts_organization_id_organizations_id_fk", + "tableFrom": "v2_users_hosts", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_users_hosts_user_id_users_id_fk": { + "name": "v2_users_hosts_user_id_users_id_fk", + "tableFrom": "v2_users_hosts", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_users_hosts_host_fk": { + "name": "v2_users_hosts_host_fk", + "tableFrom": "v2_users_hosts", + "tableTo": "v2_hosts", + "columnsFrom": [ + "organization_id", + "host_id" + ], + "columnsTo": [ + "organization_id", + "machine_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "v2_users_hosts_organization_id_user_id_host_id_pk": { + "name": "v2_users_hosts_organization_id_user_id_host_id_pk", + "columns": [ + "organization_id", + "user_id", + "host_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.v2_workspaces": { + "name": "v2_workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "host_id": { + "name": "host_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "v2_workspace_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'worktree'" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "v2_workspaces_project_id_idx": { + "name": "v2_workspaces_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_workspaces_organization_id_idx": { + "name": "v2_workspaces_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_workspaces_host_id_idx": { + "name": "v2_workspaces_host_id_idx", + "columns": [ + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "v2_workspaces_one_main_per_host": { + "name": "v2_workspaces_one_main_per_host", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"v2_workspaces\".\"type\" = 'main'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "v2_workspaces_organization_id_organizations_id_fk": { + "name": "v2_workspaces_organization_id_organizations_id_fk", + "tableFrom": "v2_workspaces", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_workspaces_project_id_v2_projects_id_fk": { + "name": "v2_workspaces_project_id_v2_projects_id_fk", + "tableFrom": "v2_workspaces", + "tableTo": "v2_projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "v2_workspaces_created_by_user_id_users_id_fk": { + "name": "v2_workspaces_created_by_user_id_users_id_fk", + "tableFrom": "v2_workspaces", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "v2_workspaces_host_fk": { + "name": "v2_workspaces_host_fk", + "tableFrom": "v2_workspaces", + "tableTo": "v2_hosts", + "columnsFrom": [ + "organization_id", + "host_id" + ], + "columnsTo": [ + "organization_id", + "machine_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspaces": { + "name": "workspaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "workspace_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspaces_project_id_idx": { + "name": "workspaces_project_id_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspaces_organization_id_idx": { + "name": "workspaces_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspaces_type_idx": { + "name": "workspaces_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspaces_organization_id_organizations_id_fk": { + "name": "workspaces_organization_id_organizations_id_fk", + "tableFrom": "workspaces", + "tableTo": "organizations", + "schemaTo": "auth", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_project_id_projects_id_fk": { + "name": "workspaces_project_id_projects_id_fk", + "tableFrom": "workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_created_by_user_id_users_id_fk": { + "name": "workspaces_created_by_user_id_users_id_fk", + "tableFrom": "workspaces", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.automation_run_status": { + "name": "automation_run_status", + "schema": "public", + "values": [ + "dispatching", + "dispatched", + "skipped_offline", + "dispatch_failed" + ] + }, + "public.automation_session_kind": { + "name": "automation_session_kind", + "schema": "public", + "values": [ + "chat", + "terminal" + ] + }, + "public.command_status": { + "name": "command_status", + "schema": "public", + "values": [ + "pending", + "completed", + "failed", + "timeout" + ] + }, + "public.device_type": { + "name": "device_type", + "schema": "public", + "values": [ + "desktop", + "mobile", + "web" + ] + }, + "public.integration_provider": { + "name": "integration_provider", + "schema": "public", + "values": [ + "linear", + "github", + "slack" + ] + }, + "public.task_priority": { + "name": "task_priority", + "schema": "public", + "values": [ + "urgent", + "high", + "medium", + "low", + "none" + ] + }, + "public.task_status": { + "name": "task_status", + "schema": "public", + "values": [ + "backlog", + "todo", + "planning", + "working", + "needs-feedback", + "ready-to-merge", + "completed", + "canceled" + ] + }, + "public.v2_client_type": { + "name": "v2_client_type", + "schema": "public", + "values": [ + "desktop", + "mobile", + "web" + ] + }, + "public.v2_users_host_role": { + "name": "v2_users_host_role", + "schema": "public", + "values": [ + "owner", + "member" + ] + }, + "public.v2_workspace_type": { + "name": "v2_workspace_type", + "schema": "public", + "values": [ + "main", + "worktree" + ] + }, + "public.workspace_type": { + "name": "workspace_type", + "schema": "public", + "values": [ + "local", + "cloud" + ] + } + }, + "schemas": { + "auth": "auth", + "ingest": "ingest" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index 8d1f943bddc..de6f9c6ca44 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -253,6 +253,34 @@ "when": 1776661088164, "tag": "0035_add_automations", "breakpoints": true + }, + { + "idx": 36, + "version": "7", + "when": 1776661543389, + "tag": "0036_drop_pending_automation_status", + "breakpoints": true + }, + { + "idx": 37, + "version": "7", + "when": 1777226062658, + "tag": "0037_drop_v2_project_repo_clone_url_unique", + "breakpoints": true + }, + { + "idx": 38, + "version": "7", + "when": 1777259447556, + "tag": "0038_v2_workspaces_main_type", + "breakpoints": true + }, + { + "idx": 39, + "version": "7", + "when": 1777266747895, + "tag": "0039_consolidate_host_client_machine_id", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/schema/enums.ts b/packages/db/src/schema/enums.ts index 0eab4d3fd96..c57d0d250b9 100644 --- a/packages/db/src/schema/enums.ts +++ b/packages/db/src/schema/enums.ts @@ -68,6 +68,10 @@ export const workspaceTypeValues = ["local", "cloud"] as const; export const workspaceTypeEnum = z.enum(workspaceTypeValues); export type WorkspaceType = z.infer; +export const v2WorkspaceTypeValues = ["main", "worktree"] as const; +export const v2WorkspaceTypeEnum = z.enum(v2WorkspaceTypeValues); +export type V2WorkspaceType = z.infer; + export const automationRunStatusValues = [ "pending", "dispatching", diff --git a/packages/db/src/schema/relations.ts b/packages/db/src/schema/relations.ts index fa9400cf189..49992b6e750 100644 --- a/packages/db/src/schema/relations.ts +++ b/packages/db/src/schema/relations.ts @@ -21,7 +21,6 @@ import { projects, sandboxImages, secrets, - sessionHosts, subscriptions, taskStatuses, tasks, @@ -314,8 +313,8 @@ export const v2UsersHostsRelations = relations(v2UsersHosts, ({ one }) => ({ references: [users.id], }), host: one(v2Hosts, { - fields: [v2UsersHosts.hostId], - references: [v2Hosts.id], + fields: [v2UsersHosts.organizationId, v2UsersHosts.hostId], + references: [v2Hosts.organizationId, v2Hosts.machineId], }), })); @@ -331,8 +330,8 @@ export const v2WorkspacesRelations = relations( references: [v2Projects.id], }), host: one(v2Hosts, { - fields: [v2Workspaces.hostId], - references: [v2Hosts.id], + fields: [v2Workspaces.organizationId, v2Workspaces.hostId], + references: [v2Hosts.organizationId, v2Hosts.machineId], }), createdBy: one(users, { fields: [v2Workspaces.createdByUserId], @@ -384,36 +383,21 @@ export const workspacesRelations = relations(workspaces, ({ one, many }) => ({ chatSessions: many(chatSessions), })); -export const chatSessionsRelations = relations( - chatSessions, - ({ one, many }) => ({ - organization: one(organizations, { - fields: [chatSessions.organizationId], - references: [organizations.id], - }), - createdBy: one(users, { - fields: [chatSessions.createdBy], - references: [users.id], - }), - workspace: one(workspaces, { - fields: [chatSessions.workspaceId], - references: [workspaces.id], - }), - v2Workspace: one(v2Workspaces, { - fields: [chatSessions.v2WorkspaceId], - references: [v2Workspaces.id], - }), - sessionHosts: many(sessionHosts), - }), -); - -export const sessionHostsRelations = relations(sessionHosts, ({ one }) => ({ - chatSession: one(chatSessions, { - fields: [sessionHosts.sessionId], - references: [chatSessions.id], - }), +export const chatSessionsRelations = relations(chatSessions, ({ one }) => ({ organization: one(organizations, { - fields: [sessionHosts.organizationId], + fields: [chatSessions.organizationId], references: [organizations.id], }), + createdBy: one(users, { + fields: [chatSessions.createdBy], + references: [users.id], + }), + workspace: one(workspaces, { + fields: [chatSessions.workspaceId], + references: [workspaces.id], + }), + v2Workspace: one(v2Workspaces, { + fields: [chatSessions.v2WorkspaceId], + references: [v2Workspaces.id], + }), })); diff --git a/packages/db/src/schema/schema.ts b/packages/db/src/schema/schema.ts index 64a3d605671..dfe9beb13a1 100644 --- a/packages/db/src/schema/schema.ts +++ b/packages/db/src/schema/schema.ts @@ -2,11 +2,13 @@ import type { ResolvedAgentConfig } from "@superset/shared/agent-settings"; import { sql } from "drizzle-orm"; import { boolean, + foreignKey, index, integer, jsonb, pgEnum, pgTable, + primaryKey, real, text, timestamp, @@ -25,6 +27,7 @@ import { taskStatusEnumValues, v2ClientTypeValues, v2UsersHostRoleValues, + v2WorkspaceTypeValues, workspaceTypeValues, } from "./enums"; import { githubRepositories } from "./github"; @@ -44,6 +47,10 @@ export const v2UsersHostRole = pgEnum( "v2_users_host_role", v2UsersHostRoleValues, ); +export const v2WorkspaceType = pgEnum( + "v2_workspace_type", + v2WorkspaceTypeValues, +); export const taskStatuses = pgTable( "task_statuses", @@ -243,7 +250,9 @@ export const subscriptions = pgTable( export type InsertSubscription = typeof subscriptions.$inferInsert; export type SelectSubscription = typeof subscriptions.$inferSelect; -// Device presence - tracks online devices for command routing +// Device presence — v1 concept. Tracks per-(user, machine) presence for +// MCP ownership verification. Untouched by the v2 host consolidation; will +// be retired when v1 is removed. export const devicePresence = pgTable( "device_presence", { @@ -405,12 +414,6 @@ export const v2Projects = pgTable( (table) => [ index("v2_projects_organization_id_idx").on(table.organizationId), unique("v2_projects_org_slug_unique").on(table.organizationId, table.slug), - // One project per repo URL per org. NULLs don't collide (PG default) - // so empty-mode projects without a remote can still be created. - uniqueIndex("v2_projects_org_repo_clone_url_unique").on( - table.organizationId, - sql`lower(${table.repoCloneUrl})`, - ), ], ); @@ -420,7 +423,6 @@ export type SelectV2Project = typeof v2Projects.$inferSelect; export const v2Hosts = pgTable( "v2_hosts", { - id: uuid().primaryKey().defaultRandom(), organizationId: uuid("organization_id") .notNull() .references(() => organizations.id, { onDelete: "cascade" }), @@ -439,11 +441,8 @@ export const v2Hosts = pgTable( .$onUpdate(() => new Date()), }, (table) => [ + primaryKey({ columns: [table.organizationId, table.machineId] }), index("v2_hosts_organization_id_idx").on(table.organizationId), - unique("v2_hosts_org_machine_id_unique").on( - table.organizationId, - table.machineId, - ), ], ); @@ -453,7 +452,6 @@ export type SelectV2Host = typeof v2Hosts.$inferSelect; export const v2Clients = pgTable( "v2_clients", { - id: uuid().primaryKey().defaultRandom(), organizationId: uuid("organization_id") .notNull() .references(() => organizations.id, { onDelete: "cascade" }), @@ -471,13 +469,11 @@ export const v2Clients = pgTable( .$onUpdate(() => new Date()), }, (table) => [ + primaryKey({ + columns: [table.organizationId, table.userId, table.machineId], + }), index("v2_clients_organization_id_idx").on(table.organizationId), index("v2_clients_user_id_idx").on(table.userId), - unique("v2_clients_org_user_machine_unique").on( - table.organizationId, - table.userId, - table.machineId, - ), ], ); @@ -487,16 +483,13 @@ export type SelectV2Client = typeof v2Clients.$inferSelect; export const v2UsersHosts = pgTable( "v2_users_hosts", { - id: uuid().primaryKey().defaultRandom(), organizationId: uuid("organization_id") .notNull() .references(() => organizations.id, { onDelete: "cascade" }), userId: uuid("user_id") .notNull() .references(() => users.id, { onDelete: "cascade" }), - hostId: uuid("host_id") - .notNull() - .references(() => v2Hosts.id, { onDelete: "cascade" }), + hostId: text("host_id").notNull(), role: v2UsersHostRole().notNull().default("member"), createdAt: timestamp("created_at", { withTimezone: true }) .notNull() @@ -507,14 +500,17 @@ export const v2UsersHosts = pgTable( .$onUpdate(() => new Date()), }, (table) => [ + primaryKey({ + columns: [table.organizationId, table.userId, table.hostId], + }), + foreignKey({ + columns: [table.organizationId, table.hostId], + foreignColumns: [v2Hosts.organizationId, v2Hosts.machineId], + name: "v2_users_hosts_host_fk", + }).onDelete("cascade"), index("v2_users_hosts_organization_id_idx").on(table.organizationId), index("v2_users_hosts_user_id_idx").on(table.userId), index("v2_users_hosts_host_id_idx").on(table.hostId), - unique("v2_users_hosts_org_user_host_unique").on( - table.organizationId, - table.userId, - table.hostId, - ), ], ); @@ -531,11 +527,10 @@ export const v2Workspaces = pgTable( projectId: uuid("project_id") .notNull() .references(() => v2Projects.id, { onDelete: "cascade" }), - hostId: uuid("host_id") - .notNull() - .references(() => v2Hosts.id), + hostId: text("host_id").notNull(), name: text().notNull(), branch: text().notNull(), + type: v2WorkspaceType().notNull().default("worktree"), createdByUserId: uuid("created_by_user_id").references(() => users.id, { onDelete: "set null", }), @@ -548,9 +543,17 @@ export const v2Workspaces = pgTable( .$onUpdate(() => new Date()), }, (table) => [ + foreignKey({ + columns: [table.organizationId, table.hostId], + foreignColumns: [v2Hosts.organizationId, v2Hosts.machineId], + name: "v2_workspaces_host_fk", + }), index("v2_workspaces_project_id_idx").on(table.projectId), index("v2_workspaces_organization_id_idx").on(table.organizationId), index("v2_workspaces_host_id_idx").on(table.hostId), + uniqueIndex("v2_workspaces_one_main_per_host") + .on(table.projectId, table.hostId) + .where(sql`${table.type} = 'main'`), ], ); @@ -683,29 +686,6 @@ export const chatSessions = pgTable( export type InsertChatSession = typeof chatSessions.$inferInsert; export type SelectChatSession = typeof chatSessions.$inferSelect; -export const sessionHosts = pgTable( - "session_hosts", - { - id: uuid().primaryKey().defaultRandom(), - sessionId: uuid("session_id") - .notNull() - .references(() => chatSessions.id, { onDelete: "cascade" }), - organizationId: uuid("organization_id") - .notNull() - .references(() => organizations.id, { onDelete: "cascade" }), - deviceId: text("device_id").notNull(), - createdAt: timestamp("created_at").notNull().defaultNow(), - }, - (table) => [ - index("session_hosts_session_id_idx").on(table.sessionId), - index("session_hosts_org_idx").on(table.organizationId), - index("session_hosts_device_id_idx").on(table.deviceId), - ], -); - -export type InsertSessionHost = typeof sessionHosts.$inferInsert; -export type SelectSessionHost = typeof sessionHosts.$inferSelect; - export const automationRunStatus = pgEnum( "automation_run_status", automationRunStatusValues, @@ -745,10 +725,11 @@ export const automations = pgTable( */ agentConfig: jsonb("agent_config").$type().notNull(), - /** Target host (v2_hosts.id). Null = owner's most-recently-online host at dispatch. */ - targetHostId: uuid("target_host_id").references(() => v2Hosts.id, { - onDelete: "set null", - }), + /** + * Target host (v2_hosts.machineId after #3784 consolidation). + * Null = owner's most-recently-online host at dispatch. + */ + targetHostId: text("target_host_id"), v2ProjectId: uuid("v2_project_id") .notNull() @@ -808,9 +789,7 @@ export const automationRuns = pgTable( /** Minute-bucketed scheduled fire time. */ scheduledFor: timestamp("scheduled_for", { withTimezone: true }).notNull(), - hostId: uuid("host_id").references(() => v2Hosts.id, { - onDelete: "set null", - }), + hostId: text("host_id"), v2WorkspaceId: uuid("v2_workspace_id"), /** null until the run reaches "dispatched". */ diff --git a/packages/host-service/src/app.ts b/packages/host-service/src/app.ts index da0d1840770..6545be5a280 100644 --- a/packages/host-service/src/app.ts +++ b/packages/host-service/src/app.ts @@ -14,6 +14,7 @@ import { ChatRuntimeManager } from "./runtime/chat"; import { WorkspaceFilesystemManager } from "./runtime/filesystem"; import type { GitCredentialProvider } from "./runtime/git"; import { createGitFactory } from "./runtime/git"; +import { runMainWorkspaceSweep } from "./runtime/main-workspace-sweep"; import { PullRequestRuntimeManager } from "./runtime/pull-requests"; import { registerWorkspaceTerminalRoute } from "./terminal/terminal"; import { appRouter } from "./trpc/router"; @@ -88,6 +89,18 @@ export function createApp(options: CreateAppOptions): CreateAppResult { const eventBus = new EventBus({ db, filesystem }); eventBus.start(); + // Backfill `kind='main'` v2 workspaces for projects already set up before + // this column shipped. Idempotent; runs in the background so it doesn't + // block server startup. + void runMainWorkspaceSweep({ + api, + db, + git, + organizationId: config.organizationId, + }).catch((err) => { + console.warn("[host-service] main-workspace sweep failed:", err); + }); + const wsAuth: MiddlewareHandler = async (c, next) => { const token = c.req.query("token"); const authorized = diff --git a/packages/host-service/src/runtime/main-workspace-sweep.ts b/packages/host-service/src/runtime/main-workspace-sweep.ts new file mode 100644 index 00000000000..cfc10022739 --- /dev/null +++ b/packages/host-service/src/runtime/main-workspace-sweep.ts @@ -0,0 +1,33 @@ +import { existsSync } from "node:fs"; +import { projects } from "../db/schema"; +import { + type EnsureMainWorkspaceContext, + ensureMainWorkspace, +} from "../trpc/router/project/utils/ensure-main-workspace"; + +/** + * Recovery path for projects set up before `type='main'` shipped. + * + * Iterates local `projects` and ensures each has a main v2 workspace bound to + * the current host. Idempotent via the `(projectId, hostId) WHERE type='main'` + * unique index, so it's safe on every boot — only does real work the first + * time after upgrade. + */ +export async function runMainWorkspaceSweep( + ctx: EnsureMainWorkspaceContext, +): Promise { + const rows = ctx.db + .select({ id: projects.id, repoPath: projects.repoPath }) + .from(projects) + .all(); + + for (const row of rows) { + if (!existsSync(row.repoPath)) { + console.warn( + `[main-workspace-sweep] skipping ${row.id}: repoPath ${row.repoPath} does not exist`, + ); + continue; + } + await ensureMainWorkspace(ctx, row.id, row.repoPath); + } +} diff --git a/packages/host-service/src/terminal/env-strip.ts b/packages/host-service/src/terminal/env-strip.ts index e5eab939cd4..5bf31f010b6 100644 --- a/packages/host-service/src/terminal/env-strip.ts +++ b/packages/host-service/src/terminal/env-strip.ts @@ -10,15 +10,15 @@ /** * Exact keys injected by desktop into host-service. * - * DESKTOP_* and DEVICE_* are exact keys (not prefixes) because - * DESKTOP_SESSION, DESKTOP_STARTUP_ID etc. are legitimate Linux vars. + * DESKTOP_* are exact keys (not prefixes) because DESKTOP_SESSION, + * DESKTOP_STARTUP_ID etc. are legitimate Linux vars. */ const HOST_SERVICE_RUNTIME_KEYS = new Set([ "AUTH_TOKEN", "CLOUD_API_URL", "DESKTOP_VITE_PORT", - "DEVICE_CLIENT_ID", - "DEVICE_NAME", + "HOST_CLIENT_ID", + "HOST_NAME", "KEEP_ALIVE_AFTER_PARENT", "ORGANIZATION_ID", ]); diff --git a/packages/host-service/src/terminal/env.test.ts b/packages/host-service/src/terminal/env.test.ts index 9a45c5e0388..3d646bd19c1 100644 --- a/packages/host-service/src/terminal/env.test.ts +++ b/packages/host-service/src/terminal/env.test.ts @@ -66,8 +66,8 @@ describe("stripTerminalRuntimeEnv", () => { AUTH_TOKEN: "secret-token", HOST_SERVICE_SECRET: "secret", ORGANIZATION_ID: "org-123", - DEVICE_CLIENT_ID: "device-abc", - DEVICE_NAME: "My Mac", + HOST_CLIENT_ID: "device-abc", + HOST_NAME: "My Mac", ELECTRON_RUN_AS_NODE: "1", HOST_DB_PATH: "/tmp/host.db", HOST_MANIFEST_DIR: "/tmp/manifests", @@ -110,7 +110,7 @@ describe("stripTerminalRuntimeEnv", () => { expect(result.AUTH_TOKEN).toBeUndefined(); expect(result.HOST_SERVICE_SECRET).toBeUndefined(); expect(result.ORGANIZATION_ID).toBeUndefined(); - expect(result.DEVICE_CLIENT_ID).toBeUndefined(); + expect(result.HOST_CLIENT_ID).toBeUndefined(); expect(result.ELECTRON_RUN_AS_NODE).toBeUndefined(); expect(result.HOST_DB_PATH).toBeUndefined(); expect(result.CLOUD_API_URL).toBeUndefined(); @@ -123,7 +123,7 @@ describe("stripTerminalRuntimeEnv", () => { expect(result.HOST_MIGRATIONS_PATH).toBeUndefined(); expect(result.HOST_SERVICE_VERSION).toBeUndefined(); expect(result.KEEP_ALIVE_AFTER_PARENT).toBeUndefined(); - expect(result.DEVICE_NAME).toBeUndefined(); + expect(result.HOST_NAME).toBeUndefined(); }); test("Node/app keys are stripped", () => { @@ -141,16 +141,16 @@ describe("stripTerminalRuntimeEnv", () => { expect(result.ELECTRON_ENABLE_LOGGING).toBeUndefined(); }); - test("HOST_* prefix is stripped, DESKTOP_*/DEVICE_* are exact-key only", () => { + test("HOST_* prefix is stripped, DESKTOP_* exact keys only", () => { const env: Record = { - // HOST_* prefix: all stripped + // HOST_* prefix: all stripped (including HOST_CLIENT_ID, HOST_NAME) HOST_DB_PATH: "/tmp/db", HOST_MANIFEST_DIR: "/tmp/manifests", HOST_SERVICE_SECRET: "secret", - // DESKTOP_* / DEVICE_*: only our exact keys stripped + HOST_CLIENT_ID: "abc", + HOST_NAME: "Mac", + // DESKTOP_*: only our exact key stripped DESKTOP_VITE_PORT: "5173", - DEVICE_CLIENT_ID: "abc", - DEVICE_NAME: "Mac", // Legitimate Linux desktop vars: must survive DESKTOP_SESSION: "gnome", DESKTOP_STARTUP_ID: "startup-123", @@ -161,8 +161,8 @@ describe("stripTerminalRuntimeEnv", () => { expect(result.HOST_MANIFEST_DIR).toBeUndefined(); expect(result.HOST_SERVICE_SECRET).toBeUndefined(); expect(result.DESKTOP_VITE_PORT).toBeUndefined(); - expect(result.DEVICE_CLIENT_ID).toBeUndefined(); - expect(result.DEVICE_NAME).toBeUndefined(); + expect(result.HOST_CLIENT_ID).toBeUndefined(); + expect(result.HOST_NAME).toBeUndefined(); // Linux desktop vars preserved expect(result.DESKTOP_SESSION).toBe("gnome"); expect(result.DESKTOP_STARTUP_ID).toBe("startup-123"); diff --git a/packages/host-service/src/trpc/router/host/host.ts b/packages/host-service/src/trpc/router/host/host.ts index 922854f0c92..c8a62e4f464 100644 --- a/packages/host-service/src/trpc/router/host/host.ts +++ b/packages/host-service/src/trpc/router/host/host.ts @@ -1,10 +1,16 @@ import os from "node:os"; -import { getDeviceName, getHashedDeviceId } from "@superset/shared/device-info"; +import { getHostId, getHostName } from "@superset/shared/host-info"; import { TRPCError } from "@trpc/server"; import type { ApiClient } from "../../../types"; import { protectedProcedure, router } from "../../index"; -const HOST_SERVICE_VERSION = "0.1.0"; +// 0.3.0: cloud `device.*` router renamed to `host.*`; `device.ensureV2Host` +// is now `host.ensure`, host registrations are keyed on (orgId, machineId) +// composite, and `targetHostId`/`v2_workspaces.host_id` are machineId text +// not uuid. Older host-service binaries call the now-removed `device.*` +// procedures and fail at registration. +// 0.2.0: `workspaceCreation.adopt` accepts optional `worktreePath`. +const HOST_SERVICE_VERSION = "0.3.0"; const ORGANIZATION_CACHE_TTL_MS = 60 * 60 * 1000; let cachedOrganization: { @@ -43,8 +49,8 @@ export const hostRouter = router({ const organization = await getOrganization(ctx.api, ctx.organizationId); return { - hostId: getHashedDeviceId(), - hostName: getDeviceName(), + hostId: getHostId(), + hostName: getHostName(), version: HOST_SERVICE_VERSION, organization, platform: os.platform(), diff --git a/packages/host-service/src/trpc/router/project/handlers.ts b/packages/host-service/src/trpc/router/project/handlers.ts index 5e967a622ba..d2e02aeb0c1 100644 --- a/packages/host-service/src/trpc/router/project/handlers.ts +++ b/packages/host-service/src/trpc/router/project/handlers.ts @@ -1,8 +1,13 @@ import { rmSync } from "node:fs"; import { TRPCError } from "@trpc/server"; import type { HostServiceContext } from "../../../types"; +import { ensureMainWorkspace } from "./utils/ensure-main-workspace"; import { persistLocalProject } from "./utils/persist-project"; -import { cloneRepoInto, resolveWithPrimaryRemote } from "./utils/resolve-repo"; +import { + cloneRepoInto, + type ResolvedRepo, + resolveLocalRepo, +} from "./utils/resolve-repo"; function slugifyProjectName(name: string): string { const slug = name @@ -22,28 +27,107 @@ function slugifyProjectName(name: string): string { interface CreateResult { projectId: string; repoPath: string; + mainWorkspaceId: string | null; +} + +function slugWithSuffix(baseSlug: string, attempt: number): string { + return attempt === 0 ? baseSlug : `${baseSlug}-${attempt + 1}`; +} + +function isSlugConflict(err: unknown): boolean { + const message = err instanceof Error ? err.message : String(err); + const lower = message.toLowerCase(); + return lower.includes("v2_projects_org_slug_unique"); +} + +async function createCloudProjectWithSlugRetry( + ctx: HostServiceContext, + args: { name: string; repoCloneUrl?: string }, +) { + const baseSlug = slugifyProjectName(args.name); + let lastError: unknown; + const maxAttempts = 10; + for (let attempt = 0; attempt < maxAttempts; attempt++) { + const slug = slugWithSuffix(baseSlug, attempt); + try { + return await ctx.api.v2Project.create.mutate({ + organizationId: ctx.organizationId, + name: args.name, + slug, + repoCloneUrl: args.repoCloneUrl, + }); + } catch (err) { + if (!isSlugConflict(err)) throw err; + lastError = err; + console.warn("[project.create] slug conflict, retrying", { + organizationId: ctx.organizationId, + name: args.name, + slug, + attempt, + }); + } + } + throw new TRPCError({ + code: "CONFLICT", + message: `Could not allocate a unique slug for "${args.name}" after ${maxAttempts} attempts`, + cause: lastError, + }); +} + +function persistLocalProjectOrWarn( + ctx: HostServiceContext, + projectId: string, + resolved: ResolvedRepo, + source: "createFromClone" | "createFromImportLocal", +): void { + try { + persistLocalProject(ctx, projectId, resolved); + } catch (err) { + console.warn( + `[project.${source}] cloud project created but local persistence failed; rerun will need to relink`, + { projectId, repoPath: resolved.repoPath, err }, + ); + throw err; + } } /** * Clone first so clone-time failures (bad URL, auth, network, dir - * collision) leave no cloud state behind; rollback the local clone on - * cloud failure. Mirrors workspace.create's local-first-then-cloud order. + * collision) leave no cloud state behind. The local clone can be removed + * if later steps fail, but cloud projects are durable once created. */ export async function createFromClone( ctx: HostServiceContext, args: { name: string; parentDir: string; url: string }, ): Promise { const resolved = await cloneRepoInto(args.url, args.parentDir); + let cloudProjectCreated = false; try { - const cloudProject = await ctx.api.v2Project.create.mutate({ - organizationId: ctx.organizationId, + const cloudProject = await createCloudProjectWithSlugRetry(ctx, { name: args.name, - slug: slugifyProjectName(args.name), repoCloneUrl: args.url, }); - persistLocalProject(ctx, cloudProject.id, resolved); - return { projectId: cloudProject.id, repoPath: resolved.repoPath }; + cloudProjectCreated = true; + persistLocalProjectOrWarn( + ctx, + cloudProject.id, + resolved, + "createFromClone", + ); + const mainWorkspace = await ensureMainWorkspace( + ctx, + cloudProject.id, + resolved.repoPath, + ); + return { + projectId: cloudProject.id, + repoPath: resolved.repoPath, + mainWorkspaceId: mainWorkspace?.id ?? null, + }; } catch (err) { + // Once a cloud project exists, keep the clone in place so rerun/recovery + // has a local repo path to relink instead of creating a second clone. + if (cloudProjectCreated) throw err; try { rmSync(resolved.repoPath, { recursive: true, force: true }); } catch (cleanupErr) { @@ -60,13 +144,25 @@ export async function createFromImportLocal( ctx: HostServiceContext, args: { name: string; repoPath: string }, ): Promise { - const resolved = await resolveWithPrimaryRemote(args.repoPath); - const cloudProject = await ctx.api.v2Project.create.mutate({ - organizationId: ctx.organizationId, + const resolved = await resolveLocalRepo(args.repoPath); + const cloudProject = await createCloudProjectWithSlugRetry(ctx, { name: args.name, - slug: slugifyProjectName(args.name), - repoCloneUrl: resolved.parsed.url, + repoCloneUrl: resolved.parsed?.url, }); - persistLocalProject(ctx, cloudProject.id, resolved); - return { projectId: cloudProject.id, repoPath: resolved.repoPath }; + persistLocalProjectOrWarn( + ctx, + cloudProject.id, + resolved, + "createFromImportLocal", + ); + const mainWorkspace = await ensureMainWorkspace( + ctx, + cloudProject.id, + resolved.repoPath, + ); + return { + projectId: cloudProject.id, + repoPath: resolved.repoPath, + mainWorkspaceId: mainWorkspace?.id ?? null, + }; } diff --git a/packages/host-service/src/trpc/router/project/project.ts b/packages/host-service/src/trpc/router/project/project.ts index 6a63f5d2cff..9052501ac9f 100644 --- a/packages/host-service/src/trpc/router/project/project.ts +++ b/packages/host-service/src/trpc/router/project/project.ts @@ -1,5 +1,5 @@ import { rmSync } from "node:fs"; -import { resolve as resolvePath } from "node:path"; +import { basename, resolve as resolvePath } from "node:path"; import { parseGitHubRemote } from "@superset/shared/github-remote"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; @@ -7,12 +7,13 @@ import { z } from "zod"; import { projects, workspaces } from "../../../db/schema"; import { protectedProcedure, router } from "../../index"; import { createFromClone, createFromImportLocal } from "./handlers"; +import { ensureMainWorkspace } from "./utils/ensure-main-workspace"; import { persistLocalProject } from "./utils/persist-project"; import { cloneRepoInto, type ResolvedRepo, + resolveLocalRepo, resolveMatchingSlug, - resolveWithPrimaryRemote, } from "./utils/resolve-repo"; export const projectRouter = router({ @@ -45,32 +46,33 @@ export const projectRouter = router({ repoPath: z.string().min(1), }), ) - .query(async ({ ctx, input }) => { - const cloudProject = await ctx.api.v2Project.get.query({ - organizationId: ctx.organizationId, - id: input.projectId, - }); - if (cloudProject.repoCloneUrl) return { conflict: null }; - - const { parsed } = await resolveWithPrimaryRemote(input.repoPath); - const { candidates } = await ctx.api.v2Project.findByGitHubRemote.query({ - organizationId: ctx.organizationId, - repoCloneUrl: parsed.url, - }); - const other = candidates.find((c) => c.id !== input.projectId); - if (!other) return { conflict: null }; - return { - conflict: { - id: other.id, - name: other.name, - }, - }; + .query(() => { + // Multiple v2 projects may point at the same GitHub URL, so a matching + // repo URL is no longer a conflict. Kept for backwards-compatible + // clients while older settings screens still call the endpoint. + return { conflict: null }; }), findByPath: protectedProcedure .input(z.object({ repoPath: z.string().min(1) })) .query(async ({ ctx, input }) => { - const { parsed } = await resolveWithPrimaryRemote(input.repoPath); + const resolved = await resolveLocalRepo(input.repoPath); + const localProject = ctx.db.query.projects + .findFirst({ where: eq(projects.repoPath, resolved.repoPath) }) + .sync(); + if (localProject) { + return { + candidates: [ + { + id: localProject.id, + name: localProject.repoName ?? basename(resolved.repoPath), + }, + ], + }; + } + + const { parsed } = resolved; + if (!parsed) return { candidates: [] }; const { candidates } = await ctx.api.v2Project.findByGitHubRemote.query({ organizationId: ctx.organizationId, repoCloneUrl: parsed.url, @@ -194,13 +196,31 @@ export const projectRouter = router({ expectedParsed.name, ); rejectIfRepoint(predictedPath); - if (existing) return { repoPath: existing.repoPath }; + if (existing) { + const mainWorkspace = await ensureMainWorkspace( + ctx, + input.projectId, + existing.repoPath, + ); + return { + repoPath: existing.repoPath, + mainWorkspaceId: mainWorkspace?.id ?? null, + }; + } const resolved = await cloneRepoInto( cloudProject.repoCloneUrl, input.mode.parentDir, ); persistLocalProject(ctx, input.projectId, resolved); - return { repoPath: resolved.repoPath }; + const mainWorkspace = await ensureMainWorkspace( + ctx, + input.projectId, + resolved.repoPath, + ); + return { + repoPath: resolved.repoPath, + mainWorkspaceId: mainWorkspace?.id ?? null, + }; } case "import": { let resolved: ResolvedRepo; @@ -217,15 +237,23 @@ export const projectRouter = router({ `${parsed.owner}/${parsed.name}`, ); } else { - resolved = await resolveWithPrimaryRemote(input.mode.repoPath); + resolved = await resolveLocalRepo(input.mode.repoPath); } rejectIfRepoint(resolved.repoPath); if (existing && existing.repoPath === resolved.repoPath) { - return { repoPath: existing.repoPath }; + const mainWorkspace = await ensureMainWorkspace( + ctx, + input.projectId, + existing.repoPath, + ); + return { + repoPath: existing.repoPath, + mainWorkspaceId: mainWorkspace?.id ?? null, + }; } - if (!cloudProject.repoCloneUrl) { + if (!cloudProject.repoCloneUrl && resolved.parsed) { await ctx.api.v2Project.linkRepoCloneUrl.mutate({ organizationId: ctx.organizationId, id: input.projectId, @@ -233,7 +261,15 @@ export const projectRouter = router({ }); } persistLocalProject(ctx, input.projectId, resolved); - return { repoPath: resolved.repoPath }; + const mainWorkspace = await ensureMainWorkspace( + ctx, + input.projectId, + resolved.repoPath, + ); + return { + repoPath: resolved.repoPath, + mainWorkspaceId: mainWorkspace?.id ?? null, + }; } } }), @@ -253,6 +289,13 @@ export const projectRouter = router({ .all(); for (const ws of localWorkspaces) { + if (ws.worktreePath === localProject.repoPath) { + await ctx.api.v2Workspace.deleteMainForHost.mutate({ + id: ws.id, + projectId: input.projectId, + }); + continue; + } try { const git = await ctx.git(localProject.repoPath); await git.raw(["worktree", "remove", ws.worktreePath]); diff --git a/packages/host-service/src/trpc/router/project/utils/ensure-main-workspace.ts b/packages/host-service/src/trpc/router/project/utils/ensure-main-workspace.ts new file mode 100644 index 00000000000..2899e6c7678 --- /dev/null +++ b/packages/host-service/src/trpc/router/project/utils/ensure-main-workspace.ts @@ -0,0 +1,93 @@ +import { getHostId, getHostName } from "@superset/shared/host-info"; +import { workspaces } from "../../../../db/schema"; +import type { HostServiceContext } from "../../../../types"; + +export type EnsureMainWorkspaceContext = Pick< + HostServiceContext, + "api" | "db" | "git" | "organizationId" +>; + +async function getCurrentBranchName( + git: Awaited>, +): Promise { + try { + const branch = await git.raw(["symbolic-ref", "--short", "HEAD"]); + const trimmed = branch.trim(); + return trimmed || null; + } catch { + try { + const branch = await git.revparse(["--abbrev-ref", "HEAD"]); + const trimmed = branch.trim(); + return trimmed && trimmed !== "HEAD" ? trimmed : null; + } catch { + return null; + } + } +} + +/** + * Ensures a `type='main'` v2 workspace exists for (projectId, currentHost), + * with a matching local `workspaces` row whose `worktreePath` is the repo root. + * + * Idempotent: safe to call from `project.setup` and from the startup sweep. + * Log-and-continue: on any cloud/local failure, logs and returns null so + * callers (e.g. `project.setup`) don't regress when a transient cloud blip + * hits. The startup sweep will backfill on the next boot. + */ +export async function ensureMainWorkspace( + ctx: EnsureMainWorkspaceContext, + projectId: string, + repoPath: string, +): Promise<{ id: string } | null> { + try { + const git = await ctx.git(repoPath); + const branch = await getCurrentBranchName(git); + if (!branch) { + console.warn( + `[ensureMainWorkspace] could not resolve current branch for ${projectId} at ${repoPath}; skipping`, + ); + return null; + } + + const host = await ctx.api.host.ensure.mutate({ + organizationId: ctx.organizationId, + machineId: getHostId(), + name: getHostName(), + }); + + const cloudRow = await ctx.api.v2Workspace.create.mutate({ + organizationId: ctx.organizationId, + projectId, + name: branch, + branch, + hostId: host.machineId, + type: "main", + }); + + ctx.db + .insert(workspaces) + .values({ + id: cloudRow.id, + projectId, + worktreePath: repoPath, + branch, + }) + .onConflictDoUpdate({ + target: workspaces.id, + set: { + projectId, + worktreePath: repoPath, + branch, + }, + }) + .run(); + + return { id: cloudRow.id }; + } catch (err) { + console.warn( + `[ensureMainWorkspace] failed for ${projectId} at ${repoPath}; will retry via startup sweep`, + err, + ); + return null; + } +} diff --git a/packages/host-service/src/trpc/router/project/utils/persist-project.ts b/packages/host-service/src/trpc/router/project/utils/persist-project.ts index 19ab45179c0..a09d0a40e8f 100644 --- a/packages/host-service/src/trpc/router/project/utils/persist-project.ts +++ b/packages/host-service/src/trpc/router/project/utils/persist-project.ts @@ -9,10 +9,10 @@ export function persistLocalProject( ): void { const repoFields = { repoPath: resolved.repoPath, - repoProvider: "github" as const, - repoOwner: resolved.parsed.owner, - repoName: resolved.parsed.name, - repoUrl: resolved.parsed.url, + repoProvider: resolved.parsed ? ("github" as const) : null, + repoOwner: resolved.parsed?.owner ?? null, + repoName: resolved.parsed?.name ?? null, + repoUrl: resolved.parsed?.url ?? null, remoteName: resolved.remoteName, }; ctx.db diff --git a/packages/host-service/src/trpc/router/project/utils/resolve-repo.test.ts b/packages/host-service/src/trpc/router/project/utils/resolve-repo.test.ts new file mode 100644 index 00000000000..0948d7590e7 --- /dev/null +++ b/packages/host-service/src/trpc/router/project/utils/resolve-repo.test.ts @@ -0,0 +1,37 @@ +import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { mkdtempSync, realpathSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import simpleGit from "simple-git"; +import { resolveLocalRepo } from "./resolve-repo"; + +describe("resolveLocalRepo", () => { + let repo: string; + + beforeEach(async () => { + repo = mkdtempSync(join(tmpdir(), "superset-local-repo-")); + await simpleGit(repo).init(); + }); + + afterEach(() => { + rmSync(repo, { recursive: true, force: true }); + }); + + test("accepts a git repo without GitHub remotes", async () => { + const resolved = await resolveLocalRepo(repo); + + expect(resolved.repoPath).toBe(realpathSync.native(repo)); + expect(resolved.remoteName).toBeNull(); + expect(resolved.parsed).toBeNull(); + }); + + test("returns origin when a GitHub origin exists", async () => { + const git = simpleGit(repo); + await git.addRemote("origin", "git@github.com:acme/example.git"); + + const resolved = await resolveLocalRepo(repo); + + expect(resolved.remoteName).toBe("origin"); + expect(resolved.parsed?.url).toBe("https://github.com/acme/example"); + }); +}); diff --git a/packages/host-service/src/trpc/router/project/utils/resolve-repo.ts b/packages/host-service/src/trpc/router/project/utils/resolve-repo.ts index 28dddfca654..8858899710d 100644 --- a/packages/host-service/src/trpc/router/project/utils/resolve-repo.ts +++ b/packages/host-service/src/trpc/router/project/utils/resolve-repo.ts @@ -11,6 +11,11 @@ import { export interface ResolvedRepo { repoPath: string; + remoteName: string | null; + parsed: ParsedGitHubRemote | null; +} + +export interface ResolvedGitHubRepo extends ResolvedRepo { remoteName: string; parsed: ParsedGitHubRemote; } @@ -52,7 +57,7 @@ async function revParseGitRoot(path: string): Promise { */ export async function resolveWithPrimaryRemote( repoPath: string, -): Promise { +): Promise { validateDirectoryPath(repoPath, "Path"); const gitRoot = await revParseGitRoot(repoPath); const remotes = await getGitHubRemotes(simpleGit(gitRoot)); @@ -77,6 +82,27 @@ export async function resolveWithPrimaryRemote( return { repoPath: gitRoot, remoteName: firstName, parsed: firstParsed }; } +/** + * Validates that a path is a git working tree and returns the canonical git + * root plus its primary GitHub remote when one exists. Local-only repos are + * valid v2 projects; they simply have no cloud clone URL or GitHub metadata. + */ +export async function resolveLocalRepo( + repoPath: string, +): Promise { + validateDirectoryPath(repoPath, "Path"); + const gitRoot = await revParseGitRoot(repoPath); + const remotes = await getGitHubRemotes(simpleGit(gitRoot)); + const originParsed = remotes.get("origin"); + if (originParsed) { + return { repoPath: gitRoot, remoteName: "origin", parsed: originParsed }; + } + const first = remotes.entries().next().value; + if (!first) return { repoPath: gitRoot, remoteName: null, parsed: null }; + const [firstName, firstParsed] = first; + return { repoPath: gitRoot, remoteName: firstName, parsed: firstParsed }; +} + /** * Validates that a path is a git working tree and returns the canonical git * root plus the GitHub remote whose `owner/name` matches `expectedSlug`. @@ -89,7 +115,7 @@ export async function resolveWithPrimaryRemote( export async function resolveMatchingSlug( repoPath: string, expectedSlug: string, -): Promise { +): Promise { validateDirectoryPath(repoPath, "Path"); const gitRoot = await revParseGitRoot(repoPath); const remotes = await getGitHubRemotes(simpleGit(gitRoot)); @@ -121,7 +147,7 @@ export async function resolveMatchingSlug( export async function cloneRepoInto( repoCloneUrl: string, parentDir: string, -): Promise { +): Promise { const parsedUrl = parseGitHubRemote(repoCloneUrl); if (!parsedUrl) { throw new TRPCError({ diff --git a/packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts b/packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts index d33f5e35c0e..ff7609dbc30 100644 --- a/packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts +++ b/packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts @@ -56,6 +56,27 @@ export const workspaceCleanupRouter = router({ .sync() : undefined; + if (local && project && local.worktreePath === project.repoPath) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: + "Main workspaces cannot be deleted. Remove them from the sidebar or remove the project from this host instead.", + }); + } + if (ctx.api) { + const cloudWorkspace = await ctx.api.v2Workspace.getFromHost.query({ + organizationId: ctx.organizationId, + id: input.workspaceId, + }); + if (cloudWorkspace?.type === "main") { + throw new TRPCError({ + code: "BAD_REQUEST", + message: + "Main workspaces cannot be deleted. Remove them from the sidebar or remove the project from this host instead.", + }); + } + } + // ─── Step 0: Preflight ───────────────────────────────────────── // Block only on dirty worktree (the common "I forgot to commit" // case). Anything else the local-cleanup phase handles as warning. diff --git a/packages/host-service/src/trpc/router/workspace-creation/procedures/adopt.ts b/packages/host-service/src/trpc/router/workspace-creation/procedures/adopt.ts index 41d2d19294e..77062c5f235 100644 --- a/packages/host-service/src/trpc/router/workspace-creation/procedures/adopt.ts +++ b/packages/host-service/src/trpc/router/workspace-creation/procedures/adopt.ts @@ -1,25 +1,127 @@ -import { getDeviceName, getHashedDeviceId } from "@superset/shared/device-info"; +import { getHostId, getHostName } from "@superset/shared/host-info"; import { TRPCError } from "@trpc/server"; -import { and, eq } from "drizzle-orm"; +import { and, eq, ne, or } from "drizzle-orm"; import { workspaces } from "../../../../db/schema"; +import type { HostServiceContext } from "../../../../types"; import { protectedProcedure } from "../../../index"; +import { ensureMainWorkspace } from "../../project/utils/ensure-main-workspace"; import { adoptInputSchema } from "../schemas"; import { - findWorktreeAtPath, + getWorktreeBranchAtPath, listWorktreeBranches, } from "../shared/branch-search"; import { requireLocalProject } from "../shared/local-project"; -import type { TerminalDescriptor } from "../shared/types"; +import type { GitClient, TerminalDescriptor } from "../shared/types"; + +type HostWorkspace = NonNullable< + Awaited< + ReturnType + > +>; + +function adoptResult(workspace: HostWorkspace) { + return { + workspace, + terminals: [] as TerminalDescriptor[], + warnings: [] as string[], + }; +} + +function deleteLocalWorkspace( + ctx: HostServiceContext, + workspaceId: string, +): void { + ctx.db.delete(workspaces).where(eq(workspaces.id, workspaceId)).run(); +} + +function persistLocalWorkspace( + ctx: HostServiceContext, + args: { + id: string; + projectId: string; + worktreePath: string; + branch: string; + }, +): void { + ctx.db + .insert(workspaces) + .values({ + id: args.id, + projectId: args.projectId, + worktreePath: args.worktreePath, + branch: args.branch, + }) + .onConflictDoUpdate({ + target: workspaces.id, + set: { + projectId: args.projectId, + worktreePath: args.worktreePath, + branch: args.branch, + }, + }) + .run(); +} + +function deleteLocalWorkspaceConflicts( + ctx: HostServiceContext, + args: { + projectId: string; + worktreePath: string; + branch: string; + keepWorkspaceId: string; + }, +): void { + ctx.db + .delete(workspaces) + .where( + and( + eq(workspaces.projectId, args.projectId), + or( + eq(workspaces.branch, args.branch), + eq(workspaces.worktreePath, args.worktreePath), + ), + ne(workspaces.id, args.keepWorkspaceId), + ), + ) + .run(); +} + +async function getHostWorkspace( + ctx: HostServiceContext, + workspaceId: string, +): Promise { + return ctx.api.v2Workspace.getFromHost.query({ + organizationId: ctx.organizationId, + id: workspaceId, + }); +} + +async function recordBaseBranch( + git: GitClient, + branch: string, + baseBranch: string | undefined, +): Promise { + if (!baseBranch) return; + await git + .raw(["config", `branch.${branch}.base`, baseBranch]) + .catch((err) => { + console.warn( + `[workspaceCreation.adopt] failed to record base branch ${baseBranch}:`, + err, + ); + }); +} export const adopt = protectedProcedure .input(adoptInputSchema) .mutation(async ({ ctx, input }) => { - const deviceClientId = getHashedDeviceId(); - const deviceName = getDeviceName(); + const machineId = getHostId(); + const hostName = getHostName(); const localProject = requireLocalProject(ctx, input.projectId); + await ensureMainWorkspace(ctx, input.projectId, localProject.repoPath); - const branch = input.branch.trim(); + let branch = input.branch.trim(); if (!branch) { throw new TRPCError({ code: "BAD_REQUEST", @@ -31,13 +133,17 @@ export const adopt = protectedProcedure let worktreePath: string; if (input.worktreePath) { - const found = await findWorktreeAtPath(git, input.worktreePath, branch); - if (!found) { + const actualBranch = await getWorktreeBranchAtPath( + git, + input.worktreePath, + ); + if (!actualBranch) { throw new TRPCError({ code: "NOT_FOUND", - message: `No git worktree registered at "${input.worktreePath}" on branch "${branch}"`, + message: `No git worktree registered at "${input.worktreePath}"`, }); } + branch = actualBranch; worktreePath = input.worktreePath; } else { // FORK NOTE: listWorktreeBranches uses (git, repoPath) — fork variant. @@ -55,23 +161,100 @@ export const adopt = protectedProcedure worktreePath = found; } - // We used to short-circuit on an existing local `workspaces` row - // (returning its id without calling cloud). That returned a - // phantom id when the cloud row had been hard-deleted — the - // picker would navigate to a workspace that no longer exists. - // Always create a fresh cloud row; if a stale local row leftover - // from a prior delete exists, replace it below. Proper host-side - // cleanup on delete is owned by the follow-up delete PR. - let host: { id: string }; + if (input.existingWorkspaceId) { + const existingCloud = await getHostWorkspace( + ctx, + input.existingWorkspaceId, + ); + if (existingCloud) { + await recordBaseBranch(git, branch, input.baseBranch); + deleteLocalWorkspaceConflicts(ctx, { + projectId: input.projectId, + worktreePath, + branch, + keepWorkspaceId: existingCloud.id, + }); + try { + persistLocalWorkspace(ctx, { + id: existingCloud.id, + projectId: input.projectId, + worktreePath, + branch, + }); + } catch (err) { + console.error( + "[workspaceCreation.adopt] local workspace relink failed", + err, + ); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: `Failed to persist existing workspace locally: ${err instanceof Error ? err.message : String(err)}`, + }); + } + return adoptResult(existingCloud); + } + } + + const existingLocal = ctx.db.query.workspaces + .findFirst({ + where: and( + eq(workspaces.projectId, input.projectId), + eq(workspaces.branch, branch), + ), + }) + .sync(); + if (existingLocal && existingLocal.worktreePath === worktreePath) { + const existingCloud = await getHostWorkspace(ctx, existingLocal.id); + if (existingCloud) { + await recordBaseBranch(git, branch, input.baseBranch); + return adoptResult(existingCloud); + } + deleteLocalWorkspace(ctx, existingLocal.id); + } + + const existingLocalByPath = ctx.db.query.workspaces + .findFirst({ + where: and( + eq(workspaces.projectId, input.projectId), + eq(workspaces.worktreePath, worktreePath), + ), + }) + .sync(); + if (existingLocalByPath) { + const existingCloud = await getHostWorkspace(ctx, existingLocalByPath.id); + if (existingCloud) { + deleteLocalWorkspaceConflicts(ctx, { + projectId: input.projectId, + worktreePath, + branch, + keepWorkspaceId: existingLocalByPath.id, + }); + const updatedCloud = + await ctx.api.v2Workspace.updateNameFromHost.mutate({ + id: existingCloud.id, + branch, + }); + ctx.db + .update(workspaces) + .set({ branch }) + .where(eq(workspaces.id, existingLocalByPath.id)) + .run(); + await recordBaseBranch(git, branch, input.baseBranch); + return adoptResult(updatedCloud); + } + deleteLocalWorkspace(ctx, existingLocalByPath.id); + } + + let host: { machineId: string }; try { - host = await ctx.api.device.ensureV2Host.mutate({ + host = await ctx.api.host.ensure.mutate({ organizationId: ctx.organizationId, - machineId: deviceClientId, - name: deviceName, + machineId, + name: hostName, }); } catch (err) { if (err instanceof TRPCError) throw err; - console.error("[workspaceCreation.adopt] ensureV2Host failed", err); + console.error("[workspaceCreation.adopt] host.ensure failed", err); throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message: `Failed to register host: ${err instanceof Error ? err.message : String(err)}`, @@ -85,7 +268,7 @@ export const adopt = protectedProcedure projectId: input.projectId, name: input.workspaceName, branch, - hostId: host.id, + hostId: host.machineId, }); } catch (err) { if (err instanceof TRPCError) throw err; @@ -103,35 +286,25 @@ export const adopt = protectedProcedure }); } + await recordBaseBranch(git, branch, input.baseBranch); + // Replace any stale local row for this (project, branch) — its // id likely points at a deleted cloud row. The new cloudRow.id // is the authoritative mapping. - const stale = ctx.db.query.workspaces - .findFirst({ - where: and( - eq(workspaces.projectId, input.projectId), - eq(workspaces.branch, branch), - ), - }) - .sync(); - if (stale && stale.id !== cloudRow.id) { - ctx.db.delete(workspaces).where(eq(workspaces.id, stale.id)).run(); - } + deleteLocalWorkspaceConflicts(ctx, { + projectId: input.projectId, + worktreePath, + branch, + keepWorkspaceId: cloudRow.id, + }); try { - ctx.db - .insert(workspaces) - .values({ - id: cloudRow.id, - projectId: input.projectId, - worktreePath, - branch, - }) - .onConflictDoUpdate({ - target: workspaces.id, - set: { projectId: input.projectId, worktreePath, branch }, - }) - .run(); + persistLocalWorkspace(ctx, { + id: cloudRow.id, + projectId: input.projectId, + worktreePath, + branch, + }); } catch (err) { console.error( "[workspaceCreation.adopt] local workspaces insert failed", @@ -151,9 +324,5 @@ export const adopt = protectedProcedure }); } - return { - workspace: cloudRow, - terminals: [] as TerminalDescriptor[], - warnings: [] as string[], - }; + return adoptResult(cloudRow); }); diff --git a/packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts b/packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts index e816468fd75..60fd5c34aba 100644 --- a/packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts +++ b/packages/host-service/src/trpc/router/workspace-creation/procedures/checkout.ts @@ -3,6 +3,7 @@ import { and, eq } from "drizzle-orm"; import { workspaces } from "../../../../db/schema"; import { resolveRef } from "../../../../runtime/git/refs"; import { protectedProcedure } from "../../../index"; +import { ensureMainWorkspace } from "../../project/utils/ensure-main-workspace"; import { checkoutInputSchema } from "../schemas"; import { finishCheckout } from "../shared/finish-checkout"; import { enablePushAutoSetupRemote } from "../shared/git-config"; @@ -18,6 +19,7 @@ export const checkout = protectedProcedure setProgress(input.pendingId, "ensuring_repo"); const localProject = requireLocalProject(ctx, input.projectId); + await ensureMainWorkspace(ctx, input.projectId, localProject.repoPath); setProgress(input.pendingId, "creating_worktree"); diff --git a/packages/host-service/src/trpc/router/workspace-creation/procedures/create.ts b/packages/host-service/src/trpc/router/workspace-creation/procedures/create.ts index c1d82a80e50..a00e584d6e2 100644 --- a/packages/host-service/src/trpc/router/workspace-creation/procedures/create.ts +++ b/packages/host-service/src/trpc/router/workspace-creation/procedures/create.ts @@ -1,4 +1,4 @@ -import { getDeviceName, getHashedDeviceId } from "@superset/shared/device-info"; +import { getHostId, getHostName } from "@superset/shared/host-info"; import { TRPCError } from "@trpc/server"; import { workspaces } from "../../../../db/schema"; import { @@ -8,6 +8,7 @@ import { resolveUpstream, } from "../../../../runtime/git/refs"; import { protectedProcedure } from "../../../index"; +import { ensureMainWorkspace } from "../../project/utils/ensure-main-workspace"; import { createInputSchema } from "../schemas"; import { enablePushAutoSetupRemote } from "../shared/git-config"; import { requireLocalProject } from "../shared/local-project"; @@ -24,11 +25,12 @@ import { deduplicateBranchName } from "../utils/sanitize-branch"; export const create = protectedProcedure .input(createInputSchema) .mutation(async ({ ctx, input }) => { - const deviceClientId = getHashedDeviceId(); - const deviceName = getDeviceName(); + const machineId = getHostId(); + const hostName = getHostName(); setProgress(input.pendingId, "ensuring_repo"); const localProject = requireLocalProject(ctx, input.projectId); + await ensureMainWorkspace(ctx, input.projectId, localProject.repoPath); setProgress(input.pendingId, "creating_worktree"); @@ -194,15 +196,15 @@ export const create = protectedProcedure } }; - let host: { id: string }; + let host: { machineId: string }; try { - host = await ctx.api.device.ensureV2Host.mutate({ + host = await ctx.api.host.ensure.mutate({ organizationId: ctx.organizationId, - machineId: deviceClientId, - name: deviceName, + machineId, + name: hostName, }); } catch (err) { - console.error("[workspaceCreation.create] ensureV2Host failed", err); + console.error("[workspaceCreation.create] host.ensure failed", err); clearProgress(input.pendingId); await rollbackWorktree(); throw new TRPCError({ @@ -217,7 +219,7 @@ export const create = protectedProcedure projectId: input.projectId, name: input.names.workspaceName, branch: branchName, - hostId: host.id, + hostId: host.machineId, }) .catch(async (err) => { console.error( diff --git a/packages/host-service/src/trpc/router/workspace-creation/schemas.ts b/packages/host-service/src/trpc/router/workspace-creation/schemas.ts index bf82f949bc0..7c98376f7bf 100644 --- a/packages/host-service/src/trpc/router/workspace-creation/schemas.ts +++ b/packages/host-service/src/trpc/router/workspace-creation/schemas.ts @@ -100,6 +100,8 @@ export const adoptInputSchema = z.object({ projectId: z.string(), workspaceName: z.string(), branch: z.string(), + baseBranch: z.string().optional(), + existingWorkspaceId: z.string().optional(), // When provided, adopt the worktree at this explicit path instead // of looking one up under /.worktrees/. Used by // the v1→v2 migration to adopt worktrees at legacy paths (e.g. diff --git a/packages/host-service/src/trpc/router/workspace-creation/shared/branch-search.test.ts b/packages/host-service/src/trpc/router/workspace-creation/shared/branch-search.test.ts new file mode 100644 index 00000000000..f91e6cb6d3a --- /dev/null +++ b/packages/host-service/src/trpc/router/workspace-creation/shared/branch-search.test.ts @@ -0,0 +1,55 @@ +import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { mkdirSync, mkdtempSync, rmSync } from "node:fs"; +import { writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import simpleGit, { type SimpleGit } from "simple-git"; +import { findWorktreeAtPath, getWorktreeBranchAtPath } from "./branch-search"; + +async function initRepo(path: string): Promise { + const git = simpleGit(path); + await git.init(); + await git.raw(["config", "user.email", "test@example.com"]); + await git.raw(["config", "user.name", "test"]); + await git.raw(["config", "commit.gpgsign", "false"]); + await git.raw(["symbolic-ref", "HEAD", "refs/heads/main"]); + await writeFile(join(path, "README.md"), "test\n"); + await git.raw(["add", "README.md"]); + await git.raw(["commit", "-m", "initial"]); + return git; +} + +describe("worktree branch lookup", () => { + let root: string; + let repo: string; + let worktreePath: string; + let git: SimpleGit; + + beforeEach(async () => { + root = mkdtempSync(join(tmpdir(), "superset-worktree-branch-")); + repo = join(root, "repo"); + worktreePath = join(root, "worktree"); + mkdirSync(repo); + git = await initRepo(repo); + await git.raw(["worktree", "add", "-b", "original", worktreePath, "main"]); + }); + + afterEach(() => { + rmSync(root, { recursive: true, force: true }); + }); + + test("reads the branch currently checked out at a worktree path", async () => { + const worktreeGit = simpleGit(worktreePath); + await worktreeGit.raw(["checkout", "-b", "renamed"]); + + await expect(getWorktreeBranchAtPath(git, worktreePath)).resolves.toBe( + "renamed", + ); + await expect( + findWorktreeAtPath(git, worktreePath, "original"), + ).resolves.toBe(false); + await expect( + findWorktreeAtPath(git, worktreePath, "renamed"), + ).resolves.toBe(true); + }); +}); diff --git a/packages/host-service/src/trpc/router/workspace-creation/shared/branch-search.ts b/packages/host-service/src/trpc/router/workspace-creation/shared/branch-search.ts index 8edb5fde052..fa2b811bb50 100644 --- a/packages/host-service/src/trpc/router/workspace-creation/shared/branch-search.ts +++ b/packages/host-service/src/trpc/router/workspace-creation/shared/branch-search.ts @@ -1,3 +1,4 @@ +import { realpathSync } from "node:fs"; import { resolve as resolvePath, sep } from "node:path"; import type { GitClient } from "./types"; @@ -42,6 +43,14 @@ export function markRefetchRemote(projectId: string): void { // FORK NOTE: listWorktreeBranches uses (git, repoPath) — not (ctx, git, projectId). // Worktrees are under /.worktrees/, not ~/.superset/worktrees. // This matches the fork's safeResolveWorktreePath in shared/worktree-paths.ts. +function normalizeWorktreePath(path: string): string { + try { + return realpathSync.native(path); + } catch { + return resolvePath(path); + } +} + export async function listWorktreeBranches( git: GitClient, repoPath: string, @@ -96,17 +105,32 @@ export async function findWorktreeAtPath( worktreePath: string, expectedBranch: string, ): Promise { - const targetPath = resolvePath(worktreePath); + const branch = await getWorktreeBranchAtPath(git, worktreePath); + return branch === expectedBranch; +} + +/** + * Returns the branch currently checked out at a registered git worktree path. + * Explicit path adoption uses this so stale database branch names do not make + * migration skip a perfectly valid worktree. + */ +export async function getWorktreeBranchAtPath( + git: GitClient, + worktreePath: string, +): Promise { + const targetPath = normalizeWorktreePath(worktreePath); try { const raw = await git.raw(["worktree", "list", "--porcelain"]); let currentPath: string | null = null; for (const line of raw.split("\n")) { if (line.startsWith("worktree ")) { - currentPath = line.slice("worktree ".length).trim(); + currentPath = normalizeWorktreePath( + line.slice("worktree ".length).trim(), + ); } else if (line.startsWith("branch refs/heads/") && currentPath) { - if (resolvePath(currentPath) !== targetPath) continue; + if (currentPath !== targetPath) continue; const branch = line.slice("branch refs/heads/".length).trim(); - return branch === expectedBranch; + return branch || null; } else if (line === "") { currentPath = null; } @@ -117,7 +141,7 @@ export async function findWorktreeAtPath( err, ); } - return false; + return null; } // Parses `git log -g` to return {branchName: ordinal} where 0 = most recent. diff --git a/packages/host-service/src/trpc/router/workspace-creation/shared/finish-checkout.ts b/packages/host-service/src/trpc/router/workspace-creation/shared/finish-checkout.ts index d35af97c39e..7be2be8f561 100644 --- a/packages/host-service/src/trpc/router/workspace-creation/shared/finish-checkout.ts +++ b/packages/host-service/src/trpc/router/workspace-creation/shared/finish-checkout.ts @@ -1,4 +1,4 @@ -import { getDeviceName, getHashedDeviceId } from "@superset/shared/device-info"; +import { getHostId, getHostName } from "@superset/shared/host-info"; import { TRPCError } from "@trpc/server"; import { workspaces } from "../../../../db/schema"; import type { HostServiceContext } from "../../../../types"; @@ -61,18 +61,18 @@ export async function finishCheckout( } }; - const deviceClientId = getHashedDeviceId(); - const deviceName = getDeviceName(); + const machineId = getHostId(); + const hostName = getHostName(); - let host: { id: string }; + let host: { machineId: string }; try { - host = await ctx.api.device.ensureV2Host.mutate({ + host = await ctx.api.host.ensure.mutate({ organizationId: ctx.organizationId, - machineId: deviceClientId, - name: deviceName, + machineId, + name: hostName, }); } catch (err) { - console.error("[workspaceCreation.checkout] ensureV2Host failed", err); + console.error("[workspaceCreation.checkout] host.ensure failed", err); clearProgress(args.pendingId); await rollbackWorktree(); throw new TRPCError({ @@ -87,7 +87,7 @@ export async function finishCheckout( projectId: args.projectId, name: args.workspaceName, branch: args.branch, - hostId: host.id, + hostId: host.machineId, }) .catch(async (err) => { console.error( diff --git a/packages/host-service/src/trpc/router/workspace/workspace.ts b/packages/host-service/src/trpc/router/workspace/workspace.ts index 1a414d9ed04..b48bc75d490 100644 --- a/packages/host-service/src/trpc/router/workspace/workspace.ts +++ b/packages/host-service/src/trpc/router/workspace/workspace.ts @@ -1,6 +1,6 @@ import { existsSync, mkdirSync } from "node:fs"; import { dirname, join } from "node:path"; -import { getDeviceName, getHashedDeviceId } from "@superset/shared/device-info"; +import { getHostId, getHostName } from "@superset/shared/host-info"; import { TRPCError } from "@trpc/server"; import { eq } from "drizzle-orm"; import { z } from "zod"; @@ -8,6 +8,7 @@ import { projects, workspaces } from "../../../db/schema"; import { invalidateLabelCache } from "../../../ports/static-ports"; import { createSimpleGitWithEnv } from "../../../runtime/git/simple-git"; import { protectedProcedure, router } from "../../index"; +import { ensureMainWorkspace } from "../project/utils/ensure-main-workspace"; export const workspaceRouter = router({ get: protectedProcedure @@ -80,13 +81,15 @@ export const workspaceRouter = router({ localProject = inserted; } + await ensureMainWorkspace(ctx, input.projectId, localProject.repoPath); + const worktreePath = join( localProject.repoPath, ".worktrees", input.branch, ); - const deviceClientId = getHashedDeviceId(); - const deviceName = getDeviceName(); + const machineId = getHostId(); + const hostName = getHostName(); const git = await ctx.git(localProject.repoPath); try { @@ -95,10 +98,10 @@ export const workspaceRouter = router({ await git.raw(["worktree", "add", "-b", input.branch, worktreePath]); } - const host = await ctx.api.device.ensureV2Host.mutate({ + const host = await ctx.api.host.ensure.mutate({ organizationId: ctx.organizationId, - machineId: deviceClientId, - name: deviceName, + machineId, + name: hostName, }); const cloudRow = await ctx.api.v2Workspace.create @@ -107,7 +110,7 @@ export const workspaceRouter = router({ projectId: input.projectId, name: input.name, branch: input.branch, - hostId: host.id, + hostId: host.machineId, }) .catch(async (err) => { try { @@ -175,17 +178,42 @@ export const workspaceRouter = router({ }); } - await ctx.api.v2Workspace.delete.mutate({ id: input.id }); - const localWorkspace = ctx.db.query.workspaces .findFirst({ where: eq(workspaces.id, input.id) }) .sync(); + const localProject = localWorkspace + ? ctx.db.query.projects + .findFirst({ where: eq(projects.id, localWorkspace.projectId) }) + .sync() + : undefined; + + if ( + localWorkspace && + localProject && + localWorkspace.worktreePath === localProject.repoPath + ) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: + "Main workspaces cannot be deleted. Remove them from the sidebar or remove the project from this host instead.", + }); + } - if (localWorkspace) { - const localProject = ctx.db.query.projects - .findFirst({ where: eq(projects.id, localWorkspace.projectId) }) - .sync(); + const cloudWorkspace = await ctx.api.v2Workspace.getFromHost.query({ + organizationId: ctx.organizationId, + id: input.id, + }); + if (cloudWorkspace?.type === "main") { + throw new TRPCError({ + code: "BAD_REQUEST", + message: + "Main workspaces cannot be deleted. Remove them from the sidebar or remove the project from this host instead.", + }); + } + await ctx.api.v2Workspace.delete.mutate({ id: input.id }); + + if (localWorkspace) { if (localProject) { try { const git = await ctx.git(localProject.repoPath); diff --git a/packages/host-service/src/tunnel/connect.ts b/packages/host-service/src/tunnel/connect.ts index 0c9d60bc0c8..b2aa15f2444 100644 --- a/packages/host-service/src/tunnel/connect.ts +++ b/packages/host-service/src/tunnel/connect.ts @@ -1,4 +1,5 @@ -import { getDeviceName, getHashedDeviceId } from "@superset/shared/device-info"; +import { getHostId, getHostName } from "@superset/shared/host-info"; +import { buildHostRoutingKey } from "@superset/shared/host-routing"; import type { JwtApiAuthProvider } from "../providers/auth/JwtAuthProvider/JwtAuthProvider"; import type { ApiClient } from "../types"; import { TunnelClient } from "./tunnel-client"; @@ -16,16 +17,16 @@ export async function connectRelay( options: ConnectRelayOptions, ): Promise { try { - const host = await options.api.device.ensureV2Host.mutate({ + const host = await options.api.host.ensure.mutate({ organizationId: options.organizationId, - machineId: getHashedDeviceId(), - name: getDeviceName(), + machineId: getHostId(), + name: getHostName(), }); - console.log(`[host-service] registered as host ${host.id}`); + console.log(`[host-service] registered as host ${host.machineId}`); const tunnel = new TunnelClient({ relayUrl: options.relayUrl, - hostId: host.id, + hostId: buildHostRoutingKey(options.organizationId, host.machineId), getAuthToken: () => options.authProvider.getJwt(), localPort: options.localPort, hostServiceSecret: options.hostServiceSecret, diff --git a/packages/shared/package.json b/packages/shared/package.json index 567abfc18fc..8aab16c310b 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -84,9 +84,13 @@ "types": "./src/tunnel-protocol.ts", "default": "./src/tunnel-protocol.ts" }, - "./device-info": { - "types": "./src/device-info.ts", - "default": "./src/device-info.ts" + "./host-info": { + "types": "./src/host-info.ts", + "default": "./src/host-info.ts" + }, + "./host-routing": { + "types": "./src/host-routing.ts", + "default": "./src/host-routing.ts" }, "./github-remote": { "types": "./src/github-remote.ts", diff --git a/packages/shared/src/device-info.ts b/packages/shared/src/host-info.ts similarity index 76% rename from packages/shared/src/device-info.ts rename to packages/shared/src/host-info.ts index abd369c4479..449162efb29 100644 --- a/packages/shared/src/device-info.ts +++ b/packages/shared/src/host-info.ts @@ -3,7 +3,9 @@ import { createHmac } from "node:crypto"; import { readFileSync } from "node:fs"; import { homedir, hostname, platform } from "node:os"; -const APP_DEVICE_SALT = "superset-desktop-device-id-v1"; +// Salt value preserved verbatim across the rename to keep existing host ids +// stable for users already registered against the cloud. +const APP_HOST_SALT = "superset-desktop-device-id-v1"; function getRawMachineId(): string { try { @@ -48,7 +50,7 @@ let cachedMachineId: string | null = null; /** * Raw machine ID for local encryption key derivation. - * Do NOT send this to the cloud - use getHashedDeviceId() instead. + * Do NOT send this to the cloud - use getHostId() instead. */ export function getMachineId(): string { if (!cachedMachineId) { @@ -60,13 +62,14 @@ export function getMachineId(): string { let cachedHashedId: string | null = null; /** - * Hashed device ID safe for cloud transmission. - * Non-reversible, stable, and app-specific. + * Stable host id safe for cloud transmission. + * HMAC of the raw machine id; non-reversible and app-specific. + * This is the canonical identifier for a machine acting as a host or client. */ -export function getHashedDeviceId(): string { +export function getHostId(): string { if (!cachedHashedId) { const machineId = getMachineId(); - cachedHashedId = createHmac("sha256", APP_DEVICE_SALT) + cachedHashedId = createHmac("sha256", APP_HOST_SALT) .update(machineId) .digest("hex") .slice(0, 32); @@ -75,19 +78,16 @@ export function getHashedDeviceId(): string { } /** - * Sanitized device name for cloud transmission. + * Sanitized host name for cloud transmission. * Returns generic identifier instead of potentially PII-containing hostname. */ -export function getDeviceName(): string { +export function getHostName(): string { const os = platform(); const osName = os === "darwin" ? "Mac" : os === "win32" ? "Windows" : "Linux"; const rawHostname = hostname(); - // Use just the first segment if it looks like a local hostname - // e.g., "johns-macbook-pro.local" -> "johns-macbook-pro" const shortName = rawHostname.split(".")[0] || rawHostname; - // If hostname looks generic or is very short, use OS name if (shortName.length < 3 || shortName === "localhost") { return `${osName} Desktop`; } diff --git a/packages/shared/src/host-routing.ts b/packages/shared/src/host-routing.ts new file mode 100644 index 00000000000..c4e26c66554 --- /dev/null +++ b/packages/shared/src/host-routing.ts @@ -0,0 +1,25 @@ +/** + * Routing key the relay uses to identify a host service tunnel. The same + * physical machine can be a host in multiple orgs, so machineId alone is + * not unique on the relay's tunnel map — scope it by org. + * + * Lives in its own module (not host-info) so the renderer can import it + * without pulling in node:child_process / node:fs. + */ +export function buildHostRoutingKey( + organizationId: string, + machineId: string, +): string { + return `${organizationId}:${machineId}`; +} + +export function parseHostRoutingKey( + key: string, +): { organizationId: string; machineId: string } | null { + const idx = key.indexOf(":"); + if (idx <= 0 || idx === key.length - 1) return null; + return { + organizationId: key.slice(0, idx), + machineId: key.slice(idx + 1), + }; +} diff --git a/packages/trpc/package.json b/packages/trpc/package.json index 0e334bde06c..bbda028f6e1 100644 --- a/packages/trpc/package.json +++ b/packages/trpc/package.json @@ -30,9 +30,12 @@ "@t3-oss/env-core": "^0.13.8", "@trpc/server": "^11.7.1", "@upstash/qstash": "^2.8.4", + "@upstash/ratelimit": "^2.0.4", + "@upstash/redis": "^1.34.3", "@vercel/blob": "^2.0.0", "@vercel/kv": "^3.0.0", "drizzle-orm": "0.45.2", + "resend": "^4.0.1", "superjson": "^2.2.5", "zod": "^4.3.5" }, diff --git a/packages/trpc/src/env.ts b/packages/trpc/src/env.ts index 73b83a0aa3d..d2ba1695017 100644 --- a/packages/trpc/src/env.ts +++ b/packages/trpc/src/env.ts @@ -13,6 +13,9 @@ export const env = createEnv({ QSTASH_TOKEN: z.string().min(1), QSTASH_CURRENT_SIGNING_KEY: z.string().min(1), QSTASH_NEXT_SIGNING_KEY: z.string().min(1), + // FORK NOTE: optional. fork はサポートメール送信先 (resend) を持たないため + // 未設定なら support.sendMigrationReport を 503 で no-op にする。 + RESEND_API_KEY: z.string().min(1).optional(), NEXT_PUBLIC_API_URL: z.string().url(), NEXT_PUBLIC_WEB_URL: z.string().url(), KV_REST_API_URL: z.string().url().optional(), diff --git a/packages/trpc/src/root.ts b/packages/trpc/src/root.ts index b5420b8ae64..65c9bca6d58 100644 --- a/packages/trpc/src/root.ts +++ b/packages/trpc/src/root.ts @@ -8,9 +8,11 @@ import { automationRouter } from "./router/automation"; import { billingRouter } from "./router/billing"; import { chatRouter } from "./router/chat"; import { deviceRouter } from "./router/device"; +import { hostRouter } from "./router/host"; import { integrationRouter } from "./router/integration"; import { organizationRouter } from "./router/organization"; import { projectRouter } from "./router/project"; +import { supportRouter } from "./router/support/support"; import { taskRouter } from "./router/task"; import { userRouter } from "./router/user"; import { v2HostRouter } from "./router/v2-host"; @@ -28,9 +30,11 @@ export const appRouter = createTRPCRouter({ billing: billingRouter, chat: chatRouter, device: deviceRouter, + host: hostRouter, integration: integrationRouter, organization: organizationRouter, project: projectRouter, + support: supportRouter, task: taskRouter, user: userRouter, v2Host: v2HostRouter, diff --git a/packages/trpc/src/router/automation/automation.ts b/packages/trpc/src/router/automation/automation.ts index 67fd759ed6b..6cb559ddad0 100644 --- a/packages/trpc/src/router/automation/automation.ts +++ b/packages/trpc/src/router/automation/automation.ts @@ -31,12 +31,17 @@ async function verifyHostAccess( hostId: string, ): Promise { const [host] = await db - .select({ id: v2Hosts.id, organizationId: v2Hosts.organizationId }) + .select({ machineId: v2Hosts.machineId }) .from(v2Hosts) - .where(eq(v2Hosts.id, hostId)) + .where( + and( + eq(v2Hosts.organizationId, organizationId), + eq(v2Hosts.machineId, hostId), + ), + ) .limit(1); - if (!host || host.organizationId !== organizationId) { + if (!host) { throw new TRPCError({ code: "NOT_FOUND", message: "Host not found", @@ -44,10 +49,14 @@ async function verifyHostAccess( } const [membership] = await db - .select({ id: v2UsersHosts.id }) + .select({ hostId: v2UsersHosts.hostId }) .from(v2UsersHosts) .where( - and(eq(v2UsersHosts.userId, userId), eq(v2UsersHosts.hostId, hostId)), + and( + eq(v2UsersHosts.userId, userId), + eq(v2UsersHosts.organizationId, organizationId), + eq(v2UsersHosts.hostId, hostId), + ), ) .limit(1); diff --git a/packages/trpc/src/router/automation/dispatch.ts b/packages/trpc/src/router/automation/dispatch.ts index 62c28828656..a3144ea1ecf 100644 --- a/packages/trpc/src/router/automation/dispatch.ts +++ b/packages/trpc/src/router/automation/dispatch.ts @@ -14,6 +14,7 @@ import { getCommandFromAgentConfig, type TerminalResolvedAgentConfig, } from "@superset/shared/agent-settings"; +import { buildHostRoutingKey } from "@superset/shared/host-routing"; import { deduplicateBranchName, sanitizeBranchNameWithMaxLength, @@ -57,7 +58,7 @@ export async function dispatchAutomation( const inserted = await recordSkipped( automation, scheduledFor, - host.id, + host.machineId, error, ); return { status: "skipped_offline", runId: inserted?.id ?? null, error }; @@ -70,7 +71,7 @@ export async function dispatchAutomation( organizationId: automation.organizationId, title: automation.name, scheduledFor, - hostId: host.id, + hostId: host.machineId, status: "dispatching", }) .onConflictDoNothing({ @@ -97,12 +98,17 @@ export async function dispatchAutomation( ttlSeconds: 300, }); + const routingKey = buildHostRoutingKey( + automation.organizationId, + host.machineId, + ); + if (automation.v2WorkspaceId) { workspaceId = automation.v2WorkspaceId; } else { const created = await createWorkspaceOnHost({ relayUrl, - hostId: host.id, + hostId: routingKey, jwt, projectId: automation.v2ProjectId, automation, @@ -121,7 +127,7 @@ export async function dispatchAutomation( if (agentConfig.kind === "chat") { const { sessionId } = await dispatchChatSession({ relayUrl, - hostId: host.id, + hostId: routingKey, jwt, workspaceId, prompt: automation.prompt, @@ -154,7 +160,7 @@ export async function dispatchAutomation( }); const { terminalId } = await dispatchTerminalSession({ relayUrl, - hostId: host.id, + hostId: routingKey, jwt, workspaceId, command, @@ -193,14 +199,18 @@ async function resolveTargetHost( const [host] = await dbWs .select() .from(v2Hosts) - .where(eq(v2Hosts.id, automation.targetHostId)) + .where( + and( + eq(v2Hosts.organizationId, automation.organizationId), + eq(v2Hosts.machineId, automation.targetHostId), + ), + ) .limit(1); return host ?? null; } const [host] = await dbWs .select({ - id: v2Hosts.id, organizationId: v2Hosts.organizationId, machineId: v2Hosts.machineId, name: v2Hosts.name, @@ -210,7 +220,13 @@ async function resolveTargetHost( updatedAt: v2Hosts.updatedAt, }) .from(v2Hosts) - .innerJoin(v2UsersHosts, eq(v2UsersHosts.hostId, v2Hosts.id)) + .innerJoin( + v2UsersHosts, + and( + eq(v2UsersHosts.organizationId, v2Hosts.organizationId), + eq(v2UsersHosts.hostId, v2Hosts.machineId), + ), + ) .where( and( eq(v2UsersHosts.userId, automation.ownerUserId), diff --git a/packages/trpc/src/router/automation/schema.ts b/packages/trpc/src/router/automation/schema.ts index 085e5cdeb16..a715ff013ef 100644 --- a/packages/trpc/src/router/automation/schema.ts +++ b/packages/trpc/src/router/automation/schema.ts @@ -38,7 +38,7 @@ export const createAutomationSchema = z.object({ name: z.string().min(1).max(200), prompt: z.string().min(1).max(20_000), agentConfig: agentConfigSchema, - targetHostId: z.string().uuid().nullish(), + targetHostId: z.string().min(1).nullish(), v2ProjectId: z.string().uuid(), v2WorkspaceId: z.string().uuid().nullish(), rrule: rruleBody, @@ -52,7 +52,7 @@ export const updateAutomationSchema = z.object({ name: z.string().min(1).max(200).optional(), prompt: z.string().min(1).max(20_000).optional(), agentConfig: agentConfigSchema.optional(), - targetHostId: z.string().uuid().nullish(), + targetHostId: z.string().min(1).nullish(), v2ProjectId: z.string().uuid().optional(), v2WorkspaceId: z.string().uuid().nullish(), rrule: rruleBody.optional(), diff --git a/packages/trpc/src/router/device/device.ts b/packages/trpc/src/router/device/device.ts index 00f11c20057..3a2478252bb 100644 --- a/packages/trpc/src/router/device/device.ts +++ b/packages/trpc/src/router/device/device.ts @@ -1,124 +1,15 @@ -import { db, dbWs } from "@superset/db/client"; -import { - devicePresence, - deviceTypeValues, - v2Clients, - v2ClientTypeValues, - v2Hosts, - v2UsersHosts, -} from "@superset/db/schema"; +import { db } from "@superset/db/client"; +import { devicePresence, deviceTypeValues } from "@superset/db/schema"; import { TRPCError, type TRPCRouterRecord } from "@trpc/server"; -import { and, eq } from "drizzle-orm"; import { z } from "zod"; -import { jwtProcedure, protectedProcedure } from "../../trpc"; +import { protectedProcedure } from "../../trpc"; +/** + * v1 device-presence procedures. Kept separate from the v2 host router so the + * device_presence table stays an isolated v1 system that gets retired with + * the rest of v1. + */ export const deviceRouter = { - ensureV2Host: jwtProcedure - .input( - z.object({ - organizationId: z.string().uuid(), - machineId: z.string().min(1), - name: z.string().min(1), - }), - ) - .mutation(async ({ ctx, input }) => { - if (!ctx.organizationIds.includes(input.organizationId)) { - throw new TRPCError({ - code: "FORBIDDEN", - message: "Not a member of this organization", - }); - } - - const [host] = await dbWs - .insert(v2Hosts) - .values({ - organizationId: input.organizationId, - machineId: input.machineId, - name: input.name, - createdByUserId: ctx.userId, - }) - .onConflictDoUpdate({ - target: [v2Hosts.organizationId, v2Hosts.machineId], - set: { - name: input.name, - }, - }) - .returning(); - - if (!host) { - throw new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: "Failed to ensure host", - }); - } - - await dbWs - .insert(v2UsersHosts) - .values({ - organizationId: input.organizationId, - userId: ctx.userId, - hostId: host.id, - role: "owner", - }) - .onConflictDoNothing({ - target: [ - v2UsersHosts.organizationId, - v2UsersHosts.userId, - v2UsersHosts.hostId, - ], - }); - - return host; - }), - - ensureV2Client: protectedProcedure - .input( - z.object({ - machineId: z.string().min(1), - type: z.enum(v2ClientTypeValues), - }), - ) - .mutation(async ({ ctx, input }) => { - const organizationId = ctx.activeOrganizationId; - if (!organizationId) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "No active organization selected", - }); - } - - const userId = ctx.session.user.id; - - const [client] = await dbWs - .insert(v2Clients) - .values({ - organizationId, - userId, - machineId: input.machineId, - type: input.type, - }) - .onConflictDoUpdate({ - target: [ - v2Clients.organizationId, - v2Clients.userId, - v2Clients.machineId, - ], - set: { - type: input.type, - }, - }) - .returning(); - - if (!client) { - throw new TRPCError({ - code: "INTERNAL_SERVER_ERROR", - message: "Failed to ensure client", - }); - } - - return client; - }), - /** * @deprecated Kept for backwards compat with shipped desktop/mobile clients * that still call heartbeat on a 30s interval. Same logic as registerDevice. @@ -215,39 +106,4 @@ export const deviceRouter = { return { device, timestamp: now }; }), - checkHostAccess: jwtProcedure - .input(z.object({ hostId: z.string().uuid() })) - .query(async ({ ctx, input }) => { - const row = await db.query.v2UsersHosts.findFirst({ - where: and( - eq(v2UsersHosts.userId, ctx.userId), - eq(v2UsersHosts.hostId, input.hostId), - ), - columns: { id: true }, - }); - return { allowed: !!row }; - }), - - setHostOnline: jwtProcedure - .input(z.object({ hostId: z.string().uuid(), isOnline: z.boolean() })) - .mutation(async ({ ctx, input }) => { - const access = await db.query.v2UsersHosts.findFirst({ - where: and( - eq(v2UsersHosts.userId, ctx.userId), - eq(v2UsersHosts.hostId, input.hostId), - ), - columns: { id: true }, - }); - if (!access) { - throw new TRPCError({ - code: "FORBIDDEN", - message: "No access to this host", - }); - } - await db - .update(v2Hosts) - .set({ isOnline: input.isOnline }) - .where(eq(v2Hosts.id, input.hostId)); - return { success: true }; - }), } satisfies TRPCRouterRecord; diff --git a/packages/trpc/src/router/host/host.ts b/packages/trpc/src/router/host/host.ts new file mode 100644 index 00000000000..9a3cc5e7fa0 --- /dev/null +++ b/packages/trpc/src/router/host/host.ts @@ -0,0 +1,182 @@ +import { db, dbWs } from "@superset/db/client"; +import { + v2Clients, + v2ClientTypeValues, + v2Hosts, + v2UsersHosts, +} from "@superset/db/schema"; +import { parseHostRoutingKey } from "@superset/shared/host-routing"; +import { TRPCError, type TRPCRouterRecord } from "@trpc/server"; +import { and, eq } from "drizzle-orm"; +import { z } from "zod"; +import { jwtProcedure, protectedProcedure } from "../../trpc"; + +export const hostRouter = { + ensure: jwtProcedure + .input( + z.object({ + organizationId: z.string().uuid(), + machineId: z.string().min(1), + name: z.string().min(1), + }), + ) + .mutation(async ({ ctx, input }) => { + if (!ctx.organizationIds.includes(input.organizationId)) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "Not a member of this organization", + }); + } + + const [host] = await dbWs + .insert(v2Hosts) + .values({ + organizationId: input.organizationId, + machineId: input.machineId, + name: input.name, + createdByUserId: ctx.userId, + }) + .onConflictDoUpdate({ + target: [v2Hosts.organizationId, v2Hosts.machineId], + set: { + name: input.name, + }, + }) + .returning(); + + if (!host) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to ensure host", + }); + } + + if (host.createdByUserId === ctx.userId) { + await dbWs + .insert(v2UsersHosts) + .values({ + organizationId: input.organizationId, + userId: ctx.userId, + hostId: host.machineId, + role: "owner", + }) + .onConflictDoNothing({ + target: [ + v2UsersHosts.organizationId, + v2UsersHosts.userId, + v2UsersHosts.hostId, + ], + }); + } + + return host; + }), + + ensureClient: protectedProcedure + .input( + z.object({ + machineId: z.string().min(1), + type: z.enum(v2ClientTypeValues), + }), + ) + .mutation(async ({ ctx, input }) => { + const organizationId = ctx.activeOrganizationId; + if (!organizationId) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "No active organization selected", + }); + } + + const userId = ctx.session.user.id; + + const [client] = await dbWs + .insert(v2Clients) + .values({ + organizationId, + userId, + machineId: input.machineId, + type: input.type, + }) + .onConflictDoUpdate({ + target: [ + v2Clients.organizationId, + v2Clients.userId, + v2Clients.machineId, + ], + set: { + type: input.type, + }, + }) + .returning(); + + if (!client) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to ensure client", + }); + } + + return client; + }), + + checkAccess: jwtProcedure + .input(z.object({ hostId: z.string().min(1) })) + .query(async ({ ctx, input }) => { + const parsed = parseHostRoutingKey(input.hostId); + if (!parsed) return { allowed: false }; + if (!ctx.organizationIds.includes(parsed.organizationId)) { + return { allowed: false }; + } + const row = await db.query.v2UsersHosts.findFirst({ + where: and( + eq(v2UsersHosts.userId, ctx.userId), + eq(v2UsersHosts.organizationId, parsed.organizationId), + eq(v2UsersHosts.hostId, parsed.machineId), + ), + columns: { hostId: true }, + }); + return { allowed: !!row }; + }), + + setOnline: jwtProcedure + .input(z.object({ hostId: z.string().min(1), isOnline: z.boolean() })) + .mutation(async ({ ctx, input }) => { + const parsed = parseHostRoutingKey(input.hostId); + if (!parsed) { + throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid hostId" }); + } + if (!ctx.organizationIds.includes(parsed.organizationId)) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "No access to this host", + }); + } + + const access = await db.query.v2UsersHosts.findFirst({ + where: and( + eq(v2UsersHosts.userId, ctx.userId), + eq(v2UsersHosts.organizationId, parsed.organizationId), + eq(v2UsersHosts.hostId, parsed.machineId), + ), + columns: { hostId: true }, + }); + if (!access) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "No access to this host", + }); + } + + await db + .update(v2Hosts) + .set({ isOnline: input.isOnline }) + .where( + and( + eq(v2Hosts.organizationId, parsed.organizationId), + eq(v2Hosts.machineId, parsed.machineId), + ), + ); + return { success: true }; + }), +} satisfies TRPCRouterRecord; diff --git a/packages/trpc/src/router/host/index.ts b/packages/trpc/src/router/host/index.ts new file mode 100644 index 00000000000..edc31cbb4d0 --- /dev/null +++ b/packages/trpc/src/router/host/index.ts @@ -0,0 +1 @@ +export { hostRouter } from "./host"; diff --git a/packages/trpc/src/router/support/support.ts b/packages/trpc/src/router/support/support.ts new file mode 100644 index 00000000000..b214e51b084 --- /dev/null +++ b/packages/trpc/src/router/support/support.ts @@ -0,0 +1,108 @@ +import { COMPANY } from "@superset/shared/constants"; +import { TRPCError } from "@trpc/server"; +import { Ratelimit } from "@upstash/ratelimit"; +import { Redis } from "@upstash/redis"; +import { Resend } from "resend"; +import { z } from "zod"; +import { env } from "../../env"; +import { createTRPCRouter, protectedProcedure } from "../../trpc"; + +// FORK NOTE: RESEND_API_KEY が未設定なら support report は無効化する。 +// fork は upstream の support@superset.sh にメール送信する立場ではないため、 +// 環境変数が無い時はランタイム 503 で no-op にして起動を阻害しない。 +const resend = env.RESEND_API_KEY ? new Resend(env.RESEND_API_KEY) : null; +const SUPPORT_EMAIL = COMPANY.MAIL_TO.replace(/^mailto:/, ""); +const supportReportRateLimit = + env.KV_REST_API_URL && env.KV_REST_API_TOKEN + ? new Ratelimit({ + redis: new Redis({ + url: env.KV_REST_API_URL, + token: env.KV_REST_API_TOKEN, + }), + limiter: Ratelimit.slidingWindow(3, "1 h"), + prefix: "ratelimit:support:migration-report", + }) + : null; + +async function assertSupportReportRateLimit({ + userId, + organizationId, +}: { + userId: string; + organizationId: string | null | undefined; +}) { + if (!supportReportRateLimit) { + if (env.NODE_ENV === "production") { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Support rate limiting is not configured", + }); + } + console.warn( + "[support/sendMigrationReport] rate limit skipped because KV is not configured", + ); + return; + } + + const { success } = await supportReportRateLimit.limit( + `${organizationId ?? "no-org"}:${userId}`, + ); + if (!success) { + throw new TRPCError({ + code: "TOO_MANY_REQUESTS", + message: "Too many support reports. Try again later.", + }); + } +} + +function sanitizeEmailBodyLine(value: string): string { + return value.replace(/[\r\n]+/g, " ").trim(); +} + +export const supportRouter = createTRPCRouter({ + sendMigrationReport: protectedProcedure + .input( + z.object({ + report: z.string().min(1).max(20_000), + }), + ) + .mutation(async ({ ctx, input }) => { + if (!resend) { + throw new TRPCError({ + code: "SERVICE_UNAVAILABLE", + message: "Support report is not configured (RESEND_API_KEY missing).", + }); + } + const organizationId = ctx.activeOrganizationId; + const user = ctx.session.user; + const safeName = user.name ? sanitizeEmailBodyLine(user.name) : ""; + const userLabel = safeName ? `${safeName} <${user.email}>` : user.email; + + await assertSupportReportRateLimit({ + userId: user.id, + organizationId, + }); + + try { + await resend.emails.send({ + from: "Superset ", + to: SUPPORT_EMAIL, + replyTo: user.email, + subject: "Superset V1 to V2 migration issue", + text: [ + `User: ${userLabel}`, + `User ID: ${user.id}`, + `Organization ID: ${organizationId ?? "none"}`, + "", + input.report, + ].join("\n"), + }); + } catch (error) { + console.error("[support/sendMigrationReport] failed", error); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to send migration report", + }); + } + }), +}); diff --git a/packages/trpc/src/router/v2-host/v2-host.ts b/packages/trpc/src/router/v2-host/v2-host.ts index b06f733d0e2..e71284eb6dd 100644 --- a/packages/trpc/src/router/v2-host/v2-host.ts +++ b/packages/trpc/src/router/v2-host/v2-host.ts @@ -10,15 +10,15 @@ import { requireActiveOrgId } from "../utils/active-org"; async function requireHostOwner( userId: string, - hostId: string, + machineId: string, organizationId: string, ) { const host = await db.query.v2Hosts.findFirst({ where: and( - eq(v2Hosts.id, hostId), eq(v2Hosts.organizationId, organizationId), + eq(v2Hosts.machineId, machineId), ), - columns: { id: true, organizationId: true, createdByUserId: true }, + columns: { machineId: true, organizationId: true, createdByUserId: true }, }); if (!host) { @@ -30,8 +30,9 @@ async function requireHostOwner( const access = await db.query.v2UsersHosts.findFirst({ where: and( - eq(v2UsersHosts.hostId, hostId), + eq(v2UsersHosts.organizationId, organizationId), eq(v2UsersHosts.userId, userId), + eq(v2UsersHosts.hostId, machineId), ), columns: { role: true }, }); @@ -67,8 +68,7 @@ export const v2HostRouter = { addMember: protectedProcedure .input( z.object({ - id: z.string().uuid(), - hostId: z.string().uuid(), + hostId: z.string().min(1), userId: z.string().uuid(), role: z.enum(v2UsersHostRoleValues).optional(), }), @@ -82,7 +82,6 @@ export const v2HostRouter = { const [inserted] = await tx .insert(v2UsersHosts) .values({ - id: input.id, organizationId, userId: input.userId, hostId: input.hostId, @@ -113,7 +112,7 @@ export const v2HostRouter = { removeMember: protectedProcedure .input( z.object({ - hostId: z.string().uuid(), + hostId: z.string().min(1), userId: z.string().uuid(), }), ) @@ -136,8 +135,9 @@ export const v2HostRouter = { const txid = await dbWs.transaction(async (tx) => { const target = await tx.query.v2UsersHosts.findFirst({ where: and( - eq(v2UsersHosts.hostId, input.hostId), + eq(v2UsersHosts.organizationId, organizationId), eq(v2UsersHosts.userId, input.userId), + eq(v2UsersHosts.hostId, input.hostId), ), columns: { role: true }, }); @@ -148,10 +148,11 @@ export const v2HostRouter = { if (target.role === "owner") { const otherOwners = await tx - .select({ id: v2UsersHosts.id }) + .select({ userId: v2UsersHosts.userId }) .from(v2UsersHosts) .where( and( + eq(v2UsersHosts.organizationId, organizationId), eq(v2UsersHosts.hostId, input.hostId), eq(v2UsersHosts.role, "owner"), ne(v2UsersHosts.userId, input.userId), @@ -170,8 +171,9 @@ export const v2HostRouter = { .delete(v2UsersHosts) .where( and( - eq(v2UsersHosts.hostId, input.hostId), + eq(v2UsersHosts.organizationId, organizationId), eq(v2UsersHosts.userId, input.userId), + eq(v2UsersHosts.hostId, input.hostId), ), ); return await getCurrentTxid(tx); @@ -183,7 +185,7 @@ export const v2HostRouter = { setMemberRole: protectedProcedure .input( z.object({ - hostId: z.string().uuid(), + hostId: z.string().min(1), userId: z.string().uuid(), role: z.enum(v2UsersHostRoleValues), }), @@ -207,8 +209,9 @@ export const v2HostRouter = { const txid = await dbWs.transaction(async (tx) => { const target = await tx.query.v2UsersHosts.findFirst({ where: and( - eq(v2UsersHosts.hostId, input.hostId), + eq(v2UsersHosts.organizationId, organizationId), eq(v2UsersHosts.userId, input.userId), + eq(v2UsersHosts.hostId, input.hostId), ), columns: { role: true }, }); @@ -222,10 +225,11 @@ export const v2HostRouter = { if (input.role === "member" && target.role === "owner") { const otherOwners = await tx - .select({ id: v2UsersHosts.id }) + .select({ userId: v2UsersHosts.userId }) .from(v2UsersHosts) .where( and( + eq(v2UsersHosts.organizationId, organizationId), eq(v2UsersHosts.hostId, input.hostId), eq(v2UsersHosts.role, "owner"), ne(v2UsersHosts.userId, input.userId), @@ -245,8 +249,9 @@ export const v2HostRouter = { .set({ role: input.role }) .where( and( - eq(v2UsersHosts.hostId, input.hostId), + eq(v2UsersHosts.organizationId, organizationId), eq(v2UsersHosts.userId, input.userId), + eq(v2UsersHosts.hostId, input.hostId), ), ); return await getCurrentTxid(tx); diff --git a/packages/trpc/src/router/v2-workspace/v2-workspace.ts b/packages/trpc/src/router/v2-workspace/v2-workspace.ts index e3ec02b8379..678ba3a3355 100644 --- a/packages/trpc/src/router/v2-workspace/v2-workspace.ts +++ b/packages/trpc/src/router/v2-workspace/v2-workspace.ts @@ -1,4 +1,5 @@ import { dbWs } from "@superset/db/client"; +import { v2WorkspaceTypeValues } from "@superset/db/enums"; import { v2Hosts, v2Projects, v2Workspaces } from "@superset/db/schema"; import { getCurrentTxid } from "@superset/db/utils"; import type { TRPCRouterRecord } from "@trpc/server"; @@ -12,6 +13,9 @@ import { requireOrgScopedResource, } from "../utils/org-resource-access"; +const MAIN_WORKSPACE_DELETE_MESSAGE = + "Main workspaces cannot be deleted through workspace delete. Remove them from the sidebar or remove the project from this host instead."; + async function getScopedProject(organizationId: string, projectId: string) { return requireOrgScopedResource( () => @@ -35,10 +39,13 @@ async function getScopedHost(organizationId: string, hostId: string) { () => dbWs.query.v2Hosts.findFirst({ columns: { - id: true, + machineId: true, organizationId: true, }, - where: eq(v2Hosts.id, hostId), + where: and( + eq(v2Hosts.organizationId, organizationId), + eq(v2Hosts.machineId, hostId), + ), }), { code: "BAD_REQUEST", @@ -102,7 +109,8 @@ export const v2WorkspaceRouter = { projectId: z.string().uuid(), name: z.string().min(1), branch: z.string().min(1), - hostId: z.string().uuid(), + hostId: z.string().min(1), + type: z.enum(v2WorkspaceTypeValues).default("worktree"), }), ) .mutation(async ({ ctx, input }) => { @@ -119,18 +127,86 @@ export const v2WorkspaceRouter = { ); const host = await getScopedHost(input.organizationId, input.hostId); - const [workspace] = await dbWs + // Relies on the partial unique index + // (project_id, host_id) WHERE type='main' for idempotency — race-safe + // even if two callers (e.g. the startup sweep and project.setup) both + // miss the existence check at the same instant. + const [inserted] = await dbWs .insert(v2Workspaces) .values({ organizationId: project.organizationId, projectId: project.id, name: input.name, branch: input.branch, - hostId: host.id, + hostId: host.machineId, + type: input.type, createdByUserId: ctx.userId, }) + .onConflictDoNothing() .returning(); - return workspace; + + if (inserted) return inserted; + + if (input.type === "main") { + const existing = await dbWs.query.v2Workspaces.findFirst({ + where: and( + eq(v2Workspaces.projectId, project.id), + eq(v2Workspaces.hostId, host.machineId), + eq(v2Workspaces.type, "main"), + ), + }); + if (existing) { + const patch: { + branch?: string; + name?: string; + } = {}; + if (existing.branch !== input.branch) { + patch.branch = input.branch; + if (existing.name === existing.branch) { + patch.name = input.name; + } + } + if (Object.keys(patch).length > 0) { + const [updated] = await dbWs + .update(v2Workspaces) + .set(patch) + .where(eq(v2Workspaces.id, existing.id)) + .returning(); + return updated ?? existing; + } + return existing; + } + } + + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: `Workspace insert returned no row (type=${input.type}, projectId=${project.id}, hostId=${host.machineId})`, + }); + }), + + getFromHost: jwtProcedure + .input( + z.object({ + organizationId: z.string().uuid(), + id: z.string().uuid(), + }), + ) + .query(async ({ ctx, input }) => { + if (!ctx.organizationIds.includes(input.organizationId)) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "Not a member of this organization", + }); + } + + return ( + (await dbWs.query.v2Workspaces.findFirst({ + where: and( + eq(v2Workspaces.id, input.id), + eq(v2Workspaces.organizationId, input.organizationId), + ), + })) ?? null + ); }), update: protectedProcedure @@ -139,7 +215,7 @@ export const v2WorkspaceRouter = { id: z.string().uuid(), name: z.string().min(1).optional(), branch: z.string().min(1).optional(), - hostId: z.string().uuid().optional(), + hostId: z.string().min(1).optional(), }), ) .mutation(async ({ ctx, input }) => { @@ -209,13 +285,11 @@ export const v2WorkspaceRouter = { }), ) .mutation(async ({ ctx, input }) => { + // FORK NOTE: full-row select so the return type aligns with + // `getFromHost` (HostWorkspace). Selecting only 4 columns made + // host-service `adopt.ts` type-fail when feeding `updatedCloud` + // into `adoptResult` after #3784 + #3779 landed together. const workspace = await dbWs.query.v2Workspaces.findFirst({ - columns: { - id: true, - organizationId: true, - name: true, - branch: true, - }, where: eq(v2Workspaces.id, input.id), }); if (!workspace) { @@ -277,7 +351,7 @@ export const v2WorkspaceRouter = { .input(z.object({ id: z.string().uuid() })) .mutation(async ({ ctx, input }) => { const workspace = await dbWs.query.v2Workspaces.findFirst({ - columns: { id: true, organizationId: true }, + columns: { id: true, organizationId: true, type: true }, where: eq(v2Workspaces.id, input.id), }); if (!workspace) { @@ -290,6 +364,52 @@ export const v2WorkspaceRouter = { message: "Not a member of this organization", }); } + if (workspace.type === "main") { + throw new TRPCError({ + code: "BAD_REQUEST", + message: MAIN_WORKSPACE_DELETE_MESSAGE, + }); + } + await dbWs.delete(v2Workspaces).where(eq(v2Workspaces.id, workspace.id)); + return { success: true, alreadyGone: false as const }; + }), + + // Main workspaces are not normal delete targets. This endpoint is reserved + // for host project removal, where the repo-root workspace must be detached + // from this host before the local project row disappears. + deleteMainForHost: jwtProcedure + .input(z.object({ id: z.string().uuid(), projectId: z.string().uuid() })) + .mutation(async ({ ctx, input }) => { + const workspace = await dbWs.query.v2Workspaces.findFirst({ + columns: { + id: true, + organizationId: true, + projectId: true, + type: true, + }, + where: eq(v2Workspaces.id, input.id), + }); + if (!workspace) { + return { success: true, alreadyGone: true as const }; + } + if (!ctx.organizationIds.includes(workspace.organizationId)) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "Not a member of this organization", + }); + } + if (workspace.projectId !== input.projectId) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Workspace does not belong to this project", + }); + } + if (workspace.type !== "main") { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Workspace is not a main workspace", + }); + } await dbWs.delete(v2Workspaces).where(eq(v2Workspaces.id, workspace.id)); return { success: true, alreadyGone: false as const }; }),