diff --git a/apps/builder/app/builder/features/topbar/add-domain.tsx b/apps/builder/app/builder/features/topbar/add-domain.tsx index 8a87a750e243..269c547e3c89 100644 --- a/apps/builder/app/builder/features/topbar/add-domain.tsx +++ b/apps/builder/app/builder/features/topbar/add-domain.tsx @@ -7,46 +7,38 @@ import { theme, Text, Grid, + toast, } from "@webstudio-is/design-system"; import { validateDomain } from "@webstudio-is/domain"; import type { Project } from "@webstudio-is/project"; -import { useId, useState } from "react"; +import { useId, useOptimistic, useRef, useState } from "react"; import { CustomCodeIcon } from "@webstudio-is/icons"; -import { trpcClient } from "~/shared/trpc/trpc-client"; +import { nativeClient } from "~/shared/trpc/trpc-client"; type DomainsAddProps = { projectId: Project["id"]; onCreate: (domain: string) => void; onExportClick: () => void; - refreshDomainResult: ( - input: { projectId: Project["id"] }, - onSuccess: () => void - ) => void; - domainState: "idle" | "submitting"; - isPublishing: boolean; + refresh: () => Promise; }; export const AddDomain = ({ projectId, onCreate, - refreshDomainResult, - domainState, - isPublishing, + refresh, onExportClick, }: DomainsAddProps) => { const id = useId(); - const { - send: create, - state: сreateState, - error: сreateSystemError, - } = trpcClient.domain.create.useMutation(); const [isOpen, setIsOpen] = useState(false); - const [domain, setDomain] = useState(""); const [error, setError] = useState(); + const buttonRef = useRef(null); + const [isPending, setIsPendingOptimistic] = useOptimistic(false); - const handleCreate = () => { - setError(undefined); + const handleCreateDomain = async (formData: FormData) => { + // Will be automatically reset on action end + setIsPendingOptimistic(true); + const domain = formData.get("domain")?.toString() ?? ""; const validationResult = validateDomain(domain); if (validationResult.success === false) { @@ -54,18 +46,22 @@ export const AddDomain = ({ return; } - create({ domain: validationResult.domain, projectId }, (data) => { - if (data.success === false) { - setError(data.error); - return; - } - - refreshDomainResult({ projectId }, () => { - setDomain(""); - setIsOpen(false); - onCreate(validationResult.domain); - }); + const result = await nativeClient.domain.create.mutate({ + domain, + projectId, }); + + if (result.success === false) { + toast.error(result.error); + setError(result.error); + return; + } + + onCreate(domain); + + await refresh(); + + setIsOpen(false); }; return ( @@ -81,7 +77,6 @@ export const AddDomain = ({ direction={"column"} onKeyDown={(event) => { if (event.key === "Escape") { - setDomain(""); setIsOpen(false); event.preventDefault(); } @@ -94,26 +89,21 @@ export const AddDomain = ({ { if (event.key === "Enter") { - handleCreate(); + buttonRef.current + ?.closest("form") + ?.requestSubmit(buttonRef.current); } if (event.key === "Escape") { - setDomain(""); setIsOpen(false); event.preventDefault(); } }} - onChange={(event) => { - setError(undefined); - setDomain(event.target.value); - }} color={error !== undefined ? "error" : undefined} /> {error !== undefined && ( @@ -121,29 +111,21 @@ export const AddDomain = ({ {error} )} - {сreateSystemError !== undefined && ( - <> - {/* Something happened with network, api etc */} - {сreateSystemError} - Please try again later - - )} )} @@ -425,40 +373,25 @@ const DomainItem = (props: { {status !== "UNVERIFIED" && ( <> - {updateStatusData?.success === false && ( - {updateStatusData.error} - )} - {updateStatusError !== undefined && ( + {updateStatusError && ( {updateStatusError} )} )} - {removeData?.success === false && ( - {removeData.error} - )} - - {removeSystemError !== undefined && ( - {removeSystemError} - )} - @@ -467,11 +400,11 @@ const DomainItem = (props: { {status === "UNVERIFIED" && ( <> - {verifyData?.success === false ? ( + {verifyError ? ( Status: Failed to verify
- {verifyData.error} + {verifyError}
) : ( <> @@ -558,18 +491,21 @@ const DomainItem = (props: { { // Sometimes Entri modal dialog hangs even if it's successful, // until they fix that, we'll just refresh the status here on every onClose event if (status === "UNVERIFIED") { - handleVerify(); + startTransition(async () => { + await handleVerify(); + await handleUpdateStatus(); + }); return; } - - handleUpdateStatus(); + startTransition(async () => { + await handleUpdateStatus(); + }); }} - isPublishing={props.isPublishing} />
@@ -579,20 +515,15 @@ const DomainItem = (props: { type DomainsProps = { newDomains: Set; domains: Domain[]; - refreshDomainResult: ( - input: { projectId: Project["id"] }, - onSuccess?: () => void - ) => void; - domainState: "idle" | "submitting"; - isPublishing: boolean; + refresh: () => Promise; + project: Project; }; export const Domains = ({ newDomains, domains, - refreshDomainResult, - domainState, - isPublishing, + refresh, + project, }: DomainsProps) => { return ( <> @@ -601,9 +532,8 @@ export const Domains = ({ key={projectDomain.domain} projectDomain={projectDomain} initiallyOpen={newDomains.has(projectDomain.domain)} - refreshDomainResult={refreshDomainResult} - domainState={domainState} - isPublishing={isPublishing} + refresh={refresh} + project={project} /> ))} diff --git a/apps/builder/app/builder/features/topbar/entri.tsx b/apps/builder/app/builder/features/topbar/entri.tsx index c57824cca00b..cffbf3c01f5f 100644 --- a/apps/builder/app/builder/features/topbar/entri.tsx +++ b/apps/builder/app/builder/features/topbar/entri.tsx @@ -10,12 +10,10 @@ export const Entri = ({ dnsRecords, domain, onClose, - isPublishing, }: { dnsRecords: DnsRecord[]; domain: string; onClose: (detail: EntriCloseDetail) => void; - isPublishing: boolean; }) => { entriGlobalStyles(); const { error, isOpen, showDialog } = useEntri({ @@ -29,9 +27,10 @@ export const Entri = ({ {error !== undefined && {error}} @@ -386,33 +395,6 @@ const getStaticPublishStatusAndText = ({ return { statusText, status }; }; -const fetchProjectDataStatus = async (projectId: Project["id"]) => { - const projectData = await nativeClient.domain.project.query({ - projectId, - }); - - if (projectData.success === false) { - throw new Error(projectData.error); - } - - if (projectData.project.latestStaticBuild == null) { - return { - status: "LOADED" as const, - statusText: "Not published", - }; - } - - const { status, statusText } = getStaticPublishStatusAndText( - projectData.project.latestStaticBuild - ); - - return { status, statusText }; -}; - -type StaticProjectStatus = - | Awaited> - | { status: "INITIAL"; statusText: string }; - const PublishStatic = ({ projectId, templates, @@ -420,58 +402,38 @@ const PublishStatic = ({ projectId: Project["id"]; templates: readonly Templates[]; }) => { + const project = useStore($project); const [_, startTransition] = useTransition(); - const [projectDataStatus, setProjectDataStatus] = - useState({ - status: "INITIAL", - statusText: "Loading", - }); + if (project == null) { + throw new Error("Project not found"); + } - useEffect(() => { - startTransition(async () => { - try { - const projectDataStatus = await fetchProjectDataStatus(projectId); - setProjectDataStatus(projectDataStatus); - } catch (error) { - setProjectDataStatus({ - status: "FAILED", - statusText: error instanceof Error ? error.message : "Unknown error", - }); - } - }); - }, [projectId]); + const { status, statusText } = + project.latestStaticBuild == null + ? { status: "LOADED" as const, statusText: "Not published" } + : getStaticPublishStatusAndText(project.latestStaticBuild); - const [optimisticPendingStatus, setOptimisticPendingStatus] = useOptimistic( - projectDataStatus, - (_currentState, optimisticValue: StaticProjectStatus) => { - return optimisticValue; - } - ); + const [isPending, setIsPendingOptimistic] = useOptimistic(false); const isPublishInProgress = - optimisticPendingStatus.status === "PENDING" || - optimisticPendingStatus.status === "INITIAL"; + project.latestStaticBuild?.publishStatus === "PENDING" || isPending; return ( - {optimisticPendingStatus.status === "FAILED" && ( - {optimisticPendingStatus.statusText} - )} + {status === "FAILED" && {statusText}} diff --git a/apps/builder/app/shared/db/canvas.server.ts b/apps/builder/app/shared/db/canvas.server.ts index 836e556f7524..f98ede7183d7 100644 --- a/apps/builder/app/shared/db/canvas.server.ts +++ b/apps/builder/app/shared/db/canvas.server.ts @@ -34,13 +34,15 @@ export const loadProductionCanvasData = async ( // Check that build deployment domains are still active and verified // for examle: redeploy created few days later if (deployment.destination !== "static") { - deployment.domains = deployment.domains.filter((domain) => - currentProjectDomains.some( - (projectDomain) => - projectDomain.domain === domain && - projectDomain.status === "ACTIVE" && - projectDomain.verified - ) + deployment.domains = deployment.domains.filter( + (domain) => + project.domain === domain || + currentProjectDomains.some( + (projectDomain) => + projectDomain.domain === domain && + projectDomain.status === "ACTIVE" && + projectDomain.verified + ) ); } diff --git a/packages/cli/src/prebuild.ts b/packages/cli/src/prebuild.ts index 4d4facffb530..815baf721c80 100644 --- a/packages/cli/src/prebuild.ts +++ b/packages/cli/src/prebuild.ts @@ -430,23 +430,25 @@ export const prebuild = async (options: { const assetsToDownload: Promise[] = []; - const appDomain = options.preview ? "wstd.work" : "wstd.io"; - - const domain = - siteData.build.deployment?.destination === "static" - ? siteData.build.deployment?.assetsDomain - : siteData.build.deployment?.projectDomain; - if (domain === undefined) { - throw new Error(`Project domain is missing from the project data`); - } + if (options.assets === true) { + const appDomain = options.preview ? "wstd.work" : "wstd.io"; + const domain = + siteData.build.deployment?.assetsDomain ?? + // fallback to project domain should not be used since 2025-01-01 (for now is used for backward compatibility) + (siteData.build.deployment?.destination !== "static" + ? siteData.build.deployment?.projectDomain + : undefined); + + if (domain === undefined) { + throw new Error(`Project domain is missing from the project data`); + } - const assetBuildUrl = `https://${domain}.${appDomain}/cgi/asset/`; + const assetBuildUrl = `https://${domain}.${appDomain}/cgi/asset/`; - const imageLoader = createImageLoader({ - imageBaseUrl: assetBuildUrl, - }); + const imageLoader = createImageLoader({ + imageBaseUrl: assetBuildUrl, + }); - if (options.assets === true) { for (const asset of siteData.assets) { if (asset.type === "image") { const imageSrc = imageLoader({ diff --git a/packages/design-system/src/components/checkbox.stories.tsx b/packages/design-system/src/components/checkbox.stories.tsx index 3072af99c1af..f1d58b99369a 100644 --- a/packages/design-system/src/components/checkbox.stories.tsx +++ b/packages/design-system/src/components/checkbox.stories.tsx @@ -1,6 +1,7 @@ import { Label } from "./label"; import { Checkbox, CheckboxAndLabel } from "./checkbox"; import { StorySection, StoryGrid } from "./storybook"; +import { Tooltip } from "./tooltip"; export default { title: "Library/Checkbox", @@ -45,6 +46,15 @@ export const Demo = () => { + + + + + + + + + ); }; diff --git a/packages/design-system/src/components/checkbox.tsx b/packages/design-system/src/components/checkbox.tsx index 159aa5edd43c..ebeb1b405584 100644 --- a/packages/design-system/src/components/checkbox.tsx +++ b/packages/design-system/src/components/checkbox.tsx @@ -56,24 +56,49 @@ const iconByState = { indeterminate: CheckboxMixedFilledIcon, }; +type ButtonProps = ComponentProps<"button"> & + Pick, "aria-checked">; + +type AriaChecked = ComponentProps["aria-checked"]; + +const ariaCheckedToDataState = ( + ariaChecked: AriaChecked +): keyof typeof iconByState => { + if (ariaChecked === "true" || ariaChecked === true) { + return "checked"; + } + if (ariaChecked === "false" || ariaChecked === false) { + return "unchecked"; + } + + if (ariaChecked === "mixed" || ariaChecked === undefined) { + return "indeterminate"; + } + + ariaChecked satisfies never; + return "indeterminate"; +}; + // We need this component basicslly just to get access to "data-state". // We could render all icons and hide one using CSS, // but that probably will be less performant. -const Button = forwardRef( - ( - props: ComponentProps<"button"> & { - "data-state"?: "checked" | "unchecked" | "indeterminate"; - }, - ref: Ref - ) => { - const Icon = iconByState[props["data-state"] ?? "unchecked"]; - return ( - - ); - } -); +const Button = forwardRef((props: ButtonProps, ref: Ref) => { + // Using aria-checked instead of data-state ensures compatibility with Tooltip, + // as Tooltip overrides the Checkbox's data-state attribute. + const dataState = ariaCheckedToDataState(props["aria-checked"]); + const Icon = iconByState[dataState]; + + return ( + + ); +}); Button.displayName = "Button"; export const Checkbox = forwardRef( diff --git a/packages/design-system/src/components/section-title.tsx b/packages/design-system/src/components/section-title.tsx index 38a6f430da14..864ef5d195af 100644 --- a/packages/design-system/src/components/section-title.tsx +++ b/packages/design-system/src/components/section-title.tsx @@ -32,6 +32,9 @@ const containerStyle = css({ height: theme.spacing[15], [buttonContentColor]: theme.colors.foregroundIconMain, [labelTextColor]: theme.colors.foregroundMain, + "&:hover": { + [chevronOpacity]: 1, + }, }); const titleButtonLayoutStyle = css({ @@ -54,9 +57,6 @@ const labelContainerStyle = css({ const titleButtonStyle = css(titleButtonLayoutStyle, { "&:focus-visible": focusRingStyle(), - "&:hover": { - [chevronOpacity]: 1, - }, }); const suffixSlotStyle = css({ diff --git a/packages/domain/src/trpc/domain.ts b/packages/domain/src/trpc/domain.ts index c2f4bfd5d963..88eebe04f6ed 100644 --- a/packages/domain/src/trpc/domain.ts +++ b/packages/domain/src/trpc/domain.ts @@ -62,19 +62,32 @@ export const domainRouter = router({ const name = `${project.id}-${nanoid()}.zip`; - let domains: string[] = []; + const domains: string[] = []; + + let hasCustomDomain = false; if (input.destination === "saas") { const currentProjectDomains = project.domainsVirtual; - domains = input.domains.filter((domain) => - currentProjectDomains.some( - (projectDomain) => - projectDomain.domain === domain && - projectDomain.status === "ACTIVE" && - projectDomain.verified + if (input.domains.includes(project.domain)) { + domains.push(project.domain); + } + + domains.push( + ...input.domains.filter((domain) => + currentProjectDomains.some( + (projectDomain) => + projectDomain.domain === domain && + projectDomain.status === "ACTIVE" && + projectDomain.verified + ) ) ); + + hasCustomDomain = currentProjectDomains.some( + (projectDomain) => + projectDomain.status === "ACTIVE" && projectDomain.verified + ); } const build = await createProductionBuild( @@ -85,7 +98,8 @@ export const domainRouter = router({ ? { destination: input.destination, domains: domains, - projectDomain: project.domain, + assetsDomain: project.domain, + excludeWstdDomainFromSearch: hasCustomDomain, } : { destination: input.destination, @@ -114,7 +128,7 @@ export const domainRouter = router({ branchName: env.GITHUB_REF_NAME, destination: input.destination, // action log helper (not used for deployment, but for action logs readablity) - projectDomainName: project.domain, + logProjectName: `${project.title} - ${project.id}`, }); if (input.destination === "static" && result.success) { diff --git a/packages/postgrest/supabase/tests/cleanup-builds.sql b/packages/postgrest/supabase/tests/cleanup-builds.sql new file mode 100644 index 000000000000..d2a0c90b6501 --- /dev/null +++ b/packages/postgrest/supabase/tests/cleanup-builds.sql @@ -0,0 +1,227 @@ +BEGIN; +SET LOCAL search_path = pgtap, public; +-- Initialize the testing environment without planning any specific number of tests +-- We are using SELECT no_plan() because we don't specify the exact number of tests upfront. +SELECT no_plan(); + +-- ========================================= +-- Setup: Insert initial data for Users, Projects, Domains, ProjectDomains, and Builds +-- ========================================= + +-- Insert a user into the "User" table +INSERT INTO "public"."User" ("id", "createdAt", "email", "username") +VALUES + ('user1', '2023-01-01 00:00:00+00', 'user1@517cce32-9af3-example.com', 'user1'); + +-- Insert projects associated with the user into the "Project" table +INSERT INTO "public"."Project" ("id", "title", "domain", "userId", "isDeleted", "createdAt") +VALUES + ('project1', 'Project One', '517cce32-9af3-project1-domain1', 'user1', false, '2023-01-01 00:00:00+00'), + ('project2', 'Project Two', '517cce32-9af3-project2-domain1', 'user1', false, '2023-01-01 00:00:00+00'); + +-- Insert custom domains into the "Domain" table +INSERT INTO "public"."Domain" ("id", "domain", "createdAt", "status", "updatedAt") +VALUES + ('project-1-custom-domain-1', '517cce32-9af3-project-1-custom-domain-1.com', '2023-01-01 00:00:00+00', 'INITIALIZING', '2023-01-01 00:00:00+00'), + ('project-1-custom-domain-2', '517cce32-9af3-project-1-custom-domain-2.com', '2023-01-01 00:00:00+00', 'INITIALIZING', '2023-01-01 00:00:00+00'), + ('project-2-custom-domain-1', '517cce32-9af3-project-2-custom-domain-1.com', '2023-01-01 00:00:00+00', 'INITIALIZING', '2023-01-01 00:00:00+00'), + ('project-2-custom-domain-2', '517cce32-9af3-project-2-custom-domain-2.com', '2023-01-01 00:00:00+00', 'INITIALIZING', '2023-01-01 00:00:00+00'); + +-- Establish relationships between projects and custom domains in the "ProjectDomain" table +INSERT INTO "public"."ProjectDomain" ("projectId", "domainId", "createdAt", "txtRecord", "cname") +VALUES + ('project1', 'project-1-custom-domain-1', '2023-01-01 00:00:00+00', 'txtRecord1', 'cname1'), + ('project1', 'project-1-custom-domain-2', '2023-01-01 00:00:00+00', 'txtRecord2', 'cname2'), + ('project2', 'project-2-custom-domain-1', '2023-01-01 00:00:00+00', 'p2-txtRecord1', 'cname1'), + ('project2', 'project-2-custom-domain-2', '2023-01-01 00:00:00+00', 'p2-txtRecord2', 'cname2'); + +-- Insert initial builds into the "Build" table +INSERT INTO "public"."Build" ( + "id", + "createdAt", + "pages", + "projectId", + "deployment", + "updatedAt", + "publishStatus" +) +VALUES + -- Development Build for Project1 + ( + 'build1-development', + '1990-01-01 00:00:00+00', + 'home', + 'project1', + NULL, + '1990-01-01 00:00:00+00', + 'PENDING' + ), + -- Development Build for Project2 + ( + 'project2-build1-development', + '1990-01-01 00:00:00+00', + 'home', + 'project2', + NULL, + '1990-01-01 00:00:00+00', + 'PENDING' + ), + -- Custom Domain Build for Project2 + ( + 'project2-build1-for-custom-domain-1', + '1990-01-02 00:00:00+00', + 'home', + 'project2', + '{"domains": ["517cce32-9af3-project-2-custom-domain-1.com"]}'::text, + '1990-01-02 00:00:00+00', + 'PUBLISHED' + ), + -- Project Domain Build for Project2 + ( + 'project2-build1-for-project-domain-1', + '1990-01-01 00:00:00+00', + 'home', + 'project2', + '{"domains": ["517cce32-9af3-project2-domain1"]}'::text, + '1990-01-01 00:00:00+00', + 'PUBLISHED' + ), + -- Custom Domain Build for Project1 + ( + 'build1-for-custom-domain-1', + '1990-01-02 00:00:00+00', + 'home', + 'project1', + '{"domains": ["517cce32-9af3-project-1-custom-domain-1.com"]}'::text, + '1990-01-02 00:00:00+00', + 'PUBLISHED' + ), + -- Project Domain Build for Project1 + ( + 'build1-for-project-domain-1', + '1990-01-01 00:00:00+00', + 'home', + 'project1', + '{"domains": ["517cce32-9af3-project1-domain1"]}'::text, + '1990-01-01 00:00:00+00', + 'PUBLISHED' + ); + +-- ========================================= +-- Test 1: Verify that initial cleanup does not clean any builds +-- ========================================= + +-- Run the database cleanup function for builds created in 1990 +SELECT database_cleanup('1990-01-01 00:00:00', '1990-12-31 23:59:59'); + +-- Assert that no builds have been cleaned up initially +SELECT is( + (SELECT count(*)::integer FROM "Build" WHERE "createdAt" BETWEEN '1990-01-01' AND '1990-12-31' AND "isCleaned" = TRUE), + 0, + 'Test 1: No builds should be cleaned up on initial cleanup' +); + +-- ========================================= +-- Test 2: Insert a new project domain build and verify cleanup +-- ========================================= + +-- Insert a new build for the project domain +INSERT INTO "public"."Build" ( + "id", + "createdAt", + "pages", + "projectId", + "deployment", + "updatedAt", + "publishStatus" +) +VALUES + -- New Project Domain Build for Project1 + ( + 'build2-for-project-domain-1', + '1990-01-02 00:00:00+00', -- A later date than the previous build + 'home', + 'project1', + '{"domains": ["517cce32-9af3-project1-domain1"]}'::text, + '1990-01-02 00:00:00+00', + 'PUBLISHED' + ); + +-- Run the database cleanup function again +SELECT database_cleanup('1990-01-01 00:00:00', '1990-12-31 23:59:59'); + +-- Assert that one build has been cleaned (the previous project domain build) +SELECT is( + (SELECT count(*)::integer FROM "Build" WHERE "createdAt" BETWEEN '1990-01-01' AND '1990-12-31' AND "isCleaned" = TRUE), + 1, + 'Test 2: Previous project domain build should be cleaned up after new build is published' +); + +-- Assert that the specific build cleaned is 'build1-for-project-domain-1' +SELECT is( + (SELECT id FROM "Build" WHERE "createdAt" BETWEEN '1990-01-01' AND '1990-12-31' AND "isCleaned" = TRUE), + 'build1-for-project-domain-1', + 'Test 2: Build "build1-for-project-domain-1" should be marked as cleaned' +); + +-- ========================================= +-- Test 3: Insert a new custom domain build and verify cleanup +-- ========================================= + +-- Insert a new build for the custom domain +INSERT INTO "public"."Build" ( + "id", + "createdAt", + "pages", + "projectId", + "deployment", + "updatedAt", + "publishStatus" +) +VALUES + -- New Custom Domain Build for Project1 + ( + 'build2-for-custom-domain-1', + '1990-01-03 00:00:00+00', -- A later date than the previous build + 'home', + 'project1', + '{"domains": ["517cce32-9af3-project-1-custom-domain-1.com"]}'::text, + '1990-01-03 00:00:00+00', + 'PUBLISHED' + ); + +-- Run the database cleanup function again +SELECT database_cleanup('1990-01-01 00:00:00', '1990-12-31 23:59:59'); + +-- Assert that two builds have been cleaned (the previous project domain and custom domain builds) +SELECT is( + (SELECT count(*)::integer FROM "Build" WHERE "createdAt" BETWEEN '1990-01-01' AND '1990-12-31' AND "isCleaned" = TRUE), + 2, + 'Test 3: Previous custom domain build should be cleaned up after new build is published' +); + +-- Assert that the builds cleaned are 'build1-for-custom-domain-1' and 'build1-for-project-domain-1' +SELECT results_eq( + $$ + SELECT id FROM "Build" WHERE "createdAt" BETWEEN '1990-01-01' AND '1990-12-31' AND "isCleaned" = TRUE ORDER BY id + $$, + $$ + SELECT * FROM ( + VALUES + ('build1-for-custom-domain-1'), + ('build1-for-project-domain-1') + ) AS expected(id) + ORDER BY "id" + $$, + 'Test 3: Builds "build1-for-custom-domain-1" and "build1-for-project-domain-1" should be marked as cleaned' +); + +-- ========================================= +-- Finish the test +-- ========================================= + +-- Finish the test by calling the finish() function, which outputs the test summary +SELECT finish(); + +-- Rollback the transaction to ensure no changes are persisted in the database +ROLLBACK; diff --git a/packages/postgrest/supabase/tests/latest-builds-domains.sql b/packages/postgrest/supabase/tests/latest-builds-domains.sql index 82e1d79aa9ae..afcb3a58423f 100644 --- a/packages/postgrest/supabase/tests/latest-builds-domains.sql +++ b/packages/postgrest/supabase/tests/latest-builds-domains.sql @@ -9,19 +9,19 @@ SELECT no_plan(); -- Insert a new user into the User table INSERT INTO "public"."User" ("id", "createdAt", "email", "username") VALUES - ('user1', '2023-01-01 00:00:00+00', 'user1@example.com', 'user1'); + ('user1', '2023-01-01 00:00:00+00', 'user1@517cce32-9af3-example.com', 'user1'); -- Insert projects associated with the user INSERT INTO "public"."Project" ("id", "title", "domain", "userId", "isDeleted", "createdAt") VALUES - ('project1', 'Project One', 'project1-domain1', 'user1', false, '2023-01-01 00:00:00+00'), - ('project2', 'Project Two', 'project2-domain1', 'user1', false, '2023-01-01 00:00:00+00'); + ('project1', 'Project One', '517cce32-9af3-project1-domain1', 'user1', false, '2023-01-01 00:00:00+00'), + ('project2', 'Project Two', '517cce32-9af3-project2-domain1', 'user1', false, '2023-01-01 00:00:00+00'); -- Insert custom domains into the Domain table INSERT INTO "public"."Domain" ("id", "domain", "createdAt", "status", "updatedAt") VALUES - ('project-1-custom-domain-1', 'project-1-custom-domain-1.com', '2023-01-01 00:00:00+00', 'INITIALIZING', '2023-01-01 00:00:00+00'), - ('project-1-custom-domain-2', 'project-1-custom-domain-2.com', '2023-01-01 00:00:00+00', 'INITIALIZING', '2023-01-01 00:00:00+00'); + ('project-1-custom-domain-1', '517cce32-9af3-project-1-custom-domain-1.com', '2023-01-01 00:00:00+00', 'INITIALIZING', '2023-01-01 00:00:00+00'), + ('project-1-custom-domain-2', '517cce32-9af3-project-1-custom-domain-2.com', '2023-01-01 00:00:00+00', 'INITIALIZING', '2023-01-01 00:00:00+00'); -- Establish relationships between projects and custom domains INSERT INTO "public"."ProjectDomain" ("projectId", "domainId", "createdAt", "txtRecord", "cname") @@ -76,7 +76,7 @@ VALUES '2023-01-02 00:00:00+00', 'home', 'project1', - '{"domains": ["some-other-domain.com", "project-1-custom-domain-1.com"]}'::text, + '{"domains": ["some-other-domain.com", "517cce32-9af3-project-1-custom-domain-1.com"]}'::text, '2023-01-02 00:00:00+00', 'PUBLISHED' ); @@ -113,7 +113,7 @@ VALUES '2024-01-02 00:00:00+00', 'home', 'project1', - '{"domains": ["some-other-domain.com", "project-1-custom-domain-1.com"]}'::text, + '{"domains": ["some-other-domain.com", "517cce32-9af3-project-1-custom-domain-1.com"]}'::text, '2024-01-02 00:00:00+00', 'PUBLISHED' ); @@ -150,7 +150,7 @@ VALUES '2024-01-03 00:00:00+00', 'home', 'project1', - '{"domains": ["some-other-domain.com", "project-1-custom-domain-2.com"]}'::text, + '{"domains": ["some-other-domain.com", "517cce32-9af3-project-1-custom-domain-2.com"]}'::text, '2024-01-03 00:00:00+00', 'PUBLISHED' ); @@ -187,7 +187,7 @@ VALUES '2024-01-04 00:00:00+00', 'home', 'project1', - '{"domains": ["some-other-domain.com", "project-1-custom-domain-2.com", "project-1-custom-domain-1.com"]}'::text, + '{"domains": ["some-other-domain.com", "517cce32-9af3-project-1-custom-domain-2.com", "517cce32-9af3-project-1-custom-domain-1.com"]}'::text, '2024-01-04 00:00:00+00', 'PUBLISHED' ); @@ -230,7 +230,7 @@ VALUES '2025-01-04 00:00:00+00', 'home', 'project2', - '{"domains": ["some-other-domain.com", "project-1-custom-domain-2.com", "project-1-custom-domain-1.com"]}'::text, + '{"domains": ["some-other-domain.com", "517cce32-9af3-project-1-custom-domain-2.com", "517cce32-9af3-project-1-custom-domain-1.com"]}'::text, '2025-01-04 00:00:00+00', 'PUBLISHED' ); diff --git a/packages/postgrest/supabase/tests/latest-builds-projects.sql b/packages/postgrest/supabase/tests/latest-builds-projects.sql index dc183cb0377b..631d302e7445 100644 --- a/packages/postgrest/supabase/tests/latest-builds-projects.sql +++ b/packages/postgrest/supabase/tests/latest-builds-projects.sql @@ -16,8 +16,8 @@ VALUES -- Insert projects associated with the user INSERT INTO "public"."Project" ("id", "title", "domain", "userId", "isDeleted", "createdAt") VALUES - ('project1', 'Project One', 'project1-domain1', 'user1', false, '2023-01-01 00:00:00+00'), - ('project2', 'Project Two', 'project2-domain1', 'user1', false, '2023-01-01 00:00:00+00'); + ('project1', 'Project One', '517cce32-9af3-project1-domain1', 'user1', false, '2023-01-01 00:00:00+00'), + ('project2', 'Project Two', '517cce32-9af3-project2-domain1', 'user1', false, '2023-01-01 00:00:00+00'); -- Insert builds with different deployment formats INSERT INTO "public"."Build" ( @@ -36,7 +36,7 @@ VALUES '2023-01-01 00:00:00+00', 'home', 'project1', - '{"projectDomain": "project1-domain1", "domains": [""]}'::text, + '{"projectDomain": "517cce32-9af3-project1-domain1", "domains": [""]}'::text, '2023-01-01 00:00:00+00', 'PUBLISHED' ), @@ -45,7 +45,7 @@ VALUES '2022-01-01 00:00:00+00', 'home', 'project1', - '{"projectDomain": "project1-domain1", "domains": [""]}'::text, + '{"projectDomain": "517cce32-9af3-project1-domain1", "domains": [""]}'::text, '2022-01-01 00:00:00+00', 'PUBLISHED' ), @@ -64,7 +64,7 @@ VALUES '2023-01-02 00:00:00+00', 'home', 'project2', - '{"domains": ["project2-domain1"]}'::text, + '{"domains": ["517cce32-9af3-project2-domain1"]}'::text, '2023-01-02 00:00:00+00', 'PENDING' ), @@ -73,7 +73,7 @@ VALUES '2022-01-02 00:00:00+00', 'home', 'project2', - '{"domains": ["project2-domain1"]}'::text, + '{"domains": ["517cce32-9af3-project2-domain1"]}'::text, '2022-01-02 00:00:00+00', 'PENDING' ); @@ -83,7 +83,7 @@ VALUES -------------------------------------------------------------------------------- SELECT is ( ( - SELECT "buildId" + SELECT ARRAY["buildId", "domain"] FROM "public"."latestBuildVirtual"( ( SELECT (p.*)::"Project" @@ -92,8 +92,24 @@ SELECT is ( ) ) ), - 'build1', - 'Test Case 1: Should return the latest build for project1 with domain matching projectDomain.' + ARRAY['build1', '517cce32-9af3-project1-domain1'], + 'Test Case 1.1: Should return the latest build for project1 with domain matching projectDomain.' +); + + +SELECT is ( + ( + SELECT ARRAY["buildId", "domain"] + FROM "public"."latestProjectDomainBuildVirtual"( + ( + SELECT (p.*)::"Project" + FROM "public"."Project" p + WHERE p."id" = 'project1' + ) + ) + ), + ARRAY['build1', '517cce32-9af3-project1-domain1'], + 'Test Case 1.2: Should return the latest build for project1 with domain matching projectDomain.' ); -------------------------------------------------------------------------------- @@ -111,9 +127,23 @@ SELECT is ( ) ), 'build2', - 'Test Case 2: Should return the latest build for project2 with domain present in domains array.' + 'Test Case 2.1: Should return the latest build for project2 with domain present in domains array.' ); +SELECT is ( + ( + SELECT "buildId" + FROM "public"."latestProjectDomainBuildVirtual"( + ( + SELECT (p.*)::"Project" + FROM "public"."Project" p + WHERE p."id" = 'project2' + ) + ) + ), + 'build2', + 'Test Case 2.2: Should return the latest build for project2 domain with domain present in domains array.' +); -------------------------------------------------------------------------------- -- Test Case 3: Update Project Domain and Verify No Build Exists for the New Domain -------------------------------------------------------------------------------- @@ -135,7 +165,22 @@ SELECT is ( ) ), 0, - 'Test Case 3: Should return 0 as no build exists for the updated domain project1-domain2.' + 'Test Case 3.1: Should return 0 as no build exists for the updated domain project1-domain2.' +); + +SELECT is ( + ( + SELECT COUNT(*)::integer + FROM "public"."latestProjectDomainBuildVirtual"( + ( + SELECT (p.*)::"Project" + FROM "public"."Project" p + WHERE p."id" = 'project1' + ) + ) + ), + 0, + 'Test Case 3.2: Should return 0 as no build exists for the updated domain project1-domain2.' ); -------------------------------------------------------------------------------- @@ -165,7 +210,7 @@ VALUES -- Verify that the latest build now reflects the updated domain SELECT is ( ( - SELECT "buildId" + SELECT ARRAY["buildId", "domain"] FROM "public"."latestBuildVirtual"( ( SELECT (p.*)::"Project" @@ -174,10 +219,26 @@ SELECT is ( ) ) ), - 'build1-for-domain2', - 'Test Case 4: Should return the latest build for project1 with the updated domain in domains array.' + ARRAY['build1-for-domain2','project1-domain2'], + 'Test Case 4.1: Should return the latest build for project1 with the updated domain in domains array.' ); +SELECT is ( + ( + SELECT ARRAY["buildId", "domain"] + FROM "public"."latestProjectDomainBuildVirtual"( + ( + SELECT (p.*)::"Project" + FROM "public"."Project" p + WHERE p."id" = 'project1' + ) + ) + ), + ARRAY['build1-for-domain2','project1-domain2'], + 'Test Case 4.2: Should return the latest build for project1 domain with the updated domain in domains array.' +); + + -------------------------------------------------------------------------------- -- Test Case 5: Register Custom Domains and Verify Latest Build for a Custom Domain -------------------------------------------------------------------------------- @@ -190,8 +251,8 @@ INSERT INTO "public"."Domain" ( "updatedAt" ) VALUES - ('project-1-custom-domain-1', 'project-1-custom-domain-1.com', '2023-01-01 00:00:00+00', 'INITIALIZING', '2023-01-01 00:00:00+00'), - ('project-1-custom-domain-2', 'project-1-custom-domain-2.com', '2023-01-01 00:00:00+00', 'INITIALIZING', '2023-01-01 00:00:00+00'); + ('project-1-custom-domain-1', '517cce32-9af3-project-1-custom-domain-1.com', '2023-01-01 00:00:00+00', 'INITIALIZING', '2023-01-01 00:00:00+00'), + ('project-1-custom-domain-2', '517cce32-9af3-project-1-custom-domain-2.com', '2023-01-01 00:00:00+00', 'INITIALIZING', '2023-01-01 00:00:00+00'); -- Establish relationships between project1 and custom domains INSERT INTO "public"."ProjectDomain" ( @@ -221,7 +282,7 @@ VALUES '2023-01-02 00:00:00+00', 'home', 'project1', - '{"domains": ["some-other-domain.com", "project-1-custom-domain-1.com"]}'::text, + '{"domains": ["some-other-domain.com", "517cce32-9af3-project-1-custom-domain-1.com"]}'::text, '2023-01-02 00:00:00+00', 'PUBLISHED' ); @@ -229,7 +290,7 @@ VALUES -- Verify that the latest build reflects the custom domain association SELECT is ( ( - SELECT "buildId" + SELECT ARRAY["buildId", "domain"] FROM "public"."latestBuildVirtual"( ( SELECT (p.*)::"Project" @@ -238,8 +299,26 @@ SELECT is ( ) ) ), - 'build1-for-custom-domain-1', - 'Test Case 5: Should return the latest build for project1 with a registered custom domain in domains array.' + ARRAY['build1-for-custom-domain-1','517cce32-9af3-project-1-custom-domain-1.com'], + 'Test Case 5.1: Should return the latest build for project1 with a registered custom domain in domains array.' +); + + +-- Ensure the latest project domain build has not changed +-- The difference between latestProjectDomainBuildVirtual and latestBuildVirtual is that the first returns data only for the project domain +SELECT is ( + ( + SELECT ARRAY["buildId", "domain"] + FROM "public"."latestProjectDomainBuildVirtual"( + ( + SELECT (p.*)::"Project" + FROM "public"."Project" p + WHERE p."id" = 'project1' + ) + ) + ), + ARRAY['build1-for-domain2','project1-domain2'], + 'Test Case 5.2: Should return the latest build for project1 domain and not affected by custom domains' ); -------------------------------------------------------------------------------- @@ -261,7 +340,7 @@ VALUES '2023-01-03 00:00:00+00', 'home', 'project1', - '{"domains": ["project1-domain2"]}'::text, + '{"domains": ["517cce32-9af3-project-1-custom-domain-1.com", "project1-domain2"]}'::text, '2023-01-03 00:00:00+00', 'PUBLISHED' ); @@ -269,7 +348,7 @@ VALUES -- Verify that the latest build reflects the preview domain SELECT is ( ( - SELECT "buildId" + SELECT ARRAY["buildId", "domain"] FROM "public"."latestBuildVirtual"( ( SELECT (p.*)::"Project" @@ -278,10 +357,26 @@ SELECT is ( ) ) ), - 'build1-for-domain2-new', - 'Test Case 6: Should return the latest build for project1 with the preview domain in domains array.' + ARRAY['build1-for-domain2-new', 'project1-domain2'], + 'Test Case 6.1: Should return the latest build for project1 with the preview domain in domains array.' +); + +SELECT is ( + ( + SELECT ARRAY["buildId", "domain"] + FROM "public"."latestProjectDomainBuildVirtual"( + ( + SELECT (p.*)::"Project" + FROM "public"."Project" p + WHERE p."id" = 'project1' + ) + ) + ), + ARRAY['build1-for-domain2-new', 'project1-domain2'], + 'Test Case 6.2: Should return the latest build for project1 with the preview domain in domains array.' ); + -------------------------------------------------------------------------------- -- Test Case 7: Publish a New Build for a Custom Domain, Delete the Custom Domain, and Verify Latest Build Update -------------------------------------------------------------------------------- @@ -301,7 +396,7 @@ VALUES '2023-01-04 00:00:00+00', 'home', 'project1', - '{"domains": ["some-other-domain.com", "project-1-custom-domain-1.com"]}'::text, + '{"domains": ["some-other-domain.com", "517cce32-9af3-project-1-custom-domain-1.com"]}'::text, '2023-01-04 00:00:00+00', 'PUBLISHED' ); diff --git a/packages/postgrest/supabase/tests/project-domains.sql b/packages/postgrest/supabase/tests/project-domains.sql index b1120da71f9a..08ca2d746756 100644 --- a/packages/postgrest/supabase/tests/project-domains.sql +++ b/packages/postgrest/supabase/tests/project-domains.sql @@ -8,19 +8,19 @@ SELECT no_plan(); -- We're inserting user_1 as the user for the test projects INSERT INTO "public"."User" ("id", "createdAt", "email", "username") VALUES - ('user_1', '2023-01-01 00:00:00+00', 'user1@example.com', 'user1'); + ('user_1', '2023-01-01 00:00:00+00', 'user1@517cce32-9af3-example.com', 'user1'); -- Insert test projects into the Project table -- project_1 and project_2 belong to user_1 and are not deleted (isDeleted = false) INSERT INTO "Project" (id, title, domain, "userId", "isDeleted") VALUES -('project_1', 'Test Project 1', 'testproject1.com', 'user_1', false), -('project_2', 'Test Project 1', 'testproject2.com', 'user_1', false); +('project_1', 'Test Project 1', '517cce32-9af3-testproject1.com', 'user_1', false), +('project_2', 'Test Project 1', '517cce32-9af3-testproject2.com', 'user_1', false); -- Insert test domains into the Domain table --- We are inserting two domains: example.com and example.org with different statuses +-- We are inserting two domains: 517cce32-9af3-example.com and 517cce32-9af3-example.org with different statuses INSERT INTO "Domain" (id, domain, status, "txtRecord") VALUES -('domain_1', 'example.com', 'INITIALIZING', 'txtRecord1'), -('domain_2', 'example.org', 'ACTIVE', 'txtRecord21'); +('domain_1', '517cce32-9af3-example.com', 'INITIALIZING', 'txtRecord1'), +('domain_2', '517cce32-9af3-example.org', 'ACTIVE', 'txtRecord21'); -- Insert test data into the ProjectDomain table -- Mapping domains to projects, project_1 has two domains, project_2 has one domain @@ -43,8 +43,8 @@ SELECT results_eq( $$ SELECT * FROM ( VALUES - ('example.com','INITIALIZING'::"DomainStatus",NULL,E'txtRecord1',E'txtRecord1',TRUE), -- Verified domain (TXT records match) - ('example.org','ACTIVE'::"DomainStatus",NULL,E'txtRecord21',E'txtRecord22',FALSE) -- Not verified domain (TXT records do not match) + ('517cce32-9af3-example.com','INITIALIZING'::"DomainStatus",NULL,E'txtRecord1',E'txtRecord1',TRUE), -- Verified domain (TXT records match) + ('517cce32-9af3-example.org','ACTIVE'::"DomainStatus",NULL,E'txtRecord21',E'txtRecord22',FALSE) -- Not verified domain (TXT records do not match) ) AS expected(domain, status, error, "domainTxtRecord", "expectedTxtRecord", verified) ORDER BY "domain" $$, @@ -63,7 +63,7 @@ SELECT results_eq( $$ SELECT * FROM ( VALUES - ('example.com','INITIALIZING'::"DomainStatus",NULL,E'txtRecord1',E'txtRecord3',FALSE) -- Not verified domain (TXT records do not match) + ('517cce32-9af3-example.com','INITIALIZING'::"DomainStatus",NULL,E'txtRecord1',E'txtRecord3',FALSE) -- Not verified domain (TXT records do not match) ) AS expected(domain, status, error, "domainTxtRecord", "expectedTxtRecord", verified) ORDER BY "domain" $$, diff --git a/packages/prisma-client/prisma/migrations/20240920091253_domain-ordering/migration.sql b/packages/prisma-client/prisma/migrations/20240920091253_domain-ordering/migration.sql new file mode 100644 index 000000000000..78ead59d2f29 --- /dev/null +++ b/packages/prisma-client/prisma/migrations/20240920091253_domain-ordering/migration.sql @@ -0,0 +1,34 @@ +-- Create the "domainsVirtual" function to return all domain-related data for a specific project. +CREATE OR REPLACE FUNCTION "domainsVirtual"("Project") +RETURNS SETOF "domainsVirtual" AS $$ + -- This function retrieves all the domain information associated with a specific project by joining the Domain and ProjectDomain tables. + -- It returns a result set conforming to the "domainsVirtual" structure, including fields such as domain status, error, verification status, etc. + SELECT + "Domain".id || '-' || "ProjectDomain"."projectId" as id, + "Domain".id AS "domainId", -- Domain ID from Domain table + "ProjectDomain"."projectId", -- Project ID from ProjectDomain table + "Domain".domain, -- Domain name + "Domain".status, -- Current domain status + "Domain".error, -- Error message, if any + "Domain"."txtRecord" AS "domainTxtRecord", -- Current TXT record from Domain table + "ProjectDomain"."txtRecord" AS "expectedTxtRecord", -- Expected TXT record from ProjectDomain table + "ProjectDomain"."cname" AS "cname", + CASE + WHEN "Domain"."txtRecord" = "ProjectDomain"."txtRecord" THEN true -- If TXT records match, domain is verified + ELSE false + END AS "verified", -- Boolean flag for verification status + "ProjectDomain"."createdAt", -- Creation timestamp from ProjectDomain table + "Domain"."updatedAt" -- Last updated timestamp from Domain table + FROM + "Domain" + JOIN + "ProjectDomain" ON "Domain".id = "ProjectDomain"."domainId" -- Joining Domain and ProjectDomain on domainId + WHERE + "ProjectDomain"."projectId" = $1.id -- Filtering by projectId passed as an argument to the function + ORDER BY "ProjectDomain"."createdAt", "Domain".id; -- Stable sort +$$ +STABLE +LANGUAGE sql; + +-- Add function-specific comments to explain its behavior. +COMMENT ON FUNCTION "domainsVirtual"("Project") IS 'Function that retrieves domain-related data for a given project by joining the Domain and ProjectDomain tables. It returns a result set that conforms to the structure defined in the domainsVirtual virtual table.'; diff --git a/packages/prisma-client/prisma/migrations/20240924174536_cleanup/migration.sql b/packages/prisma-client/prisma/migrations/20240924174536_cleanup/migration.sql new file mode 100644 index 000000000000..a9f7496a1a6d --- /dev/null +++ b/packages/prisma-client/prisma/migrations/20240924174536_cleanup/migration.sql @@ -0,0 +1,81 @@ +ALTER TABLE "Build" ADD COLUMN "isCleaned" BOOLEAN DEFAULT FALSE; + +-- PostgREST will use this function as a computed field +-- See: https://docs.postgrest.org/en/v12/references/api/resource_embedding.html#computed-relationships +CREATE OR REPLACE FUNCTION "latestProjectDomainBuildVirtual"("Project") +RETURNS SETOF "latestBuildVirtual" +ROWS 1 AS $$ -- The function is expected to return 1 row + +-- This function selects the latest build for a given project domain where: +-- 1. The "deployment" field is not NULL, ensuring it is a production build. +-- 2. The 'destination' field in the JSONB "deployment" is either NULL (for backward compatibility) +-- or equal to 'saas', indicating a non-static build. +-- 3. The selected "domain" must exist in the "Domain" table (many-to-many relation with "Project" via "ProjectDomain") +-- or it must match the "Project.domain" field directly. +-- 4. If 'projectDomain' exists in the JSONB "deployment", it is used as the "domain". +-- If not, the first element of 'domains' in the JSONB "deployment" array is used as the "domain". +-- The function returns the most recent (by "createdAt") valid build. +SELECT + b.id AS "buildId", + b."projectId", + '' as "domainsVirtualId", + p.domain AS "domain", + b."createdAt", + b."publishStatus" +FROM "Build" b +JOIN "Project" p ON b."projectId" = p.id +LEFT JOIN "ProjectDomain" pd ON pd."projectId" = p.id +WHERE b."projectId" = $1.id + AND b.deployment IS NOT NULL + -- 'destination' IS NULL for backward compatibility; 'destination' = 'saas' for non-static builds + AND ((b.deployment::jsonb ->> 'destination') IS NULL OR (b.deployment::jsonb ->> 'destination') = 'saas') + AND ( + -- Check if 'projectDomain' matches p.domain + (b.deployment::jsonb ->> 'projectDomain') = p.domain + -- Check if 'domains' contains p.domain or d.domain + OR (b.deployment::jsonb -> 'domains') @> to_jsonb(array[p.domain]) + ) +ORDER BY b."createdAt" DESC +LIMIT 1; +$$ +STABLE +LANGUAGE sql; + +-- Comment on the function to provide additional context +COMMENT ON FUNCTION "latestProjectDomainBuildVirtual"("Project") IS 'This function computes the latest build for a project domain, ensuring it is a production (non-static) build, where the domain matches either the Project.domain field or exists in the related Domain table. It provides backward compatibility for older records with a missing "destination" field.'; + + + +CREATE OR REPLACE FUNCTION database_cleanup( + from_date timestamp DEFAULT '2020-01-01 00:00:00', + to_date timestamp DEFAULT '2099-12-31 23:59:59' -- SQL should die long before this date! +) RETURNS VOID AS $$ +BEGIN + WITH latest_builds AS ( + SELECT "buildId" FROM "Project" p, LATERAL "latestProjectDomainBuildVirtual"(p) + UNION + SELECT "buildId" FROM "Project" p, LATERAL "latestBuildVirtual"(p) + UNION + SELECT lb."buildId" + FROM "Project" p, LATERAL "domainsVirtual"(p) dv, LATERAL "latestBuildVirtual"(dv) lb + ) + UPDATE "Build" + SET + "styleSources" = '[]'::text, + styles = '[]'::text, + breakpoints = '[]'::text, + "styleSourceSelections" = '[]'::text, + props = '[]'::text, + instances = '[]'::text, + "dataSources" = '[]'::text, + resources = '[]'::text, + "marketplaceProduct" = '{}'::text, + "isCleaned" = TRUE + WHERE deployment IS NOT NULL + AND id NOT IN (SELECT "buildId" FROM latest_builds) + AND "isCleaned" = FALSE + AND "createdAt" BETWEEN from_date AND to_date; -- Filter by date range (for testing purposes) +END; +$$ LANGUAGE plpgsql; + +-- SELECT database_cleanup(); diff --git a/packages/sdk/src/schema/deployment.ts b/packages/sdk/src/schema/deployment.ts index a41b1e0bccfc..bb61b61833c8 100644 --- a/packages/sdk/src/schema/deployment.ts +++ b/packages/sdk/src/schema/deployment.ts @@ -23,7 +23,12 @@ export const Deployment = z.union([ z.object({ destination: z.literal("saas").optional(), domains: z.array(z.string()), - projectDomain: z.string(), + assetsDomain: z.string().optional(), + /** + * @deprecated This field is deprecated, use `domains` instead. + */ + projectDomain: z.string().optional(), + excludeWstdDomainFromSearch: z.boolean().optional(), }), ]); diff --git a/packages/trpc-interface/src/shared/deployment.ts b/packages/trpc-interface/src/shared/deployment.ts index 6e24ad5ce75d..2dc10dc169e8 100644 --- a/packages/trpc-interface/src/shared/deployment.ts +++ b/packages/trpc-interface/src/shared/deployment.ts @@ -1,7 +1,8 @@ import { z } from "zod"; import { router, procedure } from "./trpc"; -const PublishInput = z.object({ +// Has corresponding type in saas +export const PublishInput = z.object({ // used to load build data from the builder see routes/rest.build.$buildId.ts buildId: z.string(), builderOrigin: z.string(), @@ -11,10 +12,10 @@ const PublishInput = z.object({ // preview support branchName: z.string(), // action log helper (not used for deployment, but for action logs readablity) - projectDomainName: z.string(), + logProjectName: z.string(), }); -const Output = z.discriminatedUnion("success", [ +export const Output = z.discriminatedUnion("success", [ z.object({ success: z.literal(true), }),