feat: implement ensure_publication for zero_publication management

- Added the ensure_publication function to create and verify the zero_publication if it is missing, ensuring idempotency during database initialization.
- Integrated ensure_publication into the create_db_and_tables function to prevent zero-cache crash loops on startup.
- Introduced a self-check script to validate the ensure_publication functionality on a create_all-bootstrapped database.
- Updated various components to reflect the transition from search space to workspace, including adjustments in imports and routing paths.
This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-07-05 23:17:13 -07:00
parent 7562bc78ee
commit a64c8205fe
164 changed files with 626 additions and 506 deletions

View file

@ -156,10 +156,7 @@ export function AutomationBuilderForm({
if (mode === "edit" && automation) {
return { ...buildUpdatePayload(formForPayload), status: automation.status };
}
const { workspace_id: _ignored, ...rest } = buildCreatePayload(
formForPayload,
workspaceId
);
const { workspace_id: _ignored, ...rest } = buildCreatePayload(formForPayload, workspaceId);
return rest;
}

View file

@ -51,7 +51,7 @@ export function AutomationModelFields({
errors,
}: AutomationModelFieldsProps) {
const { llm, image, vision, isLoading } = useAutomationEligibleModels();
const rolesHref = `/dashboard/${workspaceId}/search-space-settings/models`;
const rolesHref = `/dashboard/${workspaceId}/workspace-settings/models`;
return (
<div className="flex flex-col gap-4">

View file

@ -13,7 +13,7 @@ import {
modelConnectionsAtom,
modelRolesAtom,
} from "@/atoms/model-connections/model-connections-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { DocumentUploadDialogProvider } from "@/components/assistant-ui/document-upload-popup";
import { LayoutDataProvider } from "@/components/layout";
import { OnboardingTour } from "@/components/onboarding-tour";

View file

@ -1,10 +1,6 @@
import { TeamContent } from "./team-content";
export default async function TeamPage({
params,
}: {
params: Promise<{ workspace_id: string }>;
}) {
export default async function TeamPage({ params }: { params: Promise<{ workspace_id: string }> }) {
const { workspace_id } = await params;
return (

View file

@ -599,7 +599,7 @@ function MemberRow({
<DropdownMenuSeparator className="bg-popover-border" />
<DropdownMenuItem
onClick={() =>
router.push(`/dashboard/${workspaceId}/search-space-settings/team-roles`)
router.push(`/dashboard/${workspaceId}/workspace-settings/team-roles`)
}
>
Manage Roles

View file

@ -6,7 +6,7 @@ import { AlertTriangle, Info, ShieldCheck, Trash2 } from "lucide-react";
import { useCallback, useMemo, useState } from "react";
import { toast } from "sonner";
import { agentFlagsAtom } from "@/atoms/agent/agent-flags-query.atom";
import { activeWorkspaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { activeWorkspaceIdAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import {
AlertDialog,

View file

@ -13,9 +13,9 @@ import {
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import { Switch } from "@/components/ui/switch";
import type { SearchSpace } from "@/contracts/types/search-space.types";
import type { SearchSpace } from "@/contracts/types/workspace.types";
import { useElectronAPI } from "@/hooks/use-platform";
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
import { searchSpacesApiService } from "@/lib/apis/workspaces-api.service";
export function DesktopContent() {
const api = useElectronAPI();

View file

@ -17,8 +17,8 @@ import {
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import type { SearchSpace } from "@/contracts/types/search-space.types";
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
import type { SearchSpace } from "@/contracts/types/workspace.types";
import { searchSpacesApiService } from "@/lib/apis/workspaces-api.service";
import { authenticatedFetch } from "@/lib/auth-fetch";
import { buildBackendUrl } from "@/lib/env-config";
import { cn } from "@/lib/utils";

View file

@ -11,7 +11,5 @@ export default function UserSettingsLayout({
}) {
const { workspace_id } = use(params);
return (
<UserSettingsLayoutShell workspaceId={workspace_id}>{children}</UserSettingsLayoutShell>
);
return <UserSettingsLayoutShell workspaceId={workspace_id}>{children}</UserSettingsLayoutShell>;
}

View file

@ -76,7 +76,7 @@ export function SearchSpaceSettingsLayoutShell({
const selectedLabel = navItems.find((item) => item.value === activeTab)?.label ?? t("title");
const hrefFor = (tab: SearchSpaceSettingsTab) =>
`/dashboard/${workspaceId}/search-space-settings/${tab}`;
`/dashboard/${workspaceId}/workspace-settings/${tab}`;
return (
<section className="flex h-full min-h-[min(680px,calc(100vh-5rem))] w-full select-none flex-col gap-6 md:pt-6 md:flex-row">

View file

@ -6,5 +6,5 @@ export default async function SearchSpaceSettingsPage({
params: Promise<{ workspace_id: string }>;
}) {
const { workspace_id } = await params;
redirect(`/dashboard/${workspace_id}/search-space-settings/general`);
redirect(`/dashboard/${workspace_id}/workspace-settings/general`);
}

View file

@ -6,7 +6,7 @@ import { motion } from "motion/react";
import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
import { searchSpacesAtom } from "@/atoms/search-spaces/search-space-query.atoms";
import { searchSpacesAtom } from "@/atoms/workspaces/workspace-query.atoms";
import { CreateSearchSpaceDialog } from "@/components/layout";
import { Button } from "@/components/ui/button";
import { Card, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";

View file

@ -14,7 +14,7 @@ import { Separator } from "@/components/ui/separator";
import { ShortcutKbd } from "@/components/ui/shortcut-kbd";
import { Spinner } from "@/components/ui/spinner";
import { useElectronAPI } from "@/hooks/use-platform";
import { searchSpacesApiService } from "@/lib/apis/search-spaces-api.service";
import { searchSpacesApiService } from "@/lib/apis/workspaces-api.service";
import { getPostLoginRedirectPath } from "@/lib/auth-utils";
type ShortcutKey = "generalAssist" | "quickAsk" | "screenshotAssist";

View file

@ -98,15 +98,11 @@ export default function InviteAcceptPage() {
// Track invite accepted and user added events
trackSearchSpaceInviteAccepted(
result.search_space_id,
result.search_space_name,
result.role_name
);
trackSearchSpaceUserAdded(
result.search_space_id,
result.search_space_name,
result.workspace_id,
result.workspace_name,
result.role_name
);
trackSearchSpaceUserAdded(result.workspace_id, result.workspace_name, result.role_name);
}
} catch (err: any) {
setError(err.message || "Failed to accept invite");
@ -117,7 +113,7 @@ export default function InviteAcceptPage() {
const handleDecline = () => {
// Track invite declined event
trackSearchSpaceInviteDeclined(inviteInfo?.search_space_name);
trackSearchSpaceInviteDeclined(inviteInfo?.workspace_name);
router.push("/dashboard");
};
@ -180,7 +176,7 @@ export default function InviteAcceptPage() {
</motion.div>
<CardTitle className="text-2xl">Welcome to the team!</CardTitle>
<CardDescription>
You've successfully joined {acceptedData.search_space_name}
You've successfully joined {acceptedData.workspace_name}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
@ -190,7 +186,7 @@ export default function InviteAcceptPage() {
<Users className="h-5 w-5 text-primary" />
</div>
<div>
<p className="font-medium">{acceptedData.search_space_name}</p>
<p className="font-medium">{acceptedData.workspace_name}</p>
<p className="text-sm text-muted-foreground">Search Space</p>
</div>
</div>
@ -208,7 +204,7 @@ export default function InviteAcceptPage() {
<CardFooter>
<Button
className="w-full gap-2"
onClick={() => router.push(`/dashboard/${acceptedData.search_space_id}`)}
onClick={() => router.push(`/dashboard/${acceptedData.workspace_id}`)}
>
Go to Search Space
<ArrowRight className="h-4 w-4" />
@ -260,7 +256,7 @@ export default function InviteAcceptPage() {
</motion.div>
<CardTitle className="text-2xl">You're Invited!</CardTitle>
<CardDescription>
Sign in to join {inviteInfo?.search_space_name || "this search space"}
Sign in to join {inviteInfo?.workspace_name || "this search space"}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
@ -270,7 +266,7 @@ export default function InviteAcceptPage() {
<Users className="h-5 w-5 text-primary" />
</div>
<div>
<p className="font-medium">{inviteInfo?.search_space_name}</p>
<p className="font-medium">{inviteInfo?.workspace_name}</p>
<p className="text-sm text-muted-foreground">Search Space</p>
</div>
</div>
@ -307,7 +303,7 @@ export default function InviteAcceptPage() {
</motion.div>
<CardTitle className="text-2xl">You're Invited!</CardTitle>
<CardDescription>
Accept this invite to join {inviteInfo?.search_space_name || "this search space"}
Accept this invite to join {inviteInfo?.workspace_name || "this search space"}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
@ -317,7 +313,7 @@ export default function InviteAcceptPage() {
<Users className="h-5 w-5 text-primary" />
</div>
<div>
<p className="font-medium">{inviteInfo?.search_space_name}</p>
<p className="font-medium">{inviteInfo?.workspace_name}</p>
<p className="text-sm text-muted-foreground">Search Space</p>
</div>
</div>