diff --git a/apps/builder/app/builder/features/topbar/domains.tsx b/apps/builder/app/builder/features/topbar/domains.tsx index 025dff107e..a770d746aa 100644 --- a/apps/builder/app/builder/features/topbar/domains.tsx +++ b/apps/builder/app/builder/features/topbar/domains.tsx @@ -67,11 +67,14 @@ const getCname = (domain: string) => { return cnameArray.join("."); }; -const getStatusText = (props: { projectDomain: Domain }) => { - const status = props.projectDomain.verified - ? (`VERIFIED_${props.projectDomain.domain.status}` as `VERIFIED_${DomainStatus}`) +export const getStatus = (projectDomain: Domain) => + projectDomain.verified + ? (`VERIFIED_${projectDomain.domain.status}` as const) : `UNVERIFIED`; +const getStatusText = (props: { projectDomain: Domain }) => { + const status = getStatus(props.projectDomain); + let isVerifiedActive = false; let text = "Something went wrong"; diff --git a/apps/builder/app/builder/features/topbar/publish.tsx b/apps/builder/app/builder/features/topbar/publish.tsx index 5dffe0f673..15a88f6712 100644 --- a/apps/builder/app/builder/features/topbar/publish.tsx +++ b/apps/builder/app/builder/features/topbar/publish.tsx @@ -23,7 +23,7 @@ import { getPublishedUrl } from "~/shared/router-utils"; import { theme } from "@webstudio-is/design-system"; import { useAuthPermit } from "~/shared/nano-states"; import { isFeatureEnabled } from "@webstudio-is/feature-flags"; -import { Domains } from "./domains"; +import { Domains, getStatus } from "./domains"; import { CollapsibleDomainSection } from "./collapsible-domain-section"; import { CheckCircleIcon, @@ -207,7 +207,13 @@ const ChangeProjectDomain = (props: PublishButtonProps) => { ); }; -const Publish = ({ projectId }: { projectId: Project["id"] }) => { +const Publish = ({ + projectId, + domains, +}: { + projectId: Project["id"]; + domains: string[]; +}) => { const { send: publish, state: publishState, @@ -239,7 +245,7 @@ const Publish = ({ projectId }: { projectId: Project["id"] }) => { color="positive" disabled={publishState !== "idle"} onClick={() => { - publish({ projectId }); + publish({ projectId, domains }); }} > Publish @@ -260,6 +266,16 @@ const Content = (props: PublishButtonProps) => { refreshDomainResult({ projectId: props.project.id }); }, [refreshDomainResult, props.project.id]); + // In the future we would allow to select what domains to publish, + // now we just publish all verified and active domains + const domainsToPublish = domainsResult?.success + ? domainsResult.data + .filter( + (projectDomain) => getStatus(projectDomain) === "VERIFIED_ACTIVE" + ) + .map((projectDomain) => projectDomain.domain.domain) + : []; + return ( <> @@ -308,7 +324,7 @@ const Content = (props: PublishButtonProps) => { )} - + ); }; diff --git a/apps/builder/app/routes/rest.build.$buildId.tsx b/apps/builder/app/routes/rest.build.$buildId.tsx index 9820714ee8..9d010d4350 100644 --- a/apps/builder/app/routes/rest.build.$buildId.tsx +++ b/apps/builder/app/routes/rest.build.$buildId.tsx @@ -21,7 +21,7 @@ export const loader = async ({ throw json("Required project id", { status: 400 }); } - const context = await createContext(request, "prod"); + const context = await createContext(request); const pagesCanvasData = await loadProductionCanvasData(buildId, context); diff --git a/apps/builder/app/shared/context.server.ts b/apps/builder/app/shared/context.server.ts index ccf25b2f61..813e9721a5 100644 --- a/apps/builder/app/shared/context.server.ts +++ b/apps/builder/app/shared/context.server.ts @@ -4,8 +4,7 @@ import { authenticator } from "~/services/auth.server"; import { trpcClient } from "~/services/trpc.server"; const createAuthorizationContext = async ( - request: Request, - buildEnv: AppContext["authorization"]["buildEnv"] + request: Request ): Promise => { const url = new URL(request.url); @@ -13,10 +12,14 @@ const createAuthorizationContext = async ( const user = await authenticator.isAuthenticated(request); + const isServiceCall = + request.headers.has("Authorization") && + request.headers.get("Authorization") === env.TRPC_SERVER_API_TOKEN; + const context: AppContext["authorization"] = { userId: user?.id, authToken, - buildEnv, + isServiceCall, authorizeTrpc: trpcClient.authorize, }; @@ -48,11 +51,8 @@ const createDeploymentContext = (request: Request) => { /** * argument buildEnv==="prod" only if we are loading project with production build */ -export const createContext = async ( - request: Request, - buildEnv: AppContext["authorization"]["buildEnv"] = "dev" -): Promise => { - const authorization = await createAuthorizationContext(request, buildEnv); +export const createContext = async (request: Request): Promise => { + const authorization = await createAuthorizationContext(request); const domain = createDomainContext(request); const deployment = createDeploymentContext(request); diff --git a/apps/builder/app/shared/db/canvas.server.ts b/apps/builder/app/shared/db/canvas.server.ts index e1ee794a44..0af85715f4 100644 --- a/apps/builder/app/shared/db/canvas.server.ts +++ b/apps/builder/app/shared/db/canvas.server.ts @@ -4,6 +4,7 @@ import { loadAssetsByProject } from "@webstudio-is/asset-uploader/index.server"; import type { AppContext } from "@webstudio-is/trpc-interface/index.server"; import { findPageByIdOrPath } from "@webstudio-is/project-build"; import type { Build } from "@webstudio-is/prisma-client"; +import { db as domainDb } from "@webstudio-is/domain/index.server"; export const loadProductionCanvasData = async ( buildId: Build["id"], @@ -15,6 +16,34 @@ export const loadProductionCanvasData = async ( throw new Error("The project is not published"); } + const { deployment } = build; + + if (deployment === undefined) { + throw new Error("The project is not published"); + } + + const currentProjectDomainsResult = await domainDb.findMany( + { projectId: build.projectId }, + context + ); + + if (currentProjectDomainsResult.success === false) { + throw new Error(currentProjectDomainsResult.error); + } + + const currentProjectDomains = currentProjectDomainsResult.data; + + // Check that build deployment domains are still active and verified + // for examle: redeploy created few days later + const domains = deployment.domains.filter((domain) => + currentProjectDomains.some( + (projectDomain) => + projectDomain.domain.domain === domain && + projectDomain.domain.status === "ACTIVE" && + projectDomain.verified + ) + ); + const page = findPageByIdOrPath(build.pages, "/"); if (page === undefined) { @@ -24,7 +53,13 @@ export const loadProductionCanvasData = async ( const allAssets = await loadAssetsByProject(build.projectId, context); const canvasData = { - build, + build: { + ...build, + deployment: { + ...deployment, + domains, + }, + }, page, pages: [build.pages.homePage, ...build.pages.pages], }; diff --git a/packages/domain/src/db/domain.ts b/packages/domain/src/db/domain.ts index 3597974131..b5c52a4841 100644 --- a/packages/domain/src/db/domain.ts +++ b/packages/domain/src/db/domain.ts @@ -32,7 +32,7 @@ export const findMany = async ( > => { // Only builder of the project can list domains const canList = await authorizeProject.hasProjectPermit( - { projectId: props.projectId, permit: "build" }, + { projectId: props.projectId, permit: "view" }, context ); diff --git a/packages/domain/src/index.server.ts b/packages/domain/src/index.server.ts index 7caf469ef2..de6f4b1d04 100644 --- a/packages/domain/src/index.server.ts +++ b/packages/domain/src/index.server.ts @@ -1 +1,2 @@ export * from "./trpc"; +export * from "./db"; diff --git a/packages/domain/src/trpc/domain.ts b/packages/domain/src/trpc/domain.ts index 60a24c6328..e4b2b890a9 100644 --- a/packages/domain/src/trpc/domain.ts +++ b/packages/domain/src/trpc/domain.ts @@ -25,14 +25,14 @@ export const domainRouter = router({ } }), publish: procedure - .input(z.object({ projectId: z.string() })) + .input(z.object({ projectId: z.string(), domains: z.array(z.string()) })) .mutation(async ({ input, ctx }) => { try { const build = await createProductionBuild( { projectId: input.projectId, deployment: { - domains: [], + domains: input.domains, }, }, ctx diff --git a/packages/project-build/src/db/build.ts b/packages/project-build/src/db/build.ts index d985cd85b7..8a25817859 100644 --- a/packages/project-build/src/db/build.ts +++ b/packages/project-build/src/db/build.ts @@ -188,6 +188,8 @@ export const cloneBuild = async ( const data = { ...build, id: undefined, + createdAt: undefined, + updatedAt: undefined, deployment: props.deployment ? serializeDeployment(props.deployment) : undefined, diff --git a/packages/trpc-interface/src/authorize/project.server.ts b/packages/trpc-interface/src/authorize/project.server.ts index 55711594e6..4dde10c037 100644 --- a/packages/trpc-interface/src/authorize/project.server.ts +++ b/packages/trpc-interface/src/authorize/project.server.ts @@ -47,7 +47,7 @@ export const hasProjectPermit = async ( const namespace = "Project"; // Allow load production build env i.e. "published" site - if (props.permit === "view" && context.authorization.buildEnv === "prod") { + if (props.permit === "view" && context.authorization.isServiceCall) { return true; } diff --git a/packages/trpc-interface/src/context/context.server.ts b/packages/trpc-interface/src/context/context.server.ts index 101f2c809c..aa9f327397 100644 --- a/packages/trpc-interface/src/context/context.server.ts +++ b/packages/trpc-interface/src/context/context.server.ts @@ -15,9 +15,9 @@ type AuthorizationContext = { authToken: string | undefined; /** - * buildEnv==="prod" only if we are loading project with production build + * Allow service 2 service communications to skip authorization for view calls */ - buildEnv: "dev" | "prod"; + isServiceCall: boolean; // Pass trpcClient through context as only main app can initialize it authorizeTrpc: TrpcInterfaceClient["authorize"];